docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
@@ -119,6 +119,6 @@ cd website && python3 -m http.server 8080
|
||||
|
||||
## 联系方式
|
||||
|
||||
- 项目邮箱: aijianhua@example.com
|
||||
- 项目邮箱: aijianhua@aijianhua.com
|
||||
- 问题反馈: GitHub Issues
|
||||
- 开发者微信群: 请联系项目管理员
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
176
backend/routes/addresses.js
Normal file
176
backend/routes/addresses.js
Normal file
@@ -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;
|
||||
155
backend/routes/cart.js
Normal file
155
backend/routes/cart.js
Normal file
@@ -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;
|
||||
148
backend/routes/payments.js
Normal file
148
backend/routes/payments.js
Normal file
@@ -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('<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>');
|
||||
} catch (error) {
|
||||
console.error('支付回调处理失败:', error);
|
||||
res.status(500).send('<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[处理失败]]></return_msg></xml>');
|
||||
}
|
||||
});
|
||||
|
||||
// 生成随机字符串
|
||||
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;
|
||||
239
backend/routes/promotions.js
Normal file
239
backend/routes/promotions.js
Normal file
@@ -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;
|
||||
181
backend/routes/upload.js
Normal file
181
backend/routes/upload.js
Normal file
@@ -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;
|
||||
372
docs/API接口文档.md
372
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 <token>
|
||||
]
|
||||
```
|
||||
|
||||
## 购物车接口
|
||||
|
||||
### 1. 获取购物车
|
||||
|
||||
**GET** `/cart`
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```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 <token>
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|
||||
|--------|------|------|------|------|
|
||||
| product_id | integer | 是 | 商品ID | 1 |
|
||||
| quantity | integer | 是 | 数量 | 2 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "添加成功",
|
||||
"data": {
|
||||
"cart_item_id": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 更新购物车商品数量
|
||||
|
||||
**PUT** `/cart/items/{id}`
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 说明 | 示例 |
|
||||
|--------|------|------|------|
|
||||
| id | integer | 购物车项ID | 1 |
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|
||||
|--------|------|------|------|------|
|
||||
| quantity | integer | 是 | 数量 | 3 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 删除购物车商品
|
||||
|
||||
**DELETE** `/cart/items/{id}`
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 说明 | 示例 |
|
||||
|--------|------|------|------|
|
||||
| id | integer | 购物车项ID | 1 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 收货地址接口
|
||||
|
||||
### 1. 获取收货地址列表
|
||||
|
||||
**GET** `/addresses`
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```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 <token>
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|
||||
|--------|------|------|------|------|
|
||||
| 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 <token>
|
||||
```
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 说明 | 示例 |
|
||||
|--------|------|------|------|
|
||||
| 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 <token>
|
||||
```
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 说明 | 示例 |
|
||||
|--------|------|------|------|
|
||||
| id | integer | 地址ID | 1 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 支付接口
|
||||
|
||||
### 1. 发起支付
|
||||
|
||||
**POST** `/pay/orders/{order_no}`
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 说明 | 示例 |
|
||||
|--------|------|------|------|
|
||||
| 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 <token>
|
||||
```
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 说明 | 示例 |
|
||||
|--------|------|------|------|
|
||||
| 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 <token>
|
||||
}
|
||||
```
|
||||
|
||||
### 获取识别历史
|
||||
### 2. 获取识别历史
|
||||
|
||||
**GET** `/identifications/history`
|
||||
|
||||
|
||||
@@ -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. 数据库连接信息
|
||||
|
||||
**生产环境**:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行初始化
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<meta property="og:title" content="爱鉴花 - 智能花卉识别平台">
|
||||
<meta property="og:description" content="专业的AI花卉识别平台,提供精准的花卉识别服务,准确率高达95%。">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://aijianhua.com">
|
||||
<meta property="og:image" content="https://aijianhua.com/images/og-image.png">
|
||||
<meta property="og:url" content="https://www.aijianhua.com">
|
||||
<meta property="og:image" content="https://www.aijianhua.com/images/og-image.png">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://aijianhua.com/</loc>
|
||||
<loc>https://www.aijianhua.com/</loc>
|
||||
<lastmod>2024-01-15</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/index.html</loc>
|
||||
<loc>https://www.aijianhua.com/index.html</loc>
|
||||
<lastmod>2024-01-15</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/about.html</loc>
|
||||
<loc>https://www.aijianhua.com/about.html</loc>
|
||||
<lastmod>2024-01-15</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/products.html</loc>
|
||||
<loc>https://www.aijianhua.com/products.html</loc>
|
||||
<lastmod>2024-01-15</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/news.html</loc>
|
||||
<loc>https://www.aijianhua.com/news.html</loc>
|
||||
<lastmod>2024-01-15</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/contact.html</loc>
|
||||
<loc>https://www.aijianhua.com/contact.html</loc>
|
||||
<lastmod>2024-01-15</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
|
||||
Reference in New Issue
Block a user