├── 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 |
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 | 
11 |
12 | 利用scatter插件钱包 实现登录 参考[demo](http://developer.mathwallet.org/sample01/)
13 |
14 | 2. 奖池余额
15 |
16 | 
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 | 
39 |
40 | 显示距离下一整点分钟的倒计时, 比如当前时间是`14:13:21`, 那前端显示`00:39`
41 |
42 | 4. ROLLING BLOCK
43 |
44 |
45 | 
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 | 
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 | 
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 | 
86 |
87 | 上图发起交易时,memo字段里放置 `lottery:s,o,[235]`, 此图是1星的玩法,前端显示当前注数时是累加的, 点中一个,累加一次,所以是5注,达标有5种中奖可能性
88 |
89 |
90 | 
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 | 
107 |
108 | 显示 ws消息里的type3
109 |
110 | 从左到右分别取 .time所在的分钟值 data.gameid, data.result
111 |
112 | 8. LIVE
113 |
114 | 
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 |
--------------------------------------------------------------------------------