docs: 更新项目文档,完善需求和技术细节

This commit is contained in:
ylweng
2025-09-01 03:40:59 +08:00
parent 40460f78d2
commit 08a2e0c037
14 changed files with 1432 additions and 16 deletions

View File

@@ -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) => {

View File

@@ -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
View 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
View 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
View 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;

View 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
View 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;