'use strict'; const bunyan = require('bunyan'); const PrettyStream = require('bunyan-pretty-colors'); const path = require('path'); const fs = require('fs'); const prettyStdOut = new PrettyStream(); if (!fs.existsSync('minelog')) { fs.mkdirSync('minelog'); } const logger = function (option) { option = option || {}; return bunyan.createLogger({ name: "log", src: false, streams: [ { level: 'info', stream: prettyStdOut }, { level: 'info', type: 'rotating-file', path: path.join(option.root, option.file || 'info.log'), period: '1d', // daily rotation count: 365 // keep 30 days } ] }); }; const filename = 'minelog' + new Date().getFullYear() + new Date().getMonth() + new Date().getDate(); const mylog = logger(({ root: 'minelog', file: filename })); const mongoose = require('mongoose'); const User = require('../modules/user/user.model'); const Fund = require('../modules/fund/fund.model'); const system = require('../modules/system/system.model'); const { createAction } = require('../modules/action/action.handler'); const revenueLadderSheet = [ {amount: 10000, rate: 0.001}, {amount: 40000, rate: 0.003}, {amount: 50000, rate: 0.006}, {amount: 100000, rate: 0.01}, {amount: 300000, rate: 0.015}, {amount: 500000, rate: 0.018}, {amount: NaN, rate: 0.02} ]; const probe = { currentIndex: 0 }; function awardMine(fund) { if (!fund || fund.vicFundSum === 0) return 0; let rest = +fund.vicFundSum; let revenue = 0; for (let level of revenueLadderSheet) { if (rest - level.amount > 0) { revenue += level.amount * level.rate; rest -= level.amount; } else { revenue += rest * level.rate; break; } } return revenue; } // fund.option.usedGroupReward : 已领取的社群奖励总额 const doReward = async (user, fund) => { if (!user || !fund) return 0; const rewardMine = awardMine(fund); if (rewardMine === 0) return 0; mylog.info('挖矿奖励: ', rewardMine); rewardMine && await Fund.findByIdAndUpdate(fund.id, { $inc: { vicFundSum: rewardMine, }, $set: { lastMine: new Date().toISOString() } }).then(async () => { rewardMine > 0 && createAction(1, { userId: user.id, tokenType: 'vic2vic', // 新模式标记 amount: rewardMine, remain: fund.asset }).save(); if (user.inviter) { const inviter = await Fund.findOne({ userId: user.inviter }); if (inviter && +inviter.vicFundSum >= 10000) { const rewardInviter = rewardMine * 0.1; Promise.all([ Fund.findOneAndUpdate({ userId: user.inviter }, { $inc: { vicFundSum: rewardInviter } }), createAction(4, { userId: user.inviter, tokenType: 'vic2vic', // 新模式标记 amount: rewardInviter, remain: fund.asset, detail: { level: 1, info: '1级奖励' } }).save() ]).catch((err) => mylog.error(err.errmsg)); } } }); }; const mock = { touch: () => { mylog.info('-----------------\n'); }, done: () => { mylog.info('done'); }, }; async function mine (job = mock, done = mock) { //todo: 程序内检查是否可以执行,保证不会启动时错误的自动执行一次 mylog.info('[Schedule Job] 挖矿程序 开始定时计算作业...'); let currentIndex = await system.findOne({ key: 'userIndex4job' }).exec(); currentIndex = currentIndex.value; if (typeof currentIndex !== 'number') { currentIndex = 0; await new system({ key: 'userIndex4job', value: currentIndex }).save(); } let allFundNum = await Fund.countDocuments(); await system.updateOne({ key: 'restFundNum', }, { value: allFundNum }); // 批量更新,每次十个 for (; currentIndex < allFundNum;) { const userFundList = await Fund.find({ vicFundSum: { $gt: 0 } }).sort('_id').limit(10).skip(currentIndex); typeof job.touch === 'function' && job.touch(); if (!userFundList || !Array.isArray(userFundList)) return 0; for (let i = 0, len = userFundList.length; i < len; ++i) { const fund = userFundList[i]; // 一条数据库fund表记录 // if (fund.lastMine && new Date(fund.lastMine).getDate() === new Date().getDate()) { // mylog.info('已经计算过...跳过'); // continue; // } const user = await User.findById(fund.userId).exec(); mylog.info('No. ', currentIndex, ' - ', i); mylog.info('开始更新用户: ', user.id); await doReward(user, fund); mylog.info('用户: ', user.id, '更新完毕\n'); typeof job.touch === 'function' && job.touch(); await system.updateOne({ key: 'userIndex4job' }, { $set: { value: currentIndex } }); await system.updateOne({ key: 'restFundNum' }, { $inc: { value: -1 } }); } currentIndex += 10; probe.currentIndex = currentIndex; } mylog.info('收益计算分配完成'); await system.findOneAndUpdate({ key: 'userIndex4job' }, { $set: { value: 0 } }).exec(); await system.findOneAndUpdate({ key: 'mineCount', }, { $inc: { value: 1 } }, { upsert: true }); await system.findOneAndUpdate({ key: 'lastMine', }, { value: new Date().toISOString() }, { upsert: true }); typeof done === 'function' && done('收益计算分配完成'); process.exit(); } const start = async (job, done) => { const SysConfig = require('../../configSec'); mongoose.set('useCreateIndex', true); const { DB_USER_NAME, DB_PASSWD, DB_HOST, DB_PORT, DB_NAME } = SysConfig; const db = mongoose.connection; db.on('error', mylog.error); db.once('open', function() { mylog.info('数据库连接成功...'); }); mylog.info('开始连接数据库...'); await mongoose.connect(`mongodb://${DB_USER_NAME}:${DB_PASSWD}@${DB_HOST}:${DB_PORT}/${DB_NAME}`, { useNewUrlParser: true, bufferMaxEntries: 0, autoReconnect: true, useFindAndModify: false, useUnifiedTopology: true }); const beforeExit = async (msg) => { mylog.info('意外退出', msg); mylog.info('进行到第'+ probe.currentIndex + '个用户'); process.exit(0); }; process.on('SIGINT', beforeExit); process.on('uncaughtException', beforeExit); process.on('unhandledRejection', beforeExit); typeof process.send === 'function' && process.send('子进程开始执行mine'); mine(job, done); typeof process.send === 'function' && process.send('子进程mine执行完毕'); }; start();