├── scripts ├── nginx │ ├── sites-enabled │ │ └── default │ ├── modules-enabled │ │ ├── 50-mod-mail.conf │ │ ├── 50-mod-stream.conf │ │ ├── 50-mod-http-geoip.conf │ │ ├── 50-mod-http-xslt-filter.conf │ │ └── 50-mod-http-image-filter.conf │ ├── proxy_params │ ├── snippets │ │ ├── snakeoil.conf │ │ └── fastcgi-php.conf │ ├── scgi_params │ ├── uwsgi_params │ ├── fastcgi_params │ ├── fastcgi.conf │ ├── nginx.conf │ ├── koi-win │ ├── koi-utf │ ├── win-utf │ ├── sites-available │ │ └── default │ └── mime.types └── systemd │ └── dappswin.service ├── app ├── mine.go ├── rank.go ├── ws.go ├── lucky.go ├── types.go ├── news.go ├── arena.go ├── routers.go ├── vip.go ├── message.go ├── bonus.go ├── ico_test.go ├── ico.go ├── win.go ├── game.go ├── block.go ├── ws_hub.go ├── staking.go ├── tx.go ├── user.go ├── bettimes.go ├── eos.go └── wait.go ├── docs ├── lotter-1.png ├── lottery-2.png ├── 2019-01-06-11-42-40.png ├── 2019-01-06-11-43-02.png ├── 2019-01-06-11-43-40.png ├── 2019-01-06-11-47-18.png ├── 2019-01-06-11-48-23.png ├── 2019-01-06-11-50-29.png ├── 2019-01-06-11-50-56.png ├── 2019-01-06-11-52-38.png ├── game.http ├── 游戏产品API说明.md └── platform.http ├── .gitignore ├── models ├── ico.go ├── staking.go ├── vip.go ├── game.go ├── block.go ├── user.go └── tx.go ├── common ├── version.go └── types.go ├── runner.conf ├── logs └── logs.go ├── install.sh ├── database └── db.go ├── conf ├── config.go └── news.json ├── dappswin.go ├── Gopkg.toml ├── dappswin.toml ├── Readme.md ├── Makefile ├── views └── index.html └── Gopkg.lock /scripts/nginx/sites-enabled/default: -------------------------------------------------------------------------------- 1 | /etc/nginx/sites-available/default -------------------------------------------------------------------------------- /app/mine.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // 67%用于挖矿 0.2CGG per EOS 4 | // 每次总量减少5%, 单个EOS产生减少25% 5 | -------------------------------------------------------------------------------- /docs/lotter-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/lotter-1.png -------------------------------------------------------------------------------- /docs/lottery-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/lottery-2.png -------------------------------------------------------------------------------- /scripts/nginx/modules-enabled/50-mod-mail.conf: -------------------------------------------------------------------------------- 1 | /usr/share/nginx/modules-available/mod-mail.conf -------------------------------------------------------------------------------- /scripts/nginx/modules-enabled/50-mod-stream.conf: -------------------------------------------------------------------------------- 1 | /usr/share/nginx/modules-available/mod-stream.conf -------------------------------------------------------------------------------- /scripts/nginx/modules-enabled/50-mod-http-geoip.conf: -------------------------------------------------------------------------------- 1 | /usr/share/nginx/modules-available/mod-http-geoip.conf -------------------------------------------------------------------------------- /docs/2019-01-06-11-42-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/2019-01-06-11-42-40.png -------------------------------------------------------------------------------- /docs/2019-01-06-11-43-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/2019-01-06-11-43-02.png -------------------------------------------------------------------------------- /docs/2019-01-06-11-43-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/2019-01-06-11-43-40.png -------------------------------------------------------------------------------- /docs/2019-01-06-11-47-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/2019-01-06-11-47-18.png -------------------------------------------------------------------------------- /docs/2019-01-06-11-48-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/2019-01-06-11-48-23.png -------------------------------------------------------------------------------- /docs/2019-01-06-11-50-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/2019-01-06-11-50-29.png -------------------------------------------------------------------------------- /docs/2019-01-06-11-50-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/2019-01-06-11-50-56.png -------------------------------------------------------------------------------- /docs/2019-01-06-11-52-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping378/dappswin/master/docs/2019-01-06-11-52-38.png -------------------------------------------------------------------------------- /scripts/nginx/modules-enabled/50-mod-http-xslt-filter.conf: -------------------------------------------------------------------------------- 1 | /usr/share/nginx/modules-available/mod-http-xslt-filter.conf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dappswin 2 | lastupdate.tmp 3 | .vscode 4 | nodeos.log 5 | vendor/ 6 | tmp/ 7 | build/ 8 | *.tar.gz 9 | *.pdf -------------------------------------------------------------------------------- /scripts/nginx/modules-enabled/50-mod-http-image-filter.conf: -------------------------------------------------------------------------------- 1 | /usr/share/nginx/modules-available/mod-http-image-filter.conf -------------------------------------------------------------------------------- /scripts/nginx/proxy_params: -------------------------------------------------------------------------------- 1 | proxy_set_header Host $http_host; 2 | proxy_set_header X-Real-IP $remote_addr; 3 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 4 | proxy_set_header X-Forwarded-Proto $scheme; 5 | -------------------------------------------------------------------------------- /scripts/nginx/snippets/snakeoil.conf: -------------------------------------------------------------------------------- 1 | # Self signed certificates generated by the ssl-cert package 2 | # Don't use them in a production server! 3 | 4 | ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; 5 | ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; 6 | -------------------------------------------------------------------------------- /scripts/systemd/dappswin.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=dappswin go server 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | Restart=always 8 | RestartSec=30s 9 | ExecStart=/usr/local/sbin/dappswin 10 | 11 | [Install] 12 | WantedBy=default.target 13 | -------------------------------------------------------------------------------- /models/ico.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type ICO struct { 6 | gorm.Model 7 | Hash string `grom:"index"` 8 | Account string 9 | Amount uint64 10 | Status int 11 | TimeMills int64 12 | } 13 | 14 | func AddIcoRecord(m *ICO) error { 15 | d := db.Create(m) 16 | return d.Error 17 | } 18 | -------------------------------------------------------------------------------- /common/version.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/golang/glog" 7 | ) 8 | 9 | func Init() { 10 | glog.Info(fmt.Sprintf("Current version is %v (%v/%v)", CurrentVersion, BuildDate, BuildHash)) 11 | } 12 | 13 | var versions = []string{ 14 | "0.3", 15 | } 16 | 17 | var CurrentVersion string = versions[0] 18 | var BuildDate string 19 | var BuildHash string 20 | -------------------------------------------------------------------------------- /models/staking.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "dappswin/common" 5 | 6 | "github.com/shopspring/decimal" 7 | ) 8 | 9 | // Stake 质押的结构体 10 | type Stake struct { 11 | ID uint `grom:"PRIMARY_KEY" json:"-"` 12 | Name string `json:"name"` 13 | // 20,8, 整数部分取20位,小数部分支持8位 14 | Amount decimal.Decimal `json:"amount" sql:"type:decimal(20,8)"` 15 | // 赎回到账时间 16 | Date common.JSONTime `json:"date"` 17 | Status int `grom:"index" json:"status"` 18 | } 19 | -------------------------------------------------------------------------------- /runner.conf: -------------------------------------------------------------------------------- 1 | root: . 2 | tmp_path: ./build 3 | build_name: dappswin 4 | build_log: runner-build-errors.log 5 | valid_ext: .go, .tpl, .tmpl, .html, .json, *.toml 6 | no_rebuild_ext: .tpl, .tmpl, .html 7 | ignored: assets, tmp, vendor, build 8 | build_delay: 600 9 | colors: 1 10 | log_color_main: cyan 11 | log_color_build: yellow 12 | log_color_runner: green 13 | log_color_watcher: magenta 14 | log_color_app: 15 | -------------------------------------------------------------------------------- /scripts/nginx/snippets/fastcgi-php.conf: -------------------------------------------------------------------------------- 1 | # regex to split $uri to $fastcgi_script_name and $fastcgi_path 2 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 3 | 4 | # Check that the PHP script exists before passing it 5 | try_files $fastcgi_script_name =404; 6 | 7 | # Bypass the fact that try_files resets $fastcgi_path_info 8 | # see: http://trac.nginx.org/nginx/ticket/321 9 | set $path_info $fastcgi_path_info; 10 | fastcgi_param PATH_INFO $path_info; 11 | 12 | fastcgi_index index.php; 13 | include fastcgi.conf; 14 | -------------------------------------------------------------------------------- /models/vip.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type VIP struct { 4 | Level uint8 5 | Amount uint64 6 | Rebate float32 7 | } 8 | 9 | var vipInfo = []VIP{ 10 | {1, 0, 0}, 11 | {2, 10, 0.001}, 12 | {3, 100, 0.002}, 13 | {4, 1e3, 0.003}, 14 | {5, 1e4, 0.004}, 15 | {6, 1e5, 0.005}, 16 | {7, 1e6, 0.006}, 17 | {8, 1e7, 0.007}, 18 | {9, 1e8, 0.008}, 19 | } 20 | 21 | func getLevel(amount uint64) uint8 { 22 | 23 | for _, a := range vipInfo { 24 | if amount < a.Amount { 25 | return a.Level - 1 26 | } 27 | } 28 | return 9 29 | } 30 | -------------------------------------------------------------------------------- /logs/logs.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | func Init() { 8 | // glog need do this, let it don't write log file 9 | flag.Parse() 10 | flag.Set("logtostderr", "true") 11 | 12 | } 13 | 14 | // // GlogWriter serves as a bridge between the standard log package and the glog package. 15 | // type GlogWriter struct{} 16 | 17 | // // Write implements the io.Writer interface, Depth 4 is the caller current frame. 18 | // func (writer GlogWriter) Write(data []byte) (n int, err error) { 19 | // glog.InfoDepth(4, string(data)) 20 | // return len(data), nil 21 | // } 22 | -------------------------------------------------------------------------------- /scripts/nginx/scgi_params: -------------------------------------------------------------------------------- 1 | 2 | scgi_param REQUEST_METHOD $request_method; 3 | scgi_param REQUEST_URI $request_uri; 4 | scgi_param QUERY_STRING $query_string; 5 | scgi_param CONTENT_TYPE $content_type; 6 | 7 | scgi_param DOCUMENT_URI $document_uri; 8 | scgi_param DOCUMENT_ROOT $document_root; 9 | scgi_param SCGI 1; 10 | scgi_param SERVER_PROTOCOL $server_protocol; 11 | scgi_param REQUEST_SCHEME $scheme; 12 | scgi_param HTTPS $https if_not_empty; 13 | 14 | scgi_param REMOTE_ADDR $remote_addr; 15 | scgi_param REMOTE_PORT $remote_port; 16 | scgi_param SERVER_PORT $server_port; 17 | scgi_param SERVER_NAME $server_name; 18 | -------------------------------------------------------------------------------- /scripts/nginx/uwsgi_params: -------------------------------------------------------------------------------- 1 | 2 | uwsgi_param QUERY_STRING $query_string; 3 | uwsgi_param REQUEST_METHOD $request_method; 4 | uwsgi_param CONTENT_TYPE $content_type; 5 | uwsgi_param CONTENT_LENGTH $content_length; 6 | 7 | uwsgi_param REQUEST_URI $request_uri; 8 | uwsgi_param PATH_INFO $document_uri; 9 | uwsgi_param DOCUMENT_ROOT $document_root; 10 | uwsgi_param SERVER_PROTOCOL $server_protocol; 11 | uwsgi_param REQUEST_SCHEME $scheme; 12 | uwsgi_param HTTPS $https if_not_empty; 13 | 14 | uwsgi_param REMOTE_ADDR $remote_addr; 15 | uwsgi_param REMOTE_PORT $remote_port; 16 | uwsgi_param SERVER_PORT $server_port; 17 | uwsgi_param SERVER_NAME $server_name; 18 | -------------------------------------------------------------------------------- /app/rank.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type RankPerDay struct { 6 | ID int `json:"id,omitempty"` 7 | User string `json:"user,omitempty"` 8 | Amount float64 `json:"amount,omitempty"` 9 | Reward float64 `json:"reward,omitempty"` 10 | } 11 | 12 | func rankPerDay(c *gin.Context) { 13 | c.JSON(NewMsg(200, &[]RankPerDay{ 14 | {1, "wudixiaoping1", 200.1, 2}, 15 | {2, "xiaopingeos1", 100.1, 1}, 16 | {3, "aiaopingeos1", 90.1, 0.9}, 17 | {4, "biaopingeos1", 80.1, 0.8}, 18 | {5, "ciaopingeos1", 70.1, 0.7}, 19 | {6, "diaopingeos1", 60.1, 0.6}, 20 | {7, "eiaopingeos1", 50.1, 0.5}, 21 | {8, "fiaopingeos1", 40.1, 0.4}, 22 | {9, "giaopingeos1", 30.1, 0.3}, 23 | {10, "hiaopingeos1", 20.1, 0.2}, 24 | })) 25 | } 26 | -------------------------------------------------------------------------------- /app/ws.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | var Huber = newHub() 10 | 11 | // // Register register stats service 12 | // func WSRegister(router *gin.RouterGroup) { 13 | // router.GET("/ws", serveWs) 14 | // } 15 | 16 | // serveWs handles websocket requests from the peer. 17 | func serveWs(c *gin.Context) { 18 | conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) 19 | if err != nil { 20 | log.Println(err) 21 | return 22 | } 23 | client := &Client{hub: Huber, conn: conn, send: make(chan []byte, 256)} 24 | client.hub.register <- client 25 | 26 | // Allow collection of memory referenced by the caller by doing all work in 27 | // new goroutines. 28 | go client.writePump() 29 | // go client.readPump() 30 | } 31 | -------------------------------------------------------------------------------- /app/lucky.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type luckyPost struct { 6 | Name string `json:"name" binding:"required,max=12"` 7 | } 8 | 9 | type luckyRsp struct { 10 | LuckyCode int `json:"lucky_code"` 11 | Count int `json:"count"` 12 | IsAble bool `json:"is_able"` 13 | } 14 | 15 | func openLucky(c *gin.Context) { 16 | body := &luckyPost{} 17 | if err := c.ShouldBind(body); err != nil { 18 | c.JSON(NewMsg(400, "输入参数有误")) 19 | return 20 | } 21 | 22 | c.JSON(NewMsg(200, &luckyRsp{2, 2, true})) 23 | } 24 | 25 | func getLucky(c *gin.Context) { 26 | body := &luckyPost{} 27 | if err := c.ShouldBind(body); err != nil { 28 | c.JSON(NewMsg(400, "输入参数有误")) 29 | return 30 | } 31 | 32 | c.JSON(NewMsg(200, &luckyRsp{2, 1, true})) 33 | } 34 | -------------------------------------------------------------------------------- /models/game.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Game struct { 4 | Id int64 `gorm:"PRIMARY_KEY" json:"id"` 5 | Result string `gorm:"size:6" json:"result"` 6 | BlockNum uint32 `json:"blocknum"` 7 | TimeStamp int64 `json:"timestamp"` 8 | // 游戏属于的哪个分钟段的 9 | GameMintue int64 `json:"game_mintue"` 10 | Content string `json:"content"` 11 | } 12 | 13 | // AddGame insert a new Game into database and returns 14 | // last inserted Id on success. 15 | func AddGame(g *Game) (err error) { 16 | d := db.Create(g) 17 | return d.Error 18 | } 19 | 20 | // GetGameByMintue retrieves Game by Id. Returns error if 21 | // Id doesn't exist 22 | func GetGameByMintue(mintue int64) (v *Game, err error) { 23 | v = &Game{GameMintue: mintue} 24 | db.Where("GameMintue = ?", "mintue").First(&v) 25 | return v, db.Error 26 | } 27 | -------------------------------------------------------------------------------- /app/types.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/shopspring/decimal" 4 | 5 | // ws msg types 6 | const ( 7 | block = 0 8 | lotteryEOSBuy = 101 9 | lotteryEOSWin = 102 10 | lotteryGame = 103 11 | lotteryCGGBuy = 111 12 | lotteryCGGWin = 112 13 | luckyNumEosBuy = 201 14 | luckyNumEosWin = 211 15 | luckyNumGame = 213 16 | ) 17 | 18 | var wsTypes = map[string]int{ 19 | "lotteryEOSBuy": 101, 20 | "lotteryEOSWin": 102, 21 | "lotteryCGGBuy": 111, 22 | "lotteryCGGWin": 112, 23 | "lotteryCGGTotalVoted": 121, 24 | "lotteryEOSTotalVoted": 122, 25 | } 26 | 27 | var totalVotedEOS decimal.Decimal 28 | var totalVotedCGG decimal.Decimal 29 | 30 | const ( 31 | eos = iota 32 | cgg 33 | ) 34 | 35 | var coinIDs = map[string]int{ 36 | "EOS": eos, 37 | "CGG": cgg, 38 | } 39 | 40 | var coinNames = map[int]string{ 41 | eos: "EOS", 42 | cgg: "CGG", 43 | } 44 | -------------------------------------------------------------------------------- /app/news.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "dappswin/conf" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // News 轮播图信息 13 | type News struct { 14 | ID int `json:"id"` 15 | Language string `json:"language"` 16 | Subject string `json:"subject"` 17 | ImageSource string `json:"image_source"` 18 | ImageLink string `json:"image_link"` 19 | Date string `json:"date"` 20 | } 21 | 22 | func getNews(c *gin.Context) { 23 | 24 | data, err := ioutil.ReadFile(conf.C.GetString("general.newsSource")) 25 | if err != nil { 26 | c.JSON(NewMsg(http.StatusInternalServerError, "内部读取新闻错误")) 27 | return 28 | } 29 | news := &[]News{} 30 | if err := json.Unmarshal(data, news); err != nil { 31 | c.JSON(NewMsg(http.StatusInternalServerError, "内部读取新闻错误")) 32 | return 33 | } 34 | c.JSON(NewMsg(http.StatusOK, news)) 35 | } 36 | -------------------------------------------------------------------------------- /app/arena.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type Arena struct { 6 | ArenaAmount float64 `json:"arena_amount,omitempty"` 7 | TotalArenaSell float64 `json:"total_arena_sell,omitempty"` 8 | TodayArenaSell float64 `json:"today_arena_sell,omitempty"` 9 | TotalArenaBuy float64 `json:"total_arena_buy,omitempty"` 10 | TodayArenaBuy float64 `json:"today_arena_buy,omitempty"` 11 | Round int `json:"round,omitempty"` 12 | EndTime int `json:"end_time,omitempty"` 13 | LatestPrice float64 `json:"latest_price,omitempty"` 14 | LatestUser string `json:"latest_user,omitempty"` 15 | LatestTime string `json:"latest_time,omitempty"` 16 | } 17 | 18 | func arenaStatus(c *gin.Context) { 19 | c.JSON(NewMsg(200, &Arena{ 20 | 0.002, 21 | 231.2, 22 | 1.2, 23 | 200234, 24 | 123.2, 25 | 17, 26 | 1003234, 27 | 234.1, 28 | "wudipingeos2", 29 | "2019/01/12 09:34:34", 30 | })) 31 | } 32 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | set -u 5 | set -x 6 | 7 | downloadAndInstallEos() 8 | { 9 | apt update && apt install -y wget 10 | 11 | wget https://github.com/EOSIO/eos/releases/download/v1.5.2/eosio_1.5.2-1-ubuntu-18.04_amd64.deb 12 | 13 | dpkg -i eosio_1.5.2-1-ubuntu-18.04_amd64.deb || apt install -f -y && dpkg -i eosio_1.5.2-1-ubuntu-18.04_amd64.deb 14 | } 15 | 16 | type cleos >/dev/null 2>&1 || downloadAndInstallEos 17 | 18 | [ -f /usr/local/sbin/dappswin ] && rm -f /usr/local/sbin/dappswin && 19 | cp -f dappswin /usr/local/sbin/dappswin 20 | 21 | mkdir -p /etc/dappswin && [ -f /etc/dappswin/dappswin.toml ] || cp dappswin.toml /etc/dappswin/ 22 | 23 | [ -f /etc/systemd/system/dappswin.service ] && rm /etc/systemd/system/dappswin.service 24 | cp -f ./scripts/systemd/dappswin.service /etc/systemd/system/dappswin.service 25 | 26 | systemctl daemon-reload 27 | systemctl start dappswin 28 | systemctl enable dappswin 29 | -------------------------------------------------------------------------------- /app/routers.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | // Register 注册user相关路由 6 | func Register(router *gin.RouterGroup) { 7 | router.GET("/ws", serveWs) 8 | router.POST("/chain/get_currency_balance", getCurrencyBalance) 9 | 10 | // for platfom 11 | router.GET("/news", getNews) 12 | router.POST("/user/date", dateUser) 13 | router.POST("/user/page", pageUser) 14 | router.POST("/user/bind", bindUser) 15 | router.POST("/bonus/pool", bonusPool) 16 | router.GET("/bonus/stats", bonusStats) 17 | router.GET("/lock/total", locktotalStatus) 18 | router.POST("/lock/staked", stakedStatus) 19 | router.POST("/lock/unstake", unstake) 20 | router.POST("/lock/unstake/status", unstakeStatus) 21 | router.GET("/arena", arenaStatus) 22 | router.GET("/rank/stats_per_day", rankPerDay) 23 | router.POST("/lucky/submit", openLucky) 24 | router.POST("/lucky/status", getLucky) 25 | 26 | // for game 27 | router.POST("/tx/page_tx", pageTxes) 28 | router.POST("/game/page_lottery", pageLottery) 29 | } 30 | -------------------------------------------------------------------------------- /database/db.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "dappswin/conf" 8 | 9 | _ "github.com/go-sql-driver/mysql" 10 | "github.com/golang/glog" 11 | "github.com/jinzhu/gorm" 12 | ) 13 | 14 | // Db 全局连接 TODO: ensure 启动了连接池 15 | var Db *gorm.DB 16 | 17 | // Init 初始化myql 18 | func Init() { 19 | glog.Info("Connecting mysql ...") 20 | db, err := gorm.Open("mysql", getDSN()) 21 | if err != nil { 22 | glog.Exitln("failed to connect database", err) 23 | } 24 | if err := db.DB().Ping(); err != nil { 25 | glog.Exitln("mysql 不可通") 26 | } 27 | // TODO output to glog. 28 | db.SetLogger(log.New(os.Stdout, "\r\n", 0)) 29 | 30 | Db = db 31 | // Client.FlushDB() 32 | } 33 | 34 | // Close 关闭连接 35 | func Close() { 36 | Db.Close() 37 | } 38 | 39 | func getDSN() string { 40 | user := conf.C.GetString("mysql.user") + ":" + conf.C.GetString("mysql.password") 41 | host := conf.C.GetString("mysql.host") + ":" + conf.C.GetString("mysql.port") 42 | dsn := user + "@tcp(" + host + ")/dappswin?charset=utf8&parseTime=True&loc=Local" 43 | return dsn 44 | } 45 | -------------------------------------------------------------------------------- /app/vip.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // 1 1000 0.01% 4 | // 2 5000 0.02% 5 | // 3 10000 0.03% 6 | // 4 50000 0.04% 7 | // 5 100000 0.05% 8 | // 6 500000 0.06% 9 | // 7 1000000 0.08% 10 | // 8 5000000 0.12% 11 | // 9 10000000 0.16% 12 | // 10 50000000 0.20% 13 | type VIP struct { 14 | Level uint8 15 | Amount float64 16 | Rebate float64 17 | } 18 | 19 | var vip = map[float64]float64{ 20 | 1e3: 0.01, 21 | 5e3: 0.02, 22 | 1e4: 0.03, 23 | 5e4: 0.04, 24 | 1e5: 0.05, 25 | 5e5: 0.06, 26 | 1e6: 0.08, 27 | 5e6: 0.12, 28 | 1e7: 0.16, 29 | 5e7: 0.20, 30 | } 31 | 32 | var vipInfo = []VIP{ 33 | {1, 1e3, 0.01}, 34 | {2, 5e3, 0.02}, 35 | {3, 1e4, 0.03}, 36 | {4, 5e4, 0.04}, 37 | {5, 1e5, 0.05}, 38 | {6, 5e5, 0.06}, 39 | {7, 1e6, 0.08}, 40 | {8, 5e6, 0.12}, 41 | {9, 1e7, 0.16}, 42 | {10, 5e7, 0.20}, 43 | } 44 | 45 | func getVIPLevel(amount float64) uint8 { 46 | 47 | for _, a := range vipInfo { 48 | if amount < a.Amount { 49 | return a.Level - 1 50 | } 51 | } 52 | return 10 53 | } 54 | 55 | func getNewVIP(name string, newTotal float64) uint8 { 56 | return 0 57 | } 58 | -------------------------------------------------------------------------------- /app/message.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/golang/glog" 7 | ) 8 | 9 | const ( 10 | success = iota 11 | faield 12 | ) 13 | 14 | // APIMessage 封装消息给前端 15 | type APIMessage struct { 16 | Data interface{} `json:"data,omitempty"` 17 | Status int `json:"status"` 18 | Code string `json:"code"` 19 | Message string `json:"message"` 20 | } 21 | 22 | // NewMsg 统一封装消息 23 | func NewMsg(code int, data interface{}, details ...interface{}) (int, *APIMessage) { 24 | m := &APIMessage{} 25 | m.Code = strconv.Itoa(code) 26 | 27 | if code == 200 { 28 | m.Status = success 29 | switch d := data.(type) { 30 | case string: 31 | m.Message = d 32 | default: 33 | m.Data = data 34 | } 35 | 36 | } else { 37 | m.Status = faield 38 | switch d := data.(type) { 39 | case string: 40 | glog.ErrorDepth(1, d) 41 | m.Message = d 42 | case error: 43 | // TODO: handle sql error 44 | glog.ErrorDepth(1, d) 45 | m.Message = d.Error() 46 | default: 47 | m.Message = "发生不可知错误." 48 | } 49 | } 50 | 51 | return 200, m 52 | } 53 | -------------------------------------------------------------------------------- /app/bonus.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type bonusPoolRsp struct { 6 | StartTime int `json:"start_time,omitempty"` 7 | ExpectEarnings float64 `json:"expect_earnings,omitempty"` 8 | EarningsPerCGG float64 `json:"earnings_per_cgg,omitempty"` 9 | BonusAmount float64 `json:"bonus_amount,omitempty"` 10 | CoinName string `json:"coin_name"` 11 | } 12 | 13 | type bonusPoolPost struct { 14 | Name string `json:"name" binding:"required,max=12"` 15 | } 16 | 17 | func bonusPool(c *gin.Context) { 18 | body := &bonusPoolPost{} 19 | if err := c.ShouldBind(body); err != nil { 20 | c.JSON(NewMsg(400, "输入参数有误")) 21 | return 22 | } 23 | 24 | c.JSON(NewMsg(200, &[]bonusPoolRsp{{10 * 60 * 60, 0.0, 0.000024, 10.2, "EOS"}, 25 | {10 * 60 * 60, 0.0, 0.000014, 100.2, "CGG"}})) 26 | } 27 | 28 | type bonusStatusRsp struct { 29 | TotalAmountEos float64 `json:"total_amount_eos,omitempty"` 30 | TotalAmountCGG float64 `json:"total_amount_cgg,omitempty"` 31 | } 32 | 33 | func bonusStats(c *gin.Context) { 34 | 35 | c.JSON(NewMsg(200, &bonusStatusRsp{1.2, 3002.1})) 36 | } 37 | -------------------------------------------------------------------------------- /conf/config.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/golang/glog" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | // C read from etc < $home < $pwd < env 11 | var C = viper.New() 12 | 13 | func Init() { 14 | 15 | C.AddConfigPath("/etc/dappswin/") 16 | C.AddConfigPath("$HOME/.dappswin") 17 | C.AddConfigPath(".") 18 | C.SetConfigName("dappswin") 19 | C.SetConfigType("toml") 20 | 21 | C.SetEnvPrefix("DAPPSWIN") 22 | C.AutomaticEnv() 23 | // support read nested key from env: eth.host = DAPPSWIN_ETH_HOST 24 | C.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 25 | 26 | if err := C.ReadInConfig(); err != nil { 27 | glog.Error(err.Error()) 28 | } 29 | glog.Info("Using config file:", C.ConfigFileUsed()) 30 | 31 | loadDefaultConfig() 32 | C.WatchConfig() 33 | 34 | } 35 | 36 | func loadDefaultConfig() { 37 | C.SetDefault("eth.host", "127.0.0.1:8545") 38 | C.SetDefault("eth.fromBLock", 0) 39 | C.SetDefault("redis.host", "127.0.0.1:6379") 40 | C.SetDefault("redis.password", "") 41 | C.SetDefault("gin.host", ":8378") 42 | // Cfg.SetDefault("mongo.host", "127.0.0.1:27017") 43 | } 44 | -------------------------------------------------------------------------------- /scripts/nginx/fastcgi_params: -------------------------------------------------------------------------------- 1 | 2 | fastcgi_param QUERY_STRING $query_string; 3 | fastcgi_param REQUEST_METHOD $request_method; 4 | fastcgi_param CONTENT_TYPE $content_type; 5 | fastcgi_param CONTENT_LENGTH $content_length; 6 | 7 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 8 | fastcgi_param REQUEST_URI $request_uri; 9 | fastcgi_param DOCUMENT_URI $document_uri; 10 | fastcgi_param DOCUMENT_ROOT $document_root; 11 | fastcgi_param SERVER_PROTOCOL $server_protocol; 12 | fastcgi_param REQUEST_SCHEME $scheme; 13 | fastcgi_param HTTPS $https if_not_empty; 14 | 15 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 16 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 17 | 18 | fastcgi_param REMOTE_ADDR $remote_addr; 19 | fastcgi_param REMOTE_PORT $remote_port; 20 | fastcgi_param SERVER_ADDR $server_addr; 21 | fastcgi_param SERVER_PORT $server_port; 22 | fastcgi_param SERVER_NAME $server_name; 23 | 24 | # PHP only, required if PHP was built with --enable-force-cgi-redirect 25 | fastcgi_param REDIRECT_STATUS 200; 26 | -------------------------------------------------------------------------------- /scripts/nginx/fastcgi.conf: -------------------------------------------------------------------------------- 1 | 2 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 3 | fastcgi_param QUERY_STRING $query_string; 4 | fastcgi_param REQUEST_METHOD $request_method; 5 | fastcgi_param CONTENT_TYPE $content_type; 6 | fastcgi_param CONTENT_LENGTH $content_length; 7 | 8 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 9 | fastcgi_param REQUEST_URI $request_uri; 10 | fastcgi_param DOCUMENT_URI $document_uri; 11 | fastcgi_param DOCUMENT_ROOT $document_root; 12 | fastcgi_param SERVER_PROTOCOL $server_protocol; 13 | fastcgi_param REQUEST_SCHEME $scheme; 14 | fastcgi_param HTTPS $https if_not_empty; 15 | 16 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 17 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 18 | 19 | fastcgi_param REMOTE_ADDR $remote_addr; 20 | fastcgi_param REMOTE_PORT $remote_port; 21 | fastcgi_param SERVER_ADDR $server_addr; 22 | fastcgi_param SERVER_PORT $server_port; 23 | fastcgi_param SERVER_NAME $server_name; 24 | 25 | # PHP only, required if PHP was built with --enable-force-cgi-redirect 26 | fastcgi_param REDIRECT_STATUS 200; 27 | -------------------------------------------------------------------------------- /common/types.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // JSONTime 自定义时间,为了给前端返回符合2006-01-02 15:04:05的格式 10 | type JSONTime struct { 11 | Time time.Time 12 | } 13 | 14 | const ( 15 | timeFormart = "2006-01-02 15:04:05" 16 | ) 17 | 18 | // func (t *JSONTime) UnmarshalJSON(data []byte) (err error) { 19 | // now, err := time.ParseInLocation(`"`+timeFormart+`"`, string(data), time.Local) 20 | // t.Time = now 21 | // return 22 | // } 23 | 24 | // MarshalJSON 返回前端的json格式化方法 25 | func (t JSONTime) MarshalJSON() ([]byte, error) { 26 | b := make([]byte, 0, len(timeFormart)+2) 27 | b = append(b, '"') 28 | b = t.Time.AppendFormat(b, timeFormart) 29 | b = append(b, '"') 30 | return b, nil 31 | } 32 | 33 | // Value insert timestamp into mysql need this function. 34 | func (t JSONTime) Value() (driver.Value, error) { 35 | var zeroTime time.Time 36 | if t.Time.UnixNano() == zeroTime.UnixNano() { 37 | return nil, nil 38 | } 39 | return t.Time, nil 40 | } 41 | 42 | // Scan valueof time.Time 43 | func (t *JSONTime) Scan(v interface{}) error { 44 | value, ok := v.(time.Time) 45 | if ok { 46 | *t = JSONTime{Time: value} 47 | return nil 48 | } 49 | return fmt.Errorf("can not convert %v to timestamp", v) 50 | } 51 | -------------------------------------------------------------------------------- /dappswin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "dappswin/app" 5 | "dappswin/common" 6 | "dappswin/conf" 7 | "dappswin/database" 8 | "dappswin/logs" 9 | "dappswin/models" 10 | "net/http" 11 | 12 | "github.com/facebookgo/grace/gracehttp" 13 | "github.com/gin-contrib/cors" 14 | "github.com/gin-gonic/contrib/static" 15 | "github.com/gin-gonic/gin" 16 | _ "github.com/go-sql-driver/mysql" 17 | "github.com/golang/glog" 18 | ) 19 | 20 | // TODO: wss 21 | 22 | func main() { 23 | defer database.Close() 24 | defer glog.Flush() 25 | 26 | logs.Init() 27 | conf.Init() 28 | common.Init() 29 | database.Init() 30 | models.Init() 31 | app.Init() 32 | 33 | go app.ResolveRoutine() 34 | 35 | r := gin.Default() 36 | 37 | // TODO: 根据环境enable cors 38 | r.Use(cors.Default()) 39 | 40 | api := r.Group("/api") 41 | // app.WSRegister(api) 42 | // app.UserRegister(api) 43 | // app.EosRegister(api) 44 | app.Register(api) 45 | 46 | r.Use(static.Serve("/", static.LocalFile("./views", true))) 47 | 48 | server := &http.Server{ 49 | Addr: ":" + conf.C.GetString("gin.port"), 50 | Handler: r, 51 | } 52 | gracehttp.Serve(server) 53 | // TODO: enable grace + autotls 54 | // replace by certbot on nginx 55 | // glog.Error(autotls.Run(r, "dappswin.io", "www.dappswin.io")) 56 | 57 | } 58 | -------------------------------------------------------------------------------- /docs/game.http: -------------------------------------------------------------------------------- 1 | @host=http://127.0.0.1:8080 2 | # @host=http://3.1.50.78 3 | # @host=https://dappswin.io 4 | 5 | @node=https://jungle.eos.dfuse.io 6 | 7 | ### 我的投注 8 | POST {{host}}/api/tx/page_tx 9 | Content-Type: application/json 10 | 11 | { 12 | "page_index": 1, 13 | "page_size": 10, 14 | "name": "liuxuexi5211" 15 | } 16 | 17 | 18 | ### 开奖历史记录 19 | POST {{host}}/api/game/page_lottery 20 | Content-Type: application/json 21 | 22 | { 23 | "page_index": 3, 24 | "page_size": 10 25 | } 26 | 27 | 28 | ### 奖池EOS金额 29 | POST {{node}}/v1/chain/get_currency_balance HTTP/1.1 30 | Content-Type: application/json 31 | 32 | { 33 | "code": "eosio.token", 34 | "account": "xxptoken1234", 35 | "symbol": "EOS" 36 | } 37 | 38 | 39 | ### 奖池CGG金额 40 | POST {{node}}/v1/chain/get_currency_balance HTTP/1.1 41 | Content-Type: application/json 42 | 43 | { 44 | "code": "xxptoken1234", 45 | "account": "xxptoken1234", 46 | "symbol": "CGG" 47 | } 48 | 49 | 50 | ### 投注限额 是奖池金额结果的20% 51 | 52 | ### 本期当前累积投注 wsTYPE 53 | 54 | 55 | ### max计算方法, 是投注限额/当前注数/10^(星数) 56 | 1000/10/1000 = 0.1 57 | 58 | 59 | ### 查询期号, 取game_minture字段,并自动加1,这里显示的是下一期的期号 60 | POST {{host}}/api/game/page_lottery 61 | Content-Type: application/json 62 | 63 | { 64 | "page_index": 1, 65 | "page_size": 1 66 | } 67 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | [[constraint]] 28 | name = "github.com/gin-gonic/gin" 29 | version = "1.3.0" 30 | 31 | [[constraint]] 32 | name = "github.com/go-sql-driver/mysql" 33 | version = "1.4.1" 34 | 35 | [[constraint]] 36 | branch = "master" 37 | name = "github.com/golang/glog" 38 | 39 | [[constraint]] 40 | name = "github.com/gorilla/websocket" 41 | version = "1.4.0" 42 | 43 | [[constraint]] 44 | name = "github.com/jinzhu/gorm" 45 | version = "1.9.2" 46 | 47 | [[constraint]] 48 | name = "github.com/spf13/viper" 49 | version = "1.3.1" 50 | 51 | [prune] 52 | go-tests = true 53 | unused-packages = true 54 | -------------------------------------------------------------------------------- /app/ico_test.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Test_getRefund(t *testing.T) { 9 | type args struct { 10 | Time int64 11 | value float64 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want string 17 | }{ 18 | {name: "test1", args: args{0, 10 * 1e4}, want: "200.0000"}, 19 | {name: "test2", args: args{0, 100 * 1e4}, want: "2010.0000"}, 20 | {name: "test3", args: args{0, 601.2 * 1e4}, want: "12144.2400"}, 21 | {name: "test4", args: args{0, 1010 * 1e4}, want: "20503.0000"}, 22 | {name: "test5", args: args{0, 1500 * 1e4}, want: "30600.0000"}, 23 | {name: "test6", args: args{0, 1500.1234 * 1e4}, want: "30602.5174"}, 24 | {name: "test7", args: args{0, 2001 * 1e4}, want: "41020.5000"}, 25 | {name: "test8", args: args{2 + oneDayMills, 1500.1234 * 1e4}, want: "30002.4680"}, 26 | {name: "test9", args: args{2 + oneDayMills, 600 * 1e4}, want: "12000.0000"}, 27 | } 28 | 29 | // Init handle this. 30 | eosConf = &EosConf{EOS_CGG: 20, ICOStartTime: 1} 31 | 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | if got := getRefund(tt.args.Time, tt.args.value) / 1e4; fmt.Sprintf("%.4f", got) != tt.want { 35 | t.Errorf("getRefund() = %v, want %v", fmt.Sprintf("%.4f", got), tt.want) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /dappswin.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | newsSource = "/etc/dappswin/news.json" 3 | 4 | [gin] 5 | port = 8080 6 | runmode = "dev" 7 | # runmode = "pro" 8 | EnableAdmin = false 9 | 10 | [eos] 11 | # TODO 节点池 12 | RPCURL = "https://jungle.eos.dfuse.io" 13 | WalletURL = "unix:///home/xxp/eosio-wallet/keosd.sock" 14 | WalletPW = "PW5KEVCDpGccbv7cpXkoBD5eqESYtKiu4npo5ZmdJg2DpbXR9YMx6" 15 | GameAccount = "xxptoken1234" 16 | ICOAccount = "xiaopingeos2" 17 | # Token合约地址 18 | TokenAccount = "xxptoken1234" 19 | # FromBlkNum = 7529733 20 | FromBlkNum = 9191400 21 | 22 | # RPCURL = "https://proxy.eosnode.tools" 23 | # WalletURL = "unix:///root/eosio-wallet/keosd.sock" 24 | # WalletPW = "PW5Jw3kodJVotFzaaUVz7H3KsesTRxwNY761kh1sDEctTcpwf2ubm" 25 | # GameAccount = "cryptogame12" 26 | # TokenAccount = "cryptogame12" 27 | # ICOAccount = "cryptogame11" 28 | # FromBlkNum = 36118039 29 | 30 | # 间隔单位(ms) 31 | FetchIdleDur = 500 32 | EnableICO = false 33 | ICOStartTime = 1547352488000 34 | ICOTotalAmount = 60000 35 | ICOFakeAmount = 12000 36 | TokenSymbol = "CGG" 37 | EOS_CGG = 20 38 | 39 | # 用户质押锁仓地址 40 | LockAccount = "xiaopingeos4" 41 | # 官方锁仓地址,计算流通数量 42 | OfficialLockAccount = "xxptoken1234" 43 | # CGG发行总量 44 | TotalCGGAmoount = 2e7 45 | UnstakePeriod = "1m" 46 | 47 | # 平台利润地址 48 | PlatformAccount = "xiaopingeos4" 49 | 50 | [mysql] 51 | host = "127.0.0.1" 52 | port = "3306" 53 | user = "root" 54 | password = "Xiaoping@123456" -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # dappswin 2 | 3 | 4 | - 本项目提供链上Restful服务 5 | 6 | 由[dep](https://github.com/golang/dep)管理,源码启动方式如下 7 | 8 | - 开发启动流程 9 | 10 | - 修改`dappswin.toml`里mysql的配置(用户名和密码) 11 | - 手动创建database 12 | 13 | ```bash 14 | mysql -u root -pXiaoping@123456 -e "create database if not exists dappswin;" 15 | ``` 16 | - 启动 17 | 18 | ```bash 19 | # 下载依赖 20 | 21 | make deps 22 | 23 | # 以热编译方式启动 24 | 25 | make run 26 | ``` 27 | 28 | - glog的高级用法 29 | 30 | ```bash 31 | # 以V(8)级别打印所有game开头的go文件日志,还打印ico.go的日志 32 | 33 | build/dappswin -vmodule="game*=8,ico=8" 34 | 35 | # 更详细的使用可以 build/dappswin -h 查看 36 | 37 | ```bash 38 | 39 | - 同步服务器nginx配置 40 | 41 | rsync -avz --delete lottery:/etc/nginx/ ./scripts/nginx 42 | 43 | 44 | 1.项目介绍: 45 | 加密乐园(CryptoGaming)是一个全球领先、分散的加密竞猜游戏平台,通过公平、安全、共赢的平台帮助全球加密玩家赢得加密资产,并提供一个成为百万加密鲸鱼的机会。 46 | 47 | 2.项目优势: 48 | (1)公平、安全; 49 | (2)有趣社交; 50 | (3)多筹码下注; 51 | (4)真人版、VR游戏; 52 | (5)区块链竞猜完整闭环生态价值; 53 | (6)更高赔率、分红、无限彩金; 54 | (7)游戏产生利益公平分配 55 | 56 | 3.第一款游戏: 57 | 加密鲸鱼,一分钟开一次奖,基于EOS区块末尾随机数产生5位数字开奖号码,如果不是数字顺延。 58 | 具有9大竞争优势: 59 | (1)最高奖金100万倍; 60 | (2)开奖号码基于EOS区块随机产生,可验证、透明; 61 | (3)即时支付,返还奖金; 62 | (4)保护玩家隐私、安全; 63 | (5)投注即挖矿; 64 | (6)质押分红; 65 | (7)利润竞拍; 66 | (8)鲸鱼榜奖励; 67 | (9)幸运抽奖 68 | 69 | 4.项目官网、社区信息:* 70 | 官网:dappswin.io* 71 | 中文白皮书:https://dappswin.io/whitepaper/cryptogame-cn-1.0.pdf 72 | 英文白皮书:https://dappswin.io/whitepaper/cryptogame-en-1.0.pdf 73 | Discord:https://discordapp.com/invite/QWeFf2q 74 | telegram中文:https://t.me/cryptogaming_CN 75 | telegram英文:https://t.me/cryptogaming_EN 76 | Reddit:https://www.reddit.com/user/cryptogamingoffice 77 | Twitter:https://twitter.com/cryptogamingcgg 78 | 79 | 5.CCN报道:http://t.cn/EGQadnm6. 80 | bitcointalk:https://bitcointalk.org/index.php?topic=5094389.msg49109024#msg49109024 81 | 82 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build package start-docker run deps test pack 2 | DIST_PATH = dist 3 | RELEASE_PATH = $(DIST_PATH)/release 4 | BUILD_DATE = $(shell date -u) 5 | BUILD_HASH = $(shell git rev-parse HEAD) 6 | GO = go 7 | GINKGO = ginkgo 8 | GO_LINKER_FLAGS ?= -ldflags \ 9 | "-X 'dappswin/common.BuildDate=$(BUILD_DATE)' \ 10 | -X dappswin/common.BuildHash=$(BUILD_HASH)" 11 | BUILDER_GOOS_GOARCH=$(shell $(GO) env GOOS)_$(shell $(GO) env GOARCH) 12 | PACKAGESLISTS=$(shell $(GO) list ./...) 13 | TESTFLAGS ?= -short 14 | PACKAGESLISTS_COMMA=$(shell echo $(PACKAGESLISTS) | tr ' ' ',') 15 | ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 16 | 17 | test: 18 | @echo Testing ... 19 | $(GO) test ./... 20 | 21 | build: test 22 | @echo Build Linux amd64 23 | env GOOS=linux GOARCH=amd64 $(GO) build -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./dappswin.go 24 | 25 | deps: 26 | @echo make deps ... 27 | @go get github.com/gravityblast/fresh 28 | @go get github.com/golang/dep/cmd/dep 29 | @dep ensure -v 30 | 31 | run: 32 | @echo Running dappswin 33 | fresh 34 | 35 | pack: test build 36 | @echo tar builded 37 | tar zcvf dappswin.tar.gz dappswin dappswin.toml scripts install.sh 38 | 39 | govet: ## Runs govet against all packages. 40 | @echo Running GOVET 41 | $(GO) vet $(GOFLAGS) $(PACKAGESLISTS) || exit 1 42 | 43 | gofmt: ## Runs gofmt against all packages. 44 | @echo Running GOFMT 45 | @echo $(PACKAGESLISTS) 46 | @for package in $(PACKAGESLISTS);do \ 47 | echo "Checking "$$package; \ 48 | files=$$(go list -f '{{range .GoFiles}}{{$$.Dir}}/{{.}} {{end}}' $$package); \ 49 | if [ "$$files" ]; then \ 50 | gofmt_output=$$(gofmt -d -s $$files 2>&1); \ 51 | if [ "$$gofmt_output" ]; then \ 52 | echo "$$gofmt_output"; \ 53 | echo "gofmt failure"; \ 54 | exit 1; \ 55 | fi; \ 56 | fi; \ 57 | done 58 | @echo "gofmt success"; \ 59 | 60 | -------------------------------------------------------------------------------- /app/ico.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "dappswin/models" 5 | "fmt" 6 | "sort" 7 | 8 | "github.com/golang/glog" 9 | ) 10 | 11 | const ( 12 | handled = iota 13 | pending 14 | unknow 15 | ) 16 | 17 | // ICORules ico等级, 100EOS以下 多奖励0.5% 18 | var ICORules = map[int]float64{ 19 | 100 * 1e4: 0, 20 | 500 * 1e4: 50, 21 | 1000 * 1e4: 100, 22 | 1500 * 1e4: 150, 23 | 2000 * 1e4: 200, 24 | 20000 * 1e4: 250, 25 | } 26 | 27 | func getRefund(time int64, value float64) float64 { 28 | 29 | if time-eosConf.ICOStartTime > oneDayMills { 30 | return value * eosConf.EOS_CGG 31 | } 32 | 33 | sortedKeys := []int{} 34 | for k, _ := range ICORules { 35 | sortedKeys = append(sortedKeys, k) 36 | } 37 | sort.Ints(sortedKeys) 38 | for _, k := range sortedKeys { 39 | if value < float64(k) { 40 | return value*eosConf.EOS_CGG*ICORules[k]/1e4 + value*eosConf.EOS_CGG 41 | } 42 | } 43 | 44 | // here return the max reward 45 | return value*eosConf.EOS_CGG*250/1e4 + value*eosConf.EOS_CGG 46 | } 47 | 48 | var icochan = make(chan *models.ICO, 4096) 49 | var oneDayMills = int64(24 * 60 * 60 * 1000) 50 | 51 | func checkICORoutine() { 52 | for { 53 | select { 54 | case ico := <-icochan: 55 | glog.Infof("有人投ICO募资了,who=>%s 额度是%s EOS", ico.Account, fmt.Sprintf("%.4f", float64(ico.Amount)/1e4)) 56 | 57 | amount := getRefund(ico.TimeMills, float64(ico.Amount)) / 1e4 58 | quantity := fmt.Sprintf("%.4f ", amount) + eosConf.TokenSymbol 59 | glog.Infof("奖励购买的代币===========> to %s, quantity: %s", ico.Account, quantity) 60 | if hash, err := sendTokens(eosConf.ICOAccount, ico.Account, quantity, "refund CGG"); err == nil { 61 | glog.Infof("to %s, %s, hash is %s", ico.Account, quantity, hash) 62 | if err := db.Model(ico).Update("status", handled).Error; err != nil { 63 | glog.Error(err) 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /models/block.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "dappswin/database" 5 | "encoding/json" 6 | 7 | "github.com/golang/glog" 8 | "github.com/jinzhu/gorm" 9 | ) 10 | 11 | var db *gorm.DB 12 | 13 | func Init() { 14 | db = database.Db 15 | db.AutoMigrate(&Block{}, &User{}, &Tx{}, &Game{}, &ICO{}, &Stake{}) 16 | } 17 | 18 | // Block ws send to hub 19 | type Block struct { 20 | Hash string `json:"id"` 21 | Num uint32 `json:"num" gorm:"PRIMARY_KEY"` 22 | Time int64 `json:"time"` 23 | } 24 | 25 | // Message make block msg to front 26 | func (b Block) Message() []byte { 27 | 28 | m := Message{} 29 | m.Type = 0 30 | // same as func (m *Message) HandleTimeStamp() 31 | // b.Time = b.Time*2/1000 - 946684800 32 | b.Time = b.Time 33 | m.Data = b 34 | 35 | ms := []Message{} 36 | ms = append(ms, m) 37 | result, _ := json.Marshal(ms) 38 | return result 39 | } 40 | 41 | func (b *Block) LastLetter() string { 42 | if len(b.Hash) != 64 { 43 | glog.Errorf("Error on block !!!! %#v", b) 44 | return "" 45 | } 46 | return b.Hash[len(b.Hash)-1:] 47 | } 48 | 49 | func AddBlock(b *Block) (err error) { 50 | d := db.Create(b) 51 | return d.Error 52 | 53 | } 54 | 55 | func GetLastestBlock() (*Block, error) { 56 | blk := &Block{} 57 | d := db.Last(blk) 58 | return blk, d.Error 59 | } 60 | 61 | type Reward struct { 62 | Amount string `json:"amount"` 63 | ID int `json:"bookid"` 64 | Memo string `json:"memo"` 65 | Status int `json:"status"` 66 | To string `json:"to"` 67 | } 68 | 69 | // Message ws send this. 70 | type Message struct { 71 | Type int `json:"type"` 72 | BlockNum uint32 `json:"blocknum,omitempty"` 73 | Hash string `json:"id,omitempty"` 74 | TimeMills int64 `json:"time,omitempty"` 75 | Data interface{} `json:"data"` 76 | } 77 | 78 | // HandleTimeStamp 前端为了显示0.5所做的特殊处理 79 | // 80 | // (2142155917+946684800)/2*1000 == // 1544420358500 81 | func (m *Message) HandleTimeStamp() { 82 | // m.Time = m.Time*2/1000 - 946684800 83 | 84 | } 85 | -------------------------------------------------------------------------------- /scripts/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes auto; 3 | pid /run/nginx.pid; 4 | include /etc/nginx/modules-enabled/*.conf; 5 | 6 | events { 7 | worker_connections 768; 8 | # multi_accept on; 9 | } 10 | 11 | http { 12 | 13 | ## 14 | # Basic Settings 15 | ## 16 | 17 | sendfile on; 18 | tcp_nopush on; 19 | tcp_nodelay on; 20 | keepalive_timeout 65; 21 | types_hash_max_size 2048; 22 | # server_tokens off; 23 | 24 | # server_names_hash_bucket_size 64; 25 | # server_name_in_redirect off; 26 | 27 | include /etc/nginx/mime.types; 28 | default_type application/octet-stream; 29 | 30 | ## 31 | # SSL Settings 32 | ## 33 | 34 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE 35 | ssl_prefer_server_ciphers on; 36 | 37 | ## 38 | # Logging Settings 39 | ## 40 | 41 | access_log /var/log/nginx/access.log; 42 | error_log /var/log/nginx/error.log; 43 | 44 | ## 45 | # Gzip Settings 46 | ## 47 | 48 | gzip on; 49 | 50 | # gzip_vary on; 51 | # gzip_proxied any; 52 | # gzip_comp_level 6; 53 | # gzip_buffers 16 8k; 54 | # gzip_http_version 1.1; 55 | # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 56 | 57 | # websocket var 58 | map $http_upgrade $connection_upgrade { 59 | default upgrade; 60 | '' close; 61 | } 62 | 63 | ## 64 | # Virtual Host Configs 65 | ## 66 | 67 | include /etc/nginx/conf.d/*.conf; 68 | include /etc/nginx/sites-enabled/*; 69 | } 70 | 71 | 72 | #mail { 73 | # # See sample authentication script at: 74 | # # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript 75 | # 76 | # # auth_http localhost/auth.php; 77 | # # pop3_capabilities "TOP" "USER"; 78 | # # imap_capabilities "IMAP4rev1" "UIDPLUS"; 79 | # 80 | # server { 81 | # listen localhost:110; 82 | # protocol pop3; 83 | # proxy on; 84 | # } 85 | # 86 | # server { 87 | # listen localhost:143; 88 | # protocol imap; 89 | # proxy on; 90 | # } 91 | #} 92 | -------------------------------------------------------------------------------- /conf/news.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "language" : "zh-cn", 5 | "subject": "百万鲸鱼震撼上线", 6 | "image_source": "https://dappswin.io/news/lottery_cn.png", 7 | "image_link": "https://dappswin.io/lottery", 8 | "date": "2019-1-14" 9 | },{ 10 | "id": 1, 11 | "language" : "en", 12 | "subject": "lottery coming", 13 | "image_source": "https://dappswin.io/news/lottery_en.png", 14 | "image_link": "https://dappswin.io/lottery", 15 | "date": "2019-1-14" 16 | },{ 17 | "id": 2, 18 | "language" : "zh-cn", 19 | "subject": "CGG空投", 20 | "image_source": "https://dappswin.io/news/airdrop_cgg_cn.png", 21 | "image_link": "https://dappswin.io", 22 | "date": "2019-1-14" 23 | 24 | },{ 25 | "id": 2, 26 | "language" : "en", 27 | "subject": "CGG AirDrop", 28 | "image_source": "https://dappswin.io/news/airdrop_cgg_en.png", 29 | "image_link": "https://dappswin.io", 30 | "date": "2019-1-14" 31 | 32 | },{ 33 | "id": 3, 34 | "language" : "zh-cn", 35 | "subject": "discord游戏社区", 36 | "image_source": "https://dappswin.io/news/join_discord_cn.png", 37 | "image_link": "https://discordapp.com/invite/QWeFf2q", 38 | "date": "2019-1-14" 39 | 40 | },{ 41 | "id": 3, 42 | "language" : "en", 43 | "subject": "discord gaming", 44 | "image_source": "https://dappswin.io/news/join_discord_en.png", 45 | "image_link": "https://discordapp.com/invite/QWeFf2q", 46 | "date": "2019-1-14" 47 | 48 | },{ 49 | "id": 4, 50 | "language" : "zh-cn", 51 | "subject": "电报群", 52 | "image_source": "https://dappswin.io/news/join_telegram_cn.png", 53 | "image_link": "https://t.me/cryptogaming_CN", 54 | "date": "2019-1-14" 55 | 56 | },{ 57 | "id": 4, 58 | "language" : "en", 59 | "subject": "telegram", 60 | "image_source": "https://dappswin.io/news/join_telegram_en.png", 61 | "image_link": "https://t.me/cryptogaming_EN", 62 | "date": "2019-1-14" 63 | 64 | } 65 | ] 66 | -------------------------------------------------------------------------------- /models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "github.com/shopspring/decimal" 6 | ) 7 | 8 | // User 用户返佣系统 9 | type User struct { 10 | gorm.Model `json:"-"` 11 | Name string `json:"name" gorm:"size:12;unique"` 12 | PName string `json:"-" gorm:"size:12;index"` 13 | PNames string `json:"-"` 14 | Level uint8 `json:"-"` 15 | ChildrenCount int `json:"children_count"` 16 | TotalBet decimal.Decimal `json:"total_bet" sql:"type:decimal(20,8)"` 17 | TotalRebate decimal.Decimal `json:"total_rebate" sql:"type:decimal(20,8)"` 18 | Bet decimal.Decimal `json:"-" sql:"type:decimal(20,8)"` 19 | Rebate decimal.Decimal `json:"-" sql:"type:decimal(20,8)"` 20 | // ChildrenBet uint64 `json:"-"` 21 | // ChildrenRebate uint32 `json:"-"` 22 | } 23 | 24 | // func updateParent(name string, bet uint64, isNew bool) { 25 | // user := &User{} 26 | // if db.Where("name = ?", name).First(user).RecordNotFound() { 27 | // user = &User{Name: name} 28 | // } 29 | // if isNew { 30 | // user.ChildrenCount++ 31 | // } 32 | 33 | // user.TotalBet += bet 34 | // // user.ChildrenBet += bet 35 | // user.Level = getLevel(uint64(user.TotalBet / 1e4)) 36 | 37 | // if user.ID == 0 { 38 | // db.Create(user) 39 | // } else { 40 | // db.Model(user).Update(user) 41 | // } 42 | // } 43 | 44 | // func UpdateUserInfo(txmsg *Message) { 45 | // var isNew bool 46 | // user := &User{} 47 | // tx, _ := txmsg.Data.(TX) 48 | // if db.Where("name = ?", tx.From).First(user).RecordNotFound() { 49 | // isNew = true 50 | // } 51 | // amout := tx.Amount() 52 | // user.Bet += amout 53 | // _, _, pAccount := ResolveMemo(tx.Memo) 54 | 55 | // user.Name = tx.From 56 | // if pAccount != "" { 57 | // user.PName = pAccount 58 | // updateParent(user.PName, user.Bet, isNew) 59 | // } 60 | 61 | // if !strings.Contains(user.PNames, user.PName) { 62 | // user.PNames += ("," + user.PName) 63 | // } 64 | // user.TotalBet += amout 65 | // user.Level = getLevel(uint64(user.TotalBet / 1e4)) 66 | // glog.V(7).Infof("User is %#v", user) 67 | 68 | // if user.ID == 0 { 69 | // db.Create(user) 70 | // } else { 71 | // db.Model(user).Update(user) 72 | // } 73 | 74 | // } 75 | -------------------------------------------------------------------------------- /app/win.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | 8 | "dappswin/models" 9 | 10 | "github.com/golang/glog" 11 | ) 12 | 13 | var winchan = make(chan *models.Game, 4096) 14 | 15 | func checkWinRoutine() { 16 | for { 17 | select { 18 | case game := <-winchan: 19 | glog.Infof("开奖啦,激动人心的时刻到来了。。。%#v", game) 20 | txs, err := models.GetTxsInGame(game.GameMintue, pending) 21 | if err != nil { 22 | glog.Error("getTxsInGame error :", err) 23 | } 24 | msgs := []*models.Message{} 25 | msg := &models.Message{} 26 | 27 | for i, tx := range txs { 28 | _, betInfo, _ := models.ResolveMemo(tx.Memo) 29 | glog.Infof("betInfo is %s, %s, %f", betInfo, game.Result, tx.Amount) 30 | winTimes, betnum := HandleBetInfo(betInfo, []byte(game.Result)) 31 | if winTimes > 0 { 32 | winValue := calcBenefit(winTimes, betnum, tx.Amount) 33 | // TODO call cleos unlock transfer EOS and lock, if OK update SQL. 34 | // {"bookid":0,"status":0,"to":"kunoichi3141","amount":"0.3920 EOS","memo":"win|25736640:50090:e"} 35 | memo := "win|" + fmt.Sprint(game.GameMintue) + ":" + game.Result + ":" + betInfo 36 | reward := &models.Reward{Amount: winValue + " " + coinNames[tx.CoinID], ID: i, Status: handled, To: tx.From, Memo: memo} 37 | glog.Infof("开始发放奖励, %#v", reward) 38 | 39 | //构造赢家消息 40 | gameStr, _, _ := models.ResolveMemo(tx.Memo) 41 | msg = &models.Message{wsTypes[gameStr+coinNames[tx.CoinID]+"Win"], game.BlockNum, tx.TxID, tx.TimeMills, reward} 42 | // msg.HandleTimeStamp() 43 | msgs = append(msgs, msg) 44 | quan := winValue + " " + coinNames[tx.CoinID] 45 | go func(tx *models.Tx, quan string, memo string) { 46 | if hash, err := sendTokens(eosConf.GameAccount, tx.From, quan, memo); err == nil { 47 | amount, _ := strconv.ParseFloat(winValue, 64) 48 | models.AddTx(&models.Tx{TxID: hash, From: eosConf.GameAccount, To: tx.From, 49 | Amount: amount, CoinID: tx.CoinID, Memo: memo, Status: handled, TimeMills: tx.TimeMills, TimeMintue: tx.TimeMintue}) 50 | } 51 | }(&tx, quan, memo) 52 | 53 | } 54 | 55 | tx.Status = handled 56 | models.UpdateTxById(&tx) 57 | 58 | // TODO make winTX, save to DB 59 | } 60 | if len(msgs) > 0 { 61 | buf, _ := json.Marshal(msgs) 62 | Huber.broadcast <- buf 63 | } 64 | } 65 | } 66 | } 67 | 68 | // "0.1000" 69 | func calcBenefit(times int, betTimes int, amount float64) string { 70 | s := fmt.Sprintf("%.4f", amount/float64(betTimes)*float64(times)*98/100) 71 | return s 72 | } 73 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | block ws Example 5 | 53 | 90 | 91 | 92 |
93 |
94 | 95 | 96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /scripts/nginx/koi-win: -------------------------------------------------------------------------------- 1 | 2 | charset_map koi8-r windows-1251 { 3 | 4 | 80 88 ; # euro 5 | 6 | 95 95 ; # bullet 7 | 8 | 9A A0 ; #   9 | 10 | 9E B7 ; # · 11 | 12 | A3 B8 ; # small yo 13 | A4 BA ; # small Ukrainian ye 14 | 15 | A6 B3 ; # small Ukrainian i 16 | A7 BF ; # small Ukrainian yi 17 | 18 | AD B4 ; # small Ukrainian soft g 19 | AE A2 ; # small Byelorussian short u 20 | 21 | B0 B0 ; # ° 22 | 23 | B3 A8 ; # capital YO 24 | B4 AA ; # capital Ukrainian YE 25 | 26 | B6 B2 ; # capital Ukrainian I 27 | B7 AF ; # capital Ukrainian YI 28 | 29 | B9 B9 ; # numero sign 30 | 31 | BD A5 ; # capital Ukrainian soft G 32 | BE A1 ; # capital Byelorussian short U 33 | 34 | BF A9 ; # (C) 35 | 36 | C0 FE ; # small yu 37 | C1 E0 ; # small a 38 | C2 E1 ; # small b 39 | C3 F6 ; # small ts 40 | C4 E4 ; # small d 41 | C5 E5 ; # small ye 42 | C6 F4 ; # small f 43 | C7 E3 ; # small g 44 | C8 F5 ; # small kh 45 | C9 E8 ; # small i 46 | CA E9 ; # small j 47 | CB EA ; # small k 48 | CC EB ; # small l 49 | CD EC ; # small m 50 | CE ED ; # small n 51 | CF EE ; # small o 52 | 53 | D0 EF ; # small p 54 | D1 FF ; # small ya 55 | D2 F0 ; # small r 56 | D3 F1 ; # small s 57 | D4 F2 ; # small t 58 | D5 F3 ; # small u 59 | D6 E6 ; # small zh 60 | D7 E2 ; # small v 61 | D8 FC ; # small soft sign 62 | D9 FB ; # small y 63 | DA E7 ; # small z 64 | DB F8 ; # small sh 65 | DC FD ; # small e 66 | DD F9 ; # small shch 67 | DE F7 ; # small ch 68 | DF FA ; # small hard sign 69 | 70 | E0 DE ; # capital YU 71 | E1 C0 ; # capital A 72 | E2 C1 ; # capital B 73 | E3 D6 ; # capital TS 74 | E4 C4 ; # capital D 75 | E5 C5 ; # capital YE 76 | E6 D4 ; # capital F 77 | E7 C3 ; # capital G 78 | E8 D5 ; # capital KH 79 | E9 C8 ; # capital I 80 | EA C9 ; # capital J 81 | EB CA ; # capital K 82 | EC CB ; # capital L 83 | ED CC ; # capital M 84 | EE CD ; # capital N 85 | EF CE ; # capital O 86 | 87 | F0 CF ; # capital P 88 | F1 DF ; # capital YA 89 | F2 D0 ; # capital R 90 | F3 D1 ; # capital S 91 | F4 D2 ; # capital T 92 | F5 D3 ; # capital U 93 | F6 C6 ; # capital ZH 94 | F7 C2 ; # capital V 95 | F8 DC ; # capital soft sign 96 | F9 DB ; # capital Y 97 | FA C7 ; # capital Z 98 | FB D8 ; # capital SH 99 | FC DD ; # capital E 100 | FD D9 ; # capital SHCH 101 | FE D7 ; # capital CH 102 | FF DA ; # capital hard sign 103 | } 104 | -------------------------------------------------------------------------------- /docs/游戏产品API说明.md: -------------------------------------------------------------------------------- 1 | # cryptogame 游戏前端功能+API简单说明 2 | 3 | ChangeLog: 4 | 5 | - 更新投组组合 6 | 7 | 8 | 1. 登录 9 | 10 | ![](2019-01-06-11-52-38.png) 11 | 12 | 利用scatter插件钱包 实现登录 参考[demo](http://developer.mathwallet.org/sample01/) 13 | 14 | 2. 奖池余额 15 | 16 | ![](2019-01-06-11-43-02.png) 17 | 18 | demo代码返回结果是`185758.12000 EOS`, 前端显示只整数部分`185758` 19 | ```js 20 | var settings = { 21 | "async": true, 22 | "crossDomain": true, 23 | "url": "https://proxy.eosnode.tools/v1/chain/get_currency_balance", 24 | "method": "POST", 25 | "headers": { 26 | "Content-Type": "application/json", 27 | }, 28 | "processData": false, 29 | "data": "{\"code\":\"eosio.token\", \"account\":\"cryptogame12\",\"symbol\":\"EOS\"}" 30 | } 31 | 32 | $.ajax(settings).done(function (response) { 33 | console.log(response); 34 | }); 35 | ``` 36 | 3. TIME倒计时 37 | 38 | ![](2019-01-06-11-42-40.png) 39 | 40 | 显示距离下一整点分钟的倒计时, 比如当前时间是`14:13:21`, 那前端显示`00:39` 41 | 42 | 4. ROLLING BLOCK 43 | 44 | 45 | ![](2019-01-06-11-43-40.png) 46 | 47 | 信息提自 websocket消息的type0 48 | 49 | WS地址: ``wss://dappswin.io/api/ws`` 50 | 51 | ```json 52 | [{"type":0,"data":{"id":"01aec35a24c58a581bcd3b026becbacc1ec4fb4a1b198ceec4d99a381dec8f21","num":28230490,"time":1542035428500}}] 53 | ``` 54 | 55 | - id 放在 ``号码`` 上, 56 | - num放在``区块``, 57 | - time是timestamp的毫秒格式,前端只显示 23:10:28:5 58 | 59 | 4. 左上角的开奖记录 60 | 61 | ![](2019-01-06-11-47-18.png) 62 | 63 | 根据ws消息的type3里的gameid 显示 64 | 65 | ```json 66 | [{"type":3,"blocknum":28231644,"id":"01aec7dcce9b993e00e0fcad5e3a9e7ad546c9ef46b449b5f626eddc66f00f17","time":1542035428000,"data":{"gameid":15877,"result":286171}}] 67 | ``` 68 | 69 | 70 | 71 | 5. 投注限额 进度条 72 | 73 | ![](2019-01-06-11-48-23.png) 74 | 75 | 最大长度显示总余额的(第二步返回的结果)的5% 76 | 当前进度是 当前分钟里 ws消息里的 type1的 quantity":"10.0000 EOS"的总和 77 | 78 | 6. 投注组合 79 | 80 | 主要通过交易里的memo信息,后台负责解析并判定输赢 81 | 82 | 大: `b`, 小: `s`, 单:`o`, 双:`e` 83 | 数字是是用中括号括起来 `[1]` 84 | 85 | ![1.png](./lotter-1.png) 86 | 87 | 上图发起交易时,memo字段里放置 `lottery:s,o,[235]`, 此图是1星的玩法,前端显示当前注数时是累加的, 点中一个,累加一次,所以是5注,达标有5种中奖可能性 88 | 89 | 90 | ![2.png](./lottery-2.png) 91 | 92 | 上图发起交易时,memo的字段填写成 `lottery:[23][13][235]`, 此图是3星玩法,前端显示注数时,是2×2×3=12得到的。共12注,代表有12种中奖可能性 93 | 94 | 如何发起交易 可参考[demo](http://developer.mathwallet.org/sample01/)里的`转账`部分 95 | 96 | 97 | ~~- 注意:~~ 98 | 99 | ~~如果是被邀请进来的玩家,memo字段需要统一加上从Cookies里Get的邀请人信息 ``lottery:[23][13][235]@haztknzxhege``~~ 100 | 101 | ~~邀请人信息是官网的前端通过用户访问类似链接 `https://dappswin.io/link/haztknzxhege`, 自行记录到Cookies的。~~ 102 | 103 | 104 | 7. Records 开奖结果 105 | 106 | ![](2019-01-06-11-50-56.png) 107 | 108 | 显示 ws消息里的type3 109 | 110 | 从左到右分别取 .time所在的分钟值 data.gameid, data.result 111 | 112 | 8. LIVE 113 | 114 | ![](2019-01-06-11-50-29.png) 115 | 116 | 由ws消息里的type101,102,111,112构成,显示的是全网用户押注和收益的信息 117 | 118 | - 如果是type101 为EOS押注消息,从左到右以此取 data.from, data.quantity .time(精确到0.5秒) 119 | - 如果是type102 为EOS收益消息,从左到右以此取 data.to, data.amount, .time(精确到0.5秒) 120 | - 如果是type111 为CGG押注消息,从左到右以此取 data.from, data.amount, .time(精确到0.5秒) 121 | - 如果是type112 为CGG收益消息,从左到右以此取 data.to, data.amount, .time(精确到0.5秒) 122 | -------------------------------------------------------------------------------- /docs/platform.http: -------------------------------------------------------------------------------- 1 | 2 | @host=http://127.0.0.1:8080 3 | # @host=http://3.1.50.78 4 | 5 | @node=https://jungle.eos.dfuse.io 6 | 7 | ### 查询进度条 8 | POST {{host}}/api/chain/get_currency_balance HTTP/1.1 9 | Content-Type: application/json 10 | 11 | { 12 | "code": "eosio.token", 13 | "account": "cryptogame11", 14 | "symbol": "EOS" 15 | } 16 | 17 | 18 | ### 轮播图 19 | GET {{host}}/api/news HTTP/1.1 20 | Content-Type: application/json 21 | 22 | 23 | ### 绑定用户父级 24 | POST {{host}}/api/user/bind HTTP/1.1 25 | Content-Type: application/json 26 | 27 | { 28 | "name": "xiaopingeo09", 29 | "pname": "xiaopingeos2" 30 | } 31 | 32 | 33 | ### 我的下级 34 | POST {{host}}/api/user/page HTTP/1.1 35 | Content-Type: application/json 36 | 37 | { 38 | "name": "xiaopingeos2", 39 | "page_index": 1, 40 | "page_size": 10, 41 | "order_by": "total_bet" 42 | } 43 | 44 | 45 | # ### 我的佣金 46 | # POST {{host}}/api/user/page HTTP/1.1 47 | # Content-Type: application/json 48 | 49 | # { 50 | # "name": "xiaopingeos1", 51 | # "date_range": "2019/01/11-2019/01/13", 52 | # "page_index": 1, 53 | # "page_size": 10, 54 | # "order_by": "total_bet" 55 | # } 56 | 57 | 58 | ### 查询质押总量 59 | # percent是百分比%, 前端取到直接使用即可 60 | GET {{host}}/api/lock/total HTTP/1.1 61 | Content-Type: application/json 62 | 63 | 64 | ### 质押动作 65 | # 往 xiaopingeos4 发交易, {from: account.name, to: xiaopingeos4, "10.2000 CGG", "lock cgg"} 66 | 67 | 68 | ### 查询可质押CGG数量 69 | # api和我的余额CGG一样 70 | 71 | 72 | ### 查询已质押数量 73 | POST {{host}}/api/lock/staked HTTP/1.1 74 | Content-Type: application/json 75 | 76 | { 77 | "name": "xiaopingeos2" 78 | } 79 | 80 | 81 | ### 赎回动作 82 | POST {{host}}/api/lock/unstake HTTP/1.1 83 | Content-Type: application/json 84 | 85 | { 86 | "name": "xiaopingeos2", 87 | "amount": 0.1 88 | } 89 | 90 | 91 | ### 查询赎回状态 92 | POST {{host}}/api/lock/unstake/status HTTP/1.1 93 | Content-Type: application/json 94 | 95 | { 96 | "name": "xiaopingeos2" 97 | } 98 | 99 | 100 | ### 分红池 101 | POST {{host}}/api/bonus/pool HTTP/1.1 102 | Content-Type: application/json 103 | 104 | { 105 | "name": "xiaopingeos2" 106 | } 107 | 108 | 109 | ### 分红记录 110 | GET {{host}}/api/bonus/stats HTTP/1.1 111 | Content-Type: application/json 112 | 113 | 114 | ### 竞拍 115 | GET {{host}}/api/arena 116 | Content-Type: application/json 117 | 118 | 119 | ### 鲸鱼榜 120 | GET {{host}}/api/rank/stats_per_day 121 | Content-Type: application/json 122 | 123 | 124 | ### 幸运抽奖 - 查询是否具备抽奖条件 125 | POST {{host}}/api/lucky/status HTTP/1.1 126 | Content-Type: application/json 127 | 128 | { 129 | "name": "xiaopingeos2" 130 | } 131 | 132 | 133 | ### 幸运抽奖submit 134 | POST {{host}}/api/lucky/submit HTTP/1.1 135 | Content-Type: application/json 136 | 137 | { 138 | "name": "xiaopingeos2" 139 | } 140 | 141 | 142 | ### 我的EOS余额 143 | POST {{node}}/v1/chain/get_currency_balance HTTP/1.1 144 | Content-Type: application/json 145 | 146 | { 147 | "code": "eosio.token", 148 | "account": "xiaopingeos2", 149 | "symbol": "EOS" 150 | } 151 | 152 | 153 | ### 我的CGG余额 154 | POST {{node}}/v1/chain/get_currency_balance HTTP/1.1 155 | Content-Type: application/json 156 | 157 | { 158 | "code": "xxptoken1234", 159 | "account": "xiaopingeos2", 160 | "symbol": "CGG" 161 | } 162 | -------------------------------------------------------------------------------- /scripts/nginx/koi-utf: -------------------------------------------------------------------------------- 1 | 2 | # This map is not a full koi8-r <> utf8 map: it does not contain 3 | # box-drawing and some other characters. Besides this map contains 4 | # several koi8-u and Byelorussian letters which are not in koi8-r. 5 | # If you need a full and standard map, use contrib/unicode2nginx/koi-utf 6 | # map instead. 7 | 8 | charset_map koi8-r utf-8 { 9 | 10 | 80 E282AC ; # euro 11 | 12 | 95 E280A2 ; # bullet 13 | 14 | 9A C2A0 ; #   15 | 16 | 9E C2B7 ; # · 17 | 18 | A3 D191 ; # small yo 19 | A4 D194 ; # small Ukrainian ye 20 | 21 | A6 D196 ; # small Ukrainian i 22 | A7 D197 ; # small Ukrainian yi 23 | 24 | AD D291 ; # small Ukrainian soft g 25 | AE D19E ; # small Byelorussian short u 26 | 27 | B0 C2B0 ; # ° 28 | 29 | B3 D081 ; # capital YO 30 | B4 D084 ; # capital Ukrainian YE 31 | 32 | B6 D086 ; # capital Ukrainian I 33 | B7 D087 ; # capital Ukrainian YI 34 | 35 | B9 E28496 ; # numero sign 36 | 37 | BD D290 ; # capital Ukrainian soft G 38 | BE D18E ; # capital Byelorussian short U 39 | 40 | BF C2A9 ; # (C) 41 | 42 | C0 D18E ; # small yu 43 | C1 D0B0 ; # small a 44 | C2 D0B1 ; # small b 45 | C3 D186 ; # small ts 46 | C4 D0B4 ; # small d 47 | C5 D0B5 ; # small ye 48 | C6 D184 ; # small f 49 | C7 D0B3 ; # small g 50 | C8 D185 ; # small kh 51 | C9 D0B8 ; # small i 52 | CA D0B9 ; # small j 53 | CB D0BA ; # small k 54 | CC D0BB ; # small l 55 | CD D0BC ; # small m 56 | CE D0BD ; # small n 57 | CF D0BE ; # small o 58 | 59 | D0 D0BF ; # small p 60 | D1 D18F ; # small ya 61 | D2 D180 ; # small r 62 | D3 D181 ; # small s 63 | D4 D182 ; # small t 64 | D5 D183 ; # small u 65 | D6 D0B6 ; # small zh 66 | D7 D0B2 ; # small v 67 | D8 D18C ; # small soft sign 68 | D9 D18B ; # small y 69 | DA D0B7 ; # small z 70 | DB D188 ; # small sh 71 | DC D18D ; # small e 72 | DD D189 ; # small shch 73 | DE D187 ; # small ch 74 | DF D18A ; # small hard sign 75 | 76 | E0 D0AE ; # capital YU 77 | E1 D090 ; # capital A 78 | E2 D091 ; # capital B 79 | E3 D0A6 ; # capital TS 80 | E4 D094 ; # capital D 81 | E5 D095 ; # capital YE 82 | E6 D0A4 ; # capital F 83 | E7 D093 ; # capital G 84 | E8 D0A5 ; # capital KH 85 | E9 D098 ; # capital I 86 | EA D099 ; # capital J 87 | EB D09A ; # capital K 88 | EC D09B ; # capital L 89 | ED D09C ; # capital M 90 | EE D09D ; # capital N 91 | EF D09E ; # capital O 92 | 93 | F0 D09F ; # capital P 94 | F1 D0AF ; # capital YA 95 | F2 D0A0 ; # capital R 96 | F3 D0A1 ; # capital S 97 | F4 D0A2 ; # capital T 98 | F5 D0A3 ; # capital U 99 | F6 D096 ; # capital ZH 100 | F7 D092 ; # capital V 101 | F8 D0AC ; # capital soft sign 102 | F9 D0AB ; # capital Y 103 | FA D097 ; # capital Z 104 | FB D0A8 ; # capital SH 105 | FC D0AD ; # capital E 106 | FD D0A9 ; # capital SHCH 107 | FE D0A7 ; # capital CH 108 | FF D0AA ; # capital hard sign 109 | } 110 | -------------------------------------------------------------------------------- /scripts/nginx/win-utf: -------------------------------------------------------------------------------- 1 | # This map is not a full windows-1251 <> utf8 map: it does not 2 | # contain Serbian and Macedonian letters. If you need a full map, 3 | # use contrib/unicode2nginx/win-utf map instead. 4 | 5 | charset_map windows-1251 utf-8 { 6 | 7 | 82 E2809A; # single low-9 quotation mark 8 | 9 | 84 E2809E; # double low-9 quotation mark 10 | 85 E280A6; # ellipsis 11 | 86 E280A0; # dagger 12 | 87 E280A1; # double dagger 13 | 88 E282AC; # euro 14 | 89 E280B0; # per mille 15 | 16 | 91 E28098; # left single quotation mark 17 | 92 E28099; # right single quotation mark 18 | 93 E2809C; # left double quotation mark 19 | 94 E2809D; # right double quotation mark 20 | 95 E280A2; # bullet 21 | 96 E28093; # en dash 22 | 97 E28094; # em dash 23 | 24 | 99 E284A2; # trade mark sign 25 | 26 | A0 C2A0; #   27 | A1 D18E; # capital Byelorussian short U 28 | A2 D19E; # small Byelorussian short u 29 | 30 | A4 C2A4; # currency sign 31 | A5 D290; # capital Ukrainian soft G 32 | A6 C2A6; # borken bar 33 | A7 C2A7; # section sign 34 | A8 D081; # capital YO 35 | A9 C2A9; # (C) 36 | AA D084; # capital Ukrainian YE 37 | AB C2AB; # left-pointing double angle quotation mark 38 | AC C2AC; # not sign 39 | AD C2AD; # soft hypen 40 | AE C2AE; # (R) 41 | AF D087; # capital Ukrainian YI 42 | 43 | B0 C2B0; # ° 44 | B1 C2B1; # plus-minus sign 45 | B2 D086; # capital Ukrainian I 46 | B3 D196; # small Ukrainian i 47 | B4 D291; # small Ukrainian soft g 48 | B5 C2B5; # micro sign 49 | B6 C2B6; # pilcrow sign 50 | B7 C2B7; # · 51 | B8 D191; # small yo 52 | B9 E28496; # numero sign 53 | BA D194; # small Ukrainian ye 54 | BB C2BB; # right-pointing double angle quotation mark 55 | 56 | BF D197; # small Ukrainian yi 57 | 58 | C0 D090; # capital A 59 | C1 D091; # capital B 60 | C2 D092; # capital V 61 | C3 D093; # capital G 62 | C4 D094; # capital D 63 | C5 D095; # capital YE 64 | C6 D096; # capital ZH 65 | C7 D097; # capital Z 66 | C8 D098; # capital I 67 | C9 D099; # capital J 68 | CA D09A; # capital K 69 | CB D09B; # capital L 70 | CC D09C; # capital M 71 | CD D09D; # capital N 72 | CE D09E; # capital O 73 | CF D09F; # capital P 74 | 75 | D0 D0A0; # capital R 76 | D1 D0A1; # capital S 77 | D2 D0A2; # capital T 78 | D3 D0A3; # capital U 79 | D4 D0A4; # capital F 80 | D5 D0A5; # capital KH 81 | D6 D0A6; # capital TS 82 | D7 D0A7; # capital CH 83 | D8 D0A8; # capital SH 84 | D9 D0A9; # capital SHCH 85 | DA D0AA; # capital hard sign 86 | DB D0AB; # capital Y 87 | DC D0AC; # capital soft sign 88 | DD D0AD; # capital E 89 | DE D0AE; # capital YU 90 | DF D0AF; # capital YA 91 | 92 | E0 D0B0; # small a 93 | E1 D0B1; # small b 94 | E2 D0B2; # small v 95 | E3 D0B3; # small g 96 | E4 D0B4; # small d 97 | E5 D0B5; # small ye 98 | E6 D0B6; # small zh 99 | E7 D0B7; # small z 100 | E8 D0B8; # small i 101 | E9 D0B9; # small j 102 | EA D0BA; # small k 103 | EB D0BB; # small l 104 | EC D0BC; # small m 105 | ED D0BD; # small n 106 | EE D0BE; # small o 107 | EF D0BF; # small p 108 | 109 | F0 D180; # small r 110 | F1 D181; # small s 111 | F2 D182; # small t 112 | F3 D183; # small u 113 | F4 D184; # small f 114 | F5 D185; # small kh 115 | F6 D186; # small ts 116 | F7 D187; # small ch 117 | F8 D188; # small sh 118 | F9 D189; # small shch 119 | FA D18A; # small hard sign 120 | FB D18B; # small y 121 | FC D18C; # small soft sign 122 | FD D18D; # small e 123 | FE D18E; # small yu 124 | FF D18F; # small ya 125 | } 126 | -------------------------------------------------------------------------------- /scripts/nginx/sites-available/default: -------------------------------------------------------------------------------- 1 | ## 2 | # You should look at the following URL's in order to grasp a solid understanding 3 | # of Nginx configuration files in order to fully unleash the power of Nginx. 4 | # https://www.nginx.com/resources/wiki/start/ 5 | # https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/ 6 | # https://wiki.debian.org/Nginx/DirectoryStructure 7 | # 8 | # In most cases, administrators will remove this file from sites-enabled/ and 9 | # leave it as reference inside of sites-available where it will continue to be 10 | # updated by the nginx packaging team. 11 | # 12 | # This file will automatically load configuration files provided by other 13 | # applications, such as Drupal or Wordpress. These applications will be made 14 | # available underneath a path with that package name, such as /drupal8. 15 | # 16 | # Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. 17 | ## 18 | 19 | # Default server configuration 20 | 21 | server { 22 | listen 80 default_server; 23 | listen [::]:80 default_server; 24 | 25 | root /var/www/html; 26 | 27 | # Add index.php to the list if you are using PHP 28 | index index.html index.htm index.nginx-debian.html; 29 | 30 | server_name _; 31 | 32 | location / { 33 | # First attempt to serve request as file, then 34 | # as directory, then fall back to displaying a 404. 35 | try_files $uri $uri/ =404; 36 | } 37 | 38 | } 39 | 40 | server { 41 | root /var/www/html; 42 | 43 | # Add index.php to the list if you are using PHP 44 | index index.html index.htm index.nginx-debian.html; 45 | 46 | server_name dappswin.io www.dappswin.io; # managed by Certbot 47 | 48 | 49 | location / { 50 | # First attempt to serve request as file, then 51 | # as directory, then fall back to displaying a 404. 52 | try_files $uri $uri/ =404; 53 | } 54 | 55 | location ~* \.(jpg|jpeg|png|gif|ico)$ { 56 | expires 30d; 57 | } 58 | 59 | location ~* \.(css|js)$ { 60 | expires 7d; 61 | } 62 | 63 | location /api/ { 64 | proxy_pass http://localhost:8080; 65 | proxy_redirect off; 66 | proxy_set_header Host $host; 67 | proxy_set_header X-Real-IP $remote_addr; 68 | proxy_set_header REMOTE-HOST $remote_addr; 69 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 70 | proxy_connect_timeout 300; 71 | proxy_send_timeout 300; 72 | proxy_read_timeout 600; 73 | proxy_buffer_size 256k; 74 | proxy_buffers 4 256k; 75 | proxy_busy_buffers_size 256k; 76 | proxy_temp_file_write_size 256k; 77 | proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_404; 78 | proxy_max_temp_file_size 128m; 79 | } 80 | 81 | location /api/ws { 82 | proxy_pass http://localhost:8080; 83 | proxy_http_version 1.1; 84 | proxy_set_header Upgrade $http_upgrade; 85 | proxy_set_header Connection $connection_upgrade; 86 | } 87 | 88 | listen [::]:443 ssl ipv6only=on; # managed by Certbot 89 | listen 443 ssl; # managed by Certbot 90 | ssl_certificate /etc/letsencrypt/live/dappswin.io/fullchain.pem; # managed by Certbot 91 | ssl_certificate_key /etc/letsencrypt/live/dappswin.io/privkey.pem; # managed by Certbot 92 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 93 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 94 | 95 | } 96 | 97 | server { 98 | if ($host = www.dappswin.io) { 99 | return 301 https://$host$request_uri; 100 | } # managed by Certbot 101 | 102 | 103 | if ($host = dappswin.io) { 104 | return 301 https://$host$request_uri; 105 | } # managed by Certbot 106 | 107 | 108 | listen 80 ; 109 | listen [::]:80; 110 | server_name dappswin.io www.dappswin.io; 111 | return 404; # managed by Certbot 112 | } 113 | -------------------------------------------------------------------------------- /models/tx.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "dappswin/conf" 5 | "encoding/json" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/golang/glog" 10 | ) 11 | 12 | // TX lottery action. 13 | type TX struct { 14 | From string `json:"from"` 15 | To string `json:"to"` 16 | Quantity string `json:"quantity"` 17 | Memo string `json:"memo"` 18 | } 19 | 20 | // ResolveMemo return game, bet, account 21 | // 22 | // "lottery:[357][357]@parentacccount" 23 | func ResolveMemo(m string) (game, bet, account string) { 24 | var g, b, a string 25 | var str2 []string 26 | 27 | if !strings.Contains(m, ":") { 28 | return g, b, a 29 | } 30 | str := strings.Split(m, ":") 31 | g = str[0] 32 | 33 | if !strings.Contains(str[1], "@") { 34 | return g, str[1], a 35 | } 36 | str2 = strings.Split(str[1], "@") 37 | b = str2[0] 38 | a = str2[1] 39 | 40 | return g, b, a 41 | } 42 | 43 | // Amount return 0.0010 * 10000 44 | // 45 | // "0.0010 EOS" 46 | func (t *TX) Amount() uint64 { 47 | s := strings.Split(t.Quantity, " ") 48 | f, _ := strconv.ParseFloat(s[0], 64) 49 | return uint64(f * 1e4) 50 | } 51 | 52 | type TransactionResp struct { 53 | Status string `json:"status"` 54 | Trx interface{} `json:"trx"` 55 | } 56 | 57 | func (tx TransactionResp) GetActions() ([]Action, string) { 58 | actions := []Action{} 59 | if tx.Status != "executed" { 60 | return nil, "" 61 | } 62 | // tx.Trx is string 63 | if _, ok := tx.Trx.(string); ok { 64 | return nil, "" 65 | } 66 | // tx.Trx is map[string]interface {} 67 | trxBuf, _ := json.Marshal(tx.Trx) 68 | trxS := &TRX{} 69 | if err := json.Unmarshal(trxBuf, trxS); err != nil { 70 | glog.V(7).Infof("tx.Trx is not supported %v", err) 71 | return nil, "" 72 | } 73 | actions = trxS.TX.Actions 74 | return actions, trxS.ID 75 | } 76 | 77 | type TRX struct { 78 | ID string `json:"id"` 79 | TX Transaction `json:"transaction"` 80 | } 81 | 82 | type Action struct { 83 | Account string `json:"account"` 84 | Name string `json:"name"` 85 | Data TX `json:"data"` 86 | } 87 | 88 | func (a Action) Coin() string { 89 | switch a.Account { 90 | case "eosio.token": 91 | return "EOS" 92 | case conf.C.GetString("eos.TokenAccount"): 93 | return conf.C.GetString("eos.TokenSymbol") 94 | default: 95 | // glog.Warningf("not supported coin %s", a.Account) 96 | return "" 97 | } 98 | } 99 | 100 | func (a Action) IsTransfer() bool { 101 | if a.Name == "transfer" { 102 | return true 103 | } 104 | return false 105 | } 106 | 107 | type Transaction struct { 108 | Actions []Action `json:"actions"` 109 | } 110 | 111 | // Tx dave to DB 112 | type Tx struct { 113 | Id int64 `gorm:"PRIMARY_KEY" json:"id"` 114 | TxID string `gorm:"size:64" json:"hash"` 115 | BlockNum uint32 `json:"blocknum"` 116 | From string `json:"from"` 117 | To string `json:"to"` 118 | Amount float64 `json:"amount"` 119 | CoinID int `json:"coinID"` 120 | Memo string `json:"memo"` 121 | // 判断是否待处理 122 | Status int8 `gorm:"index:status" json:"status"` 123 | TimeMills int64 `json:"timestamp"` 124 | // 提取属于哪一期游戏 125 | TimeMintue int64 `gorm:"index:timemintue" json:"time_mintue"` 126 | } 127 | 128 | // AddTx insert a new Tx into database and returns 129 | // last inserted Id on success. 130 | func AddTx(m *Tx) (err error) { 131 | d := db.Create(m) 132 | return d.Error 133 | } 134 | 135 | func GetTxsInGame(time int64, status int) ([]Tx, error) { 136 | var txes []Tx 137 | db.Where("time_mintue = ? and status = ?", time, status).Find(&txes) 138 | return txes, db.Error 139 | } 140 | 141 | // UpdateTx updates Tx by Id and returns error if 142 | // the record to be updated doesn't exist 143 | func UpdateTxById(m *Tx) (err error) { 144 | db.Save(m) 145 | return db.Error 146 | } 147 | -------------------------------------------------------------------------------- /app/game.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | 7 | "dappswin/models" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/golang/glog" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | var isGameDone bool = true 15 | 16 | var gameResult string 17 | 18 | // 奖励是5位数字 19 | const gameCodeLen int = 6 20 | 21 | var cachedgameid int64 22 | 23 | var gameChan = make(chan *models.Block, 4096) 24 | 25 | func gameRoutine() { 26 | for { 27 | select { 28 | case block := <-gameChan: 29 | glog.V(7).Infof("Gamemsg Coming %v, %v, 游戏开奖结束%v", block.Hash, isNumber(block.LastLetter()), isGameDone) 30 | tm := block.Time / 1000 31 | if !isGameDone && isNumber(block.LastLetter()) { 32 | gameResult += block.LastLetter() 33 | glog.Infof("gameResult....: %s, len is %d, %d, BlockTime is %d", gameResult, len(gameResult), block.Num, block.Time) 34 | if len(gameResult) == gameCodeLen { 35 | // 记录成上一分钟的值,数据库好匹配 36 | content := getGameContent(gameResult) 37 | gameorm := &models.Game{Result: gameResult, BlockNum: block.Num, TimeStamp: tm, GameMintue: int64(tm/60) - 1, Content: content} 38 | if err := models.AddGame(gameorm); err != nil { 39 | glog.Errorf("insert game error %v", err) 40 | } 41 | gameID := tm / 60 42 | cachedgameid = gameID 43 | 44 | // 推送到待处理交易区,判定输赢 45 | winchan <- gameorm 46 | 47 | r, _ := strconv.ParseInt(gameResult, 10, 32) 48 | gamews := &GameWS{gameID, r, content} 49 | pushGameMessage(block, gamews) 50 | // TODO push to win.go 51 | isGameDone = true 52 | gameResult = "" 53 | } 54 | } else if isGameDone && tm%60 == 0 { 55 | // 揭晓上一分钟的号码 56 | totalVotedCGG = decimal.NewFromFloat(0) 57 | totalVotedEOS = decimal.NewFromFloat(0) 58 | isGameDone = false 59 | if isNumber(block.LastLetter()) { 60 | gameResult += block.LastLetter() 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | // 根据交易消息里的时间判定属于哪一期游戏,判定 68 | 69 | func pushGameMessage(blk *models.Block, game *GameWS) { 70 | m := &models.Message{winType, blk.Num, blk.Hash, blk.Time, game} 71 | m.HandleTimeStamp() 72 | // needless do this. block have handled it. 73 | // m.HandleTimeStamp() 74 | ms := []*models.Message{} 75 | ms = append(ms, m) 76 | result, _ := json.Marshal(ms) 77 | Huber.broadcast <- result 78 | } 79 | 80 | func isNumber(s string) bool { 81 | if _, err := strconv.Atoi(s); err == nil { 82 | return true 83 | } 84 | return false 85 | } 86 | 87 | type GameWS struct { 88 | ID int64 `json:"gameid"` 89 | Result int64 `json:"result"` 90 | Content string `json:"content"` 91 | } 92 | 93 | func getGameContent(result string) string { 94 | var content string 95 | if result[lastStar] >= '0' && result[lastStar] <= '4' { 96 | content = "s|" 97 | } else { 98 | content = "b|" 99 | } 100 | 101 | if result[lastStar]%2 == 0 { 102 | content += "e" 103 | } else { 104 | content += "o" 105 | } 106 | 107 | return content 108 | } 109 | 110 | type gamePagePost struct { 111 | PageIndex int `json:"page_index" binding:"required,gt=0,lt=100"` 112 | PageSize int `json:"page_size" binding:"required,gt=0,lt=100"` 113 | } 114 | 115 | type pageGameRsp struct { 116 | Count int `json:"total_items"` 117 | Pages int `json:"total_pages"` 118 | Data []*models.Game `json:"data"` 119 | } 120 | 121 | func pageLottery(c *gin.Context) { 122 | body := &gamePagePost{} 123 | if err := c.ShouldBind(body); err != nil { 124 | c.JSON(NewMsg(400, "输入参数有误")) 125 | return 126 | } 127 | games := []*models.Game{} 128 | var count int 129 | index := (body.PageIndex - 1) * body.PageSize 130 | 131 | if err := db.Where(models.Game{}).Offset(index).Limit(body.PageSize).Order("id desc").Find(&games).Error; err != nil { 132 | c.JSON(NewMsg(500, "系统内部错误")) 133 | return 134 | } 135 | if err := db.Model(models.Game{}).Count(&count).Error; err != nil { 136 | c.JSON(NewMsg(500, "系统内部错误")) 137 | return 138 | } 139 | c.JSON(NewMsg(200, &pageGameRsp{count, (count / body.PageSize) + 1, games})) 140 | } 141 | -------------------------------------------------------------------------------- /scripts/nginx/mime.types: -------------------------------------------------------------------------------- 1 | 2 | types { 3 | text/html html htm shtml; 4 | text/css css; 5 | text/xml xml; 6 | image/gif gif; 7 | image/jpeg jpeg jpg; 8 | application/javascript js; 9 | application/atom+xml atom; 10 | application/rss+xml rss; 11 | 12 | text/mathml mml; 13 | text/plain txt; 14 | text/vnd.sun.j2me.app-descriptor jad; 15 | text/vnd.wap.wml wml; 16 | text/x-component htc; 17 | 18 | image/png png; 19 | image/tiff tif tiff; 20 | image/vnd.wap.wbmp wbmp; 21 | image/x-icon ico; 22 | image/x-jng jng; 23 | image/x-ms-bmp bmp; 24 | image/svg+xml svg svgz; 25 | image/webp webp; 26 | 27 | application/font-woff woff; 28 | application/java-archive jar war ear; 29 | application/json json; 30 | application/mac-binhex40 hqx; 31 | application/msword doc; 32 | application/pdf pdf; 33 | application/postscript ps eps ai; 34 | application/rtf rtf; 35 | application/vnd.apple.mpegurl m3u8; 36 | application/vnd.ms-excel xls; 37 | application/vnd.ms-fontobject eot; 38 | application/vnd.ms-powerpoint ppt; 39 | application/vnd.wap.wmlc wmlc; 40 | application/vnd.google-earth.kml+xml kml; 41 | application/vnd.google-earth.kmz kmz; 42 | application/x-7z-compressed 7z; 43 | application/x-cocoa cco; 44 | application/x-java-archive-diff jardiff; 45 | application/x-java-jnlp-file jnlp; 46 | application/x-makeself run; 47 | application/x-perl pl pm; 48 | application/x-pilot prc pdb; 49 | application/x-rar-compressed rar; 50 | application/x-redhat-package-manager rpm; 51 | application/x-sea sea; 52 | application/x-shockwave-flash swf; 53 | application/x-stuffit sit; 54 | application/x-tcl tcl tk; 55 | application/x-x509-ca-cert der pem crt; 56 | application/x-xpinstall xpi; 57 | application/xhtml+xml xhtml; 58 | application/xspf+xml xspf; 59 | application/zip zip; 60 | 61 | application/octet-stream bin exe dll; 62 | application/octet-stream deb; 63 | application/octet-stream dmg; 64 | application/octet-stream iso img; 65 | application/octet-stream msi msp msm; 66 | 67 | application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; 68 | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; 69 | application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; 70 | 71 | audio/midi mid midi kar; 72 | audio/mpeg mp3; 73 | audio/ogg ogg; 74 | audio/x-m4a m4a; 75 | audio/x-realaudio ra; 76 | 77 | video/3gpp 3gpp 3gp; 78 | video/mp2t ts; 79 | video/mp4 mp4; 80 | video/mpeg mpeg mpg; 81 | video/quicktime mov; 82 | video/webm webm; 83 | video/x-flv flv; 84 | video/x-m4v m4v; 85 | video/x-mng mng; 86 | video/x-ms-asf asx asf; 87 | video/x-ms-wmv wmv; 88 | video/x-msvideo avi; 89 | } 90 | -------------------------------------------------------------------------------- /app/block.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "dappswin/models" 12 | 13 | "github.com/golang/glog" 14 | ) 15 | 16 | const ( 17 | blockType = iota 18 | txType 19 | gameType 20 | winType 21 | ) 22 | 23 | var cachedHeadNum uint32 24 | 25 | type InfoRsp struct { 26 | Num uint32 `json:"head_block_num"` 27 | } 28 | 29 | type BlockRsp struct { 30 | Hash string `json:"id"` 31 | Num uint32 `json:"block_num"` 32 | Time string `json:"timestamp"` 33 | Txs []models.TransactionResp `json:"transactions"` 34 | } 35 | 36 | func getBlockByNum(num uint32) (*BlockRsp, error) { 37 | params := fmt.Sprintf(`{"block_num_or_id": %d}`, num) 38 | url := eosConf.RPCURL + "/v1/chain/get_block" 39 | 40 | timeout := time.Duration(5 * time.Second) 41 | client := http.Client{ 42 | Timeout: timeout, 43 | } 44 | 45 | resp, err := client.Post(url, "application/json", strings.NewReader(params)) 46 | if nil != err { 47 | glog.Errorf("getBlockByNum - http.Post(%s) with params %s failed : %v", url, params, err) 48 | return nil, err 49 | } 50 | defer resp.Body.Close() 51 | 52 | buf, err := ioutil.ReadAll(resp.Body) 53 | if nil != err { 54 | glog.Errorf("getBlockByNum - ioutil.ReadAll failed : %v", err) 55 | return nil, err 56 | } 57 | blk := &BlockRsp{} 58 | if err = json.Unmarshal(buf, blk); nil != err { 59 | glog.Errorf("getBlockByNum - json.Unmarshall failed : %v, %s", err, string(buf)) 60 | return nil, err 61 | } 62 | 63 | return blk, nil 64 | } 65 | 66 | func resolveBlock(num uint32, retry int) { 67 | glog.V(7).Infof("resolving Block num: %d", num) 68 | var blkRsp = &BlockRsp{} 69 | var err error 70 | for i := 0; i < retry; i++ { 71 | blkRsp, err = getBlockByNum(num) 72 | if err == nil && blkRsp.Num >= cachedHeadNum { 73 | break 74 | } 75 | } 76 | // handle 77 | if err != nil || blkRsp.Hash == "" { 78 | glog.Errorf("!!!!!!!!!Error for getting block numer: %d, %v", num, err) 79 | return 80 | } 81 | 82 | tm, _ := time.Parse("2006-01-02T15:04:05.999999999", blkRsp.Time) 83 | timemills := tm.UnixNano() / 1e6 84 | glog.V(7).Infof("timemills is %#v", blkRsp.Txs) 85 | if len(blkRsp.Txs) != 0 { 86 | txschan <- &models.Message{Type: txType, BlockNum: num, Hash: "", TimeMills: timemills, Data: blkRsp.Txs} 87 | } 88 | 89 | blk := models.Block{blkRsp.Hash, blkRsp.Num, timemills} 90 | 91 | // 游戏轮数需要统计 92 | glog.V(7).Infof("Pushing Game needed block... %#v", blk) 93 | gameChan <- &blk 94 | 95 | // 广播区块信息 96 | msg := blk.Message() 97 | Huber.broadcast <- msg 98 | 99 | if err := models.AddBlock(&blk); err != nil { 100 | glog.Error("save Block error", err) 101 | } 102 | } 103 | 104 | func getHeadNum() uint32 { 105 | url := eosConf.RPCURL + "/v1/chain/get_info" 106 | timeout := time.Duration(5 * time.Second) 107 | client := http.Client{ 108 | Timeout: timeout, 109 | } 110 | resp, err := client.Post(url, "application/json", nil) 111 | if nil != err { 112 | glog.Errorf("getBlockByNum - http.Post(%s) failed : %v", url, err) 113 | return 0 114 | } 115 | defer resp.Body.Close() 116 | 117 | buf, err := ioutil.ReadAll(resp.Body) 118 | if nil != err { 119 | glog.Errorf("getBlockByNum - ioutil.ReadAll failed : %v", err) 120 | return 0 121 | } 122 | info := &InfoRsp{} 123 | if err = json.Unmarshal(buf, info); nil != err { 124 | glog.Errorf("getBlockByNum - json.Unmarshall failed : %v", err) 125 | return 0 126 | } 127 | 128 | return info.Num 129 | } 130 | 131 | func ResolveRoutine() { 132 | ticker := time.NewTicker(time.Duration(eosConf.FetchIdleDur) * time.Millisecond) 133 | defer func() { 134 | ticker.Stop() 135 | }() 136 | 137 | blk, err := models.GetLastestBlock() 138 | glog.Warningf("get Latest block is %v", blk) 139 | if err != nil { 140 | glog.Errorf("get Latest Block error %s", err.Error()) 141 | } 142 | if blk != nil && blk.Num > eosConf.FromBlkNum { 143 | cachedHeadNum = blk.Num 144 | } else { 145 | cachedHeadNum = eosConf.FromBlkNum 146 | } 147 | // cachedHeadNum = 36497629 148 | for { 149 | select { 150 | case <-ticker.C: 151 | ticker.Stop() 152 | head := getHeadNum() 153 | // head = 36497630 154 | glog.Infof("head num is %d, cached is %d", head, cachedHeadNum) 155 | 156 | for i := cachedHeadNum + 1; i <= head; i++ { 157 | resolveBlock(i, 7) 158 | cachedHeadNum = i 159 | } 160 | 161 | ticker = time.NewTicker(time.Duration(eosConf.FetchIdleDur) * time.Millisecond) 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /app/ws_hub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package app 6 | 7 | import ( 8 | "bytes" 9 | "log" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/gorilla/websocket" 14 | ) 15 | 16 | const ( 17 | // Time allowed to write a message to the peer. 18 | writeWait = 10 * time.Second 19 | 20 | // Time allowed to read the next pong message from the peer. 21 | pongWait = 60 * time.Second 22 | 23 | // Send pings to peer with this period. Must be less than pongWait. 24 | pingPeriod = (pongWait * 9) / 10 25 | 26 | // Maximum message size allowed from peer. 27 | maxMessageSize = 512 28 | ) 29 | 30 | var ( 31 | newline = []byte{'\n'} 32 | space = []byte{' '} 33 | ) 34 | 35 | // TODO , disable CheckOrigin in production. 36 | var upgrader = websocket.Upgrader{ 37 | ReadBufferSize: 1024, 38 | WriteBufferSize: 1024, 39 | CheckOrigin: func(r *http.Request) bool { 40 | return true 41 | }, 42 | } 43 | 44 | // Client is a middleman between the websocket connection and the hub. 45 | type Client struct { 46 | hub *Hub 47 | 48 | // The websocket connection. 49 | conn *websocket.Conn 50 | 51 | // Buffered channel of outbound messages. 52 | send chan []byte 53 | } 54 | 55 | // readPump pumps messages from the websocket connection to the hub. 56 | // 57 | // The application runs readPump in a per-connection goroutine. The application 58 | // ensures that there is at most one reader on a connection by executing all 59 | // reads from this goroutine. 60 | func (c *Client) readPump() { 61 | defer func() { 62 | c.hub.unregister <- c 63 | c.conn.Close() 64 | }() 65 | c.conn.SetReadLimit(maxMessageSize) 66 | c.conn.SetReadDeadline(time.Now().Add(pongWait)) 67 | c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 68 | for { 69 | _, message, err := c.conn.ReadMessage() 70 | if err != nil { 71 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 72 | log.Printf("error: %v", err) 73 | } 74 | break 75 | } 76 | message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) 77 | c.hub.broadcast <- message 78 | } 79 | } 80 | 81 | // writePump pumps messages from the hub to the websocket connection. 82 | // 83 | // A goroutine running writePump is started for each connection. The 84 | // application ensures that there is at most one writer to a connection by 85 | // executing all writes from this goroutine. 86 | func (c *Client) writePump() { 87 | ticker := time.NewTicker(pingPeriod) 88 | defer func() { 89 | ticker.Stop() 90 | c.hub.unregister <- c 91 | c.conn.Close() 92 | }() 93 | for { 94 | select { 95 | case message, ok := <-c.send: 96 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 97 | if !ok { 98 | // The hub closed the channel. 99 | c.conn.WriteMessage(websocket.CloseMessage, []byte{}) 100 | return 101 | } 102 | 103 | w, err := c.conn.NextWriter(websocket.TextMessage) 104 | if err != nil { 105 | return 106 | } 107 | w.Write(message) 108 | 109 | // Add queued chat messages to the current websocket message. 110 | n := len(c.send) 111 | for i := 0; i < n; i++ { 112 | w.Write(newline) 113 | w.Write(<-c.send) 114 | } 115 | 116 | if err := w.Close(); err != nil { 117 | return 118 | } 119 | case <-ticker.C: 120 | c.conn.SetWriteDeadline(time.Now().Add(writeWait)) 121 | if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { 122 | return 123 | } 124 | } 125 | } 126 | } 127 | 128 | // Hub maintains the set of active clients and broadcasts messages to the 129 | // clients. 130 | type Hub struct { 131 | // Registered clients. 132 | clients map[*Client]bool 133 | 134 | // Inbound messages from the clients. 135 | broadcast chan []byte 136 | 137 | // Register requests from the clients. 138 | register chan *Client 139 | 140 | // Unregister requests from clients. 141 | unregister chan *Client 142 | } 143 | 144 | func newHub() *Hub { 145 | return &Hub{ 146 | broadcast: make(chan []byte, 40960), 147 | register: make(chan *Client, 1024), 148 | unregister: make(chan *Client, 1024), 149 | clients: make(map[*Client]bool), 150 | } 151 | } 152 | 153 | func (h *Hub) run() { 154 | for { 155 | select { 156 | case client := <-h.register: 157 | h.clients[client] = true 158 | case client := <-h.unregister: 159 | if _, ok := h.clients[client]; ok { 160 | delete(h.clients, client) 161 | close(client.send) 162 | } 163 | case message := <-h.broadcast: 164 | for client := range h.clients { 165 | select { 166 | case client.send <- message: 167 | default: 168 | close(client.send) 169 | delete(h.clients, client) 170 | } 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /app/staking.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "dappswin/common" 5 | "dappswin/conf" 6 | "dappswin/models" 7 | "fmt" 8 | "strconv" 9 | "sync" 10 | "time" 11 | 12 | "github.com/gin-gonic/gin" 13 | "github.com/golang/glog" 14 | "github.com/shopspring/decimal" 15 | ) 16 | 17 | const ( 18 | staked = iota 19 | unstaking 20 | released 21 | ) 22 | 23 | // TotalLockedAmount 质押的总数量 24 | var TotalLockedAmount float64 25 | var totalSync sync.RWMutex 26 | 27 | func setTotalLockedAmount(amount float64) { 28 | totalSync.Lock() 29 | defer totalSync.Unlock() 30 | TotalLockedAmount = amount 31 | } 32 | 33 | func getTotalLockedAmount() float64 { 34 | totalSync.RLock() 35 | defer totalSync.RUnlock() 36 | cached := TotalLockedAmount 37 | return cached 38 | } 39 | 40 | // CirculatingAmount 市场流通的额度 41 | var CirculatingAmount float64 42 | var circulatSync sync.RWMutex 43 | 44 | func setCirculatingAmount(amount float64) { 45 | circulatSync.Lock() 46 | defer circulatSync.Unlock() 47 | CirculatingAmount = amount 48 | } 49 | 50 | func getCirculatingAmount() float64 { 51 | circulatSync.RLock() 52 | defer circulatSync.RUnlock() 53 | cached := CirculatingAmount 54 | return cached 55 | } 56 | 57 | func updateTotalLocked() { 58 | result := getBalance(eosConf.LockAccount, eosConf.TokenAccount, eosConf.TokenSymbol) 59 | if result == "" { 60 | return 61 | } 62 | amount, _ := strconv.ParseFloat(result, 64) 63 | glog.Info("Total locked amount is ", amount) 64 | setTotalLockedAmount(amount) 65 | } 66 | 67 | func updateCirculat() { 68 | result := getBalance(eosConf.OfficialLockAccount, eosConf.TokenAccount, eosConf.TokenSymbol) 69 | if result == "" { 70 | return 71 | } 72 | amount, _ := strconv.ParseFloat(result, 64) 73 | glog.Info("official locked amount is ", amount) 74 | setCirculatingAmount(eosConf.TotalCGGAmoount - amount) 75 | } 76 | 77 | func locktotalStatus(c *gin.Context) { 78 | c.JSON(NewMsg(200, map[string]interface{}{ 79 | "total_locked": TotalLockedAmount, 80 | "circulating": CirculatingAmount, 81 | "percent": fmt.Sprintf("%0.4f", TotalLockedAmount/CirculatingAmount*100), 82 | })) 83 | } 84 | 85 | type Account struct { 86 | Name string `json:"name" binding:"required,max=12"` 87 | } 88 | 89 | func stakedStatus(c *gin.Context) { 90 | body := Account{} 91 | if err := c.ShouldBind(&body); err != nil { 92 | c.JSON(NewMsg(400, "输入参数错误")) 93 | return 94 | } 95 | 96 | stake := models.Stake{} 97 | db.Where("name = ? AND status = ?", body.Name, staked).First(&stake) 98 | 99 | c.JSON(NewMsg(200, map[string]interface{}{ 100 | "staked": stake.Amount, 101 | "percent": stake.Amount.Div(decimal.NewFromFloat(TotalLockedAmount)).Mul(decimal.NewFromFloat(100)).StringFixed(2), 102 | })) 103 | 104 | } 105 | 106 | type unstakePost struct { 107 | Name string `json:"name" binding:"required,max=12"` 108 | Amount float64 `json:"amount" binding:"required,gt=0"` 109 | } 110 | 111 | func unstake(c *gin.Context) { 112 | body := unstakePost{} 113 | if err := c.ShouldBind(&body); err != nil { 114 | c.JSON(NewMsg(400, "输入参数错误")) 115 | return 116 | } 117 | 118 | stake := models.Stake{} 119 | db.Where("name = ? AND status = ?", body.Name, staked).First(&stake) 120 | if stake.Amount.LessThan(decimal.NewFromFloat(body.Amount)) { 121 | c.JSON(NewMsg(400, "赎回的数额大于实际质押的了")) 122 | return 123 | } 124 | amount := stake.Amount.Sub(decimal.NewFromFloat(body.Amount)) 125 | // TODO 事务 126 | db.Model(&models.Stake{}).Where("name = ? AND status = ?", body.Name, staked).Update("amount", amount) 127 | unstake := models.Stake{} 128 | if unfound := db.Where("name = ? AND status = ?", body.Name, unstaking).First(&unstake).RecordNotFound(); unfound { 129 | db.Save(&models.Stake{Name: body.Name, Amount: decimal.NewFromFloat(body.Amount), Date: common.JSONTime{Time: time.Now().Add(conf.C.GetDuration("eos.UnstakePeriod"))}, Status: unstaking}) 130 | c.JSON(NewMsg(200, "赎回成功,在排队中")) 131 | return 132 | } 133 | unAmount := unstake.Amount.Add(decimal.NewFromFloat(body.Amount)) 134 | db.Model(&models.Stake{}).Where("name = ? AND status = ?", body.Name, unstaking).Update(&models.Stake{Amount: unAmount, Date: common.JSONTime{Time: time.Now().Add(conf.C.GetDuration("eos.UnstakePeriod"))}}) 135 | 136 | c.JSON(NewMsg(200, "赎回成功,在排队中")) 137 | 138 | } 139 | 140 | func unstakeStatus(c *gin.Context) { 141 | body := Account{} 142 | if err := c.ShouldBind(&body); err != nil { 143 | c.JSON(NewMsg(400, "输入参数错误")) 144 | return 145 | } 146 | 147 | stake := models.Stake{} 148 | db.Where("name = ? AND status = ?", body.Name, unstaking).First(&stake) 149 | 150 | c.JSON(NewMsg(200, stake)) 151 | 152 | } 153 | 154 | func checkNotifyRoutine() { 155 | ticker := time.NewTicker(1 * time.Minute) 156 | defer func() { 157 | ticker.Stop() 158 | }() 159 | 160 | for { 161 | select { 162 | case <-ticker.C: 163 | glog.Info("=====检查是有需要赎回到期的。。。。。。。") 164 | 165 | now := time.Now() 166 | 167 | index, limit := 0, 100 168 | for index = 0; ; index += limit { 169 | stakes := []models.Stake{} 170 | if nok := db.Model(&models.Stake{}). 171 | Where("status = ? AND date <= ?", unstaking, now). 172 | Offset(index).Limit(limit).Find(&stakes).RecordNotFound(); nok { 173 | break 174 | } 175 | 176 | for _, stake := range stakes { 177 | // key := fmt.Sprintf("%d@%s@%d", indivi.ID, indivi.TakeUpdateTime, indivi.TakeCount) 178 | // if sentMessages[key] { 179 | // continue 180 | // } 181 | db.Delete(&stake) 182 | sendTokens(eosConf.LockAccount, stake.Name, stake.Amount.StringFixed(4)+" CGG", "unstaked=>来自赎回的质押CGG") 183 | glog.Infof("=======赎回到期了 %#v", stake) 184 | 185 | // sentMessages[key] = true 186 | } 187 | 188 | if len(stakes) < limit { 189 | break 190 | } 191 | 192 | } 193 | 194 | } 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /app/tx.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "dappswin/common" 5 | "encoding/json" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "dappswin/models" 11 | 12 | "github.com/gin-gonic/gin" 13 | "github.com/golang/glog" 14 | "github.com/shopspring/decimal" 15 | ) 16 | 17 | // TODO creat message hub, switch message.Data.(type) 18 | var txschan = make(chan *models.Message, 4096) 19 | var votechan = make(chan *models.Tx, 4096) 20 | 21 | func resloveTXRoutine() { 22 | for { 23 | select { 24 | case txsMsg := <-txschan: 25 | // TODO save to db.tx 26 | txs, ok := txsMsg.Data.([]models.TransactionResp) 27 | if !ok { 28 | glog.Error("txs asscert failed!") 29 | break 30 | } 31 | 32 | txsmsg := []*models.Message{} 33 | for _, tx := range txs { 34 | // glog.Infof("%#v", tx) 35 | actions, hash := tx.GetActions() 36 | if actions == nil { 37 | continue 38 | } 39 | 40 | glog.V(7).Infof("%#v", actions) 41 | for _, action := range actions { 42 | if !action.IsTransfer() { 43 | continue 44 | } 45 | coinName := action.Coin() 46 | if coinName == "" { 47 | continue 48 | } 49 | if msg := handleTX(coinName, hash, action, txsMsg); msg != nil { 50 | txsmsg = append(txsmsg, msg) 51 | } 52 | } 53 | } 54 | if len(txsmsg) != 0 { 55 | buf, _ := json.Marshal(txsmsg) 56 | Huber.broadcast <- buf 57 | } 58 | } 59 | } 60 | } 61 | 62 | func handleTX(coin string, hash string, action models.Action, txsMsg *models.Message) *models.Message { 63 | msg := models.Message{} 64 | glog.V(7).Infof("coin %s to %s", coin, action.Data.To) 65 | if eosConf.EnableICO && coin == "EOS" && action.Data.To == eosConf.ICOAccount { 66 | t := models.TX{Quantity: action.Data.Quantity} 67 | ico := &models.ICO{Hash: hash, Account: action.Data.From, Amount: t.Amount(), Status: pending, TimeMills: txsMsg.TimeMills} 68 | models.AddIcoRecord(ico) 69 | icochan <- ico 70 | return nil 71 | } 72 | 73 | // 操作质押的逻辑 74 | if coin == "CGG" && action.Data.To == eosConf.LockAccount { 75 | str := strings.Split(action.Data.Quantity, " ") 76 | amount, _ := strconv.ParseFloat(str[0], 64) 77 | // db.Save(&models.Stake{Name: action.Data.From, Amount: amount, Date: common.JSONTime{Time: time.Now().Add(24 * time.Hour)}, Status: staked}) 78 | stake := models.Stake{Name: action.Data.From, Amount: decimal.NewFromFloat(amount), Date: common.JSONTime{Time: time.Now()}, Status: staked} 79 | if unfound := db.Where("name = ?", action.Data.From).First(&stake).RecordNotFound(); unfound { 80 | db.Save(&stake) 81 | return nil 82 | } 83 | decimalAmount := stake.Amount.Add(decimal.NewFromFloat(amount)) 84 | db.Model(&models.Stake{}).Where("name = ?", action.Data.From).Update(&models.Stake{Amount: decimalAmount}) 85 | return nil 86 | } 87 | 88 | if action.Data.To != eosConf.GameAccount { 89 | return nil 90 | } 91 | 92 | game, _, _ := models.ResolveMemo(action.Data.Memo) 93 | 94 | msg.BlockNum = txsMsg.BlockNum 95 | msg.TimeMills = txsMsg.TimeMills 96 | t, ok := wsTypes[game+coin+"Buy"] 97 | if !ok { 98 | return nil 99 | } 100 | msg.Type = t 101 | msg.Hash = hash 102 | msg.Data = action.Data 103 | 104 | str := strings.Split(action.Data.Quantity, " ") 105 | amount, _ := strconv.ParseFloat(str[0], 64) 106 | glog.Infof("Coming Bet is %s, %s, %s, %f, timemills: %d , %d期游戏", action.Data.Quantity, str[0], action.Data.From, amount, txsMsg.TimeMills, txsMsg.TimeMills/1000/60) 107 | 108 | txdb := &models.Tx{ 109 | TxID: hash, BlockNum: txsMsg.BlockNum, 110 | From: action.Data.From, To: action.Data.To, Amount: amount, CoinID: coinIDs[coin], Memo: action.Data.Memo, 111 | Status: pending, TimeMills: txsMsg.TimeMills, TimeMintue: txsMsg.TimeMills / 1000 / 60} 112 | // 计算累积投注 113 | votechan <- txdb 114 | 115 | go models.AddTx(txdb) 116 | go updateUsersFromTX(txdb) 117 | return &msg 118 | } 119 | 120 | func votedRoutine() { 121 | for { 122 | select { 123 | case vote := <-votechan: 124 | glog.Infof("计算累积投注 %#v, cachegameid is %d", vote, cachedgameid) 125 | // if cachedgameid != 0 && vote.TimeMills/1000/60 > cachedgameid { 126 | // if vote.CoinID == eos { 127 | // totalVotedEOS = decimal.NewFromFloat(vote.Amount) 128 | // } else if vote.CoinID == cgg { 129 | // totalVotedCGG = decimal.NewFromFloat(vote.Amount) 130 | // } 131 | // pushVoteMsg(vote) 132 | // break 133 | // } 134 | 135 | if vote.CoinID == eos { 136 | totalVotedEOS = totalVotedEOS.Add(decimal.NewFromFloat(vote.Amount)) 137 | } else if vote.CoinID == cgg { 138 | totalVotedCGG = totalVotedCGG.Add(decimal.NewFromFloat(vote.Amount)) 139 | } 140 | 141 | pushVoteMsg(vote) 142 | 143 | } 144 | } 145 | } 146 | 147 | func pushVoteMsg(vote *models.Tx) { 148 | votemsgs := []*models.Message{} 149 | votemsg := &models.Message{} 150 | votemsg.Type = wsTypes["lottery"+coinNames[vote.CoinID]+"TotalVoted"] 151 | votemsg.BlockNum = vote.BlockNum 152 | votemsg.Hash = vote.TxID 153 | votemsg.TimeMills = vote.TimeMills 154 | if vote.CoinID == eos { 155 | votemsg.Data = map[string]string{"total_voted": totalVotedEOS.StringFixed(4)} 156 | } else if vote.CoinID == cgg { 157 | votemsg.Data = map[string]string{"total_voted": totalVotedCGG.StringFixed(4)} 158 | } 159 | votemsgs = append(votemsgs, votemsg) 160 | buf, _ := json.Marshal(votemsgs) 161 | Huber.broadcast <- buf 162 | } 163 | 164 | type pageTXPost struct { 165 | PageIndex int `json:"page_index" binding:"required,gt=0,lt=100"` 166 | PageSize int `json:"page_size" binding:"required,gt=0,lt=100"` 167 | Name string `json:"name" binding:"required,max=12"` 168 | } 169 | 170 | type pageTXRsp struct { 171 | Count int `json:"count"` 172 | Data []*models.Tx `json:"data"` 173 | } 174 | 175 | func pageTxes(c *gin.Context) { 176 | body := &pageTXPost{} 177 | if err := c.ShouldBind(body); err != nil { 178 | c.JSON(NewMsg(400, "输入参数有误")) 179 | return 180 | } 181 | txes := []*models.Tx{} 182 | var count int 183 | index := (body.PageIndex - 1) * body.PageSize 184 | 185 | if err := db.Where(models.Tx{From: body.Name}).Offset(index).Limit(body.PageSize).Order("time_mintue desc").Find(&txes).Count(&count).Error; err != nil { 186 | c.JSON(NewMsg(500, "系统内部错误")) 187 | return 188 | } 189 | 190 | c.JSON(NewMsg(200, &pageTXRsp{count, txes})) 191 | } 192 | -------------------------------------------------------------------------------- /app/user.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "dappswin/conf" 5 | "dappswin/models" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/golang/glog" 12 | "github.com/jinzhu/gorm" 13 | "github.com/shopspring/decimal" 14 | ) 15 | 16 | const invitedLevel = 9 17 | 18 | type InvitedUserPost struct { 19 | PageIndex int `json:"page_index" binding:"required,gt=0,lt=100"` 20 | OrderBy string `json:"order_by" binding:"required,len=9"` 21 | PageSize int `json:"page_size" binding:"required,gt=0,lt=100"` 22 | Name string `json:"name" binding:"required,max=12"` 23 | } 24 | 25 | type InvitedUserRsp struct { 26 | Count int `json:"total_items"` 27 | Pages int `json:"total_pages"` 28 | Data []*models.User `json:"data"` 29 | } 30 | 31 | func pageUser(c *gin.Context) { 32 | body := &InvitedUserPost{} 33 | if err := c.ShouldBind(body); err != nil { 34 | c.JSON(NewMsg(400, "输入参数有误")) 35 | // c.JSON(NewMsg(400, err.Error())) 36 | return 37 | } 38 | users := []*models.User{} 39 | // user := models.User{} 40 | var count int 41 | index := (body.PageIndex - 1) * body.PageSize 42 | 43 | if err := db.Where(models.User{PName: body.Name}).Offset(index).Limit(body.PageSize).Order(body.OrderBy + " desc").Find(&users).Error; err != nil { 44 | c.JSON(NewMsg(500, "系统内部错误")) 45 | return 46 | } 47 | 48 | if err := db.Model(models.User{}).Where(models.User{PName: body.Name}).Count(&count).Error; err != nil { 49 | c.JSON(NewMsg(500, "系统内部错误")) 50 | return 51 | } 52 | // // 这个业务需求需要加上自己到最前边, 送到前端展示 53 | // if unfound := db.Where(models.User{Name: body.Name}).First(&user).RecordNotFound(); !unfound { 54 | // users = append([]*models.User{&user}, users...) 55 | // } 56 | 57 | c.JSON(NewMsg(200, &InvitedUserRsp{count, (count / body.PageSize) + 1, users})) 58 | } 59 | 60 | func dateUser(c *gin.Context) { 61 | c.JSON(200, "NULL") 62 | } 63 | 64 | type loginUserPost struct { 65 | Name string `json:"name" binding:"required,max=12"` 66 | PName string `json:"pname" binding:"required,max=12"` 67 | } 68 | 69 | func bindUser(c *gin.Context) { 70 | body := &loginUserPost{} 71 | if err := c.ShouldBind(body); err != nil { 72 | c.JSON(NewMsg(400, "输入参数有误")) 73 | return 74 | } 75 | user := models.User{} 76 | if err := db.Create(&models.User{Name: body.Name, PName: body.PName}).Error; err != nil { 77 | c.JSON(NewMsg(400, "被推荐用户已经存在,绑定无效")) 78 | return 79 | } 80 | // if user.PName != "" { 81 | // c.JSON(NewMsg(400, "已经绑定过了")) 82 | // return 83 | // } 84 | user.Name = body.Name 85 | 86 | // update pnames 87 | pUser := models.User{} 88 | if unfound := db.Where("name = ?", body.PName).First(&pUser).RecordNotFound(); unfound { 89 | if err := db.Create(&models.User{Name: body.PName}).Error; err != nil { 90 | c.JSON(NewMsg(500, "系统内部错误")) 91 | return 92 | } 93 | } 94 | user.PNames = genPnames(body.PName, pUser.PNames) 95 | 96 | if err := db.Model(&user).Where("name = ?", body.Name).Update(&user).Error; err != nil { 97 | c.JSON(NewMsg(500, "系统内部错误")) 98 | return 99 | } 100 | glog.Info("user.Pnames is ", user.PNames) 101 | go updatePnamesChildren(user.PNames) 102 | 103 | c.JSON(NewMsg(200, "绑定成功")) 104 | } 105 | 106 | // xiaopingeob6,xiaopingeob5,xiaopingeob4,xiaopingeob3,xiaopingeob2,xiaopingeob1,xiaopingeoa5,xiaopingeoa4,xiaopingeoa3 107 | // 只取父级的前8代奖励 108 | func genPnames(pname string, pnames string) string { 109 | var result = pname 110 | for index, name := range strings.SplitN(pnames, ",", invitedLevel) { 111 | // index 从零开始的 112 | if name == "" || index == invitedLevel-1 { 113 | break 114 | } 115 | result += "," + name 116 | } 117 | return result 118 | } 119 | 120 | // 往上更新9代的children count 121 | func updatePnamesChildren(pnames string) error { 122 | var err error 123 | for index, pname := range strings.SplitN(pnames, ",", invitedLevel) { 124 | if pname == "" { 125 | break 126 | } 127 | glog.Info(index, pname) 128 | err = db.Model(&models.User{}).Where("name = ?", pname).Update("children_count", gorm.Expr("children_count + ?", 1)).Error 129 | if err != nil { 130 | glog.Error(err) 131 | break 132 | } 133 | } 134 | return err 135 | } 136 | 137 | // Generated by https://quicktype.io 138 | 139 | type DateUserPost struct { 140 | PageIndex int64 `json:"page_index" binding:"required,gt=0,lt=100"` 141 | PageSize int64 `json:"page_size" binding:"required,gt=0,lt=100"` 142 | Date []string `json:"date" binding:"required,len=2"` 143 | PName string `json:"pid" binding:"required,max=12"` 144 | } 145 | 146 | var invitedReward = [invitedLevel]float64{0.1, 0.05, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02} 147 | 148 | func updateUsersFromTX(tx *models.Tx) { 149 | name := tx.From 150 | amount := decimal.NewFromFloat(tx.Amount) 151 | user := models.User{} 152 | 153 | if tx.CoinID != eos { 154 | return 155 | } 156 | 157 | if unfound := db.Where("name = ?", name).First(&user).RecordNotFound(); unfound { 158 | user.Name = name 159 | if err := db.Create(&user).Error; err != nil { 160 | glog.Error(err) 161 | return 162 | } 163 | } 164 | 165 | user.Bet.Add(amount) 166 | user.TotalBet.Add(amount) 167 | db.Model(&models.User{}).Where("name = ?", name).Update(&user) 168 | totalBetFloat, _ := strconv.ParseFloat(user.TotalBet.StringFixed(4), 64) 169 | if level := getVIPLevel(totalBetFloat); level > user.Level { 170 | db.Model(&models.User{}).Where("name = ?", user.Name).Update("level", level) 171 | sendTokens(eosConf.GameAccount, user.Name, fmt.Sprintf("%0.4f EOS", vipInfo[level-1].Rebate*vipInfo[level-1].Amount), "达到新贵宾等级奖励") 172 | } 173 | 174 | var amount2 decimal.Decimal 175 | 176 | for index, pname := range strings.SplitN(user.PNames, ",", invitedLevel) { 177 | if pname == "" { 178 | break 179 | } 180 | pUser := models.User{} 181 | db.Model(&models.User{}).Where("name = ?", pname).First(&pUser) 182 | pUser.TotalBet.Add(amount) 183 | rebate := amount.Mul(decimal.NewFromFloat(0.2).Mul(decimal.NewFromFloat(invitedReward[index]))) 184 | amount2.Add(rebate) 185 | pUser.TotalRebate.Add(rebate) 186 | db.Model(&models.User{}).Where("name = ?", pname).Update(&pUser) 187 | glog.Infof("sending rebate to pname %s ==> %s", pname, rebate.String()) 188 | if _, err := sendTokens(eosConf.GameAccount, pname, rebate.StringFixed(4)+" "+coinNames[tx.CoinID], "来自邀请下属的奖励"); err != nil { 189 | glog.Error(err) 190 | } 191 | } 192 | 193 | // 分红池逻辑, 投注额的2%奖励完邀请人后,60%进入分红池 194 | quan := amount.Sub(amount2).Mul(decimal.NewFromFloat(0.6)).StringFixed(4) + " " + coinNames[tx.CoinID] 195 | sendTokens(eosConf.GameAccount, conf.C.GetString("eos.PlatformAccount"), quan, "分红池累积") 196 | } 197 | -------------------------------------------------------------------------------- /app/bettimes.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strings" 7 | ) 8 | 9 | var starCount = 6 10 | var lastStar = starCount - 1 11 | 12 | /* 13 | 一个分号的情况有2种, 14 | 1:b/s,o/e;453456 15 | 2:b/s/o/e;12345 16 | 第1种如果中奖,返回的倍率有:2,4,10,12,14几种 17 | flag表示是否猜中数字 18 | b/s o/e flag times 19 | 0 0 0 0 20 | 0 0 1 10 21 | 0 1 0 2 22 | 0 1 1 12 23 | 1 0 0 2 24 | 1 0 1 12 25 | 1 1 0 4 26 | 1 1 1 14 27 | 第2种如果中奖,返回的倍率有:2,10,12 三种 28 | b/s/o/e flag times 29 | 0 0 0 30 | 0 1 10 31 | 1 0 2 32 | 1 1 12 33 | */ 34 | func HandleBsOeAndOneStar(str []string, gameNum []byte) (int, int) { 35 | var flag int8 36 | var betnum int 37 | for _, v := range str[1] { /*用户猜中数字了*/ 38 | if byte(v) == gameNum[lastStar] { 39 | flag = 1 40 | } 41 | } 42 | betnum = len(str[1]) - 1 /*一星选取的数字个数*/ 43 | if len(str[0]) == 4 { /*str[0] maybe [b,o,] or[b,e,]or[s,o,] or[s,e,]*/ 44 | var V1Bit, V2Bit int8 45 | switch str[0][0] { 46 | case 'b': 47 | if gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9' { 48 | V1Bit = 1 49 | } 50 | case 's': 51 | if gameNum[lastStar] >= '0' && gameNum[lastStar] <= '4' { 52 | V1Bit = 1 53 | } 54 | } 55 | switch str[0][2] { 56 | case 'o': 57 | if gameNum[lastStar]%2 > 0 { 58 | V2Bit = 1 59 | } 60 | case 'e': 61 | if gameNum[lastStar]%2 == 0 { 62 | V2Bit = 1 63 | } 64 | } 65 | value := V1Bit<<2 | V2Bit<<1 | flag 66 | switch value { 67 | case 0: 68 | return 0, betnum + 2 69 | case 1: 70 | return 10, betnum + 2 71 | case 2: 72 | return 2, betnum + 2 73 | case 3: 74 | return 12, betnum + 2 75 | case 4: 76 | return 2, betnum + 2 77 | case 5: 78 | return 12, betnum + 2 79 | case 6: 80 | return 4, betnum + 2 81 | case 7: 82 | return 14, betnum + 2 83 | } 84 | return 0, betnum + 2 85 | } else { /* str[0] maybe [b,] or [s,] or[o,] or[e,]*/ 86 | var bit int8 87 | switch str[0][0] { 88 | case 'b': 89 | if gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9' { 90 | bit = 1 91 | } 92 | case 's': 93 | if gameNum[lastStar] >= '0' && gameNum[lastStar] <= '5' { 94 | bit = 1 95 | } 96 | case 'o': 97 | if gameNum[lastStar]%2 > 0 { 98 | bit = 1 99 | } 100 | case 'e': 101 | if gameNum[lastStar]%2 == 0 { 102 | bit = 1 103 | } 104 | } 105 | value := bit<<1 | flag 106 | switch value { 107 | case 0: 108 | return 0, betnum + 1 109 | case 1: 110 | return 10, betnum + 1 111 | case 2: 112 | return 2, betnum + 1 113 | case 3: 114 | return 12, betnum + 1 115 | } 116 | return 0, betnum + 1 117 | } 118 | } 119 | 120 | /* 121 | This function will return the number of times for the bets 122 | b 123 | s 124 | o 125 | e 126 | b,o 127 | b,e 128 | s,o 129 | s,e split(str,"[") 切分完之后,得到一个包含一个成员的数组。 130 | ------------------ 131 | b,[0~9] 132 | s,[0~9] 133 | o,[0~9] 134 | e,[0~9] 135 | b,o,[0~9] 136 | b,e,[0~9] 137 | s,o,[0~9] 138 | s,e,[0~9] split 返回包含2个元素的字符串数组 139 | ------------------------- 140 | [0~9] 141 | [0~9][0~9] 142 | [0~9][0~9][0~9] 143 | [0~9][0~9][0~9][0~9] 144 | [0~9][0~9][0~9][0~9][0~9] split 返回i+1个元素的字符串数组,第一个元素str[0]为空,从str[1]处理 145 | ----------------------------------------- 146 | */ 147 | func HandleBetInfo(betinfo string, gameNum []byte) (int, int) { 148 | str := strings.Split(betinfo, "[") 149 | strlen := len(str) 150 | switch strlen { /*以split 切分后返回的字符串数组长度做case 分支*/ 151 | case 1: /*handle : b/s/o/e/b,o/b,e/s,o/s,e*/ 152 | if len(str[0]) > 1 { 153 | if str[0][0] == 'b' && str[0][2] == 'o' { 154 | if (gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9') && gameNum[lastStar]%2 > 0 { 155 | fmt.Println("the reward num is: 4") 156 | return 4, 2 157 | } 158 | if ((gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9') && gameNum[lastStar]%2 == 0) || ((gameNum[lastStar] >= '0' && gameNum[lastStar] <= '4') && gameNum[lastStar]%2 > 0) { 159 | fmt.Println("the reward num is: 2") 160 | return 2, 2 161 | } 162 | } 163 | if str[0][0] == 'b' && str[0][2] == 'e' { 164 | if (gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9') && gameNum[lastStar]%2 == 0 { 165 | fmt.Println("the reward num is: 4") 166 | return 4, 2 167 | } 168 | if ((gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9') && gameNum[lastStar]%2 > 0) || ((gameNum[lastStar] >= '0' && gameNum[lastStar] <= '4') && gameNum[lastStar]%2 == 0) { 169 | fmt.Println("the reward num is: 2") 170 | return 2, 2 171 | } 172 | } 173 | if str[0][0] == 's' && str[0][2] == 'o' { 174 | if (gameNum[lastStar] >= '0' && gameNum[lastStar] <= '4') && gameNum[lastStar]%2 > 0 { 175 | fmt.Println("the reward num is: 4") 176 | return 4, 2 177 | } 178 | if ((gameNum[lastStar] >= '0' && gameNum[lastStar] <= '4') && gameNum[lastStar]%2 == 0) || ((gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9') && gameNum[lastStar]%2 > 0) { 179 | fmt.Println("the reward num is: 2") 180 | return 2, 2 181 | } 182 | } 183 | if str[0][0] == 's' && str[0][2] == 'e' { 184 | if (gameNum[lastStar] >= '0' && gameNum[lastStar] <= '4') && gameNum[lastStar]%2 == 0 { 185 | fmt.Println("the reward num is: 4") 186 | return 4, 2 187 | } 188 | if ((gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9') && gameNum[lastStar]%2 == 0) || ((gameNum[lastStar] >= '0' && gameNum[lastStar] <= '4') && gameNum[lastStar]%2 > 0) { 189 | fmt.Println("the reward num is: 2") 190 | return 2, 2 191 | } 192 | } 193 | return 0, 2 194 | } else { 195 | switch str[0][0] { 196 | case 'b': 197 | if gameNum[lastStar] >= '5' && gameNum[lastStar] <= '9' { 198 | fmt.Println("the reward num is: 2") 199 | return 2, 1 200 | } 201 | case 's': 202 | if gameNum[lastStar] >= '0' && gameNum[lastStar] <= '4' { 203 | fmt.Println("the reward num is: 2") 204 | return 2, 1 205 | } 206 | case 'o': 207 | if gameNum[lastStar]%2 > 0 { 208 | fmt.Println("the reward num is: 2") 209 | return 2, 1 210 | } 211 | case 'e': 212 | if gameNum[lastStar]%2 == 0 { 213 | fmt.Println("the reward num is: 2") 214 | return 2, 1 215 | } 216 | } 217 | return 0, 1 218 | } 219 | case 2: 220 | /*长度为2的时候,有2种情况: 221 | b,[0~9] 222 | s,[0~9] 223 | o,[0~9] 224 | e,[0~9] 225 | b,o,[0~9] 226 | b,e,[0~9] 227 | s,o,[0~9] 228 | s,e,[0~9] 229 | 一种是: 230 | 只有[0-9]*/ 231 | var times, betnum int 232 | if len(str[0]) > 0 { 233 | times, betnum = HandleBsOeAndOneStar(str, gameNum) 234 | } else { 235 | times, betnum = HandleStarNum(str, gameNum, 1) 236 | } 237 | return times, betnum 238 | case 3: 239 | times, betnum := HandleStarNum(str, gameNum, 2) 240 | return times, betnum 241 | case 4: 242 | times, betnum := HandleStarNum(str, gameNum, 3) 243 | return times, betnum 244 | case 5: 245 | times, betnum := HandleStarNum(str, gameNum, 4) 246 | return times, betnum 247 | case 6: 248 | times, betnum := HandleStarNum(str, gameNum, 5) 249 | return times, betnum 250 | case 7: 251 | times, betnum := HandleStarNum(str, gameNum, 6) 252 | return times, betnum 253 | default: 254 | fmt.Println("the betinfo is not valid") 255 | return 0, 0 256 | } 257 | } 258 | 259 | /* 260 | 多星玩儿法;不涉及选择大小,单双 261 | */ 262 | func HandleStarNum(str []string, gameNum []byte, starnum int) (int, int) { 263 | flag := make([]int, starnum) 264 | var hitflag int 265 | var betnum int 266 | j := starCount - starnum 267 | for i := 1; i <= starnum; i++ { 268 | for _, v := range str[i] { 269 | if byte(v) == gameNum[j] { 270 | flag[i-1] = 1 271 | } 272 | } 273 | j++ 274 | } 275 | betnum = len(str[1]) - 1 /*delete the last ']'*/ 276 | if len(str) > 2 { 277 | for i := 2; i <= starnum; i++ { 278 | betnum *= (len(str[i]) - 1) 279 | } 280 | } 281 | hitflag = flag[0] 282 | for i := 1; i < starnum; i++ { 283 | hitflag = hitflag & flag[i] 284 | } 285 | if hitflag > 0 { 286 | return int(math.Pow10(starnum)), betnum 287 | } else { 288 | return 0, betnum 289 | } 290 | } 291 | 292 | /* 293 | func main() { 294 | if len(os.Args) != 3 { 295 | fmt.Println("Please input the correct parameters: ./main \"b,e,[1223]/[123][456][789]\" \"45678\" ") 296 | return 297 | } 298 | betinfo := os.Args[1] 299 | gameNum := []byte(os.Args[2]) 300 | wintimes, betnum := HandleBetInfo(betinfo, gameNum) 301 | fmt.Println("you should send", wintimes, "times to user", "the bet number is:", betnum) 302 | }*/ 303 | -------------------------------------------------------------------------------- /app/eos.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "bytes" 5 | "dappswin/conf" 6 | "dappswin/database" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "io/ioutil" 11 | "net/http" 12 | "os/exec" 13 | "strings" 14 | "sync" 15 | "time" 16 | 17 | "github.com/gin-gonic/gin" 18 | "github.com/golang/glog" 19 | "github.com/jinzhu/gorm" 20 | "github.com/shopspring/decimal" 21 | ) 22 | 23 | var eosConf *EosConf 24 | var db *gorm.DB 25 | var apiEndpoint []string 26 | 27 | // InitEos 启动Eos Resolver 28 | func Init() { 29 | eosConf = newEosConf() 30 | db = database.Db.Debug() 31 | go gameRoutine() 32 | go resloveTXRoutine() 33 | go votedRoutine() 34 | go checkWinRoutine() 35 | if eosConf.EnableICO { 36 | go checkICORoutine() 37 | } 38 | go Huber.run() 39 | checkcleosExists() 40 | go Forever(updateICOBalance, time.Second*10) 41 | go Forever(updateTotalLocked, 1*time.Minute) 42 | go Forever(updateCirculat, 1*time.Minute) 43 | go checkNotifyRoutine() 44 | // 45 | } 46 | 47 | // EosConf : 48 | type EosConf struct { 49 | RPCURL string 50 | ChainID string 51 | FetchIdleDur int // 查询blk时间间隔 52 | FromBlkNum uint32 // 从哪个blocknum开始查询 53 | GameAccount string 54 | ICOAccount string 55 | EnableICO bool 56 | ICOStartTime int64 57 | TokenSymbol string 58 | TokenAccount string 59 | EOS_CGG float64 60 | WalletURL string 61 | WalletPW string 62 | TotalAmount float64 63 | LockAccount string 64 | OfficialLockAccount string 65 | TotalCGGAmoount float64 66 | } 67 | 68 | func newEosConf() *EosConf { 69 | dur := conf.C.GetInt("eos.FetchIdleDur") 70 | num := conf.C.GetInt64("eos.FromBlkNum") 71 | return &EosConf{ 72 | RPCURL: conf.C.GetString("eos.RPCURL"), 73 | ChainID: conf.C.GetString("eos.ChainID"), 74 | FetchIdleDur: dur, 75 | FromBlkNum: uint32(num), 76 | GameAccount: conf.C.GetString("eos.GameAccount"), 77 | ICOAccount: conf.C.GetString("eos.ICOAccount"), 78 | EnableICO: conf.C.GetBool("eos.EnableICO"), 79 | ICOStartTime: conf.C.GetInt64("eos.ICOStartTime"), 80 | EOS_CGG: conf.C.GetFloat64("eos.EOS_CGG"), 81 | WalletURL: conf.C.GetString("eos.WalletURL"), 82 | WalletPW: conf.C.GetString("eos.WalletPW"), 83 | TokenSymbol: conf.C.GetString("eos.TokenSymbol"), 84 | TokenAccount: conf.C.GetString("eos.TokenAccount"), 85 | TotalAmount: conf.C.GetFloat64("eos.ICOTotalAmount"), 86 | LockAccount: conf.C.GetString("eos.LockAccount"), 87 | OfficialLockAccount: conf.C.GetString("eos.OfficialLockAccount"), 88 | TotalCGGAmoount: conf.C.GetFloat64("eos.TotalCGGAmoount"), 89 | } 90 | } 91 | 92 | func sendTokens(from, to string, quan string, memo string) (hash string, err error) { 93 | 94 | cmd := exec.Command("cleos", "--wallet-url", eosConf.WalletURL, "--url", eosConf.RPCURL, "wallet", "unlock", "--password", eosConf.WalletPW) 95 | var stdout, stderr bytes.Buffer 96 | cmd.Stdout = &stdout 97 | cmd.Stderr = &stderr 98 | if err := cmd.Run(); err != nil { 99 | glog.Warningf("cleos unlock failed with %s\nErr:\n%s", err, string(stderr.Bytes())) 100 | } 101 | 102 | // defer exec.Command("cleos", "--wallet-url", eosConf.WalletURL, "--url", eosConf.RPCURL, "wallet", "lock").Run() 103 | 104 | // cleos push action eosio.token transfer '['xiaopingeos2', "xiaopingeos3", "2.0000 EOS", "转账EOS"]' -p xiaopingeos2@active 105 | // cleos push action xxptoken1234 transfer '['xiaopingeos2', "xiaopingeos3", "2.0000 CGG", "转账代币"]' -p xiaopingeos2@active 106 | var account string 107 | if strings.Contains(quan, "EOS") { 108 | account = "eosio.token" 109 | } else { 110 | account = eosConf.TokenAccount 111 | } 112 | 113 | // var sender string 114 | // if eosConf.EnableICO { 115 | // sender = eosConf.ICOAccount 116 | // } else if strings.Contains(memo, "unstaked") { 117 | // sender = eosConf.LockAccount 118 | // } else { 119 | // sender = eosConf.GameAccount 120 | // } 121 | 122 | actionData := fmt.Sprintf("[\"%s\", \"%s\", \"%s\", \"%s\"]", from, to, quan, memo) 123 | args := []string{"--wallet-url", eosConf.WalletURL, "--url", eosConf.RPCURL, "push", "action", account, "transfer", actionData, "-p", from + "@active"} 124 | cmd = exec.Command("cleos", args...) 125 | cmd.Stdout = &stdout 126 | cmd.Stderr = &stderr 127 | if err := cmd.Run(); err != nil { 128 | glog.Errorf("push transfer failed with %s\nErr:\n%s", err, string(stderr.Bytes())) 129 | return "", err 130 | } 131 | 132 | output := string(stderr.Bytes()) 133 | glog.V(7).Infof("output is : \n%s\n", output) 134 | hash1 := strings.SplitN(output, "executed transaction: ", 2) 135 | if len(hash1) != 2 { 136 | return "", errors.New("reslove hash error") 137 | 138 | } 139 | hash2 := strings.SplitN(hash1[1], " ", 2) 140 | if len(hash2) != 2 { 141 | return "", errors.New("reslove hash error") 142 | 143 | } 144 | 145 | return hash2[0], nil 146 | } 147 | 148 | func checkcleosExists() { 149 | path, err := exec.LookPath("cleos") 150 | if err != nil { 151 | glog.Fatalln("didn't find 'cleos' executable") 152 | } else { 153 | glog.Infof("'cleos' executable is in '%s'", path) 154 | } 155 | 156 | cmd := exec.Command("cleos", "--wallet-url", eosConf.WalletURL, "--url", eosConf.RPCURL, "get", "currency", "balance", "eosio.token", eosConf.GameAccount) 157 | glog.Info(cmd.Args) 158 | var stdout, stderr bytes.Buffer 159 | cmd.Stdout = &stdout 160 | cmd.Stderr = &stderr 161 | if err := cmd.Run(); err != nil { 162 | glog.Fatalf("cmd.Run() failed with %s\nErr:\n%s", err, string(stderr.Bytes())) 163 | } 164 | glog.Infof("cmd.Run() get balance of %s Out:%s", eosConf.ICOAccount, string(stdout.Bytes())) 165 | 166 | } 167 | 168 | // // EosRegister 注册balance相关路由 169 | // func EosRegister(router *gin.RouterGroup) { 170 | // router.POST("/chain/get_currency_balance", getCurrencyBalance) 171 | // } 172 | 173 | type balancePost struct { 174 | Code string `json:"code" binding:"required,max=12"` 175 | Account string `json:"account" binding:"required,len=12"` 176 | Symbol string `json:"symbol" binding:"required,len=3"` 177 | } 178 | 179 | var percentBalance json.Number = "0.00" 180 | var cacheLock sync.RWMutex 181 | 182 | func getPercent() json.Number { 183 | cacheLock.RLock() 184 | cached := percentBalance 185 | cacheLock.RUnlock() 186 | 187 | return cached 188 | } 189 | 190 | func setPercent(percent string) { 191 | cacheLock.Lock() 192 | defer cacheLock.Unlock() 193 | percentBalance = json.Number(percent) 194 | } 195 | 196 | func getCurrencyBalance(c *gin.Context) { 197 | 198 | post := balancePost{} 199 | if err := c.ShouldBind(&post); err != nil { 200 | c.JSON(200, gin.H{ 201 | "status": -1, 202 | "message": "post参数错误!", 203 | "data": nil, 204 | }) 205 | return 206 | } 207 | 208 | c.JSON(200, gin.H{ 209 | "status": 0, 210 | "message": "", 211 | "data": map[string]json.Number{ 212 | "result": getPercent(), 213 | }}) 214 | } 215 | 216 | func updateICOBalance() { 217 | result := getBalance(eosConf.ICOAccount, "eosio.token", "EOS") 218 | balance, _ := decimal.NewFromString(result) 219 | balance = balance.Add(decimal.NewFromFloat(conf.C.GetFloat64("eos.ICOFakeAmount"))) 220 | 221 | percent := balance.Div(decimal.NewFromFloat(eosConf.TotalAmount)).Mul(decimal.NewFromFloat(100)) 222 | setPercent(percent.StringFixed(2)) 223 | } 224 | 225 | func getBalance(account string, code string, symbol string) string { 226 | // ICOTotalAmount = 60000 227 | url := eosConf.RPCURL + "/v1/chain/get_currency_balance" 228 | 229 | payload := strings.NewReader("{\"code\":\"" + code + "\", \"account\":\"" + account + "\",\"symbol\":\"" + symbol + "\"}") 230 | 231 | req, err := http.NewRequest("POST", url, payload) 232 | if err != nil { 233 | glog.Error(err) 234 | return "" 235 | } 236 | 237 | req.Header.Add("Content-Type", "application/json") 238 | 239 | res, err := http.DefaultClient.Do(req) 240 | if err != nil { 241 | glog.Error(err) 242 | return "" 243 | } 244 | defer res.Body.Close() 245 | 246 | body, _ := ioutil.ReadAll(res.Body) 247 | results := []string{} 248 | if err := json.Unmarshal(body, &results); err != nil { 249 | glog.Error("unmarshal error", err) 250 | return "" 251 | } 252 | if len(results) != 1 { 253 | glog.Warningf("%s balance is %d, %v", account, 0, results) 254 | return "" 255 | } 256 | result := strings.Split(results[0], " ") 257 | if len(result) != 2 { 258 | glog.Error("result 格式有问题") 259 | return "" 260 | } 261 | return result[0] 262 | } 263 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | digest = "1:5fe3f6ede1c208a2efd3b78fe4df0306aa9624edd39476143d14f0326e5a8d29" 7 | name = "github.com/facebookgo/clock" 8 | packages = ["."] 9 | pruneopts = "UT" 10 | revision = "600d898af40aa09a7a93ecb9265d87b0504b6f03" 11 | 12 | [[projects]] 13 | branch = "master" 14 | digest = "1:1f21d86648746b776eae0dff77e4cafa2d9ccec0f0f1248c48306231ee800674" 15 | name = "github.com/facebookgo/grace" 16 | packages = [ 17 | "gracehttp", 18 | "gracenet", 19 | ] 20 | pruneopts = "UT" 21 | revision = "75cf19382434e82df4dd84953f566b8ad23d6e9e" 22 | 23 | [[projects]] 24 | branch = "master" 25 | digest = "1:17cb421603403a24edb0c7eeb382295dd31c72ab9ccf31cf7f0a37971f00aaa7" 26 | name = "github.com/facebookgo/httpdown" 27 | packages = ["."] 28 | pruneopts = "UT" 29 | revision = "5979d39b15c26299dc282711b0d65b113daccea6" 30 | 31 | [[projects]] 32 | branch = "master" 33 | digest = "1:02c7a4e944d94d6b80f51517158d10633b10775f528a3b7bdfc658d6f92415bd" 34 | name = "github.com/facebookgo/stats" 35 | packages = ["."] 36 | pruneopts = "UT" 37 | revision = "1b76add642e42c6ffba7211ad7b3939ce654526e" 38 | 39 | [[projects]] 40 | digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd" 41 | name = "github.com/fsnotify/fsnotify" 42 | packages = ["."] 43 | pruneopts = "UT" 44 | revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" 45 | version = "v1.4.7" 46 | 47 | [[projects]] 48 | digest = "1:2b59aca2665ff804f6606c8829eaee133ddd3aefbc841014660d961b0034f888" 49 | name = "github.com/gin-contrib/cors" 50 | packages = ["."] 51 | pruneopts = "UT" 52 | revision = "cf4846e6a636a76237a28d9286f163c132e841bc" 53 | version = "v1.2" 54 | 55 | [[projects]] 56 | branch = "master" 57 | digest = "1:36fe9527deed01d2a317617e59304eb2c4ce9f8a24115bcc5c2e37b3aee5bae4" 58 | name = "github.com/gin-contrib/sse" 59 | packages = ["."] 60 | pruneopts = "UT" 61 | revision = "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae" 62 | 63 | [[projects]] 64 | branch = "master" 65 | digest = "1:555fef7420c108d88754e41e02299fd10f67dc6602d643944611a4100132d3cf" 66 | name = "github.com/gin-gonic/contrib" 67 | packages = ["static"] 68 | pruneopts = "UT" 69 | revision = "54170a7b0b4b2f9219c79599b1b03830a5a7d68d" 70 | 71 | [[projects]] 72 | digest = "1:d5083934eb25e45d17f72ffa86cae3814f4a9d6c073c4f16b64147169b245606" 73 | name = "github.com/gin-gonic/gin" 74 | packages = [ 75 | ".", 76 | "binding", 77 | "json", 78 | "render", 79 | ] 80 | pruneopts = "UT" 81 | revision = "b869fe1415e4b9eb52f247441830d502aece2d4d" 82 | version = "v1.3.0" 83 | 84 | [[projects]] 85 | digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" 86 | name = "github.com/go-sql-driver/mysql" 87 | packages = ["."] 88 | pruneopts = "UT" 89 | revision = "72cd26f257d44c1114970e19afddcd812016007e" 90 | version = "v1.4.1" 91 | 92 | [[projects]] 93 | branch = "master" 94 | digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467" 95 | name = "github.com/golang/glog" 96 | packages = ["."] 97 | pruneopts = "UT" 98 | revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" 99 | 100 | [[projects]] 101 | digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d" 102 | name = "github.com/golang/protobuf" 103 | packages = ["proto"] 104 | pruneopts = "UT" 105 | revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" 106 | version = "v1.2.0" 107 | 108 | [[projects]] 109 | digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d" 110 | name = "github.com/gorilla/websocket" 111 | packages = ["."] 112 | pruneopts = "UT" 113 | revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d" 114 | version = "v1.4.0" 115 | 116 | [[projects]] 117 | digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10" 118 | name = "github.com/hashicorp/hcl" 119 | packages = [ 120 | ".", 121 | "hcl/ast", 122 | "hcl/parser", 123 | "hcl/printer", 124 | "hcl/scanner", 125 | "hcl/strconv", 126 | "hcl/token", 127 | "json/parser", 128 | "json/scanner", 129 | "json/token", 130 | ] 131 | pruneopts = "UT" 132 | revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241" 133 | version = "v1.0.0" 134 | 135 | [[projects]] 136 | digest = "1:d4e6e8584d0a94ce567d237e19192dae44d57d2767ac7e1d7fbf5626d176381a" 137 | name = "github.com/jinzhu/gorm" 138 | packages = ["."] 139 | pruneopts = "UT" 140 | revision = "472c70caa40267cb89fd8facb07fe6454b578626" 141 | version = "v1.9.2" 142 | 143 | [[projects]] 144 | branch = "master" 145 | digest = "1:fd97437fbb6b7dce04132cf06775bd258cce305c44add58eb55ca86c6c325160" 146 | name = "github.com/jinzhu/inflection" 147 | packages = ["."] 148 | pruneopts = "UT" 149 | revision = "04140366298a54a039076d798123ffa108fff46c" 150 | 151 | [[projects]] 152 | digest = "1:3e551bbb3a7c0ab2a2bf4660e7fcad16db089fdcfbb44b0199e62838038623ea" 153 | name = "github.com/json-iterator/go" 154 | packages = ["."] 155 | pruneopts = "UT" 156 | revision = "1624edc4454b8682399def8740d46db5e4362ba4" 157 | version = "v1.1.5" 158 | 159 | [[projects]] 160 | digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7" 161 | name = "github.com/magiconair/properties" 162 | packages = ["."] 163 | pruneopts = "UT" 164 | revision = "c2353362d570a7bfa228149c62842019201cfb71" 165 | version = "v1.8.0" 166 | 167 | [[projects]] 168 | digest = "1:0981502f9816113c9c8c4ac301583841855c8cf4da8c72f696b3ebedf6d0e4e5" 169 | name = "github.com/mattn/go-isatty" 170 | packages = ["."] 171 | pruneopts = "UT" 172 | revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" 173 | version = "v0.0.4" 174 | 175 | [[projects]] 176 | digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318" 177 | name = "github.com/mitchellh/mapstructure" 178 | packages = ["."] 179 | pruneopts = "UT" 180 | revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe" 181 | version = "v1.1.2" 182 | 183 | [[projects]] 184 | digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563" 185 | name = "github.com/modern-go/concurrent" 186 | packages = ["."] 187 | pruneopts = "UT" 188 | revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" 189 | version = "1.0.3" 190 | 191 | [[projects]] 192 | digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855" 193 | name = "github.com/modern-go/reflect2" 194 | packages = ["."] 195 | pruneopts = "UT" 196 | revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" 197 | version = "1.0.1" 198 | 199 | [[projects]] 200 | digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" 201 | name = "github.com/pelletier/go-toml" 202 | packages = ["."] 203 | pruneopts = "UT" 204 | revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194" 205 | version = "v1.2.0" 206 | 207 | [[projects]] 208 | digest = "1:81e02c4edb639c80559c0650f9401d3e2dcc3256d1fa215382bb7c83c1db9126" 209 | name = "github.com/shopspring/decimal" 210 | packages = ["."] 211 | pruneopts = "UT" 212 | revision = "cd690d0c9e2447b1ef2a129a6b7b49077da89b8e" 213 | version = "1.1.0" 214 | 215 | [[projects]] 216 | digest = "1:d707dbc1330c0ed177d4642d6ae102d5e2c847ebd0eb84562d0dc4f024531cfc" 217 | name = "github.com/spf13/afero" 218 | packages = [ 219 | ".", 220 | "mem", 221 | ] 222 | pruneopts = "UT" 223 | revision = "a5d6946387efe7d64d09dcba68cdd523dc1273a3" 224 | version = "v1.2.0" 225 | 226 | [[projects]] 227 | digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc" 228 | name = "github.com/spf13/cast" 229 | packages = ["."] 230 | pruneopts = "UT" 231 | revision = "8c9545af88b134710ab1cd196795e7f2388358d7" 232 | version = "v1.3.0" 233 | 234 | [[projects]] 235 | digest = "1:68ea4e23713989dc20b1bded5d9da2c5f9be14ff9885beef481848edd18c26cb" 236 | name = "github.com/spf13/jwalterweatherman" 237 | packages = ["."] 238 | pruneopts = "UT" 239 | revision = "4a4406e478ca629068e7768fc33f3f044173c0a6" 240 | version = "v1.0.0" 241 | 242 | [[projects]] 243 | digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" 244 | name = "github.com/spf13/pflag" 245 | packages = ["."] 246 | pruneopts = "UT" 247 | revision = "298182f68c66c05229eb03ac171abe6e309ee79a" 248 | version = "v1.0.3" 249 | 250 | [[projects]] 251 | digest = "1:de37e343c64582d7026bf8ab6ac5b22a72eac54f3a57020db31524affed9f423" 252 | name = "github.com/spf13/viper" 253 | packages = ["."] 254 | pruneopts = "UT" 255 | revision = "6d33b5a963d922d182c91e8a1c88d81fd150cfd4" 256 | version = "v1.3.1" 257 | 258 | [[projects]] 259 | digest = "1:03aa6e485e528acb119fb32901cf99582c380225fc7d5a02758e08b180cb56c3" 260 | name = "github.com/ugorji/go" 261 | packages = ["codec"] 262 | pruneopts = "UT" 263 | revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab" 264 | version = "v1.1.1" 265 | 266 | [[projects]] 267 | branch = "master" 268 | digest = "1:5004e851e5eccde563d17871cd9d11c82e2faa578b1a0de81dc74867ad3845a4" 269 | name = "golang.org/x/sys" 270 | packages = ["unix"] 271 | pruneopts = "UT" 272 | revision = "82a175fd1598e8a172e58ebdf5ed262bb29129e5" 273 | 274 | [[projects]] 275 | digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295" 276 | name = "golang.org/x/text" 277 | packages = [ 278 | "internal/gen", 279 | "internal/triegen", 280 | "internal/ucd", 281 | "transform", 282 | "unicode/cldr", 283 | "unicode/norm", 284 | ] 285 | pruneopts = "UT" 286 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" 287 | version = "v0.3.0" 288 | 289 | [[projects]] 290 | digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" 291 | name = "google.golang.org/appengine" 292 | packages = ["cloudsql"] 293 | pruneopts = "UT" 294 | revision = "e9657d882bb81064595ca3b56cbe2546bbabf7b1" 295 | version = "v1.4.0" 296 | 297 | [[projects]] 298 | digest = "1:cbc72c4c4886a918d6ab4b95e347ffe259846260f99ebdd8a198c2331cf2b2e9" 299 | name = "gopkg.in/go-playground/validator.v8" 300 | packages = ["."] 301 | pruneopts = "UT" 302 | revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf" 303 | version = "v8.18.2" 304 | 305 | [[projects]] 306 | digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" 307 | name = "gopkg.in/yaml.v2" 308 | packages = ["."] 309 | pruneopts = "UT" 310 | revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" 311 | version = "v2.2.2" 312 | 313 | [solve-meta] 314 | analyzer-name = "dep" 315 | analyzer-version = 1 316 | input-imports = [ 317 | "github.com/facebookgo/grace/gracehttp", 318 | "github.com/gin-contrib/cors", 319 | "github.com/gin-gonic/contrib/static", 320 | "github.com/gin-gonic/gin", 321 | "github.com/go-sql-driver/mysql", 322 | "github.com/golang/glog", 323 | "github.com/gorilla/websocket", 324 | "github.com/jinzhu/gorm", 325 | "github.com/shopspring/decimal", 326 | "github.com/spf13/viper", 327 | ] 328 | solver-name = "gps-cdcl" 329 | solver-version = 1 330 | -------------------------------------------------------------------------------- /app/wait.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // Copy from k8s 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "math/rand" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // For any test of the style: 14 | // ... 15 | // <- time.After(timeout): 16 | // t.Errorf("Timed out") 17 | // The value for timeout should effectively be "forever." Obviously we don't want our tests to truly lock up forever, but 30s 18 | // is long enough that it is effectively forever for the things that can slow down a run on a heavily contended machine 19 | // (GC, seeks, etc), but not so long as to make a developer ctrl-c a test run if they do happen to break that test. 20 | var ForeverTestTimeout = time.Second * 30 21 | 22 | // NeverStop may be passed to Until to make it never stop. 23 | var NeverStop <-chan struct{} = make(chan struct{}) 24 | 25 | // Group allows to start a group of goroutines and wait for their completion. 26 | type Group struct { 27 | wg sync.WaitGroup 28 | } 29 | 30 | func (g *Group) Wait() { 31 | g.wg.Wait() 32 | } 33 | 34 | // StartWithChannel starts f in a new goroutine in the group. 35 | // stopCh is passed to f as an argument. f should stop when stopCh is available. 36 | func (g *Group) StartWithChannel(stopCh <-chan struct{}, f func(stopCh <-chan struct{})) { 37 | g.Start(func() { 38 | f(stopCh) 39 | }) 40 | } 41 | 42 | // StartWithContext starts f in a new goroutine in the group. 43 | // ctx is passed to f as an argument. f should stop when ctx.Done() is available. 44 | func (g *Group) StartWithContext(ctx context.Context, f func(context.Context)) { 45 | g.Start(func() { 46 | f(ctx) 47 | }) 48 | } 49 | 50 | // Start starts f in a new goroutine in the group. 51 | func (g *Group) Start(f func()) { 52 | g.wg.Add(1) 53 | go func() { 54 | defer g.wg.Done() 55 | f() 56 | }() 57 | } 58 | 59 | // Forever calls f every period for ever. 60 | // 61 | // Forever is syntactic sugar on top of Until. 62 | func Forever(f func(), period time.Duration) { 63 | Until(f, period, NeverStop) 64 | } 65 | 66 | // Until loops until stop channel is closed, running f every period. 67 | // 68 | // Until is syntactic sugar on top of JitterUntil with zero jitter factor and 69 | // with sliding = true (which means the timer for period starts after the f 70 | // completes). 71 | func Until(f func(), period time.Duration, stopCh <-chan struct{}) { 72 | JitterUntil(f, period, 0.0, true, stopCh) 73 | } 74 | 75 | // NonSlidingUntil loops until stop channel is closed, running f every 76 | // period. 77 | // 78 | // NonSlidingUntil is syntactic sugar on top of JitterUntil with zero jitter 79 | // factor, with sliding = false (meaning the timer for period starts at the same 80 | // time as the function starts). 81 | func NonSlidingUntil(f func(), period time.Duration, stopCh <-chan struct{}) { 82 | JitterUntil(f, period, 0.0, false, stopCh) 83 | } 84 | 85 | // JitterUntil loops until stop channel is closed, running f every period. 86 | // 87 | // If jitterFactor is positive, the period is jittered before every run of f. 88 | // If jitterFactor is not positive, the period is unchanged and not jittered. 89 | // 90 | // If sliding is true, the period is computed after f runs. If it is false then 91 | // period includes the runtime for f. 92 | // 93 | // Close stopCh to stop. f may not be invoked if stop channel is already 94 | // closed. Pass NeverStop to if you don't want it stop. 95 | func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) { 96 | var t *time.Timer 97 | var sawTimeout bool 98 | 99 | for { 100 | select { 101 | case <-stopCh: 102 | return 103 | default: 104 | } 105 | 106 | jitteredPeriod := period 107 | if jitterFactor > 0.0 { 108 | jitteredPeriod = Jitter(period, jitterFactor) 109 | } 110 | 111 | if !sliding { 112 | t = resetOrReuseTimer(t, jitteredPeriod, sawTimeout) 113 | } 114 | 115 | func() { 116 | // defer runtime.HandleCrash() 117 | // no panic 118 | f() 119 | }() 120 | 121 | if sliding { 122 | t = resetOrReuseTimer(t, jitteredPeriod, sawTimeout) 123 | } 124 | 125 | // NOTE: b/c there is no priority selection in golang 126 | // it is possible for this to race, meaning we could 127 | // trigger t.C and stopCh, and t.C select falls through. 128 | // In order to mitigate we re-check stopCh at the beginning 129 | // of every loop to prevent extra executions of f(). 130 | select { 131 | case <-stopCh: 132 | return 133 | case <-t.C: 134 | sawTimeout = true 135 | } 136 | } 137 | } 138 | 139 | // Jitter returns a time.Duration between duration and duration + maxFactor * 140 | // duration. 141 | // 142 | // This allows clients to avoid converging on periodic behavior. If maxFactor 143 | // is 0.0, a suggested default value will be chosen. 144 | func Jitter(duration time.Duration, maxFactor float64) time.Duration { 145 | if maxFactor <= 0.0 { 146 | maxFactor = 1.0 147 | } 148 | wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration)) 149 | return wait 150 | } 151 | 152 | // ErrWaitTimeout is returned when the condition exited without success. 153 | var ErrWaitTimeout = errors.New("timed out waiting for the condition") 154 | 155 | // ConditionFunc returns true if the condition is satisfied, or an error 156 | // if the loop should be aborted. 157 | type ConditionFunc func() (done bool, err error) 158 | 159 | // Backoff holds parameters applied to a Backoff function. 160 | type Backoff struct { 161 | Duration time.Duration // the base duration 162 | Factor float64 // Duration is multiplied by factor each iteration 163 | Jitter float64 // The amount of jitter applied each iteration 164 | Steps int // Exit with error after this many steps 165 | } 166 | 167 | // ExponentialBackoff repeats a condition check with exponential backoff. 168 | // 169 | // It checks the condition up to Steps times, increasing the wait by multiplying 170 | // the previous duration by Factor. 171 | // 172 | // If Jitter is greater than zero, a random amount of each duration is added 173 | // (between duration and duration*(1+jitter)). 174 | // 175 | // If the condition never returns true, ErrWaitTimeout is returned. All other 176 | // errors terminate immediately. 177 | func ExponentialBackoff(backoff Backoff, condition ConditionFunc) error { 178 | duration := backoff.Duration 179 | for i := 0; i < backoff.Steps; i++ { 180 | if i != 0 { 181 | adjusted := duration 182 | if backoff.Jitter > 0.0 { 183 | adjusted = Jitter(duration, backoff.Jitter) 184 | } 185 | time.Sleep(adjusted) 186 | duration = time.Duration(float64(duration) * backoff.Factor) 187 | } 188 | if ok, err := condition(); err != nil || ok { 189 | return err 190 | } 191 | } 192 | return ErrWaitTimeout 193 | } 194 | 195 | // Poll tries a condition func until it returns true, an error, or the timeout 196 | // is reached. 197 | // 198 | // Poll always waits the interval before the run of 'condition'. 199 | // 'condition' will always be invoked at least once. 200 | // 201 | // Some intervals may be missed if the condition takes too long or the time 202 | // window is too short. 203 | // 204 | // If you want to Poll something forever, see PollInfinite. 205 | func Poll(interval, timeout time.Duration, condition ConditionFunc) error { 206 | return pollInternal(poller(interval, timeout), condition) 207 | } 208 | 209 | func pollInternal(wait WaitFunc, condition ConditionFunc) error { 210 | done := make(chan struct{}) 211 | defer close(done) 212 | return WaitFor(wait, condition, done) 213 | } 214 | 215 | // PollImmediate tries a condition func until it returns true, an error, or the timeout 216 | // is reached. 217 | // 218 | // Poll always checks 'condition' before waiting for the interval. 'condition' 219 | // will always be invoked at least once. 220 | // 221 | // Some intervals may be missed if the condition takes too long or the time 222 | // window is too short. 223 | // 224 | // If you want to Poll something forever, see PollInfinite. 225 | func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error { 226 | return pollImmediateInternal(poller(interval, timeout), condition) 227 | } 228 | 229 | func pollImmediateInternal(wait WaitFunc, condition ConditionFunc) error { 230 | done, err := condition() 231 | if err != nil { 232 | return err 233 | } 234 | if done { 235 | return nil 236 | } 237 | return pollInternal(wait, condition) 238 | } 239 | 240 | // PollInfinite tries a condition func until it returns true or an error 241 | // 242 | // PollInfinite always waits the interval before the run of 'condition'. 243 | // 244 | // Some intervals may be missed if the condition takes too long or the time 245 | // window is too short. 246 | func PollInfinite(interval time.Duration, condition ConditionFunc) error { 247 | done := make(chan struct{}) 248 | defer close(done) 249 | return PollUntil(interval, condition, done) 250 | } 251 | 252 | // PollImmediateInfinite tries a condition func until it returns true or an error 253 | // 254 | // PollImmediateInfinite runs the 'condition' before waiting for the interval. 255 | // 256 | // Some intervals may be missed if the condition takes too long or the time 257 | // window is too short. 258 | func PollImmediateInfinite(interval time.Duration, condition ConditionFunc) error { 259 | done, err := condition() 260 | if err != nil { 261 | return err 262 | } 263 | if done { 264 | return nil 265 | } 266 | return PollInfinite(interval, condition) 267 | } 268 | 269 | // PollUntil tries a condition func until it returns true, an error or stopCh is 270 | // closed. 271 | // 272 | // PolUntil always waits interval before the first run of 'condition'. 273 | // 'condition' will always be invoked at least once. 274 | func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { 275 | return WaitFor(poller(interval, 0), condition, stopCh) 276 | } 277 | 278 | // WaitFunc creates a channel that receives an item every time a test 279 | // should be executed and is closed when the last test should be invoked. 280 | type WaitFunc func(done <-chan struct{}) <-chan struct{} 281 | 282 | // WaitFor continually checks 'fn' as driven by 'wait'. 283 | // 284 | // WaitFor gets a channel from 'wait()'', and then invokes 'fn' once for every value 285 | // placed on the channel and once more when the channel is closed. 286 | // 287 | // If 'fn' returns an error the loop ends and that error is returned, and if 288 | // 'fn' returns true the loop ends and nil is returned. 289 | // 290 | // ErrWaitTimeout will be returned if the channel is closed without fn ever 291 | // returning true. 292 | func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error { 293 | c := wait(done) 294 | for { 295 | _, open := <-c 296 | ok, err := fn() 297 | if err != nil { 298 | return err 299 | } 300 | if ok { 301 | return nil 302 | } 303 | if !open { 304 | break 305 | } 306 | } 307 | return ErrWaitTimeout 308 | } 309 | 310 | // poller returns a WaitFunc that will send to the channel every interval until 311 | // timeout has elapsed and then closes the channel. 312 | // 313 | // Over very short intervals you may receive no ticks before the channel is 314 | // closed. A timeout of 0 is interpreted as an infinity. 315 | // 316 | // Output ticks are not buffered. If the channel is not ready to receive an 317 | // item, the tick is skipped. 318 | func poller(interval, timeout time.Duration) WaitFunc { 319 | return WaitFunc(func(done <-chan struct{}) <-chan struct{} { 320 | ch := make(chan struct{}) 321 | 322 | go func() { 323 | defer close(ch) 324 | 325 | tick := time.NewTicker(interval) 326 | defer tick.Stop() 327 | 328 | var after <-chan time.Time 329 | if timeout != 0 { 330 | // time.After is more convenient, but it 331 | // potentially leaves timers around much longer 332 | // than necessary if we exit early. 333 | timer := time.NewTimer(timeout) 334 | after = timer.C 335 | defer timer.Stop() 336 | } 337 | 338 | for { 339 | select { 340 | case <-tick.C: 341 | // If the consumer isn't ready for this signal drop it and 342 | // check the other channels. 343 | select { 344 | case ch <- struct{}{}: 345 | default: 346 | } 347 | case <-after: 348 | return 349 | case <-done: 350 | return 351 | } 352 | } 353 | }() 354 | 355 | return ch 356 | }) 357 | } 358 | 359 | // resetOrReuseTimer avoids allocating a new timer if one is already in use. 360 | // Not safe for multiple threads. 361 | func resetOrReuseTimer(t *time.Timer, d time.Duration, sawTimeout bool) *time.Timer { 362 | if t == nil { 363 | return time.NewTimer(d) 364 | } 365 | if !t.Stop() && !sawTimeout { 366 | <-t.C 367 | } 368 | t.Reset(d) 369 | return t 370 | } 371 | --------------------------------------------------------------------------------