const util = require('util'); const utils = require('./utils'); const db = require("./db"); const dbhelper = require("./dbhelper"); const bchelper = require("./bchelper"); const log = require("./log"); const dblog = require("./dblog"); const bc = require('./blockchain'); const metamgr = require('./metamgr'); const C = require('./C'); /* 1、数据库被认为是可靠如果数据库操作失败则应该挂起 2、区块链查询操作没啥成本可以多次调用,写入操作需谨慎!!! t_box_order表结构: CREATE TABLE `t_box_order` ( `idx` bigint NOT NULL AUTO_INCREMENT COMMENT '自增id', `game_id` int(11) NOT NULL DEFAULT '0' COMMENT 'game id', `batch_idx` int(11) NOT NULL DEFAULT '0' COMMENT '批次号idx', `token_id` varchar(60) NOT NULL DEFAULT '' COMMENT 'token_id', `order_id` varchar(60) NOT NULL DEFAULT '' COMMENT '订单id', `type` varchar(60) NOT NULL DEFAULT '' COMMENT 'type', `buyer_address` varchar(60) NOT NULL DEFAULT '' COMMENT '购买者', `price` varchar(60) NOT NULL DEFAULT '' COMMENT 'price', `payment_token_address` varchar(60) NOT NULL DEFAULT '' COMMENT '货币地址', `nonce` varchar(60) NOT NULL DEFAULT '' COMMENT 'nonce', `signature` varchar(255) NOT NULL DEFAULT '' COMMENT '签名', `item_id` int(11) NOT NULL DEFAULT '0' COMMENT '道具id', `token_type` int(11) NOT NULL DEFAULT '0' COMMENT 'nft类型 0:英雄 1:枪支 2:芯片', `bc_paid` int(11) NOT NULL DEFAULT '0' COMMENT '是否已支付', `bc_pay_count` int(11) NOT NULL DEFAULT '0' COMMENT '支付次数', `bc_pay_time` int(11) NOT NULL DEFAULT '0' COMMENT '本节点最后一次支付时间', `bc_pay_prepare_block_number` varchar(60) NOT NULL DEFAULT '' COMMENT '支付准备前块id', `bc_pay_success_block_number` varchar(60) NOT NULL DEFAULT '' COMMENT '支付成功块id', `bc_pay_confirm_time` int(11) NOT NULL DEFAULT '0' COMMENT '支付成功被确认时间', `bc_minted` int(11) NOT NULL DEFAULT '0' COMMENT 'nft是否已创建', `bc_mint_count` int(11) NOT NULL DEFAULT '0' COMMENT '生成nft次数', `bc_mint_time` int(11) NOT NULL DEFAULT '0' COMMENT '最后一次生成nft时间', `bc_mint_prepare_block_number` varchar(60) NOT NULL DEFAULT '' COMMENT 'nft生成准备前块id', `bc_mint_success_block_number` varchar(60) NOT NULL DEFAULT '' COMMENT 'nft生成成功块id', `bc_mint_confirm_time` int(11) NOT NULL DEFAULT '0' COMMENT 'nft生成被确认时间', `suspend` int(11) NOT NULL DEFAULT '0' COMMENT '挂起', `suspend_reason` mediumblob COMMENT '挂起原因', `done` int(11) NOT NULL DEFAULT '0' COMMENT '是否已完成', `createtime` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', `modifytime` int(11) NOT NULL DEFAULT '0' COMMENT '修改时间', PRIMARY KEY (`idx`), UNIQUE KEY `order_id` (`order_id`), UNIQUE KEY `signature` (`signature`), KEY `batch_idx` (`batch_idx`), KEY `bc_paid` (`bc_paid`), KEY `bc_minted` (`bc_minted`), KEY `done` (`done`) ) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; */ class BoxOrder { constructor(orderDb, isNewOrder) { this.isNewOrder = isNewOrder; this.orderDb = orderDb; } init () { if (this.isNewOrder) { setTimeout(this.start.bind(this), utils.randRange(100, 1000)); } else { setTimeout(this.start.bind(this), utils.randRange(1000, 3000)); } } async start() { if (!this.getMintMethod()) { await this.suspend('start', 'mint method error'); return; } //支付 await this.payment(); //创建nft await this.mint(); } async payment() { if (!this.isPaid()) { const getPrepareBlockNumber = () => { return this.orderDb['bc_pay_prepare_block_number']; }; const setPrepareBlockNumber = async (logClass, blockNumber) => { await this.updateDbMustBeSuccess( logClass, [ ['bc_pay_prepare_block_number', blockNumber], ] ); }; const getSuccessBlockNumber = () => { return this.orderDb['bc_pay_success_block_number']; }; const setSuccessBlockNumber = async (logClass, blockNumber) => { await this.updateDbMustBeSuccess( logClass, [ ['bc_pay_success_block_number', blockNumber], ] ); }; const getSyncCount = () => { return this.orderDb['bc_pay_count']; }; const eventFilter = { boxId: this.getOrderId() }; const methodArgs = [ this.orderDb['order_id'], this.orderDb['type'], this.orderDb['buyer_address'], this.orderDb['price'], this.orderDb['payment_token_address'], this.orderDb['nonce'], this.orderDb['signature'] ]; const onSyncSuccess = async (logClass, event) => { const nowTime = utils.getUtcTime(); const blockNumber = event['blockNumber']; await this.updateDbMustBeSuccess( logClass, [ ['bc_pay_count', this.orderDb['bc_pay_count'] + 1], ['bc_pay_time', nowTime], ['bc_pay_success_block_number', blockNumber], ] ); }; const onConfirmed = async (logClass) => { const nowTime = utils.getUtcTime(); await this.updateDbMustBeSuccess( logClass, [ ['bc_paid', 1], ['bc_pay_confirm_time', nowTime] ] ); }; const exec = new bchelper.ContractExecutor(); exec.instanceName = 'mallInstance'; exec.suspend = this.suspend.bind(this); exec.getLogKey = this.getOrderId.bind(this); exec.getLogData = this.getOrderDb.bind(this); exec.getPrepareBlockNumber = getPrepareBlockNumber; exec.setPrepareBlockNumber = setPrepareBlockNumber; exec.getSuccessBlockNumber = getSuccessBlockNumber; exec.setSuccessBlockNumber = setSuccessBlockNumber; exec.getSyncCount = getSyncCount; exec.searchLogClass = 'order.search'; exec.eventName = 'BEBoxPaid'; exec.eventFilter = eventFilter; exec.syncLogClass = 'order.sync'; exec.methodName = 'payForBoxWithSignature'; exec.methodArgs = methodArgs; exec.syncDbLogClass = 'order.sync'; exec.onSyncSuccess = onSyncSuccess; exec.onConfirmed = onConfirmed; exec.init(); const ok = await exec.execute(); if (!ok) { const logClass = 'payment'; await this.suspend(logClass, 'payment error'); } } } async mint() { if (!this.isMinted()) { const getPrepareBlockNumber = () => { return this.orderDb['bc_mint_prepare_block_number']; }; const setPrepareBlockNumber = async (logClass, blockNumber) => { await this.updateDbMustBeSuccess( logClass, [ ['bc_mint_prepare_block_number', blockNumber], ] ); }; const getSuccessBlockNumber = () => { return this.orderDb['bc_mint_success_block_number']; }; const setSuccessBlockNumber = async (logClass, blockNumber) => { await this.updateDbMustBeSuccess( logClass, [ ['bc_mint_success_block_number', blockNumber], ] ); }; const getSyncCount = () => { return this.orderDb['bc_mint_count']; }; const eventFilter = { tokenId: this.getTokenId() }; const methodArgs = [ this.orderDb['buyer_address'], this.getTokenId() ]; const onSyncSuccess = async (logClass, event) => { const nowTime = utils.getUtcTime(); const blockNumber = event['blockNumber']; await this.updateDbMustBeSuccess( logClass, [ ['bc_mint_count', this.orderDb['bc_mint_count'] + 1], ['bc_mint_time', nowTime], ['bc_mint_success_block_number', blockNumber], ] ); }; const onConfirmed = async (logClass) => { const nowTime = utils.getUtcTime(); await this.updateDbMustBeSuccess( logClass, [ ['bc_minted', 1], ['bc_mint_confirm_time', nowTime] ] ); }; const exec = new bchelper.ContractExecutor(); exec.instanceName = 'factoryInstance'; exec.suspend = this.suspend.bind(this); exec.getLogKey = this.getOrderId.bind(this); exec.getLogData = this.getOrderDb.bind(this); exec.getPrepareBlockNumber = getPrepareBlockNumber; exec.setPrepareBlockNumber = setPrepareBlockNumber; exec.getSuccessBlockNumber = getSuccessBlockNumber; exec.setSuccessBlockNumber = setSuccessBlockNumber; exec.getSyncCount = getSyncCount; exec.searchLogClass = 'mint.search'; exec.eventName = 'TokenMinted'; exec.eventFilter = eventFilter; exec.syncLogClass = 'mint.sync'; exec.methodName = this.getMintMethod(); exec.methodArgs = methodArgs; exec.syncDbLogClass = 'mint.sync'; exec.onSyncSuccess = onSyncSuccess; exec.onConfirmed = onConfirmed; exec.init(); const ok = await exec.execute(); if (!ok) { const logClass = 'mint'; await this.suspend(logClass, 'mint error'); } } const logClass = 'mint'; await this.dbMint(); await this.updateDbMustBeSuccess( logClass, [ ['done', 1] ]); utils.emitEvent(C.REMOVE_PENDING_ORDER_EVENT, this.getOrderId()); } async dbMint() { const logClass = 'dbMint'; { const {err, row} = dbhelper.ormSelectOne( 't_nft', [ ['token_id', this.getTokenId()] ]); if (err) { await this.suspend(logClass, err); return; } if (row) { if (row['owner_address'] != this.orderDb['buyer_address']) { await this.suspend(logClass, 'owner_address error'); return; } return; } } { const nowTime = utils.getUtcTime(); const fieldList = [ ['token_id', this.getTokenId()], ['item_id', this.orderDb['item_id']], ['game_id', this.orderDb['game_id']], ['owner_id', ''], ['owner_address', this.orderDb['buyer_address']], ['owner_name', ''], ['createtime', nowTime], ['modifytime', nowTime], ]; const err = await dbhelper.insert( 't_nft', fieldList); if (err) { log.error(util.format('%s insert nft table orderDb:%s fieldList:%s err:%s', logClass, utils.jsonEncode(this.orderDb), utils.jsonEncode(fieldList), err ) ); await this.suspend(logClass, err); } else { log.info(util.format('%s insert nft table orderDb:%s', logClass, utils.jsonEncode(this.orderDb), utils.jsonEncode(fieldList) ) ); } } } async updateDbMustBeSuccess(logClass, fieldList) { const err = await dbhelper.update( 't_box_order', [ ['order_id', this.getOrderId()] ], fieldList); if (err) { log.error(util.format('%s updateDbMustBeSuccess orderDb:%s fieldList:%s err:%s', logClass, utils.jsonEncode(this.orderDb), utils.jsonEncode(fieldList), err ) ); await this.suspend(logClass + ' updateDbMustBeSuccess ', err); } else { fieldList.forEach((val) => { const name = val[0]; const value = val[1]; this.orderDb[name] = value; }); log.info(util.format('%s updateDbMustBeSuccess orderDb:%s', logClass, utils.jsonEncode(this.orderDb), utils.jsonEncode(fieldList) ) ); } return err; } async suspend(logClass, reason) { /* 挂起等待人工处理 为啥不抛出异常是因为: 如果在调用接口的外部try catch的话,那么挂起操作产生的异常会被吞噬,这样非常危险!!! 挂起的操作现在为无限等待不受外部try catch影响。 */ try { log.warning(util.format('%s suspend orderDb:%s reason:%s', logClass, utils.jsonEncode(this.orderDb), reason ) ); await dbhelper.update( 't_box_order', [ ['order_id', this.getOrderId()] ], [ ['suspend', 1], ['suspend_reason', '' + reason], ]); this.orderDb['suspend'] = 1; this.orderDb['suspend_reason'] = '' + reason; await dblog.addLog( 'order.suspend', '', this.getOrderId(), utils.jsonEncode(this.getOrderDb()), '' + reason, logClass ); } catch (err) { log.error(util.format('%s suspend orderDb:%s err:%s', logClass, utils.jsonEncode(this.orderDb), err ) ); } while (true) { await utils.sleep(1000 * 3600 * 24); } } getOrderDb() { return this.orderDb; } getOrderId() { return this.orderDb['order_id']; } getTokenId() { return this.orderDb['token_id']; } isPaid() { return this.orderDb['bc_paid'] != 0; } isMinted() { return this.orderDb['bc_minted'] != 0; } getMintMethod() { switch (this.orderDb['token_type']) { case C.TOKEN_TYPE_HERO: { return 'mintHeroTo'; } break; case C.TOKEN_TYPE_EQUIP: { return 'mintEquipTo'; } break; case C.TOKEN_TYPE_CHIP: { return 'mintChipTo'; } break; default: { return ''; break; } } } }; exports.BoxOrder = BoxOrder;