aozhiwei 59d2beb06e 1
2022-02-17 12:18:50 +08:00

459 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;