From 54674dd06076faaca28c9736534b4bfd5e19f408 Mon Sep 17 00:00:00 2001 From: chaosBreaking Date: Sat, 7 Sep 2019 12:01:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20fund=E6=A8=A1=E5=9E=8B=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=92=8C=E5=88=9B=E5=BB=BAapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/action/action.handler.js | 97 +++++++------------- src/modules/action/action.model.js | 16 ++-- src/modules/fund/fund.controller.js | 130 ++++++++++++++++++++++----- src/modules/fund/fund.handler.js | 43 +++++++++ src/modules/fund/fund.model.js | 67 ++++++++++++-- src/modules/fund/fund.service.js | 27 +++++- 6 files changed, 276 insertions(+), 104 deletions(-) create mode 100644 src/modules/fund/fund.handler.js diff --git a/src/modules/action/action.handler.js b/src/modules/action/action.handler.js index bc011ea..1ca2d30 100644 --- a/src/modules/action/action.handler.js +++ b/src/modules/action/action.handler.js @@ -2,102 +2,71 @@ const Action = require('./action.model'); /** - * [0] 充值LOG - * [1] 提现 - * [2] 购买地产 - * [3] 分红 - * [4] 场外买入 - * [5] 场外卖出 + * [0] 充值USDT + * [1] 挖矿收益 + * [2] 社区奖励 + * [3] 提币 */ function createAction (actCode, data) { switch (actCode) { case 0: return createDepositAction(data); - case 1: return createWithdrawAction(data); - case 2: return createPurchaseAction(data); - case 3: return createBonusAction(data); - case 4: return createOtcBuyAction(data); - case 5: return createOtcSellAction(data); + case 1: return createMineRewardAction(data); + case 2: return createGroupRewardAction(data); + case 3: return createWithdrawAction(data); } } function createDepositAction(data = {}) { - // 充值要记录: 发起人,充值目标地址,充值货币类型,充值金额 - // 只创建action并返回,不保存,保存交给具体的业务在事务内执行 - const { actor, amount, tokenType, toAddress, remain } = data; - if (!actor || !amount || !tokenType || !toAddress) return false; + const { userId, amount, tokenType, remain } = data; + if (!userId || !amount || !tokenType) return false; return new Action({ - actor, + userId, type: 0, op: 1, amount, remain, - detail: { toAddress, tokenType } + tokenType, }); } -function createWithdrawAction(data = {}) { - const { actor, amount, remain } = data; + +function createMineRewardAction(data = {}) { + // 挖矿并发放奖励 + const { userId, amount, tokenType, remain } = data; + if (!userId || !amount || !tokenType) return false; return new Action({ - actor, + userId, type: 1, - op: 0, + op: 1, amount, remain, - detail: { - } + tokenType, }); } -function createPurchaseAction(data = {}) { - const { actor, estate, amount, remain } = data; + +function createGroupRewardAction(data = {}) { + // 社群奖励 + const { userId, amount, remain, tokenType } = data; return new Action({ - actor, + userId, type: 2, - op: 0, + op: 1, amount, remain, - detail: { - ...estate - } + tokenType, }); } -function createBonusAction(data = {}) { - const { actor, estate, amount, remain } = data; + +function createWithdrawAction(data = {}) { + // 体现 vic or usdt + const { userId, amount, remain, tokenType } = data; return new Action({ - actor, + userId, type: 3, - op: 1, - amount, - remain, - detail: { - ...estate - } - }); -} -function createOtcBuyAction(data = {}) { - // 场外交易的actor是交易活动的发起者,也就是去买市场挂单的人。 - const { actor, otcOrder, adv, amount, remain } = data; - return new Action({ - actor, - type: 4, - op: 1, - amount, - remain, - detail: { - order: otcOrder, - adv - } - }); -} -function createOtcSellAction(data = {}) { - const { actor, otcOrder, amount, remain } = data; - return new Action({ - actor, - type: 5, op: 0, amount, remain, - detail: { - ...otcOrder - } + tokenType, + detail: {} }); } diff --git a/src/modules/action/action.model.js b/src/modules/action/action.model.js index b8f19fe..ee67106 100644 --- a/src/modules/action/action.model.js +++ b/src/modules/action/action.model.js @@ -1,24 +1,23 @@ 'use strict'; // 用户事务模型,记录用户的买入、获得收益、兑换等一系列财务交易操作。 /** - * [0] 充值LOG - * [1] LOG提现为USDT - * [2] 用户买入地产 - * [3] 地产收益分配 - * [4] 场外买入LOG - * [5] 场外卖出LOG + * [1] 挖矿收益 + * [2] 社区奖励 + * [3] 提币 */ const mongoose = require('mongoose'); const ActionSchema = new mongoose.Schema({ - actor: { + userId: { type: String, required: true, }, type: { type: String, - required: true + }, + tokenType: { + type: String }, amount: { type: String @@ -30,7 +29,6 @@ const ActionSchema = new mongoose.Schema({ op: { // 0支出 // 1收入 - // note: 这里的支出和收入是针对平台内的财产 也就是log而言的 type: Number, }, detail: { diff --git a/src/modules/fund/fund.controller.js b/src/modules/fund/fund.controller.js index 046210b..c162a97 100644 --- a/src/modules/fund/fund.controller.js +++ b/src/modules/fund/fund.controller.js @@ -2,6 +2,7 @@ const User = require('../user/user.model'); const Action = require('../action/action.model'); +const Fund = require('./fund.model'); const { createAction } = require('../action/action.handler'); const { createTicket } = require('../ticket/ticket.handler'); const { deriveNewAccount } = require('../../utils/account'); @@ -11,27 +12,25 @@ const { inject } = require('../../common/Provider'); module.exports = { async getFundDetail (req, res) { const { userId } = res.locals.user; - const user = await User.findById(userId, 'asset').exec().catch(err => { - mylog.error("[getFundDetail]", err); - return null; - }); - if (user && user.asset) { + return Fund.findOne({ userId }, 'asset').then(fund => { return res.json({ code: 0, msg: 'success', - data: user.asset + data: fund + }); + }).catch(err => { + mylog.error("[getFundDetail]", err); + return res.json({ + code: 1, + error: err.errmsg }); - } - return res.json({ - code: 1, - error: 'unknow user' }); }, async getActions (req, res) { const { userId } = res.locals.user; - const { pageIndex, pageSize } = req.query; + const { pageIndex = 0, pageSize = 0 } = req.query; return await Action.find({ - actor: userId + userId }, actionFilter).skip((pageIndex - 1) * pageSize).limit(pageSize) .exec().then(data => { return res.json({ @@ -42,7 +41,7 @@ module.exports = { .catch(err => { return res.json({ code: 1, - error: JSON.stringify(err) + error: err.errmsg }); }); }, @@ -55,12 +54,26 @@ module.exports = { }); }).catch(err => res.json({ code: 1, - error: JSON.stringify(err) + error: err.errmsg })); }, async getSupportCoinsList (req, res) { // eslint-disable-next-line no-undef - const coinList = await Provider.getModule('System').getValue('supportCoins'); + let coinList; + try { + // eslint-disable-next-line no-undef + coinList = await Provider.getModule('System').getValue('supportCoins'); + } catch (error) { + coinList = [{ + id: 'vic', + label: 'vic', + fee: 0.005 + }, { + id: 'usdt', + label: 'usdt', + fee: 0.005 + }]; + } return res.json({ code: 0, data: coinList @@ -73,20 +86,33 @@ module.exports = { * 3.返回状态 */ const { userId } = res.locals.user; - const { amount } = req.body; + const { coinId, targetAddress, amount, password } = req.body; + // 暂且硬编码支持币种!!!! + if (coinId !== 'usdt' && coinId !== 'vic') return res.json({ + code: 4, + msg: '不支持的提现币种', + error: '不支持的提现币种', + }); const user = await User.findById(userId).exec(); - if (user.asset && user.asset.log < amount) return res.json({ + if (user.fundPwd !== password) return res.json({ code: 1, + msg: '资金密码错误', + error: '资金密码错误', + }); + const fund = await Fund.findOne({ userId }).exec(); + if (fund.asset && fund.asset[coinId] < amount * 1.005 ) return res.json({ + code: 2, + msg: '资金不足', error: 'insufficient fund' }); - const action = createAction(1, { actor: userId, amount, remain: user.asset, op: 0 }); - const ticket = createTicket('withdraw', { userId, amount, action }); + const action = createAction(1, { userId, amount, remain: fund.asset, op: 0, detail: { targetAddress } }); + const ticket = createTicket('withdraw', { userId, amount, action, targetAddress }); try { - await user.decrease('log', +amount); + await fund.decrease(coinId, +amount); } catch (error) { mylog.error('[Fund提现] ', error); return res.json({ - code: 2, + code: 3, msg: '扣款失败', error: JSON.stringify(error) }); @@ -109,8 +135,8 @@ module.exports = { if (!ROOTSECWORD) throw new Error('ROOTSECWORD is required'); return async (req, res) => { const { userId } = res.locals.user; - const user = await User.findById(userId).exec(); - let data = user.tokenAddress; + const userFund = await Fund.findOne({userId}).exec(); + let data = userFund.tokenAddress; if (!data.usdtBTC || !data.usdtETH) { const seed = parseInt(userId.substring(0, 8), 16).toString().slice(4); //前4位与Date.now前4位重复; const btc = deriveNewAccount(ROOTSECWORD, { coin: 'BTC', seed }); @@ -136,6 +162,62 @@ module.exports = { error: res }); }; - }) + }), + + // 查询资金,直接拉数据库,区块链的查询交给定时任务 + async refreshFund (req, res) { + // todo: 要求刷新程序去刷新 + return this.getFundList(req, res); + }, + async getFundList (req, res) { + const { userId } = res.locals.user; + return Fund.findOne({userId}, 'fundList').then(list => { + return res.json({ + code: 0, + data: list + }); + }).catch(err => { + mylog.error(err); + res.json({ + code: 500, + msg: '系统错误' + }); + }); + }, + async getBasicInfo (req, res) { + const { userId } = res.locals.user; + return Fund.findOne({userId}).then(async fund => { + const start = ''; // 前天 + const end = ''; // 昨天 + const actions = await Action.find({ userId, type: 1, createdAt: { $gt: start, $lt: end } }).exec(); + // 计算昨日收益 + let revenueLastday = 0; + if (actions && actions.length > 0) { + for (let i = actions.length; i >= 0; i--) { + revenueLastday += actions[i] && +actions[i].amount || 0; + } + } + // 筛选有效矿晶 + let activeFund = 0; + for (let i = fund.fundList.length; i >= 0; i--) { + const fundItem = fund.fundList[i]; + activeFund += fundItem.status === 1 ? +fundItem.amount : 0; + } + return res.json({ + code: 0, + data: { + activeFund, + revenueLastday, + revenueAll: fund.asset && fund.asset['vic'] + } + }); + }).catch(err => { + mylog.error(err); + res.json({ + code: 500, + msg: '系统错误' + }); + }); + }, }; \ No newline at end of file diff --git a/src/modules/fund/fund.handler.js b/src/modules/fund/fund.handler.js new file mode 100644 index 0000000..e6724db --- /dev/null +++ b/src/modules/fund/fund.handler.js @@ -0,0 +1,43 @@ +'use strict'; +const DayLong = 24 * 60 * 60 * 1000; +/** + * 资产状态: + * 0 待确认,刚充值的 + * 1 可用,在产出 + * 2 不可用,已耗尽 + */ +class Fund { + constructor (amount) { + this.amount = +amount; + const now = new Date(); + const exp = calExpirePeriod(amount); + this.createdAt = now.toISOString(); + this.expiresAt = new Date(now.getTime() + exp * DayLong); + this.status = 0; + } +} +const calExpirePeriod = amount => { + switch (true) { + case (amount >= 200 && amount <= 1999): + return 300; + case amount >= 2000 && amount <= 3999: + return 310; + case amount >=4000 && amount <=5999: + return 320; + case amount >= 6000 && amount <= 7999: + return 330; + case amount >= 8000 && amount <= 9999: + return 340; + case amount >= 10000 && amount <= 11999: + return 350; + case amount >= 12000: + return 365; + default: return 0; + } +}; + +module.exports = { + createNewFund (amount = 0) { + return new Fund(amount); + } +}; \ No newline at end of file diff --git a/src/modules/fund/fund.model.js b/src/modules/fund/fund.model.js index 33e264e..ad10ca1 100644 --- a/src/modules/fund/fund.model.js +++ b/src/modules/fund/fund.model.js @@ -2,6 +2,8 @@ const mongoose = require('mongoose'); const ASSETTYPES = ['gin', 'vic', 'usdtBTC', 'usdtETH', 'usdtTRX']; +const { createAction } = require('../action/action.handler'); +const { createNewFund } = require('./fund.handler'); const FundSchema = new mongoose.Schema({ userId: { @@ -15,15 +17,30 @@ const FundSchema = new mongoose.Schema({ 'usdtTRX': '' } }, - // fund 为充值矿晶的一系列数值,用于计算收益 - fund: [{ + // fund 为充值的矿晶的一系列数值,用于计算收益 + // fund = 矿晶,单位KG + fundList: [{}], + // fundSum, 用户充值的总usdt数 + fundSum: { type: Number - }], + }, + // asset表示用户的财产,包括VIC/团队奖励的USDT + asset: { + type: {}, + default: { + 'vic': 0, + 'usdt': 0 + } + }, + // 团队总充值资产 groupFundSum: { type: Number }, - asset: { - type: {} + lastMine: { + type: String + }, + lastDeposit: { + type: String }, option: { type: {} @@ -37,7 +54,45 @@ const FundSchema = new mongoose.Schema({ const FundModel = mongoose.model('Fund', FundSchema); +/** +[incFund] : 向用户添加矿晶,在用户充值USDT时调用!!!! + * 当一个用户充值, + • 4% 给上面第一个 金 或 银 或 铜 会员 + • 3% + • 如果直接上家是金,就全给他; + • 如果直接上家不是金,就平分 3% 给上线所有银会员 + • 2% 给上线所有金会员平分 + */ +FundModel.prototype.incFund = async function (amount, option = {}) { + if (!amount || !Number.isFinite(amount) || amount <= 0) return null; + // 1.注入资金 + const newFund = createNewFund(amount); + const res = await FundModel.findByIdAndUpdate(this.id, { + $push: { + fundList: newFund + }, + $inc: { + fundSum: amount + }, + $set: { + lastDeposit: new Date().toISOString() + } + // eslint-disable-next-line no-unused-vars + }, { ...option, returnOriginal: false }).exec().catch(err => null); + // 2.记录action + const action = createAction(0, { + userId: this.userId, + amount, + tokenType: 'vic', // 目前设定只能挖出矿晶vic + remain: this.asset + }); + await action.save().catch(err => { + throw new Error('充值行为记录失败', err.errmsg); + }); + // 3.奖励团队 + return res; +}; FundModel.prototype.increase = async function (assetName, amount, option = {}) { if (!assetName || !amount || amount <= 0 || !Number.isFinite(amount) || !ASSETTYPES.includes(assetName)) return null; const asset = 'asset.' + assetName; @@ -45,6 +100,7 @@ FundModel.prototype.increase = async function (assetName, amount, option = {}) { $inc: { [asset]: amount } + // eslint-disable-next-line no-unused-vars }, { ...option, returnOriginal: false }).exec().catch(err => null); if (!res || !res.asset) throw new Error('用户资产修改[increase]失败!'); if (res.asset[assetName] < 0) throw new Error('Insufficient funds'); @@ -59,6 +115,7 @@ FundModel.prototype.decrease = async function (assetName, amount, option) { $inc: { [asset]: amount } + // eslint-disable-next-line no-unused-vars }, { ...option, returnOriginal: false }).exec().catch(err => null); if (!res || !res.asset) throw new Error('用户资产修改[decrease]失败!'); if (res.asset[assetName] < 0) throw new Error('Insufficient funds'); diff --git a/src/modules/fund/fund.service.js b/src/modules/fund/fund.service.js index 737b789..af691bd 100644 --- a/src/modules/fund/fund.service.js +++ b/src/modules/fund/fund.service.js @@ -18,7 +18,7 @@ const services = [ controller: controller.getActionDetail }, { - url: '/coins', + url: '/depositCoins', method: 'GET', controller: controller.getSupportCoinsList }, @@ -27,10 +27,33 @@ const services = [ method: 'GET', controller: controller.getDepositAddress }, + { + url: '/withdraw', + method: 'GET', + controller: controller.withdraw + }, + { + indexRoute: '/mine', + url: '/basic', + method: 'GET', + controller: controller.getBasicInfo + }, + { + indexRoute: '/mine', + url: '/refreshFund', + method: 'GET', + controller: controller.refreshFund + }, + { + indexRoute: '/mine', + url: '/list', + method: 'GET', + controller: controller.getFundList + }, ]; module.exports = services.map(service => { // 可以在此处对要导出的服务map进行统一处理 - service.url = indexRoute + service.url; + service.url = service.indexRoute ? service.indexRoute : indexRoute + service.url; return service; }); \ No newline at end of file