docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user