feat:裁剪

This commit is contained in:
chaosBreaking
2019-08-31 22:44:57 +08:00
parent 8202aa17c3
commit 55d77c03a3
10 changed files with 159 additions and 577 deletions

View File

@@ -16,99 +16,97 @@
*/ */
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const User = require('../modules/user/user.model'); 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 { createAction } = require('../modules/action/action.handler');
const jobs = [ const jobs = [
{ // {
name: 'refreshEstateAndPayBack', // name: 'refreshEstateAndPayBack',
processor: async (job, done) => { // processor: async (job, done) => {
//todo: 程序内检查是否可以执行,保证不会启动时错误的自动执行一次 // //todo: 程序内检查是否可以执行,保证不会启动时错误的自动执行一次
const now = new Date().getMinutes(); // const now = new Date().getMinutes();
if (now > 0 && now < 58) { // if (now > 0 && now < 58) {
mylog.error('[Schedule Job] 不在合理时间启动...程序结束'); // mylog.error('[Schedule Job] 不在合理时间启动...程序结束');
return 0; // return 0;
} // }
mylog.info('开始定时计算作业...'); // mylog.info('开始定时计算作业...');
const query = { startTime: +(new Date().getUTCHours()) + 8 + 1 }; // const query = { startTime: +(new Date().getUTCHours()) + 8 + 1 };
const elist = await Estate.find(query).exec(); // const elist = await Estate.find(query).exec();
if (!elist || elist.length <= 0) return 0; // if (!elist || elist.length <= 0) return 0;
const [ estateSession, userSession, orderSession ] = await Promise.all([ // const [ estateSession, userSession, orderSession ] = await Promise.all([
mongoose.startSession(), // mongoose.startSession(),
mongoose.startSession(), // mongoose.startSession(),
mongoose.startSession() // mongoose.startSession()
]); // ]);
await Promise.all([ // await Promise.all([
estateSession.startTransaction(), // estateSession.startTransaction(),
userSession.startTransaction(), // userSession.startTransaction(),
orderSession.startTransaction() // orderSession.startTransaction()
]); // ]);
job.touch(); // job.touch();
for (let i = 0, length = elist.length; i < length; i++) { // for (let i = 0, length = elist.length; i < length; i++) {
const estate = elist[i]; // const estate = elist[i];
const estateId = estate.id; // const estateId = estate.id;
const userId = estate.currentOwner; // const userId = estate.currentOwner;
// todo: 更精确判读是否要回收地产 // // todo: 更精确判读是否要回收地产
if (!userId) continue; // if (!userId) continue;
const [ user, order ] = await Promise.all([ // const [ user, order ] = await Promise.all([
User.findById(userId).exec(), // User.findById(userId).exec(),
EstateOrder.findOne({ // EstateOrder.findOne({
userId, // userId,
estateId, // estateId,
status: 'holding' // status: 'holding'
}).exec() // }).exec()
]); // ]);
const profit = +estate.price * +estate.profit; // const profit = +estate.price * +estate.profit;
const payback = profit + estate.price; // const payback = profit + estate.price;
const action = createAction(3, { actor: userId, estate, amount: profit, remain: user.asset }); // const action = createAction(3, { actor: userId, estate, amount: profit, remain: user.asset });
if (!Number.isFinite(payback)) throw new Error('非法的收益数值!'); // if (!Number.isFinite(payback)) throw new Error('非法的收益数值!');
const userOpts = { returnOriginal: false, session: userSession }; // const userOpts = { returnOriginal: false, session: userSession };
const orderOpts = { returnOriginal: false, session: orderSession }; // const orderOpts = { returnOriginal: false, session: orderSession };
const estateOpts = { returnOriginal: false, session: estateSession }; // const estateOpts = { returnOriginal: false, session: estateSession };
try { // try {
await Promise.all([ // await Promise.all([
user.increase('profit', payback, userOpts), // user.increase('profit', payback, userOpts),
estate.sell(estateOpts), // estate.sell(estateOpts),
order.finish(orderOpts), // order.finish(orderOpts),
action.save() // action.save()
]); // ]);
await Promise.all([ // await Promise.all([
userSession.commitTransaction(), // userSession.commitTransaction(),
orderSession.commitTransaction(), // orderSession.commitTransaction(),
estateSession.commitTransaction() // estateSession.commitTransaction()
]); // ]);
} catch (error) { // } catch (error) {
mylog.error(error); // mylog.error(error);
await Promise.all([ // await Promise.all([
action.remove(), // action.remove(),
userSession.abortTransaction(), // userSession.abortTransaction(),
orderSession.abortTransaction(), // orderSession.abortTransaction(),
estateSession.abortTransaction() // estateSession.abortTransaction()
]); // ]);
} finally { // } finally {
await Promise.all([ // await Promise.all([
userSession.endSession(), // userSession.endSession(),
orderSession.endSession(), // orderSession.endSession(),
estateSession.endSession() // estateSession.endSession()
]); // ]);
} // }
job.touch(); // job.touch();
} // }
mylog.info('收益计算分配完成'); // mylog.info('收益计算分配完成');
done(); // done();
}, // },
type: 'every', // type: 'every',
// 写成 '*/59 * * * *' 会出现59分执行一次下一小时的0分再执行一次 // // 写成 '*/59 * * * *' 会出现59分执行一次下一小时的0分再执行一次
// 写成 '*/59 * * * *' 是每59 // // 写成 '*/59 * * * *' 是每59
// 最差的解决方案是 在58分执行 // // 最差的解决方案是 在58分执行
slot: '59 */1 * * *', // 59th minute per hour // slot: '59 */1 * * *', // 59th minute per hour
priority: 'highest', // priority: 'highest',
unique: { // unique: {
id: 'payback001' // id: 'payback001'
}, // },
skipImmediate: true, // skipImmediate: true,
timezone: "ETC/GMT+8" // timezone: "ETC/GMT+8"
} // }
]; ];
function mountJobs (Job) { function mountJobs (Job) {
jobs.map(jobConfig => { jobs.map(jobConfig => {

View File

@@ -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;

View File

@@ -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
});
});
}
};

View File

@@ -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;

View File

@@ -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;
});

View File

@@ -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,
});
}
}
};

View File

@@ -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.
*/

View File

@@ -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;

View File

@@ -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;
});

View File

@@ -20,34 +20,16 @@ const UserSchema = new mongoose.Schema({
inviter: { inviter: {
type: String, type: String,
}, },
gender: {
type: String
},
asset: { asset: {
type: {}, type: {}
default: {
log: 0, //充值的log
profit: 0 // 买卖地皮赚到的
}
},
tokenAddress: {
type: {},
default: {
'usdtBTC': '',
'usdtETH': '',
'usdtTRX': ''
}
}, },
group: [],
avatar: { avatar: {
type: String type: String
}, },
option: { option: {
type: {} type: {}
}, },
group: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
}, { }, {
timestamps: { timestamps: {
createdAt: 'createdAt', createdAt: 'createdAt',