440 lines
12 KiB
JavaScript
440 lines
12 KiB
JavaScript
const express = require('express');
|
|
router = express.Router();
|
|
|
|
/**
|
|
* @swagger
|
|
* tags:
|
|
* name: RiskManagement
|
|
* description: 风险管理API
|
|
*/
|
|
const { auth } = require('../middleware/auth');
|
|
const timeoutService = require('../services/timeoutService');
|
|
const { getDB } = require('../database');
|
|
|
|
/**
|
|
* 检查管理员权限
|
|
*/
|
|
const requireAdmin = (req, res, next) => {
|
|
if (req.user.role !== 'admin') {
|
|
return res.status(403).json({ success: false, message: '需要管理员权限' });
|
|
}
|
|
next();
|
|
};
|
|
|
|
/**
|
|
* @swagger
|
|
* /risk-management/users:
|
|
* get:
|
|
* summary: 获取风险用户列表
|
|
* tags: [RiskManagement]
|
|
* security:
|
|
* - bearerAuth: []
|
|
* parameters:
|
|
* - in: query
|
|
* name: page
|
|
* schema:
|
|
* type: integer
|
|
* default: 1
|
|
* description: 页码
|
|
* - in: query
|
|
* name: limit
|
|
* schema:
|
|
* type: integer
|
|
* default: 10
|
|
* description: 每页数量
|
|
* - in: query
|
|
* name: is_blacklisted
|
|
* schema:
|
|
* type: integer
|
|
* enum: [0, 1]
|
|
* description: 是否被拉黑
|
|
* - in: query
|
|
* name: username
|
|
* schema:
|
|
* type: string
|
|
* description: 用户名
|
|
* responses:
|
|
* 200:
|
|
* description: 成功获取风险用户列表
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* users:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* properties:
|
|
* id:
|
|
* type: integer
|
|
* username:
|
|
* type: string
|
|
* real_name:
|
|
* type: string
|
|
* is_blacklisted:
|
|
* type: boolean
|
|
* blacklist_reason:
|
|
* type: string
|
|
* blacklisted_at:
|
|
* type: string
|
|
* format: date-time
|
|
* pagination:
|
|
* type: object
|
|
* properties:
|
|
* total:
|
|
* type: integer
|
|
* page:
|
|
* type: integer
|
|
* limit:
|
|
* type: integer
|
|
* pages:
|
|
* type: integer
|
|
* 401:
|
|
* description: 未授权
|
|
* 403:
|
|
* description: 权限不足
|
|
* 500:
|
|
* description: 服务器错误
|
|
*/
|
|
router.get('/users', auth, requireAdmin, async (req, res) => {
|
|
try {
|
|
const { page = 1, limit = 10, is_blacklisted, username } = req.query;
|
|
|
|
const filters = {};
|
|
if (is_blacklisted !== undefined) {
|
|
filters.is_blacklisted = parseInt(is_blacklisted);
|
|
}
|
|
if (username) {
|
|
filters.username = username;
|
|
}
|
|
|
|
const result = await timeoutService.getRiskUsers(filters, { page, limit });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result
|
|
});
|
|
} catch (error) {
|
|
console.error('获取风险用户列表失败:', error);
|
|
res.status(500).json({ success: false, message: '获取风险用户列表失败' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /risk-management/blacklist/{userId}:
|
|
* post:
|
|
* summary: 拉黑用户
|
|
* tags: [RiskManagement]
|
|
* security:
|
|
* - bearerAuth: []
|
|
* parameters:
|
|
* - in: path
|
|
* name: userId
|
|
* schema:
|
|
* type: integer
|
|
* required: true
|
|
* description: 用户ID
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* required:
|
|
* - reason
|
|
* properties:
|
|
* reason:
|
|
* type: string
|
|
* description: 拉黑原因
|
|
* responses:
|
|
* 200:
|
|
* description: 用户已被拉黑
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: true
|
|
* message:
|
|
* type: string
|
|
* example: 用户已被拉黑
|
|
* 400:
|
|
* description: 请求参数错误
|
|
* 401:
|
|
* description: 未授权
|
|
* 403:
|
|
* description: 权限不足
|
|
* 500:
|
|
* description: 服务器错误
|
|
*/
|
|
router.post('/blacklist/:userId', auth, requireAdmin, async (req, res) => {
|
|
try {
|
|
const { userId } = req.params;
|
|
const { reason } = req.body;
|
|
const operatorId = req.user.id;
|
|
|
|
if (!reason || reason.trim() === '') {
|
|
return res.status(400).json({ success: false, message: '请提供拉黑原因' });
|
|
}
|
|
|
|
await timeoutService.blacklistUser(parseInt(userId), reason.trim(), operatorId);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '用户已被拉黑'
|
|
});
|
|
} catch (error) {
|
|
console.error('拉黑用户失败:', error);
|
|
res.status(500).json({ success: false, message: error.message || '拉黑用户失败' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /risk-management/unblacklist/{userId}:
|
|
* post:
|
|
* summary: 解除拉黑
|
|
* tags: [RiskManagement]
|
|
* security:
|
|
* - bearerAuth: []
|
|
* parameters:
|
|
* - in: path
|
|
* name: userId
|
|
* schema:
|
|
* type: integer
|
|
* required: true
|
|
* description: 用户ID
|
|
* responses:
|
|
* 200:
|
|
* description: 已解除拉黑
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: true
|
|
* message:
|
|
* type: string
|
|
* example: 已解除拉黑
|
|
* 401:
|
|
* description: 未授权
|
|
* 403:
|
|
* description: 权限不足
|
|
* 500:
|
|
* description: 服务器错误
|
|
*/
|
|
router.post('/unblacklist/:userId', auth, requireAdmin, async (req, res) => {
|
|
try {
|
|
const { userId } = req.params;
|
|
const operatorId = req.user.id;
|
|
|
|
await timeoutService.unblacklistUser(parseInt(userId), operatorId);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '已解除拉黑'
|
|
});
|
|
} catch (error) {
|
|
console.error('解除拉黑失败:', error);
|
|
res.status(500).json({ success: false, message: error.message || '解除拉黑失败' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /risk-management/overdue-transfers:
|
|
* get:
|
|
* summary: 获取超时转账列表
|
|
* tags: [RiskManagement]
|
|
* security:
|
|
* - bearerAuth: []
|
|
* parameters:
|
|
* - in: query
|
|
* name: page
|
|
* schema:
|
|
* type: integer
|
|
* default: 1
|
|
* description: 页码
|
|
* - in: query
|
|
* name: limit
|
|
* schema:
|
|
* type: integer
|
|
* default: 10
|
|
* description: 每页数量
|
|
* responses:
|
|
* 200:
|
|
* description: 成功获取超时转账列表
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* data:
|
|
* type: object
|
|
* properties:
|
|
* transfers:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* properties:
|
|
* id:
|
|
* type: integer
|
|
* user_id:
|
|
* type: integer
|
|
* recipient_id:
|
|
* type: integer
|
|
* amount:
|
|
* type: number
|
|
* status:
|
|
* type: string
|
|
* created_at:
|
|
* type: string
|
|
* format: date-time
|
|
* username:
|
|
* type: string
|
|
* recipient_name:
|
|
* type: string
|
|
* overdue_hours:
|
|
* type: number
|
|
* pagination:
|
|
* type: object
|
|
* properties:
|
|
* total:
|
|
* type: integer
|
|
* page:
|
|
* type: integer
|
|
* limit:
|
|
* type: integer
|
|
* pages:
|
|
* type: integer
|
|
* 401:
|
|
* description: 未授权
|
|
* 403:
|
|
* description: 权限不足
|
|
* 500:
|
|
* description: 服务器错误
|
|
*/
|
|
router.get('/overdue-transfers', auth, requireAdmin, async (req, res) => {
|
|
try {
|
|
const { page = 1, limit = 10 } = req.query;
|
|
const pageNum = parseInt(page, 10) || 1;
|
|
const limitNum = parseInt(limit, 10) || 10;
|
|
const offset = (pageNum - 1) * limitNum;
|
|
|
|
const db = getDB();
|
|
|
|
// 获取总数
|
|
const [countResult] = await db.execute(
|
|
'SELECT COUNT(*) as total FROM transfers WHERE is_overdue = 1'
|
|
);
|
|
const total = countResult[0].total;
|
|
|
|
// 获取数据
|
|
const [transfers] = await db.execute(
|
|
`SELECT t.*,
|
|
fu.username as from_username, fu.real_name as from_real_name,
|
|
tu.username as to_username, tu.real_name as to_real_name
|
|
FROM transfers t
|
|
LEFT JOIN users fu ON t.from_user_id = fu.id
|
|
LEFT JOIN users tu ON t.to_user_id = tu.id
|
|
WHERE t.is_overdue = 1
|
|
ORDER BY t.overdue_at DESC
|
|
LIMIT ${limitNum} OFFSET ${offset}`
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
transfers,
|
|
pagination: {
|
|
page: pageNum,
|
|
limit: limitNum,
|
|
total,
|
|
pages: Math.ceil(total / limitNum)
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('获取超时转账列表失败:', error);
|
|
res.status(500).json({ success: false, message: '获取超时转账列表失败' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 手动检查转账超时
|
|
*/
|
|
router.post('/check-timeouts', auth, requireAdmin, async (req, res) => {
|
|
try {
|
|
await timeoutService.checkTransferTimeouts();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '转账超时检查已完成'
|
|
});
|
|
} catch (error) {
|
|
console.error('手动检查转账超时失败:', error);
|
|
res.status(500).json({ success: false, message: '检查转账超时失败' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 获取风险管理统计信息
|
|
*/
|
|
router.get('/stats', auth, requireAdmin, async (req, res) => {
|
|
try {
|
|
const db = getDB();
|
|
|
|
// 获取统计数据
|
|
const [stats] = await db.execute(
|
|
`SELECT
|
|
COUNT(CASE WHEN is_risk_user = 1 THEN 1 END) as risk_users_count,
|
|
COUNT(CASE WHEN is_blacklisted = 1 THEN 1 END) as blacklisted_users_count,
|
|
COUNT(CASE WHEN is_risk_user = 1 AND is_blacklisted = 0 THEN 1 END) as risk_not_blacklisted_count
|
|
FROM users`
|
|
);
|
|
|
|
const [overdueStats] = await db.execute(
|
|
`SELECT
|
|
COUNT(*) as overdue_transfers_count,
|
|
SUM(amount) as overdue_amount_total
|
|
FROM transfers
|
|
WHERE is_overdue = 1`
|
|
);
|
|
|
|
const [todayOverdue] = await db.execute(
|
|
`SELECT COUNT(*) as today_overdue_count
|
|
FROM transfers
|
|
WHERE is_overdue = 1 AND DATE(overdue_at) = CURDATE()`
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
riskUsersCount: stats[0].risk_users_count,
|
|
blacklistedUsersCount: stats[0].blacklisted_users_count,
|
|
riskNotBlacklistedCount: stats[0].risk_not_blacklisted_count,
|
|
overdueTransfersCount: overdueStats[0].overdue_transfers_count,
|
|
overdueAmountTotal: overdueStats[0].overdue_amount_total || 0,
|
|
todayOverdueCount: todayOverdue[0].today_overdue_count
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('获取风险管理统计失败:', error);
|
|
res.status(500).json({ success: false, message: '获取统计信息失败' });
|
|
}
|
|
});
|
|
|
|
module.exports = router; |