1851 lines
54 KiB
JavaScript
1851 lines
54 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();
|
||
|
||
/**
|
||
* @swagger
|
||
* tags:
|
||
* name: Users
|
||
* description: 用户管理API
|
||
*/
|
||
|
||
/**
|
||
* @swagger
|
||
* components:
|
||
* schemas:
|
||
* User:
|
||
* type: object
|
||
* required:
|
||
* - username
|
||
* - password
|
||
* - real_name
|
||
* - id_card
|
||
* properties:
|
||
* id:
|
||
* type: integer
|
||
* description: 用户ID
|
||
* username:
|
||
* type: string
|
||
* description: 用户名
|
||
* role:
|
||
* type: string
|
||
* description: 用户角色
|
||
* enum: [user, admin, merchant]
|
||
* avatar:
|
||
* type: string
|
||
* description: 用户头像URL
|
||
* points:
|
||
* type: integer
|
||
* description: 用户积分
|
||
* real_name:
|
||
* type: string
|
||
* description: 真实姓名
|
||
* id_card:
|
||
* type: string
|
||
* description: 身份证号
|
||
* phone:
|
||
* type: string
|
||
* description: 手机号
|
||
* is_system_account:
|
||
* type: boolean
|
||
* description: 是否为系统账户
|
||
* is_distribute:
|
||
* type: boolean
|
||
* description: 是否为分发账户
|
||
* created_at:
|
||
* type: string
|
||
* format: date-time
|
||
* description: 创建时间
|
||
* updated_at:
|
||
* type: string
|
||
* format: date-time
|
||
* description: 更新时间
|
||
*/
|
||
|
||
/**
|
||
* @swagger
|
||
* /users:
|
||
* post:
|
||
* summary: 创建用户(管理员权限)
|
||
* tags: [Users]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - username
|
||
* - password
|
||
* - real_name
|
||
* - id_card
|
||
* properties:
|
||
* username:
|
||
* type: string
|
||
* password:
|
||
* type: string
|
||
* role:
|
||
* type: string
|
||
* enum: [user, admin, merchant]
|
||
* default: user
|
||
* is_system_account:
|
||
* type: boolean
|
||
* default: false
|
||
* real_name:
|
||
* type: string
|
||
* id_card:
|
||
* type: string
|
||
* wechat_qr:
|
||
* type: string
|
||
* alipay_qr:
|
||
* type: string
|
||
* bank_card:
|
||
* type: string
|
||
* unionpay_qr:
|
||
* type: string
|
||
* phone:
|
||
* type: string
|
||
* responses:
|
||
* 201:
|
||
* description: 用户创建成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* message:
|
||
* type: string
|
||
* user:
|
||
* $ref: '#/components/schemas/User'
|
||
* 400:
|
||
* description: 请求参数错误
|
||
* 401:
|
||
* description: 未授权
|
||
* 403:
|
||
* description: 权限不足
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
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: '创建用户失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /users/pending-audit:
|
||
* get:
|
||
* summary: 获取待审核用户列表(管理员权限)
|
||
* tags: [Users]
|
||
* 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:
|
||
* users:
|
||
* type: array
|
||
* items:
|
||
* $ref: '#/components/schemas/User'
|
||
* pagination:
|
||
* type: object
|
||
* properties:
|
||
* page:
|
||
* type: integer
|
||
* limit:
|
||
* type: integer
|
||
* total:
|
||
* type: integer
|
||
* pages:
|
||
* type: integer
|
||
* 401:
|
||
* description: 未授权
|
||
* 403:
|
||
* description: 权限不足
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
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,u.is_distribute,
|
||
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 ${limitNum} OFFSET ${offset}`,
|
||
listParams.slice(0, -2)
|
||
);
|
||
|
||
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 ${limitNum} OFFSET ${offset}`,
|
||
queryParams
|
||
);
|
||
|
||
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 ${limit} OFFSET ${offset}
|
||
`, countParams);
|
||
|
||
// 获取总数
|
||
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: '获取用户审核详情失败' });
|
||
}
|
||
});
|
||
/**
|
||
* @swagger
|
||
* /api/users/{id}/distribute:
|
||
* put:
|
||
* summary: 设置用户分发状态
|
||
* description: 更新指定用户的分发状态
|
||
* tags: [Users]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: id
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 用户ID
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - is_distribute
|
||
* properties:
|
||
* is_distribute:
|
||
* type: boolean
|
||
* description: 分发状态,true为启用分发,false为禁用分发
|
||
* example: true
|
||
* responses:
|
||
* 200:
|
||
* description: 分发状态更新成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* example: true
|
||
* message:
|
||
* type: string
|
||
* example: "分发状态更新成功"
|
||
* is_distribute:
|
||
* type: boolean
|
||
* description: 更新后的分发状态
|
||
* example: true
|
||
* 400:
|
||
* description: 请求参数错误
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* example: false
|
||
* message:
|
||
* type: string
|
||
* example: "分发状态无效"
|
||
* 404:
|
||
* description: 用户不存在
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* example: false
|
||
* message:
|
||
* type: string
|
||
* example: "用户不存在"
|
||
* 500:
|
||
* description: 服务器内部错误
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* example: false
|
||
* message:
|
||
* type: string
|
||
* example: "服务器内部错误"
|
||
*/
|
||
router.put('/:id/distribute', auth, async (req, res) => {
|
||
try {
|
||
const db = getDB();
|
||
const userId = req.params.id;
|
||
const { is_distribute } = req.body;
|
||
|
||
if (typeof is_distribute !== 'boolean') {
|
||
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(
|
||
'UPDATE users SET is_distribute = ? WHERE id = ?',
|
||
[is_distribute, userId]
|
||
);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '分发状态更新成功',
|
||
is_distribute
|
||
});
|
||
} catch (error) {
|
||
|
||
}
|
||
})
|
||
|
||
module.exports = router; |