diff --git a/src/jobs/jobs.js b/src/jobs/jobs.js index 20ae3bd..a91a233 100644 --- a/src/jobs/jobs.js +++ b/src/jobs/jobs.js @@ -21,6 +21,7 @@ const Action = require('../modules/action/action.model'); const system = require('../modules/system/system.model'); const { createAction } = require('../modules/action/action.handler'); const { createNewFund } = require('../modules/fund/fund.handler'); +const exec = require('child_process').exec; const tokenReleaseRate = 0.02; async function awardMine(user, fund) { @@ -148,10 +149,17 @@ async function confirm (job = mock, done = mock) { }); return typeof done === 'function' && done('矿晶确认完成'); } + +async function mineJob (job = mock, done = mock) { + exec('node ./src/jobs/mine.js'); + mylog.info('挖矿程序执行完毕'); + typeof done === 'function' && done('挖矿程序执行完毕'); +} + const jobs = [ // { // name: 'mine', - // processor: mine, + // processor: mineJob, // type: 'every', // slot: '0 0 3 * * *', // priority: 'highest', @@ -161,6 +169,18 @@ const jobs = [ // skipImmediate: true, // timezone: "ETC/GMT+8" // }, + { + name: 'mine', + processor: mineJob, + type: 'every', + slot: '0 0 2 * * *', + priority: 'highest', + unique: { + id: 'mine' + }, + skipImmediate: true, + timezone: "ETC/GMT+8" + }, { name: 'confirm', processor: confirm, diff --git a/src/jobs/mine.js b/src/jobs/mine.js new file mode 100644 index 0000000..b55ed70 --- /dev/null +++ b/src/jobs/mine.js @@ -0,0 +1,201 @@ +'use strict'; +const bunyan = require('bunyan'); +const PrettyStream = require('bunyan-pretty-colors'); +const path = require('path'); +const fs = require('fs'); + +const prettyStdOut = new PrettyStream(); +prettyStdOut.pipe(process.stdout); + +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('./src/modules/user/user.model'); +const Fund = require('./src/modules/fund/fund.model'); +const system = require('./src/modules/system/system.model'); +const { createAction } = require('./src/modules/action/action.handler'); + +const tokenReleaseRate = 0.02; +const inviteReward = 2.5; + +async function awardMine(user, fund) { + if (!user || !fund || fund.fundList.length === 0) return 0; + let sum = 0; + for (let i = 0, len = fund.fundList.length; i < len; ++i) { + const released = fund.releasedAmout || 0; + if (released === fund.releasedAmout) { + console.log('矿晶失效,开始标记'); + await Fund.findByIdAndUpdate(fund.id, { + $set: { + [`fundList.${i}.status`]: 2 //标记失效 + } + }); + return 0; + } + sum += +fund.fundList[i].amount * tokenReleaseRate; + } + return sum.toFixed(2); +} +function awardInvite(user, fund) { + const follerNum = user.group.length || 0; + const hasAwardFoller = fund.option && fund.option.usedGroupReward / 10 || 0; // 求增量: 总额 - 已领取总额(fundSum) + const newFoller = follerNum - hasAwardFoller; + if (newFoller < 0) return 0; + // 挖矿奖励 = 充值金额奖励 + 社区奖励 + return (newFoller * inviteReward).toFixed(2); +} +// fund.option.usedGroupReward : 已领取的社群奖励总额 +const doReward = async (user, fund) => { + // 1.对于充值的fundList的每一项,按照2%进行发放,累加到asset.vic,添加action,类型为1 + // 2.对于社团成员进行人头数目,按照10vic/人进行奖励,累加到asset.vic,添加action,类型为2 + if (!user || !fund) return 0; + const rewardMine = await awardMine(user, fund); + // const rewardInvite = awardInvite(user, fund); + const rewardInvite = 0; + const reward = +rewardMine + rewardInvite; + console.log('挖矿奖励: ', rewardMine); + console.log('社群奖励: ', rewardInvite); + reward && await Fund.findByIdAndUpdate(fund.id, { + $inc: { + 'asset.vic': reward, + 'asset.usdt': rewardInvite, // 用来记录所有社群奖励总额 + }, + $set: { + 'option.newFoller': rewardInvite / 5, + 'option.usedGroupReward': rewardInvite, // 本次的奖励值累加到已领取团队奖励总和内 + 'lastMine': new Date().toISOString() + } + }).then(() => { + rewardMine > 0 && createAction(1, { + userId: user.id, + tokenType: 'vic', + amount: rewardMine, + remain: fund.asset + }).save(); + rewardInvite > 0 && createAction(2, { + userId: user.id, + tokenType: 'vic', + amount: rewardInvite, + remain: fund.asset + }).save(); + }); +}; +const mock = { + touch: () => {console.log('-----------------\n');}, + done: () => {console.log('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().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(); + console.log('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; + } + 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 () => { + 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 + }); + mine(); +}; +start();