'use strict'; /** * 为何要把分属于不同业务模块的job集中到此处? * 如果后续要把任务执行与web服务器分离,就会很方便,就算不分离,也没多大坏处。 * 任务包括: * transaction : { * 1. 每小时去清理指定startTime的地产,将 * { * status: -> 1, * price: -> price * (1 + profit), * currentOwner: -> '' * } * 2. 将currentOwner对应的用户的asset.log加上a * } */ const mongoose = require('mongoose'); const User = require('../modules/user/user.model'); const Fund = require('../modules/fund/fund.model'); 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 tokenReleaseRate = 0.02; 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) { await Fund.findByIdAndUpdate(fund.id, { $set: { [`fundList.${i}.status`]: 2 //标记失效 } }); return 0; } sum += +fund.fundList[i].amount * tokenReleaseRate; } return sum; } function awardInvite(user, fund, allInviteReward) { const rewardInvite = allInviteReward - (fund.option && fund.option.usedGroupReward || 0); // 求增量: 总额 - 已领取总额(fundSum) // 挖矿奖励 = 充值金额奖励 + 社区奖励 return rewardInvite; } // 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 allInviteReward = (+user.group.length || 0) * 5; const rewardMine = await awardMine(user, fund); const rewardInvite = awardInvite(user, fund, allInviteReward); const reward = rewardMine + rewardInvite; reward && await Fund.findByIdAndUpdate(fund.id, { $inc: { 'asset.vic': reward, }, $set: { 'option.usedGroupReward': rewardInvite, // 本次的奖励值累加到已领取团队奖励总和内 'asset.usdt': allInviteReward, // 用来记录所有社群奖励总额 '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('touch');}, 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(); } // 批量更新,每次十个 const userFundList = await Fund.find().sort('_id').limit(10).skip(currentIndex); typeof job.touch === 'function' && job.touch(); mylog.info('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('开始更新用户: ', user.id); await doReward(user, fund); mylog.info('用户: ', user.id, '更新完毕'); typeof job.touch === 'function' && job.touch(); await system.updateOne({ key: 'userIndex4job' }, { $set: { value: currentIndex } }); } mylog.info('收益计算分配完成'); typeof done === 'function' && done('收益计算分配完成'); } async function confirm (job = mock, done = mock) { mylog.info('[Schedule Job] 矿晶确认程序开始执行......'); const unconfirmed = await Fund.find({ 'fundList.status': 0 }); mylog.info('共有' + unconfirmed.length + '用户需要确认'); unconfirmed.forEach(async (fund) => { const len = fund.fundList.length || 0; let acc = 0; for (let i = 0; i < len; ++i) { const fundItem = fund.fundList[i]; if (fundItem && fundItem.status === 0) { ++acc; await Fund.findByIdAndUpdate(fund.id, { $set: { [`fundList.${i}.status`]: 1 // 确认激活 } }); } } mylog.info(`用户${fund.userId} 共更新${acc}条`); job.touch(); }); return typeof done === 'function' && done('矿晶确认完成'); } const jobs = [ { name: 'mine', processor: mine, type: 'every', slot: '0 0 3 * * *', priority: 'highest', unique: { id: 'mine' }, skipImmediate: true, timezone: "ETC/GMT+8" }, { name: 'confirm', processor: confirm, type: 'every', slot: '0 0/10 * * * *', // 59 */1 * * * priority: 'highest', unique: { id: 'confirm' }, // skipImmediate: true, timezone: "ETC/GMT+8" } ]; function mountJobs (Job) { jobs.map(jobConfig => { const { name, processor, type, slot, priority, unique, skipImmediate, timezone } = jobConfig; Job.define(name, { timezone, skipImmediate, priority, unique: { 'data.type': 'active', 'data.userId': unique.id } }, processor); Job[type](slot, name); }); } module.exports = Job => mountJobs(Job);