├── .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 | } --------------------------------------------------------------------------------