459 lines
14 KiB
JavaScript
459 lines
14 KiB
JavaScript
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;
|