docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
218
backend/routes/auth.js
Normal file
218
backend/routes/auth.js
Normal file
@@ -0,0 +1,218 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const validator = require('validator');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
|
||||
const router = express.Router();
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
router.post('/register', async (req, res, next) => {
|
||||
try {
|
||||
const { username, password, phone, email, user_type = 'farmer' } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!username || !password || !phone) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '用户名、密码和手机号为必填项',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '密码长度不能少于6位',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (!validator.isMobilePhone(phone, 'zh-CN')) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '手机号格式不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (email && !validator.isEmail(email)) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '邮箱格式不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户是否已存在
|
||||
const existingUser = await dbConnector.query(
|
||||
'SELECT id FROM users WHERE username = ? OR phone = ? OR email = ?',
|
||||
[username, phone, email]
|
||||
);
|
||||
|
||||
if (existingUser.length > 0) {
|
||||
return res.status(409).json({
|
||||
code: 409,
|
||||
message: '用户名、手机号或邮箱已存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
|
||||
// 创建用户
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO users (username, password_hash, phone, email, user_type) VALUES (?, ?, ?, ?, ?)',
|
||||
[username, hashedPassword, phone, email, user_type]
|
||||
);
|
||||
|
||||
// 生成JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: result.insertId, username, user_type },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '注册成功',
|
||||
data: {
|
||||
user_id: result.insertId,
|
||||
username,
|
||||
phone,
|
||||
email,
|
||||
user_type,
|
||||
token
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
router.post('/login', async (req, res, next) => {
|
||||
try {
|
||||
const { login, password } = req.body;
|
||||
|
||||
if (!login || !password) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '登录账号和密码为必填项',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户(支持用户名、手机号、邮箱登录)
|
||||
const user = await dbConnector.query(
|
||||
'SELECT * FROM users WHERE (username = ? OR phone = ? OR email = ?) AND status = 1',
|
||||
[login, login, login]
|
||||
);
|
||||
|
||||
if (user.length === 0) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '用户不存在或已被禁用',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const userData = user[0];
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await bcrypt.compare(password, userData.password_hash);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '密码不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 更新最后登录时间
|
||||
await dbConnector.query(
|
||||
'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[userData.id]
|
||||
);
|
||||
|
||||
// 生成JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: userData.id, username: userData.username, user_type: userData.user_type },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
user_id: userData.id,
|
||||
username: userData.username,
|
||||
phone: userData.phone,
|
||||
email: userData.email,
|
||||
user_type: userData.user_type,
|
||||
avatar_url: userData.avatar_url,
|
||||
token
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
router.get('/me', async (req, res, next) => {
|
||||
try {
|
||||
// 从token中获取用户ID
|
||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '未提供认证token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
const user = await dbConnector.query(
|
||||
'SELECT id, username, phone, email, user_type, avatar_url, created_at, last_login FROM users WHERE id = ? AND status = 1',
|
||||
[decoded.userId]
|
||||
);
|
||||
|
||||
if (user.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: user[0]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '无效的token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
355
backend/routes/identifications.js
Normal file
355
backend/routes/identifications.js
Normal file
@@ -0,0 +1,355 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 配置multer用于文件上传
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const uploadDir = path.join(__dirname, '../uploads/identifications');
|
||||
|
||||
// 确保上传目录存在
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, 'identification-' + uniqueSuffix + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024, // 10MB限制
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// 只允许图片文件
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('只支持图片文件'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取识别历史记录
|
||||
*/
|
||||
router.get('/', asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 10 } = req.query;
|
||||
const userId = req.user.id;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// 查询识别记录
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, user_id, image_url, result, confidence, created_at
|
||||
FROM identifications
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[userId, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 查询总数
|
||||
const totalResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
identifications,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取单条识别记录详情
|
||||
*/
|
||||
router.get('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, user_id, image_url, result, confidence, created_at
|
||||
FROM identifications
|
||||
WHERE id = ? AND user_id = ?`,
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (identifications.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: identifications[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 花卉识别接口
|
||||
*/
|
||||
router.post('/identify', upload.single('image'), asyncHandler(async (req, res) => {
|
||||
const userId = req.user.id;
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '请上传图片文件',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 这里应该调用AI识别服务
|
||||
// 由于AI识别服务需要额外配置,这里先模拟识别结果
|
||||
|
||||
const imageUrl = `/uploads/identifications/${req.file.filename}`;
|
||||
|
||||
// 模拟AI识别结果(实际项目中应该调用真实的AI服务)
|
||||
const mockResults = [
|
||||
{ name: '玫瑰', confidence: 0.95, scientificName: 'Rosa rugosa', description: '玫瑰是蔷薇科蔷薇属的植物,具有浓郁的芳香和美丽的花朵。' },
|
||||
{ name: '百合', confidence: 0.87, scientificName: 'Lilium brownii', description: '百合是百合科百合属的植物,象征纯洁和高雅。' },
|
||||
{ name: '菊花', confidence: 0.82, scientificName: 'Chrysanthemum morifolium', description: '菊花是菊科菊属的植物,具有很高的观赏和药用价值。' }
|
||||
];
|
||||
|
||||
// 选择置信度最高的结果
|
||||
const bestResult = mockResults[0];
|
||||
|
||||
// 保存识别记录到数据库
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO identifications (user_id, image_url, result, confidence) VALUES (?, ?, ?, ?)',
|
||||
[userId, imageUrl, JSON.stringify(mockResults), bestResult.confidence]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '识别成功',
|
||||
data: {
|
||||
identification_id: result.insertId,
|
||||
image_url: imageUrl,
|
||||
results: mockResults,
|
||||
best_result: bestResult,
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 批量识别历史记录
|
||||
*/
|
||||
router.get('/batch/history', asyncHandler(async (req, res) => {
|
||||
const { start_date, end_date, min_confidence = 0.7 } = req.query;
|
||||
const userId = req.user.id;
|
||||
|
||||
let whereClause = 'WHERE user_id = ? AND confidence >= ?';
|
||||
let queryParams = [userId, parseFloat(min_confidence)];
|
||||
|
||||
if (start_date) {
|
||||
whereClause += ' AND created_at >= ?';
|
||||
queryParams.push(start_date);
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
whereClause += ' AND created_at <= ?';
|
||||
queryParams.push(end_date + ' 23:59:59');
|
||||
}
|
||||
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, image_url, result, confidence, created_at,
|
||||
DATE(created_at) as identify_date
|
||||
FROM identifications
|
||||
${whereClause}
|
||||
ORDER BY created_at DESC`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
// 按日期分组
|
||||
const groupedByDate = {};
|
||||
identifications.forEach(record => {
|
||||
const date = record.identify_date;
|
||||
if (!groupedByDate[date]) {
|
||||
groupedByDate[date] = [];
|
||||
}
|
||||
groupedByDate[date].push(record);
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total: identifications.length,
|
||||
by_date: groupedByDate,
|
||||
statistics: {
|
||||
total_count: identifications.length,
|
||||
avg_confidence: identifications.reduce((sum, item) => sum + item.confidence, 0) / identifications.length,
|
||||
date_range: {
|
||||
start: identifications.length > 0 ? identifications[identifications.length - 1].identify_date : null,
|
||||
end: identifications.length > 0 ? identifications[0].identify_date : null
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 识别统计信息
|
||||
*/
|
||||
router.get('/stats/summary', asyncHandler(async (req, res) => {
|
||||
const userId = req.user.id;
|
||||
|
||||
// 总识别次数
|
||||
const totalCountResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 平均置信度
|
||||
const avgConfidenceResult = await dbConnector.query(
|
||||
'SELECT AVG(confidence) as avg_confidence FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 最近识别时间
|
||||
const lastIdentificationResult = await dbConnector.query(
|
||||
'SELECT created_at FROM identifications WHERE user_id = ? ORDER BY created_at DESC LIMIT 1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 识别最多的花卉类型(需要解析result字段)
|
||||
const allIdentifications = await dbConnector.query(
|
||||
'SELECT result FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
const flowerCounts = {};
|
||||
allIdentifications.forEach(item => {
|
||||
try {
|
||||
const results = JSON.parse(item.result);
|
||||
if (results && results.length > 0) {
|
||||
const bestResult = results[0];
|
||||
flowerCounts[bestResult.name] = (flowerCounts[bestResult.name] || 0) + 1;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析识别结果失败:', e);
|
||||
}
|
||||
});
|
||||
|
||||
// 找出识别最多的花卉
|
||||
let mostIdentifiedFlower = null;
|
||||
let maxCount = 0;
|
||||
for (const [flower, count] of Object.entries(flowerCounts)) {
|
||||
if (count > maxCount) {
|
||||
mostIdentifiedFlower = flower;
|
||||
maxCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total_count: totalCountResult[0].total,
|
||||
avg_confidence: avgConfidenceResult[0].avg_confidence || 0,
|
||||
last_identification: lastIdentificationResult[0] ? lastIdentificationResult[0].created_at : null,
|
||||
most_identified_flower: mostIdentifiedFlower,
|
||||
flower_counts: flowerCounts,
|
||||
weekly_trend: await getWeeklyTrend(userId)
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取周趋势数据
|
||||
*/
|
||||
async function getWeeklyTrend(userId) {
|
||||
const weeklyData = await dbConnector.query(
|
||||
`SELECT
|
||||
DATE(created_at) as date,
|
||||
COUNT(*) as count,
|
||||
AVG(confidence) as avg_confidence
|
||||
FROM identifications
|
||||
WHERE user_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date DESC`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
return weeklyData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除识别记录
|
||||
*/
|
||||
router.delete('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 先获取记录信息以删除图片文件
|
||||
const identification = await dbConnector.query(
|
||||
'SELECT image_url FROM identifications WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (identification.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 删除图片文件
|
||||
if (identification[0].image_url) {
|
||||
const imagePath = path.join(__dirname, '../', identification[0].image_url);
|
||||
if (fs.existsSync(imagePath)) {
|
||||
fs.unlinkSync(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
const result = await dbConnector.query(
|
||||
'DELETE FROM identifications WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
379
backend/routes/orders.js
Normal file
379
backend/routes/orders.js
Normal file
@@ -0,0 +1,379 @@
|
||||
const express = require('express');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
router.get('/', asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 10, status, start_date, end_date } = req.query;
|
||||
const userId = req.user.id;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause = 'WHERE o.user_id = ?';
|
||||
let queryParams = [userId];
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND o.payment_status = ?';
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (start_date) {
|
||||
whereClause += ' AND o.created_at >= ?';
|
||||
queryParams.push(start_date);
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
whereClause += ' AND o.created_at <= ?';
|
||||
queryParams.push(end_date + ' 23:59:59');
|
||||
}
|
||||
|
||||
// 查询订单列表
|
||||
const orders = await dbConnector.query(
|
||||
`SELECT
|
||||
o.id, o.order_number, o.user_id, o.total_amount, o.payment_status,
|
||||
o.shipping_status, o.shipping_address, o.created_at, o.updated_at,
|
||||
u.username, u.phone
|
||||
FROM orders o
|
||||
LEFT JOIN users u ON o.user_id = u.id
|
||||
${whereClause}
|
||||
ORDER BY o.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 查询总数
|
||||
const totalResult = await dbConnector.query(
|
||||
`SELECT COUNT(*) as total
|
||||
FROM orders o
|
||||
${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
orders,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
*/
|
||||
router.get('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 查询订单基本信息
|
||||
const orders = await dbConnector.query(
|
||||
`SELECT
|
||||
o.id, o.order_number, o.user_id, o.total_amount, o.payment_status,
|
||||
o.shipping_status, o.shipping_address, o.created_at, o.updated_at,
|
||||
u.username, u.phone, u.email
|
||||
FROM orders o
|
||||
LEFT JOIN users u ON o.user_id = u.id
|
||||
WHERE o.id = ? AND o.user_id = ?`,
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (orders.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '订单不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const order = orders[0];
|
||||
|
||||
// 查询订单商品明细
|
||||
const orderItems = await dbConnector.query(
|
||||
`SELECT
|
||||
oi.id, oi.order_id, oi.product_id, oi.quantity, oi.unit_price,
|
||||
p.name as product_name, p.image as product_image
|
||||
FROM order_items oi
|
||||
LEFT JOIN products p ON oi.product_id = p.id
|
||||
WHERE oi.order_id = ?`,
|
||||
[id]
|
||||
);
|
||||
|
||||
order.items = orderItems;
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: order
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*/
|
||||
router.post('/', asyncHandler(async (req, res) => {
|
||||
const { items, shipping_address } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 参数验证
|
||||
if (!items || !Array.isArray(items) || items.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '订单商品不能为空',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (!shipping_address) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '收货地址不能为空',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbConnector.getConnection();
|
||||
await connection.beginTransaction();
|
||||
|
||||
// 验证商品库存和价格
|
||||
let totalAmount = 0;
|
||||
const productUpdates = [];
|
||||
|
||||
for (const item of items) {
|
||||
const { product_id, quantity } = item;
|
||||
|
||||
if (!product_id || !quantity || quantity <= 0) {
|
||||
throw { code: 400, message: '商品ID和数量必须为正数' };
|
||||
}
|
||||
|
||||
// 查询商品信息
|
||||
const products = await connection.query(
|
||||
'SELECT id, name, price, stock FROM products WHERE id = ? AND status = 1',
|
||||
[product_id]
|
||||
);
|
||||
|
||||
if (products.length === 0) {
|
||||
throw { code: 404, message: `商品ID ${product_id} 不存在` };
|
||||
}
|
||||
|
||||
const product = products[0];
|
||||
|
||||
// 检查库存
|
||||
if (product.stock < quantity) {
|
||||
throw { code: 400, message: `商品 ${product.name} 库存不足` };
|
||||
}
|
||||
|
||||
const itemTotal = product.price * quantity;
|
||||
totalAmount += itemTotal;
|
||||
|
||||
// 记录商品更新信息
|
||||
productUpdates.push({
|
||||
product_id,
|
||||
quantity,
|
||||
unit_price: product.price,
|
||||
new_stock: product.stock - quantity
|
||||
});
|
||||
}
|
||||
|
||||
// 生成订单号
|
||||
const orderNumber = 'O' + Date.now() + Math.random().toString(36).substr(2, 6);
|
||||
|
||||
// 创建订单
|
||||
const orderResult = await connection.query(
|
||||
'INSERT INTO orders (order_number, user_id, total_amount, shipping_address) VALUES (?, ?, ?, ?)',
|
||||
[orderNumber, userId, totalAmount, shipping_address]
|
||||
);
|
||||
|
||||
const orderId = orderResult.insertId;
|
||||
|
||||
// 创建订单商品明细
|
||||
for (const item of items) {
|
||||
const { product_id, quantity } = item;
|
||||
const productInfo = productUpdates.find(p => p.product_id === product_id);
|
||||
|
||||
await connection.query(
|
||||
'INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (?, ?, ?, ?)',
|
||||
[orderId, product_id, quantity, productInfo.unit_price]
|
||||
);
|
||||
|
||||
// 更新商品库存
|
||||
await connection.query(
|
||||
'UPDATE products SET stock = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[productInfo.new_stock, product_id]
|
||||
);
|
||||
}
|
||||
|
||||
await connection.commit();
|
||||
|
||||
// 获取完整的订单信息
|
||||
const newOrder = await dbConnector.query(
|
||||
'SELECT * FROM orders WHERE id = ?',
|
||||
[orderId]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '订单创建成功',
|
||||
data: newOrder[0]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
await connection.rollback();
|
||||
}
|
||||
|
||||
if (error.code && error.message) {
|
||||
return res.status(error.code).json({
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
router.post('/:id/cancel', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbConnector.getConnection();
|
||||
await connection.beginTransaction();
|
||||
|
||||
// 查询订单信息
|
||||
const orders = await connection.query(
|
||||
'SELECT id, payment_status, shipping_status FROM orders WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (orders.length === 0) {
|
||||
throw { code: 404, message: '订单不存在' };
|
||||
}
|
||||
|
||||
const order = orders[0];
|
||||
|
||||
// 检查订单状态是否可以取消
|
||||
if (order.payment_status === 'paid' || order.shipping_status === 'shipped') {
|
||||
throw { code: 400, message: '订单已支付或已发货,无法取消' };
|
||||
}
|
||||
|
||||
// 恢复商品库存
|
||||
const orderItems = await connection.query(
|
||||
'SELECT product_id, quantity FROM order_items WHERE order_id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
for (const item of orderItems) {
|
||||
await connection.query(
|
||||
'UPDATE products SET stock = stock + ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[item.quantity, item.product_id]
|
||||
);
|
||||
}
|
||||
|
||||
// 更新订单状态为已取消
|
||||
await connection.query(
|
||||
'UPDATE orders SET payment_status = "cancelled", updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
await connection.commit();
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '订单取消成功',
|
||||
data: null
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
await connection.rollback();
|
||||
}
|
||||
|
||||
if (error.code && error.message) {
|
||||
return res.status(error.code).json({
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* 更新订单状态(支付成功回调)
|
||||
*/
|
||||
router.put('/:id/status', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { payment_status, shipping_status } = req.body;
|
||||
|
||||
if (!payment_status && !shipping_status) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '需要提供支付状态或发货状态',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const updateFields = [];
|
||||
const updateValues = [];
|
||||
|
||||
if (payment_status) {
|
||||
updateFields.push('payment_status = ?');
|
||||
updateValues.push(payment_status);
|
||||
}
|
||||
|
||||
if (shipping_status) {
|
||||
updateFields.push('shipping_status = ?');
|
||||
updateValues.push(shipping_status);
|
||||
}
|
||||
|
||||
updateFields.push('updated_at = CURRENT_TIMESTAMP');
|
||||
updateValues.push(id);
|
||||
|
||||
const result = await dbConnector.query(
|
||||
`UPDATE orders SET ${updateFields.join(', ')} WHERE id = ?`,
|
||||
updateValues
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '订单不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '订单状态更新成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
336
backend/routes/products.js
Normal file
336
backend/routes/products.js
Normal file
@@ -0,0 +1,336 @@
|
||||
const express = require('express');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { optionalAuth } = require('../middlewares/auth');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
*/
|
||||
router.get('/', optionalAuth, asyncHandler(async (req, res) => {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 12,
|
||||
category_id,
|
||||
keyword,
|
||||
min_price,
|
||||
max_price,
|
||||
sort_by = 'created_at',
|
||||
sort_order = 'desc'
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
let whereClause = 'WHERE p.status = 1';
|
||||
let queryParams = [];
|
||||
|
||||
// 构建查询条件
|
||||
if (category_id) {
|
||||
whereClause += ' AND p.category_id = ?';
|
||||
queryParams.push(category_id);
|
||||
}
|
||||
|
||||
if (keyword) {
|
||||
whereClause += ' AND (p.name LIKE ? OR p.description LIKE ?)';
|
||||
const likeKeyword = `%${keyword}%`;
|
||||
queryParams.push(likeKeyword, likeKeyword);
|
||||
}
|
||||
|
||||
if (min_price) {
|
||||
whereClause += ' AND p.price >= ?';
|
||||
queryParams.push(parseFloat(min_price));
|
||||
}
|
||||
|
||||
if (max_price) {
|
||||
whereClause += ' AND p.price <= ?';
|
||||
queryParams.push(parseFloat(max_price));
|
||||
}
|
||||
|
||||
// 验证排序参数
|
||||
const validSortFields = ['name', 'price', 'created_at', 'stock'];
|
||||
const validSortOrders = ['asc', 'desc'];
|
||||
|
||||
const sortField = validSortFields.includes(sort_by) ? sort_by : 'created_at';
|
||||
const sortOrder = validSortOrders.includes(sort_order) ? sort_order : 'desc';
|
||||
|
||||
// 查询商品列表
|
||||
const products = await dbConnector.query(
|
||||
`SELECT
|
||||
p.id, p.name, p.category_id, p.price, p.stock, p.image,
|
||||
p.description, p.status, p.created_at, p.updated_at,
|
||||
c.name as category_name
|
||||
FROM products p
|
||||
LEFT JOIN categories c ON p.category_id = c.id
|
||||
${whereClause}
|
||||
ORDER BY p.${sortField} ${sortOrder}
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 查询总数
|
||||
const totalResult = await dbConnector.query(
|
||||
`SELECT COUNT(*) as total
|
||||
FROM products p
|
||||
${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
products,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
*/
|
||||
router.get('/:id', optionalAuth, asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const products = await dbConnector.query(
|
||||
`SELECT
|
||||
p.id, p.name, p.category_id, p.price, p.stock, p.image,
|
||||
p.description, p.status, p.created_at, p.updated_at,
|
||||
c.name as category_name
|
||||
FROM products p
|
||||
LEFT JOIN categories c ON p.category_id = c.id
|
||||
WHERE p.id = ? AND p.status = 1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (products.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '商品不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: products[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 创建商品(需要管理员权限)
|
||||
*/
|
||||
router.post('/', asyncHandler(async (req, res) => {
|
||||
const { name, category_id, price, stock, image, description } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!name || !category_id || price === undefined || stock === undefined) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '商品名称、分类、价格和库存为必填项',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (price < 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '价格不能为负数',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (stock < 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '库存不能为负数',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 检查分类是否存在
|
||||
const category = await dbConnector.query(
|
||||
'SELECT id FROM categories WHERE id = ? AND status = 1',
|
||||
[category_id]
|
||||
);
|
||||
|
||||
if (category.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '分类不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 创建商品
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO products (name, category_id, price, stock, image, description) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[name, category_id, price, stock, image, description]
|
||||
);
|
||||
|
||||
// 获取创建的商品信息
|
||||
const newProduct = await dbConnector.query(
|
||||
'SELECT * FROM products WHERE id = ?',
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '商品创建成功',
|
||||
data: newProduct[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 更新商品信息
|
||||
*/
|
||||
router.put('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name, category_id, price, stock, image, description, status } = req.body;
|
||||
|
||||
// 检查商品是否存在
|
||||
const existingProduct = await dbConnector.query(
|
||||
'SELECT id FROM products WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (existingProduct.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '商品不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
if (price !== undefined && price < 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '价格不能为负数',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (stock !== undefined && stock < 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '库存不能为负数',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (category_id !== undefined) {
|
||||
const category = await dbConnector.query(
|
||||
'SELECT id FROM categories WHERE id = ? AND status = 1',
|
||||
[category_id]
|
||||
);
|
||||
|
||||
if (category.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '分类不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 构建更新字段
|
||||
const updateFields = [];
|
||||
const updateValues = [];
|
||||
|
||||
if (name !== undefined) {
|
||||
updateFields.push('name = ?');
|
||||
updateValues.push(name);
|
||||
}
|
||||
|
||||
if (category_id !== undefined) {
|
||||
updateFields.push('category_id = ?');
|
||||
updateValues.push(category_id);
|
||||
}
|
||||
|
||||
if (price !== undefined) {
|
||||
updateFields.push('price = ?');
|
||||
updateValues.push(price);
|
||||
}
|
||||
|
||||
if (stock !== undefined) {
|
||||
updateFields.push('stock = ?');
|
||||
updateValues.push(stock);
|
||||
}
|
||||
|
||||
if (image !== undefined) {
|
||||
updateFields.push('image = ?');
|
||||
updateValues.push(image);
|
||||
}
|
||||
|
||||
if (description !== undefined) {
|
||||
updateFields.push('description = ?');
|
||||
updateValues.push(description);
|
||||
}
|
||||
|
||||
if (status !== undefined) {
|
||||
updateFields.push('status = ?');
|
||||
updateValues.push(status);
|
||||
}
|
||||
|
||||
if (updateFields.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '没有提供需要更新的字段',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
updateFields.push('updated_at = CURRENT_TIMESTAMP');
|
||||
updateValues.push(id);
|
||||
|
||||
await dbConnector.query(
|
||||
`UPDATE products SET ${updateFields.join(', ')} WHERE id = ?`,
|
||||
updateValues
|
||||
);
|
||||
|
||||
// 获取更新后的商品信息
|
||||
const updatedProduct = await dbConnector.query(
|
||||
'SELECT * FROM products WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '商品更新成功',
|
||||
data: updatedProduct[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 删除商品(软删除)
|
||||
*/
|
||||
router.delete('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await dbConnector.query(
|
||||
'UPDATE products SET status = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '商品不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '商品删除成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
269
backend/routes/users.js
Normal file
269
backend/routes/users.js
Normal file
@@ -0,0 +1,269 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const validator = require('validator');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { adminRequired } = require('../middlewares/auth');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取用户列表(管理员权限)
|
||||
*/
|
||||
router.get('/', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 10, keyword, user_type } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause = 'WHERE status = 1';
|
||||
let queryParams = [];
|
||||
|
||||
if (keyword) {
|
||||
whereClause += ' AND (username LIKE ? OR phone LIKE ? OR email LIKE ?)';
|
||||
const likeKeyword = `%${keyword}%`;
|
||||
queryParams.push(likeKeyword, likeKeyword, likeKeyword);
|
||||
}
|
||||
|
||||
if (user_type) {
|
||||
whereClause += ' AND user_type = ?';
|
||||
queryParams.push(user_type);
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
const users = await dbConnector.query(
|
||||
`SELECT id, username, phone, email, user_type, avatar_url, created_at, last_login
|
||||
FROM users ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 获取总数
|
||||
const totalResult = await dbConnector.query(
|
||||
`SELECT COUNT(*) as total FROM users ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
users,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*/
|
||||
router.get('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const users = await dbConnector.query(
|
||||
`SELECT id, username, phone, email, user_type, avatar_url,
|
||||
real_name, created_at, last_login
|
||||
FROM users WHERE id = ? AND status = 1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: users[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
router.put('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { email, real_name, avatar_url } = req.body;
|
||||
|
||||
// 检查用户是否存在
|
||||
const existingUser = await dbConnector.query(
|
||||
'SELECT id FROM users WHERE id = ? AND status = 1',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (existingUser.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 验证邮箱格式
|
||||
if (email && !validator.isEmail(email)) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '邮箱格式不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 检查邮箱是否已被其他用户使用
|
||||
if (email) {
|
||||
const emailUser = await dbConnector.query(
|
||||
'SELECT id FROM users WHERE email = ? AND id != ?',
|
||||
[email, id]
|
||||
);
|
||||
|
||||
if (emailUser.length > 0) {
|
||||
return res.status(409).json({
|
||||
code: 409,
|
||||
message: '邮箱已被其他用户使用',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 构建更新字段
|
||||
const updateFields = [];
|
||||
const updateValues = [];
|
||||
|
||||
if (email !== undefined) {
|
||||
updateFields.push('email = ?');
|
||||
updateValues.push(email);
|
||||
}
|
||||
|
||||
if (real_name !== undefined) {
|
||||
updateFields.push('real_name = ?');
|
||||
updateValues.push(real_name);
|
||||
}
|
||||
|
||||
if (avatar_url !== undefined) {
|
||||
updateFields.push('avatar_url = ?');
|
||||
updateValues.push(avatar_url);
|
||||
}
|
||||
|
||||
if (updateFields.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '没有提供需要更新的字段',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
updateFields.push('updated_at = CURRENT_TIMESTAMP');
|
||||
updateValues.push(id);
|
||||
|
||||
await dbConnector.query(
|
||||
`UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
|
||||
updateValues
|
||||
);
|
||||
|
||||
// 获取更新后的用户信息
|
||||
const updatedUser = await dbConnector.query(
|
||||
'SELECT id, username, phone, email, user_type, avatar_url, real_name FROM users WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: updatedUser[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
router.put('/:id/password', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { old_password, new_password } = req.body;
|
||||
|
||||
if (!old_password || !new_password) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '原密码和新密码为必填项',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (new_password.length < 6) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '新密码长度不能少于6位',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户当前密码
|
||||
const users = await dbConnector.query(
|
||||
'SELECT password_hash FROM users WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 验证原密码
|
||||
const isValidPassword = await bcrypt.compare(old_password, users[0].password_hash);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '原密码不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
const hashedPassword = await bcrypt.hash(new_password, 12);
|
||||
|
||||
await dbConnector.query(
|
||||
'UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[hashedPassword, id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '密码修改成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 删除用户(软删除)
|
||||
*/
|
||||
router.delete('/:id', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await dbConnector.query(
|
||||
'UPDATE users SET status = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '用户删除成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user