diff --git a/src/jobs/jobs.js b/src/jobs/jobs.js index 96bbd5d..67cc6c6 100644 --- a/src/jobs/jobs.js +++ b/src/jobs/jobs.js @@ -16,99 +16,97 @@ */ const mongoose = require('mongoose'); const User = require('../modules/user/user.model'); -const Estate = require('../modules/estate/estate.model'); -const EstateOrder = require('../modules/estateOrder/estateOrder.model'); const { createAction } = require('../modules/action/action.handler'); const jobs = [ - { - name: 'refreshEstateAndPayBack', - processor: async (job, done) => { - //todo: 程序内检查是否可以执行,保证不会启动时错误的自动执行一次 - const now = new Date().getMinutes(); - if (now > 0 && now < 58) { - mylog.error('[Schedule Job] 不在合理时间启动...程序结束'); - return 0; - } - mylog.info('开始定时计算作业...'); - const query = { startTime: +(new Date().getUTCHours()) + 8 + 1 }; - const elist = await Estate.find(query).exec(); - if (!elist || elist.length <= 0) return 0; - const [ estateSession, userSession, orderSession ] = await Promise.all([ - mongoose.startSession(), - mongoose.startSession(), - mongoose.startSession() - ]); - await Promise.all([ - estateSession.startTransaction(), - userSession.startTransaction(), - orderSession.startTransaction() - ]); - job.touch(); - for (let i = 0, length = elist.length; i < length; i++) { - const estate = elist[i]; - const estateId = estate.id; - const userId = estate.currentOwner; - // todo: 更精确判读是否要回收地产 - if (!userId) continue; - const [ user, order ] = await Promise.all([ - User.findById(userId).exec(), - EstateOrder.findOne({ - userId, - estateId, - status: 'holding' - }).exec() - ]); - const profit = +estate.price * +estate.profit; - const payback = profit + estate.price; - const action = createAction(3, { actor: userId, estate, amount: profit, remain: user.asset }); - if (!Number.isFinite(payback)) throw new Error('非法的收益数值!'); - const userOpts = { returnOriginal: false, session: userSession }; - const orderOpts = { returnOriginal: false, session: orderSession }; - const estateOpts = { returnOriginal: false, session: estateSession }; - try { - await Promise.all([ - user.increase('profit', payback, userOpts), - estate.sell(estateOpts), - order.finish(orderOpts), - action.save() - ]); - await Promise.all([ - userSession.commitTransaction(), - orderSession.commitTransaction(), - estateSession.commitTransaction() - ]); - } catch (error) { - mylog.error(error); - await Promise.all([ - action.remove(), - userSession.abortTransaction(), - orderSession.abortTransaction(), - estateSession.abortTransaction() - ]); - } finally { - await Promise.all([ - userSession.endSession(), - orderSession.endSession(), - estateSession.endSession() - ]); - } - job.touch(); - } - mylog.info('收益计算分配完成'); - done(); - }, - type: 'every', - // 写成 '*/59 * * * *' 会出现59分执行一次,下一小时的0分再执行一次 - // 写成 '*/59 * * * *' 是每59 - // 最差的解决方案是 在58分执行 - slot: '59 */1 * * *', // 59th minute per hour - priority: 'highest', - unique: { - id: 'payback001' - }, - skipImmediate: true, - timezone: "ETC/GMT+8" - } + // { + // name: 'refreshEstateAndPayBack', + // processor: async (job, done) => { + // //todo: 程序内检查是否可以执行,保证不会启动时错误的自动执行一次 + // const now = new Date().getMinutes(); + // if (now > 0 && now < 58) { + // mylog.error('[Schedule Job] 不在合理时间启动...程序结束'); + // return 0; + // } + // mylog.info('开始定时计算作业...'); + // const query = { startTime: +(new Date().getUTCHours()) + 8 + 1 }; + // const elist = await Estate.find(query).exec(); + // if (!elist || elist.length <= 0) return 0; + // const [ estateSession, userSession, orderSession ] = await Promise.all([ + // mongoose.startSession(), + // mongoose.startSession(), + // mongoose.startSession() + // ]); + // await Promise.all([ + // estateSession.startTransaction(), + // userSession.startTransaction(), + // orderSession.startTransaction() + // ]); + // job.touch(); + // for (let i = 0, length = elist.length; i < length; i++) { + // const estate = elist[i]; + // const estateId = estate.id; + // const userId = estate.currentOwner; + // // todo: 更精确判读是否要回收地产 + // if (!userId) continue; + // const [ user, order ] = await Promise.all([ + // User.findById(userId).exec(), + // EstateOrder.findOne({ + // userId, + // estateId, + // status: 'holding' + // }).exec() + // ]); + // const profit = +estate.price * +estate.profit; + // const payback = profit + estate.price; + // const action = createAction(3, { actor: userId, estate, amount: profit, remain: user.asset }); + // if (!Number.isFinite(payback)) throw new Error('非法的收益数值!'); + // const userOpts = { returnOriginal: false, session: userSession }; + // const orderOpts = { returnOriginal: false, session: orderSession }; + // const estateOpts = { returnOriginal: false, session: estateSession }; + // try { + // await Promise.all([ + // user.increase('profit', payback, userOpts), + // estate.sell(estateOpts), + // order.finish(orderOpts), + // action.save() + // ]); + // await Promise.all([ + // userSession.commitTransaction(), + // orderSession.commitTransaction(), + // estateSession.commitTransaction() + // ]); + // } catch (error) { + // mylog.error(error); + // await Promise.all([ + // action.remove(), + // userSession.abortTransaction(), + // orderSession.abortTransaction(), + // estateSession.abortTransaction() + // ]); + // } finally { + // await Promise.all([ + // userSession.endSession(), + // orderSession.endSession(), + // estateSession.endSession() + // ]); + // } + // job.touch(); + // } + // mylog.info('收益计算分配完成'); + // done(); + // }, + // type: 'every', + // // 写成 '*/59 * * * *' 会出现59分执行一次,下一小时的0分再执行一次 + // // 写成 '*/59 * * * *' 是每59 + // // 最差的解决方案是 在58分执行 + // slot: '59 */1 * * *', // 59th minute per hour + // priority: 'highest', + // unique: { + // id: 'payback001' + // }, + // skipImmediate: true, + // timezone: "ETC/GMT+8" + // } ]; function mountJobs (Job) { jobs.map(jobConfig => { diff --git a/src/modules/fund/fund.model.js b/src/modules/fund/fund.model.js new file mode 100644 index 0000000..33e264e --- /dev/null +++ b/src/modules/fund/fund.model.js @@ -0,0 +1,68 @@ +'use strict'; + +const mongoose = require('mongoose'); +const ASSETTYPES = ['gin', 'vic', 'usdtBTC', 'usdtETH', 'usdtTRX']; + +const FundSchema = new mongoose.Schema({ + userId: { + type: String + }, + tokenAddress: { + type: {}, + default: { + 'usdtBTC': '', + 'usdtETH': '', + 'usdtTRX': '' + } + }, + // fund 为充值矿晶的一系列数值,用于计算收益 + fund: [{ + type: Number + }], + groupFundSum: { + type: Number + }, + asset: { + type: {} + }, + option: { + type: {} + }, +}, { + timestamps: { + createdAt: 'createdAt', + updatedAt: 'updatedAt' + } +}); + + +const FundModel = mongoose.model('Fund', FundSchema); + +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; + const res = await FundModel.findByIdAndUpdate(this.id, { + $inc: { + [asset]: amount + } + }, { ...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'); + return res; +}; +FundModel.prototype.decrease = async function (assetName, amount, option) { + // 传入的amount应该是大于0的正数 + if (!assetName || !amount || amount <= 0 || !Number.isFinite(amount) || !ASSETTYPES.includes(assetName)) return null; + amount = 0 - amount; + const asset = 'asset.' + assetName; + const res = await FundModel.findByIdAndUpdate(this.id, { + $inc: { + [asset]: amount + } + }, { ...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'); + return res; +}; + +module.exports = FundModel; \ No newline at end of file diff --git a/src/modules/otcAds/otcAds.controller.js b/src/modules/otcAds/otcAds.controller.js deleted file mode 100644 index 84f0b27..0000000 --- a/src/modules/otcAds/otcAds.controller.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; -const mongoose = require('mongoose'); -const OtcAds = require('./otcAds.model'); -const User = require('../user/user.model'); - -module.exports = { - publishAd: async function (req, res) { - const { userId } = res.locals.user; - const { type, totalAmount, option } = req.body; - if (!Number.isFinite(+totalAmount)) return res.json({ - code: 4, - msg: 'illegal amount' - }); - const user = await User.findById(userId).exec(); - if (!user) return res.json({ - code: 1, - msg: 'unknow user' - }); - const balance = +user.asset.profit - +totalAmount; - if (!Number.isFinite(balance) || balance < 0) return res.json({ - code: 2, - msg: 'insufficient fund' - }); - const ads = new OtcAds({ userId, type, totalAmount: +totalAmount, restAmount: +totalAmount, option }); - try { - await Promise.all([ - user.decrease('profit', +totalAmount), - ads.save() - ]); - res.json({ - code: 0, - msg: 'success', - data: ads - }); - } catch (error) { - mylog.error(error); - res.json({ - code: 3, - error: JSON.stringify(error) - }); - } - - }, - revokeAd: async function (req, res) { - const { adId } = req.body; - const userId = res.locals.user ? res.locals.user.userId : ''; - const [ad, user] = await Promise.all([ - OtcAds.findById(adId).exec(), - User.findById(userId).exec() - ]); - if (!ad || !user) return res.json({ - code: 1, - msg: 'unknow user or advertisement' - }); - const restAmount = +(ad.restAmount); - await ad.updateOne({ status: 'canceled' }).exec(); - if (ad.type === 'sell') { - // 卖单回收冻结的币,而且只能回到profit - const session = await mongoose.startSession(); - await session.startTransaction(); - const option = { session }; - try { - await user.increase('profit', restAmount, option); - await session.commitTransaction(); - session.endSession(); - } catch (error) { - await session.abortTransaction(); - session.endSession(); - return res.json({ - code: 500, - msg: '撤销出错,用户余额未能恢复' - }); - } - } - return res.json({ - code: 0, - msg: 'advertisement revoked' - }); - }, - getAdList: async function (req, res) { - const { pageSize, pageIndex, type, status } = req.query; - OtcAds.find({ type, status }, 'type totalAmount restAmount status orders').limit(+pageSize).skip((+pageIndex - 1) * +pageSize).then(data => { - res.json({ - code: 0, - data - }); - }).catch(error => { - res.json({ - code: 500, - error - }); - }); - }, - getAdDetail: async function (req, res) { - const { adId } = req.query; - OtcAds.findById(adId, 'type totalAmount restAmount status orders').then(data => { - res.json({ - code: 0, - data - }); - }).catch(error => { - res.json({ - code: 500, - error - }); - }); - } -}; \ No newline at end of file diff --git a/src/modules/otcAds/otcAds.model.js b/src/modules/otcAds/otcAds.model.js deleted file mode 100644 index 25d5008..0000000 --- a/src/modules/otcAds/otcAds.model.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -const mongoose = require('mongoose'); -const OtcAdsSchema = new mongoose.Schema({ - userId: { - // 所属用户的objId - type: String, - required: true - }, - type: { - // 订单类型, 买单'buy' 卖单'sell' - type: String, - required: true, - enum: ['buy', 'sell'] - }, - totalAmount: { - //挂单总额 - type: Number, - required: true - }, - restAmount: { - //当前剩余额度 - type: Number, - required: true - }, - status: { - // 订单状态 - /** - * available: 挂单中 - * locked: 锁定 - * finished: 结束 - * canceled: 中止/取消 - */ - type: String, - default: 'available', - enum: ['available', 'locked', 'finished', 'canceled'] - }, - orders: { - // 涉及到的所有订单详情/快照 - type: {}, - default: {} - }, - option: { - type: {} - } -}, { - timestamps: { - createdAt: 'createdAt', - updatedAt: 'updatedAt' - } -}); - -const otcAdsSchema = mongoose.model('otcads', OtcAdsSchema); - -module.exports = otcAdsSchema; \ No newline at end of file diff --git a/src/modules/otcAds/otcAds.service.js b/src/modules/otcAds/otcAds.service.js deleted file mode 100644 index ae78d32..0000000 --- a/src/modules/otcAds/otcAds.service.js +++ /dev/null @@ -1,30 +0,0 @@ -const indexRoute = '/otcads'; -const controller = require('./otcAds.controller'); -const services = [ - { - url: '/publish', - method: 'POST', - controller: controller.publishAd - }, - { - url: '/revoke', - method: 'POST', - controller: controller.revokeAd - }, - { - url: '/getAdList', - method: 'GET', - controller: controller.getAdList - }, - { - url: '/getAdDetail', - method: 'GET', - controller: controller.getAdDetail - } -]; - -module.exports = services.map(service => { - // 可以在此处对要导出的服务map进行统一处理 - service.url = indexRoute + service.url; - return service; -}); \ No newline at end of file diff --git a/src/modules/otcOrder/otcOrder.controller.js b/src/modules/otcOrder/otcOrder.controller.js deleted file mode 100644 index 4ec7ec7..0000000 --- a/src/modules/otcOrder/otcOrder.controller.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -const otcOrder = require('./otcOrder.model'); -const { buy, sell } = require('./otcOrder.handler'); - -module.exports = { - getOtcOrderList (req, res) { - const { userId } = res.locals.user; - const { pageSize, pageIndex, type } = req.query; - otcOrder.find({ type, userId }).skip((+pageIndex - 1) * +pageSize).limit(+pageSize) - .then(list => { - res.json({ - code: 0, - msg: '', - data: list - }); - }) - .catch(err => { - res.json({ - code: 1, - msg: err - }); - }); - }, - getOtcOrder (req, res) { - const { userId } = res.locals.user; - const option = req.query; - otcOrder.find({ userId, ...option }, 'detail') - .then(list => { - res.json({ - code: 0, - msg: '', - data: list - }); - }) - .catch(err => { - res.json({ - code: 1, - msg: err - }); - }); - }, - async buy (req, res) { - let { adId, amount } = req.body; - amount = +amount; - const { userId } = res.locals.user; - if (!userId || !adId || !Number.isFinite(amount)) return res.json({ - code: 1, - msg: 'invalid parameers' - }); - const exec = await buy(userId, adId, amount); - if (exec && exec.res) { - return res.json({ - code: 0, - msg: exec.msg, - data: exec.res - }); - } else { - return res.json({ - code: exec.code, - msg: exec.msg, - }); - } - }, - async sell (req, res) { - let { adId, amount } = req.body; - amount = +amount; - const { userId } = res.locals.user; - if (!userId || !adId || !Number.isFinite(amount)) return res.json({ - code: 1, - msg: 'invalid parameers' - }); - const exec = await sell(userId, adId, amount); - if (exec && exec.res) { - return res.json({ - code: 0, - msg: exec.msg, - data: exec.res - }); - } else { - return res.json({ - code: exec.code, - msg: exec.msg, - }); - } - } -}; \ No newline at end of file diff --git a/src/modules/otcOrder/otcOrder.handler.js b/src/modules/otcOrder/otcOrder.handler.js deleted file mode 100644 index 50a642b..0000000 --- a/src/modules/otcOrder/otcOrder.handler.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict'; -const mongoose = require('mongoose'); -const User = require('../user/user.model'); -const OtcAds = require('../otcAds/otcAds.model'); -const OtcOrder = require('../otcOrder/otcOrder.model'); -const { createAction } = require('../action/action.handler'); -/** - - * 1. 生成对应的action - * 2. 生成订单 - * 3. 冻结卖家资金 - * 4. - * - * @param {String} actorId - * @param {String} adId - * @param {Number} amount - * @returns - */ -async function otcBuy (actorId, adId, amount) { - try { - const actor = await User.findById(actorId).exec(); - if (!actor) return { res: null, code: 1, msg: 'unknow user' }; - const adv = await OtcAds.findById(adId).exec(); - const order = new OtcOrder({ }); - const action = createAction(4, { actor: actorId, adv, }); - let orderSession = await mongoose.startSession(); - let actorSession = await mongoose.startSession(); - orderSession.startTransaction(); - actorSession.startTransaction(); - const actorOpts = { session: actorSession, returnOriginal: false }; - const orderOpts = { session: orderSession, returnOriginal: false }; - try { - await action.save(); - await orderSession.commitTransaction(); - await actorSession.commitTransaction(); - return { res : { }, code: 0, msg: 'success' }; - } catch (error) { - await orderSession.abortTransaction(); - await actorSession.abortTransaction(); - return { res: null, code: 3, msg: 'Insufficient funds' }; - } finally { - orderSession.endSession(); - actorSession.endSession(); - } - } catch (error) { - return { res: null, code: 500, msg: JSON.stringify(error) }; - } -} - -async function otcSell (actorId, adId, amount) { - try { - const actor = await User.findById(actorId).exec(); - if (!actor) return { res: null, code: 1, msg: 'unknow user' }; - const adv = await OtcAds.findById(adId).exec(); - const order = new OtcOrder({ }); - const action = createAction(5, { actor: actorId, adv, }); - let orderSession = await mongoose.startSession(); - let actorSession = await mongoose.startSession(); - orderSession.startTransaction(); - actorSession.startTransaction(); - const actorOpts = { session: actorSession, returnOriginal: false }; - const orderOpts = { session: orderSession, returnOriginal: false }; - try { - await action.save(); - await orderSession.commitTransaction(); - await actorSession.commitTransaction(); - return { res : { }, code: 0, msg: 'success' }; - } catch (error) { - await action.remove(); - await orderSession.abortTransaction(); - await actorSession.abortTransaction(); - return { res: null, code: 3, msg: 'Insufficient funds' }; - } finally { - orderSession.endSession(); - actorSession.endSession(); - } - } catch (error) { - return { res: null, code: 500, msg: JSON.stringify(error) }; - } -} - - -module.exports = { - otcBuy, - otcSell -}; - -/** - * 卖NEW的卖家广告 --- 场外买家 - * 1.在广告页面,输入想要购买的数量amount,选择支付方式,选择支付币种,点击购买,发送请求。 - * 2. - * 2.1 后台生成订单otcOrder,包含上面的参数。 - * 2.2 冻结卖家广告中的amount数量的资金。 - * 2.3 分别通知广告主和买家 - * 2.4 买家付款,点击我已付款按钮,订单状态变为 买家已付款 - * 2.5 卖家收到款,点击确认收款,订单状态变为,卖家已放币。 - * 2.6 订单状态变更为finished - * 2.7 广告状态变更,若该广告涉及金额已空,则改为finished,否则回到available - * 3. - */ \ No newline at end of file diff --git a/src/modules/otcOrder/otcOrder.model.js b/src/modules/otcOrder/otcOrder.model.js deleted file mode 100644 index f8b1644..0000000 --- a/src/modules/otcOrder/otcOrder.model.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -const mongoose = require('mongoose'); -const OtcOrderSchema = new mongoose.Schema({ - type: { - // 订单类型,买单[buy]or卖单[sell] - type: String, - required: true - }, - adId: { - // 对应广告id - type: String, - required: true - }, - from: { - // 转账发起人,卖家 - type: String, - }, - to: { - // 收款人, 买家 - type: String, - }, - coinType: { - type: String, - }, - payMethod: { - type: String, - enum : ['alipay', 'wechatpay', 'bankcard'], - }, - amount: { - type: Number, - required: true - }, - status: { - // 订单状态 - /* - * hanging: 有效 - * locked: 锁定 - * finished: 结束 - */ - type: String, - enum : ['alipay', 'wechatpay', 'bankcard'], - }, - option: { - type: {} - } -}, { - timestamps: { - createdAt: 'createdAt', - updatedAt: 'updatedAt' - } -}); - -const otcOrderSchema = mongoose.model('otcOrder', OtcOrderSchema); - -module.exports = otcOrderSchema; \ No newline at end of file diff --git a/src/modules/otcOrder/otcOrder.service.js b/src/modules/otcOrder/otcOrder.service.js deleted file mode 100644 index 30d740e..0000000 --- a/src/modules/otcOrder/otcOrder.service.js +++ /dev/null @@ -1,30 +0,0 @@ -const indexRoute = '/otcorder'; -const controller = require('./otcOrder.controller'); -const services = [ - { - url: '/list', - method: 'GET', - controller: controller.getOtcOrderList - }, - { - url: '/detail', - method: 'GET', - controller: controller.getOtcOrder - }, - { - url: '/buy', - method: 'POST', - controller: controller.buy - }, - { - url: '/sell', - method: 'POST', - controller: controller.sell - }, -]; - -module.exports = services.map(service => { - // 可以在此处对要导出的服务map进行统一处理 - service.url = indexRoute + service.url; - return service; -}); \ No newline at end of file diff --git a/src/modules/user/user.model.js b/src/modules/user/user.model.js index 67eee32..aaf8c7e 100644 --- a/src/modules/user/user.model.js +++ b/src/modules/user/user.model.js @@ -20,34 +20,16 @@ const UserSchema = new mongoose.Schema({ inviter: { type: String, }, - gender: { - type: String - }, asset: { - type: {}, - default: { - log: 0, //充值的log - profit: 0 // 买卖地皮赚到的 - } - }, - tokenAddress: { - type: {}, - default: { - 'usdtBTC': '', - 'usdtETH': '', - 'usdtTRX': '' - } + type: {} }, + group: [], avatar: { type: String }, option: { type: {} }, - group: [{ - type: mongoose.Schema.Types.ObjectId, - ref: 'User' - }] }, { timestamps: { createdAt: 'createdAt',