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