feat:裁剪
This commit is contained in:
68
src/modules/fund/fund.model.js
Normal file
68
src/modules/fund/fund.model.js
Normal 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;
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user