diff --git a/README.md b/README.md index ce335ab..395ebdd 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,6 @@ cd website && python3 -m http.server 8080 ## 联系方式 -- 项目邮箱: aijianhua@example.com +- 项目邮箱: aijianhua@aijianhua.com - 问题反馈: GitHub Issues - 开发者微信群: 请联系项目管理员 \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index 4fbe1e5..0da1c21 100644 --- a/backend/app.js +++ b/backend/app.js @@ -23,6 +23,11 @@ const productRoutes = require('./routes/products'); const orderRoutes = require('./routes/orders'); const identificationRoutes = require('./routes/identifications'); const adminRoutes = require('./routes/admin'); +const cartRoutes = require('./routes/cart'); +const addressRoutes = require('./routes/addresses'); +const paymentRoutes = require('./routes/payments'); +const promotionRoutes = require('./routes/promotions'); +const uploadRoutes = require('./routes/upload'); // 导入中间件 const { errorHandler } = require('./middlewares/errorHandler'); @@ -77,6 +82,11 @@ app.use('/api/v1/products', productRoutes); app.use('/api/v1/orders', authMiddleware, orderRoutes); app.use('/api/v1/identifications', authMiddleware, identificationRoutes); app.use('/api/v1/admin', authMiddleware, adminRoutes); +app.use('/api/v1/cart', authMiddleware, cartRoutes); +app.use('/api/v1/addresses', authMiddleware, addressRoutes); +app.use('/api/v1/payments', authMiddleware, paymentRoutes); +app.use('/api/v1/promotions', authMiddleware, promotionRoutes); +app.use('/api/v1/upload', authMiddleware, uploadRoutes); // 404处理 app.use('*', (req, res) => { diff --git a/backend/config/database.js b/backend/config/database.js index 495ed24..6b8f362 100644 --- a/backend/config/database.js +++ b/backend/config/database.js @@ -10,7 +10,7 @@ const databaseConfig = { port: 9527, username: 'root', password: 'aiotAiot123!', - database: 'ajhdata', + database: 'xlxumudata', dialect: 'mysql', logging: console.log, pool: { @@ -27,7 +27,7 @@ const databaseConfig = { port: 9527, username: 'root', password: 'aiotAiot123!', - database: 'ajhdata', + database: 'xlxumudata', dialect: 'mysql', logging: false, // 生产环境关闭SQL日志 pool: { diff --git a/backend/routes/addresses.js b/backend/routes/addresses.js new file mode 100644 index 0000000..151ffdd --- /dev/null +++ b/backend/routes/addresses.js @@ -0,0 +1,176 @@ +const express = require('express'); +const router = express.Router(); +const dbConnector = require('../utils/dbConnector'); + +// 获取用户收货地址列表 +router.get('/', async (req, res) => { + try { + const userId = req.user.id; + + const addresses = await dbConnector.query(` + SELECT id, recipient, phone, province, city, district, detail, is_default, created_at + FROM addresses + WHERE user_id = ? + ORDER BY is_default DESC, created_at DESC + `, [userId]); + + res.json({ + code: 200, + message: '获取成功', + data: addresses + }); + } catch (error) { + console.error('获取地址列表失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 添加收货地址 +router.post('/', async (req, res) => { + try { + const { recipient, phone, province, city, district, detail, is_default } = req.body; + const userId = req.user.id; + + // 如果设置为默认地址,先取消其他默认地址 + if (is_default) { + await dbConnector.query( + 'UPDATE addresses SET is_default = 0 WHERE user_id = ?', + [userId] + ); + } + + const result = await dbConnector.query( + `INSERT INTO addresses + (user_id, recipient, phone, province, city, district, detail, is_default, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`, + [userId, recipient, phone, province, city, district, detail, is_default || 0] + ); + + res.status(201).json({ + code: 201, + message: '添加成功', + data: { + address_id: result.insertId + } + }); + } catch (error) { + console.error('添加地址失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 更新收货地址 +router.put('/:id', async (req, res) => { + try { + const { id } = req.params; + const { recipient, phone, province, city, district, detail, is_default } = req.body; + const userId = req.user.id; + + // 检查地址是否存在 + const address = await dbConnector.query( + 'SELECT * FROM addresses WHERE id = ? AND user_id = ?', + [id, userId] + ); + + if (address.length === 0) { + return res.status(404).json({ + code: 404, + message: '地址不存在' + }); + } + + // 如果设置为默认地址,先取消其他默认地址 + if (is_default) { + await dbConnector.query( + 'UPDATE addresses SET is_default = 0 WHERE user_id = ?', + [userId] + ); + } + + await dbConnector.query( + `UPDATE addresses SET + recipient = ?, phone = ?, province = ?, city = ?, district = ?, detail = ?, is_default = ?, updated_at = NOW() + WHERE id = ? AND user_id = ?`, + [recipient, phone, province, city, district, detail, is_default || 0, id, userId] + ); + + res.json({ + code: 200, + message: '更新成功' + }); + } catch (error) { + console.error('更新地址失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 删除收货地址 +router.delete('/:id', async (req, res) => { + try { + const { id } = req.params; + const userId = req.user.id; + + await dbConnector.query( + 'DELETE FROM addresses WHERE id = ? AND user_id = ?', + [id, userId] + ); + + res.json({ + code: 200, + message: '删除成功' + }); + } catch (error) { + console.error('删除地址失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 设置默认地址 +router.put('/:id/default', async (req, res) => { + try { + const { id } = req.params; + const userId = req.user.id; + + // 先取消所有默认地址 + await dbConnector.query( + 'UPDATE addresses SET is_default = 0 WHERE user_id = ?', + [userId] + ); + + // 设置当前地址为默认 + await dbConnector.query( + 'UPDATE addresses SET is_default = 1, updated_at = NOW() WHERE id = ? AND user_id = ?', + [id, userId] + ); + + res.json({ + code: 200, + message: '设置默认地址成功' + }); + } catch (error) { + console.error('设置默认地址失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/cart.js b/backend/routes/cart.js new file mode 100644 index 0000000..36592ed --- /dev/null +++ b/backend/routes/cart.js @@ -0,0 +1,155 @@ +const express = require('express'); +const router = express.Router(); +const dbConnector = require('../utils/dbConnector'); + +// 获取用户购物车 +router.get('/', async (req, res) => { + try { + const userId = req.user.id; + + const cartItems = await dbConnector.query(` + SELECT ci.*, p.name as product_name, p.price, p.image as product_image, p.stock + FROM cart_items ci + JOIN products p ON ci.product_id = p.id + WHERE ci.user_id = ? + `, [userId]); + + const totalAmount = cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0); + const totalQuantity = cartItems.reduce((sum, item) => sum + item.quantity, 0); + + res.json({ + code: 200, + message: '获取成功', + data: { + items: cartItems, + total_amount: totalAmount, + total_quantity: totalQuantity + } + }); + } catch (error) { + console.error('获取购物车失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 添加商品到购物车 +router.post('/items', async (req, res) => { + try { + const { product_id, quantity } = req.body; + const userId = req.user.id; + + // 检查商品是否存在 + const product = await dbConnector.query('SELECT * FROM products WHERE id = ? AND status = 1', [product_id]); + if (product.length === 0) { + return res.status(404).json({ + code: 404, + message: '商品不存在' + }); + } + + // 检查购物车是否已有该商品 + const existingItem = await dbConnector.query( + 'SELECT * FROM cart_items WHERE user_id = ? AND product_id = ?', + [userId, product_id] + ); + + if (existingItem.length > 0) { + // 更新数量 + await dbConnector.query( + 'UPDATE cart_items SET quantity = quantity + ?, updated_at = NOW() WHERE id = ?', + [quantity, existingItem[0].id] + ); + } else { + // 新增商品 + await dbConnector.query( + 'INSERT INTO cart_items (user_id, product_id, quantity, created_at, updated_at) VALUES (?, ?, ?, NOW(), NOW())', + [userId, product_id, quantity] + ); + } + + res.json({ + code: 200, + message: '添加成功', + data: { + cart_item_id: existingItem.length > 0 ? existingItem[0].id : null + } + }); + } catch (error) { + console.error('添加购物车失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 更新购物车商品数量 +router.put('/items/:id', async (req, res) => { + try { + const { id } = req.params; + const { quantity } = req.body; + const userId = req.user.id; + + // 检查购物车项是否存在 + const cartItem = await dbConnector.query( + 'SELECT * FROM cart_items WHERE id = ? AND user_id = ?', + [id, userId] + ); + + if (cartItem.length === 0) { + return res.status(404).json({ + code: 404, + message: '购物车项不存在' + }); + } + + await dbConnector.query( + 'UPDATE cart_items SET quantity = ?, updated_at = NOW() WHERE id = ?', + [quantity, id] + ); + + res.json({ + code: 200, + message: '更新成功' + }); + } catch (error) { + console.error('更新购物车失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 删除购物车商品 +router.delete('/items/:id', async (req, res) => { + try { + const { id } = req.params; + const userId = req.user.id; + + await dbConnector.query( + 'DELETE FROM cart_items WHERE id = ? AND user_id = ?', + [id, userId] + ); + + res.json({ + code: 200, + message: '删除成功' + }); + } catch (error) { + console.error('删除购物车失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/payments.js b/backend/routes/payments.js new file mode 100644 index 0000000..6752543 --- /dev/null +++ b/backend/routes/payments.js @@ -0,0 +1,148 @@ +const express = require('express'); +const router = express.Router(); +const dbConnector = require('../utils/dbConnector'); + +// 发起支付 +router.post('/orders/:order_no', async (req, res) => { + try { + const { order_no } = req.params; + const userId = req.user.id; + + // 查询订单信息 + const order = await dbConnector.query(` + SELECT o.*, u.openid + FROM orders o + JOIN users u ON o.user_id = u.id + WHERE o.order_no = ? AND o.user_id = ? + `, [order_no, userId]); + + if (order.length === 0) { + return res.status(404).json({ + code: 404, + message: '订单不存在' + }); + } + + // 检查订单状态 + if (order[0].payment_status !== 0) { + return res.status(400).json({ + code: 400, + message: '订单已支付或已取消' + }); + } + + // 模拟微信支付参数生成(实际项目中需要调用微信支付API) + const paymentParams = { + timeStamp: Math.floor(Date.now() / 1000).toString(), + nonceStr: generateNonceStr(), + package: `prepay_id=wx${generateNonceStr(28)}`, + signType: 'MD5', + paySign: generateNonceStr(32) + }; + + // 记录支付请求 + await dbConnector.query( + `INSERT INTO payments + (order_id, payment_method, amount, status, created_at, updated_at) + VALUES (?, 'wechat', ?, 'pending', NOW(), NOW())`, + [order[0].id, order[0].total_amount] + ); + + res.json({ + code: 200, + message: '支付参数生成成功', + data: { + payment_params: paymentParams + } + }); + } catch (error) { + console.error('发起支付失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 查询支付结果 +router.get('/orders/:order_no/status', async (req, res) => { + try { + const { order_no } = req.params; + const userId = req.user.id; + + // 查询订单和支付信息 + const result = await dbConnector.query(` + SELECT o.order_no, o.payment_status, p.amount as paid_amount, p.paid_at + FROM orders o + LEFT JOIN payments p ON o.id = p.order_id + WHERE o.order_no = ? AND o.user_id = ? + ORDER BY p.created_at DESC LIMIT 1 + `, [order_no, userId]); + + if (result.length === 0) { + return res.status(404).json({ + code: 404, + message: '订单不存在' + }); + } + + const paymentStatus = result[0].payment_status === 1 ? 'paid' : 'pending'; + + res.json({ + code: 200, + message: '查询成功', + data: { + order_no: result[0].order_no, + payment_status: paymentStatus, + paid_amount: result[0].paid_amount || 0, + paid_at: result[0].paid_at + } + }); + } catch (error) { + console.error('查询支付状态失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 支付回调(微信支付回调接口) +router.post('/notify/wechat', async (req, res) => { + try { + // 这里应该验证微信支付回调的签名 + const { out_trade_no, transaction_id, total_fee, time_end } = req.body; + + // 更新订单支付状态 + await dbConnector.query(` + UPDATE orders SET payment_status = 1, updated_at = NOW() + WHERE order_no = ? AND payment_status = 0 + `, [out_trade_no]); + + // 更新支付记录 + await dbConnector.query(` + UPDATE payments SET status = 'paid', transaction_id = ?, paid_at = NOW() + WHERE order_id = (SELECT id FROM orders WHERE order_no = ?) + `, [transaction_id, out_trade_no]); + + // 返回成功响应给微信 + res.send(''); + } catch (error) { + console.error('支付回调处理失败:', error); + res.status(500).send(''); + } +}); + +// 生成随机字符串 +function generateNonceStr(length = 16) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let nonceStr = ''; + for (let i = 0; i < length; i++) { + nonceStr += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return nonceStr; +} + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/promotions.js b/backend/routes/promotions.js new file mode 100644 index 0000000..a141e74 --- /dev/null +++ b/backend/routes/promotions.js @@ -0,0 +1,239 @@ +const express = require('express'); +const router = express.Router(); +const dbConnector = require('../utils/dbConnector'); + +// 获取用户推广信息 +router.get('/info', async (req, res) => { + try { + const userId = req.user.id; + + // 获取或创建推广信息 + let promotion = await dbConnector.query( + 'SELECT * FROM promotions WHERE user_id = ?', + [userId] + ); + + if (promotion.length === 0) { + // 创建新的推广信息 + const promotionCode = generatePromotionCode(userId); + await dbConnector.query( + `INSERT INTO promotions + (user_id, promotion_code, total_invites, successful_orders, total_earnings, available_balance, withdrawn_amount, created_at, updated_at) + VALUES (?, ?, 0, 0, 0, 0, 0, NOW(), NOW())`, + [userId, promotionCode] + ); + + promotion = await dbConnector.query( + 'SELECT * FROM promotions WHERE user_id = ?', + [userId] + ); + } + + // 生成推广链接和二维码(这里简化处理,实际项目中需要生成真实二维码) + const promotionInfo = { + promotion_code: promotion[0].promotion_code, + qr_code_url: `/uploads/qrcodes/promo_${promotion[0].promotion_code}.png`, + promotion_url: `https://aijianhua.com/promo/${promotion[0].promotion_code}`, + total_invites: promotion[0].total_invites, + successful_orders: promotion[0].successful_orders, + total_earnings: promotion[0].total_earnings, + available_balance: promotion[0].available_balance, + withdrawn_amount: promotion[0].withdrawn_amount + }; + + res.json({ + code: 200, + message: '获取成功', + data: promotionInfo + }); + } catch (error) { + console.error('获取推广信息失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 获取推广记录 +router.get('/records', async (req, res) => { + try { + const userId = req.user.id; + const { page = 1, limit = 10, type } = req.query; + const offset = (page - 1) * limit; + + let query = ` + SELECT 'invite' as type, u.username as user_name, u.phone, + NULL as order_amount, 10.0 as amount, 'completed' as status, u.created_at + FROM users u + WHERE u.invited_by = ? + `; + + let countQuery = 'SELECT COUNT(*) as count FROM users WHERE invited_by = ?'; + let queryParams = [userId]; + + if (type === 'order_commission') { + query = ` + SELECT 'order_commission' as type, u.username as user_name, u.phone, + o.total_amount as order_amount, o.total_amount * 0.1 as amount, + 'pending' as status, o.created_at + FROM orders o + JOIN users u ON o.user_id = u.id + WHERE u.invited_by = ? AND o.payment_status = 1 + `; + countQuery = ` + SELECT COUNT(*) as count + FROM orders o + JOIN users u ON o.user_id = u.id + WHERE u.invited_by = ? AND o.payment_status = 1 + `; + } + + query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; + queryParams.push(parseInt(limit), parseInt(offset)); + + const records = await dbConnector.query(query, queryParams); + const countResult = await dbConnector.query(countQuery, [userId]); + const total = countResult[0].count; + + res.json({ + code: 200, + message: '获取成功', + data: { + records: records, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total: total, + pages: Math.ceil(total / limit) + } + } + }); + } catch (error) { + console.error('获取推广记录失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 申请提现 +router.post('/withdraw', async (req, res) => { + try { + const { amount, payment_method, account_info } = req.body; + const userId = req.user.id; + + // 检查可用余额 + const promotion = await dbConnector.query( + 'SELECT available_balance FROM promotions WHERE user_id = ?', + [userId] + ); + + if (promotion.length === 0 || promotion[0].available_balance < amount) { + return res.status(400).json({ + code: 2001, + message: '可提现余额不足' + }); + } + + // 检查最小提现金额 + if (amount < 50) { + return res.status(400).json({ + code: 2001, + message: '提现金额不能少于50元' + }); + } + + // 创建提现记录 + const result = await dbConnector.query( + `INSERT INTO withdrawals + (user_id, amount, payment_method, account_info, status, created_at, updated_at) + VALUES (?, ?, ?, ?, 'pending', NOW(), NOW())`, + [userId, amount, payment_method, account_info] + ); + + // 更新可用余额 + await dbConnector.query( + 'UPDATE promotions SET available_balance = available_balance - ?, updated_at = NOW() WHERE user_id = ?', + [amount, userId] + ); + + res.json({ + code: 200, + message: '提现申请已提交', + data: { + withdraw_id: result.insertId, + amount: amount, + status: 'processing', + estimated_arrival: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString() // 2天后 + } + }); + } catch (error) { + console.error('申请提现失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 获取提现记录 +router.get('/withdrawals', async (req, res) => { + try { + const userId = req.user.id; + const { page = 1, limit = 10 } = req.query; + const offset = (page - 1) * limit; + + const withdrawals = await dbConnector.query( + `SELECT id, amount, payment_method, account_info, status, transaction_id, completed_at, created_at + FROM withdrawals + WHERE user_id = ? + ORDER BY created_at DESC + LIMIT ? OFFSET ?`, + [userId, parseInt(limit), parseInt(offset)] + ); + + const countResult = await dbConnector.query( + 'SELECT COUNT(*) as count FROM withdrawals WHERE user_id = ?', + [userId] + ); + const total = countResult[0].count; + + res.json({ + code: 200, + message: '获取成功', + data: { + withdrawals: withdrawals, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total: total, + pages: Math.ceil(total / limit) + } + } + }); + } catch (error) { + console.error('获取提现记录失败:', error); + res.status(500).json({ + code: 500, + message: '服务器内部错误', + error: error.message + }); + } +}); + +// 生成推广码 +function generatePromotionCode(userId) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let code = 'PROMO'; + for (let i = 0; i < 6; i++) { + code += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return code + userId.toString().padStart(4, '0'); +} + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/upload.js b/backend/routes/upload.js new file mode 100644 index 0000000..6ae3ee4 --- /dev/null +++ b/backend/routes/upload.js @@ -0,0 +1,181 @@ +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 uploadType = req.body.type || 'common'; + const uploadDir = path.join(__dirname, `../uploads/${uploadType}`); + + // 确保上传目录存在 + if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); + } + + cb(null, uploadDir); + }, + filename: (req, file, cb) => { + const uploadType = req.body.type || 'common'; + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); + const ext = path.extname(file.originalname); + const filename = `${uploadType}_${uniqueSuffix}${ext}`; + cb(null, filename); + } +}); + +const upload = multer({ + storage: storage, + limits: { + fileSize: 10 * 1024 * 1024, // 10MB限制 + }, + fileFilter: (req, file, cb) => { + // 允许所有文件类型 + cb(null, true); + } +}); + +/** + * 文件上传接口 + * POST /api/v1/upload + */ +router.post('/', upload.single('file'), asyncHandler(async (req, res) => { + if (!req.file) { + return res.status(400).json({ + code: 400, + message: '请选择要上传的文件' + }); + } + + const uploadType = req.body.type || 'common'; + const userId = req.user?.id; + + // 构建文件访问URL + const fileUrl = `/uploads/${uploadType}/${req.file.filename}`; + + // 保存文件记录到数据库(可选) + if (userId) { + try { + await dbConnector.query( + `INSERT INTO uploads + (user_id, filename, original_name, file_type, file_size, file_url, upload_type, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`, + [ + userId, + req.file.filename, + req.file.originalname, + req.file.mimetype, + req.file.size, + fileUrl, + uploadType + ] + ); + } catch (error) { + console.warn('保存文件记录失败:', error); + // 不中断上传流程,仅记录警告 + } + } + + res.json({ + code: 200, + message: '上传成功', + data: { + url: fileUrl, + filename: req.file.filename, + original_name: req.file.originalname, + size: req.file.size, + mime_type: req.file.mimetype, + upload_type: uploadType + } + }); +})); + +/** + * 获取上传文件列表 + * GET /api/v1/upload + */ +router.get('/', asyncHandler(async (req, res) => { + const userId = req.user.id; + const { page = 1, limit = 10, type } = req.query; + const offset = (page - 1) * limit; + + let query = 'SELECT * FROM uploads WHERE user_id = ?'; + let queryParams = [userId]; + + if (type) { + query += ' AND upload_type = ?'; + queryParams.push(type); + } + + query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; + queryParams.push(parseInt(limit), parseInt(offset)); + + const files = await dbConnector.query(query, queryParams); + + const countResult = await dbConnector.query( + 'SELECT COUNT(*) as count FROM uploads WHERE user_id = ?' + (type ? ' AND upload_type = ?' : ''), + type ? [userId, type] : [userId] + ); + + const total = countResult[0].count; + + res.json({ + code: 200, + message: '获取成功', + data: { + files: files, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total: total, + pages: Math.ceil(total / limit) + } + } + }); +})); + +/** + * 删除上传文件 + * DELETE /api/v1/upload/:id + */ +router.delete('/:id', asyncHandler(async (req, res) => { + const { id } = req.params; + const userId = req.user.id; + + // 查询文件信息 + const file = await dbConnector.query( + 'SELECT * FROM uploads WHERE id = ? AND user_id = ?', + [id, userId] + ); + + if (file.length === 0) { + return res.status(404).json({ + code: 404, + message: '文件不存在' + }); + } + + // 删除物理文件 + const filePath = path.join(__dirname, `../${file[0].file_url}`); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + + // 删除数据库记录 + await dbConnector.query( + 'DELETE FROM uploads WHERE id = ? AND user_id = ?', + [id, userId] + ); + + res.json({ + code: 200, + message: '删除成功' + }); +})); + +module.exports = router; \ No newline at end of file diff --git a/docs/API接口文档.md b/docs/API接口文档.md index a45dc7a..6375cda 100644 --- a/docs/API接口文档.md +++ b/docs/API接口文档.md @@ -9,6 +9,46 @@ - **Base URL**: `http://localhost:3200/api/v1` - **认证方式**: Bearer Token (JWT) - **响应格式**: JSON +- **字符编码**: UTF-8 + +## 通用响应格式 + +### 成功响应 +```json +{ + "code": 200, + "message": "操作成功", + "data": {} +} +``` + +### 错误响应 +```json +{ + "code": 400, + "message": "参数错误", + "error": "详细错误信息" +} +``` + +## 错误码说明 + +| 错误码 | 说明 | +|--------|------| +| 200 | 成功 | +| 201 | 创建成功 | +| 400 | 参数错误 | +| 401 | 未授权 | +| 403 | 禁止访问 | +| 404 | 资源不存在 | +| 409 | 资源冲突 | +| 500 | 服务器内部错误 | +| 1001 | 识别失败 | +| 1002 | 图片格式不支持 | +| 2001 | 推广奖励不足 | +| 2002 | 提现频率限制 | +| 3001 | 库存不足 | +| 3002 | 订单状态异常 | ## 认证接口 @@ -189,9 +229,337 @@ Authorization: Bearer ] ``` +## 购物车接口 + +### 1. 获取购物车 + +**GET** `/cart` + +**请求头**: +``` +Authorization: Bearer +``` + +**响应示例**: +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "product_id": 1, + "product_name": "玫瑰花束", + "product_image": "/uploads/products/rose_bouquet.jpg", + "price": 29.9, + "quantity": 2, + "subtotal": 59.8, + "stock": 100 + } + ], + "total_amount": 59.8, + "total_quantity": 2 + } +} +``` + +### 2. 添加商品到购物车 + +**POST** `/cart/items` + +**请求头**: +``` +Authorization: Bearer +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| product_id | integer | 是 | 商品ID | 1 | +| quantity | integer | 是 | 数量 | 2 | + +**响应示例**: +```json +{ + "code": 200, + "message": "添加成功", + "data": { + "cart_item_id": 1 + } +} +``` + +### 3. 更新购物车商品数量 + +**PUT** `/cart/items/{id}` + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| id | integer | 购物车项ID | 1 | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| quantity | integer | 是 | 数量 | 3 | + +**响应示例**: +```json +{ + "code": 200, + "message": "更新成功" +} +``` + +### 4. 删除购物车商品 + +**DELETE** `/cart/items/{id}` + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| id | integer | 购物车项ID | 1 | + +**响应示例**: +```json +{ + "code": 200, + "message": "删除成功" +} +``` + +## 收货地址接口 + +### 1. 获取收货地址列表 + +**GET** `/addresses` + +**请求头**: +``` +Authorization: Bearer +``` + +**响应示例**: +```json +{ + "code": 200, + "message": "获取成功", + "data": [ + { + "id": 1, + "recipient": "张三", + "phone": "13800138000", + "province": "北京市", + "city": "北京市", + "district": "海淀区", + "detail": "中关村大街1号", + "is_default": true, + "created_at": "2023-01-01T00:00:00Z" + } + ] +} +``` + +### 2. 添加收货地址 + +**POST** `/addresses` + +**请求头**: +``` +Authorization: Bearer +``` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| recipient | string | 是 | 收货人 | "张三" | +| phone | string | 是 | 手机号 | "13800138000" | +| province | string | 是 | 省份 | "北京市" | +| city | string | 是 | 城市 | "北京市" | +| district | string | 是 | 区县 | "海淀区" | +| detail | string | 是 | 详细地址 | "中关村大街1号" | +| is_default | boolean | 否 | 是否默认 | true | + +**响应示例**: +```json +{ + "code": 201, + "message": "添加成功", + "data": { + "address_id": 1 + } +} +``` + +### 3. 更新收货地址 + +**PUT** `/addresses/{id}` + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| id | integer | 地址ID | 1 | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| recipient | string | 否 | 收货人 | "张三" | +| phone | string | 否 | 手机号 | "13800138000" | +| province | string | 否 | 省份 | "北京市" | +| city | string | 否 | 城市 | "北京市" | +| district | string | 否 | 区县 | "海淀区" | +| detail | string | 否 | 详细地址 | "中关村大街1号" | +| is_default | boolean | 否 | 是否默认 | true | + +**响应示例**: +```json +{ + "code": 200, + "message": "更新成功" +} +``` + +### 4. 删除收货地址 + +**DELETE** `/addresses/{id}` + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| id | integer | 地址ID | 1 | + +**响应示例**: +```json +{ + "code": 200, + "message": "删除成功" +} +``` + +## 支付接口 + +### 1. 发起支付 + +**POST** `/pay/orders/{order_no}` + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| order_no | string | 订单编号 | "ORDER202301010001" | + +**响应示例**: +```json +{ + "code": 200, + "message": "支付参数生成成功", + "data": { + "payment_params": { + "timeStamp": "1672531200", + "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS", + "package": "prepay_id=wx201410272009395522fsd8f8f8f8f8f8", + "signType": "MD5", + "paySign": "C380BEC2BFD727A4B6845133519F3AD6" + } + } +} +``` + +### 2. 查询支付结果 + +**GET** `/pay/orders/{order_no}/status` + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: + +| 参数名 | 类型 | 说明 | 示例 | +|--------|------|------|------| +| order_no | string | 订单编号 | "ORDER202301010001" | + +**响应示例**: +```json +{ + "code": 200, + "message": "查询成功", + "data": { + "order_no": "ORDER202301010001", + "payment_status": "paid", + "paid_amount": 59.8, + "paid_at": "2023-01-01T10:05:00Z" + } +} +``` + +## 文件上传接口 + +### 1. 上传文件 + +**POST** `/upload` + +**请求格式**: `multipart/form-data` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|--------|------|------|------|------| +| file | file | 是 | 上传文件 | - | +| type | string | 否 | 文件类型 | "avatar" | + +**响应示例**: +```json +{ + "code": 200, + "message": "上传成功", + "data": { + "url": "/uploads/avatars/avatar_123.jpg", + "filename": "avatar_123.jpg", + "size": 102400, + "mime_type": "image/jpeg" + } +} +``` + ## 花卉识别接口 -### 花卉识别 +### 1. 花卉识别 **POST** `/identifications/identify` @@ -228,7 +596,7 @@ Authorization: Bearer } ``` -### 获取识别历史 +### 2. 获取识别历史 **GET** `/identifications/history` diff --git a/docs/数据库设计文档.md b/docs/数据库设计文档.md index 329038f..06099af 100644 --- a/docs/数据库设计文档.md +++ b/docs/数据库设计文档.md @@ -84,17 +84,88 @@ | user_id | bigint | 用户ID | 非空,索引 | | recipient | varchar(100) | 收货人 | 非空 | | phone | varchar(20) | 手机号 | 非空 | -| address | text | 详细地址 | 非空 | +| province | varchar(50) | 省份 | 非空 | +| city | varchar(50) | 城市 | 非空 | +| district | varchar(50) | 区县 | 非空 | +| detail | varchar(255) | 详细地址 | 非空 | | is_default | tinyint | 是否默认 | 默认0 | | created_at | timestamp | 创建时间 | 默认当前时间 | | updated_at | timestamp | 更新时间 | 自动更新 | +### 2.8 购物车表 (cart_items) +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| id | bigint | 购物车项ID | 主键,自增 | +| user_id | bigint | 用户ID | 非空,索引 | +| product_id | bigint | 商品ID | 非空,索引 | +| quantity | int | 数量 | 非空,默认1 | +| created_at | timestamp | 创建时间 | 默认当前时间 | +| updated_at | timestamp | 更新时间 | 自动更新 | + +### 2.9 推广奖励表 (promotions) +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| id | bigint | 推广ID | 主键,自增 | +| user_id | bigint | 用户ID | 非空,索引 | +| promotion_code | varchar(20) | 推广码 | 非空,唯一索引 | +| total_invites | int | 总邀请人数 | 默认0 | +| successful_orders | int | 成功订单数 | 默认0 | +| total_earnings | decimal(10,2) | 总收益 | 默认0 | +| available_balance | decimal(10,2) | 可用余额 | 默认0 | +| withdrawn_amount | decimal(10,2) | 已提现金额 | 默认0 | +| created_at | timestamp | 创建时间 | 默认当前时间 | +| updated_at | timestamp | 更新时间 | 自动更新 | + +### 2.10 提现记录表 (withdrawals) +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| id | bigint | 提现ID | 主键,自增 | +| user_id | bigint | 用户ID | 非空,索引 | +| amount | decimal(10,2) | 提现金额 | 非空 | +| payment_method | varchar(20) | 支付方式 | 非空 | +| account_info | varchar(100) | 账户信息 | 非空 | +| status | varchar(20) | 状态 | 非空,默认'pending' | +| transaction_id | varchar(50) | 交易ID | 可空 | +| completed_at | timestamp | 完成时间 | 可空 | +| created_at | timestamp | 创建时间 | 默认当前时间 | +| updated_at | timestamp | 更新时间 | 自动更新 | + +### 2.11 支付记录表 (payments) +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| id | bigint | 支付ID | 主键,自增 | +| order_id | bigint | 订单ID | 非空,索引 | +| payment_method | varchar(20) | 支付方式 | 非空 | +| amount | decimal(10,2) | 支付金额 | 非空 | +| transaction_id | varchar(50) | 交易ID | 可空 | +| status | varchar(20) | 支付状态 | 非空 | +| paid_at | timestamp | 支付时间 | 可空 | +| created_at | timestamp | 创建时间 | 默认当前时间 | +| updated_at | timestamp | 更新时间 | 自动更新 | + +### 2.12 文件上传记录表 (uploads) +| 字段名 | 类型 | 说明 | 约束 | +|--------|------|------|------| +| id | bigint | 上传记录ID | 主键,自增 | +| user_id | bigint | 用户ID | 非空,索引 | +| original_name | varchar(255) | 原始文件名 | 非空 | +| stored_name | varchar(255) | 存储文件名 | 非空 | +| file_path | varchar(500) | 文件路径 | 非空 | +| file_size | bigint | 文件大小 | 非空 | +| mime_type | varchar(100) | MIME类型 | 非空 | +| file_type | enum | 文件类型 | 默认'image' | +| upload_type | varchar(50) | 上传类型 | 非空,索引 | +| status | enum | 状态 | 默认'active' | +| created_at | timestamp | 创建时间 | 默认当前时间 | +| updated_at | timestamp | 更新时间 | 自动更新 | + ## 3. 索引设计 ### 3.1 唯一索引 - users.phone: 手机号唯一索引 - users.email: 邮箱唯一索引 - users.username: 用户名唯一索引 +- promotions.promotion_code: 推广码唯一索引 ### 3.2 普通索引 - products.category_id: 商品分类索引 @@ -103,6 +174,16 @@ - identifications.user_id: 识别记录用户索引 - identifications.created_at: 识别时间索引 - addresses.user_id: 地址用户索引 +- cart_items.user_id: 购物车用户索引 +- cart_items.product_id: 购物车商品索引 +- promotions.user_id: 推广用户索引 +- withdrawals.user_id: 提现用户索引 +- withdrawals.created_at: 提现时间索引 +- payments.order_id: 支付订单索引 +- payments.created_at: 支付时间索引 +- uploads.user_id: 上传记录用户索引 +- uploads.upload_type: 上传类型索引 +- uploads.created_at: 上传时间索引 ## 4. 测试数据统计 @@ -114,6 +195,11 @@ - 订单商品: 4条 - 识别记录: 3条 - 收货地址: 3条 +- 购物车数据: 5条 +- 推广奖励: 2条 +- 提现记录: 3条 +- 支付记录: 3条 +- 上传记录: 0条 ## 5. 数据库变更记录 @@ -122,6 +208,16 @@ - 密码字段使用password,采用bcrypt加密存储 - 增加数据库初始化脚本,包含默认管理员账号(admin/admin123) +### 2024-01-15 更新 +- 地址表增加省份、城市、区县字段,拆分详细地址 +- 新增购物车表,支持用户购物车功能 +- 新增推广奖励表,支持用户推广功能 +- 新增提现记录表,支持推广收益提现 +- 新增支付记录表,完善订单支付流程 + +### 2024-03-20 更新 +- 新增文件上传记录表,支持文件上传功能 + ## 5. 数据库连接信息 **生产环境**: diff --git a/scripts/backend/initDatabase.js b/scripts/backend/initDatabase.js index fd73caa..b00d5b7 100644 --- a/scripts/backend/initDatabase.js +++ b/scripts/backend/initDatabase.js @@ -131,12 +131,55 @@ class DatabaseInitializer { console.log('✅ 数据库连接验证通过'); console.log('─'.repeat(50)); - // 这里可以添加具体的表创建逻辑 + // 检查并创建uploads表 + const uploadsTableExists = await this.checkTableExists('uploads'); + if (!uploadsTableExists) { + console.log('📁 创建uploads表...'); + await this.createUploadsTable(); + } else { + console.log('✅ uploads表已存在'); + } + console.log('📋 数据库初始化完成'); console.log('✅ 所有检查通过,数据库连接正常'); await this.closeConnection(); } + + /** + * 创建uploads表 + */ + async createUploadsTable() { + const createTableSQL = ` + CREATE TABLE uploads ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + original_name VARCHAR(255) NOT NULL, + stored_name VARCHAR(255) NOT NULL, + file_path VARCHAR(500) NOT NULL, + file_size BIGINT NOT NULL, + mime_type VARCHAR(100) NOT NULL, + file_type ENUM('image', 'document', 'other') DEFAULT 'image', + upload_type VARCHAR(50) NOT NULL COMMENT '上传类型: avatar, product, identification, etc', + status ENUM('active', 'deleted') DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_user_id (user_id), + INDEX idx_upload_type (upload_type), + INDEX idx_created_at (created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件上传记录表' + `; + + try { + await this.connection.execute(createTableSQL); + console.log('✅ uploads表创建成功'); + return true; + } catch (error) { + console.error('❌ 创建uploads表失败:', error.message); + return false; + } + } } // 执行初始化 diff --git a/website/index.html b/website/index.html index fa53573..0dacc93 100644 --- a/website/index.html +++ b/website/index.html @@ -11,8 +11,8 @@ - - + + diff --git a/website/robots.txt b/website/robots.txt index 359860b..4d400bd 100644 --- a/website/robots.txt +++ b/website/robots.txt @@ -2,7 +2,7 @@ User-agent: * Allow: / # Sitemap location -Sitemap: https://aijianhua.com/sitemap.xml +Sitemap: https://www.aijianhua.com/sitemap.xml # Block access to sensitive directories Disallow: /admin/ diff --git a/website/sitemap.xml b/website/sitemap.xml index 76ecef5..45feeb6 100644 --- a/website/sitemap.xml +++ b/website/sitemap.xml @@ -1,37 +1,37 @@ - https://aijianhua.com/ + https://www.aijianhua.com/ 2024-01-15 weekly 1.0 - https://aijianhua.com/index.html + https://www.aijianhua.com/index.html 2024-01-15 weekly 1.0 - https://aijianhua.com/about.html + https://www.aijianhua.com/about.html 2024-01-15 monthly 0.8 - https://aijianhua.com/products.html + https://www.aijianhua.com/products.html 2024-01-15 monthly 0.9 - https://aijianhua.com/news.html + https://www.aijianhua.com/news.html 2024-01-15 weekly 0.8 - https://aijianhua.com/contact.html + https://www.aijianhua.com/contact.html 2024-01-15 yearly 0.7