refactor(backend): 重构动物相关 API 接口
- 更新了动物数据结构和相关类型定义 - 优化了动物列表、详情、创建、更新和删除接口 - 新增了更新动物状态接口 - 移除了与认领记录相关的接口 -调整了 API 响应结构
This commit is contained in:
@@ -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
|
||||
|
||||
# 测试环境数据库
|
||||
|
||||
@@ -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: {
|
||||
|
||||
13
backend/package-lock.json
generated
13
backend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
};
|
||||
225
backend/src/controllers/admin/index.js
Normal file
225
backend/src/controllers/admin/index.js
Normal 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);
|
||||
}
|
||||
};
|
||||
166
backend/src/controllers/animal/index.js
Normal file
166
backend/src/controllers/animal/index.js
Normal 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;
|
||||
@@ -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
|
||||
};
|
||||
402
backend/src/controllers/order/index.js
Normal file
402
backend/src/controllers/order/index.js
Normal 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
|
||||
};
|
||||
152
backend/src/controllers/travel/index.js
Normal file
152
backend/src/controllers/travel/index.js
Normal 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;
|
||||
129
backend/src/controllers/user/index.js
Normal file
129
backend/src/controllers/user/index.js
Normal 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;
|
||||
@@ -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: '更新时间'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// 安全返回用户信息(去除敏感信息)
|
||||
|
||||
88
backend/src/models/admin.js
Normal file
88
backend/src/models/admin.js
Normal 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
327
backend/src/routes/admin.js
Normal 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;
|
||||
533
backend/src/routes/animal.js
Normal file
533
backend/src/routes/animal.js
Normal 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;
|
||||
@@ -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:
|
||||
|
||||
53
backend/src/routes/order.js
Normal file
53
backend/src/routes/order.js
Normal 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;
|
||||
434
backend/src/routes/travel.js
Normal file
434
backend/src/routes/travel.js
Normal 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
380
backend/src/routes/user.js
Normal 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;
|
||||
@@ -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'
|
||||
|
||||
// 显示启动横幅
|
||||
|
||||
158
backend/src/services/admin/index.js
Normal file
158
backend/src/services/admin/index.js
Normal 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();
|
||||
262
backend/src/services/animal/index.js
Normal file
262
backend/src/services/animal/index.js
Normal 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;
|
||||
368
backend/src/services/order/index.js
Normal file
368
backend/src/services/order/index.js
Normal 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();
|
||||
206
backend/src/services/travel/index.js
Normal file
206
backend/src/services/travel/index.js
Normal 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;
|
||||
165
backend/src/services/user/index.js
Normal file
165
backend/src/services/user/index.js
Normal 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;
|
||||
Reference in New Issue
Block a user