const express = require('express'); const { getDB } = require('../database'); const { auth } = require('../middleware/auth'); const router = express.Router(); /** * @swagger * tags: * name: Cart * description: 购物车管理相关接口 */ /** * @swagger * components: * schemas: * CartItem: * type: object * properties: * id: * type: integer * description: 购物车项ID * user_id: * type: integer * description: 用户ID * product_id: * type: integer * description: 商品ID * quantity: * type: integer * description: 商品数量 * spec_combination_id: * type: integer * description: 商品规格组合ID * created_at: * type: string * format: date-time * description: 创建时间 * updated_at: * type: string * format: date-time * description: 更新时间 * product: * type: object * properties: * id: * type: integer * name: * type: string * price: * type: integer * points_price: * type: integer * rongdou_price: * type: integer * image_url: * type: string * stock: * type: integer * status: * type: string */ /** * @swagger * /api/cart: * get: * summary: 获取购物车列表 * tags: [Cart] * security: * - bearerAuth: [] * responses: * 200: * description: 获取购物车成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * data: * type: object * properties: * items: * type: array * items: * $ref: '#/components/schemas/CartItem' * total_count: * type: integer * description: 购物车商品总数量 * total_amount: * type: integer * description: 购物车总金额 * total_points: * type: integer * description: 购物车总积分 * total_rongdou: * type: integer * description: 购物车总融豆 * 401: * description: 未授权 * 500: * description: 服务器错误 */ router.get('/', auth, async (req, res) => { try { const userId = req.user.id; // 获取购物车商品列表 const query = ` SELECT c.id, c.user_id, c.product_id, c.quantity, c.specification_id, c.created_at, c.updated_at, p.name, p.price, p.points_price, p.rongdou_price, p.image_url, p.stock, p.status, p.shop_name, p.shop_avatar, psc.combination_key, psc.price_adjustment, psc.points_adjustment, psc.rongdou_adjustment, psc.stock as spec_stock, GROUP_CONCAT(CONCAT(sn.display_name, ':', sv.display_value) ORDER BY sn.sort_order SEPARATOR ' | ') as spec_display FROM cart_items c LEFT JOIN products p ON c.product_id = p.id LEFT JOIN product_spec_combinations psc ON c.specification_id = psc.id LEFT JOIN JSON_TABLE(psc.spec_values, '$[*]' COLUMNS (spec_value_id INT PATH '$')) jt ON psc.id IS NOT NULL LEFT JOIN spec_values sv ON jt.spec_value_id = sv.id LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id WHERE c.user_id = ? AND p.status = 'active' GROUP BY c.id ORDER BY c.created_at DESC `; const [cartItems] = await getDB().execute(query, [userId]); // 计算总计信息 let totalCount = 0; let totalAmount = 0; let totalPoints = 0; let totalRongdou = 0; const items = cartItems.map(item => { const finalPrice = item.price + (item.price_adjustment || 0); const finalPointsPrice = item.points_price + (item.points_adjustment || 0); const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0); totalCount += item.quantity; totalAmount += finalPrice * item.quantity; totalPoints += finalPointsPrice * item.quantity; totalRongdou += finalRongdouPrice * item.quantity; return { id: item.id, user_id: item.user_id, product_id: item.product_id, quantity: item.quantity, spec_combination_id: item.spec_combination_id, created_at: item.created_at, updated_at: item.updated_at, product: { id: item.product_id, name: item.name, price: finalPrice, points_price: finalPointsPrice, rongdou_price: finalRongdouPrice, image_url: item.image_url, stock: item.spec_combination_id ? item.spec_stock : item.stock, status: item.status, shop_name: item.shop_name, shop_avatar: item.shop_avatar }, specification: item.spec_combination_id ? { id: item.spec_combination_id, combination_key: item.combination_key, spec_display: item.spec_display, price_adjustment: item.price_adjustment, points_adjustment: item.points_adjustment, rongdou_adjustment: item.rongdou_adjustment } : null }; }); res.json({ success: true, data: { items, total_count: totalCount, total_amount: totalAmount, total_points: totalPoints, total_rongdou: totalRongdou } }); } catch (error) { console.error('获取购物车失败:', error); res.status(500).json({ success: false, message: '获取购物车失败' }); } }); /** * @swagger * /api/cart: * post: * summary: 添加商品到购物车 * tags: [Cart] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * product_id: * type: integer * description: 商品ID * quantity: * type: integer * description: 商品数量 * minimum: 1 * spec_combination_id: * type: integer * description: 商品规格组合ID(可选) * required: * - product_id * - quantity * responses: * 201: * description: 添加到购物车成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * data: * type: object * properties: * cart_item_id: * type: integer * 400: * description: 参数错误或库存不足 * 401: * description: 未授权 * 404: * description: 商品不存在或已下架 * 500: * description: 服务器错误 */ router.post('/add', auth, async (req, res) => { const db = getDB(); await db.query('START TRANSACTION'); try { const { productId, quantity, specificationId } = req.body; const userId = req.user.id; // 验证必填字段 if (!productId || !quantity || quantity < 1) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '请填写正确的商品信息和数量' }); } // 检查商品是否存在且有效 const [products] = await db.execute( 'SELECT id, name, stock, status FROM products WHERE id = ?', [productId] ); if (products.length === 0 || products[0].status !== 'active') { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '商品不存在或已下架' }); } const product = products[0]; let availableStock = product.stock; // 如果指定了规格组合,检查规格组合库存 if (specificationId) { const [specs] = await db.execute( 'SELECT id, stock, status FROM product_spec_combinations WHERE id = ? AND product_id = ?', [specificationId, productId] ); if (specs.length === 0 || specs[0].status !== 'active') { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '商品规格组合不存在或已下架' }); } availableStock = specs[0].stock; } // 检查购物车中是否已存在相同商品和规格组合 const [existingItems] = await db.execute( 'SELECT id, quantity FROM cart_items WHERE user_id = ? AND product_id = ? AND (specification_id = ? OR (specification_id IS NULL AND ? IS NULL))', [userId, productId, specificationId, specificationId] ); let finalQuantity = quantity; if (existingItems.length > 0) { finalQuantity += existingItems[0].quantity; } // 检查库存是否足够 if (availableStock < finalQuantity) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '库存不足' }); } let cartItemId; if (existingItems.length > 0) { // 更新现有购物车项的数量 await db.execute( 'UPDATE cart_items SET quantity = ?, updated_at = NOW() WHERE id = ?', [finalQuantity, existingItems[0].id] ); cartItemId = existingItems[0].id; } else { // 添加新的购物车项 const [result] = await db.execute( 'INSERT INTO cart_items (user_id, product_id, quantity, specification_id, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())', [userId, productId, quantity, specificationId] ); cartItemId = result.insertId; } await db.query('COMMIT'); res.status(201).json({ success: true, message: '添加到购物车成功', data: { cart_item_id: cartItemId } }); } catch (error) { await db.query('ROLLBACK'); console.error('添加到购物车失败:', error); res.status(500).json({ success: false, message: '添加到购物车失败' }); } }); /** * @swagger * /api/cart/{id}: * put: * summary: 更新购物车商品数量 * tags: [Cart] * security: * - bearerAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: 购物车项ID * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * quantity: * type: integer * description: 新的商品数量 * minimum: 1 * required: * - quantity * 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', auth, async (req, res) => { const db = getDB(); await db.query('START TRANSACTION'); try { const cartItemId = req.params.id; const { quantity } = req.body; const userId = req.user.id; // 验证数量 if (!quantity || quantity < 1) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '商品数量必须大于0' }); } // 检查购物车项是否存在且属于当前用户 const [cartItems] = await db.execute( 'SELECT id, product_id, specification_id FROM cart_items WHERE id = ? AND user_id = ?', [cartItemId, userId] ); console.log(cartItems,'cartItems'); if (cartItems.length === 0) { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '购物车项不存在' }); } const cartItem = cartItems[0]; // 检查商品库存 const [products] = await db.execute( 'SELECT stock, status FROM products WHERE id = ?', [cartItem.product_id] ); if (products.length === 0 || products[0].status !== 'active') { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '商品不存在或已下架' }); } let availableStock = products[0].stock; // 如果有规格,检查规格库存 if (cartItem.specification_id) { const [specs] = await db.execute( 'SELECT stock, status FROM product_spec_combinations WHERE id = ?', [cartItem.specification_id] ); if (specs.length === 0 || specs[0].status !== 'active') { await db.query('ROLLBACK'); return res.status(404).json({ success: false, message: '商品规格不存在或已下架' }); } availableStock = specs[0].stock; } // 检查库存是否足够 if (availableStock < quantity) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '库存不足' }); } // 更新购物车项数量 await db.execute( 'UPDATE cart_items SET quantity = ?, updated_at = NOW() WHERE id = ?', [quantity, cartItemId] ); 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/cart/{id}: * delete: * summary: 删除购物车商品 * tags: [Cart] * 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 * 401: * description: 未授权 * 404: * description: 购物车项不存在 * 500: * description: 服务器错误 */ router.delete('/:id', auth, async (req, res) => { try { const cartItemId = req.params.id; const userId = req.user.id; // 检查购物车项是否存在且属于当前用户 const [cartItems] = await getDB().execute( 'SELECT id FROM cart_items WHERE id = ? AND user_id = ?', [cartItemId, userId] ); if (cartItems.length === 0) { return res.status(404).json({ success: false, message: '购物车项不存在' }); } // 删除购物车项 await getDB().execute( 'DELETE FROM cart_items WHERE id = ?', [cartItemId] ); res.json({ success: true, message: '删除购物车商品成功' }); } catch (error) { console.error('删除购物车商品失败:', error); res.status(500).json({ success: false, message: '删除购物车商品失败' }); } }); /** * @swagger * /api/cart/batch: * delete: * summary: 批量删除购物车商品 * tags: [Cart] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * cart_item_ids: * type: array * items: * type: integer * description: 购物车项ID数组 * required: * - cart_item_ids * responses: * 200: * description: 批量删除购物车商品成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * data: * type: object * properties: * deleted_count: * type: integer * description: 删除的商品数量 * 400: * description: 参数错误 * 401: * description: 未授权 * 500: * description: 服务器错误 */ router.delete('/batch', auth, async (req, res) => { try { const { cart_item_ids } = req.body; const userId = req.user.id; // 验证参数 if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) { return res.status(400).json({ success: false, message: '请选择要删除的商品' }); } // 构建删除条件 const placeholders = cart_item_ids.map(() => '?').join(','); const query = `DELETE FROM cart_items WHERE id IN (${placeholders}) AND user_id = ?`; const params = [...cart_item_ids, userId]; const [result] = await getDB().execute(query, params); res.json({ success: true, message: '批量删除购物车商品成功', data: { deleted_count: result.affectedRows } }); } catch (error) { console.error('批量删除购物车商品失败:', error); res.status(500).json({ success: false, message: '批量删除购物车商品失败' }); } }); /** * @swagger * /api/cart/clear: * delete: * summary: 清空购物车 * tags: [Cart] * security: * - bearerAuth: [] * responses: * 200: * description: 清空购物车成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * 401: * description: 未授权 * 500: * description: 服务器错误 */ router.delete('/clear', auth, async (req, res) => { try { const userId = req.user.id; // 清空用户购物车 await getDB().execute( 'DELETE FROM cart_items WHERE user_id = ?', [userId] ); res.json({ success: true, message: '清空购物车成功' }); } catch (error) { console.error('清空购物车失败:', error); res.status(500).json({ success: false, message: '清空购物车失败' }); } }); /** * @swagger * /api/cart/count: * get: * summary: 获取购物车商品数量 * tags: [Cart] * security: * - bearerAuth: [] * responses: * 200: * description: 获取购物车商品数量成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * data: * type: object * properties: * count: * type: integer * description: 购物车商品总数量 * 401: * description: 未授权 * 500: * description: 服务器错误 */ router.get('/count', auth, async (req, res) => { try { const userId = req.user.id; // 获取购物车商品总数量 const [result] = await getDB().execute( 'SELECT SUM(quantity) as count FROM cart_items WHERE user_id = ?', [userId] ); const count = result[0].count || 0; res.json({ success: true, data: { count } }); } catch (error) { console.error('获取购物车商品数量失败:', error); res.status(500).json({ success: false, message: '获取购物车商品数量失败' }); } }); /** * @swagger * /api/cart/checkout: * post: * summary: 购物车结账 * tags: [Cart] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * cart_item_ids: * type: array * items: * type: integer * description: 要结账的购物车项ID数组 * shipping_address: * type: string * description: 收货地址 * required: * - cart_item_ids * - shipping_address * responses: * 201: * description: 结账成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * data: * type: object * properties: * order_id: * type: integer * order_no: * type: string * total_amount: * type: integer * total_points: * type: integer * total_rongdou: * type: integer * 400: * description: 参数错误或库存不足 * 401: * description: 未授权 * 500: * description: 服务器错误 */ router.post('/checkout', auth, async (req, res) => { const db = getDB(); await db.query('START TRANSACTION'); try { const { cart_item_ids, shipping_address } = req.body; const userId = req.user.id; // 验证参数 if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '请选择要结账的商品' }); } if (!shipping_address) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '请填写收货地址' }); } // 获取购物车商品信息 const placeholders = cart_item_ids.map(() => '?').join(','); const cartQuery = ` SELECT c.id, c.product_id, c.quantity, c.spec_combination_id, p.name, p.price, p.points_price, p.rongdou_price, p.stock, p.status, psc.price_adjustment, psc.points_adjustment, psc.rongdou_adjustment, psc.stock as spec_stock FROM cart_items c LEFT JOIN products p ON c.product_id = p.id LEFT JOIN product_spec_combinations psc ON c.spec_combination_id = psc.id WHERE c.id IN (${placeholders}) AND c.user_id = ? `; const [cartItems] = await db.execute(cartQuery, [...cart_item_ids, userId]); if (cartItems.length === 0) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '购物车商品不存在' }); } // 验证商品状态和库存 let totalAmount = 0; let totalPoints = 0; let totalRongdou = 0; for (const item of cartItems) { if (item.status !== 'active') { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: `商品 ${item.name} 已下架` }); } const availableStock = item.spec_combination_id ? item.spec_stock : item.stock; if (availableStock < item.quantity) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: `商品 ${item.name} 库存不足` }); } const finalPrice = item.price + (item.price_adjustment || 0); const finalPointsPrice = item.points_price + (item.points_adjustment || 0); const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0); totalAmount += finalPrice * item.quantity; totalPoints += finalPointsPrice * item.quantity; totalRongdou += finalRongdouPrice * item.quantity; } // 检查用户积分和融豆是否足够 const [users] = await db.execute( 'SELECT points, rongdou FROM users WHERE id = ?', [userId] ); 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: '积分不足' }); } if (user.rongdou < totalRongdou) { await db.query('ROLLBACK'); return res.status(400).json({ success: false, message: '融豆不足' }); } // 生成订单号 const orderNo = 'ORD' + Date.now() + Math.random().toString(36).substr(2, 5).toUpperCase(); // 创建订单 const [orderResult] = await db.execute( `INSERT INTO orders (order_no, user_id, total_amount, total_points, total_rongdou, status, shipping_address, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 'pending', ?, NOW(), NOW())`, [orderNo, userId, totalAmount, totalPoints, totalRongdou, shipping_address] ); const orderId = orderResult.insertId; // 创建订单项 for (const item of cartItems) { const finalPrice = item.price + (item.price_adjustment || 0); const finalPointsPrice = item.points_price + (item.points_adjustment || 0); const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0); await db.execute( `INSERT INTO order_items (order_id, product_id, spec_combination_id, quantity, price, points_price, rongdou_price, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`, [orderId, item.product_id, item.spec_combination_id, item.quantity, finalPrice, finalPointsPrice, finalRongdouPrice] ); // 更新库存 if (item.spec_combination_id) { await db.execute( 'UPDATE product_spec_combinations SET stock = stock - ? WHERE id = ?', [item.quantity, item.spec_combination_id] ); } else { await db.execute( 'UPDATE products SET stock = stock - ? WHERE id = ?', [item.quantity, item.product_id] ); } } // 扣除用户积分和融豆 await db.execute( 'UPDATE users SET points = points - ?, rongdou = rongdou - ? WHERE id = ?', [totalPoints, totalRongdou, userId] ); // 删除已结账的购物车项 const deletePlaceholders = cart_item_ids.map(() => '?').join(','); await db.execute( `DELETE FROM cart_items WHERE id IN (${deletePlaceholders}) AND user_id = ?`, [...cart_item_ids, userId] ); await db.query('COMMIT'); res.status(201).json({ success: true, message: '结账成功', data: { order_id: orderId, order_no: orderNo, total_amount: totalAmount, total_points: totalPoints, total_rongdou: totalRongdou } }); } catch (error) { await db.query('ROLLBACK'); console.error('购物车结账失败:', error); res.status(500).json({ success: false, message: '结账失败' }); } }); module.exports = router;