1549 lines
47 KiB
JavaScript
1549 lines
47 KiB
JavaScript
|
|
const express = require('express');
|
|||
|
|
const bcrypt = require('bcryptjs');
|
|||
|
|
const { getDB } = require('../database');
|
|||
|
|
const { auth, adminAuth } = require('../middleware/auth');
|
|||
|
|
const dayjs = require('dayjs');
|
|||
|
|
|
|||
|
|
const router = express.Router();
|
|||
|
|
|
|||
|
|
// 创建用户(管理员权限)
|
|||
|
|
router.post('/', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
await db.query('START TRANSACTION');
|
|||
|
|
|
|||
|
|
const {
|
|||
|
|
username,
|
|||
|
|
password,
|
|||
|
|
role = 'user',
|
|||
|
|
isSystemAccount = false, // 是否为虚拟商户
|
|||
|
|
realName,
|
|||
|
|
idCard,
|
|||
|
|
wechatQr,
|
|||
|
|
alipayQr,
|
|||
|
|
bankCard,
|
|||
|
|
unionpayQr,
|
|||
|
|
phone
|
|||
|
|
} = req.body;
|
|||
|
|
|
|||
|
|
if (!username || !password) {
|
|||
|
|
return res.status(400).json({ success: false, message: '用户名和密码不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!realName || !idCard) {
|
|||
|
|
return res.status(400).json({ success: false, message: '姓名和身份证号不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证身份证号格式
|
|||
|
|
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
|
|||
|
|
if (!idCardRegex.test(idCard)) {
|
|||
|
|
return res.status(400).json({ success: false, message: '身份证号格式不正确' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查用户是否已存在
|
|||
|
|
const [existingUsers] = await db.execute(
|
|||
|
|
'SELECT id FROM users WHERE username = ? OR id_card = ? OR (phone IS NOT NULL AND phone = ?)',
|
|||
|
|
[username, idCard, phone || null]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (existingUsers.length > 0) {
|
|||
|
|
return res.status(400).json({ success: false, message: '用户名、身份证号或手机号已存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加密密码
|
|||
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|||
|
|
|
|||
|
|
// 创建用户
|
|||
|
|
const [result] = await db.execute(
|
|||
|
|
'INSERT INTO users (username, password, role, is_system_account, points, real_name, id_card, wechat_qr, alipay_qr, bank_card, unionpay_qr, phone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|||
|
|
[username, hashedPassword, role, isSystemAccount, 0, realName, idCard, wechatQr, alipayQr, bankCard, unionpayQr, phone]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const userId = result.insertId;
|
|||
|
|
|
|||
|
|
// 用户余额已在创建用户时设置为默认值0.00,无需额外操作
|
|||
|
|
|
|||
|
|
await db.query('COMMIT');
|
|||
|
|
|
|||
|
|
// 返回创建的用户信息(不包含密码)
|
|||
|
|
const [newUser] = await db.execute(
|
|||
|
|
'SELECT id, username, role, avatar, points, real_name, phone, created_at, updated_at FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.status(201).json({
|
|||
|
|
success: true,
|
|||
|
|
message: '用户创建成功',
|
|||
|
|
user: newUser[0]
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
try {
|
|||
|
|
await getDB().query('ROLLBACK');
|
|||
|
|
} catch (rollbackError) {
|
|||
|
|
console.error('回滚错误:', rollbackError);
|
|||
|
|
}
|
|||
|
|
console.error('创建用户错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '创建用户失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取待审核用户列表(管理员权限)
|
|||
|
|
*/
|
|||
|
|
router.get('/pending-audit', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const { page = 1, limit = 10 } = req.query;
|
|||
|
|
const pageNum = parseInt(page) || 1;
|
|||
|
|
const limitNum = parseInt(limit) || 10;
|
|||
|
|
const offset = (pageNum - 1) * limitNum;
|
|||
|
|
|
|||
|
|
// 获取待审核用户总数
|
|||
|
|
const [countResult] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as total FROM users WHERE audit_status = ?',
|
|||
|
|
['pending']
|
|||
|
|
);
|
|||
|
|
const total = countResult[0].total;
|
|||
|
|
|
|||
|
|
// 获取待审核用户列表
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
`SELECT id, username, phone, real_name, business_license, id_card_front, id_card_back,
|
|||
|
|
wechat_qr, alipay_qr, unionpay_qr, bank_card, audit_status, created_at
|
|||
|
|
FROM users
|
|||
|
|
WHERE audit_status = ?
|
|||
|
|
ORDER BY created_at ASC
|
|||
|
|
LIMIT ${limitNum} OFFSET ${offset}`,
|
|||
|
|
['pending']
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
users,
|
|||
|
|
pagination: {
|
|||
|
|
page: pageNum,
|
|||
|
|
limit: limitNum,
|
|||
|
|
total,
|
|||
|
|
pages: Math.ceil(total / limitNum)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取待审核用户列表错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取待审核用户列表失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取用户列表用于转账(普通用户权限)
|
|||
|
|
router.get('/for-transfer', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
|
|||
|
|
// 获取所有用户的基本信息(用于转账选择)
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
'SELECT id, username, real_name FROM users WHERE id != ? ORDER BY username',
|
|||
|
|
[req.user.id]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: users
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取转账用户列表错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户列表失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取用户列表(管理员权限)
|
|||
|
|
router.get('/', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const { page = 1, limit = 10, search = '', role = '', city = '', district = '', sort = 'created_at', order = 'desc' } = req.query;
|
|||
|
|
|
|||
|
|
// 确保参数为有效数字
|
|||
|
|
const pageNum = Math.max(1, parseInt(page) || 1);
|
|||
|
|
const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10));
|
|||
|
|
const offset = Math.max(0, (pageNum - 1) * limitNum);
|
|||
|
|
|
|||
|
|
let whereConditions = [];
|
|||
|
|
let countParams = [];
|
|||
|
|
let listParams = [];
|
|||
|
|
|
|||
|
|
// 构建查询条件
|
|||
|
|
if (search) {
|
|||
|
|
whereConditions.push('(u.username LIKE ? OR u.real_name LIKE ?)');
|
|||
|
|
countParams.push(`%${search}%`, `%${search}%`);
|
|||
|
|
listParams.push(`%${search}%`, `%${search}%`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (role && role !== 'all') {
|
|||
|
|
whereConditions.push('u.role = ?');
|
|||
|
|
countParams.push(role);
|
|||
|
|
listParams.push(role);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (city) {
|
|||
|
|
whereConditions.push('u.city = ?');
|
|||
|
|
countParams.push(city);
|
|||
|
|
listParams.push(city);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (district) {
|
|||
|
|
whereConditions.push('u.district_id = ?');
|
|||
|
|
countParams.push(district);
|
|||
|
|
listParams.push(district);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
|
|||
|
|
|
|||
|
|
// 添加分页参数
|
|||
|
|
listParams.push(limitNum.toString(), offset.toString());
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 获取总数
|
|||
|
|
const [countResult] = await db.execute(
|
|||
|
|
`SELECT COUNT(*) as total FROM users u
|
|||
|
|
LEFT JOIN zhejiang_regions r ON u.district_id = r.id
|
|||
|
|
${whereClause}`,
|
|||
|
|
countParams
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 验证排序字段,防止SQL注入
|
|||
|
|
const validSortFields = ['id', 'username', 'role', 'points', 'balance', 'created_at', 'updated_at'];
|
|||
|
|
const sortField = validSortFields.includes(sort) ? sort : 'created_at';
|
|||
|
|
|
|||
|
|
// 验证排序方向
|
|||
|
|
const sortOrder = (order && (order.toUpperCase() === 'ASC' || order.toUpperCase() === 'DESC'))
|
|||
|
|
? order.toUpperCase()
|
|||
|
|
: 'DESC';
|
|||
|
|
|
|||
|
|
// 获取用户列表,关联地区信息和转账统计
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
`SELECT u.id, u.username, u.role, u.avatar, u.points, u.balance, u.real_name, u.id_card, u.phone,
|
|||
|
|
u.wechat_qr, u.alipay_qr, u.bank_card, u.unionpay_qr, u.audit_status, u.is_system_account,
|
|||
|
|
u.created_at, u.updated_at, u.city, u.district_id,u.id_card_front,u.id_card_back,
|
|||
|
|
u.business_license,
|
|||
|
|
r.city_name, r.district_name,
|
|||
|
|
COALESCE(yesterday_out.amount, 0) as yesterday_transfer_amount,
|
|||
|
|
COALESCE(today_in.amount, 0) as today_received_amount
|
|||
|
|
FROM users u
|
|||
|
|
LEFT JOIN zhejiang_regions r ON u.district_id = r.id
|
|||
|
|
LEFT JOIN (
|
|||
|
|
SELECT from_user_id, SUM(amount) as amount
|
|||
|
|
FROM transfers
|
|||
|
|
WHERE created_at >= DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))
|
|||
|
|
AND created_at < DATE(NOW())
|
|||
|
|
AND status IN ('confirmed', 'received')
|
|||
|
|
GROUP BY from_user_id
|
|||
|
|
) yesterday_out ON u.id = yesterday_out.from_user_id
|
|||
|
|
LEFT JOIN (
|
|||
|
|
SELECT to_user_id, SUM(amount) as amount
|
|||
|
|
FROM transfers
|
|||
|
|
WHERE created_at >= DATE(NOW())
|
|||
|
|
AND created_at < DATE(DATE_ADD(NOW(), INTERVAL 1 DAY))
|
|||
|
|
AND status IN ('confirmed', 'received')
|
|||
|
|
GROUP BY to_user_id
|
|||
|
|
) today_in ON u.id = today_in.to_user_id
|
|||
|
|
${whereClause}
|
|||
|
|
ORDER BY u.${sortField} ${sortOrder}
|
|||
|
|
LIMIT ? OFFSET ?`,
|
|||
|
|
listParams
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
users,
|
|||
|
|
total: countResult[0].total,
|
|||
|
|
page: pageNum,
|
|||
|
|
limit: limitNum,
|
|||
|
|
totalPages: Math.ceil(countResult[0].total / limitNum)
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户列表错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户列表失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取当前用户的个人资料
|
|||
|
|
router.get('/profile', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.user.id;
|
|||
|
|
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
'SELECT * FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
const profile = {
|
|||
|
|
...users[0],
|
|||
|
|
nickname: users[0].username, // 添加nickname字段,映射到username
|
|||
|
|
realName: users[0].real_name,
|
|||
|
|
idCard: users[0].id_card,
|
|||
|
|
wechatQr: users[0].wechat_qr,
|
|||
|
|
alipayQr: users[0].alipay_qr,
|
|||
|
|
bankCard: users[0].bank_card,
|
|||
|
|
unionpayQr: users[0].unionpay_qr,
|
|||
|
|
businessLicense: users[0].business_license,
|
|||
|
|
idCardFront: users[0].id_card_front,
|
|||
|
|
idCardBack: users[0].id_card_back
|
|||
|
|
};
|
|||
|
|
res.json({ success: true, user: profile });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户资料错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户资料失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取当前用户的收款码状态
|
|||
|
|
* 用于检查用户是否已上传微信、支付宝、云闪付收款码
|
|||
|
|
*/
|
|||
|
|
router.get('/payment-codes-status', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.user.id;
|
|||
|
|
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
'SELECT wechat_qr, alipay_qr, unionpay_qr FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const paymentCodes = users[0];
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
wechat_qr: paymentCodes.wechat_qr || '',
|
|||
|
|
alipay_qr: paymentCodes.alipay_qr || '',
|
|||
|
|
unionpay_qr: paymentCodes.unionpay_qr || ''
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取收款码状态错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取收款码状态失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取用户收款信息
|
|||
|
|
router.get('/payment-info/:userId', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const targetUserId = req.params.userId;
|
|||
|
|
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
'SELECT id, username, wechat_qr, alipay_qr, unionpay_qr, bank_card FROM users WHERE id = ?',
|
|||
|
|
[targetUserId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const user = users[0];
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
id: user.id,
|
|||
|
|
username: user.username,
|
|||
|
|
wechat_qr: user.wechat_qr,
|
|||
|
|
alipay_qr: user.alipay_qr,
|
|||
|
|
unionpay_qr: user.unionpay_qr,
|
|||
|
|
bank_card: user.bank_card
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户收款信息错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户收款信息失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取当前用户的统计信息
|
|||
|
|
router.get('/stats', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.user.id;
|
|||
|
|
|
|||
|
|
// 如果是管理员,返回全局统计
|
|||
|
|
if (req.user.role === 'admin') {
|
|||
|
|
// 总用户数
|
|||
|
|
const [totalUsers] = await db.execute('SELECT COUNT(*) as count FROM users');
|
|||
|
|
|
|||
|
|
// 管理员数量
|
|||
|
|
const [adminUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "admin"');
|
|||
|
|
|
|||
|
|
// 普通用户数量
|
|||
|
|
const [regularUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "user"');
|
|||
|
|
|
|||
|
|
// 本月新增用户
|
|||
|
|
const [monthUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 上月新增用户
|
|||
|
|
const [lastMonthUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 计算月增长率
|
|||
|
|
const monthGrowthRate = lastMonthUsers[0].count > 0
|
|||
|
|
? ((monthUsers[0].count - lastMonthUsers[0].count) / lastMonthUsers[0].count * 100).toFixed(2)
|
|||
|
|
: 0;
|
|||
|
|
|
|||
|
|
// 用户总积分
|
|||
|
|
const [totalPoints] = await db.execute('SELECT COALESCE(SUM(points), 0) as total FROM users');
|
|||
|
|
|
|||
|
|
// 今日新增用户
|
|||
|
|
const [todayUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = CURDATE()'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 昨日新增用户
|
|||
|
|
const [yesterdayUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 活跃用户数(有订单的用户)
|
|||
|
|
const [activeUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(DISTINCT user_id) as count FROM orders'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
stats: {
|
|||
|
|
totalUsers: totalUsers[0].count,
|
|||
|
|
adminUsers: adminUsers[0].count,
|
|||
|
|
regularUsers: regularUsers[0].count,
|
|||
|
|
monthNewUsers: monthUsers[0].count,
|
|||
|
|
todayUsers: todayUsers[0].count,
|
|||
|
|
yesterdayUsers: yesterdayUsers[0].count,
|
|||
|
|
monthlyGrowth: parseFloat(monthGrowthRate),
|
|||
|
|
totalPoints: totalPoints[0].total,
|
|||
|
|
activeUsers: activeUsers[0].count
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// 普通用户返回个人统计
|
|||
|
|
// 用户订单数
|
|||
|
|
const [orderCount] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM orders WHERE user_id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 用户总消费
|
|||
|
|
const [totalSpent] = await db.execute(
|
|||
|
|
'SELECT COALESCE(SUM(total_amount), 0) as total FROM orders WHERE user_id = ? AND status = "completed"',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 用户积分历史
|
|||
|
|
const [pointsEarned] = await db.execute(
|
|||
|
|
'SELECT COALESCE(SUM(amount), 0) as total FROM points_history WHERE user_id = ? AND type = "earn"',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const [pointsSpent] = await db.execute(
|
|||
|
|
'SELECT COALESCE(SUM(amount), 0) as total FROM points_history WHERE user_id = ? AND type = "spend"',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
stats: {
|
|||
|
|
orderCount: orderCount[0].count,
|
|||
|
|
totalSpent: totalSpent[0].total,
|
|||
|
|
pointsEarned: pointsEarned[0].total,
|
|||
|
|
pointsSpent: pointsSpent[0].total,
|
|||
|
|
currentPoints: req.user.points || 0
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户统计错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户统计失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取用户统计信息(管理员权限)- 必须在/:id路由之前定义
|
|||
|
|
router.get('/admin/stats', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
// 总用户数
|
|||
|
|
const [totalUsers] = await db.execute('SELECT COUNT(*) as count FROM users');
|
|||
|
|
|
|||
|
|
// 管理员数量
|
|||
|
|
const [adminUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "admin"');
|
|||
|
|
|
|||
|
|
// 普通用户数量
|
|||
|
|
const [regularUsers] = await db.execute('SELECT COUNT(*) as count FROM users WHERE role = "user"');
|
|||
|
|
|
|||
|
|
// 本月新增用户
|
|||
|
|
const [monthUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 上月新增用户
|
|||
|
|
const [lastMonthUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM users WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 计算月增长率
|
|||
|
|
const monthGrowthRate = lastMonthUsers[0].count > 0
|
|||
|
|
? ((monthUsers[0].count - lastMonthUsers[0].count) / lastMonthUsers[0].count * 100).toFixed(2)
|
|||
|
|
: 0;
|
|||
|
|
|
|||
|
|
// 用户总积分
|
|||
|
|
const [totalPoints] = await db.execute('SELECT COALESCE(SUM(points), 0) as total FROM users');
|
|||
|
|
|
|||
|
|
// 今日新增用户
|
|||
|
|
const [todayUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = CURDATE()'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 昨日新增用户
|
|||
|
|
const [yesterdayUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 活跃用户数(有订单的用户)
|
|||
|
|
const [activeUsers] = await db.execute(
|
|||
|
|
'SELECT COUNT(DISTINCT user_id) as count FROM orders'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
stats: {
|
|||
|
|
totalUsers: totalUsers[0].count,
|
|||
|
|
adminUsers: adminUsers[0].count,
|
|||
|
|
regularUsers: regularUsers[0].count,
|
|||
|
|
monthNewUsers: monthUsers[0].count,
|
|||
|
|
todayUsers: todayUsers[0].count,
|
|||
|
|
yesterdayUsers: yesterdayUsers[0].count,
|
|||
|
|
monthlyGrowth: parseFloat(monthGrowthRate),
|
|||
|
|
totalPoints: totalPoints[0].total,
|
|||
|
|
activeUsers: activeUsers[0].count
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户统计错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户统计失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取当前用户积分
|
|||
|
|
router.get('/points', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const userId = req.user.id;
|
|||
|
|
|
|||
|
|
const [users] = await getDB().execute(
|
|||
|
|
'SELECT points FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
points: users[0].points
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户积分错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户积分失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取用户积分历史记录
|
|||
|
|
router.get('/points/history', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const userId = req.user.id;
|
|||
|
|
const { page = 1, limit = 20, type } = req.query;
|
|||
|
|
|
|||
|
|
// 确保参数为有效数字
|
|||
|
|
const pageNum = Math.max(1, parseInt(page) || 1);
|
|||
|
|
const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 20));
|
|||
|
|
const offset = Math.max(0, (pageNum - 1) * limitNum);
|
|||
|
|
|
|||
|
|
let whereClause = 'WHERE user_id = ?';
|
|||
|
|
let queryParams = [userId];
|
|||
|
|
|
|||
|
|
if (type && ['earn', 'spend'].includes(type)) {
|
|||
|
|
whereClause += ' AND type = ?';
|
|||
|
|
queryParams.push(type);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取总数
|
|||
|
|
const [countResult] = await getDB().execute(
|
|||
|
|
`SELECT COUNT(*) as total FROM points_history ${whereClause}`,
|
|||
|
|
queryParams
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取历史记录
|
|||
|
|
const [records] = await getDB().execute(
|
|||
|
|
`SELECT id, type, amount, description, order_id, created_at
|
|||
|
|
FROM points_history
|
|||
|
|
${whereClause}
|
|||
|
|
ORDER BY created_at DESC
|
|||
|
|
LIMIT ? OFFSET ?`,
|
|||
|
|
[...queryParams, limitNum.toString(), offset.toString()]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
records,
|
|||
|
|
pagination: {
|
|||
|
|
page: pageNum,
|
|||
|
|
limit: limitNum,
|
|||
|
|
total: countResult[0].total,
|
|||
|
|
totalPages: Math.ceil(countResult[0].total / limitNum)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取积分历史失败:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取积分历史失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取用户增长趋势数据(管理员权限)
|
|||
|
|
router.get('/growth-trend', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const { days = 7 } = req.query;
|
|||
|
|
const daysNum = Math.min(90, Math.max(1, parseInt(days) || 7));
|
|||
|
|
|
|||
|
|
// 获取指定天数内的用户注册趋势
|
|||
|
|
const [trendData] = await db.execute(`
|
|||
|
|
SELECT
|
|||
|
|
DATE(created_at) as date,
|
|||
|
|
COUNT(*) as count
|
|||
|
|
FROM users
|
|||
|
|
WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
|
|||
|
|
GROUP BY DATE(created_at)
|
|||
|
|
ORDER BY date ASC
|
|||
|
|
`, [daysNum]);
|
|||
|
|
|
|||
|
|
// 填充缺失的日期(注册数为0)
|
|||
|
|
const result = [];
|
|||
|
|
|
|||
|
|
for (let i = daysNum - 1; i >= 0; i--) {
|
|||
|
|
const date = dayjs().subtract(i, 'day');
|
|||
|
|
const dateStr = date.format('YYYY-MM-DD');
|
|||
|
|
|
|||
|
|
// 修复日期比较:将数据库返回的Date对象转换为字符串进行比较
|
|||
|
|
const existingData = trendData.find(item => {
|
|||
|
|
const itemDateStr = dayjs(item.date).format('YYYY-MM-DD');
|
|||
|
|
return itemDateStr === dateStr;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
result.push({
|
|||
|
|
date: date.format('MM-DD'),
|
|||
|
|
count: existingData ? existingData.count : 0
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: result
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户增长趋势错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户增长趋势失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取日收入统计数据(管理员权限)
|
|||
|
|
router.get('/daily-revenue', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const { days = 30 } = req.query;
|
|||
|
|
const daysNum = Math.min(90, Math.max(1, parseInt(days) || 30));
|
|||
|
|
|
|||
|
|
// 获取指定天数内的用户注册数据,按天统计
|
|||
|
|
const [dailyData] = await db.execute(`
|
|||
|
|
SELECT
|
|||
|
|
DATE(created_at) as date,
|
|||
|
|
COUNT(*) as user_count
|
|||
|
|
FROM users
|
|||
|
|
WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
|
|||
|
|
GROUP BY DATE(created_at)
|
|||
|
|
ORDER BY date ASC
|
|||
|
|
`, [daysNum]);
|
|||
|
|
|
|||
|
|
// 填充缺失的日期(注册数为0)
|
|||
|
|
const result = [];
|
|||
|
|
|
|||
|
|
for (let i = daysNum - 1; i >= 0; i--) {
|
|||
|
|
const date = dayjs().subtract(i, 'day');
|
|||
|
|
const dateStr = date.format('YYYY-MM-DD'); // YYYY-MM-DD格式
|
|||
|
|
const dateDisplay = date.format('M/D'); // 显示格式
|
|||
|
|
|
|||
|
|
const existingData = dailyData.find(item => {
|
|||
|
|
const itemDateStr = dayjs(item.date).format('YYYY-MM-DD');
|
|||
|
|
return itemDateStr === dateStr;
|
|||
|
|
});
|
|||
|
|
const userCount = existingData ? existingData.user_count : 0;
|
|||
|
|
const revenue = userCount * 398; // 每个用户398元收入
|
|||
|
|
|
|||
|
|
result.push({
|
|||
|
|
date: dateDisplay,
|
|||
|
|
userCount: userCount,
|
|||
|
|
amount: revenue
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: result
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取日收入统计错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取日收入统计失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 生成注册码(管理员权限)==================== 激活码管理 ====================
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成激活码(管理员权限)
|
|||
|
|
*/
|
|||
|
|
router.post('/registration-codes', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const adminId = req.user.id;
|
|||
|
|
|
|||
|
|
// 生成6位随机激活码
|
|||
|
|
const crypto = require('crypto');
|
|||
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|||
|
|
let code = '';
|
|||
|
|
for (let i = 0; i < 6; i++) {
|
|||
|
|
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置过期时间为1小时后
|
|||
|
|
const expiresAt = req.body.expiresAt || new Date(Date.now() + 60 * 60 * 1000);
|
|||
|
|
|
|||
|
|
// 插入激活码
|
|||
|
|
const [result] = await db.execute(
|
|||
|
|
'INSERT INTO registration_codes (code, expires_at, created_by_admin_id) VALUES (?, ?, ?)',
|
|||
|
|
[code, expiresAt, adminId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.status(201).json({
|
|||
|
|
success: true,
|
|||
|
|
message: '激活码生成成功',
|
|||
|
|
data: {
|
|||
|
|
id: result.insertId,
|
|||
|
|
code,
|
|||
|
|
expiresAt,
|
|||
|
|
createdAt: new Date()
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('生成激活码错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '生成激活码失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量生成激活码(管理员权限)
|
|||
|
|
*/
|
|||
|
|
router.post('/registration-codes/batch', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const adminId = req.user.id;
|
|||
|
|
const { count = 1 } = req.body;
|
|||
|
|
|
|||
|
|
// 验证参数
|
|||
|
|
const codeCount = Math.max(1, Math.min(100, parseInt(count) || 1));
|
|||
|
|
|
|||
|
|
const crypto = require('crypto');
|
|||
|
|
const codes = [];
|
|||
|
|
const values = [];
|
|||
|
|
const expiresAt = req.body.expiresAt || new Date(Date.now() + 60 * 60 * 1000);
|
|||
|
|
|
|||
|
|
// 生成指定数量的激活码
|
|||
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|||
|
|
for (let i = 0; i < codeCount; i++) {
|
|||
|
|
let code = '';
|
|||
|
|
for (let j = 0; j < 6; j++) {
|
|||
|
|
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|||
|
|
}
|
|||
|
|
codes.push(code);
|
|||
|
|
values.push(code, expiresAt, adminId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量插入数据库
|
|||
|
|
const placeholders = Array(codeCount).fill('(?, ?, ?)').join(', ');
|
|||
|
|
|
|||
|
|
await db.execute(
|
|||
|
|
`INSERT INTO registration_codes (code, expires_at, created_by_admin_id) VALUES ${placeholders}`,
|
|||
|
|
values
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.status(201).json({
|
|||
|
|
success: true,
|
|||
|
|
message: `成功生成 ${codeCount} 个激活码`,
|
|||
|
|
data: {
|
|||
|
|
codes,
|
|||
|
|
count: codeCount,
|
|||
|
|
expiresAt,
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('批量生成激活码错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '批量生成激活码失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取激活码列表(管理员权限)
|
|||
|
|
*/
|
|||
|
|
router.get('/registration-codes', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const { page = 1, limit = 20, status, keyword, sort = 'created_at', order = 'desc' } = req.query;
|
|||
|
|
const offset = (page - 1) * limit;
|
|||
|
|
|
|||
|
|
let whereClause = '';
|
|||
|
|
let whereConditions = [];
|
|||
|
|
let countParams = [];
|
|||
|
|
let listParams = [];
|
|||
|
|
|
|||
|
|
// 根据状态筛选
|
|||
|
|
if (status === 'unused') {
|
|||
|
|
whereConditions.push('rc.is_used = FALSE AND rc.expires_at > NOW()');
|
|||
|
|
} else if (status === 'used') {
|
|||
|
|
whereConditions.push('rc.is_used = TRUE');
|
|||
|
|
} else if (status === 'expired') {
|
|||
|
|
whereConditions.push('rc.is_used = FALSE AND rc.expires_at <= NOW()');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 关键词搜索
|
|||
|
|
if(keyword){
|
|||
|
|
whereConditions.push(`rc.code LIKE '%${keyword}%'`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建WHERE子句
|
|||
|
|
if (whereConditions.length > 0) {
|
|||
|
|
whereClause = 'WHERE ' + whereConditions.join(' AND ');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理排序参数
|
|||
|
|
const allowedSortFields = ['created_at', 'expires_at', 'used_at', 'code', 'status'];
|
|||
|
|
const allowedOrders = ['asc', 'desc'];
|
|||
|
|
|
|||
|
|
let sortField = 'rc.created_at';
|
|||
|
|
let sortOrder = 'DESC';
|
|||
|
|
|
|||
|
|
if (allowedSortFields.includes(sort)) {
|
|||
|
|
if (sort === 'status') {
|
|||
|
|
// 状态字段需要使用CASE表达式
|
|||
|
|
sortField = `CASE
|
|||
|
|
WHEN rc.is_used = TRUE THEN 'used'
|
|||
|
|
WHEN rc.expires_at <= NOW() THEN 'expired'
|
|||
|
|
ELSE 'unused'
|
|||
|
|
END`;
|
|||
|
|
} else {
|
|||
|
|
sortField = `rc.${sort}`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (allowedOrders.includes(order.toLowerCase())) {
|
|||
|
|
sortOrder = order.toUpperCase();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置查询参数(MySQL驱动需要字符串形式的LIMIT和OFFSET)
|
|||
|
|
const limitStr = String(parseInt(limit));
|
|||
|
|
const offsetStr = String(parseInt(offset));
|
|||
|
|
listParams = [limitStr, offsetStr];
|
|||
|
|
countParams = [];
|
|||
|
|
|
|||
|
|
// 获取激活码列表
|
|||
|
|
const [codes] = await db.execute(`
|
|||
|
|
SELECT
|
|||
|
|
rc.id,
|
|||
|
|
rc.code,
|
|||
|
|
rc.created_at,
|
|||
|
|
rc.expires_at,
|
|||
|
|
rc.used_at,
|
|||
|
|
rc.is_used,
|
|||
|
|
admin.username as created_by_admin,
|
|||
|
|
user.username as used_by_user,
|
|||
|
|
CASE
|
|||
|
|
WHEN rc.is_used = TRUE THEN 'used'
|
|||
|
|
WHEN rc.expires_at <= NOW() THEN 'expired'
|
|||
|
|
ELSE 'unused'
|
|||
|
|
END as status
|
|||
|
|
FROM registration_codes rc
|
|||
|
|
LEFT JOIN users admin ON rc.created_by_admin_id = admin.id
|
|||
|
|
LEFT JOIN users user ON rc.used_by_user_id = user.id
|
|||
|
|
${whereClause}
|
|||
|
|
ORDER BY ${sortField} ${sortOrder}
|
|||
|
|
LIMIT ? OFFSET ?
|
|||
|
|
`, listParams);
|
|||
|
|
|
|||
|
|
// 获取总数
|
|||
|
|
const [countResult] = await db.execute(`
|
|||
|
|
SELECT COUNT(*) as total
|
|||
|
|
FROM registration_codes rc
|
|||
|
|
${whereClause}
|
|||
|
|
`, countParams);
|
|||
|
|
|
|||
|
|
const total = countResult[0].total;
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
codes,
|
|||
|
|
pagination: {
|
|||
|
|
page: parseInt(page),
|
|||
|
|
limit: parseInt(limit),
|
|||
|
|
total,
|
|||
|
|
pages: Math.ceil(total / limit)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取激活码列表错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取激活码列表失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除激活码(管理员权限)
|
|||
|
|
*/
|
|||
|
|
router.delete('/registration-codes/:id', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const codeId = req.params.id;
|
|||
|
|
|
|||
|
|
// 检查激活码是否存在
|
|||
|
|
const [codes] = await db.execute(
|
|||
|
|
'SELECT id, is_used FROM registration_codes WHERE id = ?',
|
|||
|
|
[codeId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (codes.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '激活码不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 不能删除已使用的激活码
|
|||
|
|
if (codes[0].is_used) {
|
|||
|
|
return res.status(400).json({ success: false, message: '不能删除已使用的激活码' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除激活码
|
|||
|
|
await db.execute('DELETE FROM registration_codes WHERE id = ?', [codeId]);
|
|||
|
|
|
|||
|
|
res.json({ success: true, message: '激活码删除成功' });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('删除激活码错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '删除激活码失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// 获取当前用户个人资料
|
|||
|
|
router.get('/profile', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.user.id;
|
|||
|
|
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, business_license, id_card_front, id_card_back, audit_status, created_at, updated_at FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 转换字段名以匹配前端
|
|||
|
|
const user = users[0];
|
|||
|
|
const profile = {
|
|||
|
|
...user,
|
|||
|
|
nickname: user.username, // 添加nickname字段,映射到username
|
|||
|
|
realName: user.real_name,
|
|||
|
|
idCard: user.id_card,
|
|||
|
|
wechatQr: user.wechat_qr,
|
|||
|
|
alipayQr: user.alipay_qr,
|
|||
|
|
bankCard: user.bank_card,
|
|||
|
|
unionpayQr: user.unionpay_qr,
|
|||
|
|
businessLicense: user.business_license,
|
|||
|
|
idCardFront: user.id_card_front,
|
|||
|
|
idCardBack: user.id_card_back,
|
|||
|
|
auditStatus: user.audit_status
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
res.json({ success: true, user: profile });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户个人资料错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户个人资料失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 更新当前用户个人资料
|
|||
|
|
router.put('/profile', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.user.id;
|
|||
|
|
const {
|
|||
|
|
username,
|
|||
|
|
nickname,
|
|||
|
|
avatar,
|
|||
|
|
realName,
|
|||
|
|
idCard,
|
|||
|
|
phone,
|
|||
|
|
wechatQr,
|
|||
|
|
alipayQr,
|
|||
|
|
bankCard,
|
|||
|
|
unionpayQr,
|
|||
|
|
businessLicense,
|
|||
|
|
idCardFront,
|
|||
|
|
idCardBack,
|
|||
|
|
city,
|
|||
|
|
districtId
|
|||
|
|
} = req.body;
|
|||
|
|
|
|||
|
|
// 处理nickname字段,如果提供了nickname,则使用nickname作为username
|
|||
|
|
const finalUsername = nickname || username;
|
|||
|
|
|
|||
|
|
// 检查用户名、身份证号和手机号是否已被其他用户使用
|
|||
|
|
if (finalUsername || idCard || phone) {
|
|||
|
|
const conditions = [];
|
|||
|
|
const checkValues = [];
|
|||
|
|
|
|||
|
|
if (finalUsername) {
|
|||
|
|
conditions.push('username = ?');
|
|||
|
|
checkValues.push(finalUsername);
|
|||
|
|
}
|
|||
|
|
if (idCard) {
|
|||
|
|
conditions.push('id_card = ?');
|
|||
|
|
checkValues.push(idCard);
|
|||
|
|
}
|
|||
|
|
if (phone) {
|
|||
|
|
conditions.push('phone = ?');
|
|||
|
|
checkValues.push(phone);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (conditions.length > 0) {
|
|||
|
|
const [existingUsers] = await db.execute(
|
|||
|
|
`SELECT id FROM users WHERE (${conditions.join(' OR ')}) AND id != ?`,
|
|||
|
|
[...checkValues, userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (existingUsers.length > 0) {
|
|||
|
|
return res.status(400).json({ success: false, message: '用户名、身份证号或手机号已被使用' });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证身份证号格式
|
|||
|
|
if (idCard) {
|
|||
|
|
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
|
|||
|
|
if (!idCardRegex.test(idCard)) {
|
|||
|
|
return res.status(400).json({ success: false, message: '身份证号格式不正确' });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建更新字段
|
|||
|
|
const updateFields = [];
|
|||
|
|
const updateValues = [];
|
|||
|
|
|
|||
|
|
if (finalUsername !== undefined) {
|
|||
|
|
updateFields.push('username = ?');
|
|||
|
|
updateValues.push(finalUsername);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (avatar !== undefined) {
|
|||
|
|
updateFields.push('avatar = ?');
|
|||
|
|
updateValues.push(avatar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (realName !== undefined) {
|
|||
|
|
updateFields.push('real_name = ?');
|
|||
|
|
updateValues.push(realName);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (idCard !== undefined) {
|
|||
|
|
updateFields.push('id_card = ?');
|
|||
|
|
updateValues.push(idCard);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (phone !== undefined) {
|
|||
|
|
updateFields.push('phone = ?');
|
|||
|
|
updateValues.push(phone);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加城市和地区字段更新
|
|||
|
|
if (city !== undefined) {
|
|||
|
|
updateFields.push('city = ?');
|
|||
|
|
updateValues.push(city);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (districtId !== undefined) {
|
|||
|
|
updateFields.push('district_id = ?');
|
|||
|
|
updateValues.push(districtId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否更新了需要重新审核的关键信息
|
|||
|
|
let needsReaudit = false;
|
|||
|
|
|
|||
|
|
if (wechatQr !== undefined) {
|
|||
|
|
updateFields.push('wechat_qr = ?');
|
|||
|
|
updateValues.push(wechatQr);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (alipayQr !== undefined) {
|
|||
|
|
updateFields.push('alipay_qr = ?');
|
|||
|
|
updateValues.push(alipayQr);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (bankCard !== undefined) {
|
|||
|
|
updateFields.push('bank_card = ?');
|
|||
|
|
updateValues.push(bankCard);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (unionpayQr !== undefined) {
|
|||
|
|
updateFields.push('unionpay_qr = ?');
|
|||
|
|
updateValues.push(unionpayQr);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (city !== undefined) {
|
|||
|
|
updateFields.push('city = ?');
|
|||
|
|
updateValues.push(city);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (districtId !== undefined) {
|
|||
|
|
updateFields.push('district_id = ?');
|
|||
|
|
updateValues.push(districtId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (businessLicense !== undefined) {
|
|||
|
|
updateFields.push('business_license = ?');
|
|||
|
|
updateValues.push(businessLicense);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (idCardFront !== undefined) {
|
|||
|
|
updateFields.push('id_card_front = ?');
|
|||
|
|
updateValues.push(idCardFront);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (idCardBack !== undefined) {
|
|||
|
|
updateFields.push('id_card_back = ?');
|
|||
|
|
updateValues.push(idCardBack);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果更新了关键信息且用户不是管理员,则重置审核状态为待审核
|
|||
|
|
if (needsReaudit && req.user.role !== 'admin') {
|
|||
|
|
updateFields.push('audit_status = ?');
|
|||
|
|
updateValues.push('pending');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (updateFields.length === 0) {
|
|||
|
|
return res.status(400).json({ success: false, message: '没有要更新的字段' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
updateValues.push(userId);
|
|||
|
|
|
|||
|
|
await db.execute(
|
|||
|
|
`UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
|
|||
|
|
updateValues
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 返回更新后的用户信息
|
|||
|
|
const [updatedUsers] = await db.execute(
|
|||
|
|
'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, business_license, id_card_front, id_card_back, audit_status, is_system_account, created_at, updated_at FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 转换字段名以匹配前端
|
|||
|
|
const user = updatedUsers[0];
|
|||
|
|
const profile = {
|
|||
|
|
...user,
|
|||
|
|
nickname: user.username, // 添加nickname字段,映射到username
|
|||
|
|
realName: user.real_name,
|
|||
|
|
idCard: user.id_card,
|
|||
|
|
wechatQr: user.wechat_qr,
|
|||
|
|
alipayQr: user.alipay_qr,
|
|||
|
|
bankCard: user.bank_card,
|
|||
|
|
unionpayQr: user.unionpay_qr,
|
|||
|
|
businessLicense: user.business_license,
|
|||
|
|
idCardFront: user.id_card_front,
|
|||
|
|
idCardBack: user.id_card_back,
|
|||
|
|
auditStatus: user.audit_status
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
message: '个人资料更新成功',
|
|||
|
|
data: profile
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('更新个人资料错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '更新个人资料失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// 获取用户详情
|
|||
|
|
router.get('/:id', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.params.id;
|
|||
|
|
|
|||
|
|
// 只有管理员或用户本人可以查看详情
|
|||
|
|
if (req.user.role !== 'admin' && req.user.id != userId) {
|
|||
|
|
return res.status(403).json({ success: false, message: '权限不足' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, created_at, updated_at FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json({ success: true, user: users[0] });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户详情错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户详情失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 更新用户信息
|
|||
|
|
router.put('/:id', auth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.params.id;
|
|||
|
|
const {
|
|||
|
|
username,
|
|||
|
|
password,
|
|||
|
|
role,
|
|||
|
|
isSystemAccount,
|
|||
|
|
avatar,
|
|||
|
|
realName,
|
|||
|
|
idCard,
|
|||
|
|
phone,
|
|||
|
|
wechatQr,
|
|||
|
|
alipayQr,
|
|||
|
|
bankCard,
|
|||
|
|
unionpayQr,
|
|||
|
|
city,
|
|||
|
|
districtId,
|
|||
|
|
idCardFront,
|
|||
|
|
idCardBack,
|
|||
|
|
businessLicense,
|
|||
|
|
} = req.body;
|
|||
|
|
|
|||
|
|
// 只有管理员或用户本人可以更新信息
|
|||
|
|
if (req.user.role !== 'admin' && req.user.id != userId) {
|
|||
|
|
return res.status(403).json({ success: false, message: '权限不足' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 非管理员不能修改角色
|
|||
|
|
if (req.user.role !== 'admin' && role) {
|
|||
|
|
return res.status(403).json({ success: false, message: '无权限修改用户角色' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查用户名、身份证号和手机号是否已被其他用户使用
|
|||
|
|
if (username || idCard || phone) {
|
|||
|
|
const conditions = [];
|
|||
|
|
const checkValues = [];
|
|||
|
|
|
|||
|
|
if (username) {
|
|||
|
|
conditions.push('username = ?');
|
|||
|
|
checkValues.push(username);
|
|||
|
|
}
|
|||
|
|
if (idCard) {
|
|||
|
|
conditions.push('id_card = ?');
|
|||
|
|
checkValues.push(idCard);
|
|||
|
|
}
|
|||
|
|
if (phone) {
|
|||
|
|
conditions.push('phone = ?');
|
|||
|
|
checkValues.push(phone);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (conditions.length > 0) {
|
|||
|
|
const [existingUsers] = await db.execute(
|
|||
|
|
`SELECT id FROM users WHERE (${conditions.join(' OR ')}) AND id != ?`,
|
|||
|
|
[...checkValues, userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (existingUsers.length > 0) {
|
|||
|
|
return res.status(400).json({ success: false, message: '用户名、身份证号或手机号已被使用' });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证身份证号格式
|
|||
|
|
if (idCard) {
|
|||
|
|
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
|
|||
|
|
if (!idCardRegex.test(idCard)) {
|
|||
|
|
return res.status(400).json({ success: false, message: '身份证号格式不正确' });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建更新字段
|
|||
|
|
const updateFields = [];
|
|||
|
|
const updateValues = [];
|
|||
|
|
|
|||
|
|
if (username) {
|
|||
|
|
updateFields.push('username = ?');
|
|||
|
|
updateValues.push(username);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理密码更新
|
|||
|
|
if (password && password.trim() !== '') {
|
|||
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|||
|
|
updateFields.push('password = ?');
|
|||
|
|
updateValues.push(hashedPassword);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (role && req.user.role === 'admin') {
|
|||
|
|
updateFields.push('role = ?');
|
|||
|
|
updateValues.push(role);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 只有管理员可以修改账户类型
|
|||
|
|
if (isSystemAccount !== undefined && req.user.role === 'admin') {
|
|||
|
|
updateFields.push('is_system_account = ?');
|
|||
|
|
updateValues.push(isSystemAccount);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (avatar !== undefined) {
|
|||
|
|
updateFields.push('avatar = ?');
|
|||
|
|
updateValues.push(avatar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (realName !== undefined) {
|
|||
|
|
updateFields.push('real_name = ?');
|
|||
|
|
updateValues.push(realName);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (idCard !== undefined) {
|
|||
|
|
updateFields.push('id_card = ?');
|
|||
|
|
updateValues.push(idCard);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (phone !== undefined) {
|
|||
|
|
updateFields.push('phone = ?');
|
|||
|
|
updateValues.push(phone);
|
|||
|
|
}
|
|||
|
|
if (city !== undefined) {
|
|||
|
|
updateFields.push('city = ?');
|
|||
|
|
updateValues.push(city);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (districtId !== undefined) {
|
|||
|
|
updateFields.push('district_id = ?');
|
|||
|
|
updateValues.push(districtId);
|
|||
|
|
}
|
|||
|
|
// 检查是否更新了需要重新审核的关键信息
|
|||
|
|
let needsReaudit = false;
|
|||
|
|
|
|||
|
|
if (wechatQr !== undefined) {
|
|||
|
|
updateFields.push('wechat_qr = ?');
|
|||
|
|
updateValues.push(wechatQr);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (alipayQr !== undefined) {
|
|||
|
|
updateFields.push('alipay_qr = ?');
|
|||
|
|
updateValues.push(alipayQr);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (bankCard !== undefined) {
|
|||
|
|
updateFields.push('bank_card = ?');
|
|||
|
|
updateValues.push(bankCard);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (unionpayQr !== undefined) {
|
|||
|
|
updateFields.push('unionpay_qr = ?');
|
|||
|
|
updateValues.push(unionpayQr);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
if (idCardFront !== undefined) {
|
|||
|
|
updateFields.push('id_card_front = ?');
|
|||
|
|
updateValues.push(idCardFront);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
if (idCardBack !== undefined) {
|
|||
|
|
updateFields.push('id_card_back = ?');
|
|||
|
|
updateValues.push(idCardBack);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
if (businessLicense !== undefined) {
|
|||
|
|
updateFields.push('business_license = ?');
|
|||
|
|
updateValues.push(businessLicense);
|
|||
|
|
needsReaudit = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果更新了关键信息且用户不是管理员,则重置审核状态为待审核
|
|||
|
|
if (needsReaudit && req.user.role !== 'admin') {
|
|||
|
|
updateFields.push('audit_status = ?');
|
|||
|
|
updateValues.push('pending');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (updateFields.length === 0) {
|
|||
|
|
return res.status(400).json({ success: false, message: '没有要更新的字段' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
updateValues.push(userId);
|
|||
|
|
|
|||
|
|
await db.execute(
|
|||
|
|
`UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
|
|||
|
|
updateValues
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 返回更新后的用户信息
|
|||
|
|
const [updatedUsers] = await db.execute(
|
|||
|
|
'SELECT id, username, role, avatar, points, real_name, id_card, phone, wechat_qr, alipay_qr, bank_card, unionpay_qr, city, district_id, created_at, updated_at FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
message: '用户信息更新成功',
|
|||
|
|
user: updatedUsers[0]
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('更新用户信息错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '更新用户信息失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 删除用户(管理员权限)
|
|||
|
|
router.delete('/:id', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.params.id;
|
|||
|
|
|
|||
|
|
// 不能删除自己
|
|||
|
|
if (req.user.id == userId) {
|
|||
|
|
return res.status(400).json({ success: false, message: '不能删除自己的账户' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查用户是否存在
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
'SELECT id FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除用户
|
|||
|
|
await db.execute('DELETE FROM users WHERE id = ?', [userId]);
|
|||
|
|
|
|||
|
|
res.json({ success: true, message: '用户删除成功' });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('删除用户错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '删除用户失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 审核用户(管理员权限)
|
|||
|
|
*/
|
|||
|
|
router.put('/:id/audit', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.params.id;
|
|||
|
|
const { action, note } = req.body; // action: 'approve' 或 'reject'
|
|||
|
|
|
|||
|
|
if (!action || !['approve', 'reject'].includes(action)) {
|
|||
|
|
return res.status(400).json({ success: false, message: '审核操作无效' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查用户是否存在且为待审核状态
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
'SELECT id, username, audit_status FROM users WHERE id = ?',
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const user = users[0];
|
|||
|
|
if (user.audit_status !== 'pending') {
|
|||
|
|
return res.status(400).json({ success: false, message: '该用户不是待审核状态' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新审核状态
|
|||
|
|
const auditStatus = action === 'approve' ? 'approved' : 'rejected';
|
|||
|
|
await db.execute(
|
|||
|
|
`UPDATE users SET
|
|||
|
|
audit_status = ?,
|
|||
|
|
audit_note = ?,
|
|||
|
|
audited_by = ?,
|
|||
|
|
audited_at = NOW()
|
|||
|
|
WHERE id = ?`,
|
|||
|
|
[auditStatus, note || null, req.user.id, userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const message = action === 'approve' ? '用户审核通过' : '用户审核拒绝';
|
|||
|
|
res.json({ success: true, message });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('审核用户错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '审核用户失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取用户审核详情(管理员权限)
|
|||
|
|
*/
|
|||
|
|
router.get('/:id/audit-detail', auth, adminAuth, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
const userId = req.params.id;
|
|||
|
|
|
|||
|
|
// 获取用户详细信息
|
|||
|
|
const [users] = await db.execute(
|
|||
|
|
`SELECT u.id, u.username, u.phone, u.real_name, u.business_license,
|
|||
|
|
u.id_card_front, u.id_card_back, u.audit_status, u.audit_note,
|
|||
|
|
u.audited_at, u.created_at,
|
|||
|
|
auditor.username as auditor_name
|
|||
|
|
FROM users u
|
|||
|
|
LEFT JOIN users auditor ON u.audited_by = auditor.id
|
|||
|
|
WHERE u.id = ?`,
|
|||
|
|
[userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (users.length === 0) {
|
|||
|
|
return res.status(404).json({ success: false, message: '用户不存在' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: users[0]
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户审核详情错误:', error);
|
|||
|
|
res.status(500).json({ success: false, message: '获取用户审核详情失败' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
module.exports = router;
|