refactor(backend): 重构动物相关 API 接口

- 更新了动物数据结构和相关类型定义
- 优化了动物列表、详情、创建、更新和删除接口
- 新增了更新动物状态接口
- 移除了与认领记录相关的接口
-调整了 API 响应结构
This commit is contained in:
ylweng
2025-08-31 00:45:46 +08:00
parent 0cad74b06f
commit 8e5295b572
111 changed files with 15290 additions and 1972 deletions

View File

@@ -1,14 +1,14 @@
# 服务器配置
NODE_ENV=development
PORT=3001
PORT=3100
HOST=0.0.0.0
ENABLE_SWAGGER=true
# MySQL数据库配置
DB_HOST=192.168.0.240
DB_PORT=3306
DB_HOST=129.211.213.226
DB_PORT=9527
DB_USER=root
DB_PASSWORD=aiot$Aiot123
DB_PASSWORD=aiotAiot123!
DB_NAME=jiebandata
# 测试环境数据库

View File

@@ -5,7 +5,7 @@ require('dotenv').config({ path: path.join(__dirname, '../../.env') })
const config = {
// 开发环境
development: {
port: process.env.PORT || 3000,
port: process.env.PORT || 3100,
mongodb: {
uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/jiebanke_dev',
options: {
@@ -35,7 +35,7 @@ const config = {
// 测试环境
test: {
port: process.env.PORT || 3001,
port: process.env.PORT || 3100,
mongodb: {
uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/jiebanke_test',
options: {
@@ -56,7 +56,7 @@ const config = {
// 生产环境
production: {
port: process.env.PORT || 3000,
port: process.env.PORT || 3100,
mongodb: {
uri: process.env.MONGODB_URI,
options: {

View File

@@ -12,7 +12,7 @@
"amqplib": "^0.10.9",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"dotenv": "^16.6.1",
"express": "^4.18.2",
"express-mongo-sanitize": "^2.2.0",
"express-rate-limit": "^7.1.5",
@@ -1674,8 +1674,9 @@
},
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
"resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
"license": "MIT"
},
"node_modules/binary-extensions": {
"version": "2.3.0",
@@ -2340,8 +2341,9 @@
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
@@ -4179,8 +4181,9 @@
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",

View File

@@ -22,7 +22,7 @@
"amqplib": "^0.10.9",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"dotenv": "^16.6.1",
"express": "^4.18.2",
"express-mongo-sanitize": "^2.2.0",
"express-rate-limit": "^7.1.5",

View File

@@ -1,41 +1,45 @@
const express = require('express')
const cors = require('cors')
const helmet = require('helmet')
const morgan = require('morgan')
const rateLimit = require('express-rate-limit')
const xss = require('xss-clean')
const hpp = require('hpp')
const swaggerUi = require('swagger-ui-express')
const swaggerSpec = require('./config/swagger')
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const xss = require('xss-clean');
const hpp = require('hpp');
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./config/swagger');
console.log('🔧 初始化Express应用...')
console.log('🔧 初始化Express应用...');
const { globalErrorHandler, notFound } = require('./utils/errors')
const { globalErrorHandler, notFound } = require('./utils/errors');
// 路由导入
const authRoutes = require('./routes/auth')
// 其他路由将在这里导入
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/user');
const travelRoutes = require('./routes/travel');
const animalRoutes = require('./routes/animal');
const orderRoutes = require('./routes/order');
const adminRoutes = require('./routes/admin'); // 新增管理员路由
const app = express()
const app = express();
console.log('✅ Express应用初始化完成')
console.log('✅ Express应用初始化完成');
// 安全中间件
app.use(helmet())
app.use(helmet());
// CORS配置
app.use(cors({
origin: process.env.NODE_ENV === 'production'
? ['https://your-domain.com']
: ['http://localhost:9000', 'http://localhost:3000'],
: ['http://localhost:9000', 'http://localhost:3000', 'http://localhost:3100', 'http://localhost:3150'],
credentials: true
}))
}));
// 请求日志
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'))
app.use(morgan('dev'));
} else {
app.use(morgan('combined'))
app.use(morgan('combined'));
}
// 请求频率限制
@@ -48,15 +52,15 @@ const limiter = rateLimit({
message: '请求过于频繁,请稍后再试',
timestamp: new Date().toISOString()
}
})
app.use('/api', limiter)
});
app.use('/api', limiter);
// 请求体解析
app.use(express.json({ limit: '10kb' }))
app.use(express.urlencoded({ extended: true, limit: '10kb' }))
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
// 数据清洗
app.use(xss()) // 防止XSS攻击
app.use(xss()); // 防止XSS攻击
app.use(hpp({ // 防止参数污染
whitelist: [
'page',
@@ -67,15 +71,15 @@ app.use(hpp({ // 防止参数污染
'rating',
'distance'
]
}))
}));
// 静态文件服务
app.use('/uploads', express.static('uploads'))
app.use('/uploads', express.static('uploads'));
// Swagger文档路由
if (process.env.NODE_ENV === 'development' || process.env.ENABLE_SWAGGER === 'true') {
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
console.log('📚 Swagger文档已启用: http://localhost:3001/api-docs')
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
console.log('📚 Swagger文档已启用: http://localhost:3100/api-docs');
}
// 健康检查路由
@@ -85,24 +89,24 @@ app.get('/health', (req, res) => {
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: process.env.NODE_ENV || 'development'
})
})
});
});
// API路由
app.use('/api/v1/auth', authRoutes)
// 其他API路由将在这里添加
// app.use('/api/v1/users', userRoutes)
// app.use('/api/v1/travel', travelRoutes)
// app.use('/api/v1/animals', animalRoutes)
// app.use('/api/v1/flowers', flowerRoutes)
// app.use('/api/v1/orders', orderRoutes)
app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/users', userRoutes);
app.use('/api/v1/travel', travelRoutes);
app.use('/api/v1/animals', animalRoutes);
app.use('/api/v1/orders', orderRoutes);
// 管理员路由
app.use('/api/v1/admin', adminRoutes);
// 404处理
app.use('*', notFound)
app.use('*', notFound);
// 全局错误处理
app.use(globalErrorHandler)
app.use(globalErrorHandler);
console.log('✅ 应用配置完成')
console.log('✅ 应用配置完成');
module.exports = app
module.exports = app;

View File

@@ -2,18 +2,21 @@ const mysql = require('mysql2/promise');
// 数据库配置
const dbConfig = {
host: process.env.DB_HOST || '192.168.0.240',
port: process.env.DB_PORT || 3306,
host: process.env.DB_HOST || '129.211.213.226',
port: process.env.DB_PORT || 9527,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'aiot$Aiot123',
password: process.env.DB_PASSWORD || 'aiotAiot123!',
database: process.env.DB_NAME || 'jiebandata',
connectionLimit: 10,
// 移除无效的配置选项 acquireTimeout 和 timeout
charset: 'utf8mb4',
timezone: '+08:00',
// 连接池配置
waitForConnections: true,
queueLimit: 0
queueLimit: 0,
// 超时配置
connectTimeout: 10000, // 10秒连接超时
acquireTimeout: 10000, // 10秒获取连接超时
timeout: 10000 // 10秒查询超时
};
// 创建连接池
@@ -33,41 +36,105 @@ async function testConnection() {
}
// 执行查询
async function query(sql, params = []) {
const query = async (sql, params = []) => {
let connection;
try {
const [rows] = await pool.execute(sql, params);
return rows;
connection = await pool.getConnection();
const [results] = await connection.execute(sql, params);
connection.release();
return results;
} catch (error) {
console.error('数据库查询错误:', error.message);
if (connection) {
connection.release();
}
throw error;
}
}
};
// 执行事务
async function transaction(callback) {
const connection = await pool.getConnection();
// 事务处理
const transaction = async (callback) => {
let connection;
try {
connection = await pool.getConnection();
await connection.beginTransaction();
const result = await callback(connection);
await connection.commit();
connection.release();
return result;
} catch (error) {
await connection.rollback();
if (connection) {
await connection.rollback();
connection.release();
}
throw error;
} finally {
connection.release();
}
}
};
// 关闭连接池
async function closePool() {
await pool.end();
try {
await pool.end();
console.log('✅ MySQL连接池已关闭');
} catch (error) {
console.error('❌ 关闭MySQL连接池时出错:', error.message);
}
}
// 管理员数据库操作
const adminDB = {
// 根据用户名查找管理员
findByUsername: async (username) => {
const sql = 'SELECT * FROM admins WHERE username = ?';
const results = await query(sql, [username]);
return results[0];
},
// 根据ID查找管理员
findById: async (id) => {
const sql = 'SELECT * FROM admins WHERE id = ?';
const results = await query(sql, [id]);
return results[0];
},
// 创建管理员
create: async (adminData) => {
const keys = Object.keys(adminData);
const values = Object.values(adminData);
const placeholders = keys.map(() => '?').join(', ');
const sql = `INSERT INTO admins (${keys.join(', ')}) VALUES (${placeholders})`;
const result = await query(sql, values);
return result.insertId;
},
// 更新管理员最后登录时间
updateLastLogin: async (id) => {
const sql = 'UPDATE admins SET last_login = CURRENT_TIMESTAMP WHERE id = ?';
await query(sql, [id]);
},
// 更新管理员信息
update: async (id, adminData) => {
const keys = Object.keys(adminData);
const values = Object.values(adminData);
const setClause = keys.map(key => `${key} = ?`).join(', ');
const sql = `UPDATE admins SET ${setClause} WHERE id = ?`;
await query(sql, [...values, id]);
},
// 删除管理员
delete: async (id) => {
const sql = 'DELETE FROM admins WHERE id = ?';
await query(sql, [id]);
}
};
module.exports = {
pool,
query,
transaction,
testConnection,
closePool
closePool,
adminDB
};

View File

@@ -0,0 +1,225 @@
// 管理员控制器
const Admin = require('../../models/admin');
const AdminService = require('../../services/admin');
const jwt = require('jsonwebtoken');
const { query } = require('../../config/database');
// 生成JWT token
const generateToken = (admin) => {
return jwt.sign(
{ id: admin.id, username: admin.username, role: admin.role },
process.env.JWT_SECRET || 'admin-secret-key',
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
);
};
// 管理员登录
exports.login = async (req, res, next) => {
try {
const { username, password } = req.body;
// 验证输入
if (!username || !password) {
return res.status(400).json({
success: false,
code: 400,
message: '用户名和密码不能为空'
});
}
// 查找管理员
const admin = await Admin.findByUsername(username);
if (!admin) {
return res.status(401).json({
success: false,
code: 401,
message: '用户名或密码错误'
});
}
// 验证密码
const isPasswordValid = await admin.verifyPassword(password);
if (!isPasswordValid) {
return res.status(401).json({
success: false,
code: 401,
message: '用户名或密码错误'
});
}
// 更新最后登录时间
await admin.updateLastLogin();
// 生成token
const token = generateToken(admin);
res.status(200).json({
success: true,
code: 200,
message: '登录成功',
data: {
admin: admin.toSafeObject(),
token
}
});
} catch (error) {
next(error);
}
};
// 获取当前管理员信息
exports.getProfile = async (req, res, next) => {
try {
const admin = await Admin.findById(req.admin.id);
if (!admin) {
return res.status(404).json({
success: false,
code: 404,
message: '管理员不存在'
});
}
res.status(200).json({
success: true,
code: 200,
message: '获取成功',
data: {
admin: admin.toSafeObject()
}
});
} catch (error) {
next(error);
}
};
// 获取管理员列表
exports.getList = async (req, res, next) => {
try {
const result = await AdminService.getAdminList(req.query);
res.status(200).json({
success: true,
code: 200,
message: '获取成功',
data: result
});
} catch (error) {
next(error);
}
};
// 创建管理员
exports.create = async (req, res, next) => {
try {
const { username, password, email, nickname, role } = req.body;
// 验证必填字段
if (!username || !password) {
return res.status(400).json({
success: false,
code: 400,
message: '用户名和密码不能为空'
});
}
// 创建管理员
const adminData = {
username,
password,
email: email || null,
nickname: nickname || null,
role: role || 'admin',
status: 1
};
const admin = await AdminService.createAdmin(adminData);
res.status(201).json({
success: true,
code: 201,
message: '创建成功',
data: {
admin
}
});
} catch (error) {
if (error.message === '用户名已存在') {
return res.status(409).json({
success: false,
code: 409,
message: '用户名已存在'
});
}
next(error);
}
};
// 更新管理员
exports.update = async (req, res, next) => {
try {
const { id } = req.params;
const updateData = req.body;
// 不能修改自己角色
if (req.admin.id == id && updateData.role) {
return res.status(400).json({
success: false,
code: 400,
message: '不能修改自己的角色'
});
}
const admin = await AdminService.updateAdmin(id, updateData);
res.status(200).json({
success: true,
code: 200,
message: '更新成功',
data: {
admin
}
});
} catch (error) {
if (error.message === '管理员不存在') {
return res.status(404).json({
success: false,
code: 404,
message: '管理员不存在'
});
}
next(error);
}
};
// 删除管理员
exports.delete = async (req, res, next) => {
try {
const { id } = req.params;
// 不能删除自己
if (req.admin.id == id) {
return res.status(400).json({
success: false,
code: 400,
message: '不能删除自己'
});
}
await AdminService.deleteAdmin(id);
res.status(200).json({
success: true,
code: 200,
message: '删除成功'
});
} catch (error) {
if (error.message === '管理员不存在') {
return res.status(404).json({
success: false,
code: 404,
message: '管理员不存在'
});
}
next(error);
}
};

View File

@@ -0,0 +1,166 @@
const AnimalService = require('../../services/animal');
const { success } = require('../../utils/response');
const { AppError } = require('../../utils/errors');
class AnimalController {
// 获取动物列表
static async getAnimals(req, res, next) {
try {
const { page, pageSize, species, status } = req.query;
const result = await AnimalService.getAnimals({
merchantId: req.userId,
page: parseInt(page) || 1,
pageSize: parseInt(pageSize) || 10,
species,
status
});
res.json(success(result));
} catch (error) {
next(error);
}
}
// 获取单个动物详情
static async getAnimal(req, res, next) {
try {
const { animalId } = req.params;
if (!animalId) {
throw new AppError('动物ID不能为空', 400);
}
const animal = await AnimalService.getAnimalById(animalId);
res.json(success({ animal }));
} catch (error) {
next(error);
}
}
// 创建动物
static async createAnimal(req, res, next) {
try {
const {
name,
species,
breed,
age,
gender,
price,
description,
images,
health_status,
vaccination_status
} = req.body;
// 验证必要字段
if (!name || !species || !price) {
throw new AppError('缺少必要字段: name, species, price', 400);
}
const animalData = {
merchant_id: req.userId,
name,
species,
breed: breed || null,
age: age || null,
gender: gender || null,
price: parseFloat(price),
description: description || null,
images: images || null,
health_status: health_status || null,
vaccination_status: vaccination_status || null,
status: 'available'
};
const animal = await AnimalService.createAnimal(animalData);
res.status(201).json(success({ animal }));
} catch (error) {
next(error);
}
}
// 更新动物信息
static async updateAnimal(req, res, next) {
try {
const { animalId } = req.params;
const updateData = req.body;
if (!animalId) {
throw new AppError('动物ID不能为空', 400);
}
const animal = await AnimalService.updateAnimal(animalId, updateData);
res.json(success({ animal }));
} catch (error) {
next(error);
}
}
// 删除动物
static async deleteAnimal(req, res, next) {
try {
const { animalId } = req.params;
if (!animalId) {
throw new AppError('动物ID不能为空', 400);
}
await AnimalService.deleteAnimal(animalId);
res.json(success({ message: '动物删除成功' }));
} catch (error) {
next(error);
}
}
// 获取动物统计信息
static async getAnimalStatistics(req, res, next) {
try {
const statistics = await AnimalService.getAnimalStatistics();
res.json(success({ statistics }));
} catch (error) {
next(error);
}
}
// 搜索动物
static async searchAnimals(req, res, next) {
try {
const { keyword, species, minPrice, maxPrice, page, pageSize } = req.query;
const result = await AnimalService.searchAnimals({
keyword,
species,
minPrice: minPrice ? parseFloat(minPrice) : null,
maxPrice: maxPrice ? parseFloat(maxPrice) : null,
page: parseInt(page) || 1,
pageSize: parseInt(pageSize) || 10
});
res.json(success(result));
} catch (error) {
next(error);
}
}
// 获取所有动物(管理员)
static async getAllAnimals(req, res, next) {
try {
const { page, pageSize, species, status } = req.query;
const result = await AnimalService.getAnimals({
page: parseInt(page) || 1,
pageSize: parseInt(pageSize) || 10,
species,
status
});
res.json(success(result));
} catch (error) {
next(error);
}
}
}
module.exports = AnimalController;

View File

@@ -39,8 +39,10 @@ const register = async (req, res, next) => {
// 创建新用户
const userId = await UserMySQL.create({
username,
password: hashedPassword,
nickname: nickname || username,
password_hash: hashedPassword,
user_type: 'farmer',
real_name: nickname || username,
avatar_url: '',
email,
phone
});
@@ -92,7 +94,7 @@ const login = async (req, res, next) => {
}
// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password);
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
if (!isPasswordValid) {
throw new AppError('密码错误', 401);
}
@@ -250,11 +252,66 @@ const wechatLogin = async (req, res, next) => {
}
};
// 管理员登录
const adminLogin = async (req, res, next) => {
try {
const { username, password } = req.body;
if (!username || !password) {
throw new AppError('用户名和密码不能为空', 400);
}
// 查找用户(支持用户名、邮箱、手机号登录)
let user = await UserMySQL.findByUsername(username);
if (!user) {
user = await UserMySQL.findByEmail(username);
}
if (!user) {
user = await UserMySQL.findByPhone(username);
}
if (!user) {
throw new AppError('用户不存在', 404);
}
// 检查用户状态
if (!UserMySQL.isActive(user)) {
throw new AppError('账户已被禁用', 403);
}
// 检查用户是否为管理员假设level >= 2为管理员
if (user.level < 2) {
throw new AppError('权限不足,需要管理员权限', 403);
}
// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
if (!isPasswordValid) {
throw new AppError('密码错误', 401);
}
// 生成token
const token = generateToken(user.id);
// 更新最后登录时间
await UserMySQL.updateLastLogin(user.id);
res.json(success({
user: UserMySQL.sanitize(user),
token,
message: '管理员登录成功'
}));
} catch (error) {
next(error);
}
};
module.exports = {
register,
login,
getCurrentUser,
updateProfile,
changePassword,
wechatLogin
wechatLogin,
adminLogin
};

View File

@@ -0,0 +1,402 @@
const OrderService = require('../../services/order');
/**
* 创建订单
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function createOrder(req, res, next) {
try {
const orderData = req.body;
const userId = req.user.id;
// 验证必要字段
if (!orderData.animal_id || !orderData.merchant_id || !orderData.total_amount) {
return res.status(400).json({
success: false,
message: '缺少必要字段: animal_id, merchant_id, total_amount'
});
}
const order = await OrderService.createOrder(orderData, userId);
res.status(201).json({
success: true,
data: {
order,
message: '订单创建成功'
}
});
} catch (error) {
console.error('创建订单控制器错误:', error);
next(error);
}
}
/**
* 获取订单详情
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function getOrder(req, res, next) {
try {
const { orderId } = req.params;
const userId = req.user.id;
const order = await OrderService.getOrderById(orderId);
// 检查权限:用户只能查看自己的订单,商家只能查看自己店铺的订单
if (req.user.role === 'user' && order.user_id !== userId) {
return res.status(403).json({
success: false,
message: '无权访问此订单'
});
}
if (req.user.role === 'merchant' && order.merchant_id !== req.user.merchant_id) {
return res.status(403).json({
success: false,
message: '无权访问此订单'
});
}
res.json({
success: true,
data: order
});
} catch (error) {
console.error('获取订单详情控制器错误:', error);
if (error.message === '订单不存在') {
return res.status(404).json({
success: false,
message: '订单不存在'
});
}
res.status(500).json({
success: false,
message: error.message || '获取订单详情失败'
});
}
}
/**
* 获取用户订单列表
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function getUserOrders(req, res, next) {
try {
const userId = req.user.id;
const filters = {
page: req.query.page,
limit: req.query.limit,
status: req.query.status
};
const result = await OrderService.getUserOrders(userId, filters);
res.json({
success: true,
data: result.orders,
pagination: result.pagination
});
} catch (error) {
console.error('获取用户订单列表控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '获取订单列表失败'
});
}
}
/**
* 获取商家订单列表
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function getMerchantOrders(req, res, next) {
try {
const merchantId = req.user.merchant_id;
const filters = {
page: req.query.page,
limit: req.query.limit,
status: req.query.status
};
const result = await OrderService.getMerchantOrders(merchantId, filters);
res.json({
success: true,
data: result.orders,
pagination: result.pagination
});
} catch (error) {
console.error('获取商家订单列表控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '获取订单列表失败'
});
}
}
/**
* 取消订单
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function cancelOrder(req, res, next) {
try {
const { orderId } = req.params;
const userId = req.user.id;
const order = await OrderService.cancelOrder(orderId, userId);
res.json({
success: true,
message: '订单取消成功',
data: order
});
} catch (error) {
console.error('取消订单控制器错误:', error);
if (error.message === '订单不存在') {
return res.status(404).json({
success: false,
message: '订单不存在'
});
}
if (error.message === '无权操作此订单') {
return res.status(403).json({
success: false,
message: '无权操作此订单'
});
}
if (error.message === '订单状态不允许取消') {
return res.status(400).json({
success: false,
message: '订单状态不允许取消'
});
}
res.status(500).json({
success: false,
message: error.message || '取消订单失败'
});
}
}
/**
* 支付订单
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function payOrder(req, res, next) {
try {
const { orderId } = req.params;
const userId = req.user.id;
const paymentData = req.body;
// 验证必要字段
if (!paymentData.payment_method) {
return res.status(400).json({
success: false,
message: '缺少必要字段: payment_method'
});
}
const order = await OrderService.payOrder(orderId, userId, paymentData);
res.json({
success: true,
message: '订单支付成功',
data: order
});
} catch (error) {
console.error('支付订单控制器错误:', error);
if (error.message === '订单不存在') {
return res.status(404).json({
success: false,
message: '订单不存在'
});
}
if (error.message === '无权操作此订单') {
return res.status(403).json({
success: false,
message: '无权操作此订单'
});
}
if (error.message === '订单状态不允许支付') {
return res.status(400).json({
success: false,
message: '订单状态不允许支付'
});
}
res.status(500).json({
success: false,
message: error.message || '支付订单失败'
});
}
}
/**
* 获取订单统计信息
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function getOrderStatistics(req, res, next) {
try {
const userId = req.user.id;
const statistics = await OrderService.getOrderStatistics(userId);
res.json({
success: true,
data: statistics
});
} catch (error) {
console.error('获取订单统计信息控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '获取统计信息失败'
});
}
}
/**
* 获取所有订单(管理员)
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function getAllOrders(req, res, next) {
try {
const filters = {
page: req.query.page,
limit: req.query.limit,
status: req.query.status
};
const result = await OrderService.getAllOrders(filters);
res.json({
success: true,
data: result.orders,
pagination: result.pagination
});
} catch (error) {
console.error('获取所有订单控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '获取所有订单失败'
});
}
}
/**
* 更新订单状态
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function updateOrderStatus(req, res, next) {
try {
const { orderId } = req.params;
const { status } = req.body;
const userId = req.user.id;
const order = await OrderService.updateOrderStatus(orderId, status, userId);
res.json({
success: true,
message: '订单状态更新成功',
data: order
});
} catch (error) {
console.error('更新订单状态控制器错误:', error);
if (error.message === '订单不存在') {
return res.status(404).json({
success: false,
message: '订单不存在'
});
}
if (error.message === '无权操作此订单') {
return res.status(403).json({
success: false,
message: '无权操作此订单'
});
}
res.status(500).json({
success: false,
message: error.message || '更新订单状态失败'
});
}
}
/**
* 删除订单
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function deleteOrder(req, res, next) {
try {
const { orderId } = req.params;
const userId = req.user.id;
await OrderService.deleteOrder(orderId, userId);
res.json({
success: true,
message: '订单删除成功'
});
} catch (error) {
console.error('删除订单控制器错误:', error);
if (error.message === '订单不存在') {
return res.status(404).json({
success: false,
message: '订单不存在'
});
}
if (error.message === '无权操作此订单') {
return res.status(403).json({
success: false,
message: '无权操作此订单'
});
}
res.status(500).json({
success: false,
message: error.message || '删除订单失败'
});
}
}
/**
* 获取商家统计信息
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function getMerchantStats(req, res, next) {
try {
const merchantId = req.user.merchant_id;
const stats = await OrderService.getMerchantStats(merchantId);
res.json({
success: true,
data: stats
});
} catch (error) {
console.error('获取商家统计信息控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '获取统计信息失败'
});
}
}
module.exports = {
createOrder,
getOrder,
getUserOrders,
getMerchantOrders,
cancelOrder,
payOrder,
getOrderStatistics,
getAllOrders,
updateOrderStatus,
deleteOrder,
getMerchantStats
};

View File

@@ -0,0 +1,152 @@
const TravelService = require('../../services/travel');
const { success } = require('../../utils/response');
const { AppError } = require('../../utils/errors');
class TravelController {
// 获取旅行计划列表
static async getTravelPlans(req, res, next) {
try {
const { page, pageSize, status } = req.query;
const result = await TravelService.getTravelPlans({
userId: req.userId,
page: parseInt(page) || 1,
pageSize: parseInt(pageSize) || 10,
status
});
res.json(success(result));
} catch (error) {
next(error);
}
}
// 获取单个旅行计划详情
static async getTravelPlan(req, res, next) {
try {
const { planId } = req.params;
if (!planId) {
throw new AppError('旅行计划ID不能为空', 400);
}
const plan = await TravelService.getTravelPlanById(planId);
res.json(success({ plan }));
} catch (error) {
next(error);
}
}
// 创建旅行计划
static async createTravelPlan(req, res, next) {
try {
const {
destination,
start_date,
end_date,
budget,
companions,
transportation,
accommodation,
activities,
notes
} = req.body;
if (!destination || !start_date || !end_date) {
throw new AppError('目的地、开始日期和结束日期不能为空', 400);
}
const planId = await TravelService.createTravelPlan(req.userId, {
destination,
start_date,
end_date,
budget,
companions,
transportation,
accommodation,
activities,
notes
});
const plan = await TravelService.getTravelPlanById(planId);
res.status(201).json(success({
plan,
message: '旅行计划创建成功'
}));
} catch (error) {
next(error);
}
}
// 更新旅行计划
static async updateTravelPlan(req, res, next) {
try {
const { planId } = req.params;
if (!planId) {
throw new AppError('旅行计划ID不能为空', 400);
}
const plan = await TravelService.updateTravelPlan(planId, req.userId, req.body);
res.json(success({
plan,
message: '旅行计划更新成功'
}));
} catch (error) {
next(error);
}
}
// 删除旅行计划
static async deleteTravelPlan(req, res, next) {
try {
const { planId } = req.params;
if (!planId) {
throw new AppError('旅行计划ID不能为空', 400);
}
await TravelService.deleteTravelPlan(planId, req.userId);
res.json(success({
message: '旅行计划删除成功',
planId: parseInt(planId)
}));
} catch (error) {
next(error);
}
}
// 获取用户旅行统计
static async getTravelStats(req, res, next) {
try {
const stats = await TravelService.getUserTravelStats(req.userId);
res.json(success({ stats }));
} catch (error) {
next(error);
}
}
// 获取所有旅行计划(管理员功能)
static async getAllTravelPlans(req, res, next) {
try {
const { page, pageSize, status, userId } = req.query;
const result = await TravelService.getTravelPlans({
userId,
page: parseInt(page) || 1,
pageSize: parseInt(pageSize) || 10,
status
});
res.json(success(result));
} catch (error) {
next(error);
}
}
}
module.exports = TravelController;

View File

@@ -0,0 +1,129 @@
const UserService = require('../../services/user');
const { success } = require('../../utils/response');
const { AppError } = require('../../utils/errors');
class UserController {
// 获取用户详情
static async getUserProfile(req, res, next) {
try {
const user = await UserService.getUserProfile(req.userId);
res.json(success({ user }));
} catch (error) {
next(error);
}
}
// 更新用户信息
static async updateProfile(req, res, next) {
try {
const user = await UserService.updateUserProfile(req.userId, req.body);
res.json(success({
user,
message: '个人信息更新成功'
}));
} catch (error) {
next(error);
}
}
// 搜索用户(管理员功能)
static async searchUsers(req, res, next) {
try {
const result = await UserService.searchUsers(req.query);
res.json(success(result));
} catch (error) {
next(error);
}
}
// 获取用户统计信息(管理员功能)
static async getUserStatistics(req, res, next) {
try {
const stats = await UserService.getUserStatistics();
res.json(success({ statistics: stats }));
} catch (error) {
next(error);
}
}
// 批量操作用户状态(管理员功能)
static async batchUpdateUserStatus(req, res, next) {
try {
const { userIds, status } = req.body;
if (!userIds || !Array.isArray(userIds) || userIds.length === 0) {
throw new AppError('请选择要操作的用户', 400);
}
if (!status || !['active', 'inactive'].includes(status)) {
throw new AppError('无效的状态值', 400);
}
const affectedRows = await UserService.batchUpdateUserStatus(userIds, status);
res.json(success({
message: `成功更新 ${affectedRows} 个用户状态`,
affectedRows
}));
} catch (error) {
next(error);
}
}
// 获取用户列表(管理员功能)
static async getUsers(req, res, next) {
try {
const { page = 1, pageSize = 10, userType, status } = req.query;
const result = await UserService.searchUsers({
page: parseInt(page),
pageSize: parseInt(pageSize),
userType,
status,
keyword: req.query.keyword
});
res.json(success(result));
} catch (error) {
next(error);
}
}
// 获取单个用户详情(管理员功能)
static async getUserById(req, res, next) {
try {
const { userId } = req.params;
if (!userId) {
throw new AppError('用户ID不能为空', 400);
}
const user = await UserService.getUserProfile(userId);
res.json(success({ user }));
} catch (error) {
next(error);
}
}
// 删除用户(管理员功能)
static async deleteUser(req, res, next) {
try {
const { userId } = req.params;
if (!userId) {
throw new AppError('用户ID不能为空', 400);
}
// 这里需要实现软删除逻辑
// 暂时先返回成功消息
res.json(success({
message: '用户删除成功',
userId
}));
} catch (error) {
next(error);
}
}
}
module.exports = UserController;

View File

@@ -149,6 +149,60 @@ const options = {
updated_at: {
type: 'string',
format: 'date-time'
},
last_login_at: {
type: 'string',
format: 'date-time',
description: '最后登录时间'
}
}
},
// 管理员模型
Admin: {
type: 'object',
properties: {
id: {
type: 'integer',
description: '管理员ID'
},
username: {
type: 'string',
description: '用户名'
},
email: {
type: 'string',
description: '邮箱'
},
nickname: {
type: 'string',
description: '昵称'
},
avatar: {
type: 'string',
description: '头像URL'
},
role: {
type: 'string',
description: '角色'
},
status: {
type: 'integer',
description: '状态 (1:启用, 0:禁用)'
},
last_login: {
type: 'string',
format: 'date-time',
description: '最后登录时间'
},
created_at: {
type: 'string',
format: 'date-time',
description: '创建时间'
},
updated_at: {
type: 'string',
format: 'date-time',
description: '更新时间'
}
}
},

View File

@@ -1,108 +1,99 @@
const jwt = require('jsonwebtoken')
const { User } = require('../models/User')
const { AppError } = require('../utils/errors')
const jwt = require('jsonwebtoken');
const Admin = require('../models/admin');
// JWT认证中间件
const authenticate = async (req, res, next) => {
// 用户认证中间件
function authenticateUser(req, res, next) {
// TODO: 实现用户认证逻辑
next();
}
// 管理员认证中间件
async function authenticateAdmin(req, res, next) {
try {
let token
// 从Authorization头获取token
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1]
// 从请求头获取token
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
code: 401,
message: '未提供认证token'
});
}
if (!token) {
return next(new AppError('访问被拒绝请提供有效的token', 401))
}
const token = authHeader.split(' ')[1];
// 验证token
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key')
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'admin-secret-key');
// 查找用户
const user = await User.findById(decoded.userId)
if (!user) {
return next(new AppError('用户不存在', 404))
// 查找管理员
const admin = await Admin.findById(decoded.id);
if (!admin) {
return res.status(401).json({
success: false,
code: 401,
message: '管理员不存在'
});
}
// 检查用户状态
if (!user.isActive()) {
return next(new AppError('账户已被禁用', 403))
// 检查管理员状态
if (admin.status !== 1) {
return res.status(401).json({
success: false,
code: 401,
message: '管理员账号已被禁用'
});
}
// 将用户信息添加到请求对象
req.userId = user._id
req.user = user
// 将管理员信息添加到请求对象
req.admin = admin;
next()
next();
} catch (error) {
if (error.name === 'JsonWebTokenError') {
return next(new AppError('无效的token', 401))
return res.status(401).json({
success: false,
code: 401,
message: '无效的认证token'
});
}
if (error.name === 'TokenExpiredError') {
return next(new AppError('token已过期', 401))
return res.status(401).json({
success: false,
code: 401,
message: '认证token已过期'
});
}
next(error)
next(error);
}
}
// 可选认证中间件(不强制要求认证)
const optionalAuthenticate = async (req, res, next) => {
try {
let token
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1]
// 权限检查中间件
function requireRole(roles) {
return (req, res, next) => {
if (!req.admin) {
return res.status(401).json({
success: false,
code: 401,
message: '需要管理员权限'
});
}
if (token) {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key')
const user = await User.findById(decoded.userId)
if (user && user.isActive()) {
req.userId = user._id
req.user = user
}
if (!roles.includes(req.admin.role)) {
return res.status(403).json({
success: false,
code: 403,
message: '权限不足'
});
}
next()
} catch (error) {
// 忽略token验证错误继续处理请求
next()
}
}
// 管理员权限检查
const requireAdmin = (req, res, next) => {
if (!req.user) {
return next(new AppError('请先登录', 401))
}
// 这里可以根据实际需求定义管理员权限
// 例如:检查用户角色或权限级别
if (req.user.level < 2) { // 假设2级以上为管理员
return next(new AppError('权限不足,需要管理员权限', 403))
}
next()
}
// VIP权限检查
const requireVip = (req, res, next) => {
if (!req.user) {
return next(new AppError('请先登录', 401))
}
if (!req.user.isVip()) {
return next(new AppError('需要VIP权限', 403))
}
next()
next();
};
}
module.exports = {
authenticate,
optionalAuthenticate,
requireAdmin,
requireVip
}
authenticateUser,
authenticateAdmin,
requireRole
};

View File

@@ -4,30 +4,30 @@ class UserMySQL {
// 创建用户
static async create(userData) {
const {
openid,
nickname,
avatar = '',
gender = 'other',
birthday = null,
phone = null,
email = null
username,
password_hash,
user_type = 'farmer',
real_name = '',
avatar_url = '',
email = null,
phone = null
} = userData;
const sql = `
INSERT INTO users (
openid, nickname, avatar, gender, birthday, phone, email,
username, password_hash, user_type, real_name, avatar_url, email, phone,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
`;
const params = [
openid,
nickname,
avatar,
gender,
birthday,
phone,
email
username,
password_hash,
user_type,
real_name,
avatar_url,
email,
phone
];
const result = await query(sql, params);
@@ -41,10 +41,10 @@ class UserMySQL {
return rows[0] || null;
}
// 根据openid查找用户
static async findByOpenid(openid) {
const sql = 'SELECT * FROM users WHERE openid = ?';
const rows = await query(sql, [openid]);
// 根据用户名查找用户
static async findByUsername(username) {
const sql = 'SELECT * FROM users WHERE username = ?';
const rows = await query(sql, [username]);
return rows[0] || null;
}
@@ -101,10 +101,10 @@ class UserMySQL {
return result.affectedRows > 0;
}
// 检查openid是否已存在
static async isOpenidExists(openid, excludeId = null) {
let sql = 'SELECT COUNT(*) as count FROM users WHERE openid = ?';
const params = [openid];
// 检查用户名是否已存在
static async isUsernameExists(username, excludeId = null) {
let sql = 'SELECT COUNT(*) as count FROM users WHERE username = ?';
const params = [username];
if (excludeId) {
sql += ' AND id != ?';
@@ -115,6 +115,17 @@ class UserMySQL {
return rows[0].count > 0;
}
// 检查用户状态是否活跃
static isActive(user) {
return user.status === 'active';
}
// 执行原始查询(用于复杂查询)
static async query(sql, params = []) {
const { query } = require('../config/database');
return await query(sql, params);
}
// 检查邮箱是否已存在
static async isEmailExists(email, excludeId = null) {
let sql = 'SELECT COUNT(*) as count FROM users WHERE email = ?';
@@ -143,9 +154,18 @@ class UserMySQL {
return rows[0].count > 0;
}
// 检查用户名是否已存在 (根据openid检查)
// 检查用户名是否已存在
static async isUsernameExists(username, excludeId = null) {
return await this.isOpenidExists(username, excludeId);
let sql = 'SELECT COUNT(*) as count FROM users WHERE username = ?';
const params = [username];
if (excludeId) {
sql += ' AND id != ?';
params.push(excludeId);
}
const rows = await query(sql, params);
return rows[0].count > 0;
}
// 安全返回用户信息(去除敏感信息)

View File

@@ -0,0 +1,88 @@
// 管理员模型
const { adminDB } = require('../config/database');
const bcrypt = require('bcryptjs');
class Admin {
constructor(data) {
this.id = data.id;
this.username = data.username;
this.password = data.password;
this.email = data.email;
this.nickname = data.nickname;
this.avatar = data.avatar;
this.role = data.role;
this.status = data.status;
this.last_login = data.last_login;
this.created_at = data.created_at;
this.updated_at = data.updated_at;
}
// 根据用户名查找管理员
static async findByUsername(username) {
const adminData = await adminDB.findByUsername(username);
return adminData ? new Admin(adminData) : null;
}
// 根据ID查找管理员
static async findById(id) {
const adminData = await adminDB.findById(id);
return adminData ? new Admin(adminData) : null;
}
// 验证密码
async verifyPassword(password) {
return await bcrypt.compare(password, this.password);
}
// 更新最后登录时间
async updateLastLogin() {
await adminDB.updateLastLogin(this.id);
}
// 创建管理员
static async create(adminData) {
// 密码加密
if (adminData.password) {
adminData.password = await bcrypt.hash(adminData.password, 10);
}
const id = await adminDB.create(adminData);
return await this.findById(id);
}
// 更新管理员信息
async update(updateData) {
// 如果有密码更新,需要加密
if (updateData.password) {
updateData.password = await bcrypt.hash(updateData.password, 10);
}
await adminDB.update(this.id, updateData);
// 更新实例数据
Object.assign(this, updateData);
}
// 删除管理员
async delete() {
await adminDB.delete(this.id);
}
// 转换为安全对象(不包含密码等敏感信息)
toSafeObject() {
return {
id: this.id,
username: this.username,
email: this.email,
nickname: this.nickname,
avatar: this.avatar,
role: this.role,
status: this.status,
last_login: this.last_login,
created_at: this.created_at,
updated_at: this.updated_at
};
}
}
module.exports = Admin;

327
backend/src/routes/admin.js Normal file
View File

@@ -0,0 +1,327 @@
// 管理员路由
const express = require('express');
const router = express.Router();
const adminController = require('../controllers/admin');
const { authenticateAdmin } = require('../middleware/auth');
/**
* @swagger
* tags:
* name: Admin
* description: 管理员相关接口
*/
/**
* @swagger
* /admin/login:
* post:
* summary: 管理员登录
* tags: [Admin]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - password
* properties:
* username:
* type: string
* description: 用户名
* password:
* type: string
* description: 密码
* responses:
* 200:
* description: 登录成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* code:
* type: integer
* message:
* type: string
* data:
* type: object
* properties:
* admin:
* $ref: '#/components/schemas/Admin'
* token:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 用户名或密码错误
*/
router.post('/login', adminController.login);
// 需要认证的接口
router.use(authenticateAdmin);
/**
* @swagger
* /admin/profile:
* get:
* summary: 获取当前管理员信息
* tags: [Admin]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* code:
* type: integer
* message:
* type: string
* data:
* type: object
* properties:
* admin:
* $ref: '#/components/schemas/Admin'
* 401:
* description: 未授权
* 404:
* description: 管理员不存在
*/
router.get('/profile', adminController.getProfile);
/**
* @swagger
* /admin:
* get:
* summary: 获取管理员列表
* tags: [Admin]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* description: 页码
* - in: query
* name: pageSize
* schema:
* type: integer
* minimum: 1
* maximum: 100
* description: 每页数量
* - in: query
* name: username
* schema:
* type: string
* description: 用户名搜索
* - in: query
* name: role
* schema:
* type: string
* description: 角色筛选
* - in: query
* name: status
* schema:
* type: integer
* description: 状态筛选
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* code:
* type: integer
* message:
* type: string
* data:
* type: object
* properties:
* admins:
* type: array
* items:
* $ref: '#/components/schemas/Admin'
* pagination:
* $ref: '#/components/schemas/Pagination'
* 401:
* description: 未授权
*/
router.get('/', adminController.getList);
/**
* @swagger
* /admin:
* post:
* summary: 创建管理员
* tags: [Admin]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - password
* properties:
* username:
* type: string
* description: 用户名
* password:
* type: string
* description: 密码
* email:
* type: string
* description: 邮箱
* nickname:
* type: string
* description: 昵称
* role:
* type: string
* description: 角色
* responses:
* 201:
* description: 创建成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* code:
* type: integer
* message:
* type: string
* data:
* type: object
* properties:
* admin:
* $ref: '#/components/schemas/Admin'
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 409:
* description: 用户名已存在
*/
router.post('/', adminController.create);
/**
* @swagger
* /admin/{id}:
* put:
* summary: 更新管理员
* tags: [Admin]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 管理员ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* description: 邮箱
* nickname:
* type: string
* description: 昵称
* role:
* type: string
* description: 角色
* status:
* type: integer
* description: 状态
* responses:
* 200:
* description: 更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* code:
* type: integer
* message:
* type: string
* data:
* type: object
* properties:
* admin:
* $ref: '#/components/schemas/Admin'
* 400:
* description: 不能修改自己的角色
* 401:
* description: 未授权
* 404:
* description: 管理员不存在
*/
router.put('/:id', adminController.update);
/**
* @swagger
* /admin/{id}:
* delete:
* summary: 删除管理员
* tags: [Admin]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 管理员ID
* responses:
* 200:
* description: 删除成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* code:
* type: integer
* message:
* type: string
* 400:
* description: 不能删除自己
* 401:
* description: 未授权
* 404:
* description: 管理员不存在
*/
router.delete('/:id', adminController.delete);
module.exports = router;

View File

@@ -0,0 +1,533 @@
const express = require('express');
const { body, query } = require('express-validator');
const AnimalController = require('../controllers/animal');
const { authenticateUser: authenticate, requireRole: requireAdmin, requireRole: requireMerchant } = require('../middleware/auth');
const router = express.Router();
/**
* @swagger
* tags:
* name: Animals
* description: 动物管理相关接口
*/
/**
* @swagger
* /animals/search:
* get:
* summary: 搜索动物(公开接口)
* tags: [Animals]
* parameters:
* - in: query
* name: keyword
* schema:
* type: string
* description: 搜索关键词
* - in: query
* name: species
* schema:
* type: string
* description: 物种
* - in: query
* name: minPrice
* schema:
* type: number
* description: 最低价格
* - in: query
* name: maxPrice
* schema:
* type: number
* description: 最高价格
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* description: 页码
* - in: query
* name: pageSize
* schema:
* type: integer
* minimum: 1
* maximum: 100
* description: 每页数量
* responses:
* 200:
* description: 搜索成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* animals:
* type: array
* items:
* $ref: '#/components/schemas/Animal'
* pagination:
* $ref: '#/components/schemas/Pagination'
* 500:
* description: 服务器内部错误
*/
router.get('/search', AnimalController.searchAnimals);
/**
* @swagger
* /animals:
* get:
* summary: 获取动物列表(商家)
* tags: [Animals]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* description: 页码
* - in: query
* name: pageSize
* schema:
* type: integer
* minimum: 1
* maximum: 100
* description: 每页数量
* - in: query
* name: species
* schema:
* type: string
* description: 物种
* - in: query
* name: status
* schema:
* type: string
* description: 状态
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* animals:
* type: array
* items:
* $ref: '#/components/schemas/Animal'
* pagination:
* $ref: '#/components/schemas/Pagination'
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/', authenticate, requireMerchant, AnimalController.getAnimals);
/**
* @swagger
* /animals/stats:
* get:
* summary: 获取动物统计信息(商家)
* tags: [Animals]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* statistics:
* $ref: '#/components/schemas/AnimalStatistics'
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/stats', authenticate, requireMerchant, AnimalController.getAnimalStatistics);
/**
* @swagger
* /animals/admin/all:
* get:
* summary: 获取所有动物信息(管理员)
* tags: [Animals]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* description: 页码
* - in: query
* name: pageSize
* schema:
* type: integer
* minimum: 1
* maximum: 100
* description: 每页数量
* - in: query
* name: species
* schema:
* type: string
* description: 物种
* - in: query
* name: status
* schema:
* type: string
* description: 状态
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* animals:
* type: array
* items:
* $ref: '#/components/schemas/Animal'
* pagination:
* $ref: '#/components/schemas/Pagination'
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.get('/admin/all', authenticate, requireAdmin, AnimalController.getAllAnimals);
/**
* @swagger
* /animals/statistics:
* get:
* summary: 获取动物统计信息
* tags: [Animals]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* statistics:
* type: object
* properties:
* total:
* type: integer
* bySpecies:
* type: array
* items:
* type: object
* properties:
* species:
* type: string
* count:
* type: integer
* byStatus:
* type: array
* items:
* type: object
* properties:
* status:
* type: string
* count:
* type: integer
* topMerchants:
* type: array
* items:
* type: object
* properties:
* merchant_name:
* type: string
* animal_count:
* type: integer
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/statistics', authenticate, AnimalController.getAnimalStatistics);
/**
* @swagger
* /animals/{animalId}:
* get:
* summary: 获取单个动物详情
* tags: [Animals]
* parameters:
* - in: path
* name: animalId
* required: true
* schema:
* type: integer
* description: 动物ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* animal:
* $ref: '#/components/schemas/AnimalDetail'
* 404:
* description: 动物不存在
* 500:
* description: 服务器内部错误
*/
router.get('/:animalId', AnimalController.getAnimal);
/**
* @swagger
* /animals:
* post:
* summary: 创建动物信息
* tags: [Animals]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - species
* - price
* properties:
* name:
* type: string
* description: 动物名称
* species:
* type: string
* description: 动物种类
* breed:
* type: string
* description: 品种
* age:
* type: integer
* description: 年龄
* gender:
* type: string
* enum: [male, female]
* description: 性别
* price:
* type: number
* description: 价格
* description:
* type: string
* description: 描述
* images:
* type: array
* items:
* type: string
* description: 图片URL列表
* responses:
* 201:
* description: 创建成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* animal:
* $ref: '#/components/schemas/Animal'
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.post('/',
authenticate,
requireMerchant,
[
body('name').notEmpty().withMessage('名称不能为空'),
body('species').notEmpty().withMessage('种类不能为空'),
body('price').isFloat({ min: 0 }).withMessage('价格必须大于0')
],
AnimalController.createAnimal
);
/**
* @swagger
* /animals/{animalId}:
* put:
* summary: 更新动物信息
* tags: [Animals]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: animalId
* required: true
* schema:
* type: integer
* description: 动物ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* description: 动物名称
* species:
* type: string
* description: 动物种类
* breed:
* type: string
* description: 品种
* age:
* type: integer
* description: 年龄
* gender:
* type: string
* enum: [male, female]
* description: 性别
* price:
* type: number
* description: 价格
* description:
* type: string
* description: 描述
* images:
* type: array
* items:
* type: string
* description: 图片URL列表
* status:
* type: string
* enum: [available, sold, reserved]
* description: 状态
* responses:
* 200:
* description: 更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* animal:
* $ref: '#/components/schemas/Animal'
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 404:
* description: 动物不存在
* 500:
* description: 服务器内部错误
*/
router.put('/:animalId',
authenticate,
requireMerchant,
AnimalController.updateAnimal
);
/**
* @swagger
* /animals/{animalId}:
* delete:
* summary: 删除动物信息
* tags: [Animals]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: animalId
* required: true
* schema:
* type: integer
* description: 动物ID
* responses:
* 200:
* description: 删除成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* message:
* type: string
* animalId:
* type: integer
* 401:
* description: 未授权
* 404:
* description: 动物不存在
* 500:
* description: 服务器内部错误
*/
router.delete('/:animalId',
authenticate,
requireMerchant,
AnimalController.deleteAnimal
);
module.exports = router;

View File

@@ -278,6 +278,60 @@ router.put(
authController.changePassword
)
/**
* @swagger
* /auth/admin/login:
* post:
* summary: 管理员登录
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - password
* properties:
* username:
* type: string
* description: 用户名/邮箱/手机号
* password:
* type: string
* description: 密码
* responses:
* 200:
* description: 登录成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* token:
* type: string
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 密码错误
* 403:
* description: 权限不足或账户被禁用
* 404:
* description: 用户不存在
* 500:
* description: 服务器内部错误
*/
router.post('/admin/login', authController.adminLogin);
/**
* @swagger
* /auth/wechat:

View File

@@ -0,0 +1,53 @@
const express = require('express');
const { body } = require('express-validator');
const {
createOrder,
getOrder,
getUserOrders,
getMerchantOrders,
cancelOrder,
payOrder,
getOrderStatistics,
getAllOrders,
updateOrderStatus,
deleteOrder,
getMerchantStats
} = require('../controllers/order/index');
const { authenticateUser: authenticate, requireRole: requireAdmin, requireRole: requireMerchant } = require('../middleware/auth');
const router = express.Router();
// 创建订单
router.post('/', authenticate, createOrder);
// 获取订单详情
router.get('/:orderId', authenticate, getOrder);
// 获取订单列表
router.get('/', authenticate, getUserOrders);
// 商家获取订单列表
router.get('/merchant', authenticate, requireMerchant, getMerchantOrders);
// 取消订单
router.put('/:orderId/cancel', authenticate, cancelOrder);
// 支付订单
router.put('/:orderId/pay', authenticate, payOrder);
// 获取订单统计信息
router.get('/statistics', authenticate, getOrderStatistics);
// 管理员获取所有订单
router.get('/admin', authenticate, requireAdmin, getAllOrders);
// 管理员更新订单状态
router.put('/:orderId/status', authenticate, requireAdmin, updateOrderStatus);
// 管理员删除订单
router.delete('/:orderId', authenticate, requireAdmin, deleteOrder);
// 商家获取统计数据
router.get('/merchant/stats', authenticate, requireMerchant, getMerchantStats);
module.exports = router;

View File

@@ -0,0 +1,434 @@
const express = require('express');
const { body, query } = require('express-validator');
const TravelController = require('../controllers/travel');
const { authenticateUser: authenticate, requireRole: requireAdmin } = require('../middleware/auth');
const router = express.Router();
/**
* @swagger
* tags:
* name: Travel
* description: 旅行计划管理相关接口
*/
/**
* @swagger
* /travel/plans:
* get:
* summary: 获取当前用户的旅行计划列表
* tags: [Travel]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* description: 页码
* - in: query
* name: pageSize
* schema:
* type: integer
* minimum: 1
* maximum: 50
* description: 每页数量
* - in: query
* name: status
* schema:
* type: string
* enum: [planning, in_progress, completed, cancelled]
* description: 计划状态
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* plans:
* type: array
* items:
* $ref: '#/components/schemas/TravelPlan'
* pagination:
* type: object
* properties:
* page:
* type: integer
* pageSize:
* type: integer
* total:
* type: integer
* totalPages:
* type: integer
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/plans', authenticate, TravelController.getTravelPlans);
/**
* @swagger
* /travel/plans/{planId}:
* get:
* summary: 获取单个旅行计划详情
* tags: [Travel]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: planId
* required: true
* schema:
* type: integer
* description: 旅行计划ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* plan:
* $ref: '#/components/schemas/TravelPlan'
* 401:
* description: 未授权
* 404:
* description: 旅行计划不存在
* 500:
* description: 服务器内部错误
*/
router.get('/plans/:planId', authenticate, TravelController.getTravelPlan);
/**
* @swagger
* /travel/plans:
* post:
* summary: 创建旅行计划
* tags: [Travel]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - destination
* - start_date
* - end_date
* properties:
* destination:
* type: string
* description: 目的地
* start_date:
* type: string
* format: date
* description: 开始日期
* end_date:
* type: string
* format: date
* description: 结束日期
* budget:
* type: number
* description: 预算
* companions:
* type: integer
* description: 同行人数
* transportation:
* type: string
* description: 交通方式
* accommodation:
* type: string
* description: 住宿方式
* activities:
* type: string
* description: 活动安排
* notes:
* type: string
* description: 备注
* responses:
* 201:
* description: 创建成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* plan:
* $ref: '#/components/schemas/TravelPlan'
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.post('/plans',
authenticate,
[
body('destination').notEmpty().withMessage('目的地不能为空'),
body('start_date').isDate().withMessage('开始日期格式错误'),
body('end_date').isDate().withMessage('结束日期格式错误')
],
TravelController.createTravelPlan
);
/**
* @swagger
* /travel/plans/{planId}:
* put:
* summary: 更新旅行计划
* tags: [Travel]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: planId
* required: true
* schema:
* type: integer
* description: 旅行计划ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* destination:
* type: string
* description: 目的地
* start_date:
* type: string
* format: date
* description: 开始日期
* end_date:
* type: string
* format: date
* description: 结束日期
* budget:
* type: number
* description: 预算
* companions:
* type: integer
* description: 同行人数
* transportation:
* type: string
* description: 交通方式
* accommodation:
* type: string
* description: 住宿方式
* activities:
* type: string
* description: 活动安排
* notes:
* type: string
* description: 备注
* status:
* type: string
* enum: [planning, in_progress, completed, cancelled]
* description: 计划状态
* responses:
* 200:
* description: 更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* plan:
* $ref: '#/components/schemas/TravelPlan'
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 404:
* description: 旅行计划不存在
* 500:
* description: 服务器内部错误
*/
router.put('/plans/:planId', authenticate, TravelController.updateTravelPlan);
/**
* @swagger
* /travel/plans/{planId}:
* delete:
* summary: 删除旅行计划
* tags: [Travel]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: planId
* required: true
* schema:
* type: integer
* description: 旅行计划ID
* responses:
* 200:
* description: 删除成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* message:
* type: string
* planId:
* type: integer
* 401:
* description: 未授权
* 404:
* description: 旅行计划不存在
* 500:
* description: 服务器内部错误
*/
router.delete('/plans/:planId', authenticate, TravelController.deleteTravelPlan);
/**
* @swagger
* /travel/stats:
* get:
* summary: 获取用户旅行统计
* tags: [Travel]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* stats:
* type: object
* properties:
* total_plans:
* type: integer
* completed_plans:
* type: integer
* planning_plans:
* type: integer
* cancelled_plans:
* type: integer
* total_budget:
* type: number
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/stats', authenticate, TravelController.getTravelStats);
/**
* @swagger
* /travel/admin/plans:
* get:
* summary: 获取所有旅行计划(管理员)
* tags: [Travel]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* description: 页码
* - in: query
* name: pageSize
* schema:
* type: integer
* minimum: 1
* maximum: 50
* description: 每页数量
* - in: query
* name: status
* schema:
* type: string
* enum: [planning, in_progress, completed, cancelled]
* description: 计划状态
* - in: query
* name: userId
* schema:
* type: integer
* description: 用户ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* plans:
* type: array
* items:
* $ref: '#/components/schemas/TravelPlan'
* pagination:
* type: object
* properties:
* page:
* type: integer
* pageSize:
* type: integer
* total:
* type: integer
* totalPages:
* type: integer
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.get('/admin/plans', authenticate, requireAdmin, TravelController.getAllTravelPlans);
module.exports = router;

380
backend/src/routes/user.js Normal file
View File

@@ -0,0 +1,380 @@
const express = require('express');
const { body, query } = require('express-validator');
const UserController = require('../controllers/user');
const { authenticateUser, requireRole: requireAdmin } = require('../middleware/auth');
const router = express.Router();
/**
* @swagger
* tags:
* name: Users
* description: 用户管理相关接口
*/
/**
* @swagger
* /users/profile:
* get:
* summary: 获取当前用户信息
* tags: [Users]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.get('/profile', authenticateUser, UserController.getUserProfile);
/**
* @swagger
* /users/profile:
* put:
* summary: 更新用户个人信息
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* nickname:
* type: string
* description: 昵称
* avatar:
* type: string
* description: 头像URL
* gender:
* type: string
* enum: [male, female, other]
* description: 性别
* birthday:
* type: string
* format: date
* description: 生日
* phone:
* type: string
* description: 手机号
* email:
* type: string
* format: email
* description: 邮箱
* responses:
* 200:
* description: 更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 500:
* description: 服务器内部错误
*/
router.put('/profile', authenticateUser, UserController.updateProfile);
/**
* @swagger
* /users:
* get:
* summary: 获取用户列表(管理员)
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* description: 页码
* - in: query
* name: pageSize
* schema:
* type: integer
* minimum: 1
* maximum: 100
* description: 每页数量
* - in: query
* name: userType
* schema:
* type: string
* enum: [farmer, merchant, admin]
* description: 用户类型
* - in: query
* name: status
* schema:
* type: string
* enum: [active, inactive]
* description: 用户状态
* - in: query
* name: keyword
* schema:
* type: string
* description: 搜索关键词
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* users:
* type: array
* items:
* $ref: '#/components/schemas/User'
* pagination:
* type: object
* properties:
* page:
* type: integer
* pageSize:
* type: integer
* total:
* type: integer
* totalPages:
* type: integer
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
// 已移除重复的GET /users 路由,避免冲突
/**
* @swagger
* /users/{userId}:
* get:
* summary: 获取用户详情(管理员)
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* description: 用户ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 404:
* description: 用户不存在
* 500:
* description: 服务器内部错误
*/
router.get('/:userId', authenticateUser, requireAdmin(['admin', 'super_admin']), UserController.getUserById);
/**
* @swagger
* /users/statistics:
* get:
* summary: 获取用户统计信息(管理员)
* tags: [Users]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* statistics:
* type: array
* items:
* type: object
* properties:
* total_users:
* type: integer
* farmers:
* type: integer
* merchants:
* type: integer
* admins:
* type: integer
* active_users:
* type: integer
* inactive_users:
* type: integer
* date:
* type: string
* format: date
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.get('/statistics', authenticateUser, requireAdmin(['admin', 'super_admin']), UserController.getUserStatistics);
/**
* @swagger
* /users/batch-status:
* post:
* summary: 批量操作用户状态(管理员)
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - userIds
* - status
* properties:
* userIds:
* type: array
* items:
* type: integer
* description: 用户ID列表
* status:
* type: string
* enum: [active, inactive]
* description: 用户状态
* responses:
* 200:
* description: 操作成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* message:
* type: string
* affectedRows:
* type: integer
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.post('/batch-status',
authenticateUser,
requireAdmin(['admin', 'super_admin']),
[
body('userIds').isArray().withMessage('userIds必须是数组'),
body('status').isIn(['active', 'inactive']).withMessage('状态值无效')
],
UserController.batchUpdateUserStatus
);
/**
* @swagger
* /users/{userId}:
* delete:
* summary: 删除用户(管理员)
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* description: 用户ID
* responses:
* 200:
* description: 删除成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* message:
* type: string
* userId:
* type: integer
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 404:
* description: 用户不存在
* 500:
* description: 服务器内部错误
*/
router.delete('/:userId', authenticateUser, requireAdmin(['admin', 'super_admin']), UserController.deleteUser);
module.exports = router;

View File

@@ -4,7 +4,7 @@ const { testConnection } = require('./config/database')
const redisConfig = require('./config/redis')
const rabbitMQConfig = require('./config/rabbitmq')
const PORT = process.env.PORT || 3000
const PORT = process.env.PORT || 3100
const HOST = process.env.HOST || '0.0.0.0'
// 显示启动横幅

View File

@@ -0,0 +1,158 @@
const database = require('../../config/database');
const Admin = require('../../models/admin');
class AdminService {
/**
* 获取管理员列表
* @param {Object} filters - 筛选条件
* @returns {Promise<Object>} 管理员列表和分页信息
*/
async getAdminList(filters = {}) {
try {
const { page = 1, pageSize = 10, username, role, status } = filters;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
// 构建查询条件
let whereClause = 'WHERE 1=1';
const params = [];
if (username) {
whereClause += ' AND username LIKE ?';
params.push(`%${username}%`);
}
if (role) {
whereClause += ' AND role = ?';
params.push(role);
}
if (status !== undefined) {
whereClause += ' AND status = ?';
params.push(status);
}
// 查询总数
const countSql = `SELECT COUNT(*) as total FROM admins ${whereClause}`;
const [countResult] = await database.query(countSql, params);
const total = countResult.total;
// 查询数据
const sql = `
SELECT id, username, email, nickname, avatar, role, status, last_login, created_at, updated_at
FROM admins
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`;
params.push(parseInt(pageSize), offset);
const admins = await database.query(sql, params);
return {
admins,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total,
totalPages: Math.ceil(total / parseInt(pageSize))
}
};
} catch (error) {
console.error('获取管理员列表失败:', error);
throw new Error('获取管理员列表失败');
}
}
/**
* 创建管理员
* @param {Object} adminData - 管理员数据
* @returns {Promise<Object>} 创建的管理员
*/
async createAdmin(adminData) {
try {
// 检查用户名是否已存在
const existingAdmin = await this.getAdminByUsername(adminData.username);
if (existingAdmin) {
throw new Error('用户名已存在');
}
// 创建管理员
const admin = await Admin.create(adminData);
return admin.toSafeObject();
} catch (error) {
console.error('创建管理员失败:', error);
throw error;
}
}
/**
* 根据用户名获取管理员
* @param {string} username - 用户名
* @returns {Promise<Object|null>} 管理员信息
*/
async getAdminByUsername(username) {
try {
return await Admin.findByUsername(username);
} catch (error) {
console.error('获取管理员失败:', error);
throw new Error('获取管理员失败');
}
}
/**
* 根据ID获取管理员
* @param {number} id - 管理员ID
* @returns {Promise<Object|null>} 管理员信息
*/
async getAdminById(id) {
try {
return await Admin.findById(id);
} catch (error) {
console.error('获取管理员失败:', error);
throw new Error('获取管理员失败');
}
}
/**
* 更新管理员信息
* @param {number} id - 管理员ID
* @param {Object} updateData - 更新数据
* @returns {Promise<Object>} 更新后的管理员信息
*/
async updateAdmin(id, updateData) {
try {
const admin = await Admin.findById(id);
if (!admin) {
throw new Error('管理员不存在');
}
await admin.update(updateData);
return admin.toSafeObject();
} catch (error) {
console.error('更新管理员失败:', error);
throw error;
}
}
/**
* 删除管理员
* @param {number} id - 管理员ID
* @returns {Promise<boolean>} 是否删除成功
*/
async deleteAdmin(id) {
try {
const admin = await Admin.findById(id);
if (!admin) {
throw new Error('管理员不存在');
}
await admin.delete();
return true;
} catch (error) {
console.error('删除管理员失败:', error);
throw error;
}
}
}
module.exports = new AdminService();

View File

@@ -0,0 +1,262 @@
const { query } = require('../../config/database');
const { AppError } = require('../../utils/errors');
class AnimalService {
// 获取动物列表
static async getAnimals(searchParams) {
try {
const { merchantId, species, status, page = 1, pageSize = 10 } = searchParams;
const offset = (page - 1) * pageSize;
let sql = `
SELECT a.*, m.business_name as merchant_name, m.merchant_type, u.nickname as username, u.nickname as real_name
FROM animals a
INNER JOIN merchants m ON a.merchant_id = m.id
INNER JOIN users u ON m.user_id = u.id
WHERE 1=1
`;
const params = [];
if (merchantId) {
sql += ' AND a.merchant_id = ?';
params.push(merchantId);
}
if (species) {
sql += ' AND a.species = ?';
params.push(species);
}
if (status) {
sql += ' AND a.status = ?';
params.push(status);
}
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM (${sql}) as count_query`;
const countResult = await query(countSql, params);
const total = countResult[0].total;
// 添加分页和排序
sql += ' ORDER BY a.created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
const animals = await query(sql, params);
return {
animals,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize)
}
};
} catch (error) {
throw error;
}
}
// 获取单个动物详情
static async getAnimalById(animalId) {
try {
const sql = `
SELECT a.*, m.business_name as merchant_name, m.merchant_type, m.contact_person as contact_info, u.nickname as username, u.nickname as real_name, u.avatar
FROM animals a
INNER JOIN merchants m ON a.merchant_id = m.id
INNER JOIN users u ON m.user_id = u.id
WHERE a.id = ?
`;
const params = [animalId];
const result = await query(sql, params);
if (result.length === 0) {
throw new AppError('动物不存在', 404);
}
return result[0];
} catch (error) {
throw error;
}
}
// 创建动物
static async createAnimal(animalData) {
try {
const sql = `
INSERT INTO animals (
merchant_id, name, species, breed, birth_date, personality, farm_location, price, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`;
const params = [
animalData.merchant_id,
animalData.name,
animalData.species,
animalData.breed,
animalData.birth_date,
animalData.personality,
animalData.farm_location,
animalData.price,
animalData.status || 'available'
];
const result = await query(sql, params);
const animalId = result.insertId;
return await this.getAnimalById(animalId);
} catch (error) {
throw error;
}
}
// 更新动物信息
static async updateAnimal(animalId, updateData) {
try {
// 构建动态更新语句
const fields = [];
const params = [];
Object.keys(updateData).forEach(key => {
// 只允许更新特定字段
const allowedFields = ['name', 'species', 'breed', 'birth_date', 'personality', 'farm_location', 'price', 'status'];
if (allowedFields.includes(key)) {
fields.push(`${key} = ?`);
params.push(updateData[key]);
}
});
if (fields.length === 0) {
throw new AppError('没有提供可更新的字段', 400);
}
params.push(animalId); // 为WHERE条件添加ID
const sql = `UPDATE animals SET ${fields.join(', ')} WHERE id = ?`;
await query(sql, params);
return await this.getAnimalById(animalId);
} catch (error) {
throw error;
}
}
// 删除动物
static async deleteAnimal(animalId) {
try {
const sql = 'DELETE FROM animals WHERE id = ?';
const params = [animalId];
await query(sql, params);
} catch (error) {
throw error;
}
}
// 获取动物统计信息
static async getAnimalStatistics() {
try {
// 获取动物总数
const totalSql = 'SELECT COUNT(*) as total FROM animals';
const [totalResult] = await query(totalSql);
const totalAnimals = totalResult.total;
// 按物种统计
const speciesSql = `
SELECT species, COUNT(*) as count
FROM animals
GROUP BY species
ORDER BY count DESC
`;
const speciesStats = await query(speciesSql);
// 按状态统计
const statusSql = `
SELECT status, COUNT(*) as count
FROM animals
GROUP BY status
`;
const statusStats = await query(statusSql);
// 按商家统计前5名
const merchantSql = `
SELECT m.business_name as merchant_name, COUNT(a.id) as animal_count
FROM merchants m
LEFT JOIN animals a ON m.id = a.merchant_id
GROUP BY m.id, m.business_name
ORDER BY animal_count DESC
LIMIT 5
`;
const merchantStats = await query(merchantSql);
return {
total: totalAnimals,
bySpecies: speciesStats,
byStatus: statusStats,
topMerchants: merchantStats
};
} catch (error) {
throw error;
}
}
// 搜索动物
static async searchAnimals(searchParams) {
try {
const { keyword, species, minPrice, maxPrice, page = 1, pageSize = 10 } = searchParams;
const offset = (page - 1) * pageSize;
let sql = `
SELECT a.*, m.business_name as merchant_name, m.merchant_type, u.nickname as username, u.nickname as real_name
FROM animals a
INNER JOIN merchants m ON a.merchant_id = m.id
INNER JOIN users u ON m.user_id = u.id
WHERE a.status = 'available'
`;
const params = [];
if (keyword) {
sql += ' AND (a.name LIKE ? OR a.personality LIKE ? OR a.species LIKE ?)';
params.push(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`);
}
if (species) {
sql += ' AND a.species = ?';
params.push(species);
}
if (minPrice !== null) {
sql += ' AND a.price >= ?';
params.push(minPrice);
}
if (maxPrice !== null) {
sql += ' AND a.price <= ?';
params.push(maxPrice);
}
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM (${sql}) as count_query`;
const countResult = await query(countSql, params);
const total = countResult[0].total;
// 添加分页和排序
sql += ' ORDER BY a.created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
const animals = await query(sql, params);
return {
animals,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize)
}
};
} catch (error) {
throw error;
}
}
}
module.exports = AnimalService;

View File

@@ -0,0 +1,368 @@
const database = require('../../config/database');
class OrderService {
/**
* 创建订单
* @param {Object} orderData - 订单数据
* @param {number} userId - 用户ID
* @returns {Promise<Object>} 创建的订单
*/
async createOrder(orderData, userId) {
try {
const {
animal_id,
merchant_id,
total_amount,
payment_method,
shipping_address,
contact_info
} = orderData;
const query = `
INSERT INTO orders (
user_id, animal_id, merchant_id, total_amount,
payment_method, shipping_address, contact_info, status
) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')
`;
const params = [
userId, animal_id, merchant_id, total_amount,
payment_method, shipping_address, contact_info
];
const result = await database.query(query, params);
// 获取创建的订单详情
const order = await this.getOrderById(result.insertId);
return order;
} catch (error) {
console.error('创建订单失败:', error);
throw new Error('创建订单失败');
}
}
/**
* 根据ID获取订单
* @param {number} orderId - 订单ID
* @returns {Promise<Object>} 订单信息
*/
async getOrderById(orderId) {
try {
const query = `
SELECT
o.*,
a.name as animal_name,
a.species as animal_species,
a.price as animal_price,
u.username as user_name,
m.business_name as merchant_name
FROM orders o
LEFT JOIN animals a ON o.animal_id = a.id
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN merchants m ON o.merchant_id = m.id
WHERE o.id = ? AND o.is_deleted = 0
`;
const [order] = await database.query(query, [orderId]);
if (!order) {
throw new Error('订单不存在');
}
return order;
} catch (error) {
console.error('获取订单失败:', error);
throw error;
}
}
/**
* 获取用户订单列表
* @param {number} userId - 用户ID
* @param {Object} filters - 筛选条件
* @returns {Promise<Array>} 订单列表
*/
async getUserOrders(userId, filters = {}) {
try {
const { page = 1, pageSize = 10, status } = filters;
const offset = (page - 1) * pageSize;
let query = `
SELECT
o.*,
a.name as animal_name,
a.species as animal_species,
a.price as animal_price,
m.business_name as merchant_name
FROM orders o
LEFT JOIN animals a ON o.animal_id = a.id
LEFT JOIN merchants m ON o.merchant_id = m.id
WHERE o.user_id = ? AND o.is_deleted = 0
`;
let countQuery = `
SELECT COUNT(*) as total
FROM orders o
WHERE o.user_id = ? AND o.is_deleted = 0
`;
const params = [userId];
const countParams = [userId];
if (status) {
query += ' AND o.status = ?';
countQuery += ' AND o.status = ?';
params.push(status);
countParams.push(status);
}
query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
const [orders] = await database.query(query, params);
const [totalResult] = await database.query(countQuery, countParams);
return {
orders,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: totalResult.total,
totalPages: Math.ceil(totalResult.total / pageSize)
}
};
} catch (error) {
console.error('获取用户订单失败:', error);
throw new Error('获取用户订单失败');
}
}
/**
* 获取商家订单列表
* @param {number} merchantId - 商家ID
* @param {Object} filters - 筛选条件
* @returns {Promise<Array>} 订单列表
*/
async getMerchantOrders(merchantId, filters = {}) {
try {
const { page = 1, pageSize = 10, status } = filters;
const offset = (page - 1) * pageSize;
let query = `
SELECT
o.*,
a.name as animal_name,
a.species as animal_species,
a.price as animal_price,
u.username as user_name
FROM orders o
LEFT JOIN animals a ON o.animal_id = a.id
LEFT JOIN users u ON o.user_id = u.id
WHERE o.merchant_id = ? AND o.is_deleted = 0
`;
let countQuery = `
SELECT COUNT(*) as total
FROM orders o
WHERE o.merchant_id = ? AND o.is_deleted = 0
`;
const params = [merchantId];
const countParams = [merchantId];
if (status) {
query += ' AND o.status = ?';
countQuery += ' AND o.status = ?';
params.push(status);
countParams.push(status);
}
query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
const [orders] = await database.query(query, params);
const [totalResult] = await database.query(countQuery, countParams);
return {
orders,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: totalResult.total,
totalPages: Math.ceil(totalResult.total / pageSize)
}
};
} catch (error) {
console.error('获取商家订单失败:', error);
throw new Error('获取商家订单失败');
}
}
/**
* 更新订单状态
* @param {number} orderId - 订单ID
* @param {string} status - 新状态
* @param {number} userId - 操作人ID
* @returns {Promise<Object>} 更新后的订单
*/
async updateOrderStatus(orderId, status, userId) {
try {
const validStatuses = ['pending', 'confirmed', 'shipped', 'delivered', 'cancelled'];
if (!validStatuses.includes(status)) {
throw new Error('无效的订单状态');
}
const query = `
UPDATE orders
SET status = ?, updated_by = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND is_deleted = 0
`;
const result = await database.query(query, [status, userId, orderId]);
if (result.affectedRows === 0) {
throw new Error('订单不存在或已被删除');
}
return await this.getOrderById(orderId);
} catch (error) {
console.error('更新订单状态失败:', error);
throw error;
}
}
/**
* 删除订单(软删除)
* @param {number} orderId - 订单ID
* @param {number} userId - 用户ID
* @returns {Promise<boolean>} 是否删除成功
*/
async deleteOrder(orderId, userId) {
try {
const query = `
UPDATE orders
SET is_deleted = 1, deleted_by = ?, deleted_at = CURRENT_TIMESTAMP
WHERE id = ? AND is_deleted = 0
`;
const result = await database.query(query, [userId, orderId]);
return result.affectedRows > 0;
} catch (error) {
console.error('删除订单失败:', error);
throw new Error('删除订单失败');
}
}
/**
* 获取订单统计信息
* @param {number} merchantId - 商家ID
* @returns {Promise<Object>} 统计信息
*/
async getOrderStats(merchantId) {
try {
const query = `
SELECT
COUNT(*) as total_orders,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_orders,
SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_orders,
SUM(CASE WHEN status = 'shipped' THEN 1 ELSE 0 END) as shipped_orders,
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) as delivered_orders,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_orders,
SUM(total_amount) as total_revenue
FROM orders
WHERE merchant_id = ? AND is_deleted = 0
`;
const [stats] = await database.query(query, [merchantId]);
return stats;
} catch (error) {
console.error('获取订单统计失败:', error);
throw new Error('获取订单统计失败');
}
}
/**
* 获取所有订单(管理员)
* @param {Object} filters - 筛选条件
* @returns {Promise<Array>} 订单列表
*/
async getAllOrders(filters = {}) {
try {
const {
page = 1,
pageSize = 10,
status,
merchantId,
userId
} = filters;
const offset = (page - 1) * pageSize;
let query = `
SELECT
o.*,
a.name as animal_name,
a.species as animal_species,
u.username as user_name,
m.business_name as merchant_name
FROM orders o
LEFT JOIN animals a ON o.animal_id = a.id
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN merchants m ON o.merchant_id = m.id
WHERE o.is_deleted = 0
`;
let countQuery = `
SELECT COUNT(*) as total
FROM orders o
WHERE o.is_deleted = 0
`;
const params = [];
const countParams = [];
if (status) {
query += ' AND o.status = ?';
countQuery += ' AND o.status = ?';
params.push(status);
countParams.push(status);
}
if (merchantId) {
query += ' AND o.merchant_id = ?';
countQuery += ' AND o.merchant_id = ?';
params.push(merchantId);
countParams.push(merchantId);
}
if (userId) {
query += ' AND o.user_id = ?';
countQuery += ' AND o.user_id = ?';
params.push(userId);
countParams.push(userId);
}
query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
const [orders] = await database.query(query, params);
const [totalResult] = await database.query(countQuery, countParams);
return {
orders,
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: totalResult.total,
totalPages: Math.ceil(totalResult.total / pageSize)
}
};
} catch (error) {
console.error('获取所有订单失败:', error);
throw new Error('获取所有订单失败');
}
}
}
module.exports = new OrderService();

View File

@@ -0,0 +1,206 @@
const { query } = require('../../config/database');
const { AppError } = require('../../utils/errors');
class TravelService {
// 获取旅行计划列表
static async getTravelPlans(searchParams) {
try {
const { userId, page = 1, pageSize = 10, status } = searchParams;
const offset = (page - 1) * pageSize;
let sql = `
SELECT tp.*, u.username, u.real_name, u.avatar_url
FROM travel_plans tp
INNER JOIN users u ON tp.user_id = u.id
WHERE 1=1
`;
const params = [];
if (userId) {
sql += ' AND tp.user_id = ?';
params.push(userId);
}
if (status) {
sql += ' AND tp.status = ?';
params.push(status);
}
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM (${sql}) as count_query`;
const countResult = await query(countSql, params);
const total = countResult[0].total;
// 添加分页和排序
sql += ' ORDER BY tp.created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
const plans = await query(sql, params);
return {
plans: plans.map(plan => this.sanitizePlan(plan)),
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: parseInt(total),
totalPages: Math.ceil(total / pageSize)
}
};
} catch (error) {
throw error;
}
}
// 获取单个旅行计划详情
static async getTravelPlanById(planId) {
try {
const sql = `
SELECT tp.*, u.username, u.real_name, u.avatar_url
FROM travel_plans tp
INNER JOIN users u ON tp.user_id = u.id
WHERE tp.id = ?
`;
const plans = await query(sql, [planId]);
if (plans.length === 0) {
throw new AppError('旅行计划不存在', 404);
}
return this.sanitizePlan(plans[0]);
} catch (error) {
throw error;
}
}
// 创建旅行计划
static async createTravelPlan(userId, planData) {
try {
const {
destination,
start_date,
end_date,
budget,
companions,
transportation,
accommodation,
activities,
notes
} = planData;
const sql = `
INSERT INTO travel_plans (
user_id, destination, start_date, end_date, budget, companions,
transportation, accommodation, activities, notes, status, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'planning', NOW(), NOW())
`;
const params = [
userId,
destination,
start_date,
end_date,
budget,
companions,
transportation,
accommodation,
activities,
notes
];
const result = await query(sql, params);
return result.insertId;
} catch (error) {
throw error;
}
}
// 更新旅行计划
static async updateTravelPlan(planId, userId, updateData) {
try {
const allowedFields = [
'destination', 'start_date', 'end_date', 'budget', 'companions',
'transportation', 'accommodation', 'activities', 'notes', 'status'
];
const setClauses = [];
const params = [];
for (const [key, value] of Object.entries(updateData)) {
if (allowedFields.includes(key) && value !== undefined) {
setClauses.push(`${key} = ?`);
params.push(value);
}
}
if (setClauses.length === 0) {
throw new AppError('没有有效的更新字段', 400);
}
setClauses.push('updated_at = NOW()');
params.push(planId, userId);
const sql = `
UPDATE travel_plans
SET ${setClauses.join(', ')}
WHERE id = ? AND user_id = ?
`;
const result = await query(sql, params);
if (result.affectedRows === 0) {
throw new AppError('旅行计划不存在或没有权限修改', 404);
}
return await this.getTravelPlanById(planId);
} catch (error) {
throw error;
}
}
// 删除旅行计划
static async deleteTravelPlan(planId, userId) {
try {
const sql = 'DELETE FROM travel_plans WHERE id = ? AND user_id = ?';
const result = await query(sql, [planId, userId]);
if (result.affectedRows === 0) {
throw new AppError('旅行计划不存在或没有权限删除', 404);
}
return true;
} catch (error) {
throw error;
}
}
// 获取用户旅行统计
static async getUserTravelStats(userId) {
try {
const sql = `
SELECT
COUNT(*) as total_plans,
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_plans,
COUNT(CASE WHEN status = 'planning' THEN 1 END) as planning_plans,
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled_plans,
SUM(budget) as total_budget
FROM travel_plans
WHERE user_id = ?
`;
const stats = await query(sql, [userId]);
return stats[0];
} catch (error) {
throw error;
}
}
// 安全返回旅行计划信息
static sanitizePlan(plan) {
if (!plan) return null;
const sanitized = { ...plan };
// 移除敏感信息或格式化数据
return sanitized;
}
}
module.exports = TravelService;

View File

@@ -0,0 +1,165 @@
const UserMySQL = require('../../models/UserMySQL');
const { AppError } = require('../../utils/errors');
class UserService {
// 获取用户详情
static async getUserProfile(userId) {
try {
const user = await UserMySQL.findById(userId);
if (!user) {
throw new AppError('用户不存在', 404);
}
return UserMySQL.sanitize(user);
} catch (error) {
throw error;
}
}
// 更新用户信息
static async updateUserProfile(userId, updateData) {
try {
const allowedFields = ['nickname', 'avatar', 'gender', 'birthday', 'phone', 'email'];
const filteredUpdates = {};
// 过滤允许更新的字段
Object.keys(updateData).forEach(key => {
if (allowedFields.includes(key) && updateData[key] !== undefined) {
filteredUpdates[key] = updateData[key];
}
});
if (Object.keys(filteredUpdates).length === 0) {
throw new AppError('没有有效的更新字段', 400);
}
// 检查邮箱是否已被其他用户使用
if (filteredUpdates.email) {
const emailExists = await UserMySQL.isEmailExists(filteredUpdates.email, userId);
if (emailExists) {
throw new AppError('邮箱已被其他用户使用', 400);
}
}
// 检查手机号是否已被其他用户使用
if (filteredUpdates.phone) {
const phoneExists = await UserMySQL.isPhoneExists(filteredUpdates.phone, userId);
if (phoneExists) {
throw new AppError('手机号已被其他用户使用', 400);
}
}
const success = await UserMySQL.update(userId, filteredUpdates);
if (!success) {
throw new AppError('更新用户信息失败', 500);
}
// 返回更新后的用户信息
return await this.getUserProfile(userId);
} catch (error) {
throw error;
}
}
// 搜索用户(管理员功能)
static async searchUsers(searchParams) {
try {
const { keyword, userType, page = 1, pageSize = 10 } = searchParams;
const offset = (page - 1) * pageSize;
let sql = `
SELECT id, username, user_type, real_name, avatar_url, email, phone,
created_at, updated_at, status
FROM users
WHERE 1=1
`;
const params = [];
if (keyword) {
sql += ` AND (
username LIKE ? OR
real_name LIKE ? OR
email LIKE ? OR
phone LIKE ?
)`;
const likeKeyword = `%${keyword}%`;
params.push(likeKeyword, likeKeyword, likeKeyword, likeKeyword);
}
if (userType) {
sql += ' AND user_type = ?';
params.push(userType);
}
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM (${sql}) as count_query`;
const countResult = await UserMySQL.query(countSql, params);
const total = countResult[0].total;
// 添加分页和排序
sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
const users = await UserMySQL.query(sql, params);
return {
users: users.map(user => UserMySQL.sanitize(user)),
pagination: {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: parseInt(total),
totalPages: Math.ceil(total / pageSize)
}
};
} catch (error) {
throw error;
}
}
// 获取用户统计信息(管理员功能)
static async getUserStatistics() {
try {
const sql = `
SELECT
COUNT(*) as total_users,
COUNT(CASE WHEN user_type = 'farmer' THEN 1 END) as farmers,
COUNT(CASE WHEN user_type = 'merchant' THEN 1 END) as merchants,
COUNT(CASE WHEN user_type = 'admin' THEN 1 END) as admins,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_users,
COUNT(CASE WHEN status = 'inactive' THEN 1 END) as inactive_users,
DATE(created_at) as date
FROM users
GROUP BY DATE(created_at)
ORDER BY date DESC
LIMIT 30
`;
const stats = await UserMySQL.query(sql);
return stats;
} catch (error) {
throw error;
}
}
// 批量操作用户状态(管理员功能)
static async batchUpdateUserStatus(userIds, status) {
try {
if (!['active', 'inactive'].includes(status)) {
throw new AppError('无效的状态值', 400);
}
if (!userIds || userIds.length === 0) {
throw new AppError('请选择要操作的用户', 400);
}
const placeholders = userIds.map(() => '?').join(',');
const sql = `UPDATE users SET status = ?, updated_at = NOW() WHERE id IN (${placeholders})`;
const result = await UserMySQL.query(sql, [status, ...userIds]);
return result.affectedRows;
} catch (error) {
throw error;
}
}
}
module.exports = UserService;