626 lines
18 KiB
JavaScript
626 lines
18 KiB
JavaScript
const express = require('express');
|
||
const router = express.Router();
|
||
const { getDB } = require('../database');
|
||
const matchingService = require('../services/matchingService');
|
||
const { auth } = require('../middleware/auth');
|
||
const { default: axios } = require('axios');
|
||
|
||
|
||
router.post('/create', auth, async (req, res) => {
|
||
try {
|
||
console.log('匹配订单创建请求 - 用户ID:', req.user.id);
|
||
console.log('请求体:', req.body);
|
||
const userId = req.user.id;
|
||
const { matchingType = 'small', customAmount } = req.body;
|
||
const [user_type] = await getDB().query(`SELECT count(*) as total FROM users WHERE id=${userId} and user_type='directly_operated'`);
|
||
if(user_type[0].total > 0){
|
||
return res.status(400).json({message: '平台暂不支持直营用户获得融豆'})
|
||
}
|
||
// 验证匹配类型
|
||
if (!['small', 'large'].includes(matchingType)) {
|
||
return res.status(400).json({ message: '无效的匹配类型' });
|
||
}
|
||
|
||
// 验证大额匹配的金额
|
||
if (matchingType === 'large') {
|
||
if (!customAmount || typeof customAmount !== 'number') {
|
||
return res.status(400).json({ message: '大额匹配需要指定金额' });
|
||
}
|
||
if (customAmount < 3000 || customAmount > 50000) {
|
||
return res.status(400).json({ message: '大额匹配金额必须在3000-50000之间' });
|
||
}
|
||
}
|
||
|
||
// 检查用户是否有未完成的匹配订单(排除已失败的订单)
|
||
const [existingOrders] = await getDB().execute(
|
||
'SELECT COUNT(*) as count FROM matching_orders WHERE initiator_id = ? AND status IN ("pending", "matching")',
|
||
[userId]
|
||
);
|
||
|
||
if (existingOrders[0].count > 0) {
|
||
return res.status(400).json({ message: '您有未完成的匹配订单,请等待完成后再创建新订单' });
|
||
}
|
||
|
||
// 校验用户是否已上传必要的证件和收款码
|
||
const [userInfo] = await getDB().execute(
|
||
'SELECT business_license, id_card_front, id_card_back, wechat_qr, alipay_qr, bank_card, unionpay_qr FROM users WHERE id = ?',
|
||
[userId]
|
||
);
|
||
|
||
if (userInfo.length === 0) {
|
||
return res.status(404).json({ message: '用户不存在' });
|
||
}
|
||
|
||
const user = userInfo[0];
|
||
|
||
// 检查证件是否已上传
|
||
if (!user.business_license || !user.id_card_front || !user.id_card_back) {
|
||
return res.status(400).json({
|
||
message: '开始匹配前,请先在个人中心上传营业执照和身份证正反面',
|
||
code: 'MISSING_DOCUMENTS'
|
||
});
|
||
}
|
||
|
||
// 检查收款码是否已上传(至少需要一种收款方式)
|
||
if (!user.wechat_qr && !user.alipay_qr && !user.bank_card && !user.unionpay_qr) {
|
||
return res.status(400).json({
|
||
message: '开始匹配前,请先在个人中心设置至少一种收款方式(微信、支付宝、银行卡或云闪付)',
|
||
code: 'MISSING_PAYMENT_METHODS'
|
||
});
|
||
}
|
||
|
||
// 创建匹配订单
|
||
const result = await matchingService.createMatchingOrder(userId, matchingType, customAmount);
|
||
|
||
const message = matchingType === 'small'
|
||
? '小额匹配成功!已为您生成3笔转账分配'
|
||
: `大额匹配成功!已为您生成${result.totalAmount}笔转账分配`;
|
||
|
||
res.json({
|
||
success: true,
|
||
message,
|
||
data: {
|
||
matchingOrderId: result.orderId,
|
||
amounts: result.amounts,
|
||
matchingType: result.matchingType,
|
||
totalAmount: result.totalAmount
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('创建匹配订单失败:', error);
|
||
res.status(500).json({ message: error.message || '匹配失败,请稍后重试' });
|
||
}
|
||
});
|
||
|
||
|
||
router.get('/my-orders', auth, async (req, res) => {
|
||
try {
|
||
const userId = req.user.id;
|
||
const page = parseInt(req.query.page) || 1;
|
||
const limit = parseInt(req.query.limit) || 10;
|
||
|
||
const orders = await matchingService.getUserMatchingOrders(userId, page, limit);
|
||
|
||
res.json({
|
||
success: true,
|
||
data: orders
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('获取匹配订单失败:', error);
|
||
res.status(500).json({ message: '获取匹配订单失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/matching/pending-allocations:
|
||
* get:
|
||
* summary: 获取用户待处理的分配
|
||
* tags: [Matching]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* responses:
|
||
* 200:
|
||
* description: 成功获取待处理分配
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* data:
|
||
* type: array
|
||
* items:
|
||
* $ref: '#/components/schemas/Allocation'
|
||
* 401:
|
||
* description: 未授权
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.get('/pending-allocations', auth, async (req, res) => {
|
||
try {
|
||
const userId = req.user.id;
|
||
|
||
const allocations = await matchingService.getUserPendingAllocations(userId);
|
||
|
||
res.json({
|
||
success: true,
|
||
data: allocations
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('获取待处理分配失败:', error);
|
||
res.status(500).json({ message: '获取待处理分配失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/matching/allocation/{id}:
|
||
* get:
|
||
* summary: 获取分配详情
|
||
* tags: [Matching]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: id
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 分配ID
|
||
* responses:
|
||
* 200:
|
||
* description: 成功获取分配详情
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* data:
|
||
* $ref: '#/components/schemas/Allocation'
|
||
* 401:
|
||
* description: 未授权
|
||
* 403:
|
||
* description: 无权限访问
|
||
* 404:
|
||
* description: 分配不存在
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.get('/allocation/:id', auth, async (req, res) => {
|
||
try {
|
||
const db = getDB();
|
||
const allocationId = req.params.id;
|
||
const userId = req.user.id;
|
||
|
||
// 首先获取分配信息
|
||
const [allocations] = await db.execute(`
|
||
SELECT
|
||
oa.id,
|
||
oa.from_user_id,
|
||
oa.to_user_id,
|
||
oa.cycle_number,
|
||
oa.amount,
|
||
oa.status,
|
||
oa.created_at,
|
||
from_user.username as from_user_name,
|
||
to_user.username as to_user_name
|
||
FROM transfers oa
|
||
JOIN users from_user ON oa.from_user_id = from_user.id
|
||
JOIN users to_user ON oa.to_user_id = to_user.id
|
||
WHERE oa.id = ?
|
||
`, [allocationId]);
|
||
|
||
if (allocations.length === 0) {
|
||
return res.status(404).json({ success: false, message: '分配不存在' });
|
||
}
|
||
|
||
const allocation = allocations[0];
|
||
|
||
// 检查权限:只有分配的发起人或接收人可以查看
|
||
if (allocation.from_user_id !== userId && allocation.to_user_id !== userId) {
|
||
return res.status(403).json({ success: false, message: '无权限访问此分配' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
data: allocation
|
||
});
|
||
} catch (error) {
|
||
console.error('获取分配详情错误:', error);
|
||
res.status(500).json({ success: false, message: '获取分配详情失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/matching/confirm-allocation/{allocationId}:
|
||
* post:
|
||
* summary: 确认分配(创建转账)
|
||
* tags: [Matching]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: allocationId
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 分配ID
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* transferAmount:
|
||
* type: number
|
||
* description: 转账金额
|
||
* description:
|
||
* type: string
|
||
* description: 转账描述
|
||
* voucher:
|
||
* type: string
|
||
* description: 转账凭证(图片URL)
|
||
* required:
|
||
* - voucher
|
||
* responses:
|
||
* 200:
|
||
* description: 转账凭证提交成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* message:
|
||
* type: string
|
||
* data:
|
||
* type: object
|
||
* properties:
|
||
* transferId:
|
||
* type: integer
|
||
* 400:
|
||
* description: 参数错误
|
||
* 401:
|
||
* description: 未授权
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.post('/confirm-allocation/:allocationId', auth, async (req, res) => {
|
||
try {
|
||
const { allocationId } = req.params;
|
||
const userId = req.user.id;
|
||
const { transferAmount, description, voucher } = req.body; // 获取转账信息
|
||
|
||
// 校验转账凭证是否存在
|
||
if (!voucher) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '请上传转账凭证'
|
||
});
|
||
}
|
||
|
||
// 调用服务层方法,传递完整的转账信息
|
||
const transferId = matchingService.confirmAllocation(
|
||
allocationId,
|
||
userId,
|
||
transferAmount,
|
||
description,
|
||
voucher
|
||
);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '转账凭证已提交,转账记录已创建',
|
||
data: { transferId }
|
||
});
|
||
|
||
// axios.post('http://localhost:5000/ocr',{
|
||
// id: allocationId
|
||
// }).then(res => {
|
||
// console.log(res.data)
|
||
// })
|
||
|
||
} catch (error) {
|
||
console.error('确认分配失败:', error);
|
||
res.status(500).json({ message: error.message || '确认分配失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/matching/reject-allocation/{allocationId}:
|
||
* post:
|
||
* summary: 拒绝分配
|
||
* tags: [Matching]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: allocationId
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 分配ID
|
||
* requestBody:
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* reason:
|
||
* type: string
|
||
* description: 拒绝原因
|
||
* responses:
|
||
* 200:
|
||
* description: 拒绝分配成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* message:
|
||
* type: string
|
||
* 401:
|
||
* description: 未授权
|
||
* 404:
|
||
* description: 分配不存在或无权限
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.post('/reject-allocation/:allocationId', auth, async (req, res) => {
|
||
try {
|
||
const { allocationId } = req.params;
|
||
const userId = req.user.id;
|
||
const { reason } = req.body;
|
||
|
||
const db = getDB();
|
||
|
||
// 获取分配信息
|
||
const [allocations] = await db.execute(
|
||
'SELECT * FROM transfers WHERE id = ? AND from_user_id = ?',
|
||
[allocationId, userId]
|
||
);
|
||
|
||
if (allocations.length === 0) {
|
||
return res.status(404).json({ message: '分配不存在或无权限' });
|
||
}
|
||
|
||
const allocation = allocations[0];
|
||
|
||
// 更新分配状态
|
||
await db.execute(
|
||
'UPDATE transfers SET status = "rejected" WHERE id = ?',
|
||
[allocationId]
|
||
);
|
||
|
||
// 记录拒绝动作
|
||
await db.execute(
|
||
'INSERT INTO matching_records (matching_order_id, user_id, action, note) VALUES (?, ?, "reject", ?)',
|
||
[allocation.matching_order_id, userId, reason || '用户拒绝']
|
||
);
|
||
|
||
// 检查订单状态是否需要更新
|
||
const statusResult = await matchingService.checkOrderStatusAfterRejection(
|
||
allocation.matching_order_id,
|
||
allocation.cycle_number
|
||
);
|
||
|
||
let message = '已拒绝分配';
|
||
if (statusResult === 'failed') {
|
||
message = '已拒绝分配,该轮次所有分配均被拒绝,匹配订单已失败';
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('拒绝分配失败:', error);
|
||
res.status(500).json({ message: '拒绝分配失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/matching/order/{orderId}:
|
||
* get:
|
||
* summary: 获取匹配订单详情
|
||
* tags: [Matching]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: orderId
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 订单ID
|
||
* responses:
|
||
* 200:
|
||
* description: 成功获取订单详情
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* data:
|
||
* type: object
|
||
* properties:
|
||
* order:
|
||
* $ref: '#/components/schemas/MatchingOrder'
|
||
* allocations:
|
||
* type: array
|
||
* items:
|
||
* $ref: '#/components/schemas/Allocation'
|
||
* records:
|
||
* type: array
|
||
* items:
|
||
* type: object
|
||
* 401:
|
||
* description: 未授权
|
||
* 403:
|
||
* description: 无权限查看
|
||
* 404:
|
||
* description: 订单不存在
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.get('/order/:orderId', auth, async (req, res) => {
|
||
try {
|
||
const { orderId } = req.params;
|
||
const userId = req.user.id;
|
||
|
||
const db = getDB();
|
||
|
||
// 获取订单基本信息
|
||
const [orders] = await db.execute(
|
||
`SELECT mo.*, u.username as initiator_name,u.real_name as initiator_real_name
|
||
FROM matching_orders mo
|
||
JOIN users u ON mo.initiator_id = u.id
|
||
WHERE mo.id = ?`,
|
||
[orderId]
|
||
);
|
||
|
||
if (orders.length === 0) {
|
||
return res.status(404).json({ message: '匹配订单不存在' });
|
||
}
|
||
|
||
const order = orders[0];
|
||
|
||
// 检查权限(订单发起人或参与者)
|
||
const [userCheck] = await db.execute(
|
||
`SELECT COUNT(*) as count FROM (
|
||
SELECT initiator_id as user_id FROM matching_orders WHERE id = ?
|
||
UNION
|
||
SELECT from_user_id as user_id FROM transfers WHERE id = ?
|
||
) as participants WHERE user_id = ?`,
|
||
[orderId, orderId, userId]
|
||
);
|
||
|
||
if (userCheck[0].count === 0) {
|
||
return res.status(403).json({ message: '无权限查看此订单' });
|
||
}
|
||
|
||
// 获取分配信息
|
||
const [allocations] = await db.execute(
|
||
`SELECT oa.*,
|
||
uf.username as from_user_name,
|
||
ut.username as to_user_name
|
||
FROM transfers oa
|
||
JOIN users uf ON oa.from_user_id = uf.id
|
||
JOIN users ut ON oa.to_user_id = ut.id
|
||
WHERE oa.id = ?
|
||
ORDER BY oa.cycle_number, oa.created_at`,
|
||
[orderId]
|
||
);
|
||
|
||
// 获取匹配记录
|
||
const [records] = await db.execute(
|
||
`SELECT mr.*, u.username
|
||
FROM matching_records mr
|
||
JOIN users u ON mr.user_id = u.id
|
||
WHERE mr.matching_order_id = ?
|
||
ORDER BY mr.created_at`,
|
||
[orderId]
|
||
);
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
order,
|
||
allocations,
|
||
records
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('获取匹配订单详情失败:', error);
|
||
res.status(500).json({ message: '获取匹配订单详情失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/matching/stats:
|
||
* get:
|
||
* summary: 获取匹配统计信息
|
||
* tags: [Matching]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* responses:
|
||
* 200:
|
||
* description: 成功获取统计信息
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* data:
|
||
* type: object
|
||
* properties:
|
||
* userStats:
|
||
* type: object
|
||
* properties:
|
||
* initiated_orders:
|
||
* type: integer
|
||
* participated_allocations:
|
||
* type: integer
|
||
* total_initiated_amount:
|
||
* type: number
|
||
* total_participated_amount:
|
||
* type: number
|
||
* 401:
|
||
* description: 未授权
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.get('/stats', auth, async (req, res) => {
|
||
try {
|
||
const userId = req.user.id;
|
||
const db = getDB();
|
||
|
||
// 获取用户统计
|
||
const [userStats] = await db.execute(
|
||
`SELECT
|
||
COUNT(CASE WHEN mo.initiator_id = ? THEN 1 END) as initiated_orders,
|
||
COUNT(CASE WHEN oa.from_user_id = ? THEN 1 END) as participated_allocations,
|
||
SUM(CASE WHEN mo.initiator_id = ? AND mo.status = 'completed' THEN mo.amount ELSE 0 END) as total_initiated_amount,
|
||
SUM(CASE WHEN oa.from_user_id = ? AND oa.status = 'completed' THEN oa.amount ELSE 0 END) as total_participated_amount
|
||
FROM matching_orders mo
|
||
LEFT JOIN transfers oa ON mo.id = oa.id`,
|
||
[userId, userId, userId, userId]
|
||
);
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
userStats: userStats[0]
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('获取匹配统计失败:', error);
|
||
res.status(500).json({ message: '获取匹配统计失败' });
|
||
}
|
||
});
|
||
|
||
|
||
|
||
module.exports = router; |