const express = require('express'); const { getDB } = require('../database'); const { auth, adminAuth } = require('../middleware/auth'); const router = express.Router(); /** * @swagger * tags: * name: Orders * description: 订单管理相关接口 */ /** * @swagger * components: * schemas: * Order: * type: object * properties: * id: * type: integer * description: 订单ID * order_no: * type: string * description: 订单编号 * user_id: * type: integer * description: 用户ID * total_amount: * type: number * description: 订单总金额 * total_points: * type: number * description: 订单总积分 * status: * type: string * enum: [pending, shipped, completed, cancelled] * description: 订单状态 * address: * type: string * description: 收货地址 * created_at: * type: string * format: date-time * description: 创建时间 * updated_at: * type: string * format: date-time * description: 更新时间 * username: * type: string * description: 用户名 */ // 生成订单号 function generateOrderNo() { const timestamp = Date.now().toString(); const random = Math.random().toString(36).substr(2, 5); return `ORD${timestamp}${random}`.toUpperCase(); } /** * @swagger * /api/orders: * get: * summary: 获取订单列表 * tags: [Orders] * security: * - bearerAuth: [] * parameters: * - in: query * name: page * schema: * type: integer * default: 1 * description: 页码 * - in: query * name: limit * schema: * type: integer * default: 10 * description: 每页数量 * - in: query * name: search * schema: * type: string * description: 搜索关键词(订单号或用户名) * - in: query * name: orderNumber * schema: * type: string * description: 订单号 * - in: query * name: username * schema: * type: string * description: 用户名 * - in: query * name: status * schema: * type: string * enum: [pending, shipped, completed, cancelled] * description: 订单状态 * - in: query * name: startDate * schema: * type: string * format: date * description: 开始日期 * - in: query * name: endDate * schema: * type: string * format: date * description: 结束日期 * responses: * 200: * description: 成功获取订单列表 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * data: * type: object * properties: * orders: * type: array * items: * $ref: '#/components/schemas/Order' * pagination: * type: object * properties: * page: * type: integer * limit: * type: integer * total: * type: integer * pages: * type: integer * 401: * description: 未授权 * 500: * description: 服务器错误 */ router.get('/', auth, async (req, res) => { try { const { page = 1, limit = 10, search = '', orderNumber = '', username = '', status = '', startDate = '', endDate = '' } = req.query; // 确保参数为有效数字 const pageNum = parseInt(page) || 1; const limitNum = parseInt(limit) || 10; const offset = (pageNum - 1) * limitNum; const isAdmin = req.user.role === 'admin'; let whereClause = 'WHERE 1=1'; const params = []; // 非管理员只能查看自己的订单 if (!isAdmin) { whereClause += ' AND o.user_id = ?'; params.push(req.user.id); } if (search) { whereClause += ' AND (o.order_no LIKE ? OR u.username LIKE ?)'; params.push(`%${search}%`, `%${search}%`); } if (orderNumber) { whereClause += ' AND o.order_no LIKE ?'; params.push(`%${orderNumber}%`); } if (username ) { whereClause += ' AND u.username LIKE ?'; params.push(`%${username}%`); } if (status && status.trim()) { whereClause += ' AND o.status = ?'; params.push(status); } if (startDate && startDate.trim()) { whereClause += ' AND DATE(o.created_at) >= ?'; params.push(startDate); } if (endDate && endDate.trim()) { whereClause += ' AND DATE(o.created_at) <= ?'; params.push(endDate); } // 获取总数 const countQuery = ` SELECT COUNT(*) as total FROM orders as o LEFT JOIN users u ON o.user_id = u.id ${whereClause} `; console.log(countQuery,params); const [countResult] = await getDB().execute(countQuery, params); const total = countResult[0].total; console.log(total,'数量'); // 获取订单列表 const query = ` SELECT o.id, o.order_no, o.user_id, o.total_amount, o.total_points, o.status, o.address, o.created_at, o.updated_at, u.username FROM orders o LEFT JOIN users u ON o.user_id = u.id ${whereClause} ORDER BY o.created_at DESC LIMIT ${limitNum} OFFSET ${offset} `; const [orders] = await getDB().execute(query, [...params]); res.json({ success: true, data: { orders, pagination: { page: pageNum, limit: limitNum, total, pages: Math.ceil(total / limitNum) } } }); } catch (error) { console.error('获取订单列表失败:', error); res.status(500).json({ success: false, message: '获取订单列表失败' }); } }); /** * @swagger * /api/orders/{id}: * get: * summary: 获取单个订单详情 * tags: [Orders] * security: * - bearerAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: 订单ID * responses: * 200: * description: 成功获取订单详情 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * data: * type: object * properties: * order: * $ref: '#/components/schemas/Order' * 401: * description: 未授权 * 404: * description: 订单不存在 * 500: * description: 服务器错误 */ router.get('/:id', auth, async (req, res) => { try { const { id } = req.params; const isAdmin = req.user.role === 'admin'; let whereClause = 'WHERE o.id = ?'; const params = [id]; // 非管理员只能查看自己的订单 if (!isAdmin) { whereClause += ' AND o.user_id = ?'; params.push(req.user.id); } const query = ` SELECT o.id, o.order_no, o.user_id, o.total_amount, o.total_points, o.status, o.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} `; const [orders] = await getDB().execute(query, params); if (orders.length === 0) { return res.status(404).json({ success: false, message: '订单不存在' }); } res.json({ success: true, data: { order: orders[0] } }); } catch (error) { console.error('获取订单详情失败:', error); res.status(500).json({ success: false, message: '获取订单详情失败' }); } }); /** * @swagger * /api/orders: * post: * summary: 创建订单 * tags: [Orders] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * product_id: * type: integer * description: 商品ID * quantity: * type: integer * description: 购买数量 * shipping_address: * type: string * description: 收货地址 * required: * - product_id * - quantity * - shipping_address * responses: * 201: * description: 订单创建成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * data: * type: object * properties: * orderId: * type: integer * orderNumber: * type: string * pointsUsed: * type: integer * 400: * description: 参数错误或积分不足 * 401: * description: 未授权 * 404: * description: 商品不存在或已下架 * 500: * description: 服务器错误 */ router.post('/', auth, async (req, res) => { const db = getDB(); await db.query('START TRANSACTION'); try { const { product_id, quantity, shipping_address } = req.body; const user_id = req.user.id; // 验证必填字段 if (!product_id || !quantity || !shipping_address) { return res.status(400).json({ success: false, message: '请填写所有必填字段' }); } // 检查商品是否存在且有效 const [products] = await db.execute( 'SELECT id, name, points_price, stock, status FROM products WHERE id = ?', [product_id] ); if (products.length === 0 || products[0].status !== 'active') { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '商品不存在或已下架' }); } const product = products[0]; // 检查库存 if (product.stock < quantity) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '库存不足' }); } // 计算总积分 const totalPoints = product.points_price * quantity; // 检查用户积分是否足够 const [users] = await db.execute( 'SELECT id, username, points FROM users WHERE id = ?', [user_id] ); if (users.length === 0) { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '用户不存在' }); } const user = users[0]; if (user.points < totalPoints) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '积分不足' }); } // 生成订单号 const orderNumber = 'ORD' + Date.now() + Math.random().toString(36).substr(2, 4).toUpperCase(); // 创建订单 const [orderResult] = await db.execute( `INSERT INTO orders (order_no, user_id, total_amount, total_points, status, address, created_at, updated_at) VALUES (?, ?, ?, ?, 'pending', ?, NOW(), NOW())`, [orderNumber, user_id, 0, totalPoints, shipping_address] ); // 扣除用户积分 await db.execute( 'UPDATE users SET points = points - ? WHERE id = ?', [totalPoints, user_id] ); // 减少商品库存 await db.execute( 'UPDATE products SET stock = stock - ? WHERE id = ?', [quantity, product_id] ); // 记录积分历史 await db.execute( `INSERT INTO points_history (user_id, amount, type, description, created_at) VALUES (?, ?, 'spend', ?, NOW())`, [user_id, -totalPoints, `购买商品:${product.name}`] ); await db.query('COMMIT'); res.status(201).json({ success: true, message: '订单创建成功', data: { orderId: orderResult.insertId, orderNumber, pointsUsed: totalPoints } }); } catch (error) { await db.query('ROLLBACK'); console.error('创建订单失败:', error); res.status(500).json({ success: false, message: '创建订单失败' }); } }); /** * @swagger * /api/orders/{id}/cancel: * put: * summary: 用户取消订单 * tags: [Orders] * security: * - bearerAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: 订单ID * responses: * 200: * description: 订单取消成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * 400: * description: 只能取消待处理的订单 * 401: * description: 未授权 * 404: * description: 订单不存在 * 500: * description: 服务器错误 */ router.put('/:id/cancel', auth, async (req, res) => { const db = getDB(); await db.query('START TRANSACTION'); try { const orderId = req.params.id; const userId = req.user.id; // 检查订单是否存在且属于当前用户 const [orders] = await db.execute( 'SELECT id, user_id, total_points, status FROM orders WHERE id = ? AND user_id = ?', [orderId, userId] ); if (orders.length === 0) { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '订单不存在' }); } const order = orders[0]; if (order.status !== 'pending') { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '只能取消待处理的订单' }); } // 退还用户积分 await db.execute( 'UPDATE users SET points = points + ? WHERE id = ?', [order.total_points, userId] ); // 记录积分历史 await db.execute( `INSERT INTO points_history (user_id, amount, type, description, created_at) VALUES (?, ?, 'refund', '订单取消退还积分', NOW())`, [userId, order.total_points] ); // 更新订单状态 await db.execute( 'UPDATE orders SET status = "cancelled", updated_at = NOW() WHERE id = ?', [orderId] ); await db.query('COMMIT'); res.json({ success: true, message: '订单已取消' }); } catch (error) { await db.query('ROLLBACK'); console.error('取消订单失败:', error); res.status(500).json({ success: false, message: '取消订单失败' }); } }); /** * @swagger * /api/orders/{id}/confirm: * put: * summary: 确认收货 * tags: [Orders] * security: * - bearerAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: 订单ID * responses: * 200: * description: 确认收货成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * 400: * description: 只能确认已发货的订单 * 401: * description: 未授权 * 404: * description: 订单不存在 * 500: * description: 服务器错误 */ router.put('/:id/confirm', auth, async (req, res) => { try { const orderId = req.params.id; const userId = req.user.id; // 检查订单是否存在且属于当前用户 const [orders] = await getDB().execute( 'SELECT id, status FROM orders WHERE id = ? AND user_id = ?', [orderId, userId] ); if (orders.length === 0) { return res.status(404).json({ success: false, message: '订单不存在' }); } const order = orders[0]; if (order.status !== 'shipped') { return res.status(400).json({ success: false, message: '只能确认已发货的订单' }); } // 更新订单状态 await getDB().execute( 'UPDATE orders SET status = "completed", updated_at = NOW() WHERE id = ?', [orderId] ); res.json({ success: true, message: '确认收货成功' }); } catch (error) { console.error('确认收货失败:', error); res.status(500).json({ success: false, message: '确认收货失败' }); } }); /** * @swagger * /api/orders/{id}/status: * put: * summary: 更新订单状态(管理员) * tags: [Orders] * security: * - bearerAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: 订单ID * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * status: * type: string * enum: [pending, shipped, completed, cancelled] * description: 订单状态 * required: * - status * responses: * 200: * description: 订单状态更新成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * 400: * description: 无效的订单状态 * 401: * description: 未授权 * 403: * description: 无管理员权限 * 404: * description: 订单不存在 * 500: * description: 服务器错误 */ router.put('/:id/status', auth, adminAuth, async (req, res) => { const db = getDB(); await db.query('START TRANSACTION'); try { const orderId = req.params.id; const { status } = req.body; const validStatuses = ['pending', 'shipped', 'completed', 'cancelled']; if (!validStatuses.includes(status)) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '无效的订单状态' }); } // 检查订单是否存在 const [orders] = await db.execute( 'SELECT id, user_id, total_points, status FROM orders WHERE id = ?', [orderId] ); if (orders.length === 0) { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '订单不存在' }); } const order = orders[0]; // 如果是取消订单,需要退还积分 if (status === 'cancelled' && order.status !== 'cancelled') { // 退还用户积分 await db.execute( 'UPDATE users SET points = points + ? WHERE id = ?', [order.total_points, order.user_id] ); // 记录积分历史 await db.execute( `INSERT INTO points_history (user_id, amount, type, description, created_at) VALUES (?, ?, 'earn', '订单取消退还积分', NOW())`, [order.user_id, order.points_cost] ); } // 更新订单状态 await db.execute( 'UPDATE orders SET status = ?, updated_at = NOW() WHERE id = ?', [status, orderId] ); await db.query('COMMIT'); res.json({ success: true, message: '订单状态已更新' }); } catch (error) { await db.query('ROLLBACK'); console.error('更新订单状态失败:', error); res.status(500).json({ success: false, message: '更新订单状态失败' }); } }); /** * @swagger * /api/orders/stats: * get: * summary: 获取订单统计信息(管理员权限) * tags: [Orders] * security: * - bearerAuth: [] * responses: * 200: * description: 成功获取订单统计信息 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * data: * type: object * properties: * totalOrders: * type: integer * description: 总订单数 * pendingOrders: * type: integer * description: 待发货订单数 * completedOrders: * type: integer * description: 已完成订单数 * monthOrders: * type: integer * description: 本月新增订单数 * monthGrowthRate: * type: number * description: 月增长率 * totalPointsConsumed: * type: number * description: 总积分消费 * 401: * description: 未授权 * 403: * description: 无管理员权限 * 500: * description: 服务器错误 */ router.get('/stats', auth, adminAuth, async (req, res) => { try { // 总订单数 const [totalOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders'); // 待发货订单数 const [pendingOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders WHERE status = "pending"'); // 已完成订单数 const [completedOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders WHERE status = "completed"'); // 本月新增订单 const [monthOrders] = await getDB().execute( 'SELECT COUNT(*) as count FROM orders WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())' ); // 上月订单数(用于计算增长率) const [lastMonthOrders] = await getDB().execute( 'SELECT COUNT(*) as count FROM orders WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))' ); // 计算月增长率 const lastMonthCount = lastMonthOrders[0].count; const currentMonthCount = monthOrders[0].count; let monthGrowthRate = 0; if (lastMonthCount > 0) { monthGrowthRate = ((currentMonthCount - lastMonthCount) / lastMonthCount * 100).toFixed(1); } // 总积分消费 const [totalPointsConsumed] = await getDB().execute('SELECT SUM(points_cost) as total FROM orders WHERE status != "cancelled"'); res.json({ success: true, data: { totalOrders: totalOrders[0].count, pendingOrders: pendingOrders[0].count, completedOrders: completedOrders[0].count, monthOrders: monthOrders[0].count, monthGrowthRate: parseFloat(monthGrowthRate), totalPointsConsumed: totalPointsConsumed[0].total || 0 } }); } catch (error) { console.error('获取订单统计失败:', error); res.status(500).json({ success: false, message: '获取订单统计失败' }); } }); module.exports = router;