const express = require('express'); const { getDB } = require('../database'); const { auth, adminAuth } = require('../middleware/auth'); const router = express.Router(); /** * @swagger * /api/products: * get: * summary: 获取商品列表 * description: 返回商品列表,支持分页、搜索、分类、状态过滤 * tags: [products] * parameters: * - in: query * name: page * schema: * type: integer * description: 页码,默认1 * - in: query * name: limit * schema: * type: integer * description: 每页数量,默认10,最大100 * - in: query * name: search * schema: * type: string * description: 搜索商品名称 * - in: query * name: category * schema: * type: string * description: 分类名称 * - in: query * name: status * schema: * type: string * description: 商品状态(active/inactive) * responses: * 200: * description: 成功返回商品列表 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * data: * type: array * items: * type: object * properties: * id: * type: string * example: "1" * name: * type: string * example: "商品A" * rongdou_price: * type: number * example: 100 * points_price: * type: number * example: 1000 * stock: * type: integer * example: 100 * image: * type: string * example: "https://example.com/image.jpg" * description: * type: string * example: "这是一个商品" * status: * type: string * example: "active" * category_id: * type: string * example: "1" * created_at: * type: string * format: date-time * example: "2023-01-01T00:00:00Z" * updated_at: * type: string * format: date-time * example: "2023-01-01T00:00:00Z" * sales: * type: integer * example: 100 * images: * type: array * items: * type: string * example: "https://example.com/image.jpg" * 400: * description: 无效的分页参数 * 500: * description: 服务器内部错误 */ // 商品管理路由 router.get('/', async (req, res) => { try { const { page = 1, limit = 10, search = '', category = '', status = '' } = req.query; // 确保参数为有效数字 const pageNum = Math.max(1, parseInt(page) || 1); const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10)); // 限制最大100条 const offset = Math.max(0, (pageNum - 1) * limitNum); let filteredProducts = [] console.log('分页参数:', { pageNum, limitNum, offset, search, category, status }); let whereClause = 'WHERE 1=1'; const params = []; if (search) { whereClause += ' AND name LIKE ?'; params.push(`%${search}%`); } if (status) { whereClause += ' AND status = ?'; params.push(status); } else { whereClause += ' AND status = "active"'; } // 获取总数 const countQuery = `SELECT COUNT(*) as total FROM products ${whereClause}`; const [countResult] = await getDB().execute(countQuery, params); const total = countResult[0].total; // 获取商品列表 const query = ` SELECT id, name, rongdou_price, points_price, stock, image_url as image, description, status, payment_methods, created_at, updated_at, sales, images FROM products ${whereClause} ORDER BY created_at DESC LIMIT ${limitNum} OFFSET ${offset} `; // 确保参数数组正确传递 const queryParams = [...params]; console.log('Query params:', queryParams, 'Query:', query); const [products] = await getDB().execute(query, queryParams); products.forEach(item=>{ item.payment_methods = JSON.parse(item.payment_methods) item.images = JSON.parse(item.images) }) if (category) { // 先根据分类名称获取分类ID const query = `SELECT * FROM category WHERE category_name = ?` const [getCategory] = await getDB().execute(query, [category]) const [getSecondCategory] = await getDB().execute('SELECT * FROM category WHERE parent_id = ?', [getCategory[0].id]) const sumCategory = getCategory.concat(getSecondCategory).map(item=>item.id) // 再根据分类ID获取商品ID const getProductCategory = [] for (const item of sumCategory) { const [getProductCategoryItem] = await getDB().execute('SELECT * FROM products_category WHERE category_id = ?', [item]) getProductCategory.push(...getProductCategoryItem) } const productIds = [] for (const item of getProductCategory) { productIds.push(item.product_id) } filteredProducts = products.filter(item=>productIds.includes(item.id)) } else { filteredProducts = products } res.json({ success: true, data: { products: filteredProducts, // products: products, 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('/categories', async (req, res) => { // try { // const [categories] = await getDB().execute( // 'SELECT DISTINCT category FROM products WHERE status = "active" AND category IS NOT NULL' // ); // res.json({ // success: true, // data: { // categories: categories.map(item => item.category) // } // }); // } catch (error) { // console.error('获取商品分类失败:', error); // res.status(500).json({ success: false, message: '获取商品分类失败' }); // } // }); // 获取热销商品 router.get('/hot', async (req, res) => { try { // 从活跃商品中随机获取2个商品 const [products] = await getDB().execute( `SELECT id, name, price, points_price, rongdou_price, stock, image_url, images, description, shop_name, shop_avatar, payment_methods, sales, rating, status, created_at, updated_at FROM products WHERE status = 'active' AND stock > 0 ORDER BY sales DESC LIMIT 2` ); // 格式化商品数据 const formattedProducts = products.map(product => ({ ...product, images: product.images ? JSON.parse(product.images) : (product.image_url ? [product.image_url] : []), payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : ['points'], // 保持向后兼容 points: product.points_price, image: product.image_url })); res.json({ success: true, data: { products: formattedProducts } }); } catch (error) { console.error('获取热销商品失败:', error); res.status(500).json({ success: false, message: '获取热销商品失败' }); } }); /** * @swagger * /products/flash-sale: * get: * summary: 获取秒杀商品 * tags: [Products] * responses: * 200: * description: 成功获取秒杀商品 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * data: * type: object * properties: * products: * type: array * items: * $ref: '#/components/schemas/Product' */ router.get('/cheap', async (req, res) => { try { const [products] = await getDB().execute( `SELECT id, start_time, end_time, flash_stock, flash_price, products_id FROM flash_product WHERE end_time > NOW() AND flash_stock > 0 ORDER BY RAND() LIMIT 2` ); const tempProducts = await Promise.all(products.map(async item=>{ const [product] = await getDB().execute( `SELECT id, name, price, points_price, rongdou_price, stock, image_url, images, description, shop_name, shop_avatar, payment_methods, sales, rating, status, created_at, updated_at FROM products WHERE id = ?`, [item.products_id] ) item = { ...product[0], images: product.images ? JSON.parse(product.images) : (product.image_url ? [product.image_url] : []), payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : ['points'], ...item, points: product.points_price, image: product.image_url, id: product[0].id, } return item })) res.json({ success: true, data: { products: tempProducts } }); } catch (error) { console.error('获取秒杀商品失败:', error); res.status(500).json({ success: false, message: '获取秒杀商品失败' }); } }); /** * @swagger * /products/{id}: * get: * summary: 获取单个商品详情(包含增强规格信息) * tags: [Products] * parameters: * - in: path * name: id * schema: * type: integer * required: true * description: 商品ID * responses: * 200: * description: 成功获取商品详情,包含完整的规格信息 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * data: * type: object * properties: * product: * type: object * properties: * id: * type: integer * name: * type: string * category: * type: string * price: * type: number * points_price: * type: number * rongdou_price: * type: number * stock: * type: integer * specifications: * type: array * description: 商品规格组合列表(笛卡尔积规格系统) * items: * type: object * properties: * id: * type: integer * description: 规格组合ID * combination_key: * type: string * description: 规格组合键(如:1-3-5) * spec_display: * type: string * description: 规格显示文本(如:颜色:红色 | 尺寸:XL) * spec_details: * type: array * description: 规格详细信息 * items: * type: object * properties: * id: * type: integer * spec_name: * type: string * description: 规格名称 * spec_display_name: * type: string * description: 规格显示名称 * value: * type: string * description: 规格值 * display_value: * type: string * description: 规格显示值 * color_code: * type: string * description: 颜色代码 * image_url: * type: string * description: 规格图片 * price_adjustment: * type: number * description: 价格调整 * points_adjustment: * type: number * description: 积分调整 * rongdou_adjustment: * type: number * description: 融豆调整 * stock: * type: integer * description: 规格库存 * sku_code: * type: string * description: SKU编码 * barcode: * type: string * description: 条形码 * weight: * type: number * description: 重量 * volume: * type: number * description: 体积 * actual_price: * type: number * description: 实际价格(基础价格+调整) * actual_points_price: * type: number * description: 实际积分价格 * actual_rongdou_price: * type: number * description: 实际融豆价格 * is_available: * type: boolean * description: 是否有库存 * specification_count: * type: integer * description: 规格总数 * available_specifications: * type: integer * description: 有库存的规格数量 * attributes: * type: array * description: 商品属性 * isFavorited: * type: boolean * description: 是否已收藏 * 404: * description: 商品不存在 */ router.get('/:id', async (req, res) => { try { const { id } = req.params; const userId = req.user?.id; // 可选的用户ID,用于检查收藏状态 const query = ` SELECT id, name, price, points_price, rongdou_price, stock, image_url, images, videos, description, details, shop_name, shop_avatar, payment_methods, sales, rating, status, created_at, updated_at FROM products WHERE id = ? AND status = 'active' `; const [products] = await getDB().execute(query, [id]); if (products.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); } const product = products[0]; // 获取商品的规格组合(新的笛卡尔积规格系统) const [specCombinations] = await getDB().execute( `SELECT psc.*, GROUP_CONCAT(CONCAT(sn.display_name, ':', sv.display_value) ORDER BY sn.sort_order SEPARATOR ' | ') as spec_display FROM product_spec_combinations psc LEFT JOIN JSON_TABLE(psc.spec_values, '$[*]' COLUMNS (spec_value_id INT PATH '$')) jt ON TRUE 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 psc.product_id = ? AND psc.status = 'active' GROUP BY psc.id ORDER BY psc.combination_key`, [id] ); // console.log(123,specCombinations); // 为每个规格组合获取详细的规格值信息 const enhancedSpecifications = []; for (const combination of specCombinations) { // 智能解析 spec_values 字段,兼容多种数据格式 let specValueIds = []; try { if (combination.spec_values) { // 如果是 Buffer 对象,先转换为字符串 let specValuesStr = combination.spec_values; if (Buffer.isBuffer(specValuesStr)) { specValuesStr = specValuesStr.toString('utf8'); } // 尝试 JSON 解析 if (typeof specValuesStr === 'string') { specValuesStr = specValuesStr.trim(); if (specValuesStr.startsWith('[') && specValuesStr.endsWith(']')) { // JSON 数组格式 specValueIds = JSON.parse(specValuesStr); } else if (specValuesStr.includes(',')) { // 逗号分隔的字符串格式 specValueIds = specValuesStr.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); } else if (specValuesStr && !isNaN(parseInt(specValuesStr))) { // 单个数字 specValueIds = [parseInt(specValuesStr)]; } } else if (Array.isArray(specValuesStr)) { // 已经是数组 specValueIds = specValuesStr; } } } catch (parseError) { console.warn(`解析规格值失败 (combination_id: ${combination.id}):`, parseError.message); specValueIds = []; } // 获取规格值详情 if (specValueIds && specValueIds.length > 0) { const placeholders = specValueIds.map(() => '?').join(','); const [specDetails] = await getDB().execute( `SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name FROM spec_values sv LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id WHERE sv.id IN (${placeholders}) ORDER BY sn.sort_order, sv.sort_order`, specValueIds ); enhancedSpecifications.push({ id: combination.id, combination_key: combination.combination_key, spec_display: combination.spec_display, spec_details: specDetails, price_adjustment: combination.price_adjustment || 0, points_adjustment: combination.points_adjustment || 0, rongdou_adjustment: combination.rongdou_adjustment || 0, stock: combination.stock, sku_code: combination.sku_code, barcode: combination.barcode, weight: combination.weight, volume: combination.volume, actual_price: product.price + (combination.price_adjustment || 0), actual_points_price: product.points_price + (combination.points_adjustment || 0), actual_rongdou_price: product.rongdou_price + (combination.rongdou_adjustment || 0), is_available: combination.stock > 0, status: combination.status, created_at: combination.created_at, updated_at: combination.updated_at }); } } // console.log(123,enhancedSpecifications); // 获取商品属性 const [attributes] = await getDB().execute( 'SELECT * FROM product_attributes WHERE product_id = ? ORDER BY sort_order, id', [id] ); // 检查用户是否收藏了该商品 let isFavorited = false; if (userId) { const [favorites] = await getDB().execute( 'SELECT id FROM product_favorites WHERE user_id = ? AND product_id = ?', [userId, id] ); isFavorited = favorites.length > 0; } // 构建增强的商品数据 const enhancedProduct = { ...product, images: (() => { try { if (product.images) { let imagesStr = product.images; if (Buffer.isBuffer(imagesStr)) { imagesStr = imagesStr.toString('utf8'); } if (typeof imagesStr === 'string') { imagesStr = imagesStr.trim(); if (imagesStr.startsWith('[') && imagesStr.endsWith(']')) { return JSON.parse(imagesStr); } } } return product.image_url ? [product.image_url] : []; } catch (e) { console.warn('解析商品图片失败:', e.message); return product.image_url ? [product.image_url] : []; } })(), videos: (() => { try { if (product.videos) { let videosStr = product.videos; if (Buffer.isBuffer(videosStr)) { videosStr = videosStr.toString('utf8'); } if (typeof videosStr === 'string') { videosStr = videosStr.trim(); if (videosStr.startsWith('[') && videosStr.endsWith(']')) { return JSON.parse(videosStr); } } } return []; } catch (e) { console.warn('解析商品视频失败:', e.message); return []; } })(), payment_methods: (() => { try { if (product.payment_methods) { let methodsStr = product.payment_methods; if (Buffer.isBuffer(methodsStr)) { methodsStr = methodsStr.toString('utf8'); } if (typeof methodsStr === 'string') { methodsStr = methodsStr.trim(); if (methodsStr.startsWith('[') && methodsStr.endsWith(']')) { return JSON.parse(methodsStr); } } } return ['points']; } catch (e) { console.warn('解析支付方式失败:', e.message); return ['points']; } })(), specifications: enhancedSpecifications, attributes, isFavorited, // 规格统计信息 specification_count: enhancedSpecifications.length, available_specifications: enhancedSpecifications.filter(spec => spec.is_available).length, // 保持向后兼容 points: product.points_price, image: product.image_url, // tags: product.category ? [product.category] : [] }; res.json({ success: true, data: { product: enhancedProduct } }); } catch (error) { console.error('获取商品详情失败:', error); res.status(500).json({ success: false, message: '获取商品详情失败' }); } }); // 创建商品(管理员权限) router.post('/', auth, adminAuth, async (req, res) => { try { const { name, description, price, points_price, rongdou_price = 0, stock, category, image_url, images = [], videos = [], details, status = 'active', shop_name, shop_avatar, payment_methods = ['points', 'rongdou', 'points_rongdou'], specifications = [], attributes = [] } = req.body; if (!name || !price || (!points_price && !rongdou_price) || stock === undefined) { return res.status(400).json({ message: '商品名称、原价、积分价格或融豆价格、库存不能为空' }); } const [result] = await getDB().execute( `INSERT INTO products (name, description, price, points_price, rongdou_price, stock, category, image_url, images, videos, details, shop_name, shop_avatar, payment_methods, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`, [name, description, price, points_price, rongdou_price, stock, category || null, image_url, JSON.stringify(images), JSON.stringify(videos), details, shop_name, shop_avatar, JSON.stringify(payment_methods), status] ); const productId = result.insertId; // 添加商品属性 if (attributes && attributes.length > 0) { for (const attr of attributes) { await getDB().execute( `INSERT INTO product_attributes (product_id, attribute_key, attribute_value, sort_order) VALUES (?, ?, ?, ?)`, [productId, attr.key, attr.value, attr.sort_order || 0] ); } } res.status(201).json({ success: true, message: '商品创建成功', data: { productId } }); } catch (error) { console.error('创建商品错误:', error); res.status(500).json({ message: '创建商品失败' }); } }); // 更新商品(管理员权限) router.put('/:id', auth, adminAuth, async (req, res) => { try { const productId = req.params.id; const { name, description, price, points_price, rongdou_price, stock, category, image_url, images, videos, details, status, shop_name, shop_avatar, payment_methods, specifications, attributes } = req.body; // 检查商品是否存在 const [products] = await getDB().execute( 'SELECT id FROM products WHERE id = ?', [productId] ); if (products.length === 0) { return res.status(404).json({ message: '商品不存在' }); } // 构建更新字段 const updateFields = []; const updateValues = []; if (name) { updateFields.push('name = ?'); updateValues.push(name); } if (description !== undefined) { updateFields.push('description = ?'); updateValues.push(description); } if (price !== undefined) { updateFields.push('price = ?'); updateValues.push(price); } if (points_price !== undefined) { updateFields.push('points_price = ?'); updateValues.push(points_price); } if (rongdou_price !== undefined) { updateFields.push('rongdou_price = ?'); updateValues.push(rongdou_price); } if (stock !== undefined) { updateFields.push('stock = ?'); updateValues.push(stock); } if (category !== undefined) { updateFields.push('category = ?'); updateValues.push(category); } if (image_url !== undefined) { updateFields.push('image_url = ?'); updateValues.push(image_url); } if (images !== undefined) { updateFields.push('images = ?'); updateValues.push(JSON.stringify(images || [])); } if (videos !== undefined) { updateFields.push('videos = ?'); updateValues.push(JSON.stringify(videos || [])); } if (details !== undefined) { updateFields.push('details = ?'); updateValues.push(details); } if (shop_name !== undefined) { updateFields.push('shop_name = ?'); updateValues.push(shop_name); } if (shop_avatar !== undefined) { updateFields.push('shop_avatar = ?'); updateValues.push(shop_avatar); } if (payment_methods !== undefined) { updateFields.push('payment_methods = ?'); updateValues.push(JSON.stringify(payment_methods || [])); } if (status) { updateFields.push('status = ?'); updateValues.push(status); } if (updateFields.length === 0) { return res.status(400).json({ message: '没有要更新的字段' }); } updateFields.push('updated_at = NOW()'); updateValues.push(productId); await getDB().execute( `UPDATE products SET ${updateFields.join(', ')} WHERE id = ?`, updateValues ); // 更新商品属性 if (attributes !== undefined) { // 删除原有属性 await getDB().execute('DELETE FROM product_attributes WHERE product_id = ?', [productId]); // 添加新属性 if (attributes && attributes.length > 0) { for (const attr of attributes) { await getDB().execute( `INSERT INTO product_attributes (product_id, attribute_key, attribute_value, sort_order) VALUES (?, ?, ?, ?)`, [productId, attr.key, attr.value, attr.sort_order || 0] ); } } } res.json({ success: true, message: '商品更新成功' }); } catch (error) { console.error('更新商品错误:', error); res.status(500).json({ message: '更新商品失败' }); } }); // 删除商品(管理员权限) router.delete('/:id', auth, adminAuth, async (req, res) => { try { const { id } = req.params; // 检查商品是否存在 const checkQuery = 'SELECT id FROM products WHERE id = ?'; const [existing] = await getDB().execute(checkQuery, [id]); if (existing.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); } // 检查是否有相关订单 const orderCheckQuery = 'SELECT id FROM orders WHERE product_id = ? LIMIT 1'; const [orders] = await getDB().execute(orderCheckQuery, [id]); if (orders.length > 0) { return res.status(400).json({ success: false, message: '该商品存在相关订单,无法删除' }); } const query = 'DELETE FROM products WHERE id = ?'; await getDB().execute(query, [id]); res.json({ success: true, message: '商品删除成功' }); } catch (error) { console.error('删除商品失败:', error); res.status(500).json({ success: false, message: '删除商品失败' }); } }); // 获取商品统计信息(管理员权限) router.get('/stats', auth, adminAuth, async (req, res) => { try { // 获取商品总数 const totalQuery = 'SELECT COUNT(*) as total FROM products'; const [totalResult] = await getDB().execute(totalQuery); const totalProducts = totalResult[0].total; // 获取活跃商品数 const activeQuery = 'SELECT COUNT(*) as total FROM products WHERE status = "active"'; const [activeResult] = await getDB().execute(activeQuery); const activeProducts = activeResult[0].total; // 获取库存不足商品数(库存小于10) const lowStockQuery = 'SELECT COUNT(*) as total FROM products WHERE stock < 10'; const [lowStockResult] = await getDB().execute(lowStockQuery); const lowStockProducts = lowStockResult[0].total; // 获取本月新增商品数 const monthlyQuery = ` SELECT COUNT(*) as total FROM products WHERE YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE()) `; const [monthlyResult] = await getDB().execute(monthlyQuery); const monthlyProducts = monthlyResult[0].total; // 计算月增长率 const lastMonthQuery = ` SELECT COUNT(*) as total FROM products WHERE YEAR(created_at) = YEAR(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) `; const [lastMonthResult] = await getDB().execute(lastMonthQuery); const lastMonthProducts = lastMonthResult[0].total; const monthlyGrowth = lastMonthProducts > 0 ? ((monthlyProducts - lastMonthProducts) / lastMonthProducts * 100).toFixed(1) : 0; res.json({ success: true, data: { stats: { totalProducts, activeProducts, lowStockProducts, monthlyProducts, monthlyGrowth: parseFloat(monthlyGrowth) } } }); } catch (error) { console.error('获取商品统计失败:', error); res.status(500).json({ success: false, message: '获取商品统计失败' }); } }); // 获取商品评论 router.get('/:id/reviews', async (req, res) => { try { const { id } = req.params; const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const offset = (page - 1) * limit; // 获取评论列表 const [reviews] = await getDB().execute( `SELECT pr.id, pr.rating, pr.comment as content, pr.images, pr.created_at as createdAt, u.username as user_name, u.avatar as user_avatar FROM product_reviews pr JOIN users u ON pr.user_id = u.id WHERE pr.product_id = ? ORDER BY pr.created_at DESC LIMIT ${limit} OFFSET ${offset}`, [id] ); // 获取评论总数 const [countResult] = await getDB().execute( 'SELECT COUNT(*) as total FROM product_reviews WHERE product_id = ?', [id] ); // 计算平均评分 const [avgResult] = await getDB().execute( 'SELECT AVG(rating) as avg_rating FROM product_reviews WHERE product_id = ?', [id] ); // 格式化评论数据 const formattedReviews = reviews.map(review => ({ id: review.id, user: { name: review.user_name, avatar: review.user_avatar }, rating: review.rating, content: review.content, createdAt: review.createdAt, images: review.images ? JSON.parse(review.images) : null })); res.json({ success: true, data: { reviews: formattedReviews, total: countResult[0].total, averageRating: avgResult[0].avg_rating ? parseFloat(avgResult[0].avg_rating).toFixed(1) : 0, pagination: { page, limit, total: countResult[0].total, totalPages: Math.ceil(countResult[0].total / limit) } } }); } catch (error) { console.error('获取商品评论失败:', error); res.status(500).json({ success: false, message: '获取商品评论失败' }); } }); // 获取推荐商品 router.get('/:id/recommended', async (req, res) => { try { // 获取商品id const id = parseInt(req.params.id); // 通过商品id获取该商品的分类id const categoryQuery = ` SELECT category_id FROM products_category WHERE product_id = ? `; const [currentCategory] = await getDB().execute(categoryQuery, [id]); let parentId = null; let categoryIds = currentCategory.map(item => item.category_id); if(currentCategory.length > 0 && currentCategory[0] !== null) { // 获取该商品的一级分类id const item = currentCategory[0]; const levelQuery = ` SELECT * FROM category WHERE id = ? AND level = 1 `; const [fatherCategory] = await getDB().execute(levelQuery, [item.category_id]); if(fatherCategory.length > 0) { // 该商品为一级分类商品,不存在二级分类 parentId = fatherCategory[0].id; categoryIds.push(parentId); } else { // 获取该商品的二级分类id const secondlevelQuery = ` SELECT * FROM category WHERE id = ? AND level = 2 `; const [secondlevelCategory] = await getDB().execute(secondlevelQuery, [item.category_id]); if(secondlevelCategory.length > 0) { // 通过二级分类获取一级分类id parentId = secondlevelCategory[0].parent_id; categoryIds.push(parentId); } } } // categoryIds目前存储该商品的一级分类id及其目录下所有二级分类的id const allSecondCategoryIdsQuery = ` SELECT id FROM category WHERE parent_id = ? AND level = 2 `; const [secondCategoryIds] = await getDB().execute(allSecondCategoryIdsQuery, [parentId]); categoryIds.push(...secondCategoryIds.map(item => item.id)); let recommendedProducts = []; let filteredRecommendProductIds = []; // 如果有分类ID,先获取同类商品 if (categoryIds.length > 0) { const recommendId = ` SELECT * FROM products_category WHERE category_id IN (${categoryIds.map(() => '?').join(',')}) `; const [recommendProductIds] = await getDB().execute(recommendId, categoryIds); filteredRecommendProductIds = [...new Set(recommendProductIds.map(item => item.product_id))]; // 获取同类别的其他商品作为推荐 if (filteredRecommendProductIds.length > 0) { const query = ` SELECT id, name, price, points_price as points, stock, image_url as image, description FROM products WHERE id IN (${filteredRecommendProductIds.map(() => '?').join(',')}) AND id != ? `; const [categoryProducts] = await getDB().execute(query, [...filteredRecommendProductIds, id]); recommendedProducts = categoryProducts; } } // 如果同类别商品不足,补充其他热门商品 if (recommendedProducts.length < 6) { let recommendQuery = ` SELECT products_id FROM recommend_product WHERE 1 = 1 `; if (filteredRecommendProductIds.length > 0) { recommendQuery += ` AND products_id NOT IN (${filteredRecommendProductIds.map(() => '?').join(',')})`; } recommendQuery += ` ORDER BY RAND() LIMIT ${6 - recommendedProducts.length}`; // 根据是否有排除ID来传递参数 const queryParams = filteredRecommendProductIds.length > 0 ? [...filteredRecommendProductIds] : []; const [recommendProductIds] = await getDB().execute(recommendQuery, queryParams); filteredRecommendProductIds.push(...recommendProductIds.map(item => item.products_id)); for (const item of recommendProductIds) { const recommendQuery = ` SELECT id, name, price, points_price as points, stock, image_url as image, description FROM products WHERE id = ? `; const [recommendProduct] = await getDB().execute(recommendQuery, [item.products_id]); recommendedProducts.push(recommendProduct[0]); } if (recommendProductIds.length < 6) { // 补充其他热门商品 const additionalQuery = ` SELECT id, name, price, points_price as points, stock, image_url as image, description FROM products WHERE id NOT IN (${filteredRecommendProductIds.map(() => '?').join(',')}) ORDER BY RAND() LIMIT ${6 - recommendedProducts.length} `; const [additionalProducts] = await getDB().execute( additionalQuery, filteredRecommendProductIds ); recommendedProducts.push(...additionalProducts); } } res.json({ success: true, data: { products: recommendedProducts } }); } catch (error) { console.error('获取推荐商品失败:', error); res.status(500).json({ success: false, message: '获取推荐商品失败' }); } }); // 收藏商品 router.post('/:id/favorite', auth, async (req, res) => { try { const productId = req.params.id; const userId = req.user.id; // 检查商品是否存在 const [products] = await getDB().execute('SELECT id FROM products WHERE id = ?', [productId]); if (products.length === 0) { return res.status(404).json({ message: '商品不存在' }); } // 检查是否已收藏 const [existing] = await getDB().execute( 'SELECT id FROM product_favorites WHERE user_id = ? AND product_id = ?', [userId, productId] ); if (existing.length > 0) { return res.status(400).json({ message: '商品已收藏' }); } await getDB().execute( 'INSERT INTO product_favorites (user_id, product_id, created_at) VALUES (?, ?, NOW())', [userId, productId] ); res.json({ success: true, message: '收藏成功' }); } catch (error) { console.error('收藏商品错误:', error); res.status(500).json({ message: '收藏失败' }); } }); // 取消收藏商品 router.delete('/:id/favorite', auth, async (req, res) => { try { const productId = req.params.id; const userId = req.user.id; const [result] = await getDB().execute( 'DELETE FROM product_favorites WHERE user_id = ? AND product_id = ?', [userId, productId] ); if (result.affectedRows === 0) { return res.status(404).json({ message: '未收藏该商品' }); } res.json({ success: true, message: '取消收藏成功' }); } catch (error) { console.error('取消收藏错误:', error); res.status(500).json({ message: '取消收藏失败' }); } }); // 获取用户收藏的商品列表 router.get('/favorites', auth, async (req, res) => { try { const userId = req.user.id; const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const offset = (page - 1) * limit; const [favorites] = await getDB().execute( `SELECT p.*, pf.created_at as favorite_time FROM product_favorites pf JOIN products p ON pf.product_id = p.id WHERE pf.user_id = ? AND p.status = 'active' ORDER BY pf.created_at DESC LIMIT ${limit} OFFSET ${offset}`, [userId] ); const [countResult] = await getDB().execute( `SELECT COUNT(*) as total FROM product_favorites pf JOIN products p ON pf.product_id = p.id WHERE pf.user_id = ? AND p.status = 'active'`, [userId] ); res.json({ success: true, data: { products: favorites.map(product => ({ ...product, images: product.images ? JSON.parse(product.images) : [], videos: product.videos ? JSON.parse(product.videos) : [], payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : [] })), pagination: { page, limit, total: countResult[0].total, totalPages: Math.ceil(countResult[0].total / limit) } } }); } catch (error) { console.error('获取收藏列表错误:', error); res.status(500).json({ message: '获取收藏列表失败' }); } }); // 获取商品属性 router.get('/:id/attributes', async (req, res) => { try { const productId = req.params.id; const [attributes] = await getDB().execute( 'SELECT * FROM product_attributes WHERE product_id = ? ORDER BY sort_order, id', [productId] ); res.json({ success: true, data: attributes }); } catch (error) { console.error('获取商品属性错误:', error); res.status(500).json({ message: '获取商品属性失败' }); } }); // 创建商品评论 router.post('/:id/reviews', auth, async (req, res) => { try { const productId = req.params.id; const userId = req.user.id; const { orderId, rating, comment, images = [] } = req.body; // 验证必填字段 if (!orderId || !rating || rating < 1 || rating > 5) { return res.status(400).json({ message: '订单ID和评分(1-5)不能为空' }); } // 检查订单是否存在且属于当前用户 const [orders] = await getDB().execute( `SELECT o.id FROM orders o JOIN order_items oi ON o.id = oi.order_id WHERE o.id = ? AND o.user_id = ? AND oi.product_id = ? AND o.status = 'delivered'`, [orderId, userId, productId] ); if (orders.length === 0) { return res.status(400).json({ message: '只能评价已完成的订单商品' }); } // 检查是否已经评价过 const [existingReviews] = await getDB().execute( 'SELECT id FROM product_reviews WHERE product_id = ? AND user_id = ? AND order_id = ?', [productId, userId, orderId] ); if (existingReviews.length > 0) { return res.status(400).json({ message: '该商品已评价过' }); } // 创建评论 const [result] = await getDB().execute( `INSERT INTO product_reviews (product_id, user_id, order_id, rating, comment, images, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())`, [productId, userId, orderId, rating, comment, JSON.stringify(images)] ); // 更新商品平均评分 const [avgResult] = await getDB().execute( 'SELECT AVG(rating) as avg_rating FROM product_reviews WHERE product_id = ?', [productId] ); await getDB().execute( 'UPDATE products SET rating = ? WHERE id = ?', [parseFloat(avgResult[0].avg_rating).toFixed(2), productId] ); res.status(201).json({ success: true, message: '评价成功', data: { reviewId: result.insertId } }); } catch (error) { console.error('创建商品评论错误:', error); res.status(500).json({ message: '评价失败' }); } }); module.exports = router;