├── .dockerignore
├── .gitignore
├── Dockerfile
├── README.md
├── build.bat
├── build.sh
├── config
├── btc.json
├── command.json
├── eth.json
├── message.json
├── prod.json
├── settings.json
└── wallet.conf
├── docker-compose.yml
├── sql
├── address.sql
├── collect.sql
├── database.sql
├── deposit.sql
├── height.sql
├── process.sql
├── transaction.sql
└── withdraw.sql
├── src
├── apis
│ ├── common.go
│ ├── deposit.go
│ ├── process.go
│ ├── test.go
│ └── withdraw.go
├── dao
│ ├── address.go
│ ├── collect.go
│ ├── common.go
│ ├── deposit.go
│ ├── height.go
│ ├── process.go
│ ├── transaction.go
│ └── withdraw.go
├── databases
│ ├── mysql.go
│ └── redis.go
├── entities
│ ├── deposit.go
│ ├── process.go
│ ├── transaction.go
│ └── withdraw.go
├── rpcs
│ ├── btc.go
│ ├── common.go
│ └── eth.go
├── services
│ ├── collectService.go
│ ├── common.go
│ ├── depositService.go
│ ├── heightService.go
│ ├── stableService.go
│ └── withdrawService.go
└── utils
│ ├── collection.go
│ ├── config.go
│ ├── json.go
│ ├── message.go
│ └── status.go
├── tests
├── api
│ └── apis_test.go
├── blockchain_test.go
├── config_test.go
├── dao
│ ├── addressDAO_test.go
│ ├── collectDAO_test.go
│ ├── depositDAO_test.go
│ ├── heightDAO_test.go
│ ├── processDAO_test.go
│ └── withdrawDAO_test.go
├── database_test.go
├── goroutine_test.go
├── language_test.go
├── logger_test.go
├── redis_test.go
├── reflect_test.go
├── regexp_test.go
├── rpc
│ ├── btcrpc_test.go
│ ├── ethrpc_test.go
│ └── rpcs_test.go
├── status_test.go
└── svc
│ ├── collectService_test.go
│ └── depositService_test.go
└── wallet.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | pkg
2 | src/github.com
3 | wallet.exe
4 | wallet
5 | *.md
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | src/github.com
3 | pkg
4 | *.exe
5 | wallet
6 | logs
7 | config/dev.json
8 | config/coin.json
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang
2 |
3 | ARG coin
4 | ARG port
5 |
6 | WORKDIR /app
7 | COPY . /app
8 | ENV GOPATH=/app
9 | ENV PORT=$port
10 | RUN cp config/$coin.json config/coin.json \
11 | && go get -u github.com/go-sql-driver/mysql \
12 | && go get -u github.com/stretchr/testify \
13 | && go get -u github.com/go-redis/redis
14 |
15 | EXPOSE $port
16 | CMD ["go", "run", "wallet.go"]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 钱包系统
2 | 基于区块链的钱包项目,配备充币、归集和提币三大块服务。并可以使用多种(目前只实现了RPC)API调用,使用前需配置各个币种的钱包节点,并在该项目的配置信息中指明。一个项目实例只能跑一个币种,充提归三个服务可以按自身需求开关。
3 | > 使用前最好具备一定的区块链知识,**如因为操作不当导致资产丢失,本平台不担负任何责任**
4 | ## 环境配置
5 | > 如果使用docker配置服务,可不手动做如下配置
6 | * golang1.10.2
7 | * mysql:存储流水和交易
8 | * redis:缓存交易状态
9 | ## 安装和配置
10 | ### 下载项目文件
11 |
12 | `> git clone git@github.com:opteacher/golang-wallet.git`
13 |
14 | ### 配置项目
15 | * `config/settings.json` 全局配置
16 | ```json
17 | {
18 | "env": "dev",
19 | "services": [
20 | "withdraw", "deposit"
21 | ]
22 | }
23 | ```
24 | | - | - | - |
25 | |---|---|---|
26 | | env | 使用环境 |
27 | | services | 开启服务 | withdraw(提币)deposit(充币)collect(归集) |
28 |
29 |
30 | * `config/coin.json` 币种相关配置
31 | ```json
32 | {
33 | "name": "ETH",
34 | "url": "http://127.0.0.1:8545",
35 | "decimal": 8,
36 | "stable": 2,
37 | "collect": "0xb78f085e2759baf782c705cd3a9fcb5d39fa7b3c",
38 | "minCollect": 0.0001,
39 | "collectInterval": 30,
40 | "tradePassword": "FROM",
41 | "unlockDuration": 20000,
42 | "withdraw": "0x1c6f567e577a351917615fb1c8f1222dc96ba18d"
43 | }
44 | ```
45 |
46 | | - | - |
47 | |---|---|
48 | | name | 币种简称 |
49 | | url | 钱包节点 |
50 | | decimal | 币种精度 |
51 | | stable | 转账到账最低稳定块高(防止支链追赶主链) |
52 | | collect | 归集地址 |
53 | | minCollect | 最小归集金额 |
54 | | collectInterval | 归集间隔 |
55 | | tradePassword | 充值账户的交易密钥 |
56 | | unlockDuration | 解锁充值账户的时间 |
57 | | withdraw | 提币账户/地址 |
58 |
59 | * `config/(dev/prod/..).json` 环境配置,可以自定义名字,并在settings.json指定
60 | ```json
61 | {
62 | "db": {
63 | "url": "127.0.0.1:3306",
64 | "name": "test",
65 | "username": "root",
66 | "password": "12345",
67 | "max_conn": 20
68 | },
69 | "redis": {
70 | "password": "12345",
71 | "time_format": "RFC3339",
72 | "clusters": [
73 | {
74 | "name": "main",
75 | "url": "127.0.0.1:6379"
76 | }
77 | ]
78 | }
79 | }
80 | ```
81 |
82 | | - | - | - | - |
83 | |---|---|---|---|
84 | | db | 数据库配置 | | |
85 | | - | url | 数据库位置 | |
86 | | - | name | 数据库名 | |
87 | | - | username | 登陆用户名 | |
88 | | - | password | 登陆用户密码 | |
89 | | - | max_conn | 连接池最大连接数 | |
90 | | redis | redis缓存配置 | | |
91 | | - | password | 查询操作密码 | |
92 | | - | time_format | 存储的时间格式 | |
93 | | - | clusters | 集群列表 | |
94 | | - | - | name | 节点名 |
95 | | - | - | url | 节点URL |
96 |
97 | > 如果集群列表clusters只有一个节点,则会以单客户端形式调用redis
98 |
99 | * `config/(dev/prod/..).json` 同上
100 | ```json
101 | {
102 | "apis": {
103 | "rpc": {
104 | "active": true,
105 | "port": 8037
106 | }
107 | },
108 | "callbacks": {
109 | "redis": {
110 | "active": true
111 | },
112 | "rpc": {
113 | "active": false,
114 | "deposit_url": "",
115 | "withdraw_url": ""
116 | }
117 | }
118 | }
119 | ```
120 |
121 | | - | - | - | - |
122 | |---|---|---|---|
123 | | apis | 外部接口配置 | | |
124 | | - | rpc | http请求 | 具体接口参照[API接口表](#api接口表) |
125 | | - | - | active | 激活 |
126 | | - | - | port | 占用端口 |
127 | | callbacks | 交易进度提示的回调配置 | | |
128 | | - | redis | redis发布响应 | |
129 | | - | - | active | 激活(使用的是上面定义的redis配置) |
130 | | - | rpc | http回调 | |
131 | | - | - | active | 激活 |
132 | | - | - | deposit_url | 充币URL(格式:[Method] URL) |
133 | | - | - | withdraw_url | 提币URL(同上) |
134 | | - | - | collect_url | 归集URL(同上) |
135 |
136 |
137 | ### 下载依赖包
138 | ```
139 | go get -u github.com/go-sql-driver/mysql
140 | go get -u github.com/stretchr/testify
141 | go get -u github.com/go-redis/redis
142 | ```
143 |
144 | ## 使用说明
145 | ### 运行钱包
146 | `go run wallet.go`
147 |
148 | ### API接口表
149 |
150 | | Method | URL | Parameters | - |
151 | |---|---|---|---|
152 | | GET | /api/deposit/{asset}/address | - | 获取新地址 |
153 | | GET | /api/deposit/{asset}/height | - | 获取链最大高度 |
154 | | GET | /api/deposit/{asset} | tx_hash 交易hash(可选)
address 地址(可选) | 获取充币交易 |
155 | | POST | /api/withdraw/{asset} | id 提币id
value 金额
target 目标地址 | 提币 |
156 | | GET | /api/withdraw/{asset} | tx_hash 交易hash
id 交易id(二选一) | 获取提币交易 |
157 | | GET | /api/withdraw/{asset}/valid_address/{address} | - | 验证地址有效性 |
158 | | GET | /api/process/{asset}/txid/{tx_hash} | - | 查询交易进度 |
159 |
160 | > 参数的传递方式:POST和PUT请求都是通过application/json方式传递,GET请求通过query方式传递
--------------------------------------------------------------------------------
/build.bat:
--------------------------------------------------------------------------------
1 | set GOPATH=%CD%
2 | set /p input=Whether install/update modules(Y/n):
3 | if %input%==Y (
4 | go get -u github.com/go-sql-driver/mysql
5 | go get -u github.com/stretchr/testify
6 | go get -u github.com/go-redis/redis
7 | )
8 | go build wallet.go
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | export GOPATH=`pwd`
2 | read -p "Whether install/update modules(Y/n default n): " input
3 | if [ $input = "Y" ]
4 | then \
5 | /home/george/applications/go/bin/go get -u github.com/go-sql-driver/mysql |\
6 | /home/george/applications/go/bin/go get -u github.com/stretchr/testify |\
7 | /home/george/applications/go/bin/go get -u github.com/go-redis/redis
8 | fi
9 | /home/george/applications/go/bin/go build wallet.go
--------------------------------------------------------------------------------
/config/btc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BTC",
3 | "url": "http://18.144.17.127:8332",
4 | "decimal": 18,
5 | "stable": 12,
6 | "rpcUser": "123",
7 | "rpcPassword": "123",
8 | "collect": "2MscuBnDRh6PR7UkALXYLYgPyiiJBiVswPK",
9 | "minCollect": 0.0001,
10 | "collectInterval": 30,
11 | "deposit": "deposit",
12 | "unlockDuration": 0,
13 | "withdraw": "2MscuBnDRh6PR7UkALXYLYgPyiiJBiVswPK"
14 | }
--------------------------------------------------------------------------------
/config/command.json:
--------------------------------------------------------------------------------
1 | {
2 | "unknown": "未识别命令,使用help命令查看运行选项",
3 | "help": "版本:wallet.0.0.1\nhelp命令:\n\tversion\t查看版本详情\n\tservice\t指定运行的服务(deposit/collect/withdraw),如果不指定,则运行所有服务\n可使用 help 查看命令详细信息",
4 | "helps": {
5 | "version": "",
6 | "service": ""
7 | },
8 | "version": "开发版 wallet.0.0.1 beta"
9 | }
--------------------------------------------------------------------------------
/config/eth.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ETH",
3 | "url": "http://18.144.17.127:8545",
4 | "decimal": 18,
5 | "stable": 12,
6 | "collect": "0x6f594ed7ccdae38e9a3cf8923b0ebe42d3b211b9",
7 | "minCollect": 0.0001,
8 | "collectInterval": 30,
9 | "tradePassword": "deposit",
10 | "unlockDuration": 20000,
11 | "withdraw": "0x6f594ed7ccdae38e9a3cf8923b0ebe42d3b211b9"
12 | }
--------------------------------------------------------------------------------
/config/message.json:
--------------------------------------------------------------------------------
1 | {
2 | "logs": {
3 | "debug": false
4 | },
5 | "level": {
6 | "0": "错误",
7 | "1": "警告",
8 | "2": "消息",
9 | "3": "测试"
10 | },
11 | "storage": {
12 | "file": {
13 | "active": true,
14 | "split": "level",
15 | "split_comment": "level/type default:level",
16 | "split_mode": "file",
17 | "split_mode_comment": "file/folder default:file",
18 | "rotate": "time",
19 | "rotate_comment": "time/size default:time",
20 | "path": "logs",
21 | "nameFormat": "{split}_{time}{suffix}.log"
22 | }
23 | },
24 | "errors": {
25 | "E0010": "数据库连接失败:%v",
26 | "E0011": "无法找到指定的SQL语句:%s",
27 | "E0012": "数据库表插入错误:%v",
28 | "E0013": "数据库表查询错误:%v",
29 | "E0014": "扫描数据库查询结果集错误:%v",
30 | "E0015": "查询SQL文件错误:%v",
31 | "E0016": "创建表失败:%v",
32 | "E0017": "打开SQL文件失败:%v",
33 | "E0018": "读取SQL文件错误:%v",
34 | "E0019": "SQL文件格式错误:%v",
35 | "E0020": "无法找到%s的高度记录",
36 | "E0021": "数据库表更新错误:%v",
37 | "E0022": "JSON转字符串失败:%v",
38 | "E0023": "字符串转JSON失败:%v",
39 | "E0024": "HTTP请求失败:%v",
40 | "E0025": "HTTP返回提解析失败:%v",
41 | "E0026": "区块链请求错误:%v",
42 | "E0029": "无法将字符串转化为数字:%s",
43 | "E0030": "更新数据库却没有指定有效的更新字段",
44 | "E0031": "请求账户余额失败:%v",
45 | "E0032": "构建手续费失败:%v",
46 | "E0033": "解锁用户账户失败,请检查密码是否正确:%v",
47 | "E0034": "获取手续费失败:%v",
48 | "E0035": "发送交易失败:%v",
49 | "E0036": "获取新地址失败:%v",
50 | "E0037": "获取交易失败:%v",
51 | "E0038": "获取可用提币ID失败:%v",
52 | "E0039": "获取所有未稳定提币交易失败:%v",
53 | "E0041": "解析链上的数字错误:%v",
54 | "E0042": "redis连接失败:%v",
55 | "E0043": "构建请求失败:%v",
56 | "E0044": "错误的Callback配置,请采用`[Method] URL`的格式定义(用空格连接,Method可选,默认POST)"
57 | },
58 | "warnings": {
59 | "W0001": "指定块未包含交易",
60 | "W0036": "使用了错误的请求method,要求:%s;实际:%s",
61 | "W0037": "未找到指定的Path:%s",
62 | "W0038": "提交的参数错误:%v",
63 | "W0039": "钱包节点返回nil"
64 | },
65 | "information": {
66 | "I0": "这是一条测试信息"
67 | },
68 | "debugs": {
69 | "D0": "Debug一下:%v",
70 | "D0040": "从链上获取的信息为空"
71 | }
72 | }
--------------------------------------------------------------------------------
/config/prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "db": {
3 | "url": "database:3306",
4 | "name": "wallet_service",
5 | "username": "root",
6 | "password": "12345",
7 | "max_conn": 20
8 | },
9 | "redis": {
10 | "password": "abcd",
11 | "time_format": "RFC3339",
12 | "process_pub_key": "wallet transactions process",
13 | "clusters": [
14 | {
15 | "name": "main",
16 | "url": "redis:6379"
17 | }
18 | ]
19 | },
20 | "apis": {
21 | "rpc": {
22 | "active": true,
23 | "port": 8037
24 | },
25 | "mq": {
26 | "active": false
27 | }
28 | },
29 | "callbacks": {
30 | "redis": {
31 | "active": true
32 | },
33 | "rpc": {
34 | "active": false,
35 | "deposit_url": "",
36 | "withdraw_url": ""
37 | },
38 | "mq": {
39 | "active": false
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/config/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": "dev",
3 | "services": [
4 | "withdraw", "deposit"
5 | ]
6 | }
--------------------------------------------------------------------------------
/config/wallet.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 |
5 | location ~ /api/\w+/ETH {
6 | proxy_pass http://eth:8037;
7 | }
8 |
9 | location ~ /api/\w+/BTC {
10 | proxy_pass http://btc:8038;
11 | }
12 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | nginx:
4 | container_name: nginx
5 | image: nginx
6 | volumes:
7 | - ./config/wallet.conf:/etc/nginx/conf.d/default.conf
8 | ports:
9 | - "8089:80"
10 | expose:
11 | - "8089"
12 | depends_on:
13 | - eth
14 | - btc
15 |
16 | database:
17 | container_name: mysql
18 | image: mysql
19 | restart: unless-stopped
20 | environment:
21 | MYSQL_ROOT_PASSWORD: 12345
22 | volumes:
23 | - ./data/mysql:/var/lib/mysql
24 | - ./sql/database.sql:/docker-entrypoint-initdb.d/wallet.sql
25 | command: --default-authentication-plugin=mysql_native_password
26 |
27 | redis:
28 | container_name: redis
29 | image: redis
30 | restart: unless-stopped
31 | command: --requirepass abcd
32 | ports:
33 | - "6379:6379"
34 | expose:
35 | - "6379"
36 |
37 | eth:
38 | container_name: wallet-eth
39 | build:
40 | context: .
41 | args:
42 | coin: eth
43 | port: 8037
44 | volumes:
45 | - /var/log/wallet/eth:/app/logs
46 | depends_on:
47 | - database
48 | - redis
49 |
50 | btc:
51 | container_name: wallet-btc
52 | build:
53 | context: .
54 | args:
55 | coin: btc
56 | port: 8038
57 | volumes:
58 | - /var/log/wallet/btc:/app/logs
59 | depends_on:
60 | - database
61 | - redis
--------------------------------------------------------------------------------
/sql/address.sql:
--------------------------------------------------------------------------------
1 | # CreateTable
2 | CREATE TABLE IF NOT EXISTS address (
3 | id INTEGER NOT NULL AUTO_INCREMENT,
4 | asset VARCHAR(255) NOT NULL,
5 | address VARCHAR(255) NOT NULL UNIQUE,
6 | inuse TINYINT(1) NOT NULL DEFAULT 0,
7 | PRIMARY KEY(id)
8 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8
9 |
10 | # NewAddress
11 | INSERT INTO address (asset, address) VALUES (?, ?)
12 |
13 | # NewAddressInuse
14 | INSERT INTO address (asset, address, inuse) VALUES (?, ?, 1)
15 |
16 | # FindByAsset
17 | SELECT address FROM address WHERE inuse=1 AND asset=?
--------------------------------------------------------------------------------
/sql/collect.sql:
--------------------------------------------------------------------------------
1 | # CreateTable
2 | CREATE TABLE IF NOT EXISTS collect (
3 | id INTEGER NOT NULL AUTO_INCREMENT,
4 | tx_hash VARCHAR(255) UNIQUE,
5 | address VARCHAR(255) NOT NULL,
6 | amount DECIMAL(64,20) NOT NULL,
7 | asset CHAR(32) NOT NULL,
8 | create_time DATETIME DEFAULT NOW(),
9 | PRIMARY KEY(id)
10 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8
11 |
12 | # AddSentCollect
13 | INSERT INTO collect (%s) VALUES (%s)
--------------------------------------------------------------------------------
/sql/database.sql:
--------------------------------------------------------------------------------
1 | # CreateDatabase
2 | CREATE DATABASE IF NOT EXISTS `wallet_service` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
--------------------------------------------------------------------------------
/sql/deposit.sql:
--------------------------------------------------------------------------------
1 | # CreateTable
2 | CREATE TABLE IF NOT EXISTS deposit (
3 | id INTEGER NOT NULL AUTO_INCREMENT,
4 | tx_hash VARCHAR(255) NOT NULL UNIQUE,
5 | address VARCHAR(255) NOT NULL,
6 | amount DECIMAL(64,20) NOT NULL,
7 | asset CHAR(32) NOT NULL,
8 | height INTEGER(11) NOT NULL,
9 | tx_index INTEGER,
10 | status INTEGER(11) DEFAULT 1,
11 | create_time DATETIME DEFAULT NOW(),
12 | update_time DATETIME DEFAULT NOW() ON UPDATE NOW(),
13 | PRIMARY KEY(id)
14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8
15 |
16 | # AddScannedDeposit
17 | INSERT INTO deposit (tx_hash, address, amount, asset, height, tx_index) VALUES (?, ?, ?, ?, ?, ?)
18 |
19 | # AddDepositWithTime
20 | INSERT INTO deposit (tx_hash, address, amount, asset, height, tx_index, create_time) VALUES (?, ?, ?, ?, ?, ?, ?)
21 |
22 | # AddStableDeposit
23 | INSERT INTO deposit (tx_hash, address, amount, asset, height, tx_index, create_time, status) VALUES (?, ?, ?, ?, ?, ?, ?, 2)
24 |
25 | # GetUnstableDeposit
26 | SELECT tx_hash, address, amount, asset, height, tx_index FROM deposit WHERE asset=? AND status<2
27 |
28 | # DepositIntoStable
29 | UPDATE deposit SET status=2 WHERE tx_hash=?
30 |
31 | # GetDepositId
32 | SELECT id FROM deposit WHERE tx_hash=?
33 |
34 | # GetDeposits
35 | SELECT id, tx_hash, address, amount, asset, height, tx_index, status, create_time, update_time FROM deposit WHERE %s
36 |
37 | # CheckExists
38 | SELECT COUNT(id) AS num FROM deposit WHERE tx_hash=?
--------------------------------------------------------------------------------
/sql/height.sql:
--------------------------------------------------------------------------------
1 | # CreateTable
2 | CREATE TABLE IF NOT EXISTS height (
3 | asset CHAR(32) NOT NULL UNIQUE,
4 | height INTEGER(11) DEFAULT 0,
5 | PRIMARY KEY(asset)
6 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8
7 |
8 | # AddAsset
9 | INSERT INTO height (asset) VALUES (?)
10 |
11 | # GetHeight
12 | SELECT height FROM height WHERE asset=?
13 |
14 | # UpdateHeight
15 | UPDATE height SET %s WHERE asset=?
16 |
--------------------------------------------------------------------------------
/sql/process.sql:
--------------------------------------------------------------------------------
1 | # CreateTable
2 | CREATE TABLE IF NOT EXISTS process (
3 | tx_hash VARCHAR(255) NOT NULL UNIQUE,
4 | asset CHAR(32) NOT NULL,
5 | `type` VARCHAR(64) NOT NULL COMMENT 'DEPOSIT/COLLECT/WITHDRAW',
6 | height INTEGER(11) DEFAULT 0,
7 | current_height INTEGER(11),
8 | complete_height INTEGER(11),
9 | process VARCHAR(64) NOT NULL,
10 | cancelable TINYINT(1) NOT NULL DEFAULT 1 COMMENT '0/1: 不/可取消',
11 | last_update_time DATETIME DEFAULT NOW() ON UPDATE NOW(),
12 | PRIMARY KEY(tx_hash)
13 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8
14 |
15 | # AddProcess
16 | INSERT INTO process (%s) VALUES (%s)
17 |
18 | # UpdateProcessByHash
19 | UPDATE process SET %s WHERE tx_hash=?
20 |
21 | # CheckProcsExists
22 | SELECT tx_hash FROM process WHERE tx_hash=?
23 |
24 | # QueryProcess
25 | SELECT tx_hash, asset, `type`, height, current_height, complete_height, process, cancelable, last_update_time from process WHERE asset=? AND tx_hash=?
--------------------------------------------------------------------------------
/sql/transaction.sql:
--------------------------------------------------------------------------------
1 | # CreateTable
2 | CREATE TABLE IF NOT EXISTS `transaction` (
3 | opr_info VARCHAR(255) UNIQUE COMMENT 'type_id',
4 | tx_hash VARCHAR(255) NOT NULL,
5 | block_hash VARCHAR(255) NOT NULL,
6 | `from` VARCHAR(255) NOT NULL,
7 | `to` VARCHAR(255) NOT NULL,
8 | amount DECIMAL(64,20) NOT NULL,
9 | asset CHAR(32) NOT NULL,
10 | height INTEGER(11) DEFAULT 0,
11 | tx_index INTEGER DEFAULT 0,
12 | create_time DATETIME DEFAULT NOW(),
13 | PRIMARY KEY (opr_info)
14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8
15 |
16 | # AddTransaction
17 | INSERT INTO `transaction` (%s) VALUES (%s)
18 |
19 | # CheckExists
20 | SELECT COUNT(opr_info) AS num FROM `transaction` WHERE opr_info=?
--------------------------------------------------------------------------------
/sql/withdraw.sql:
--------------------------------------------------------------------------------
1 | # CreateTable
2 | CREATE TABLE IF NOT EXISTS withdraw (
3 | id INTEGER NOT NULL UNIQUE,
4 | tx_hash VARCHAR(255) UNIQUE,
5 | address VARCHAR(255) NOT NULL,
6 | amount DECIMAL(64,20) NOT NULL,
7 | asset CHAR(32) NOT NULL,
8 | height INTEGER(11),
9 | tx_index INTEGER,
10 | status INTEGER(11) DEFAULT 1,
11 | create_time DATETIME DEFAULT NOW(),
12 | update_time DATETIME DEFAULT NOW() ON UPDATE NOW(),
13 | PRIMARY KEY(id)
14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8
15 |
16 | # RecvNewWithdraw
17 | INSERT INTO withdraw (id, address, amount, asset) VALUES (?, ?, ?, ?)
18 |
19 | # SentForTxHash
20 | UPDATE withdraw SET tx_hash=?, status=2 WHERE asset=? AND id=?
21 |
22 | # WithdrawIntoStable
23 | UPDATE withdraw SET status=4 WHERE asset=? AND tx_hash=?
24 |
25 | # WithdrawIntoChain
26 | UPDATE withdraw SET status=3, %s WHERE asset=? AND tx_hash=?
27 |
28 | # GetAvailableId
29 | SELECT MAX(id) + 1 AS new_id FROM withdraw WHERE asset=?
30 |
31 | # GetUnstableWithdraw
32 | SELECT id, tx_hash, address, amount, asset, height, tx_index, status, create_time, update_time FROM withdraw WHERE asset=? AND status=3
33 |
34 | # GetUnfinishWithdraw
35 | SELECT id, tx_hash, address, amount, asset, height, tx_index, status, create_time, update_time FROM withdraw WHERE asset=? AND status >= 1 AND status < 3
36 |
37 | # GetWithdrawId
38 | SELECT id FROM withdraw WHERE asset=? AND tx_hash=?
39 |
40 | # GetWithdraws
41 | SELECT id, tx_hash, address, amount, asset, height, tx_index, status, create_time, update_time FROM withdraw WHERE %s
42 |
43 | # CheckExistsById
44 | SELECT COUNT(id) AS num FROM withdraw WHERE asset=? AND id=?
45 |
46 | # DeleteById
47 | DELETE FROM withdraw WHERE asset=? AND id=?
--------------------------------------------------------------------------------
/src/apis/common.go:
--------------------------------------------------------------------------------
1 | package apis
2 |
3 | import (
4 | "net/http"
5 | "regexp"
6 | "encoding/json"
7 | "fmt"
8 | "utils"
9 | "strings"
10 | "reflect"
11 | "net"
12 | )
13 |
14 | const WithdrawPath = "/api/withdraw"
15 | const WpLen = len(WithdrawPath)
16 | const DepositPath = "/api/deposit"
17 | const DpLen = len(DepositPath)
18 | const ProcessPath = "/api/process"
19 | const PcsLen = len(ProcessPath)
20 | const TestPath = "/api/test"
21 | const TstLen = len(TestPath)
22 |
23 | type RespVO struct {
24 | Code int `json:"code"`
25 | Msg string `json:"message"`
26 | Data interface {} `json:"data"`
27 | }
28 |
29 | type api struct {
30 | Func interface {}
31 | Method string
32 | }
33 |
34 | func subHandler(w http.ResponseWriter, req *http.Request, routeMap map[string]interface {}) {
35 | w.Header().Set("Access-Control-Allow-Origin", "*")
36 | w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
37 | w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT ,DELETE")
38 | w.Header().Set("Content-Type", "application/json; charset=UTF-8")
39 |
40 | uri := strings.Split(req.RequestURI, "?")
41 | if len(uri) == 2 {
42 | req.RequestURI = uri[0]
43 | req.ParseForm()
44 | }
45 | for route, handle := range routeMap {
46 | reqGrp := strings.Split(route, " ")
47 | if len(reqGrp) != 2 {
48 | continue
49 | }
50 | method := reqGrp[0]
51 | path := reqGrp[1]
52 | re := regexp.MustCompile(path)
53 | if !re.MatchString(req.RequestURI) { continue }
54 | if strings.ToUpper(req.Method) == method {
55 | a := reflect.ValueOf(handle).Call([]reflect.Value {
56 | reflect.ValueOf(w), reflect.ValueOf(req),
57 | })
58 | w.Write(a[0].Bytes())
59 | return
60 | } else {
61 | utils.LogIdxEx(utils.WARNING, 36, method, req.Method)
62 | //var resp RespVO
63 | //resp.Code = 405
64 | //resp.Msg = fmt.Sprintf(utils.GetIdxMsg("W0036"), method, req.Method)
65 | //respJSON , _:= json.Marshal(resp)
66 | //w.Write(respJSON)
67 | //return
68 | }
69 | }
70 | utils.LogIdxEx(utils.WARNING, 37, req.RequestURI)
71 | var resp RespVO
72 | resp.Code = 404
73 | resp.Msg = fmt.Sprintf(utils.GetIdxMsg("W0037"), req.RequestURI)
74 | respJSON , _:= json.Marshal(resp)
75 | w.Write(respJSON)
76 | }
77 |
78 | func HttpHandler(w http.ResponseWriter, req *http.Request) {
79 | utils.LogMsgEx(utils.INFO, "%s\t\t%s", req.Method, req.RequestURI)
80 | switch {
81 | case len(req.RequestURI) >= WpLen && req.RequestURI[:WpLen] == WithdrawPath:
82 | subHandler(w, req, wdRouteMap)
83 | case len(req.RequestURI) >= DpLen && req.RequestURI[:DpLen] == DepositPath:
84 | subHandler(w, req, dpRouteMap)
85 | case len(req.RequestURI) >= TstLen && req.RequestURI[:TstLen] == TestPath:
86 | subHandler(w, req, tstRouteMap)
87 | case len(req.RequestURI) >= PcsLen && req.RequestURI[:PcsLen] == ProcessPath:
88 | subHandler(w, req, pcsRouteMap)
89 | default:
90 | utils.LogIdxEx(utils.WARNING, 37, req.RequestURI)
91 | var resp RespVO
92 | resp.Code = 404
93 | resp.Msg = fmt.Sprintf(utils.GetIdxMsg("W0037"), req.RequestURI)
94 | respJSON , _:= json.Marshal(resp)
95 | w.Header().Set("Content-Type", "application/json; charset=UTF-8")
96 | w.Write(respJSON)
97 | }
98 | }
99 |
100 | func SocketHandler(conn net.Conn) {
101 | buffer := make([]byte, 2048)
102 | for {
103 | var n int
104 | var err error
105 | if n, err = conn.Read(buffer); err != nil {
106 | utils.LogMsgEx(utils.ERROR, "SOCKET连接错误:%v", err)
107 | return
108 | }
109 | utils.LogMsgEx(utils.INFO, "SOCKET\t%s", string(buffer[:n]))
110 | }
111 | }
--------------------------------------------------------------------------------
/src/apis/deposit.go:
--------------------------------------------------------------------------------
1 | package apis
2 |
3 | import (
4 | "net/http"
5 | "regexp"
6 | "rpcs"
7 | "encoding/json"
8 | "dao"
9 | "entities"
10 | "fmt"
11 | )
12 |
13 | const newAddressPath = "^/api/deposit/([A-Z]{3,})/address$"
14 | const getHeightPath = "^/api/deposit/([A-Z]{3,})/height$"
15 | const getDepositsPath = "^/api/deposit/([A-Z]{3,})$"
16 |
17 | var dpRouteMap = map[string]interface {} {
18 | fmt.Sprintf("%s %s", http.MethodGet, newAddressPath): newAddress,
19 | fmt.Sprintf("%s %s", http.MethodGet, getHeightPath): queryHeight,
20 | fmt.Sprintf("%s %s", http.MethodGet, getDepositsPath): getDeposits,
21 | }
22 |
23 | func newAddress(w http.ResponseWriter, req *http.Request) []byte {
24 | var resp RespVO
25 | re := regexp.MustCompile(newAddressPath)
26 | params := re.FindStringSubmatch(req.RequestURI)[1:]
27 | if len(params) == 0 {
28 | resp.Code = 500
29 | resp.Msg = "需要指定币种的名字"
30 | ret, _ := json.Marshal(resp)
31 | return ret
32 | }
33 |
34 | asset := params[0]
35 | rpc := rpcs.GetRPC(asset)
36 |
37 | var address string
38 | var err error
39 | if address, err = rpc.GetNewAddress(); err != nil {
40 | resp.Code = 500
41 | resp.Msg = err.Error()
42 | ret, _ := json.Marshal(resp)
43 | return ret
44 | }
45 |
46 | // 保存进充值地址数据库
47 | if _, err = dao.GetAddressDAO().NewAddressInuse(asset, address); err != nil {
48 | resp.Code = 500
49 | resp.Msg = err.Error()
50 | ret, _ := json.Marshal(resp)
51 | return ret
52 | }
53 |
54 | resp.Code = 200
55 | resp.Data = address
56 | ret, _ := json.Marshal(resp)
57 | return ret
58 | }
59 | func queryHeight(w http.ResponseWriter, req *http.Request) []byte {
60 | var resp RespVO
61 | re := regexp.MustCompile(getHeightPath)
62 | params := re.FindStringSubmatch(req.RequestURI)[1:]
63 | if len(params) == 0 {
64 | resp.Code = 500
65 | resp.Msg = "需要指定币种的名字"
66 | ret, _ := json.Marshal(resp)
67 | return ret
68 | }
69 |
70 | coinName := params[0]
71 | rpc := rpcs.GetRPC(coinName)
72 |
73 | var height uint64
74 | var err error
75 | if height, err = rpc.GetCurrentHeight(); err != nil {
76 | resp.Code = 500
77 | resp.Msg = err.Error()
78 | ret, _ := json.Marshal(resp)
79 | return ret
80 | }
81 |
82 | resp.Code = 200
83 | resp.Data = height
84 | ret, _ := json.Marshal(resp)
85 | return ret
86 | }
87 | func getDeposits(w http.ResponseWriter, req *http.Request) []byte {
88 | var resp RespVO
89 | re := regexp.MustCompile(getDepositsPath)
90 | params := re.FindStringSubmatch(req.RequestURI)[1:]
91 | if len(params) == 0 {
92 | resp.Code = 500
93 | resp.Msg = "需要指定币种的名字"
94 | ret, _ := json.Marshal(resp)
95 | return ret
96 | }
97 |
98 | conds := make(map[string]interface {})
99 | conds["asset"] = params[0]
100 | var result []entities.DatabaseDeposit
101 | var err error
102 | if txHash := req.Form.Get("tx_hash"); txHash != "" {
103 | conds["tx_hash"] = txHash
104 |
105 | // txhash是唯一的,所以指定的话,直接返回
106 | if result ,err = dao.GetDepositDAO().GetDeposits(conds); err != nil {
107 | resp.Code = 500
108 | resp.Msg = err.Error()
109 | ret, _ := json.Marshal(resp)
110 | return ret
111 | }
112 | resp.Code = 200
113 | resp.Data = result
114 | ret, _ := json.Marshal(resp)
115 | return ret
116 | }
117 | if address := req.Form.Get("address"); address != "" {
118 | conds["address"] = address
119 | }
120 |
121 | if result ,err = dao.GetDepositDAO().GetDeposits(conds); err != nil {
122 | resp.Code = 500
123 | resp.Msg = err.Error()
124 | ret, _ := json.Marshal(resp)
125 | return ret
126 | }
127 | resp.Code = 200
128 | resp.Data = result
129 | ret, _ := json.Marshal(resp)
130 | return ret
131 | }
--------------------------------------------------------------------------------
/src/apis/process.go:
--------------------------------------------------------------------------------
1 | package apis
2 |
3 | import (
4 | "strconv"
5 | "encoding/json"
6 | "dao"
7 | "net/http"
8 | "regexp"
9 | "fmt"
10 | )
11 |
12 | const processByTxidPath = "^/api/process/([A-Z]{3,})/txid/([a-zA-Z0-9]{1,})"
13 | const processByOpidPath = "^/api/process/([A-Z]{3,})/type/(WITHDRAW|DEPOSIT)/id/([0-9]{1,})"
14 |
15 | var pcsRouteMap = map[string]interface {} {
16 | fmt.Sprintf("%s %s", http.MethodGet, processByTxidPath): queryProcessByTxid,
17 | fmt.Sprintf("%s %s", http.MethodGet, processByOpidPath): queryProcessByOpid,
18 | }
19 |
20 | func queryProcessByTxid(w http.ResponseWriter, req *http.Request) []byte {
21 | var resp RespVO
22 | re := regexp.MustCompile(processByTxidPath)
23 | params := re.FindStringSubmatch(req.RequestURI)[1:]
24 | if len(params) == 0 {
25 | resp.Code = 500
26 | resp.Msg = "需要指定币种的名字"
27 | ret, _ := json.Marshal(resp)
28 | return ret
29 | }
30 | asset := params[0]
31 | if len(params) == 1 {
32 | resp.Code = 500
33 | resp.Msg = "需要指定查询的交易哈希"
34 | ret, _ := json.Marshal(resp)
35 | return ret
36 | }
37 | txId := params[1]
38 |
39 | if process, err := dao.GetProcessDAO().QueryProcessByTxHash(asset, txId); err != nil {
40 | resp.Code = 500
41 | resp.Msg = fmt.Sprintf("未找到指定交易:%v", err)
42 | ret, _ := json.Marshal(resp)
43 | return ret
44 | } else {
45 | resp.Code = 200
46 | resp.Data = process
47 | ret, _ := json.Marshal(resp)
48 | return ret
49 | }
50 | }
51 |
52 | func queryProcessByOpid(w http.ResponseWriter, req *http.Request) []byte {
53 | var resp RespVO
54 | var err error
55 | re := regexp.MustCompile(processByOpidPath)
56 | params := re.FindStringSubmatch(req.RequestURI)[1:]
57 | if len(params) == 0 {
58 | resp.Code = 500
59 | resp.Msg = "需要指定币种的名字"
60 | ret, _ := json.Marshal(resp)
61 | return ret
62 | }
63 | asset := params[0]
64 | if len(params) <= 2{
65 | resp.Code = 500
66 | resp.Msg = "需要指定查询的操作类型和操作id"
67 | ret, _ := json.Marshal(resp)
68 | return ret
69 | }
70 | typ := params[1]
71 | var id int
72 | if id, err = strconv.Atoi(params[2]); err != nil {
73 | resp.Code = 500
74 | resp.Msg = fmt.Sprintf("操作id必须是数字:%v", err)
75 | ret, _ := json.Marshal(resp)
76 | return ret
77 | }
78 |
79 | if process, err := dao.GetProcessDAO().QueryProcessByTypAndId(asset, typ, id); err != nil {
80 | resp.Code = 500
81 | resp.Msg = fmt.Sprintf("未找到指定交易:%v", err)
82 | ret, _ := json.Marshal(resp)
83 | return ret
84 | } else {
85 | resp.Code = 200
86 | resp.Data = process
87 | ret, _ := json.Marshal(resp)
88 | return ret
89 | }
90 | }
--------------------------------------------------------------------------------
/src/apis/test.go:
--------------------------------------------------------------------------------
1 | package apis
2 |
3 | import (
4 | "utils"
5 | "encoding/json"
6 | "io/ioutil"
7 | "rpcs"
8 | "fmt"
9 | "net/http"
10 | "regexp"
11 | )
12 |
13 | const transferPath = "^/api/test/([A-Z]{3,})/transfer$"
14 | const miningPath = "^/api/test/([A-Z]{3,})/mining$"
15 |
16 | var tstRouteMap = map[string]interface {} {
17 | fmt.Sprintf("%s %s", http.MethodPost, transferPath): transfer,
18 | fmt.Sprintf("%s %s", http.MethodPut, miningPath): doMining,
19 | fmt.Sprintf("%s %s", http.MethodGet, miningPath): isMining,
20 | }
21 |
22 | type transactionReq struct {
23 | From string `json:"from"`
24 | To string `json:"to"`
25 | Amount float64 `json:"amount"`
26 | }
27 |
28 | func transfer(w http.ResponseWriter, req *http.Request) []byte {
29 | var resp RespVO
30 | re := regexp.MustCompile(transferPath)
31 | params := re.FindStringSubmatch(req.RequestURI)[1:]
32 | if len(params) == 0 {
33 | resp.Code = 500
34 | resp.Msg = "需要指定币种的名字"
35 | ret, _ := json.Marshal(resp)
36 | return ret
37 | }
38 |
39 | // 参数解析
40 | var body []byte
41 | var err error
42 | if body, err = ioutil.ReadAll(req.Body); err != nil {
43 | utils.LogMsgEx(utils.WARNING, "解析请求体错误:%v", err)
44 | resp.Code = 500
45 | resp.Msg = err.Error()
46 | ret, _ := json.Marshal(resp)
47 | return ret
48 | }
49 | defer req.Body.Close()
50 |
51 | utils.LogMsgEx(utils.INFO, "收到交易请求:%s", string(body))
52 |
53 | var txReq transactionReq
54 | if err = json.Unmarshal(body, &txReq); err != nil {
55 | utils.LogIdxEx(utils.WARNING, 38, err)
56 | resp.Code = 500
57 | resp.Msg = err.Error()
58 | ret, _ := json.Marshal(resp)
59 | return ret
60 | }
61 |
62 | rpc := rpcs.GetRPC(params[0])
63 | var txHash string
64 | tradePwd := utils.GetConfig().GetCoinSettings().TradePassword
65 | if txHash, err = rpc.SendTransaction(txReq.From, txReq.To, txReq.Amount, tradePwd); err != nil {
66 | utils.LogMsgEx(utils.ERROR, "发送交易失败:%v", err)
67 | resp.Code = 500
68 | resp.Msg = err.Error()
69 | ret, _ := json.Marshal(resp)
70 | return ret
71 | }
72 |
73 | resp.Code = 200
74 | resp.Data = txHash
75 | ret, _ := json.Marshal(resp)
76 | return []byte(ret)
77 | }
78 |
79 | type miningReq struct {
80 | Enable bool `json:"enable"`
81 | Speed int `json:"speed"`
82 | }
83 |
84 | func doMining(w http.ResponseWriter, req *http.Request) []byte {
85 | var resp RespVO
86 | re := regexp.MustCompile(miningPath)
87 | params := re.FindStringSubmatch(req.RequestURI)[1:]
88 | if len(params) == 0 {
89 | resp.Code = 500
90 | resp.Msg = "需要指定币种的名字"
91 | ret, _ := json.Marshal(resp)
92 | return ret
93 | }
94 |
95 | var body []byte
96 | var err error
97 | if body, err = ioutil.ReadAll(req.Body); err != nil {
98 | utils.LogMsgEx(utils.WARNING, "解析请求体错误:%v", err)
99 | resp.Code = 500
100 | resp.Msg = err.Error()
101 | ret, _ := json.Marshal(resp)
102 | return ret
103 | }
104 | defer req.Body.Close()
105 |
106 | var mining miningReq
107 | if err = json.Unmarshal(body, &mining); err != nil {
108 | utils.LogIdxEx(utils.WARNING, 38, err)
109 | resp.Code = 500
110 | resp.Msg = err.Error()
111 | ret, _ := json.Marshal(resp)
112 | return ret
113 | }
114 | miningSpeed := 1
115 | if mining.Speed > 1 {
116 | miningSpeed = mining.Speed
117 | }
118 | rpc := rpcs.GetRPC(params[0])
119 | if res, err := rpc.EnableMining(mining.Enable, miningSpeed); err != nil {
120 | utils.LogMsgEx(utils.WARNING, "调整挖矿状态失败:%v", err)
121 | resp.Code = 500
122 | resp.Msg = err.Error()
123 | ret, _ := json.Marshal(resp)
124 | return ret
125 | } else {
126 | resp.Code = 200
127 | resp.Data = res
128 | ret, _ := json.Marshal(resp)
129 | return []byte(ret)
130 | }
131 | resp.Code = 200
132 | ret, _ := json.Marshal(resp)
133 | return []byte(ret)
134 | }
135 |
136 | func isMining(w http.ResponseWriter, req *http.Request) []byte {
137 | var resp RespVO
138 | resp.Code = 200
139 | resp.Data = rpcs.GetRPC(utils.GetConfig().GetCoinSettings().Name).IsMining()
140 | ret, _ := json.Marshal(resp)
141 | return []byte(ret)
142 | }
--------------------------------------------------------------------------------
/src/apis/withdraw.go:
--------------------------------------------------------------------------------
1 | package apis
2 |
3 | import (
4 | "net/http"
5 | "regexp"
6 | "encoding/json"
7 | "io/ioutil"
8 | "utils"
9 | "entities"
10 | "services"
11 | "dao"
12 | "strings"
13 | "fmt"
14 | "strconv"
15 | "rpcs"
16 | )
17 |
18 | type withdrawReq struct {
19 | Id int `json:"id"`
20 | Value float64 `json:"value"`
21 | Target string `json:"target"`
22 | }
23 |
24 | const withdrawPath = "^/api/withdraw/([A-Z]{3,})$"
25 | const validAddrPath = "^/api/withdraw/([A-Z]{3,})/valid_address/(\\w+)$"
26 | const cancelPath = "^/api/withdraw/([A-Z]{3,})/id/(\\d+)$"// 只能用提币id撤销提币
27 |
28 | var wdRouteMap = map[string]interface {} {
29 | fmt.Sprintf("%s %s", http.MethodPost, withdrawPath): doWithdraw,
30 | fmt.Sprintf("%s %s", http.MethodGet, withdrawPath): getWithdraw,
31 | fmt.Sprintf("%s %s", http.MethodGet, validAddrPath): validAddress,
32 | fmt.Sprintf("%s %s", http.MethodDelete, cancelPath): cancelWithdraw,
33 | }
34 |
35 | func doWithdraw(w http.ResponseWriter, req *http.Request) []byte {
36 | var resp RespVO
37 | re := regexp.MustCompile(withdrawPath)
38 | params := re.FindStringSubmatch(req.RequestURI)[1:]
39 | if len(params) == 0 {
40 | resp.Code = 500
41 | resp.Msg = "需要指定币种的名字"
42 | ret, _ := json.Marshal(resp)
43 | return ret
44 | }
45 | asset := params[0]
46 |
47 | // 参数解析
48 | var body []byte
49 | var err error
50 | if body, err = ioutil.ReadAll(req.Body); err != nil {
51 | utils.LogMsgEx(utils.WARNING, "解析请求体错误:%v", err)
52 | resp.Code = 500
53 | resp.Msg = err.Error()
54 | ret, _ := json.Marshal(resp)
55 | return ret
56 | }
57 | defer req.Body.Close()
58 |
59 | utils.LogMsgEx(utils.INFO, "收到提币请求:%s", string(body))
60 |
61 | var wdReq withdrawReq
62 | if err = json.Unmarshal(body, &wdReq); err != nil {
63 | utils.LogIdxEx(utils.WARNING, 38, err)
64 | resp.Code = 500
65 | resp.Msg = err.Error()
66 | ret, _ := json.Marshal(resp)
67 | return ret
68 | }
69 |
70 | // 参数判断
71 | var wdToSvc entities.BaseWithdraw
72 | if wdReq.Id == 0 {
73 | // 没有指定提币id,从数据库中挑选最大的id值
74 | asset := utils.GetConfig().GetCoinSettings().Name
75 | if wdToSvc.Id, err = dao.GetWithdrawDAO().GetAvailableId(asset); err != nil {
76 | utils.LogMsgEx(utils.WARNING, "从数据库获取提币ID错误:%v", err)
77 | resp.Code = 500
78 | resp.Msg = err.Error()
79 | ret, _ := json.Marshal(resp)
80 | return ret
81 | }
82 | }
83 |
84 | var exist bool
85 | if exist, err = dao.GetWithdrawDAO().CheckExistsById(asset, wdReq.Id); err != nil {
86 | utils.LogMsgEx(utils.WARNING, "从数据库检查提币ID错误:%v", err)
87 | resp.Code = 500
88 | resp.Msg = err.Error()
89 | ret, _ := json.Marshal(resp)
90 | return ret
91 | }
92 | if exist {
93 | errStr := fmt.Sprintf("收到重复的提币请求,Id:%d", wdReq.Id)
94 | utils.LogMsgEx(utils.WARNING, errStr, nil)
95 | resp.Code = 500
96 | resp.Msg = errStr
97 | ret, _ := json.Marshal(resp)
98 | return ret
99 | } else {
100 | wdToSvc.Id = wdReq.Id
101 | }
102 | wdToSvc.Asset = strings.ToUpper(asset)
103 | if wdReq.Value == 0 {
104 | utils.LogMsgEx(utils.WARNING, "提币金额未指定", nil)
105 | resp.Code = 400
106 | resp.Msg = "提币金额未指定"
107 | ret, _ := json.Marshal(resp)
108 | return ret
109 | } else {
110 | wdToSvc.Amount = wdReq.Value
111 | }
112 | if wdReq.Target == "" {
113 | utils.LogMsgEx(utils.WARNING, "提币目标地址不存在", nil)
114 | resp.Code = 400
115 | resp.Msg = "提币目标地址不存在"
116 | ret, _ := json.Marshal(resp)
117 | return ret
118 | } else {
119 | wdToSvc.Address = wdReq.Target
120 | wdToSvc.To = wdReq.Target
121 | }
122 | services.RevWithdrawSig <- wdToSvc
123 |
124 | resp.Code = 200
125 | resp.Data = wdToSvc.Id
126 | ret, _ := json.Marshal(resp)
127 | return []byte(ret)
128 | }
129 | func getWithdraw(w http.ResponseWriter, req *http.Request) []byte {
130 | var resp RespVO
131 | re := regexp.MustCompile(withdrawPath)
132 | params := re.FindStringSubmatch(req.RequestURI)[1:]
133 | if len(params) == 0 {
134 | resp.Code = 500
135 | resp.Msg = "需要指定币种的名字"
136 | ret, _ := json.Marshal(resp)
137 | return ret
138 | }
139 |
140 | conds := make(map[string]interface {})
141 | conds["asset"] = params[0]
142 | var result []entities.DatabaseWithdraw
143 | var err error
144 | if txHash := req.Form.Get("tx_hash"); txHash != "" {
145 | conds["tx_hash"] = txHash
146 | } else if id := req.Form.Get("id"); id != "" {
147 | if conds["id"], err = strconv.ParseInt(id, 10, 64); err != nil {
148 | utils.LogMsgEx(utils.ERROR, "提币id必须是数字:%s", id)
149 | resp.Code = 500
150 | resp.Msg = err.Error()
151 | ret, _ := json.Marshal(resp)
152 | return ret
153 | }
154 | }
155 |
156 | if result ,err = dao.GetWithdrawDAO().GetWithdraws(conds); err != nil {
157 | resp.Code = 500
158 | resp.Msg = err.Error()
159 | ret, _ := json.Marshal(resp)
160 | return ret
161 | }
162 | resp.Code = 200
163 | resp.Data = result
164 | ret, _ := json.Marshal(resp)
165 | return ret
166 | }
167 | func validAddress(w http.ResponseWriter, req *http.Request) []byte {
168 | var resp RespVO
169 | re := regexp.MustCompile(validAddrPath)
170 | params := re.FindStringSubmatch(req.RequestURI)[1:]
171 | if len(params) == 0 {
172 | resp.Code = 500
173 | resp.Msg = "需要指定币种的名字"
174 | ret, _ := json.Marshal(resp)
175 | return ret
176 | }
177 | if len(params) == 1 {
178 | resp.Code = 500
179 | resp.Msg = "需要指定需检验的地址"
180 | ret, _ := json.Marshal(resp)
181 | return ret
182 | }
183 |
184 | var isValid bool
185 | var err error
186 | if isValid, err = rpcs.GetRPC(params[0]).ValidAddress(params[1]); err != nil {
187 | utils.LogMsgEx(utils.ERROR, "检验地址出错:%v", err)
188 | resp.Code = 500
189 | resp.Msg = err.Error()
190 | ret, _ := json.Marshal(resp)
191 | return ret
192 | }
193 | resp.Code = 200
194 | resp.Data = isValid
195 | ret, _ := json.Marshal(resp)
196 | return ret
197 | }
198 | func cancelWithdraw(w http.ResponseWriter, req *http.Request) []byte {
199 | var resp RespVO
200 | re := regexp.MustCompile(cancelPath)
201 | params := re.FindStringSubmatch(req.RequestURI)[1:]
202 | if len(params) == 0 {
203 | resp.Code = 500
204 | resp.Msg = "需要指定币种的名字"
205 | ret, _ := json.Marshal(resp)
206 | return ret
207 | }
208 | asset := params[0]
209 | if len(params) == 1 {
210 | resp.Code = 500
211 | resp.Msg = "需要指定撤销的提币id(非交易id/hash)"
212 | ret, _ := json.Marshal(resp)
213 | return ret
214 | }
215 | id, _ := strconv.Atoi(params[1])
216 |
217 | if process, err := dao.GetProcessDAO().QueryProcessByTypAndId(asset, entities.WITHDRAW, id); err != nil {
218 | resp.Code = 500
219 | resp.Msg = fmt.Sprintf("未找到指定提币:%d,出错信息:%v", id, err)
220 | ret, _ := json.Marshal(resp)
221 | return ret
222 | } else {
223 | if !process.Cancelable {
224 | resp.Code = 200
225 | resp.Data = process
226 | resp.Msg = fmt.Sprintf("提币已处于无法撤销的状态:%s", process.Process)
227 | ret, _ := json.Marshal(resp)
228 | return ret
229 | }
230 |
231 | // 先从服务中剔除
232 | services.GetWithdrawService().RemoveWithdraw(asset, id)
233 |
234 | // 再删除数据库和缓存中的信息
235 | var result interface {}
236 | if result, err = dao.GetWithdrawDAO().DeleteById(asset, id); err != nil {
237 | resp.Code = 500
238 | resp.Msg = fmt.Sprintf("删除提币记录(%d)失败:%v", id, err)
239 | ret, _ := json.Marshal(resp)
240 | return ret
241 | }
242 |
243 | if _, err = dao.GetProcessDAO().DeleteById(asset, entities.WITHDRAW, id); err != nil {
244 | resp.Code = 500
245 | resp.Msg = fmt.Sprintf("删除提币进度(%d)失败:%v", id, err)
246 | ret, _ := json.Marshal(resp)
247 | return ret
248 | }
249 |
250 | // @_@:解冻资产
251 |
252 | resp.Code = 200
253 | resp.Data = result
254 | ret, _ := json.Marshal(resp)
255 | return ret
256 | }
257 | }
--------------------------------------------------------------------------------
/src/dao/address.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "sync"
5 | "database/sql"
6 | "unsafe"
7 | )
8 |
9 | type addressDao struct {
10 | baseDao
11 | sync.Once
12 | }
13 |
14 | var _addressDao *addressDao
15 |
16 | func GetAddressDAO() *addressDao {
17 | if _addressDao == nil {
18 | _addressDao = new(addressDao)
19 | _addressDao.Once = sync.Once {}
20 | _addressDao.Once.Do(func() {
21 | _addressDao.create("address")
22 | })
23 | }
24 | return _addressDao
25 | }
26 |
27 | func (d *addressDao) newAddress(asset string, address string, inuse bool) (int64, error) {
28 | useSQL := "NewAddress"
29 | if inuse { useSQL = "NewAddressInuse" }
30 | props := []interface {} { asset, address }
31 | return insertTemplate((*baseDao)(unsafe.Pointer(d)), useSQL, props)
32 | }
33 |
34 | func (d *addressDao) NewAddress(asset string, address string) (int64, error) {
35 | return d.newAddress(asset, address, false)
36 | }
37 |
38 | func (d *addressDao) NewAddressInuse(asset string, address string) (int64, error) {
39 | return d.newAddress(asset, address, true)
40 | }
41 |
42 | func (d *addressDao) FindInuseByAsset(asset string) ([]string, error) {
43 | conds := []interface {} { asset }
44 | var result []map[string]interface {}
45 | var err error
46 | if result, err = selectTemplate((*baseDao)(unsafe.Pointer(d)), "FindByAsset", conds); err != nil {
47 | return []string {}, err
48 | }
49 | var ret []string
50 | for _, entity := range result {
51 | ret = append(ret, string(*entity["address"].(*sql.RawBytes)))
52 | }
53 | return ret, nil
54 | }
--------------------------------------------------------------------------------
/src/dao/collect.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "sync"
5 | "unsafe"
6 | )
7 |
8 | type collectDao struct {
9 | baseDao
10 | sync.Once
11 | }
12 |
13 | var _collectDao *collectDao
14 |
15 | func GetCollectDAO() *collectDao {
16 | if _collectDao == nil {
17 | _collectDao = new(collectDao)
18 | _collectDao.Once = sync.Once {}
19 | _collectDao.Once.Do(func() {
20 | _collectDao.create("collect")
21 | })
22 | }
23 | return _collectDao
24 | }
25 |
26 | func (d *collectDao) AddSentCollect(txHash string, asset string, address string, amount float64) (int64, error) {
27 | params := make(map[string]interface {})
28 | params["address"] = address
29 | params["amount"] = amount
30 | params["asset"] = asset
31 | if txHash != "" {
32 | params["tx_hash"] = txHash
33 | }
34 | return insertPartsTemplate((*baseDao)(unsafe.Pointer(d)), "AddSentCollect", params)
35 | }
--------------------------------------------------------------------------------
/src/dao/common.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "os"
5 | "bufio"
6 | "strings"
7 | "io"
8 | "database/sql"
9 | "databases"
10 | "errors"
11 | "fmt"
12 | "utils"
13 | "reflect"
14 | )
15 |
16 | type baseDao struct {
17 | sqls map[string]string
18 | }
19 |
20 | func (dao *baseDao) create(sqlFile string) error {
21 | var err error
22 | if dao.sqls, err = loadSQL(fmt.Sprintf("sql/%s.sql", sqlFile)); err != nil {
23 | panic(utils.LogIdxEx(utils.ERROR, 15, err))
24 | }
25 |
26 | var db *sql.DB
27 | if db, err = databases.ConnectMySQL(); err != nil {
28 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
29 | }
30 |
31 | var createSQL string
32 | var ok bool
33 | if createSQL, ok = dao.sqls["CreateTable"]; ok {
34 | if _, err = db.Exec(createSQL); err != nil {
35 | panic(utils.LogIdxEx(utils.ERROR, 16, err))
36 | }
37 | } else {
38 | return utils.LogIdxEx(utils.ERROR, 11, errors.New("CreateTable"))
39 | }
40 | return nil
41 | }
42 |
43 | func loadSQL(sqlFile string) (map[string]string, error) {
44 | var file *os.File
45 | var err error
46 | if file, err = os.Open(sqlFile); err != nil {
47 | panic(utils.LogIdxEx(utils.ERROR, 17, err))
48 | }
49 | defer file.Close()
50 |
51 | rd := bufio.NewReader(file)
52 | sqlMap := map[string]string {}
53 | var key string
54 | var fmrKey string
55 | for {
56 | line, err := rd.ReadString('\n')
57 | line = strings.TrimSpace(line)
58 |
59 | if err != nil && err != io.EOF {
60 | panic(utils.LogIdxEx(utils.ERROR, 18, err))
61 | }
62 |
63 | if line == "" || line == "\n" {
64 | fmrKey = key
65 | key = ""
66 | } else if line[0] == '#' && len(key) != 0 {
67 | utils.LogIdxEx(utils.ERROR, 19, errors.New("SQL标题要以#开头"))
68 | break
69 | } else if line[0] == '#' {
70 | key = line[1:]
71 | key = strings.TrimSpace(key)
72 | sqlMap[key] = ""
73 | } else if len(key) == 0 {
74 | key = fmrKey
75 | sqlMap[key] += line
76 | } else {
77 | sqlMap[key] += line
78 | }
79 |
80 | if err == io.EOF {
81 | break
82 | }
83 | }
84 | return sqlMap, nil
85 | }
86 |
87 | func insertTemplate(d *baseDao, sqlName string, props []interface {}) (int64, error) {
88 | var db *sql.DB
89 | var err error
90 | if db, err = databases.ConnectMySQL(); err != nil {
91 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
92 | }
93 | defer db.Close()
94 |
95 | var insertSQL string
96 | var ok bool
97 | if insertSQL, ok = d.sqls[sqlName]; !ok {
98 | return 0, utils.LogIdxEx(utils.ERROR, 11, sqlName)
99 | }
100 |
101 | var result sql.Result
102 | if result, err = db.Exec(insertSQL, props...); err != nil {
103 | panic(utils.LogIdxEx(utils.ERROR, 12, err))
104 | }
105 | return result.RowsAffected()
106 | }
107 |
108 | func saveTemplate(d *baseDao, selSqlName string, istSqlName string, updSqlName string, conds []interface{}, props []interface{}, keys []string) (int64, error) {
109 |
110 | var db *sql.DB
111 | var err error
112 | if db, err = databases.ConnectMySQL(); err != nil {
113 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
114 | }
115 | defer db.Close()
116 |
117 | var selectSQL string
118 | var ok bool
119 | if selectSQL, ok = d.sqls[selSqlName]; !ok {
120 | return 0, utils.LogIdxEx(utils.ERROR, 11, selSqlName)
121 | }
122 |
123 | var rows *sql.Rows
124 | if rows, err = db.Query(selectSQL, conds...); err != nil {
125 | panic(utils.LogIdxEx(utils.ERROR, 13, err))
126 | }
127 | defer rows.Close()
128 |
129 | var result sql.Result
130 | if !rows.Next() {
131 | if len(keys) != 0 {
132 | if len(props) != len(keys) {
133 | panic(utils.LogMsgEx(utils.ERROR, "键值无法一一对应", nil))
134 | }
135 |
136 | params := make(map[string]interface {})
137 | for i, k := range keys {
138 | params[k] = props[i]
139 | }
140 | return insertPartsTemplate(d, istSqlName, params)
141 | } else {
142 | var insertSQL string
143 | var ok bool
144 | if insertSQL, ok = d.sqls[istSqlName]; !ok {
145 | return 0, utils.LogIdxEx(utils.ERROR, 11, istSqlName)
146 | }
147 |
148 | if result, err = db.Exec(insertSQL, props...); err != nil {
149 | panic(utils.LogIdxEx(utils.ERROR, 12, err))
150 | }
151 | return result.RowsAffected()
152 | }
153 | } else {
154 | if updSqlName == "" {
155 | return 0, nil
156 | }
157 | if len(props) != len(keys) {
158 | panic(utils.LogMsgEx(utils.ERROR, "键值无法一一对应", nil))
159 | }
160 |
161 | var updateSQL string
162 | if updateSQL, ok = d.sqls[updSqlName]; !ok {
163 | return 0, utils.LogIdxEx(utils.ERROR, 11, updSqlName)
164 | }
165 |
166 | var content []string
167 | for _, key := range keys {
168 | content = append(content, key + "=?")
169 | }
170 | updateSQL = fmt.Sprintf(updateSQL, strings.Join(content, ","))
171 |
172 | if result, err = db.Exec(updateSQL, append(props, conds...)...); err != nil {
173 | panic(utils.LogIdxEx(utils.ERROR, 12, err))
174 | }
175 | }
176 | return result.RowsAffected()
177 | }
178 |
179 | func selectTemplate(d *baseDao, sqlName string, conds []interface {}) ([]map[string]interface {}, error) {
180 | var db *sql.DB
181 | var err error
182 | if db, err = databases.ConnectMySQL(); err != nil {
183 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
184 | }
185 | defer db.Close()
186 |
187 | var selectSQL string
188 | var ok bool
189 | if selectSQL, ok = d.sqls[sqlName]; !ok {
190 | return nil, utils.LogIdxEx(utils.ERROR, 11, sqlName)
191 | }
192 |
193 | var rows *sql.Rows
194 | if rows, err = db.Query(selectSQL, conds...); err != nil {
195 | panic(utils.LogIdxEx(utils.ERROR, 13, err))
196 | }
197 | defer rows.Close()
198 |
199 | var result []map[string]interface {}
200 | for rows.Next() {
201 | var entity = make(map[string]interface {})
202 | var colTyps []*sql.ColumnType
203 | if colTyps, err = rows.ColumnTypes(); err != nil {
204 | panic(utils.LogIdxEx(utils.ERROR, 14, err))
205 | }
206 | var params []interface {}
207 | for _, colTyp := range colTyps {
208 | entity[colTyp.Name()] = reflect.New(colTyp.ScanType()).Interface()
209 | params = append(params, entity[colTyp.Name()])
210 | }
211 | if err = rows.Scan(params...); err != nil {
212 | utils.LogIdxEx(utils.ERROR, 14, err)
213 | continue
214 | }
215 | result = append(result, entity)
216 | }
217 |
218 | if err = rows.Err(); err != nil {
219 | panic(utils.LogIdxEx(utils.ERROR, 14, err))
220 | }
221 | return result, nil
222 | }
223 |
224 | func selectPartsTemplate(d *baseDao, sqlName string, conds map[string]interface{}) ([]map[string]interface {}, error) {
225 | var db *sql.DB
226 | var err error
227 | if db, err = databases.ConnectMySQL(); err != nil {
228 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
229 | }
230 | defer db.Close()
231 |
232 | var selectSQL string
233 | var ok bool
234 | if selectSQL, ok = d.sqls[sqlName]; !ok {
235 | return nil, utils.LogIdxEx(utils.ERROR, 11, sqlName)
236 | }
237 |
238 | var rows *sql.Rows
239 | var keys []string
240 | var vals []interface {}
241 | for key, val := range conds {
242 | keys = append(keys, fmt.Sprintf("%s=?", key))
243 | vals = append(vals, val)
244 | }
245 | selectSQL = fmt.Sprintf(selectSQL, strings.Join(keys, " AND "))
246 | if rows, err = db.Query(selectSQL, vals...); err != nil {
247 | panic(utils.LogIdxEx(utils.ERROR, 13, err))
248 | }
249 | defer rows.Close()
250 |
251 | var result []map[string]interface {}
252 | for rows.Next() {
253 | var entity = make(map[string]interface {})
254 | var colTyps []*sql.ColumnType
255 | if colTyps, err = rows.ColumnTypes(); err != nil {
256 | panic(utils.LogIdxEx(utils.ERROR, 14, err))
257 | }
258 | var params []interface {}
259 | for _, colTyp := range colTyps {
260 | entity[colTyp.Name()] = reflect.New(colTyp.ScanType()).Interface()
261 | params = append(params, entity[colTyp.Name()])
262 | }
263 | if err = rows.Scan(params...); err != nil {
264 | utils.LogIdxEx(utils.ERROR, 14, err)
265 | continue
266 | }
267 | result = append(result, entity)
268 | }
269 |
270 | if err = rows.Err(); err != nil {
271 | panic(utils.LogIdxEx(utils.ERROR, 14, err))
272 | }
273 | return result, nil
274 | }
275 |
276 | func updateTemplate(d *baseDao, sqlName string, conds []interface {}, props []interface {}) (int64, error) {
277 | var db *sql.DB
278 | var err error
279 | if db, err = databases.ConnectMySQL(); err != nil {
280 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
281 | }
282 | defer db.Close()
283 |
284 | var updateSQL string
285 | var ok bool
286 | if updateSQL, ok = d.sqls[sqlName]; !ok {
287 | return 0, utils.LogIdxEx(utils.ERROR, 11, sqlName)
288 | }
289 |
290 | var result sql.Result
291 | if props == nil || len(props) == 0 {
292 | if result, err = db.Exec(updateSQL, conds...); err != nil {
293 | panic(utils.LogIdxEx(utils.ERROR, 21, err))
294 | }
295 | return result.RowsAffected()
296 | } else {
297 | if result, err = db.Exec(updateSQL, append(props, conds...)...); err != nil {
298 | panic(utils.LogIdxEx(utils.ERROR, 21, err))
299 | }
300 | return result.RowsAffected()
301 | }
302 | }
303 |
304 | func updatePartsTemplate(d *baseDao, sqlName string, conds []interface {}, props map[string]interface{}) (int64, error) {
305 | var db *sql.DB
306 | var err error
307 | if db, err = databases.ConnectMySQL(); err != nil {
308 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
309 | }
310 | defer db.Close()
311 |
312 | var updateSQL string
313 | var ok bool
314 | if updateSQL, ok = d.sqls[sqlName]; !ok {
315 | return 0, utils.LogIdxEx(utils.ERROR, 11, sqlName)
316 | }
317 |
318 | var result sql.Result
319 | if props == nil || len(props) == 0 {
320 | if result, err = db.Exec(updateSQL, conds...); err != nil {
321 | panic(utils.LogIdxEx(utils.ERROR, 21, err))
322 | }
323 | return result.RowsAffected()
324 | }
325 |
326 | var content []string
327 | var values []interface {}
328 | for k, v := range props {
329 | content = append(content, k + "=?")
330 | values = append(values, v)
331 | }
332 |
333 | updateSQL = fmt.Sprintf(updateSQL, strings.Join(content, ","))
334 | if result, err = db.Exec(updateSQL, append(values, conds...)...); err != nil {
335 | panic(utils.LogIdxEx(utils.ERROR, 21, err))
336 | }
337 | return result.RowsAffected()
338 | }
339 |
340 | func insertPartsTemplate(d *baseDao, sqlName string, props map[string]interface {}) (int64, error) {
341 | var db *sql.DB
342 | var err error
343 | if db, err = databases.ConnectMySQL(); err != nil {
344 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
345 | }
346 | defer db.Close()
347 |
348 | var insertSQL string
349 | var ok bool
350 | if insertSQL, ok = d.sqls[sqlName]; !ok {
351 | return 0, utils.LogIdxEx(utils.ERROR, 11, sqlName)
352 | }
353 |
354 | var keys []string
355 | var vals []string
356 | var propts []interface {}
357 | for k, v := range props {
358 | keys = append(keys, k)
359 | vals = append(vals, "?")
360 | propts = append(propts, v)
361 | }
362 |
363 | var result sql.Result
364 | insertSQL = fmt.Sprintf(insertSQL, strings.Join(keys, ","), strings.Join(vals, ","))
365 | if result, err = db.Exec(insertSQL, propts...); err != nil {
366 | panic(utils.LogIdxEx(utils.ERROR, 12, err))
367 | }
368 | return result.RowsAffected()
369 | }
370 |
371 | func deleteTemplate(d *baseDao, sqlName string, conds []interface {}) (int64, error) {
372 | var db *sql.DB
373 | var err error
374 | if db, err = databases.ConnectMySQL(); err != nil {
375 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
376 | }
377 | defer db.Close()
378 |
379 | var deleteSQL string
380 | var ok bool
381 | if deleteSQL, ok = d.sqls[sqlName]; !ok {
382 | return 0, utils.LogIdxEx(utils.ERROR, 11, sqlName)
383 | }
384 |
385 | if result, err := db.Exec(deleteSQL, conds...); err != nil {
386 | panic(utils.LogIdxEx(utils.ERROR, 21, err))
387 | } else {
388 | return result.RowsAffected()
389 | }
390 | }
--------------------------------------------------------------------------------
/src/dao/deposit.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "sync"
5 | "database/sql"
6 | "entities"
7 | "unsafe"
8 | "time"
9 | "strconv"
10 | "utils"
11 | "github.com/go-sql-driver/mysql"
12 | )
13 |
14 | type depositDao struct {
15 | baseDao
16 | sync.Once
17 | }
18 |
19 | var _depositDao *depositDao
20 |
21 | func GetDepositDAO() *depositDao {
22 | if _depositDao == nil {
23 | _depositDao = new(depositDao)
24 | _depositDao.Once = sync.Once {}
25 | _depositDao.Once.Do(func() {
26 | _depositDao.create("deposit")
27 | })
28 | }
29 | return _depositDao
30 | }
31 |
32 | func (d *depositDao) AddScannedDeposit(deposit *entities.BaseDeposit) (int64, error) {
33 | var params = []interface {} {
34 | deposit.TxHash,
35 | deposit.Address,
36 | deposit.Amount,
37 | deposit.Asset,
38 | deposit.Height,
39 | deposit.TxIndex,
40 | }
41 | var useSQL = "AddScannedDeposit"
42 | if deposit.CreateTime.Year() > 1000 {
43 | useSQL = "AddDepositWithTime"
44 | params = append(params, deposit.CreateTime)
45 | }
46 | return insertTemplate((*baseDao)(unsafe.Pointer(d)), useSQL, params)
47 | }
48 |
49 | func (d *depositDao) AddStableDeposit(deposit *entities.BaseDeposit) (int64, error) {
50 | if deposit.CreateTime.Year() < 1000 {
51 | deposit.CreateTime = time.Now()
52 | }
53 | return insertTemplate((*baseDao)(unsafe.Pointer(d)), "AddStableDeposit", []interface {} {
54 | deposit.TxHash,
55 | deposit.Address,
56 | deposit.Amount,
57 | deposit.Asset,
58 | deposit.Height,
59 | deposit.TxIndex,
60 | deposit.CreateTime,
61 | })
62 | }
63 |
64 | func (d *depositDao) GetUnstableDeposit(asset string) ([]entities.BaseDeposit, error) {
65 | bd := (*baseDao)(unsafe.Pointer(d))
66 | conds := []interface {} { asset }
67 | var result []map[string]interface {}
68 | var err error
69 | if result, err = selectTemplate(bd, "GetUnstableDeposit", conds); err != nil {
70 | return nil, err
71 | }
72 |
73 | var ret []entities.BaseDeposit
74 | for _, entity := range result {
75 | var deposit entities.BaseDeposit
76 | deposit.To = string(*entity["address"].(*sql.RawBytes))
77 | deposit.Address = deposit.To
78 | deposit.Amount, err = strconv.ParseFloat(string(*entity["amount"].(*sql.RawBytes)), 64)
79 | if err != nil {
80 | panic(utils.LogMsgEx(utils.ERROR, "解析交易金额失败:%v", err))
81 | }
82 | deposit.Asset = asset
83 | deposit.TxHash = string(*entity["tx_hash"].(*sql.RawBytes))
84 | deposit.Height = uint64(*entity["height"].(*int32))
85 | deposit.TxIndex = int(entity["tx_index"].(*sql.NullInt64).Int64)
86 | ret = append(ret, deposit)
87 | }
88 | return ret, nil
89 | }
90 |
91 | func (d *depositDao) DepositIntoStable(txHash string) (int64, error) {
92 | return updatePartsTemplate((*baseDao)(unsafe.Pointer(d)), "DepositIntoStable",
93 | []interface {} { txHash }, nil)
94 | }
95 |
96 | func (d *depositDao) GetDepositId(txHash string) (int, error) {
97 | var result []map[string]interface {}
98 | var err error
99 | bd := (*baseDao)(unsafe.Pointer(d))
100 | conds := []interface {} { txHash }
101 | if result, err = selectTemplate(bd, "GetDepositId", conds); err != nil {
102 | return -1, err
103 | }
104 |
105 | if len(result) != 1 {
106 | return -1, utils.LogMsgEx(utils.ERROR, "找不到交易:%s的充币ID", txHash)
107 | }
108 | depositId := (int) (*(result[0]["id"].(*int32)))
109 | return depositId, nil
110 | }
111 |
112 | func (d *depositDao) GetDeposits(conds map[string]interface {}) ([]entities.DatabaseDeposit, error) {
113 | bd := (*baseDao)(unsafe.Pointer(d))
114 | var result []map[string]interface {}
115 | var err error
116 | if result, err = selectPartsTemplate(bd, "GetDeposits", conds); err != nil {
117 | return nil, err
118 | }
119 |
120 | var ret []entities.DatabaseDeposit
121 | for _, entity := range result {
122 | var deposit entities.DatabaseDeposit
123 | deposit.Id = int(*entity["id"].(*int32))
124 | deposit.TxHash = string(*entity["tx_hash"].(*sql.RawBytes))
125 | deposit.To = string(*entity["address"].(*sql.RawBytes))
126 | deposit.Address = deposit.To
127 | deposit.Amount, err = strconv.ParseFloat(string(*entity["amount"].(*sql.RawBytes)), 64)
128 | if err != nil {
129 | panic(utils.LogMsgEx(utils.ERROR, "解析交易金额失败:%v", err))
130 | }
131 | deposit.Asset = string(*entity["asset"].(*sql.RawBytes))
132 | deposit.Height = uint64(*entity["height"].(*int32))
133 | deposit.TxIndex = int(entity["tx_index"].(*sql.NullInt64).Int64)
134 | deposit.Status = int(entity["status"].(*sql.NullInt64).Int64)
135 | deposit.CreateTime = entity["create_time"].(*mysql.NullTime).Time
136 | deposit.UpdateTime = entity["update_time"].(*mysql.NullTime).Time
137 | ret = append(ret, deposit)
138 | }
139 | return ret, nil
140 | }
141 |
142 | func (d *depositDao) CheckExists(txHash string) (bool, error) {
143 | bd := (*baseDao)(unsafe.Pointer(d))
144 | conds := []interface {} { txHash }
145 | var result []map[string]interface {}
146 | var err error
147 | if result, err = selectTemplate(bd, "CheckExists", conds); err != nil {
148 | return false, err
149 | }
150 |
151 | if len(result) != 1 {
152 | return false, utils.LogMsgEx(utils.ERROR, "数据库查询结果异常:COUNT返回无结果")
153 | }
154 | var ok bool
155 | var tmp interface {}
156 | if tmp, ok = result[0]["num"]; !ok {
157 | return false, utils.LogMsgEx(utils.ERROR, "COUNT结果没有指定键值:num")
158 | }
159 | return *tmp.(*int64) != 0, nil
160 | }
--------------------------------------------------------------------------------
/src/dao/height.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "sync"
5 | "database/sql"
6 | "utils"
7 | "unsafe"
8 | )
9 |
10 | type heightDao struct {
11 | baseDao
12 | sync.Once
13 | }
14 |
15 | var _heightDao *heightDao
16 |
17 | func GetHeightDAO() *heightDao {
18 | if _heightDao == nil {
19 | _heightDao = new(heightDao)
20 | _heightDao.Once = sync.Once {}
21 | _heightDao.Once.Do(func() {
22 | _heightDao.create("height")
23 | })
24 | }
25 | return _heightDao
26 | }
27 |
28 | func (d *heightDao) ChkOrAddAsset(asset string) (int64, error) {
29 | conds := []interface {} { asset }
30 | return saveTemplate((*baseDao)(unsafe.Pointer(d)),
31 | "GetHeight", "AddAsset", "",
32 | conds, conds, nil)
33 | }
34 |
35 | func (d *heightDao) GetHeight(asset string) (int64, error) {
36 | var result []map[string]interface {}
37 | var err error
38 | var conds = []interface {} { asset }
39 | bd :=(*baseDao)(unsafe.Pointer(d))
40 | if result, err = selectTemplate(bd, "GetHeight", conds); err != nil {
41 | return 0, err
42 | }
43 | if len(result) != 1 {
44 | return 0, utils.LogMsgEx(utils.WARNING, "获取高度失败", nil)
45 | }
46 | return result[0]["height"].(*sql.NullInt64).Int64, nil
47 | }
48 |
49 | func (d *heightDao) UpdateHeight(asset string, height uint64) (int64, error) {
50 | conds := []interface {} { asset }
51 | props := map[string]interface {} {
52 | "height": height,
53 | }
54 | return updatePartsTemplate((*baseDao)(unsafe.Pointer(d)), "UpdateHeight", conds, props)
55 | }
--------------------------------------------------------------------------------
/src/dao/process.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "sync"
5 | "entities"
6 | "utils"
7 | "databases"
8 | "github.com/go-redis/redis"
9 | "time"
10 | "fmt"
11 | "strings"
12 | "encoding/json"
13 | "net/http"
14 | "bytes"
15 | "strconv"
16 | )
17 |
18 | var _timeFormat = map[string]string {
19 | "ANSIC": time.ANSIC,
20 | "UnixDate": time.UnixDate,
21 | "RubyDate": time.RubyDate,
22 | "RFC822": time.RFC822,
23 | "RFC822Z": time.RFC822Z,
24 | "RFC850": time.RFC850,
25 | "RFC1123": time.RFC1123,
26 | "RFC1123Z": time.RFC1123Z,
27 | "RFC3339": time.RFC3339,
28 | "RFC3339Nano": time.RFC3339Nano,
29 | "Kitchen": time.Kitchen,
30 | }
31 |
32 | type processDao struct {
33 | baseDao
34 | sync.Once
35 | }
36 |
37 | var _processDao *processDao
38 |
39 | func GetProcessDAO() *processDao {
40 | if _processDao == nil {
41 | _processDao = new(processDao)
42 | _processDao.Once = sync.Once {}
43 | _processDao.Once.Do(func() {})
44 | }
45 | return _processDao
46 | }
47 |
48 | func (d *processDao) SaveProcess(process *entities.DatabaseProcess) (int64, error) {
49 | if process.Asset == "" {
50 | return 0, utils.LogMsgEx(utils.ERROR, "保存进度的时候需指定币种", nil)
51 | }
52 |
53 | var cli redis.Cmdable
54 | var err error
55 | if cli, err = databases.ConnectRedis(); err != nil {
56 | return 0, utils.LogIdxEx(utils.ERROR, 42, err)
57 | }
58 |
59 | var key string
60 | if process.TxHash != "" {
61 | key = fmt.Sprintf("process_%s_%s", process.Asset, process.TxHash)
62 | var n int64
63 | k := fmt.Sprintf("process_%s_%s_%d", process.Asset, process.Type, process.Id)
64 | if n, err = cli.Exists(k).Result(); n > 0 {
65 | if process.Id == 0 {
66 | var id uint64
67 | id, _ = cli.HGet(k, "id").Uint64()
68 | process.Id = int(id)
69 | }
70 | if process.Process == "" {
71 | process.Process, _ = cli.HGet(k, "process").Result()
72 | }
73 | if process.Height == 0 {
74 | process.Height, _ = cli.HGet(k, "height").Uint64()
75 | }
76 | if process.CurrentHeight == 0 {
77 | process.CurrentHeight, _ = cli.HGet(k, "current_height").Uint64()
78 | }
79 | if process.CompleteHeight == 0 {
80 | process.CompleteHeight, _ = cli.HGet(k, "complete_height").Uint64()
81 | }
82 | cli.Del(k)
83 | }
84 | } else {
85 | if process.Type == "" || process.Id == 0 {
86 | return 0, utils.LogMsgEx(utils.ERROR, "未指定哈希、类型和ID", nil)
87 | }
88 | key = fmt.Sprintf("process_%s_%s_%d", process.Asset, process.Type, process.Id)
89 | }
90 |
91 | if process.Id != 0 {
92 | if err = cli.HSet(key, "id", process.Id).Err(); err != nil {
93 | return 0, utils.LogMsgEx(utils.ERROR, "设置id失败:%v", err)
94 | }
95 | }
96 | if process.TxHash != "" {
97 | if err = cli.HSet(key, "tx_hash", process.TxHash).Err(); err != nil {
98 | return 0, utils.LogMsgEx(utils.ERROR, "设置tx_hash失败:%v", err)
99 | }
100 | }
101 | if process.Asset != "" {
102 | if err = cli.HSet(key, "asset", process.Asset).Err(); err != nil {
103 | return 0, utils.LogMsgEx(utils.ERROR, "设置asset失败:%v", err)
104 | }
105 | }
106 | if process.Type != "" {
107 | if err = cli.HSet(key, "type", process.Type).Err(); err != nil {
108 | return 0, utils.LogMsgEx(utils.ERROR, "设置type失败:%v", err)
109 | }
110 | }
111 | if utils.StrArrayContains(entities.Processes, process.Process) {
112 | if err = cli.HSet(key, "process", process.Process).Err(); err != nil {
113 | return 0, utils.LogMsgEx(utils.ERROR, "设置process失败:%v", err)
114 | }
115 | }
116 | if err = cli.HSet(key, "cancelable", process.Cancelable).Err(); err != nil {
117 | return 0, utils.LogMsgEx(utils.ERROR, "设置cancelable失败:%v", err)
118 | }
119 | if process.Height != 0 {
120 | if err = cli.HSet(key, "height", process.Height).Err(); err != nil {
121 | return 0, utils.LogMsgEx(utils.ERROR, "设置height失败:%v", err)
122 | }
123 | }
124 | if process.CurrentHeight != 0 {
125 | if err = cli.HSet(key, "current_height", process.CurrentHeight).Err(); err != nil {
126 | return 0, utils.LogMsgEx(utils.ERROR, "设置current_height失败:%v", err)
127 | }
128 | }
129 | if process.CompleteHeight != 0 {
130 | if err = cli.HSet(key, "complete_height", process.CompleteHeight).Err(); err != nil {
131 | return 0, utils.LogMsgEx(utils.ERROR, "设置complete_height失败:%v", err)
132 | }
133 | }
134 | idTmFmt := utils.GetConfig().GetSubsSettings().Redis.TimeFormat
135 | var ok bool
136 | if idTmFmt, ok = _timeFormat[idTmFmt]; !ok {
137 | idTmFmt = _timeFormat["RFC3339"]
138 | }
139 | if err = cli.HSet(key, "last_update_time", time.Now().Format(idTmFmt)).Err(); err != nil {
140 | return 0, utils.LogMsgEx(utils.ERROR, "设置last_update_time失败:%v", err)
141 | }
142 | // 如果交易完成,会持久化到数据库,redis挂1天
143 | if process.Process == entities.FINISH {
144 | cli.Expire(key, 24 * time.Hour)
145 | }
146 | // 发布这条交易的进度键
147 | var procs entities.DatabaseProcess
148 | if procs, err = d.queryProcess(key); err != nil {
149 | return 0, utils.LogMsgEx(utils.ERROR, "获取进度失败:%v", err)
150 | }
151 | if utils.GetConfig().GetSubsSettings().Callbacks.Redis.Active {
152 | var strProcs []byte
153 | if strProcs, err = json.Marshal(procs); err != nil {
154 | return 0, utils.LogIdxEx(utils.ERROR, 22, err)
155 | }
156 | pocsPubKey := utils.GetConfig().GetSubsSettings().Redis.ProcessPubKey
157 | if _, err = cli.Publish(pocsPubKey, strProcs).Result(); err != nil {
158 | return 0, utils.LogMsgEx(utils.ERROR, "发布进度错误:%v", err)
159 | }
160 | }
161 | if utils.GetConfig().GetSubsSettings().Callbacks.RPC.Active {
162 | rpcSet := utils.GetConfig().GetSubsSettings().Callbacks.RPC
163 | url := ""
164 | switch procs.Type {
165 | case entities.DEPOSIT:
166 | url = rpcSet.DepositURL
167 | case entities.WITHDRAW:
168 | url = rpcSet.WithdrawURL
169 | case entities.COLLECT:
170 | url = rpcSet.CollectURL
171 | }
172 | if url == "" {
173 | return 1, nil
174 | }
175 | strAry := strings.Split(url, " ")
176 | method := http.MethodPost
177 | switch len(strAry) {
178 | case 1:
179 | // 默认采用POST格式发送回调
180 | case 2:
181 | method = strAry[0]
182 | url = strAry[1]
183 | default:
184 | panic(utils.LogIdxEx(utils.ERROR, 44))
185 | }
186 |
187 | var strProcs []byte
188 | if strProcs, err = json.Marshal(procs); err != nil {
189 | return 0, utils.LogIdxEx(utils.ERROR, 22, err)
190 | }
191 | var req *http.Request
192 | if req, err = http.NewRequest(method, url, bytes.NewBuffer(strProcs)); err != nil {
193 | return 0, utils.LogMsgEx(utils.ERROR, "构建请求失败:%v", err)
194 | }
195 | req.Header.Add("Content-Type", "application/json")
196 | client := &http.Client {}
197 | if _, err := client.Do(req); err != nil {
198 | return 0, utils.LogMsgEx(utils.ERROR, "发送回调请求:%v", err)
199 | }
200 | }
201 | return 1, nil
202 | }
203 |
204 | func (d *processDao) QueryProcessByTypAndId(asset string, typ string, id int) (entities.DatabaseProcess, error) {
205 | return d.queryProcess(fmt.Sprintf("process_%s_%s_%d", asset, typ, id))
206 | }
207 |
208 | func (d *processDao) QueryProcessByTxHash(asset string, txHash string) (entities.DatabaseProcess, error) {
209 | return d.queryProcess(fmt.Sprintf("process_%s_%s", asset, txHash))
210 | }
211 |
212 | func (d *processDao) queryProcess(key string) (entities.DatabaseProcess, error) {
213 | var ret entities.DatabaseProcess
214 | var cli redis.Cmdable
215 | var err error
216 | if cli, err = databases.ConnectRedis(); err != nil {
217 | return ret, utils.LogIdxEx(utils.ERROR, 42, err)
218 | }
219 |
220 | var id int64
221 | id, err = cli.HGet(key, "id").Int64()
222 | ret.Id = int(id)
223 | ret.TxHash, err = cli.HGet(key, "tx_hash").Result()
224 | ret.Asset, err = cli.HGet(key, "asset").Result()
225 | ret.Type, err = cli.HGet(key, "type").Result()
226 | ret.Process, err = cli.HGet(key, "process").Result()
227 | var strCancelable string
228 | strCancelable, err = cli.HGet(key, "cancelable").Result()
229 | ret.Cancelable = strCancelable != "0" && strCancelable != "false"
230 | ret.Height, err = cli.HGet(key, "height").Uint64()
231 | ret.CurrentHeight, err = cli.HGet(key, "current_height").Uint64()
232 | ret.CompleteHeight, err = cli.HGet(key, "complete_height").Uint64()
233 | var strLstUpdTm string
234 | strLstUpdTm, err = cli.HGet(key, "last_update_time").Result()
235 | idTmFmt := utils.GetConfig().GetSubsSettings().Redis.TimeFormat
236 | var ok bool
237 | if idTmFmt, ok = _timeFormat[idTmFmt]; !ok {
238 | idTmFmt = _timeFormat["RFC3339"]
239 | }
240 | ret.LastUpdateTime, err = time.Parse(idTmFmt, strLstUpdTm)
241 | if err != nil {
242 | return ret, utils.LogMsgEx(utils.ERROR, "获取进度失败:%v", err)
243 | } else {
244 | return ret, nil
245 | }
246 | }
247 |
248 | func (d *processDao) UpdateHeight(asset string, curHeight uint64) (int64, error) {
249 | var cli redis.Cmdable
250 | var err error
251 | if cli, err = databases.ConnectRedis(); err != nil {
252 | return 0, utils.LogIdxEx(utils.ERROR, 42, err)
253 | }
254 |
255 | var keys []string
256 | if keys, err = cli.Keys("process_*").Result(); err != nil {
257 | return 0, utils.LogMsgEx(utils.ERROR, "获取所有键失败:%v", err)
258 | }
259 |
260 | numKeys := len(keys)
261 | for _, key := range keys {
262 | var process string
263 | if process, err = cli.HGet(key, "process").Result(); err != nil {
264 | utils.LogMsgEx(utils.ERROR, "查找进度失败:%v", err)
265 | continue
266 | }
267 | if strings.ToUpper(process) == entities.FINISH {
268 | continue
269 | }
270 | if err = cli.HSet(key, "current_height", curHeight).Err(); err != nil {
271 | utils.LogMsgEx(utils.ERROR, "更新交易:%s失败:%v", key, err)
272 | numKeys--
273 | }
274 | }
275 | return int64(numKeys), nil
276 | }
277 |
278 | func (d *processDao) DeleteById(asset string, typ string, id int) (int64, error) {
279 | key := fmt.Sprintf("process_%s_%s_%d", asset, typ, id)
280 |
281 | var cli redis.Cmdable
282 | var err error
283 | if cli, err = databases.ConnectRedis(); err != nil {
284 | return 0, utils.LogIdxEx(utils.ERROR, 42, err)
285 | }
286 |
287 | if cli.Exists(key).Val() == 1 {
288 | return cli.Del(key).Result()
289 | } else {
290 | if keys, err := cli.Keys("process_*").Result(); err != nil {
291 | return -1, utils.LogMsgEx(utils.ERROR, "获取所有进度键值失败:%v", err)
292 | } else {
293 | for _, k := range keys {
294 | strId := cli.HGet(k, "id").Val()
295 | if len(strId) == 0 {
296 | continue
297 | }
298 | if i, err := strconv.Atoi(strId); err != nil {
299 | utils.LogMsgEx(utils.ERROR, "存储的进度ID(%s)非数字,解析出错:%v", strId, err)
300 | continue
301 | } else if i != id {
302 | continue
303 | }
304 | return cli.Del(k).Result()
305 | }
306 | return 0, nil
307 | }
308 | }
309 | }
--------------------------------------------------------------------------------
/src/dao/transaction.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "sync"
5 | "entities"
6 | "unsafe"
7 | "utils"
8 | )
9 |
10 | type transactionDao struct {
11 | baseDao
12 | sync.Once
13 | }
14 |
15 | var _transactionDao *transactionDao
16 |
17 | func GetTransactionDAO() *transactionDao {
18 | if _transactionDao == nil {
19 | _transactionDao = new(transactionDao)
20 | _transactionDao.Once = sync.Once {}
21 | _transactionDao.Once.Do(func() {
22 | _transactionDao.create("transaction")
23 | })
24 | }
25 | return _transactionDao
26 | }
27 |
28 | func (d *transactionDao) AddTransaction(tx entities.Transaction, oprInf string) (int64, error) {
29 | bd := (*baseDao)(unsafe.Pointer(d))
30 | var result []map[string]interface {}
31 | var err error
32 | if result, err = selectTemplate(bd, "CheckExists", []interface {} { oprInf }); err != nil {
33 | return 0, utils.LogMsgEx(utils.ERROR, "检测交易存在与否失败:%v", err)
34 | }
35 | if len(result) != 1 {
36 | return 0, utils.LogMsgEx(utils.ERROR, "检测结果不等于1", nil)
37 | }
38 | var ok bool
39 | var tmp interface {}
40 | if tmp, ok = result[0]["num"]; !ok {
41 | return 0, utils.LogMsgEx(utils.ERROR, "检测存在数量的num分量不存在", nil)
42 | }
43 | if *tmp.(*int64) != 0 {
44 | utils.LogMsgEx(utils.WARNING, "交易:%s已被记录", oprInf)
45 | return 0, nil
46 | }
47 |
48 | entity := make(map[string]interface {})
49 | entity["opr_info"] = oprInf
50 | if tx.TxHash != "" {
51 | entity["tx_hash"] = tx.TxHash
52 | }
53 | if tx.BlockHash != "" {
54 | entity["block_hash"] = tx.BlockHash
55 | }
56 | if tx.From != "" {
57 | entity["`from`"] = tx.From
58 | }
59 | if tx.To != "" {
60 | entity["`to`"] = tx.To
61 | }
62 | if tx.Amount != 0 {
63 | entity["amount"] = tx.Amount
64 | }
65 | if tx.Asset != "" {
66 | entity["asset"] = tx.Asset
67 | }
68 | if tx.Height != 0 {
69 | entity["height"] = tx.Height
70 | }
71 | if tx.TxIndex != 0 {
72 | entity["tx_index"] = tx.TxIndex
73 | }
74 | if tx.CreateTime.Year() > 1990 {
75 | entity["create_time"] = tx.CreateTime
76 | }
77 | return insertPartsTemplate(bd, "AddTransaction", entity)
78 | }
--------------------------------------------------------------------------------
/src/dao/withdraw.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "sync"
5 | "entities"
6 | "unsafe"
7 | "utils"
8 | "errors"
9 | "database/sql"
10 | "strconv"
11 | "github.com/go-sql-driver/mysql"
12 | )
13 |
14 | type withdrawDao struct {
15 | baseDao
16 | sync.Once
17 | }
18 |
19 | var _withdrawDao *withdrawDao
20 |
21 | func GetWithdrawDAO() *withdrawDao {
22 | if _withdrawDao == nil {
23 | _withdrawDao = new(withdrawDao)
24 | _withdrawDao.Once = sync.Once {}
25 | _withdrawDao.Once.Do(func() {
26 | _withdrawDao.create("withdraw")
27 | })
28 | }
29 | return _withdrawDao
30 | }
31 |
32 | func (d *withdrawDao) getWithdraws(asset string, sqlName string) ([]entities.DatabaseWithdraw, error) {
33 | var result []map[string]interface {}
34 | var err error
35 | conds := []interface {} { asset }
36 | if result, err = selectTemplate((*baseDao)(unsafe.Pointer(d)), sqlName, conds); err != nil {
37 | return nil, utils.LogIdxEx(utils.ERROR, 39, err)
38 | }
39 |
40 | var ret []entities.DatabaseWithdraw
41 | for _, entity := range result {
42 | var bwd entities.DatabaseWithdraw
43 | bwd.Id = int(*entity["id"].(*int32))
44 | bwd.TxHash = string(*entity["tx_hash"].(*sql.RawBytes))
45 | bwd.Address = string(*entity["address"].(*sql.RawBytes))
46 | bwd.Amount, err = strconv.ParseFloat(string(*entity["amount"].(*sql.RawBytes)), 64)
47 | if err != nil {
48 | panic(utils.LogMsgEx(utils.ERROR, "解析交易金额失败:%v", err))
49 | }
50 | bwd.Asset = string(*entity["asset"].(*sql.RawBytes))
51 | bwd.Height = uint64(entity["height"].(*sql.NullInt64).Int64)
52 | bwd.TxIndex = int(entity["tx_index"].(*sql.NullInt64).Int64)
53 | bwd.Status = int(entity["status"].(*sql.NullInt64).Int64)
54 | bwd.CreateTime = entity["create_time"].(*mysql.NullTime).Time
55 | bwd.UpdateTime = entity["update_time"].(*mysql.NullTime).Time
56 | ret = append(ret, bwd)
57 | }
58 | return ret, nil
59 | }
60 |
61 | func (d *withdrawDao) GetUnfinishWithdraw(asset string) ([]entities.DatabaseWithdraw, error) {
62 | return d.getWithdraws(asset, "GetUnfinishWithdraw")
63 | }
64 |
65 | func (d *withdrawDao) GetUnstableWithdraw(asset string) ([]entities.DatabaseWithdraw, error) {
66 | return d.getWithdraws(asset, "GetUnstableWithdraw")
67 | }
68 |
69 | func (d *withdrawDao) GetAvailableId(asset string) (int, error) {
70 | var result []map[string]interface {}
71 | var err error
72 | conds := []interface {} { asset }
73 | if result, err = selectTemplate((*baseDao)(unsafe.Pointer(d)), "GetAvailableId", conds); err != nil {
74 | return 0, utils.LogIdxEx(utils.ERROR, 38, err)
75 | }
76 | if len(result) != 1 {
77 | return 0, utils.LogIdxEx(utils.ERROR, 38, errors.New("返回的id数量不等于1"))
78 | }
79 |
80 | newId := result[0]
81 | var ret *sql.NullInt64
82 | var ok bool
83 | if ret, ok = newId["new_id"].(*sql.NullInt64); !ok {
84 | return 0, utils.LogIdxEx(utils.ERROR, 38, errors.New("返回值不包含new_id"))
85 | }
86 | return int(ret.Int64), nil
87 | }
88 |
89 | func (d *withdrawDao) RecvNewWithdraw(withdraw entities.BaseWithdraw) (int64, error) {
90 | return insertTemplate((*baseDao)(unsafe.Pointer(d)), "RecvNewWithdraw", []interface {} {
91 | withdraw.Id,
92 | withdraw.Address,
93 | withdraw.Amount,
94 | withdraw.Asset,
95 | })
96 | }
97 |
98 | func (d *withdrawDao) WithdrawIntoStable(asset string, txHash string) (int64, error) {
99 | return updatePartsTemplate((*baseDao)(unsafe.Pointer(d)), "WithdrawIntoStable",
100 | []interface {} { asset, txHash }, nil)
101 | }
102 |
103 | func (d *withdrawDao) WithdrawIntoChain(asset string, txHash string, height uint64, txIndex int) (int64, error) {
104 | return updatePartsTemplate((*baseDao)(unsafe.Pointer(d)), "WithdrawIntoChain",
105 | []interface {} { asset, txHash }, map[string]interface {} {
106 | "height": height,
107 | "tx_index": txIndex,
108 | })
109 | }
110 |
111 | func (d *withdrawDao) SentForTxHash(asset string, txHash string, id int) (int64, error) {
112 | return updateTemplate((*baseDao)(unsafe.Pointer(d)), "SentForTxHash",
113 | []interface {} { asset, id }, []interface {} { txHash })
114 | }
115 |
116 | func (d *withdrawDao) GetWithdrawId(asset string, txHash string) (int, error) {
117 | var result []map[string]interface {}
118 | var err error
119 | bd := (*baseDao)(unsafe.Pointer(d))
120 | conds := []interface {} { asset, txHash }
121 | if result, err = selectTemplate(bd, "GetWithdrawId", conds); err != nil {
122 | return -1, err
123 | }
124 |
125 | if len(result) != 1 {
126 | return -1, utils.LogMsgEx(utils.ERROR, "找不到交易:%s的提币ID", txHash)
127 | }
128 | return int(*result[0]["id"].(*int32)), nil
129 | }
130 |
131 | func (d *withdrawDao) GetWithdraws(conds map[string]interface {}) ([]entities.DatabaseWithdraw, error) {
132 | bd := (*baseDao)(unsafe.Pointer(d))
133 | var result []map[string]interface {}
134 | var err error
135 | if result, err = selectPartsTemplate(bd, "GetWithdraws", conds); err != nil {
136 | return nil, utils.LogMsgEx(utils.ERROR, "查询提币记录失败:%v", err)
137 | }
138 |
139 | var ret []entities.DatabaseWithdraw
140 | for _, entity := range result {
141 | var withdraw entities.DatabaseWithdraw
142 | withdraw.Id = int(*entity["id"].(*int32))
143 | withdraw.TxHash = string(*entity["tx_hash"].(*sql.RawBytes))
144 | withdraw.To = string(*entity["address"].(*sql.RawBytes))
145 | withdraw.Address = withdraw.To
146 | withdraw.Amount, err = strconv.ParseFloat(string(*entity["amount"].(*sql.RawBytes)), 64)
147 | if err != nil {
148 | panic(utils.LogMsgEx(utils.ERROR, "解析交易金额失败:%v", err))
149 | }
150 | withdraw.Asset = string(*entity["asset"].(*sql.RawBytes))
151 | withdraw.Height = uint64(entity["height"].(*sql.NullInt64).Int64)
152 | withdraw.TxIndex = int(entity["tx_index"].(*sql.NullInt64).Int64)
153 | withdraw.Status = int(entity["status"].(*sql.NullInt64).Int64)
154 | withdraw.CreateTime = entity["create_time"].(*mysql.NullTime).Time
155 | withdraw.UpdateTime = entity["update_time"].(*mysql.NullTime).Time
156 | ret = append(ret, withdraw)
157 | }
158 | return ret, nil
159 | }
160 |
161 | func (d *withdrawDao) CheckExistsById(asset string, id int) (bool, error) {
162 | bd := (*baseDao)(unsafe.Pointer(d))
163 | conds := []interface {} { asset, id }
164 | var result []map[string]interface {}
165 | var err error
166 | if result, err = selectTemplate(bd, "CheckExistsById", conds); err != nil {
167 | return false, err
168 | }
169 |
170 | if len(result) != 1 {
171 | return false, utils.LogMsgEx(utils.ERROR, "数据库查询结果异常:COUNT返回无结果")
172 | }
173 | var ok bool
174 | var tmp interface {}
175 | if tmp, ok = result[0]["num"]; !ok {
176 | return false, utils.LogMsgEx(utils.ERROR, "COUNT结果没有指定键值:num")
177 | }
178 | return *tmp.(*int64) != 0, nil
179 | }
180 |
181 | func (d *withdrawDao) DeleteById(asset string, id int) ([]entities.DatabaseWithdraw, error) {
182 | conds := make(map[string]interface {})
183 | conds["asset"] = asset
184 | conds["id"] = id
185 | if result, err := d.GetWithdraws(conds); err != nil {
186 | return result, err
187 | } else {
188 | bd := (*baseDao)(unsafe.Pointer(d))
189 | if _, err := deleteTemplate(bd, "DeleteById", []interface {} { asset, id }); err != nil {
190 | return result, utils.LogMsgEx(utils.ERROR, "删除提币记录失败:%v", err)
191 | } else {
192 | return result, nil
193 | }
194 | }
195 | }
--------------------------------------------------------------------------------
/src/databases/mysql.go:
--------------------------------------------------------------------------------
1 | package databases
2 |
3 | import (
4 | "fmt"
5 | "database/sql"
6 | _ "github.com/go-sql-driver/mysql"
7 | "utils"
8 | "strings"
9 | "net/url"
10 | "time"
11 | )
12 |
13 | var PARAMS = []string {
14 | "charset=utf8",
15 | "tls=skip-verify",
16 | "parseTime=true",
17 | fmt.Sprintf("loc=%s", url.QueryEscape("Asia/Shanghai")),
18 | }
19 |
20 | func ConnectMySQL() (*sql.DB, error) {
21 | config := utils.GetConfig()
22 | params := config.GetSubsSettings().Db
23 | db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?%s",
24 | params.Username, params.Password, params.Url, params.Name, strings.Join(PARAMS, "&")))
25 | if err != nil {
26 | panic(utils.LogIdxEx(utils.ERROR, 10, err))
27 | }
28 | // 如果超出最大连接数,等待可用的连接
29 | for db.Stats().OpenConnections >= config.GetSubsSettings().Db.MaxConn {
30 | time.Sleep(5 * time.Second)
31 | }
32 | return db, nil
33 | }
--------------------------------------------------------------------------------
/src/databases/redis.go:
--------------------------------------------------------------------------------
1 | package databases
2 |
3 | import (
4 | "github.com/go-redis/redis"
5 | "sync"
6 | "utils"
7 | )
8 |
9 | var __redisClis redis.Cmdable
10 | var __once sync.Once
11 |
12 | func ConnectRedis() (redis.Cmdable, error) {
13 | var err error
14 | if __redisClis == nil {
15 | __once = sync.Once {}
16 | __once.Do(func() {
17 | err = createClients()
18 | })
19 | }
20 | return __redisClis, err
21 | }
22 |
23 | func createClients() error {
24 | redisCfg := utils.GetConfig().GetSubsSettings().Redis
25 | if len(redisCfg.Clusters) > 1 {
26 | var redisAddr []string
27 | for _, rds := range redisCfg.Clusters {
28 | redisAddr = append(redisAddr, rds.Url)
29 | }
30 | redisClis := redis.NewClusterClient(&redis.ClusterOptions {
31 | Addrs: redisAddr,
32 | Password: redisCfg.Password,
33 | })
34 | err := redisClis.Ping().Err()
35 | __redisClis = redisClis
36 | return err
37 | } else {
38 | redisCli := redis.NewClient(&redis.Options {
39 | Addr: redisCfg.Clusters[0].Url,
40 | Password: redisCfg.Password,
41 | DB: 0,
42 | })
43 | err := redisCli.Ping().Err()
44 | __redisClis = redisCli
45 | return err
46 | }
47 | }
--------------------------------------------------------------------------------
/src/entities/deposit.go:
--------------------------------------------------------------------------------
1 | package entities
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | const (
8 | DEPOSIT_FOUND = iota + 1
9 | DEPOSIT_FINISHED
10 | )
11 |
12 | type BaseDeposit struct {
13 | Transaction
14 | Address string `json:"address"`
15 | }
16 |
17 | type DatabaseDeposit struct {
18 | BaseDeposit
19 | Id int `json:"id"`
20 | Status int `json:"status"`
21 | UpdateTime time.Time `json:"update_time"`
22 | }
23 |
24 | func TurnTxToDeposit(tx *Transaction) BaseDeposit {
25 | var deposit BaseDeposit
26 | deposit.TxHash = tx.TxHash
27 | deposit.Address = tx.To
28 | deposit.Height = tx.Height
29 | deposit.CreateTime = tx.CreateTime
30 | deposit.Amount = tx.Amount
31 | deposit.Asset = tx.Asset
32 | deposit.TxIndex = tx.TxIndex
33 | return deposit
34 | }
--------------------------------------------------------------------------------
/src/entities/process.go:
--------------------------------------------------------------------------------
1 | package entities
2 |
3 | import "time"
4 |
5 | const (
6 | DEPOSIT = "DEPOSIT"
7 | COLLECT = "COLLECT"
8 | WITHDRAW = "WITHDRAW"
9 | )
10 |
11 | var Types = []string {
12 | DEPOSIT, COLLECT, WITHDRAW,
13 | }
14 |
15 | const (
16 | AUDIT = "AUDIT"
17 | LOAD = "LOAD"
18 | SENT = "SENT"
19 | SENDING = "SENDING"
20 | INCHAIN = "INCHAIN"
21 | FINISH = "FINISH"
22 | )
23 |
24 | var Processes = []string {
25 | AUDIT, LOAD, SENT, SENDING, INCHAIN, FINISH,
26 | }
27 |
28 | type BaseProcess struct {
29 | Id int `json:"id"`
30 | TxHash string `json:"tx_hash"`
31 | Asset string `json:"asset"`
32 | Type string `json:"type"`
33 | Process string `json:"process"`
34 | Cancelable bool `json:"cancelable"`
35 | }
36 |
37 | type DatabaseProcess struct {
38 | BaseProcess
39 | Height uint64 `json:"height"`
40 | CurrentHeight uint64 `json:"current_height"`
41 | CompleteHeight uint64 `json:"complete_height"`
42 | LastUpdateTime time.Time`json:"last_update_time"`
43 | }
--------------------------------------------------------------------------------
/src/entities/transaction.go:
--------------------------------------------------------------------------------
1 | package entities
2 |
3 | import "time"
4 |
5 | type Transaction struct {
6 | TxHash string `json:"tx_hash"`
7 | BlockHash string `json:"block_hash"`
8 | From string `json:"from"`
9 | To string `json:"to"`
10 | Amount float64 `json:"amount"`
11 | Asset string `json:"asset"`
12 | Height uint64 `json:"height"`
13 | TxIndex int `json:"tx_index"`
14 | CreateTime time.Time `json:"create_time"`
15 | }
--------------------------------------------------------------------------------
/src/entities/withdraw.go:
--------------------------------------------------------------------------------
1 | package entities
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | const (
8 | WITHDRAW_LOAD = iota + 1
9 | WITHDRAW_SENT
10 | WITHDRAW_INCHAIN
11 | WITHDRAW_FINISHED
12 | )
13 |
14 | type BaseWithdraw struct {
15 | Transaction
16 | Id int `json:"id"`
17 | Address string `json:"address"`
18 | }
19 |
20 | type DatabaseWithdraw struct {
21 | BaseWithdraw
22 | Status int `json:"status"`
23 | UpdateTime time.Time `json:"update_time"`
24 | }
25 |
26 | func TurnToBaseWithdraw(wd *DatabaseWithdraw) BaseWithdraw {
27 | var ret BaseWithdraw
28 | ret.Id = wd.Id
29 | ret.Asset = wd.Asset
30 | ret.Address = wd.Address
31 | ret.Amount = wd.Amount
32 | return ret
33 | }
--------------------------------------------------------------------------------
/src/rpcs/btc.go:
--------------------------------------------------------------------------------
1 | package rpcs
2 |
3 | import (
4 | "sync"
5 | "utils"
6 | "entities"
7 | "encoding/json"
8 | "fmt"
9 | "bytes"
10 | "net/http"
11 | "io/ioutil"
12 | "math/rand"
13 | "time"
14 | "math"
15 | )
16 |
17 | type btc struct {
18 | sync.Once
19 | coinName string
20 | callUrl string
21 | assSite string
22 | decimal int
23 | Stable int
24 | rpcUser string
25 | rpcPassword string
26 | account string
27 | isMining bool
28 | }
29 |
30 | var __btc *btc
31 |
32 | func (r *rpc) BTC() *btc {
33 | if __btc == nil {
34 | __btc = new(btc)
35 | __btc.Once = sync.Once {}
36 | __btc.Once.Do(func() {
37 | __btc.create()
38 | })
39 | }
40 | return __btc
41 | }
42 |
43 | func (rpc *btc) create() {
44 | setting := utils.GetConfig().GetCoinSettings()
45 | rpc.coinName = setting.Name
46 | rpc.callUrl = setting.Url
47 | rpc.assSite = setting.AssistSite
48 | rpc.decimal = setting.Decimal
49 | rpc.Stable = setting.Stable
50 | rpc.rpcUser = setting.RPCUser
51 | rpc.rpcPassword = setting.RPCPassword
52 | rpc.account = setting.Deposit
53 | rpc.isMining = false
54 | }
55 |
56 | type BtcResp struct {
57 | Error interface {} `json:"error"`
58 | Id string `json:"id"`
59 | Result interface{} `json:"result"`
60 | }
61 |
62 | func (rpc *btc) sendRequest(method string, params []interface {}) (BtcResp, error) {
63 | id := fmt.Sprintf("%d", rand.Intn(1000))
64 | reqBody := RequestBody { method, params, id }
65 | reqStr, err := json.Marshal(reqBody)
66 | if err != nil {
67 | panic(utils.LogIdxEx(utils.ERROR, 22, err))
68 | }
69 | utils.LogMsgEx(utils.DEBUG, fmt.Sprintf("Request body: %s", reqStr), nil)
70 |
71 | reqBuf := bytes.NewBuffer([]byte(reqStr))
72 | var req *http.Request
73 | if req, err = http.NewRequest(http.MethodPost, rpc.callUrl, reqBuf); err != nil {
74 | panic(utils.LogIdxEx(utils.ERROR, 43, err))
75 | }
76 | req.Header.Add("Content-Type", "application/json")
77 | req.SetBasicAuth(rpc.rpcUser, rpc.rpcPassword)
78 | client := &http.Client {}
79 | res, err := client.Do(req)
80 | if err != nil {
81 | panic(utils.LogIdxEx(utils.ERROR, 24, err))
82 | }
83 | defer res.Body.Close()
84 |
85 | bodyStr, err := ioutil.ReadAll(res.Body)
86 | if err != nil {
87 | panic(utils.LogIdxEx(utils.ERROR, 25, err))
88 | }
89 | utils.LogMsgEx(utils.DEBUG, fmt.Sprintf("Response body: %s", bodyStr), nil)
90 |
91 | var resBody BtcResp
92 | if err = json.Unmarshal(bodyStr, &resBody); err != nil {
93 | panic(utils.LogIdxEx(utils.ERROR, 23, err))
94 | }
95 | if resBody.Result == nil {
96 | err = utils.LogIdxEx(utils.DEBUG, 40, nil)
97 | }
98 | if resBody.Error != nil {
99 | tmp := resBody.Error.(map[string]interface {})
100 | err = utils.LogIdxEx(utils.ERROR, 26, tmp["message"])
101 | }
102 | return resBody, err
103 | }
104 | func (rpc *btc) GetTransactions(height uint) ([]entities.Transaction, error) {
105 | var resp BtcResp
106 | var err error
107 | // 根据块高获取块哈希
108 | if resp, err = rpc.sendRequest("getblockhash", []interface {} { height }); err != nil {
109 | return []entities.Transaction {}, utils.LogIdxEx(utils.ERROR, 26, err)
110 | }
111 | blockHash := resp.Result.(string)
112 |
113 | // 根据块哈希获取块
114 | if resp, err = rpc.sendRequest("getblock", []interface {} { blockHash }); err != nil {
115 | return []entities.Transaction {}, utils.LogIdxEx(utils.ERROR, 26, err)
116 | }
117 | result := resp.Result.(map[string]interface {})
118 |
119 | var txs []entities.Transaction
120 | for _, txHash := range result["tx"].([]interface {}) {
121 | var txsTmp []entities.Transaction
122 | if txsTmp, err = rpc.GetTransaction(txHash.(string)); err != nil {
123 | utils.LogMsgEx(utils.ERROR, "找不到指定交易,交易HASH:%s,错误:%v", txHash, err)
124 | continue
125 | }
126 | txs = append(txs, txsTmp...)
127 | }
128 | return txs, nil
129 | }
130 | func (rpc *btc) GetCurrentHeight() (uint64, error) {
131 | var err error
132 | var resp BtcResp
133 | if resp, err = rpc.sendRequest("getblockcount", []interface {} {}); err != nil {
134 | utils.LogIdxEx(utils.ERROR, 26, err)
135 | return 0, err
136 | }
137 | return uint64(resp.Result.(float64)), nil
138 | }
139 | func (rpc *btc) GetDepositAmount() (map[string]float64, error) {
140 | if balance, err := rpc.GetBalance(rpc.account); err != nil {
141 | return map[string]float64 {}, err
142 | } else {
143 | return map[string]float64 { rpc.account: balance }, nil
144 | }
145 | }
146 | func (rpc *btc) GetBalance(address string) (float64, error) {
147 | var err error
148 | var resp BtcResp
149 | if resp, err = rpc.sendRequest("getbalance", []interface {} { address }); err != nil {
150 | return -1, utils.LogIdxEx(utils.ERROR, 26, err)
151 | }
152 | return resp.Result.(float64) * math.Pow10(-rpc.decimal), nil
153 | }
154 | func (rpc *btc) SendTransaction(from string, to string, amount float64, password string) (string, error) {
155 | params := []interface {} { from, to }
156 | params = append(params, math.Floor(amount * math.Pow10(rpc.decimal)))
157 | if resp, err := rpc.sendRequest("sendfrom", params); err != nil {
158 | return "", utils.LogIdxEx(utils.ERROR, 35, err)
159 | } else {
160 | return resp.Result.(string), nil
161 | }
162 | }
163 | func (rpc *btc) SendFrom(from string, amount float64) (string, error) {
164 | coinSet := utils.GetConfig().GetCoinSettings()
165 | return rpc.SendTransaction(rpc.account, coinSet.Collect, amount, rpc.account)
166 | }
167 | func (rpc *btc) SendTo(to string, amount float64) (string, error) {
168 | coinSet := utils.GetConfig().GetCoinSettings()
169 | return rpc.SendTransaction(coinSet.Withdraw, to, amount, coinSet.TradePassword)
170 | }
171 | func (rpc *btc) GetNewAddress() (string, error) {
172 | var resp BtcResp
173 | var err error
174 | if resp, err = rpc.sendRequest("getnewaddress", []interface {} { rpc.account }); err != nil {
175 | return "", utils.LogIdxEx(utils.ERROR, 36, err)
176 | }
177 | return resp.Result.(string), nil
178 | }
179 | func (rpc *btc) ValidAddress(address string) (bool, error) {
180 | if resp, err := rpc.sendRequest("validateaddress", []interface {} { address }); err != nil {
181 | return false, utils.LogMsgEx(utils.ERROR, "地址验证错误:%v", err)
182 | } else {
183 | result := utils.JsonObject {
184 | Data: resp.Result.(map[string]interface {}),
185 | }
186 | if !result.Contain("isvalid") {
187 | return false, nil
188 | }
189 | if tmp, err := result.Get("isvalid"); err != nil {
190 | return false, err
191 | } else {
192 | return tmp.(bool), nil
193 | }
194 | }
195 | return false, nil
196 | }
197 | func (rpc *btc) GetTransaction(txHash string) ([]entities.Transaction, error) {
198 | var err error
199 | var resp BtcResp
200 | if resp, err = rpc.sendRequest("getrawtransaction", []interface {} { txHash, 1 }); err != nil {
201 | return []entities.Transaction {}, utils.LogIdxEx(utils.ERROR, 37, err)
202 | }
203 | result := utils.JsonObject {
204 | Data: resp.Result.(map[string]interface {}),
205 | }
206 | blockTime, _ := result.Get("blocktime")
207 | blockHash, _ := result.Get("blockhash")
208 |
209 | var txs []entities.Transaction
210 | if !result.Contain("vout") {
211 | return nil, utils.LogMsgEx(utils.ERROR, "交易不含vout分量")
212 | }
213 | tmp, _ := result.Get("vout")
214 | for _, vout := range tmp.([]interface {}) {
215 | vos := utils.JsonObject { vout.(map[string]interface {}) }
216 | if tmp, err = vos.Get("scriptPubKey.type"); err != nil || tmp.(string) == "nulldata" {
217 | continue
218 | }
219 | if tmp, err = vos.Get("scriptPubKey.addresses"); err != nil {
220 | continue
221 | }
222 | var tx entities.Transaction
223 | tx.TxHash = txHash
224 | tx.CreateTime = time.Unix(int64(blockTime.(float64)), 0)
225 | if !vos.Contain("value") {
226 | continue
227 | }
228 | tmp, _ = vos.Get("value")
229 | tx.Amount = tmp.(float64) * math.Pow10(-rpc.decimal)
230 | tmp, _ = vos.Get("scriptPubKey.addresses")
231 | tx.To = tmp.([]interface {})[0].(string)
232 | //tx.From
233 | tx.BlockHash = blockHash.(string)
234 | if !vos.Contain("n") {
235 | continue
236 | }
237 | tmp, _ = vos.Get("n")
238 | tx.TxIndex = int(tmp.(float64))
239 | tx.Height, err = rpc.GetTxExistsHeight(txHash)
240 | if err != nil {
241 | continue
242 | }
243 | tx.Asset = rpc.coinName
244 | txs = append(txs, tx)
245 | }
246 | return txs, nil
247 | }
248 | func (rpc *btc) GetTxExistsHeight(txHash string) (uint64, error) {
249 | var err error
250 | var resp BtcResp
251 | if resp, err = rpc.sendRequest("gettransaction", []interface {} { txHash }); err != nil {
252 | return 0, utils.LogIdxEx(utils.ERROR, 37, err)
253 | }
254 | result := utils.JsonObject {
255 | Data: resp.Result.(map[string]interface {}),
256 | }
257 | if !result.Contain("blockindex") {
258 | return 0, utils.LogMsgEx(utils.ERROR, "指定交易:%s中不存在块索引", txHash)
259 | }
260 | tmp, _ := result.Get("blockindex")
261 | return uint64(tmp.(float64)) + 1, nil
262 | }
263 | func (rpc *btc) EnableMining(enable bool, speed int) (bool, error) {
264 | //colAddr := utils.GetConfig().GetCoinSettings().Collect
265 | if enable && !rpc.isMining {
266 | go func() {
267 | for rpc.isMining {
268 | rpc.sendRequest("generate", []interface {} { speed })
269 |
270 | time.Sleep(1 * time.Second)
271 | }
272 | }()
273 | }
274 | rpc.isMining = enable
275 | return true, nil
276 | }
277 | func (rpc *btc) IsMining() bool {
278 | return rpc.isMining
279 | }
--------------------------------------------------------------------------------
/src/rpcs/common.go:
--------------------------------------------------------------------------------
1 | package rpcs
2 |
3 | import (
4 | "entities"
5 | "reflect"
6 | )
7 |
8 | type Rpc interface {
9 | GetTransactions(height uint) ([]entities.Transaction, error)
10 | GetCurrentHeight() (uint64, error)
11 | GetDepositAmount() (map[string]float64, error)
12 | GetBalance(address string) (float64, error)
13 | SendTransaction(from string, to string, amount float64, password string) (string, error)
14 | SendFrom(from string, amount float64) (string, error)
15 | SendTo(to string, amount float64) (string, error)
16 | GetNewAddress() (string, error)
17 | ValidAddress(address string) (bool, error)
18 | GetTransaction(txHash string) ([]entities.Transaction, error)
19 | GetTxExistsHeight(txHash string) (uint64, error)
20 | EnableMining(enable bool, speed int) (bool, error)
21 | IsMining() bool
22 | }
23 |
24 | type rpc struct {
25 |
26 | }
27 |
28 | var __rpc = new(rpc)
29 |
30 | func GetRPC(name string) Rpc {
31 | return reflect.ValueOf(__rpc).MethodByName(name).Call(nil)[0].Interface().(Rpc)
32 | }
33 |
34 | type RequestBody struct {
35 | Method string `json:"method"`
36 | Params []interface{} `json:"params"`
37 | Id string `json:"id"`
38 | }
39 |
--------------------------------------------------------------------------------
/src/rpcs/eth.go:
--------------------------------------------------------------------------------
1 | package rpcs
2 |
3 | import (
4 | "sync"
5 | "utils"
6 | "entities"
7 | "encoding/json"
8 | "bytes"
9 | "net/http"
10 | "io/ioutil"
11 | "fmt"
12 | "strconv"
13 | "math/rand"
14 | "time"
15 | "math/big"
16 | "math"
17 | "dao"
18 | )
19 |
20 | type eth struct {
21 | sync.Once
22 | coinName string
23 | callUrl string
24 | decimal int
25 | Stable int
26 | isMining bool
27 | }
28 |
29 | var __eth *eth
30 |
31 | func (r *rpc) ETH() *eth {
32 | if __eth == nil {
33 | __eth = new(eth)
34 | __eth.Once = sync.Once {}
35 | __eth.Once.Do(func() {
36 | __eth.create()
37 | })
38 | }
39 | return __eth
40 | }
41 |
42 | func (rpc *eth) create() {
43 | setting := utils.GetConfig().GetCoinSettings()
44 | rpc.coinName = setting.Name
45 | rpc.callUrl = setting.Url
46 | rpc.decimal = setting.Decimal
47 | rpc.Stable = setting.Stable
48 | rpc.isMining = false
49 | }
50 |
51 | type EthSucceedResp struct {
52 | JsonRpc string `json:"jsonrpc"`
53 | Id string `json:"id"`
54 | Result interface{} `json:"result"`
55 | }
56 |
57 | type EthFailedResp struct {
58 | JsonRpc string `json:"jsonrpc"`
59 | Id string `json:"id"`
60 | Error struct {
61 | Code int `json:"code"`
62 | Message string `json:"message"`
63 | } `json:"error"`
64 | }
65 |
66 | type EstimateGasBody struct {
67 | From string `json:"from"`
68 | To string `json:"to"`
69 | Value string `json:"value"`
70 | }
71 |
72 | type TransactionBody struct {
73 | EstimateGasBody
74 | Gas string `json:"gas"`
75 | }
76 |
77 | func (rpc *eth) strProp(tx map[string]interface{}, key string) (string, error) {
78 | var itfc interface {}
79 | var ok bool
80 | if itfc, ok = tx[key]; !ok {
81 | return "", utils.LogMsgEx(utils.ERROR, "交易未包含所需字段:%s", key)
82 | }
83 | if itfc == nil {
84 | return "", nil// utils.LogMsgEx(utils.WARNING, "字段:%s为nil", key)
85 | }
86 | return itfc.(string), nil
87 | }
88 | func (rpc *eth) numProp(tx map[string]interface{}, key string) (*big.Float, error) {
89 | var err error
90 | var numTmp = big.NewFloat(0)
91 | var strTmp string
92 | if strTmp, err = rpc.strProp(tx, key); err != nil {
93 | return numTmp, err
94 | }
95 | if len(strTmp) == 0 {
96 | return numTmp, nil
97 | }
98 | if strTmp[:2] == "0x" {
99 | strTmp = strTmp[2:]
100 | }
101 | if numTmp, _, err = numTmp.Parse(strTmp, 16); err != nil {
102 | return numTmp, utils.LogIdxEx(utils.ERROR, 29, strTmp)
103 | }
104 | return numTmp, nil
105 | }
106 | func (rpc *eth) sendRequest(method string, params []interface {}) (EthSucceedResp, error) {
107 | id := fmt.Sprintf("%d", rand.Intn(1000))
108 | reqBody := RequestBody { method, params, id }
109 | reqStr, err := json.Marshal(reqBody)
110 | if err != nil {
111 | panic(utils.LogIdxEx(utils.ERROR, 22, err))
112 | }
113 | utils.LogMsgEx(utils.DEBUG, fmt.Sprintf("Request body: %s", reqStr), nil)
114 |
115 | reqBuf := bytes.NewBuffer([]byte(reqStr))
116 | res, err := http.Post(rpc.callUrl, "application/json", reqBuf)
117 | if err != nil {
118 | panic(utils.LogIdxEx(utils.ERROR, 24, err))
119 | }
120 | defer res.Body.Close()
121 |
122 | bodyStr, err := ioutil.ReadAll(res.Body)
123 | if err != nil {
124 | panic(utils.LogIdxEx(utils.ERROR, 25, err))
125 | }
126 | utils.LogMsgEx(utils.DEBUG, fmt.Sprintf("Response body: %s", bodyStr), nil)
127 |
128 | testBody := make(map[string]interface {})
129 | if err = json.Unmarshal(bodyStr, &testBody); err != nil {
130 | panic(utils.LogIdxEx(utils.ERROR, 23, err))
131 | }
132 | var resBody EthSucceedResp
133 | if _, ok := testBody["error"]; ok {
134 | var resError EthFailedResp
135 | if err = json.Unmarshal(bodyStr, &resError); err != nil {
136 | panic(utils.LogIdxEx(utils.ERROR, 23, err))
137 | } else {
138 | return resBody, utils.LogIdxEx(utils.ERROR, 26, resError.Error.Message)
139 | }
140 | } else {
141 | if err = json.Unmarshal(bodyStr, &resBody); err != nil {
142 | panic(utils.LogIdxEx(utils.ERROR, 23, err))
143 | }
144 | }
145 | if resBody.Result == nil {
146 | err = utils.LogIdxEx(utils.DEBUG, 40, nil)
147 | }
148 | return resBody, err
149 | }
150 | func (rpc *eth) GetTransactions(height uint) ([]entities.Transaction, error) {
151 | var err error
152 | // 发送请求获取指定高度的块
153 | var resp EthSucceedResp
154 | rand.Seed(time.Now().Unix())
155 | params := []interface{} { "0x" + strconv.FormatUint(uint64(height), 16), true }
156 | if resp, err = rpc.sendRequest("eth_getBlockByNumber", params); err != nil {
157 | return nil, utils.LogIdxEx(utils.ERROR, 26, err)
158 | }
159 |
160 | // 解析返回数据,提取交易
161 | if resp.Result == nil {
162 | return []entities.Transaction {}, utils.LogMsgEx(utils.DEBUG, "找不到指定块高的块:%d", height)
163 | }
164 | respData := resp.Result.(map[string]interface {})
165 | var txsObj interface {}
166 | var ok bool
167 | if txsObj, ok = respData["transactions"]; !ok {
168 | return nil, utils.LogIdxEx(utils.WARNING, 0001, nil)
169 | }
170 |
171 | // 扫描所有交易
172 | txs := txsObj.([]interface {})
173 | transactions := []entities.Transaction {}
174 | for i, tx := range txs {
175 | rawTx := tx.(map[string]interface {})
176 | rpcTx := entities.Transaction{}
177 | // 交易地址
178 | if rpcTx.From, err = rpc.strProp(rawTx, "from"); err != nil {
179 | rpcTx.From = ""
180 | }
181 | if rpcTx.To, err = rpc.strProp(rawTx, "to"); err != nil {
182 | rpcTx.To = ""
183 | }
184 | rpcTx.Asset = rpc.coinName
185 | rpcTx.TxIndex = i
186 | var heightBig *big.Float
187 | if heightBig, err = rpc.numProp(rawTx, "blockNumber"); err != nil {
188 | continue
189 | }
190 | rpcTx.Height, _ = heightBig.Uint64()
191 | var timestampBig *big.Float
192 | if timestampBig, err = rpc.numProp(respData, "timestamp"); err != nil {
193 | continue
194 | }
195 | timeInt64, _ := timestampBig.Int64()
196 | rpcTx.CreateTime = time.Unix(timeInt64, 0)
197 | var valueBig *big.Float
198 | if valueBig, err = rpc.numProp(rawTx, "value"); err != nil {
199 | continue
200 | }
201 | rpcTx.Amount, _ = valueBig.Mul(valueBig, big.NewFloat(math.Pow10(-rpc.decimal))).Float64()
202 | if rpcTx.TxHash, err = rpc.strProp(rawTx, "hash"); err != nil {
203 | continue
204 | }
205 |
206 | transactions = append(transactions, rpcTx)
207 | }
208 | return transactions, nil
209 | }
210 | func (rpc *eth) GetCurrentHeight() (uint64, error) {
211 | var err error
212 | var resp EthSucceedResp
213 | rand.Seed(time.Now().Unix())
214 | if resp, err = rpc.sendRequest("eth_blockNumber", []interface{} {}); err != nil {
215 | return 0, utils.LogIdxEx(utils.ERROR, 26, err)
216 | }
217 | strHeight := resp.Result.(string)
218 | if strHeight[:2] == "0x" {
219 | strHeight = strHeight[2:]
220 | }
221 | return strconv.ParseUint(strHeight, 16, 64)
222 | }
223 | func (rpc *eth) GetDepositAmount() (map[string]float64, error) {
224 | var err error
225 | addrAmount := make(map[string]float64)
226 | coinCfg := utils.GetConfig().GetCoinSettings()
227 |
228 | var addresses []string
229 | addrDAO := dao.GetAddressDAO()
230 | if addresses, err = addrDAO.FindInuseByAsset(coinCfg.Name); err != nil {
231 | return addrAmount, utils.LogMsgEx(utils.ERROR, "获取充值地址失败:%v", err)
232 | }
233 |
234 | for _, addr := range addresses {
235 | var balance float64
236 | if balance, err = rpc.GetBalance(addr); err != nil {
237 | utils.LogMsgEx(utils.WARNING, "从%s获取余额失败", addr)
238 | continue
239 | }
240 |
241 | if balance < coinCfg.MinCollect {
242 | continue
243 | }
244 |
245 | addrAmount[addr] = balance
246 | }
247 | return addrAmount, nil
248 | }
249 | func (rpc *eth) SendTransaction(from string, to string, amount float64, password string) (string, error) {
250 | var err error
251 | coinSet := utils.GetConfig().GetCoinSettings()
252 |
253 | // 处理转账金额
254 | amountBig := big.NewFloat(amount)
255 | decimal := math.Pow10(rpc.decimal)
256 | amountBig.Mul(amountBig, big.NewFloat(decimal))
257 | amountFin := big.NewInt(0)
258 | amountBig.Int(amountFin)
259 | cvtAmount := fmt.Sprintf("0x%x", amountFin)
260 |
261 | // 计算手续费数量
262 | var paramEstimateGas EstimateGasBody
263 | paramEstimateGas.From = from
264 | paramEstimateGas.To = to
265 | paramEstimateGas.Value = cvtAmount
266 | var resp EthSucceedResp
267 | if resp, err = rpc.sendRequest("eth_estimateGas", []interface {} {
268 | paramEstimateGas,
269 | }); err != nil {
270 | return "", utils.LogIdxEx(utils.ERROR, 32, err)
271 | }
272 | gasNum := resp.Result
273 |
274 | // 解锁用户账户
275 | if resp, err = rpc.sendRequest("personal_unlockAccount", []interface {} {
276 | from, password, coinSet.UnlockDuration,
277 | }); err != nil {
278 | return "", utils.LogIdxEx(utils.ERROR, 33, err)
279 | }
280 |
281 | // 发送转账请求
282 | var paramTransaction TransactionBody
283 | paramTransaction.From = from
284 | paramTransaction.To = to
285 | paramTransaction.Value = cvtAmount
286 | paramTransaction.Gas = gasNum.(string)
287 | if resp, err = rpc.sendRequest("eth_sendTransaction", []interface {} {
288 | paramTransaction,
289 | }); err != nil {
290 | return "", utils.LogIdxEx(utils.ERROR, 35, err)
291 | }
292 | if resp.Result == nil {
293 | return "", nil
294 | } else {
295 | return resp.Result.(string), nil
296 | }
297 | }
298 | func (rpc *eth) SendFrom(from string, amount float64) (string, error) {
299 | coinSet := utils.GetConfig().GetCoinSettings()
300 | return rpc.SendTransaction(from, coinSet.Collect, amount, coinSet.TradePassword)
301 | }
302 | func (rpc *eth) SendTo(to string, amount float64) (string, error) {
303 | coinSet := utils.GetConfig().GetCoinSettings()
304 | return rpc.SendTransaction(coinSet.Withdraw, to, amount, coinSet.TradePassword)
305 | }
306 | func (rpc *eth) GetBalance(address string) (float64, error) {
307 | var err error
308 | var resp EthSucceedResp
309 | params := []interface {} { address, "latest" }
310 | if resp, err = rpc.sendRequest("eth_getBalance", params); err != nil {
311 | return -1, utils.LogIdxEx(utils.ERROR, 31, err)
312 | }
313 | if resp.Result == nil {
314 | return -1, nil
315 | }
316 |
317 | result := resp.Result.(string)
318 | if result[0:2] == "0x" {
319 | result = result[2:]
320 | }
321 | balanceBig := big.NewFloat(0)
322 | if balanceBig, _, err = balanceBig.Parse(result, 16); err != nil {
323 | return -1, utils.LogIdxEx(utils.ERROR, 41, err)
324 | }
325 | ret, _ := balanceBig.Mul(balanceBig, big.NewFloat(math.Pow10(-rpc.decimal))).Float64()
326 | return ret, nil
327 | }
328 | func (rpc *eth) GetNewAddress() (string, error) {
329 | var err error
330 | var resp EthSucceedResp
331 | params := []interface {} { utils.GetConfig().GetCoinSettings().TradePassword }
332 | if resp, err = rpc.sendRequest("personal_newAccount", params); err != nil {
333 | return "", utils.LogIdxEx(utils.ERROR, 36, err)
334 | }
335 | return resp.Result.(string), nil
336 | }
337 | func (rpc *eth) ValidAddress(address string) (bool, error) {
338 | if balance, err := rpc.GetBalance(address); err != nil || balance == -1 {
339 | return false, err
340 | } else {
341 | return true, nil
342 | }
343 | }
344 | func (rpc *eth) GetTransaction(txHash string) ([]entities.Transaction, error) {
345 | var err error
346 | var resp EthSucceedResp
347 | var tx entities.Transaction
348 | if resp, err = rpc.sendRequest("eth_getTransactionByHash", []interface {} {
349 | txHash,
350 | }); err != nil {
351 | return []entities.Transaction {}, utils.LogIdxEx(utils.ERROR, 37, err)
352 | }
353 | result := resp.Result.(map[string]interface {})
354 |
355 | var numTmp *big.Float
356 | tx.TxHash = txHash
357 | tx.Asset = "ETH"
358 | if numTmp, err = rpc.numProp(result, "blockNumber"); err != nil {
359 | tx.Height = 0
360 | } else {
361 | tx.Height, _ = numTmp.Uint64()
362 | }
363 | if numTmp, err = rpc.numProp(result, "transactionIndex"); err != nil {
364 | tx.TxIndex = -1
365 | } else {
366 | txIdxInt64, _ := numTmp.Int64()
367 | tx.TxIndex = int(txIdxInt64)
368 | }
369 | tx.From = result["from"].(string)
370 | tx.To = result["to"].(string)
371 | tx.BlockHash = result["blockHash"].(string)
372 | if numTmp, err = rpc.numProp(result, "value"); err != nil {
373 | return []entities.Transaction {}, utils.LogIdxEx(utils.ERROR, 41, err)
374 | }
375 | tx.Amount, _ = numTmp.Mul(numTmp, big.NewFloat(math.Pow10(-rpc.decimal))).Float64()
376 | return []entities.Transaction { tx }, nil
377 | }
378 | func (rpc *eth) GetTxExistsHeight(txHash string) (uint64, error) {
379 | var err error
380 | var resp EthSucceedResp
381 | if resp, err = rpc.sendRequest("eth_getTransactionByHash", []interface {} {
382 | txHash,
383 | }); err != nil {
384 | return 0, utils.LogIdxEx(utils.ERROR, 37, err)
385 | }
386 | result := resp.Result.(map[string]interface {})
387 |
388 | var numTmp *big.Float
389 | var height uint64
390 | if numTmp, err = rpc.numProp(result, "blockNumber"); err != nil {
391 | return 0, utils.LogIdxEx(utils.ERROR, 37, err)
392 | } else {
393 | height, _ = numTmp.Uint64()
394 | return height, nil
395 | }
396 | }
397 | func (rpc *eth) EnableMining(enable bool, speed int) (bool, error) {
398 | rpc.isMining = enable
399 | method := "miner_start"
400 | if !rpc.isMining {
401 | method = "miner_stop"
402 | }
403 | if _, err := rpc.sendRequest(method, []interface {} { speed }); err != nil {
404 | return false, utils.LogMsgEx(utils.ERROR, "调整挖矿状态失败:%v", err)
405 | }
406 | return true, nil
407 | }
408 | func (rpc *eth) IsMining() bool {
409 | return rpc.isMining
410 | }
--------------------------------------------------------------------------------
/src/services/collectService.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "sync"
5 | "utils"
6 | "rpcs"
7 | "dao"
8 | "time"
9 | )
10 |
11 | type collectService struct {
12 | BaseService
13 | sync.Once
14 | addresses []string
15 | }
16 |
17 | var _collectService *collectService
18 |
19 | func GetCollectService() *collectService {
20 | if _collectService == nil {
21 | _collectService = new(collectService)
22 | _collectService.Once = sync.Once {}
23 | _collectService.Once.Do(func() {
24 | _collectService.create()
25 | })
26 | }
27 | return _collectService
28 | }
29 |
30 | func (service *collectService) create() error {
31 | service.name = "collectService"
32 | service.status.RegAsObs(service)
33 | return service.BaseService.create()
34 | }
35 |
36 | func (service *collectService) BeforeTurn(s *utils.Status, tgtStt int) {
37 | switch tgtStt {
38 | case INIT:
39 | utils.LogMsgEx(utils.INFO, "initialization", nil)
40 | case START:
41 | utils.LogMsgEx(utils.INFO, "start", nil)
42 | }
43 | }
44 |
45 | func (service *collectService) AfterTurn(s *utils.Status, srcStt int) {
46 | switch s.Current() {
47 | case INIT:
48 | utils.LogMsgEx(utils.INFO, "initialized", nil)
49 | case START:
50 | // 启动归集协程
51 | go service.doCollect()
52 | utils.LogMsgEx(utils.INFO, "started", nil)
53 | }
54 | }
55 |
56 | func (service *collectService) doCollect() {
57 | var err error
58 | coinSet := utils.GetConfig().GetCoinSettings()
59 | rpc := rpcs.GetRPC(coinSet.Name)
60 | for err == nil && service.status.Current() == START {
61 | utils.LogMsgEx(utils.INFO, "开始一轮归集", nil)
62 |
63 | // 获取所有有余额需要归集的地址/账户
64 | addrAmount := make(map[string]float64)
65 | if addrAmount, err = rpc.GetDepositAmount(); err != nil {
66 | utils.LogMsgEx(utils.ERROR, "获取充值地址上余额时,发生错误:%v", err)
67 | continue
68 | }
69 | utils.LogMsgEx(utils.INFO, "发现%d个地址/账户需要归集", len(addrAmount))
70 |
71 | for addr, balance := range addrAmount {
72 | // 发起转账请求
73 | var txHash string
74 | if txHash, err = rpc.SendFrom(addr, balance); err != nil {
75 | utils.LogMsgEx(utils.ERROR, "发送归集请求失败:%v", err)
76 | err = nil
77 | continue
78 | }
79 | utils.LogMsgEx(utils.INFO, "地址/账户%s下的余额已被归集", addr)
80 | utils.LogMsgEx(utils.INFO, "归集的交易ID为:%s", txHash)
81 |
82 | // 把归集的记录保存进数据库
83 | if _, err = dao.GetCollectDAO().AddSentCollect(txHash, coinSet.Name, addr, balance); err != nil {
84 | utils.LogMsgEx(utils.ERROR, "添加发送的归集记录失败:%v", err)
85 | continue
86 | }
87 | utils.LogMsgEx(utils.INFO, "%s归集记录已持久化到数据库", txHash)
88 | }
89 |
90 | // 定时
91 | time.Sleep(coinSet.CollectInterval * time.Second)
92 | }
93 |
94 | service.status.TurnTo(DESTORY)
95 | }
--------------------------------------------------------------------------------
/src/services/common.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "dao"
5 | "entities"
6 | "utils"
7 | "unsafe"
8 | "rpcs"
9 | "fmt"
10 | )
11 |
12 | const (
13 | DESTORY = iota
14 | STOP
15 | NONE
16 | CREATE
17 | INIT
18 | START
19 | )
20 |
21 | var ServiceStatus = []string {
22 | "DESTORY", "STOP", "NONE", "CREATE", "INIT", "START",
23 | }
24 |
25 | type BaseService struct {
26 | status utils.Status
27 | name string
28 | }
29 |
30 | func (service *BaseService) create() error {
31 | service.status.Init([]int { DESTORY, CREATE, INIT, START, STOP })
32 | return nil
33 | }
34 |
35 | func (service *BaseService) Init() {
36 | service.status.TurnTo(INIT)
37 | }
38 |
39 | func (service *BaseService) Start() {
40 | service.status.TurnTo(START)
41 | }
42 |
43 | func (service *BaseService) Stop() {
44 | if service.status.Current() == START {
45 | service.status.TurnTo(STOP)
46 | } else {
47 | service.status.TurnTo(DESTORY)
48 | }
49 | }
50 |
51 | func (service *BaseService) IsInit() bool {
52 | return service.status.Current() >= INIT
53 | }
54 |
55 | func (service *BaseService) IsDestroy() bool {
56 | return service.status.Current() == DESTORY
57 | }
58 |
59 | func (service *BaseService) CurrentStatus() int {
60 | return service.status.Current()
61 | }
62 |
63 | func (service *BaseService) Name() string {
64 | return service.name
65 | }
66 |
67 | func GetInitedServices() []*BaseService {
68 | var svcs []*BaseService
69 | if GetWithdrawService().IsInit() {
70 | svcs = append(svcs, (*BaseService)(unsafe.Pointer(GetWithdrawService())))
71 | }
72 | if GetCollectService().IsInit() {
73 | svcs = append(svcs, (*BaseService)(unsafe.Pointer(GetCollectService())))
74 | }
75 | if GetStableService().IsInit() {
76 | svcs = append(svcs, (*BaseService)(unsafe.Pointer(GetStableService())))
77 | }
78 | if GetDepositService().IsInit() {
79 | svcs = append(svcs, (*BaseService)(unsafe.Pointer(GetDepositService())))
80 | }
81 | return svcs
82 | }
83 |
84 | func TxIntoStable(txHash string, asset string) error {
85 | var err error
86 | if _, err = dao.GetDepositDAO().DepositIntoStable(txHash); err != nil {
87 | return utils.LogMsgEx(utils.ERROR, "更新充币记录失败:%v", err)
88 | }
89 | if _, err = dao.GetWithdrawDAO().WithdrawIntoStable(asset, txHash); err != nil {
90 | return utils.LogMsgEx(utils.ERROR, "更新充币记录失败:%v", err)
91 | }
92 |
93 | var process entities.DatabaseProcess
94 | process.Asset = asset
95 | process.TxHash = txHash
96 | process.Process = entities.FINISH
97 | if _, err = dao.GetProcessDAO().SaveProcess(&process); err != nil {
98 | return utils.LogMsgEx(utils.ERROR, "插入/更新进度表失败:%v", err)
99 | }
100 |
101 | // 查询process获得id和type
102 | if process, err = dao.GetProcessDAO().QueryProcessByTxHash(asset, txHash); err != nil {
103 | return utils.LogMsgEx(utils.ERROR, "查询进度表失败:%v", err)
104 | }
105 |
106 | // 添加交易进数据库
107 | var txs []entities.Transaction
108 | if txs, err = rpcs.GetRPC(asset).GetTransaction(txHash); err != nil {
109 | return utils.LogMsgEx(utils.ERROR, "从链查询交易失败:%v", err)
110 | }
111 | for _, tx := range txs {
112 | if _, err = dao.GetTransactionDAO().AddTransaction(tx,
113 | fmt.Sprintf("%s_%d", process.Type, process.Id),
114 | ); err != nil {
115 | return utils.LogMsgEx(utils.ERROR, "插入交易表失败:%v", err)
116 | }
117 | }
118 |
119 | utils.LogMsgEx(utils.INFO, "交易完成:%s", txHash)
120 | return nil
121 | }
122 |
123 | var toNotifySig = make(chan entities.Transaction)
124 | var RevWithdrawSig = make(chan entities.BaseWithdraw)
--------------------------------------------------------------------------------
/src/services/depositService.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "sync"
5 | "utils"
6 | "dao"
7 | "rpcs"
8 | "entities"
9 | "time"
10 | "strconv"
11 | )
12 |
13 | /***
14 | 充值服务:启动子协程扫描链,过滤充值交易,并提交到notify服务等待其稳定
15 | 子协程(startScanChain):扫描区块,找到充值交易
16 | */
17 | type depositService struct {
18 | BaseService
19 | sync.Once
20 | addresses []string
21 | height uint64
22 | }
23 |
24 | var _depositService *depositService
25 |
26 | func GetDepositService() *depositService {
27 | if _depositService == nil {
28 | _depositService = new(depositService)
29 | _depositService.Once = sync.Once {}
30 | _depositService.Once.Do(func() {
31 | _depositService.create()
32 | })
33 | }
34 | return _depositService
35 | }
36 |
37 | func (service *depositService) create() error {
38 | service.name = "depositService"
39 | service.status.RegAsObs(service)
40 | return service.BaseService.create()
41 | }
42 |
43 | func (service *depositService) BeforeTurn(s *utils.Status, tgtStt int) {
44 | var err error
45 | switch tgtStt {
46 | case INIT:
47 | utils.LogMsgEx(utils.INFO, "initialization", nil)
48 | // 加载所有内部地址
49 | if err = service.loadAddresses(); err != nil {
50 | panic(utils.LogMsgEx(utils.ERROR, "加载地址失败:%v", err))
51 | }
52 | // 获取上一次扫描的块高
53 | if err = service.getCurrentHeight(); err != nil {
54 | panic(utils.LogMsgEx(utils.ERROR, "获取当前块高失败:%v", err))
55 | }
56 | case START:
57 | utils.LogMsgEx(utils.INFO, "start", nil)
58 | }
59 | }
60 |
61 | func (service *depositService) AfterTurn(s *utils.Status, srcStt int) {
62 | switch s.Current() {
63 | case INIT:
64 | utils.LogMsgEx(utils.INFO, "initialized", nil)
65 | case START:
66 | // 开启协程扫描区块链上的交易记录
67 | go service.startScanChain()
68 | utils.LogMsgEx(utils.INFO, "started", nil)
69 | }
70 | }
71 |
72 | func (service *depositService) loadAddresses() error {
73 | coinSetting := utils.GetConfig().GetCoinSettings()
74 | var err error
75 | service.addresses, err = dao.GetAddressDAO().FindInuseByAsset(coinSetting.Name)
76 | return err
77 | }
78 |
79 | func (service *depositService) getCurrentHeight() error {
80 | coinSetting := utils.GetConfig().GetCoinSettings()
81 | var err error
82 | var height int64
83 | if height, err = dao.GetHeightDAO().GetHeight(coinSetting.Name); err != nil {
84 | utils.LogMsgEx(utils.ERROR, "查询不到块高:%v", err)
85 | return nil
86 | }
87 | if height == 0 {
88 | if _, err = dao.GetHeightDAO().ChkOrAddAsset(coinSetting.Name); err != nil {
89 | return err
90 | }
91 | }
92 | service.height = uint64(height)
93 | return nil
94 | }
95 |
96 | func (service *depositService) startScanChain() {
97 | var err error
98 | coinSet := utils.GetConfig().GetCoinSettings()
99 | rpc := rpcs.GetRPC(coinSet.Name)
100 | acc := 0
101 | for err == nil && service.status.Current() == START {
102 | // 获取当前块高
103 | var curHeight uint64
104 | if curHeight, err = rpc.GetCurrentHeight(); err != nil {
105 | utils.LogMsgEx(utils.ERROR, "获取块高失败:%v", err)
106 | continue
107 | }
108 | if curHeight - service.height <= /*uint64(coinSet.Stable)*/0 {
109 | if acc % 500 == 0 {
110 | utils.LogMsgEx(utils.INFO, "已达到最高块高", nil)
111 | }
112 | acc++
113 | continue
114 | }
115 |
116 | utils.LogMsgEx(utils.INFO, "块高: %d", service.height)
117 |
118 | // 获取指定高度的交易
119 | var txs []entities.Transaction
120 | if txs, err = rpc.GetTransactions(uint(service.height)); err != nil {
121 | if _, e := strconv.ParseInt(err.Error(), 10, 64); e != nil {
122 | utils.LogMsgEx(utils.ERROR, "获取交易失败:%v", err)
123 | } else {
124 | err = nil
125 | }
126 | continue
127 | }
128 |
129 | for _, tx := range txs {
130 | // 如果充值地址不属于钱包,跳过
131 | if !utils.StrArrayContains(service.addresses, tx.To) {
132 | continue
133 | }
134 | // 如果充值金额为0,跳过
135 | if tx.Amount == 0 {
136 | continue
137 | }
138 |
139 | utils.LogMsgEx(utils.INFO, "发现交易:%v", tx)
140 |
141 | // 检测是否重复
142 | var exist bool
143 | if exist, err = dao.GetDepositDAO().CheckExists(tx.TxHash); err != nil {
144 | utils.LogMsgEx(utils.ERROR, "查找指定交易:%s失败:%v", tx.TxHash, err)
145 | continue
146 | }
147 | if exist {
148 | utils.LogMsgEx(utils.WARNING, "检测到重复交易:%s,跳过", tx.TxHash)
149 | continue
150 | }
151 |
152 | deposit := entities.TurnTxToDeposit(&tx)
153 |
154 | // 持久化到数据库
155 | if _, err = dao.GetDepositDAO().AddScannedDeposit(&deposit); err != nil {
156 | utils.LogMsgEx(utils.ERROR, "添加未稳定提币记录失败:%v", err)
157 | continue
158 | }
159 |
160 | // 获取交易id,并插入进度表
161 | var id int
162 | if id, err = dao.GetDepositDAO().GetDepositId(deposit.TxHash); err != nil {
163 | utils.LogMsgEx(utils.ERROR, "获取充值交易id失败:%v", err)
164 | continue
165 | }
166 | if _, err = dao.GetProcessDAO().SaveProcess(&entities.DatabaseProcess {
167 | BaseProcess: entities.BaseProcess {
168 | Id: id,
169 | TxHash: deposit.TxHash,
170 | Asset: deposit.Asset,
171 | Type: entities.DEPOSIT,
172 | Process: entities.INCHAIN,
173 | Cancelable: false,
174 | },
175 | Height: deposit.Height,
176 | CurrentHeight: curHeight,
177 | CompleteHeight: deposit.Height + uint64(coinSet.Stable),
178 | LastUpdateTime: time.Now(),
179 | }); err != nil {
180 | utils.LogMsgEx(utils.ERROR, "插入进度表失败:%v", err)
181 | continue
182 | }
183 |
184 | // 如果已经达到稳定块高,直接存入数据库
185 | if deposit.Height + uint64(coinSet.Stable) >= curHeight {
186 | utils.LogMsgEx(utils.INFO, "交易(%s)进入稳定状态", deposit.TxHash)
187 |
188 | if err = TxIntoStable(tx.TxHash, tx.Asset); err != nil {
189 | utils.LogMsgEx(utils.ERROR, "插入稳定交易记录失败:%v", err)
190 | continue
191 | }
192 | } else {
193 | // 未进入稳定状态,抛给通知等待服务
194 | toNotifySig <- tx
195 | utils.LogMsgEx(utils.INFO, "交易(%s)进入等待列队", deposit.TxHash)
196 | }
197 | }
198 |
199 | // 持久化高度到height表
200 | if service.height % 20 == 0 {
201 | if _, err = dao.GetHeightDAO().UpdateHeight(coinSet.Name, service.height); err != nil {
202 | utils.LogMsgEx(utils.ERROR, "更新块高失败:%v", err)
203 | continue
204 | }
205 | utils.LogMsgEx(utils.INFO, "块高更新到:%d", service.height)
206 | }
207 | service.height++
208 |
209 | // 更新块高到进度redis
210 | if _, err = dao.GetProcessDAO().UpdateHeight(coinSet.Name, service.height); err != nil {
211 | utils.LogMsgEx(utils.ERROR, "更新块高失败:%v", err)
212 | continue
213 | }
214 | }
215 | close(toNotifySig)
216 | service.status.TurnTo(DESTORY)
217 | }
--------------------------------------------------------------------------------
/src/services/heightService.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "sync"
5 | "utils"
6 | "rpcs"
7 | "dao"
8 | )
9 |
10 | type heightService struct {
11 | BaseService
12 | sync.Once
13 | }
14 |
15 | var _heightService *heightService
16 |
17 | func GetHeightService() *heightService {
18 | if _heightService == nil {
19 | _heightService = new(heightService)
20 | _heightService.Once = sync.Once {}
21 | _heightService.Once.Do(func() {
22 | _heightService.create()
23 | })
24 | }
25 | return _heightService
26 | }
27 |
28 | func (service *heightService) create() error {
29 | service.name = "withdrawService"
30 | service.status.RegAsObs(service)
31 | return service.BaseService.create()
32 | }
33 |
34 | func (service *heightService) BeforeTurn(s *utils.Status, tgtStt int) {
35 | switch tgtStt {
36 | case INIT:
37 | utils.LogMsgEx(utils.INFO, "initialization", nil)
38 | case START:
39 | utils.LogMsgEx(utils.INFO, "start", nil)
40 | }
41 | }
42 |
43 | func (service *heightService) AfterTurn(s *utils.Status, srcStt int) {
44 | switch s.Current() {
45 | case INIT:
46 | utils.LogMsgEx(utils.INFO, "initialized", nil)
47 | case START:
48 | // 启动子协程同步进度表的高度
49 | go service.syncProcessHeight()
50 | utils.LogMsgEx(utils.INFO, "started", nil)
51 | }
52 | }
53 |
54 | func (service *heightService) syncProcessHeight() {
55 | asset := utils.GetConfig().GetCoinSettings().Name
56 | rpc := rpcs.GetRPC(asset)
57 | var err error
58 | for err == nil && service.status.Current() == START {
59 | var curHeight uint64
60 | if curHeight, err = rpc.GetCurrentHeight(); err != nil {
61 | utils.LogMsgEx(utils.ERROR, "获取块高失败:%v", err)
62 | continue
63 | }
64 | if _, err = dao.GetProcessDAO().UpdateHeight(asset, curHeight); err != nil {
65 | utils.LogMsgEx(utils.ERROR, "更新块高失败:%v", err)
66 | continue
67 | }
68 | }
69 | service.status.TurnTo(DESTORY)
70 | }
--------------------------------------------------------------------------------
/src/services/stableService.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "entities"
5 | "sync"
6 | "utils"
7 | "dao"
8 | "rpcs"
9 | )
10 |
11 | /***
12 | 等待稳定服务:接收来自充值和提币的交易,等待其稳定后做后续操作
13 | 子协程(startWaitForStable):等待交易进入稳定状态
14 | 子协程(waitForUnstableTransaction):等待来自充值和提币的交易
15 | */
16 | type stableService struct {
17 | BaseService
18 | sync.Once
19 | waitForStableTxs []entities.Transaction
20 | waitTxsCounter map[string]uint
21 | waitForStableTxsLock *sync.Mutex
22 | }
23 |
24 | var _stableService *stableService
25 |
26 | func GetStableService() *stableService {
27 | if _stableService == nil {
28 | _stableService = new(stableService)
29 | _stableService.Once = sync.Once {}
30 | _stableService.Once.Do(func() {
31 | _stableService.create()
32 | })
33 | }
34 | return _stableService
35 | }
36 |
37 | func (service *stableService) create() error {
38 | service.name = "stableService"
39 | service.status.RegAsObs(service)
40 | service.waitTxsCounter = make(map[string]uint)
41 | service.waitForStableTxsLock = new(sync.Mutex)
42 | return service.BaseService.create()
43 | }
44 |
45 | func (service *stableService) BeforeTurn(s *utils.Status, tgtStt int) {
46 | var err error
47 | switch tgtStt {
48 | case INIT:
49 | utils.LogMsgEx(utils.INFO, "initialization", nil)
50 | // 加载数据库中所有未稳定的充币交易
51 | if err = service.loadIncompleteTransactions(); err != nil {
52 | panic(utils.LogMsgEx(utils.ERROR, "加载未完成的交易失败:%v", err))
53 | }
54 | case START:
55 | utils.LogMsgEx(utils.INFO, "start", nil)
56 | }
57 | }
58 |
59 | func (service *stableService) AfterTurn(s *utils.Status, srcStt int) {
60 | switch s.Current() {
61 | case INIT:
62 | utils.LogMsgEx(utils.INFO, "initialized", nil)
63 | case START:
64 | // 开启协程等待充币交易稳定
65 | go service.startWaitForStable()
66 | // 开启协程等待接收充币服务发来的交易
67 | go service.waitForUnstableTransaction()
68 | utils.LogMsgEx(utils.INFO, "started", nil)
69 | }
70 | }
71 |
72 | func (service *stableService) loadIncompleteTransactions() error {
73 | coinSetting := utils.GetConfig().GetCoinSettings()
74 | var err error
75 | var deposits []entities.BaseDeposit
76 | if deposits, err = dao.GetDepositDAO().GetUnstableDeposit(coinSetting.Name); err != nil {
77 | return err
78 | }
79 | var withdraws []entities.DatabaseWithdraw
80 | if withdraws, err = dao.GetWithdrawDAO().GetUnstableWithdraw(coinSetting.Name); err != nil {
81 | return err
82 | }
83 | for _, deposit := range deposits {
84 | utils.LogMsgEx(utils.INFO, "发现一笔待稳定的充值记录:%s", deposit.TxHash)
85 | service.waitForStableTxs = append(service.waitForStableTxs, deposit.Transaction)
86 | service.waitTxsCounter[deposit.TxHash] = 0
87 | }
88 | for _, withdraw := range withdraws {
89 | utils.LogMsgEx(utils.INFO, "发现一笔待稳定的提币记录:%s", withdraw.TxHash)
90 | service.waitForStableTxs = append(service.waitForStableTxs, withdraw.Transaction)
91 | service.waitTxsCounter[withdraw.TxHash] = 0
92 | }
93 | return err
94 | }
95 |
96 | func (service *stableService) startWaitForStable() {
97 | var err error
98 | coinSet := utils.GetConfig().GetCoinSettings()
99 | for err == nil && service.status.Current() == START {
100 | //如果达到协程上限,则等待
101 | service.waitForStableTxsLock.Lock()
102 | for i, tx := range service.waitForStableTxs {
103 | // 获取当前块高
104 | var curHeight uint64
105 | if curHeight, err = rpcs.GetRPC(coinSet.Name).GetCurrentHeight(); err != nil {
106 | utils.LogMsgEx(utils.ERROR, "获取块高失败:%v", err)
107 | continue
108 | }
109 |
110 | stableHeight := uint64(coinSet.Stable)
111 | if curHeight >= tx.Height + stableHeight {
112 | utils.LogMsgEx(utils.INFO, "交易:%s已进入稳定状态", tx.TxHash)
113 |
114 | if err = TxIntoStable(tx.TxHash, tx.Asset); err != nil {
115 | continue
116 | }
117 |
118 | service.waitForStableTxs = append(service.waitForStableTxs[:i], service.waitForStableTxs[i + 1:]...)
119 | delete(service.waitTxsCounter, tx.TxHash)
120 | break
121 | } else {
122 | if service.waitTxsCounter[tx.TxHash] % 20 == 0 {
123 | utils.LogMsgEx(utils.INFO, "交易:%s等待稳定,%d/%d",
124 | tx.TxHash, curHeight, tx.Height + stableHeight)
125 | }
126 | service.waitTxsCounter[tx.TxHash]++
127 | }
128 | }
129 | service.waitForStableTxsLock.Unlock()
130 | }
131 | service.status.TurnTo(DESTORY)
132 | }
133 |
134 | func (service *stableService) waitForUnstableTransaction() {
135 | var err error
136 | for err == nil && service.status.Current() == START {
137 | var tx entities.Transaction
138 | var ok bool
139 | if tx, ok = <- toNotifySig; !ok {
140 | break
141 | }
142 | utils.LogMsgEx(utils.INFO, "接收到一笔需等待的交易:%v", tx)
143 |
144 | service.waitForStableTxsLock.Lock()
145 | service.waitForStableTxs = append(service.waitForStableTxs, tx)
146 | service.waitTxsCounter[tx.TxHash] = 0
147 | service.waitForStableTxsLock.Unlock()
148 | utils.LogMsgEx(utils.INFO, "交易(%s)已处于等待状态", tx.TxHash)
149 | }
150 | service.status.TurnTo(DESTORY)
151 | }
--------------------------------------------------------------------------------
/src/services/withdrawService.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "sync"
5 | "utils"
6 | "entities"
7 | "dao"
8 | "rpcs"
9 | "time"
10 | )
11 |
12 | /***
13 | 提币服务:接收来自API的提币操作,并启动子协程等待其入链后交给通知服务
14 | 子协程(waitForWithdraw):等待从API发来的提币请求
15 | 子协程(sendTransactions):发送列队中的提币请求
16 | 子协程(waitForInchain):查询发起的提币交易,检查是否入链
17 | */
18 | type withdrawService struct {
19 | BaseService
20 | sync.Once
21 | wdsToSend []entities.BaseWithdraw
22 | sendDelayList map[int]int
23 | wdsToSendLock *sync.RWMutex
24 | wdsToInchain map[string]uint
25 | wdsToInchainLock *sync.RWMutex
26 | }
27 |
28 | var _withdrawService *withdrawService
29 |
30 | func GetWithdrawService() *withdrawService {
31 | if _withdrawService == nil {
32 | _withdrawService = new(withdrawService)
33 | _withdrawService.Once = sync.Once {}
34 | _withdrawService.Once.Do(func() {
35 | _withdrawService.create()
36 | })
37 | }
38 | return _withdrawService
39 | }
40 |
41 | func (service *withdrawService) create() error {
42 | service.name = "withdrawService"
43 | service.status.RegAsObs(service)
44 | service.wdsToInchain = make(map[string]uint)
45 | service.wdsToSendLock = new(sync.RWMutex)
46 | service.wdsToInchainLock = new(sync.RWMutex)
47 | service.sendDelayList = make(map[int]int)
48 | return service.BaseService.create()
49 | }
50 |
51 | func (service *withdrawService) BeforeTurn(s *utils.Status, tgtStt int) {
52 | var err error
53 | switch tgtStt {
54 | case INIT:
55 | utils.LogMsgEx(utils.INFO, "initialization", nil)
56 | // 加载所有未稳定的提币交易
57 | if err = service.loadWithdrawUnsent(); err != nil {
58 | panic(utils.LogMsgEx(utils.ERROR, "加载提币失败:%v", err))
59 | }
60 | case START:
61 | utils.LogMsgEx(utils.INFO, "start", nil)
62 | }
63 | }
64 |
65 | func (service *withdrawService) AfterTurn(s *utils.Status, srcStt int) {
66 | switch s.Current() {
67 | case INIT:
68 | utils.LogMsgEx(utils.INFO, "initialized", nil)
69 | case START:
70 | // 启动子协程等待API来的提币请求
71 | go service.waitForWithdraw()
72 | // 启动子协程发送请求
73 | go service.sendTransactions()
74 | // 启动子协程等待请求交易入链
75 | go service.waitForInchain()
76 | utils.LogMsgEx(utils.INFO, "started", nil)
77 | }
78 | }
79 |
80 | func (service *withdrawService) RemoveWithdraw(asset string, id int) {
81 | for i, wd := range service.wdsToSend {
82 | if wd.Id == id && wd.Asset == asset {
83 | service.wdsToSendLock.Lock()
84 | service.wdsToSend = append(service.wdsToSend[:i], service.wdsToSend[i + 1:]...)
85 | service.wdsToSendLock.Unlock()
86 | return
87 | }
88 | }
89 | for txid := range service.wdsToInchain {
90 | if i, err := dao.GetWithdrawDAO().GetWithdrawId(asset, txid); err != nil {
91 | utils.LogMsgEx(utils.ERROR, "未找到交易id为:%s的操作,错误为:%v", txid, err)
92 | continue
93 | } else if i == id {
94 | service.wdsToInchainLock.Lock()
95 | delete(service.wdsToInchain, txid)
96 | service.wdsToInchainLock.Unlock()
97 | return
98 | }
99 | }
100 | }
101 |
102 | func (service *withdrawService) loadWithdrawUnsent() error {
103 | var withdraws []entities.DatabaseWithdraw
104 | var err error
105 | asset := utils.GetConfig().GetCoinSettings().Name
106 | if withdraws, err = dao.GetWithdrawDAO().GetUnfinishWithdraw(asset); err != nil {
107 | return utils.LogMsgEx(utils.ERROR, "获取未发送的提币交易失败:%v", err)
108 | }
109 |
110 | for _, withdraw := range withdraws {
111 | if withdraw.Status < entities.WITHDRAW_SENT {
112 | service.wdsToSend = append(service.wdsToSend, entities.TurnToBaseWithdraw(&withdraw))
113 | utils.LogMsgEx(utils.INFO, "加载提币请求进待发送列队:%v", withdraw)
114 | } else if withdraw.Status < entities.WITHDRAW_INCHAIN {
115 | service.wdsToInchain[withdraw.TxHash] = 0
116 | utils.LogMsgEx(utils.INFO, "加载提币请求进待入链列队:%v", withdraw)
117 | }
118 | }
119 | return nil
120 | }
121 |
122 | func (service *withdrawService) waitForWithdraw() {
123 | var err error
124 | for err == nil && service.status.Current() == START {
125 | // 等待接收来自API的提币请求
126 | var withdraw entities.BaseWithdraw
127 | var ok bool
128 | if withdraw, ok = <- RevWithdrawSig; !ok {
129 | break
130 | }
131 | utils.LogMsgEx(utils.INFO, "接收到一笔待发送的提币:%v", withdraw)
132 |
133 | // 持久化到数据库
134 | if _, err = dao.GetWithdrawDAO().RecvNewWithdraw(withdraw); err != nil {
135 | utils.LogMsgEx(utils.ERROR, "新增提币请求失败:%v", err)
136 | continue
137 | }
138 | if _, err = dao.GetProcessDAO().SaveProcess(&entities.DatabaseProcess {
139 | BaseProcess: entities.BaseProcess {
140 | Id: withdraw.Id,
141 | Asset: withdraw.Asset,
142 | Type: entities.WITHDRAW,
143 | Process: entities.LOAD,
144 | Cancelable: true,
145 | },
146 | LastUpdateTime: time.Now(),
147 | }); err != nil {
148 | utils.LogMsgEx(utils.ERROR, "新增提币请求失败:%v", err)
149 | continue
150 | }
151 | utils.LogMsgEx(utils.INFO, "已持久化到数据库:%d", withdraw.Id)
152 |
153 | // 保存到内存
154 | service.wdsToSendLock.Lock()
155 | service.wdsToSend = append(service.wdsToSend, withdraw)
156 | service.wdsToSendLock.Unlock()
157 | utils.LogMsgEx(utils.INFO, "进入待发送提币列队:%d", withdraw.Id)
158 | }
159 | service.status.TurnTo(DESTORY)
160 | }
161 |
162 | func (service *withdrawService) sendTransactions() {
163 | asset := utils.GetConfig().GetCoinSettings().Name
164 | rpc := rpcs.GetRPC(asset)
165 | var err error
166 | for err == nil && service.status.Current() == START {
167 | service.wdsToSendLock.RLock()
168 | for i, withdraw := range service.wdsToSend {
169 | // 判断是否是发送延迟提币
170 | if n, ok := service.sendDelayList[withdraw.Id]; ok {
171 | if n > 0 {
172 | service.sendDelayList[withdraw.Id] = n - 1
173 | continue
174 | } else {
175 | delete(service.sendDelayList, withdraw.Id)
176 | }
177 | }
178 |
179 | // 发送提币转账请求
180 | var txHash string
181 | if txHash, err = rpc.SendTo(withdraw.Address, withdraw.Amount); err != nil {
182 | utils.LogMsgEx(utils.ERROR, "发送提币请求失败:%v", err)
183 | err = nil // 如果发送失败,重复尝试
184 | continue
185 | }
186 | if txHash == "" {
187 | utils.LogMsgEx(utils.ERROR, "空的交易ID", nil)
188 | service.sendDelayList[withdraw.Id] = 100000
189 | continue
190 | }
191 | utils.LogMsgEx(utils.INFO, "提币请求已发送,收到交易ID:%s", txHash)
192 |
193 | // 持久化到数据库
194 | if _, err = dao.GetWithdrawDAO().SentForTxHash(asset, txHash, withdraw.Id); err != nil {
195 | utils.LogMsgEx(utils.ERROR, "持久化到数据库失败:%v", err)
196 | continue
197 | }
198 | if _, err = dao.GetProcessDAO().SaveProcess(&entities.DatabaseProcess {
199 | BaseProcess: entities.BaseProcess {
200 | Id: withdraw.Id,
201 | TxHash: txHash,
202 | Asset: withdraw.Asset,
203 | Type: entities.WITHDRAW,
204 | Process: entities.SENT,
205 | Cancelable: false,
206 | },
207 | LastUpdateTime: time.Now(),
208 | }); err != nil {
209 | utils.LogMsgEx(utils.ERROR, "持久化到数据库失败:%v", err)
210 | continue
211 | }
212 | utils.LogMsgEx(utils.INFO, "交易:%s已持久化", txHash)
213 |
214 | // 插入待入链列表
215 | service.wdsToInchainLock.Lock()
216 | service.wdsToInchain[txHash] = 0
217 | service.wdsToInchainLock.Unlock()
218 |
219 | // 如果发送成功,立刻删除这笔提币请求(以防重复发提币,为了防止索引出错,立即跳出循环)
220 | service.wdsToSend = append(service.wdsToSend[:i], service.wdsToSend[i + 1:]...)
221 | break
222 | }
223 | service.wdsToSendLock.RUnlock()
224 | }
225 | service.status.TurnTo(DESTORY)
226 | }
227 |
228 | func (service *withdrawService) waitForInchain() {
229 | asset := utils.GetConfig().GetCoinSettings().Name
230 | rpc := rpcs.GetRPC(asset)
231 | var err error
232 | for err == nil && service.status.Current() == START {
233 | service.wdsToInchainLock.RLock()
234 | for txHash, num := range service.wdsToInchain {
235 | // 检查交易的块高
236 | var height uint64
237 | if height, err = rpc.GetTxExistsHeight(txHash); err != nil {
238 | utils.LogMsgEx(utils.ERROR, "交易:%s查询错误:%v", txHash, err)
239 | continue
240 | }
241 |
242 | // 如果已经入链,发送给notify服务等待稳定
243 | if height == 0 {
244 | if num % 100 == 0 {
245 | utils.LogMsgEx(utils.INFO, "交易:%s等待入链", txHash)
246 | }
247 | service.wdsToInchain[txHash]++
248 | continue
249 | }
250 | utils.LogMsgEx(utils.INFO, "交易:%s已经入链,高度:%d", txHash, height)
251 |
252 | // 更新状态
253 | var txs []entities.Transaction
254 | if txs, err = rpc.GetTransaction(txHash); err != nil {
255 | utils.LogMsgEx(utils.ERROR, "获取交易:%s失败:%v", txHash, err)
256 | continue
257 | }
258 | for _, wd := range txs {
259 | if _, err = dao.GetWithdrawDAO().WithdrawIntoChain(asset, txHash, height, wd.TxIndex); err != nil {
260 | utils.LogMsgEx(utils.ERROR, "持久化到数据库失败:%v", err)
261 | continue
262 | }
263 |
264 | var id int
265 | if id, err = dao.GetWithdrawDAO().GetWithdrawId(asset, txHash); err != nil {
266 | utils.LogMsgEx(utils.ERROR, "获取提币交易id失败:%v", err)
267 | continue
268 | }
269 | if _, err = dao.GetProcessDAO().SaveProcess(&entities.DatabaseProcess {
270 | BaseProcess: entities.BaseProcess {
271 | Id: id,
272 | TxHash: txHash,
273 | Asset: wd.Asset,
274 | Type: entities.WITHDRAW,
275 | Process: entities.INCHAIN,
276 | Cancelable: false,
277 | },
278 | Height: wd.Height,
279 | CompleteHeight: wd.Height + uint64(utils.GetConfig().GetCoinSettings().Stable),
280 | LastUpdateTime: time.Now(),
281 | }); err != nil {
282 | utils.LogMsgEx(utils.ERROR, "持久化到数据库失败:%v", err)
283 | continue
284 | }
285 | utils.LogMsgEx(utils.INFO, "交易:%s已持久化", txHash)
286 |
287 | toNotifySig <- wd
288 | }
289 |
290 | // 同样,一旦发送给待稳定服务,立刻从列表中删除(为了防止索引出错,立即跳出循环)
291 | delete(service.wdsToInchain, txHash)
292 | break
293 | }
294 | service.wdsToInchainLock.RUnlock()
295 | }
296 | service.status.TurnTo(DESTORY)
297 | }
--------------------------------------------------------------------------------
/src/utils/collection.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | func IntArrayContains(array []int, target int) bool {
4 | for _, i := range array {
5 | if i == target { return true }
6 | }
7 | return false
8 | }
9 |
10 | func StrArrayContains(array []string, target string) bool {
11 | for _, i := range array {
12 | if i == target { return true }
13 | }
14 | return false
15 | }
--------------------------------------------------------------------------------
/src/utils/config.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "os"
5 | "log"
6 | "fmt"
7 | "encoding/json"
8 | "sync"
9 | "time"
10 | )
11 |
12 | type baseSetting struct {
13 | Env string `json:"env"`
14 | Services []string `json:"services"`
15 | }
16 |
17 | type subsSetting struct {
18 | Db struct {
19 | Url string `json:"url"`
20 | Name string `json:"name"`
21 | Username string `json:"username"`
22 | Password string `json:"password"`
23 | MaxConn int `json:"max_conn"`
24 | } `json:"db"`
25 | Redis struct {
26 | Password string `json:"password"`
27 | TimeFormat string `json:"time_format"`
28 | ProcessPubKey string`json:"process_pub_key"`
29 | Clusters []struct {
30 | Name string `json:"name"`
31 | Url string `json:"url"`
32 | } `json:"clusters"`
33 | } `json:"redis"`
34 | APIs struct {
35 | RPC struct {
36 | Active bool `json:"active"`
37 | Port int `json:"port"`
38 | } `json:"rpc"`
39 | Socket struct {
40 | Active bool `json:"active"`
41 | Port int `json:"port"`
42 | } `json:"socket"`
43 | MQ struct {
44 | Active bool `json:"active"`
45 | } `json:"mq"`
46 | } `json:"apis"`
47 | Callbacks struct {
48 | Redis struct {
49 | Active bool `json:"active"`
50 | } `json:"redis"`
51 | RPC struct {
52 | Active bool `json:"active"`
53 | DepositURL string `json:"deposit_url"`
54 | WithdrawURL string `json:"withdraw_url"`
55 | CollectURL string `json:"collect_url"`
56 | } `json:"rpc"`
57 | MQ struct {
58 | Active bool `json:"active"`
59 | } `json:"mq"`
60 | } `json:"callbacks"`
61 | }
62 |
63 | type coinSetting struct {
64 | Name string `json:"name"`
65 | Url string `json:"url"`
66 | AssistSite string `json:"assistSite"`
67 | Decimal int `json:"decimal"`
68 | Stable int `json:"stable"`
69 | RPCUser string `json:"rpcUser"`
70 | RPCPassword string `json:"rpcPassword"`
71 | Collect string `json:"collect"`
72 | Deposit string `json:"deposit"`
73 | MinCollect float64 `json:"minCollect"`
74 | CollectInterval time.Duration `json:"collectInterval"`
75 | TradePassword string `json:"tradePassword"`
76 | UnlockDuration int `json:"unlockDuration"`
77 | Withdraw string `json:"withdraw"`
78 | }
79 |
80 | type msgsSetting struct {
81 | Logs struct {
82 | Debug bool `json:"debug"`
83 | } `json:"logs"`
84 | Level map[string]string `json:"level"`
85 | Storage struct {
86 | File struct {
87 | Active bool `json:"active"`
88 | Split string `json:"split"`
89 | SplitMode string `json:"split_mode"`
90 | Rotate string `json:"rotate"`
91 | Path string `json:"path"`
92 | NameFormat string `json:"nameFormat"`
93 | } `json:"file"`
94 | } `json:"storage"`
95 | Errors map[string]string `json:"errors"`
96 | Warnings map[string]string `json:"warnings"`
97 | Information map[string]string `json:"information"`
98 | Debugs map[string]string `json:"debugs"`
99 | }
100 |
101 | type cmdsSetting struct {
102 | Unknown string `json:"unknown"`
103 | Help string `json:"help"`
104 | Version string `json:"version"`
105 | }
106 |
107 | type config struct {
108 | sync.Once
109 | base baseSetting
110 | subs subsSetting
111 | coin coinSetting
112 | msgs msgsSetting
113 | cmds cmdsSetting
114 | }
115 |
116 | var _config *config
117 |
118 | func GetConfig() *config {
119 | if _config == nil {
120 | _config = new(config)
121 | _config.Once = sync.Once {}
122 | _config.Once.Do(func() {
123 | _config.create()
124 | })
125 | }
126 | return _config
127 | }
128 |
129 | func (cfg *config) create() error {
130 | var err error
131 | if err = cfg.loadJson("settings", &cfg.base); err != nil {
132 | panic(err)
133 | }
134 | if err = cfg.loadJson(cfg.base.Env, &cfg.subs); err != nil {
135 | panic(err)
136 | }
137 | if err = cfg.loadJson("coin", &cfg.coin); err != nil {
138 | panic(err)
139 | }
140 | if err = cfg.loadJson("message", &cfg.msgs); err != nil {
141 | panic(err)
142 | }
143 | if err = cfg.loadJson("command", &cfg.cmds); err != nil {
144 | panic(err)
145 | }
146 | return nil
147 | }
148 |
149 | func (cfg *config) loadJson(fileName string, data interface {}) error {
150 | file, err := os.Open(fmt.Sprintf("config/%s.json", fileName))
151 | if err != nil {
152 | log.Fatal(err)
153 | }
154 | defer file.Close()
155 |
156 | chunks := make([]byte, 1024, 1024)
157 | bufData := []byte {}
158 | totalLen := 0
159 | for {
160 | n, err := file.Read(chunks)
161 | if n == 0 { break }
162 | totalLen += n
163 | if err != nil {
164 | log.Fatal(err)
165 | return err
166 | }
167 | bufData = append(bufData, chunks...)
168 | }
169 |
170 | if err = json.Unmarshal(bufData[:totalLen], &data); err != nil {
171 | log.Println(err)
172 | return err
173 | }
174 | return nil
175 | }
176 |
177 | func (cfg *config) GetBaseSettings() baseSetting {
178 | return cfg.base
179 | }
180 |
181 | func (cfg *config) GetSubsSettings() subsSetting {
182 | return cfg.subs
183 | }
184 |
185 | func (cfg *config) GetCoinSettings() coinSetting {
186 | return cfg.coin
187 | }
188 |
189 | func (cfg *config) GetMsgsSettings() msgsSetting {
190 | return cfg.msgs
191 | }
192 |
193 | func (cfg *config) GetCmdsSettings() cmdsSetting {
194 | return cfg.cmds
195 | }
--------------------------------------------------------------------------------
/src/utils/json.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | type JsonObject struct {
8 | Data map[string]interface {}
9 | }
10 |
11 | func (obj *JsonObject) Contain(key string) bool {
12 | tmp := obj.Data
13 | ks := strings.Split(key, ".")
14 | for i, k := range ks {
15 | if t, ok := tmp[k]; ok {
16 | if i == len(ks) - 1 {
17 | return true
18 | } else {
19 | switch t.(type) {
20 | case map[string]interface {}:
21 | tmp = t.(map[string]interface {})
22 | default:
23 | return false
24 | }
25 | }
26 | tmp = t.(map[string]interface {})
27 | } else {
28 | return false
29 | }
30 | }
31 | return true
32 | }
33 |
34 | func (obj *JsonObject) Get(key string) (interface {}, error) {
35 | tmp := obj.Data
36 | ks := strings.Split(key, ".")
37 | for i, k := range ks {
38 | if t, ok := tmp[k]; ok {
39 | if i == len(ks) - 1 {
40 | return t, nil
41 | } else {
42 | switch t.(type) {
43 | case map[string]interface {}:
44 | tmp = t.(map[string]interface {})
45 | default:
46 | return nil, LogMsgEx(ERROR, "不存在指定键值:%s(%s)", key, k)
47 | }
48 | }
49 | } else {
50 | return nil, LogMsgEx(ERROR, "不存在指定键值:%s(%s)", key, k)
51 | }
52 | }
53 | return nil, nil
54 | }
--------------------------------------------------------------------------------
/src/utils/message.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strconv"
7 | "reflect"
8 | "errors"
9 | "os"
10 | "strings"
11 | "time"
12 | "path"
13 | "runtime"
14 | )
15 |
16 | const (
17 | ERROR = iota
18 | WARNING
19 | INFO
20 | DEBUG
21 | )
22 |
23 | var levels = map[int]string {
24 | ERROR: "E", WARNING: "W", INFO: "I", DEBUG: "D",
25 | }
26 |
27 | var logFiles = make(map[string]*os.File)
28 |
29 | func GetIdxMsg(idx string) string {
30 | msgSet := GetConfig().GetMsgsSettings()
31 | var msgs = map[string]string {}
32 | switch idx[0:1] {
33 | case "E":
34 | msgs = msgSet.Errors
35 | case "W":
36 | msgs = msgSet.Warnings
37 | case "I":
38 | msgs = msgSet.Information
39 | case "D":
40 | msgs = msgSet.Debugs
41 | }
42 | return msgs[idx]
43 | }
44 |
45 | func LogIdxEx(level int, id int, detail ...interface {}) error {
46 | msgSet := GetConfig().GetMsgsSettings()
47 | if !msgSet.Logs.Debug && level == DEBUG {
48 | return procsDetail(detail)
49 | }
50 |
51 | log.SetFlags(log.Lshortfile)
52 |
53 | var msgs = map[string]string {}
54 | var prefix string
55 | switch level {
56 | case ERROR:
57 | msgs = msgSet.Errors
58 | prefix = "E"
59 | case WARNING:
60 | msgs = msgSet.Warnings
61 | prefix = "W"
62 | case INFO:
63 | msgs = msgSet.Information
64 | prefix = "I"
65 | case DEBUG:
66 | msgs = msgSet.Debugs
67 | prefix = "D"
68 | }
69 |
70 | if msg, ok := msgs[prefix + fmt.Sprintf("%04d", id)]; ok {
71 | return logMsgEx(level, msg, detail...)
72 | } else {
73 | return logMsgEx(level, "未找到指定的消息信息:%d", id)
74 | }
75 | }
76 |
77 | func logMsgEx(level int, msg string, detail ...interface {}) error {
78 | msgSet := GetConfig().GetMsgsSettings()
79 | if !msgSet.Logs.Debug && level == DEBUG {
80 | return procsDetail(detail)
81 | }
82 |
83 | log.SetFlags(log.Lshortfile)
84 |
85 | if _, ok := levels[level]; !ok {
86 | level = ERROR
87 | }
88 |
89 | if len(detail) >= 1 && detail[0] != nil {
90 | msg = fmt.Sprintf(msg, detail...)
91 | }
92 |
93 | strLevel := GetConfig().GetMsgsSettings().Level[strconv.Itoa(level)]
94 | logTxt := fmt.Sprintf("[%s] %s\n", strLevel, msg)
95 |
96 | var err error
97 | fileLog := GetConfig().msgs.Storage.File
98 | if fileLog.Active {
99 | // 如果文件夹不存在,创建
100 | if len(fileLog.Path) != 0 {
101 | if _, err = os.Stat(fileLog.Path); os.IsNotExist(err) {
102 | if err = os.Mkdir(fileLog.Path, os.ModePerm); err != nil {
103 | log.Fatal(err)
104 | }
105 | }
106 | }
107 | // 添加发生的文件和行数
108 | location := "???:?? "
109 | if _, file, line, ok := runtime.Caller(2); ok {
110 | projPath := ""
111 | if projPath, err = os.Getwd(); err != nil {
112 | log.Fatal(err)
113 | }
114 | projPath = path.Join(projPath, "src")
115 | file = strings.Replace(file, projPath, "", -1)
116 | location = fmt.Sprintf("%s:%d ", file, line)
117 | }
118 | // 根据split做分离
119 | switch fileLog.Split {
120 | case "type":
121 | case "level":
122 | fallthrough
123 | default:
124 | // 组装文件名
125 | logFileTag := levels[level]
126 | if _, ok := logFiles[logFileTag]; !ok {
127 | splitName := GetConfig().msgs.Level[strconv.Itoa(level)]
128 | fileName := genLogFileName(fileLog.NameFormat, splitName)
129 | if logFiles[logFileTag], err = os.OpenFile(
130 | path.Join(fileLog.Path, fileName),
131 | os.O_APPEND | os.O_CREATE | os.O_WRONLY,
132 | 0644,
133 | ); err != nil {
134 | log.Fatal(err)
135 | }
136 | }
137 | if _, err = logFiles[logFileTag].Write([]byte(location + logTxt)); err != nil {
138 | log.Fatal(err)
139 | }
140 | }
141 | }
142 |
143 | log.Output(3, logTxt)
144 | if len(detail) > 1 {
145 | return procsDetail(msg)
146 | } else {
147 | return procsDetail(detail[0])
148 | }
149 | }
150 |
151 | func genLogFileName(nameFmt string, split string) string {
152 | ret := strings.Replace(nameFmt, "{split}", split, -1)
153 | ret = strings.Replace(ret, "{time}", time.Now().Format("2006-01-02"), -1)
154 | ret = strings.Replace(ret, "{suffix}", "", -1)
155 | return ret
156 | }
157 |
158 | func procsDetail(detail interface {}) error {
159 | if detail != nil {
160 | typName := reflect.TypeOf(detail).Name()
161 | switch typName {
162 | case "error":
163 | return detail.(error)
164 | case "string":
165 | return errors.New(detail.(string))
166 | case "int":
167 | fallthrough
168 | case "int32":
169 | fallthrough
170 | case "int64":
171 | fallthrough
172 | case "uint":
173 | fallthrough
174 | case "uint32":
175 | fallthrough
176 | case "uint64":
177 | return errors.New(fmt.Sprintf("%d", detail))
178 | default:
179 | return nil
180 | }
181 | }
182 | return nil
183 | }
184 |
185 | func LogMsgEx(level int, msg string, detail ...interface {}) error {
186 | return logMsgEx(level, msg, detail...)
187 | }
188 |
189 | func CloseAllLogStorage() {
190 | for _, f := range logFiles {
191 | f.Close()
192 | }
193 | }
--------------------------------------------------------------------------------
/src/utils/status.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "sync"
6 | )
7 |
8 | type Observer interface {
9 | BeforeTurn(status *Status, tgtStt int)
10 | AfterTurn(status *Status, srcStt int)
11 | }
12 |
13 | type Status struct {
14 | AllStatus []int
15 | statusVal int
16 | observers []Observer
17 | mux sync.RWMutex
18 | }
19 |
20 | func (stt *Status) Init(stts []int) {
21 | if stts != nil {
22 | stt.AllStatus = stts
23 | }
24 | }
25 |
26 | func (stt *Status) TurnTo(status int) (int, error) {
27 | orgStt := stt.statusVal
28 | if !IntArrayContains(stt.AllStatus, status) {
29 | return orgStt, errors.New("Could not find identified status")
30 | }
31 | if stt.statusVal == status {
32 | return stt.statusVal, nil
33 | }
34 | for _, obs := range stt.observers {
35 | obs.BeforeTurn(stt, status)
36 | }
37 | stt.statusVal = status
38 | for _, obs := range stt.observers {
39 | obs.AfterTurn(stt, orgStt)
40 | }
41 | return orgStt, nil
42 | }
43 |
44 | func (stt *Status) Current() int {
45 | return stt.statusVal
46 | }
47 |
48 | func (stt *Status) RegAsObs(obs Observer) error {
49 | stt.observers = append(stt.observers, obs)
50 | return nil
51 | }
--------------------------------------------------------------------------------
/tests/api/apis_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "testing"
5 | "net/http"
6 | "io/ioutil"
7 | "encoding/json"
8 | "apis"
9 | "github.com/stretchr/testify/assert"
10 | "reflect"
11 | "fmt"
12 | )
13 |
14 | func getFromServer(t *testing.T, path string) apis.RespVO {
15 | var resp *http.Response
16 | var err error
17 | if resp, err = http.Get(fmt.Sprintf("http://localhost:8037%s", path)); err != nil {
18 | t.Fatal(err)
19 | }
20 |
21 | var bodyBtary []byte
22 | if bodyBtary, err = ioutil.ReadAll(resp.Body); err != nil {
23 | t.Fatal(err)
24 | }
25 | defer resp.Body.Close()
26 |
27 | var result apis.RespVO
28 | if err = json.Unmarshal(bodyBtary, &result); err != nil {
29 | t.Fatal(err)
30 | }
31 | t.Log(result)
32 | return result
33 | }
34 |
35 | func TestGetHeightAPI(t *testing.T) {
36 | result := getFromServer(t, "/api/deposit/ETH/height")
37 |
38 | assert.Equal(t, 200, result.Code)
39 | assert.Equal(t, "", result.Msg)
40 | assert.Equal(t, "float64", reflect.TypeOf(result.Data).Name())
41 | }
42 |
43 | func TestNewAddressAPI(t *testing.T) {
44 | result := getFromServer(t, "/api/deposit/ETH/address")
45 |
46 | assert.Equal(t, 200, result.Code)
47 | assert.Equal(t, "", result.Msg)
48 | assert.Equal(t, "string", reflect.TypeOf(result.Data).Name())
49 | }
50 |
51 | func TestQueryProcess(t *testing.T) {
52 | result := getFromServer(t, "/api/common/ETH/process/0x6bccc827978af918acaf93e16f3887d9890e61e09dcbbe1a5b3676767cf9f232")
53 |
54 | assert.Equal(t, 200, result.Code)
55 | assert.Equal(t, "", result.Msg)
56 | data := result.Data.(map[string]interface {})
57 | assert.Equal(t, "0x6bccc827978af918acaf93e16f3887d9890e61e09dcbbe1a5b3676767cf9f232", data["tx_hash"])
58 | }
--------------------------------------------------------------------------------
/tests/blockchain_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "bytes"
7 | "net/http"
8 | "io/ioutil"
9 | "testing"
10 | )
11 |
12 | const URL = "http://18.144.17.127:8545"
13 | type ReqBody struct {
14 | Method string `json:method`
15 | Params []string `json:params`
16 | Id string `json:id`
17 | }
18 |
19 | func TestBlockChain(t *testing.T) {
20 | log.SetFlags(log.Lshortfile)
21 |
22 | // Request from blockchain
23 | reqBody := ReqBody { "eth_blockNumber", []string {}, "latest" }
24 | reqStr, err := json.Marshal(reqBody)
25 | if err != nil {
26 | log.Fatal(err)
27 | return
28 | }
29 | reqBuf := bytes.NewBuffer([]byte(reqStr))
30 | res, err := http.Post(URL, "application/json", reqBuf)
31 | defer res.Body.Close()
32 |
33 | // Parse response body
34 | bodyStr, err := ioutil.ReadAll(res.Body)
35 | log.Println(string(bodyStr))
36 | }
37 |
--------------------------------------------------------------------------------
/tests/config_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "utils"
5 | "log"
6 | "testing"
7 | )
8 |
9 | func TestConfig(t *testing.T) {
10 | log.SetFlags(log.Lshortfile)
11 |
12 | //Test config
13 | config := utils.GetConfig()
14 | log.Println(config.GetBaseSettings())
15 | log.Println(config.GetSubsSettings())
16 | }
--------------------------------------------------------------------------------
/tests/dao/addressDAO_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "dao"
5 | "testing"
6 | "fmt"
7 | )
8 |
9 | func TestNewAddressDAO(t *testing.T) {
10 | fmt.Println(dao.GetAddressDAO().NewAddress("BTC", "0xabcd"))
11 | }
12 |
13 | func TestFindAddressByAsset(t *testing.T) {
14 | fmt.Println(dao.GetAddressDAO().FindInuseByAsset("ETH"))
15 | }
16 |
--------------------------------------------------------------------------------
/tests/dao/collectDAO_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "testing"
5 | "fmt"
6 | "dao"
7 | )
8 |
9 | func TestAddCollect(t *testing.T) {
10 | fmt.Println(dao.GetCollectDAO().AddSentCollect("0x1234", "ETH", "0xabcd", 120))
11 | }
--------------------------------------------------------------------------------
/tests/dao/depositDAO_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "dao"
5 | "entities"
6 | "testing"
7 | "fmt"
8 | )
9 |
10 | func TestAddScannedDepositDAO(t *testing.T) {
11 | var deposit entities.BaseDeposit
12 | deposit.TxHash = "0x12345"
13 | deposit.Address = "0xabcd"
14 | deposit.Amount = 1000
15 | deposit.TxIndex = 0
16 | deposit.Height = 200000
17 | deposit.Asset = "ETH"
18 | fmt.Println(dao.GetDepositDAO().AddScannedDeposit(&deposit))
19 | }
20 |
21 | func TestAddStableDepositDAO(t *testing.T) {
22 | var deposit entities.BaseDeposit
23 | deposit.TxHash = "0x12345"
24 | deposit.Address = "0xabcd"
25 | deposit.Amount = 1000
26 | deposit.TxIndex = 0
27 | deposit.Height = 200000
28 | deposit.Asset = "ETH"
29 | fmt.Println(dao.GetDepositDAO().AddStableDeposit(&deposit))
30 | }
31 |
32 | func TestGetUnstableDepositDAO(t *testing.T) {
33 | fmt.Println(dao.GetDepositDAO().GetUnstableDeposit("ETH"))
34 | }
35 |
36 | func TestDepositIntoStableDAO(t *testing.T) {
37 | fmt.Println(dao.GetDepositDAO().DepositIntoStable("0x12345"))
38 | }
--------------------------------------------------------------------------------
/tests/dao/heightDAO_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "testing"
5 | "fmt"
6 | "dao"
7 | )
8 |
9 | func TestHeight(t *testing.T) {
10 | fmt.Println(dao.GetHeightDAO().GetHeight("ETH"))
11 | }
12 |
13 | func TestAddAsset(t *testing.T) {
14 | fmt.Println(dao.GetHeightDAO().ChkOrAddAsset("ETH"))
15 | }
16 |
17 | func TestUpdateHeight(t *testing.T) {
18 | fmt.Println(dao.GetHeightDAO().UpdateHeight("BTC", 10))
19 | }
--------------------------------------------------------------------------------
/tests/dao/processDAO_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "entities"
5 | "dao"
6 | "testing"
7 | "fmt"
8 | )
9 |
10 | func TestInsertProcsDAO(t *testing.T) {
11 | var processForAdd entities.DatabaseProcess
12 | processForAdd.TxHash = "0xabcd"
13 | processForAdd.Cancelable = true
14 | processForAdd.Process = entities.AUDIT
15 | processForAdd.Type = entities.WITHDRAW
16 | processForAdd.Asset = "ETH"
17 | fmt.Println(dao.GetProcessDAO().SaveProcess(&processForAdd))
18 | }
19 |
20 | func TestUpdateProcsDAO(t *testing.T) {
21 | var processForUpd entities.DatabaseProcess
22 | processForUpd.TxHash = "0xabcd"
23 | processForUpd.Height = 2000
24 | processForUpd.Cancelable = true
25 | fmt.Println(dao.GetProcessDAO().SaveProcess(&processForUpd))
26 | }
27 |
28 | func TestQueryProcsDAO(t *testing.T) {
29 | fmt.Println(dao.GetProcessDAO().QueryProcess("ETH", "0xabcd"))
30 | }
--------------------------------------------------------------------------------
/tests/dao/withdrawDAO_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "testing"
5 | "entities"
6 | "fmt"
7 | "dao"
8 | )
9 |
10 | func TestNewWithdraw(t *testing.T) {
11 | var wd entities.BaseWithdraw
12 | wd.Id = 12345
13 | wd.TxHash = "0x12345"
14 | wd.Asset = "ETH"
15 | wd.Address = "0xabcd"
16 | wd.Amount = 123.5
17 | fmt.Println(dao.GetWithdrawDAO().NewWithdraw(wd))
18 | }
19 |
20 | func TestWithdrawIntoChain(t *testing.T) {
21 | fmt.Println(dao.GetWithdrawDAO().WithdrawIntoChain("0x12345", 12345, 0))
22 | }
23 |
24 | func TestWithdrawIntoStable(t *testing.T) {
25 | fmt.Println(dao.GetWithdrawDAO().WithdrawIntoStable("0x12345"))
26 | }
27 |
28 | func TestGetAllUnstable(t *testing.T) {
29 | fmt.Println(dao.GetWithdrawDAO().GetAllUnstable("ETH"))
30 | }
31 |
32 | func TestGetAvailableId(t *testing.T) {
33 | fmt.Println(dao.GetWithdrawDAO().GetAvailableId("ETH"))
34 | }
--------------------------------------------------------------------------------
/tests/database_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "log"
7 | "testing"
8 | )
9 |
10 | const DBUrl = ""
11 | const DBName = "test"
12 | const DBUserName = "root"
13 | const DBPassword = "59524148chenOP"
14 |
15 | const DropTable = "DROP TABLE IF EXISTS user"
16 | const CreateTable = `CREATE TABLE IF NOT EXISTS user (
17 | id INTEGER NOT NULL AUTO_INCREMENT,
18 | username VARCHAR(20) NOT NULL UNIQUE,
19 | password VARCHAR(20) NOT NULL,
20 | PRIMARY KEY(username),
21 | INDEX(id)
22 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8`
23 | const InsertTest = "INSERT INTO user (username, password) VALUES (?, ?)"
24 | const DeleteTest = "DELETE FROM user WHERE username=?"
25 | const UpdateTest = "UPDATE user SET password=? WHERE username=?"
26 | const SelectTest = "SELECT username, password FROM user"
27 | const SelOneTest = SelectTest + " WHERE username=?"
28 |
29 | func displayAll(db *sql.DB) bool {
30 | rows, err := db.Query(SelectTest)
31 | if err != nil {
32 | log.Fatal(err)
33 | return false
34 | }
35 | for rows.Next() {
36 | var username string
37 | var password string
38 | if err := rows.Scan(&username, &password); err != nil {
39 | log.Fatal(err)
40 | continue
41 | }
42 | log.Printf("username: %s, password: %s\n", username, password)
43 | }
44 | if err := rows.Err(); err != nil {
45 | log.Fatal(err)
46 | return false
47 | }
48 | return true
49 | }
50 |
51 | func TestDatabase(t *testing.T) {
52 | log.SetFlags(log.Lshortfile)
53 |
54 | //Connect database
55 | var err error
56 | var db *sql.DB
57 | var insertTest *sql.Stmt
58 | var tx *sql.Tx
59 | var result sql.Result
60 | var affectedRows [20]int64
61 |
62 | fmt.Println()
63 | if db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@%s/%s?charset=utf8&tls=skip-verify",
64 | DBUserName, DBPassword, DBUrl, DBName)); err != nil {
65 | log.Fatal(err)
66 | return
67 | }
68 | if err := db.Ping(); err != nil {
69 | log.Fatal(err)
70 | return
71 | }
72 |
73 | //Drop and create table
74 | if _, err = db.Exec(DropTable); err != nil {
75 | log.Fatal(err)
76 | return
77 | }
78 | if _, err = db.Exec(CreateTable); err != nil {
79 | log.Fatal(err)
80 | return
81 | }
82 | log.Println("Create table succeed")
83 |
84 | //Test insert and transaction
85 | if insertTest, err = db.Prepare(InsertTest); err != nil {
86 | log.Fatal(err)
87 | return
88 | }
89 | // updateTest, err := db.Prepare(UpdateTest)
90 | // if err != nil {
91 | // log.Fatal(err)
92 | // return
93 | // }
94 |
95 | if tx, err = db.Begin(); err != nil {
96 | log.Fatal(err)
97 | return
98 | }
99 |
100 | if result, err = tx.Stmt(insertTest).Exec("opteacher", "12345"); err != nil {
101 | log.Fatal(err)
102 | tx.Rollback()
103 | return
104 | }
105 | affectedRows[0], _ = result.RowsAffected()
106 | if result, err = tx.Stmt(insertTest).Exec("tyoukasin", "54321"); err != nil {
107 | log.Fatal(err)
108 | tx.Rollback()
109 | return
110 | }
111 | affectedRows[1], _ = result.RowsAffected()
112 | if err = tx.Commit(); err != nil {
113 | log.Fatal(err)
114 | tx.Rollback()
115 | return
116 | }
117 | log.Printf("Insert table succeed, affected %d rows\n", affectedRows[0] + affectedRows[1])
118 | if !displayAll(db) { return }
119 |
120 | //Test update
121 | if result, err = db.Exec(UpdateTest, "abcde", "opteacher"); err != nil {
122 | log.Fatal(err)
123 | return
124 | }
125 | lastUpdateId, _ := result.RowsAffected()
126 | log.Printf("Update table succeed, affected %d rows\n", lastUpdateId)
127 | if !displayAll(db) { return }
128 |
129 | //Test delete
130 | if result, err = db.Exec(DeleteTest, "tyoukasin"); err != nil {
131 | log.Fatal(err)
132 | return
133 | }
134 | lastDeleteId, _ := result.RowsAffected()
135 | log.Printf("Delete table record succeed, affected %d rows\n", lastDeleteId)
136 | if !displayAll(db) { return }
137 | }
138 |
--------------------------------------------------------------------------------
/tests/goroutine_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "sync"
5 | "log"
6 | "fmt"
7 | "time"
8 | "testing"
9 | )
10 |
11 | var sig = make(chan int)
12 | var end = make(chan bool)
13 | func testChannel() {
14 | for i := range sig {
15 | time.Sleep(2000 * time.Millisecond)
16 | log.Println(i)
17 | }
18 | end <- true
19 | log.Println("End")
20 | }
21 | func testChannelByOK() {
22 | for {
23 | i, ok := <- sig
24 | if !ok { break }
25 | time.Sleep(2000 * time.Millisecond)
26 | log.Println(i)
27 | }
28 | log.Println("End")
29 | }
30 |
31 | func TestGoroutine(t *testing.T) {
32 | log.SetFlags(log.Lshortfile)
33 |
34 | // Test goroutine
35 | wg := new(sync.WaitGroup)
36 | wg.Add(2)
37 | for i := 0; i < 2; i++ {
38 | go func(id int) {
39 | log.Println(id);
40 | defer wg.Done()
41 | }(i)
42 | }
43 | wg.Wait()
44 |
45 | // Test channel
46 | fmt.Println()
47 | go testChannel()
48 | for i := 0; i < 5; i++ {
49 | sig <- i
50 | }
51 | close(sig)
52 | <- end
53 | close(end)
54 |
55 | //Use of-idiom test channel
56 | fmt.Println()
57 | sig = make(chan int)
58 | go testChannelByOK()
59 | sig <- 20
60 | sig <- 30
61 | sig <- 40
62 | close(sig)
63 | time.Sleep(5 * time.Second)
64 | }
65 |
--------------------------------------------------------------------------------
/tests/language_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "log"
5 | "fmt"
6 | "reflect"
7 | "testing"
8 | "math/big"
9 | "math"
10 | "strings"
11 | )
12 |
13 | type itfc interface {
14 | Test() string
15 | }
16 |
17 | type test struct {
18 |
19 | }
20 |
21 | func (t *test) Test() string {
22 | return "abcd"
23 | }
24 |
25 | func TestLang(t *testing.T) {
26 | log.SetFlags(log.Lshortfile)
27 | fmt.Println("abcd")
28 | var c float64 = 58500000000000000000
29 | log.Println(reflect.TypeOf(c).Name())
30 |
31 | var a itfc
32 | var b = new(test)
33 | a = b
34 | fmt.Println(a.Test())
35 |
36 | reflect.ArrayOf(10, reflect.TypeOf(test {}))
37 | fmt.Println()
38 |
39 | abcd := []int {1}
40 | abcd = append(abcd[:0], abcd[1:]...)
41 | fmt.Println(abcd)
42 |
43 | var ttttt = big.NewFloat(0)
44 | var y = 0
45 | var err error
46 | if ttttt, y, err = ttttt.Parse("6194049F30F7200000", 16); err != nil {
47 | log.Fatal(err)
48 | } else {
49 | log.Println(ttttt.Mul(ttttt, big.NewFloat(math.Pow10(-18))).String())
50 | log.Println(y)
51 | }
52 |
53 | aa := 1
54 | bb := 2
55 | cc := 3
56 | switch {
57 | case aa == 1:
58 | fmt.Println("aa")
59 | fallthrough
60 | case bb == 2:
61 | fmt.Println("bb")
62 | fallthrough
63 | case cc == 3:
64 | fmt.Println("cc")
65 | default:
66 | fmt.Println("dd")
67 | }
68 |
69 | fmt.Println(strings.Split("abcd", "."))
70 | }
71 |
--------------------------------------------------------------------------------
/tests/logger_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "utils"
5 | "errors"
6 | "testing"
7 | )
8 |
9 | func TestLogger(t *testing.T) {
10 | utils.LogMsgEx(utils.ERROR, "测试1:abcd", nil)
11 | a := 2000
12 | utils.LogMsgEx(utils.WARNING, "测试2:%d", a)
13 |
14 | utils.LogIdxEx(utils.INFO, 0, nil)
15 |
16 | err := errors.New("error test")
17 | utils.LogIdxEx(utils.DEBUG, 0, err)
18 | }
--------------------------------------------------------------------------------
/tests/redis_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "testing"
5 | "github.com/go-redis/redis"
6 | "databases"
7 | "log"
8 | "time"
9 | )
10 |
11 | func TestRedis(t *testing.T) {
12 | var cli redis.Cmdable
13 | var err error
14 | if cli, err = databases.ConnectRedis(); err != nil {
15 | log.Fatal(err)
16 | }
17 | err = cli.Set("abcd", "helloWorld", 20 * time.Second).Err()
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 | }
22 |
23 | func TestPubSub(t *testing.T) {
24 | var cli redis.Cmdable
25 | var err error
26 | if cli, err = databases.ConnectRedis(); err != nil {
27 | log.Fatal(err)
28 | }
29 | }
--------------------------------------------------------------------------------
/tests/reflect_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "testing"
5 | "fmt"
6 | "reflect"
7 | )
8 |
9 | func TestUseNameGetValue(t *testing.T) {
10 | fmt.Println(reflect.ValueOf("RFC3339").String())
11 | }
--------------------------------------------------------------------------------
/tests/regexp_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "testing"
5 | "regexp"
6 | "fmt"
7 | )
8 |
9 | func TestRegExp(t *testing.T) {
10 | re := regexp.MustCompile("^/api/withdraw/([A-Z]{3,})/process$")
11 | fmt.Println(re.FindStringSubmatch("/api/withdraw/ETH/process"))
12 | }
13 |
--------------------------------------------------------------------------------
/tests/rpc/btcrpc_test.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "testing"
5 | "fmt"
6 | "rpcs"
7 | )
8 |
9 | func TestBtcGetCurrentHeight(t *testing.T) {
10 | fmt.Println(rpcs.GetRPC("BTC").GetCurrentHeight())
11 | }
12 |
13 | func TestBtcGetTransactions(t *testing.T) {
14 | fmt.Println(rpcs.GetRPC("BTC").GetTransactions(1))
15 | }
16 |
17 | func TestBtcGetTxExistsHeight(t *testing.T) {
18 | fmt.Println(rpcs.GetRPC("BTC").GetTxExistsHeight(
19 | "3334ed8aa4a1c215f7c3ce6445dad14b2cd66cd004a1df39c9d6b6aad68d8edd"))
20 | }
21 |
22 | func TestGetNewAddress(t *testing.T) {
23 | fmt.Println(rpcs.GetRPC("BTC").GetNewAddress())
24 | }
25 |
26 | func TestBtcGetBalance(t *testing.T) {
27 | fmt.Println(rpcs.GetRPC("BTC").GetBalance(""))
28 | }
29 |
30 | func TestBtcGetDepositAmount(t *testing.T) {
31 | fmt.Println(rpcs.GetRPC("BTC").GetDepositAmount())
32 | }
--------------------------------------------------------------------------------
/tests/rpc/ethrpc_test.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "log"
5 | "rpcs"
6 | "testing"
7 | "fmt"
8 | )
9 |
10 | func TestGetTransactions(t *testing.T) {
11 | log.SetFlags(log.Lshortfile)
12 |
13 | //Test RPC
14 | fmt.Println(rpcs.GetRPC("ETH").GetTransactions(0))
15 | }
16 |
17 | func TestSendTransaction(t *testing.T) {
18 | var err error
19 | var txHash string
20 | if txHash, err = rpcs.GetRPC("ETH").SendFrom(
21 | "0x47e8e8e49a8c1c308e84439f2c55ef18710f5ed6",
22 | 10.23456); err != nil {
23 | log.Fatal(err)
24 | }
25 | log.Println(txHash)
26 | }
27 |
28 | func TestNewAddress(t *testing.T) {
29 | var addr string
30 | var err error
31 | if addr, err = rpcs.GetRPC("ETH").GetNewAddress(); err != nil {
32 | log.Fatal(err)
33 | } else {
34 | log.Println(addr)
35 | }
36 | }
37 |
38 | func TestGetTxExistsHeight(t *testing.T) {
39 | fmt.Println(rpcs.GetRPC("ETH").GetTxExistsHeight("abcd"))
40 | }
--------------------------------------------------------------------------------
/tests/rpc/rpcs_test.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "log"
5 | "fmt"
6 | "rpcs"
7 | "testing"
8 | )
9 |
10 | func TestRPC(t *testing.T) {
11 | log.SetFlags(log.Lshortfile)
12 |
13 | fmt.Println(rpcs.GetRPC("ETH").GetCurrentHeight())
14 | }
--------------------------------------------------------------------------------
/tests/status_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "fmt"
5 | "utils"
6 | "log"
7 | "testing"
8 | )
9 |
10 | type TestObs struct {
11 | }
12 |
13 | func (o *TestObs) BeforeTurn(s *utils.Status, tgtStt int) {
14 | log.Printf("Before turn: %d, to %d\n", s.Current(), tgtStt)
15 | }
16 |
17 | func (o *TestObs) AfterTurn(s *utils.Status, srcStt int) {
18 | log.Printf("After turn: %d, from %d\n", s.Current(), srcStt)
19 | }
20 |
21 | func TestStatus(t *testing.T) {
22 | log.SetFlags(log.Lshortfile)
23 |
24 | //Test status and observer
25 | var err error
26 | fmt.Println()
27 | o := TestObs {}
28 | const (
29 | NONE = iota
30 | INIT
31 | START
32 | UNEXISTS
33 | )
34 | a := utils.Status {
35 | AllStatus: []int { NONE, INIT, START },
36 | }
37 | a.RegAsObs(&o)
38 | log.Println(a.Current())
39 |
40 | a.TurnTo(START)
41 | log.Println(a.Current())
42 |
43 | if _, err = a.TurnTo(UNEXISTS); err != nil {
44 | log.Println(err)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/svc/collectService_test.go:
--------------------------------------------------------------------------------
1 | package svc
2 |
3 | import (
4 | "services"
5 | "time"
6 | "testing"
7 | )
8 |
9 | func TestCollectService(t *testing.T) {
10 | services.GetCollectService().Start()
11 | time.Sleep(20 * time.Second)
12 | }
13 |
--------------------------------------------------------------------------------
/tests/svc/depositService_test.go:
--------------------------------------------------------------------------------
1 | package svc
2 |
3 | import (
4 | "services"
5 | "log"
6 | "time"
7 | "testing"
8 | )
9 |
10 | func TestDepositService(t *testing.T) {
11 | log.SetFlags(log.Lshortfile)
12 |
13 | //Test service
14 | depositService := services.GetDepositService()
15 | notifyService := services.GetStableService()
16 | depositService.Init()
17 | notifyService.Init()
18 |
19 | depositService.Start()
20 | notifyService.Start()
21 |
22 | time.Sleep(20 * time.Second)
23 |
24 | depositService.Stop()
25 | notifyService.Stop()
26 | for !depositService.IsDestroy() && !notifyService.IsDestroy() {}
27 | }
28 |
--------------------------------------------------------------------------------
/wallet.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "utils"
5 | "services"
6 | "unsafe"
7 | "apis"
8 | "log"
9 | "fmt"
10 | "net"
11 | "os"
12 | "os/signal"
13 | "strconv"
14 | "net/http"
15 | )
16 |
17 | func initServices(svcs []*services.BaseService) {
18 | for _, svc := range svcs {
19 | svc.Init()
20 | }
21 | }
22 |
23 | func runServices() {
24 | for _, svc := range services.GetInitedServices() {
25 | svc.Start()
26 | }
27 | }
28 |
29 | func stopServices() {
30 | for _, svc := range services.GetInitedServices() {
31 | svc.Stop()
32 | }
33 | }
34 |
35 | func safeExit() {
36 | c := make(chan os.Signal, 1)
37 | signal.Notify(c, os.Interrupt, os.Kill)
38 |
39 | <- c
40 | utils.LogMsgEx(utils.WARNING, "正在安全退出", nil)
41 |
42 | svcStts := make(map[string]int)
43 | for notYet := true; notYet; {
44 | for _, svc := range services.GetInitedServices() {
45 | if !svc.IsDestroy() {
46 | if stt, ok := svcStts[svc.Name()]; ok {
47 | if stt == svc.CurrentStatus() { continue }
48 | }
49 | utils.LogMsgEx(utils.WARNING, "%s服务还未安全退出,处于状态:%s",
50 | svc.Name(), services.ServiceStatus[svc.CurrentStatus()])
51 | svcStts[svc.Name()] = svc.CurrentStatus()
52 | notYet = true
53 | break
54 | } else {
55 | notYet = false
56 | }
57 | }
58 | }
59 |
60 | // 关闭日志的存储介质
61 | utils.CloseAllLogStorage()
62 | log.Println("退出完毕")
63 | }
64 |
65 | var depositServices = []*services.BaseService {
66 | (*services.BaseService)(unsafe.Pointer(services.GetDepositService())),
67 | (*services.BaseService)(unsafe.Pointer(services.GetStableService())),
68 | }
69 |
70 | var collectServices = []*services.BaseService {
71 | (*services.BaseService)(unsafe.Pointer(services.GetCollectService())),
72 | }
73 |
74 | var withdrawServices = []*services.BaseService {
75 | (*services.BaseService)(unsafe.Pointer(services.GetHeightService())),
76 | (*services.BaseService)(unsafe.Pointer(services.GetWithdrawService())),
77 | (*services.BaseService)(unsafe.Pointer(services.GetStableService())),
78 | }
79 |
80 | func main() {
81 | bsSet := utils.GetConfig().GetBaseSettings()
82 | sbSet := utils.GetConfig().GetSubsSettings()
83 |
84 | // 服务的初始化和启动
85 | if len(bsSet.Services) == 0 {
86 | initServices(depositServices)
87 | initServices(collectServices)
88 | initServices(withdrawServices)
89 | } else {
90 | for _, svc := range bsSet.Services {
91 | switch svc {
92 | case "deposit": initServices(depositServices)
93 | case "collect": initServices(collectServices)
94 | case "withdraw": initServices(withdrawServices)
95 | }
96 | }
97 | }
98 | runServices()
99 |
100 | // API的初始化和监听
101 | if sbSet.APIs.RPC.Active {
102 | go func() {
103 | port := 0
104 | strPort := os.Getenv("PORT")
105 | if len(strPort) != 0 {
106 | port, _ = strconv.Atoi(os.Getenv("PORT"))
107 | } else {
108 | port = sbSet.APIs.Socket.Port
109 | }
110 | utils.LogMsgEx(utils.INFO, "HTTP服务器监听于:%d", port)
111 | http.HandleFunc("/", apis.HttpHandler)
112 | log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
113 |
114 | utils.LogMsgEx(utils.WARNING, "正在安全退出", nil)
115 | stopServices()
116 | }()
117 | }
118 | if sbSet.APIs.Socket.Active {
119 | go func() {
120 | utils.LogMsgEx(utils.INFO, "SOCKET服务器监听于:%d", sbSet.APIs.Socket.Port)
121 | url := fmt.Sprintf("localhost:%d", sbSet.APIs.Socket.Port)
122 | var socket net.Listener
123 | var err error
124 | if socket, err = net.Listen("tcp", url); err != nil {
125 | log.Fatal(err)
126 | }
127 | defer socket.Close()
128 | for {
129 | var conn net.Conn
130 | if conn, err = socket.Accept(); err != nil {
131 | utils.LogMsgEx(utils.WARNING, "接收SOCKET信息错误:%v", err)
132 | continue
133 | }
134 | utils.LogMsgEx(utils.INFO, "接收来自:%s的消息", conn.RemoteAddr().String())
135 | apis.SocketHandler(conn)
136 | }
137 | utils.LogMsgEx(utils.WARNING, "正在安全退出", nil)
138 | stopServices()
139 | }()
140 | }
141 |
142 | // 处理服务安全退出
143 | safeExit()
144 | }
--------------------------------------------------------------------------------