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

@@ -119,6 +119,6 @@ cd website && python3 -m http.server 8080
## 联系方式
- 项目邮箱: aijianhua@example.com
- 项目邮箱: aijianhua@aijianhua.com
- 问题反馈: GitHub Issues
- 开发者微信群: 请联系项目管理员

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;

View File

@@ -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`

View File

@@ -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. 数据库连接信息
**生产环境**:

View File

@@ -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;
}
}
}
// 执行初始化

View File

@@ -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">

View File

@@ -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/

View File

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