docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 应用配置
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
PORT=3200
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
||||
|
||||
# 数据库配置
|
||||
|
||||
@@ -6,10 +6,10 @@ PORT=3200
|
||||
APP_NAME=爱鉴花小程序后端
|
||||
|
||||
# MySQL数据库配置
|
||||
DB_HOST=192.168.0.240
|
||||
DB_PORT=3306
|
||||
DB_HOST=129.211.213.226
|
||||
DB_PORT=9527
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=aiot$Aiot123
|
||||
DB_PASSWORD=aiotAiot123!
|
||||
DB_DATABASE=ajhdata
|
||||
|
||||
# Redis配置
|
||||
|
||||
@@ -7,10 +7,10 @@ PORT=3000
|
||||
APP_NAME=爱鉴花小程序后端
|
||||
|
||||
# MySQL数据库配置
|
||||
DB_HOST=192.168.0.240
|
||||
DB_PORT=3306
|
||||
DB_HOST=129.211.213.226
|
||||
DB_PORT=9527
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=aiot$Aiot123
|
||||
DB_PASSWORD=aiotAiot123!
|
||||
DB_DATABASE=ajhdata
|
||||
|
||||
# Redis配置
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **基础URL**: `http://localhost:3000/api/v1`
|
||||
- **基础URL**: `http://localhost:3200/api/v1`
|
||||
- **认证方式**: Bearer Token (JWT)
|
||||
- **数据格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
## 技术栈
|
||||
- Node.js
|
||||
- Express.js
|
||||
- MongoDB
|
||||
- SQLite(开发环境)
|
||||
- MySQL(生产环境)
|
||||
- Redis
|
||||
|
||||
## 文件结构
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
## 📋 数据库连接信息
|
||||
|
||||
### 测试环境
|
||||
- **主机**: 192.168.0.240
|
||||
- **端口**: 3306
|
||||
### 开发环境 (MySQL)
|
||||
- **主机**: 129.211.213.226
|
||||
- **端口**: 9527
|
||||
- **用户名**: root
|
||||
- **密码**: aiot$Aiot123
|
||||
- **密码**: aiotAiot123!
|
||||
- **数据库**: ajhdata
|
||||
|
||||
### 生产环境
|
||||
### 生产环境 (MySQL)
|
||||
- **主机**: 129.211.213.226
|
||||
- **端口**: 9527
|
||||
- **用户名**: root
|
||||
|
||||
@@ -22,13 +22,14 @@ const userRoutes = require('./routes/users');
|
||||
const productRoutes = require('./routes/products');
|
||||
const orderRoutes = require('./routes/orders');
|
||||
const identificationRoutes = require('./routes/identifications');
|
||||
const adminRoutes = require('./routes/admin');
|
||||
|
||||
// 导入中间件
|
||||
const { errorHandler } = require('./middlewares/errorHandler');
|
||||
const { authMiddleware } = require('./middlewares/auth');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const PORT = process.env.PORT || 3201;
|
||||
|
||||
// 安全中间件
|
||||
app.use(helmet());
|
||||
@@ -75,6 +76,7 @@ app.use('/api/v1/users', authMiddleware, userRoutes);
|
||||
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);
|
||||
|
||||
// 404处理
|
||||
app.use('*', (req, res) => {
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*/
|
||||
|
||||
const databaseConfig = {
|
||||
// 测试环境配置
|
||||
// 开发环境配置
|
||||
development: {
|
||||
host: '192.168.0.240',
|
||||
port: 3306,
|
||||
host: '129.211.213.226',
|
||||
port: 9527,
|
||||
username: 'root',
|
||||
password: 'aiot$Aiot123',
|
||||
password: 'aiotAiot123!',
|
||||
database: 'ajhdata',
|
||||
dialect: 'mysql',
|
||||
logging: console.log,
|
||||
|
||||
@@ -21,7 +21,7 @@ const options = {
|
||||
{
|
||||
url: process.env.NODE_ENV === 'production'
|
||||
? 'https://api.aijianhua.com'
|
||||
: `http://localhost:${process.env.PORT || 3000}`,
|
||||
: `http://localhost:${process.env.PORT || 3200}`,
|
||||
description: process.env.NODE_ENV === 'production' ? '生产环境' : '开发环境'
|
||||
}
|
||||
],
|
||||
|
||||
BIN
backend/database.sqlite
Normal file
BIN
backend/database.sqlite
Normal file
Binary file not shown.
5309
backend/package-lock.json
generated
5309
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,49 +5,43 @@
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"dev": "nodemon app.js",
|
||||
"test": "jest",
|
||||
"lint": "eslint .",
|
||||
"dev": "NODE_ENV=development PORT=3200 nodemon app.js",
|
||||
"prod": "NODE_ENV=production node app.js",
|
||||
"db:init": "node scripts/initDatabase.js",
|
||||
"db:check": "node scripts/initDatabase.js --check",
|
||||
"db:migrate": "node scripts/migrate.js",
|
||||
"db:seed": "node scripts/seedData.js"
|
||||
"test": "jest",
|
||||
"docs": "node scripts/generateDocs.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^3.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"helmet": "^7.0.0",
|
||||
"compression": "^1.7.4",
|
||||
"morgan": "^1.10.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"validator": "^13.11.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"helmet": "^6.1.5",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.6.0",
|
||||
"redis": "^4.6.8",
|
||||
"socket.io": "^4.7.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"swagger-ui-express": "^4.6.3",
|
||||
"validator": "^13.11.0",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1",
|
||||
"jest": "^29.6.2",
|
||||
"eslint": "^8.47.0",
|
||||
"prettier": "^3.0.2",
|
||||
"@types/node": "^20.5.0",
|
||||
"typescript": "^5.1.6"
|
||||
"nodemon": "^2.0.22"
|
||||
},
|
||||
"keywords": [
|
||||
"wechat",
|
||||
"mini-program",
|
||||
"plant-identification",
|
||||
"ecommerce",
|
||||
"api"
|
||||
"aijianhua",
|
||||
"plant",
|
||||
"identification",
|
||||
"api",
|
||||
"express"
|
||||
],
|
||||
"author": "爱鉴花开发团队",
|
||||
"author": "爱鉴花团队",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
265
backend/routes/admin.js
Normal file
265
backend/routes/admin.js
Normal file
@@ -0,0 +1,265 @@
|
||||
const express = require('express');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { adminRequired } = require('../middlewares/auth');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取系统统计概览
|
||||
*/
|
||||
router.get('/statistics/overview', adminRequired, asyncHandler(async (req, res) => {
|
||||
// 获取总用户数
|
||||
const totalUsersResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM users WHERE status = 1'
|
||||
);
|
||||
|
||||
// 获取今日新增用户数
|
||||
const newUsersTodayResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM users WHERE status = 1 AND DATE(created_at) = CURDATE()'
|
||||
);
|
||||
|
||||
// 获取总订单数
|
||||
const totalOrdersResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM orders'
|
||||
);
|
||||
|
||||
// 获取总收入
|
||||
const totalRevenueResult = await dbConnector.query(
|
||||
'SELECT COALESCE(SUM(total_amount), 0) as total FROM orders WHERE payment_status = 1'
|
||||
);
|
||||
|
||||
// 获取总识别次数
|
||||
const totalIdentificationsResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM identifications'
|
||||
);
|
||||
|
||||
// 获取今日活跃用户数
|
||||
const activeUsersTodayResult = await dbConnector.query(
|
||||
`SELECT COUNT(DISTINCT user_id) as total FROM (
|
||||
SELECT user_id FROM orders WHERE DATE(created_at) = CURDATE()
|
||||
UNION
|
||||
SELECT user_id FROM identifications WHERE DATE(created_at) = CURDATE()
|
||||
) as active_users`
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total_users: totalUsersResult[0].total,
|
||||
new_users_today: newUsersTodayResult[0].total,
|
||||
total_orders: totalOrdersResult[0].total,
|
||||
total_revenue: parseFloat(totalRevenueResult[0].total),
|
||||
total_identifications: totalIdentificationsResult[0].total,
|
||||
active_users_today: activeUsersTodayResult[0].total
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取趋势数据
|
||||
*/
|
||||
router.get('/statistics/trend', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { days = 7, type = 'users' } = req.query;
|
||||
const dayCount = parseInt(days);
|
||||
|
||||
let query = '';
|
||||
let dataKey = '';
|
||||
|
||||
switch (type) {
|
||||
case 'users':
|
||||
query = `
|
||||
SELECT DATE(created_at) as date, COUNT(*) as count
|
||||
FROM users
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date
|
||||
`;
|
||||
dataKey = 'new_users';
|
||||
break;
|
||||
case 'orders':
|
||||
query = `
|
||||
SELECT DATE(created_at) as date, COUNT(*) as count
|
||||
FROM orders
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date
|
||||
`;
|
||||
dataKey = 'new_orders';
|
||||
break;
|
||||
case 'revenue':
|
||||
query = `
|
||||
SELECT DATE(created_at) as date, COALESCE(SUM(total_amount), 0) as amount
|
||||
FROM orders
|
||||
WHERE payment_status = 1 AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date
|
||||
`;
|
||||
dataKey = 'revenue';
|
||||
break;
|
||||
case 'identifications':
|
||||
query = `
|
||||
SELECT DATE(created_at) as date, COUNT(*) as count
|
||||
FROM identifications
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date
|
||||
`;
|
||||
dataKey = 'identifications';
|
||||
break;
|
||||
default:
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '不支持的统计类型',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const trendData = await dbConnector.query(query, [dayCount]);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
type,
|
||||
days: dayCount,
|
||||
trend: trendData,
|
||||
total: trendData.reduce((sum, item) => sum + (item.count || item.amount || 0), 0)
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取系统配置列表
|
||||
*/
|
||||
router.get('/config', adminRequired, asyncHandler(async (req, res) => {
|
||||
const configs = await dbConnector.query(
|
||||
'SELECT config_key, config_value, config_type, description FROM system_config ORDER BY config_key'
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: configs
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取单个系统配置
|
||||
*/
|
||||
router.get('/config/:key', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { key } = req.params;
|
||||
|
||||
const configs = await dbConnector.query(
|
||||
'SELECT config_key, config_value, config_type, description FROM system_config WHERE config_key = ?',
|
||||
[key]
|
||||
);
|
||||
|
||||
if (configs.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '配置项不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: configs[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 更新系统配置
|
||||
*/
|
||||
router.put('/config/:key', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { key } = req.params;
|
||||
const { value } = req.body;
|
||||
|
||||
if (value === undefined) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '配置值不能为空',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const result = await dbConnector.query(
|
||||
'UPDATE system_config SET config_value = ?, updated_at = CURRENT_TIMESTAMP WHERE config_key = ?',
|
||||
[value, key]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '配置项不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取操作日志
|
||||
*/
|
||||
router.get('/logs', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 20, module, action, username } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause = 'WHERE 1=1';
|
||||
let queryParams = [];
|
||||
|
||||
if (module) {
|
||||
whereClause += ' AND module = ?';
|
||||
queryParams.push(module);
|
||||
}
|
||||
|
||||
if (action) {
|
||||
whereClause += ' AND action = ?';
|
||||
queryParams.push(action);
|
||||
}
|
||||
|
||||
if (username) {
|
||||
whereClause += ' AND username LIKE ?';
|
||||
queryParams.push(`%${username}%`);
|
||||
}
|
||||
|
||||
// 获取日志列表
|
||||
const logs = await dbConnector.query(
|
||||
`SELECT id, user_id, username, user_type, module, action, target_id,
|
||||
description, ip_address, user_agent, created_at
|
||||
FROM operation_logs ${whereClause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 获取总数
|
||||
const totalResult = await dbConnector.query(
|
||||
`SELECT COUNT(*) as total FROM operation_logs ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
logs,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
@@ -66,7 +66,7 @@ router.post('/register', async (req, res, next) => {
|
||||
|
||||
// 创建用户
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO users (username, password_hash, phone, email, user_type) VALUES (?, ?, ?, ?, ?)',
|
||||
'INSERT INTO users (username, password, phone, email, user_type) VALUES (?, ?, ?, ?, ?)',
|
||||
[username, hashedPassword, phone, email, user_type]
|
||||
);
|
||||
|
||||
@@ -127,7 +127,7 @@ router.post('/login', async (req, res, next) => {
|
||||
const userData = user[0];
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await bcrypt.compare(password, userData.password_hash);
|
||||
const isValidPassword = await bcrypt.compare(password, userData.password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
|
||||
260
backend/scripts/initSQLite.js
Normal file
260
backend/scripts/initSQLite.js
Normal file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* SQLite数据库初始化脚本
|
||||
* 用于创建SQLite数据库表结构
|
||||
*/
|
||||
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
const databaseConfig = require('../config/database');
|
||||
|
||||
class SQLiteInitializer {
|
||||
constructor() {
|
||||
this.db = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建数据库连接
|
||||
*/
|
||||
createConnection() {
|
||||
try {
|
||||
this.db = new sqlite3.Database(databaseConfig.storage);
|
||||
console.log('✅ SQLite数据库连接成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ SQLite数据库连接失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL语句
|
||||
*/
|
||||
run(sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, params, function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建users表
|
||||
*/
|
||||
async createUsersTable() {
|
||||
const sql = `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
phone VARCHAR(20) UNIQUE,
|
||||
email VARCHAR(100) UNIQUE,
|
||||
user_type VARCHAR(20) DEFAULT 'user',
|
||||
status INTEGER DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`;
|
||||
|
||||
try {
|
||||
await this.run(sql);
|
||||
console.log('✅ users表创建成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 创建users表失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建orders表
|
||||
*/
|
||||
async createOrdersTable() {
|
||||
const sql = `
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_number VARCHAR(50) UNIQUE NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
total_amount DECIMAL(10,2) NOT NULL,
|
||||
payment_status INTEGER DEFAULT 0,
|
||||
shipping_status INTEGER DEFAULT 0,
|
||||
shipping_address TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)
|
||||
`;
|
||||
|
||||
try {
|
||||
await this.run(sql);
|
||||
console.log('✅ orders表创建成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 创建orders表失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建identifications表
|
||||
*/
|
||||
async createIdentificationsTable() {
|
||||
const sql = `
|
||||
CREATE TABLE IF NOT EXISTS identifications (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
image_url VARCHAR(255) NOT NULL,
|
||||
result TEXT NOT NULL,
|
||||
confidence DECIMAL(5,4) NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)
|
||||
`;
|
||||
|
||||
try {
|
||||
await this.run(sql);
|
||||
console.log('✅ identifications表创建成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 创建identifications表失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建products表
|
||||
*/
|
||||
async createProductsTable() {
|
||||
const sql = `
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
category_id INTEGER,
|
||||
price DECIMAL(10,2) NOT NULL,
|
||||
stock INTEGER DEFAULT 0,
|
||||
image VARCHAR(255),
|
||||
description TEXT,
|
||||
status INTEGER DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`;
|
||||
|
||||
try {
|
||||
await this.run(sql);
|
||||
console.log('✅ products表创建成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 创建products表失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入初始管理员用户
|
||||
*/
|
||||
async insertAdminUser() {
|
||||
const sql = `
|
||||
INSERT OR IGNORE INTO users (username, password, phone, email, user_type, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
// 密码: admin123 (bcrypt加密)
|
||||
const hashedPassword = '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi';
|
||||
|
||||
try {
|
||||
await this.run(sql, [
|
||||
'admin',
|
||||
hashedPassword,
|
||||
'13800138000',
|
||||
'admin@example.com',
|
||||
'admin',
|
||||
1
|
||||
]);
|
||||
console.log('✅ 管理员用户创建成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 创建管理员用户失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭数据库连接
|
||||
*/
|
||||
closeConnection() {
|
||||
if (this.db) {
|
||||
this.db.close();
|
||||
console.log('🔌 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主初始化方法
|
||||
*/
|
||||
async initialize() {
|
||||
console.log('🚀 开始SQLite数据库初始化...');
|
||||
console.log(`📋 环境: ${process.env.NODE_ENV || 'development'}`);
|
||||
console.log(`🗄️ 数据库文件: ${databaseConfig.storage}`);
|
||||
console.log('─'.repeat(50));
|
||||
|
||||
// 创建连接
|
||||
const connected = this.createConnection();
|
||||
if (!connected) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 创建表
|
||||
const tablesCreated = await Promise.all([
|
||||
this.createUsersTable(),
|
||||
this.createOrdersTable(),
|
||||
this.createIdentificationsTable(),
|
||||
this.createProductsTable()
|
||||
]);
|
||||
|
||||
if (tablesCreated.every(result => result)) {
|
||||
console.log('✅ 所有表创建成功');
|
||||
} else {
|
||||
console.error('❌ 表创建过程中出现错误');
|
||||
this.closeConnection();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 插入初始数据
|
||||
const adminCreated = await this.insertAdminUser();
|
||||
if (!adminCreated) {
|
||||
console.warn('⚠️ 管理员用户创建失败或已存在');
|
||||
}
|
||||
|
||||
console.log('✅ SQLite数据库初始化完成');
|
||||
this.closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
// 执行初始化
|
||||
const initializer = new SQLiteInitializer();
|
||||
|
||||
// 处理命令行参数
|
||||
const args = process.argv.slice(2);
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log(`
|
||||
使用方法: node scripts/initSQLite.js [选项]
|
||||
|
||||
选项:
|
||||
--help, -h 显示帮助信息
|
||||
|
||||
示例:
|
||||
node scripts/initSQLite.js # 完整初始化
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
initializer.initialize().catch(error => {
|
||||
console.error('❌ 初始化过程中发生错误:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -4,12 +4,14 @@
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const databaseConfig = require('../config/database');
|
||||
|
||||
class DBConnector {
|
||||
constructor() {
|
||||
this.pool = null;
|
||||
this.connection = null;
|
||||
this.db = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -18,25 +20,31 @@ class DBConnector {
|
||||
*/
|
||||
init() {
|
||||
try {
|
||||
this.pool = mysql.createPool({
|
||||
host: databaseConfig.host,
|
||||
port: databaseConfig.port,
|
||||
user: databaseConfig.username,
|
||||
password: databaseConfig.password,
|
||||
database: databaseConfig.database,
|
||||
connectionLimit: databaseConfig.pool.max,
|
||||
acquireTimeout: databaseConfig.pool.acquire,
|
||||
timeout: databaseConfig.pool.idle,
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
decimalNumbers: true,
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: false
|
||||
});
|
||||
|
||||
console.log('✅ 数据库连接池初始化成功');
|
||||
if (databaseConfig.dialect === 'sqlite') {
|
||||
// SQLite配置
|
||||
this.db = new sqlite3.Database(databaseConfig.storage);
|
||||
console.log('✅ SQLite数据库连接成功');
|
||||
} else {
|
||||
// MySQL配置
|
||||
this.pool = mysql.createPool({
|
||||
host: databaseConfig.host,
|
||||
port: databaseConfig.port,
|
||||
user: databaseConfig.username,
|
||||
password: databaseConfig.password,
|
||||
database: databaseConfig.database,
|
||||
connectionLimit: databaseConfig.pool.max,
|
||||
acquireTimeout: databaseConfig.pool.acquire,
|
||||
timeout: databaseConfig.pool.idle,
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
decimalNumbers: true,
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: false
|
||||
});
|
||||
console.log('✅ MySQL数据库连接池初始化成功');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接池初始化失败:', error.message);
|
||||
console.error('❌ 数据库连接初始化失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -62,19 +70,36 @@ class DBConnector {
|
||||
* @returns {Promise} 查询结果
|
||||
*/
|
||||
async query(sql, params = []) {
|
||||
let connection;
|
||||
try {
|
||||
connection = await this.getConnection();
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
return rows;
|
||||
} catch (error) {
|
||||
console.error('❌ SQL执行失败:', error.message);
|
||||
console.error('SQL:', sql);
|
||||
console.error('参数:', params);
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
if (databaseConfig.dialect === 'sqlite') {
|
||||
// SQLite查询实现
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.all(sql, params, (err, rows) => {
|
||||
if (err) {
|
||||
console.error('❌ SQL执行失败:', err.message);
|
||||
console.error('SQL:', sql);
|
||||
console.error('参数:', params);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// MySQL查询实现
|
||||
let connection;
|
||||
try {
|
||||
connection = await this.getConnection();
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
return rows;
|
||||
} catch (error) {
|
||||
console.error('❌ SQL执行失败:', error.message);
|
||||
console.error('SQL:', sql);
|
||||
console.error('参数:', params);
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
278
backend/后台管理系统API文档.md
Normal file
278
backend/后台管理系统API文档.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# 爱鉴花后台管理系统 API 接口文档
|
||||
|
||||
基于 OpenAPI 3.0 规范
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **Base URL**: `http://localhost:3200/api`
|
||||
- **认证方式**: Bearer Token (JWT)
|
||||
- **响应格式**: JSON
|
||||
|
||||
## 统一响应格式
|
||||
|
||||
所有接口都遵循以下响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
## 认证接口
|
||||
|
||||
### 管理员登录
|
||||
|
||||
**POST** `/auth/login`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| login | string | 是 | 用户名/手机号/邮箱 |
|
||||
| password | string | 是 | 密码 |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "登录成功",
|
||||
"data": {
|
||||
"user_id": 1,
|
||||
"username": "admin",
|
||||
"user_type": "admin",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 用户管理接口
|
||||
|
||||
### 获取用户列表
|
||||
|
||||
**GET** `/users`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| page | number | 否 | 页码,默认1 |
|
||||
| limit | number | 否 | 每页数量,默认10 |
|
||||
| keyword | string | 否 | 搜索关键词 |
|
||||
| user_type | string | 否 | 用户类型过滤 |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"users": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "testuser",
|
||||
"phone": "13800138000",
|
||||
"email": "test@example.com",
|
||||
"user_type": "farmer",
|
||||
"created_at": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"total": 100,
|
||||
"pages": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 获取用户详情
|
||||
|
||||
**GET** `/users/{id}`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | number | 是 | 用户ID |
|
||||
|
||||
### 删除用户
|
||||
|
||||
**DELETE** `/users/{id}`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
## 商品管理接口
|
||||
|
||||
### 获取商品列表
|
||||
|
||||
**GET** `/products`
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| page | number | 否 | 页码,默认1 |
|
||||
| limit | number | 否 | 每页数量,默认10 |
|
||||
| category_id | number | 否 | 分类ID过滤 |
|
||||
| min_price | number | 否 | 最低价格 |
|
||||
| max_price | number | 否 | 最高价格 |
|
||||
| status | number | 否 | 状态过滤 |
|
||||
|
||||
### 创建商品
|
||||
|
||||
**POST** `/products`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "玫瑰花",
|
||||
"category_id": 1,
|
||||
"price": 29.9,
|
||||
"stock": 100,
|
||||
"image": "/uploads/roses.jpg",
|
||||
"description": "新鲜玫瑰花束"
|
||||
}
|
||||
```
|
||||
|
||||
## 订单管理接口
|
||||
|
||||
### 获取订单列表
|
||||
|
||||
**GET** `/orders`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| page | number | 否 | 页码,默认1 |
|
||||
| limit | number | 否 | 每页数量,默认10 |
|
||||
| status | number | 否 | 订单状态过滤 |
|
||||
| start_date | string | 否 | 开始日期 |
|
||||
| end_date | string | 否 | 结束日期 |
|
||||
|
||||
### 更新订单状态
|
||||
|
||||
**PUT** `/orders/{id}/status`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"payment_status": 1,
|
||||
"shipping_status": 2
|
||||
}
|
||||
```
|
||||
|
||||
## 识别记录管理接口
|
||||
|
||||
### 获取识别记录列表
|
||||
|
||||
**GET** `/identifications`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| page | number | 否 | 页码,默认1 |
|
||||
| limit | number | 否 | 每页数量,默认10 |
|
||||
| start_date | string | 否 | 开始日期 |
|
||||
| end_date | string | 否 | 结束日期 |
|
||||
| min_confidence | number | 否 | 最低置信度 |
|
||||
|
||||
## 数据统计接口
|
||||
|
||||
### 获取系统统计概览
|
||||
|
||||
**GET** `/admin/statistics/overview`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"total_users": 1000,
|
||||
"new_users_today": 10,
|
||||
"total_orders": 500,
|
||||
"total_revenue": 15000.50,
|
||||
"total_identifications": 2000,
|
||||
"active_users_today": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 获取趋势数据
|
||||
|
||||
**GET** `/admin/statistics/trend`
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| days | number | 否 | 天数,默认7 |
|
||||
| type | string | 否 | 统计类型(users/orders/revenue/identifications) |
|
||||
|
||||
## 系统配置接口
|
||||
|
||||
### 获取系统配置
|
||||
|
||||
**GET** `/admin/config`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
### 更新系统配置
|
||||
|
||||
**PUT** `/admin/config/{key}`
|
||||
|
||||
**权限要求**: 管理员
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"value": "new_value"
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 成功 |
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权 |
|
||||
| 403 | 权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 409 | 资源冲突 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 安全要求
|
||||
|
||||
1. 所有敏感操作需要管理员权限
|
||||
2. API请求需要携带有效的JWT Token
|
||||
3. 密码传输需要加密
|
||||
4. 文件上传需要验证文件类型和大小
|
||||
|
||||
---
|
||||
|
||||
*文档版本: 1.0.0*
|
||||
*最后更新: 2024-01-01*
|
||||
Reference in New Issue
Block a user