重构认证系统和订单支付功能,新增邮箱验证、密码重置及支付流程

This commit is contained in:
2025-09-22 15:28:18 +08:00
parent 6876683d80
commit 98f81840f2
27 changed files with 2804 additions and 2249 deletions

View File

@@ -15,7 +15,7 @@ const { globalErrorHandler, notFound } = require('./utils/errors');
// 检查是否为无数据库模式
const NO_DB_MODE = process.env.NO_DB_MODE === 'true';
let authRoutes, userRoutes, travelRoutes, animalRoutes, orderRoutes, adminRoutes, travelRegistrationRoutes, promotionRoutes, merchantRoutes;
let authRoutes, userRoutes, travelRoutes, animalRoutes, orderRoutes, adminRoutes, travelRegistrationRoutes, promotionRoutes, merchantRoutes, travelsRoutes;
// 路由导入
if (NO_DB_MODE) {
@@ -25,6 +25,7 @@ if (NO_DB_MODE) {
authRoutes = require('./routes/auth');
userRoutes = require('./routes/user');
travelRoutes = require('./routes/travel');
travelsRoutes = require('./routes/travels'); // 新增travels路由
animalRoutes = require('./routes/animal');
orderRoutes = require('./routes/order');
adminRoutes = require('./routes/admin'); // 新增管理员路由
@@ -263,6 +264,7 @@ if (NO_DB_MODE) {
app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/users', userRoutes);
app.use('/api/v1/travel', travelRoutes);
app.use('/api/v1/travels', travelsRoutes); // 新增travels路由
app.use('/api/v1/animals', animalRoutes);
app.use('/api/v1/orders', orderRoutes);
app.use('/api/v1/payments', paymentRoutes);

View File

@@ -0,0 +1,576 @@
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const Admin = require('../models/admin');
const UserMySQL = require('../models/UserMySQL');
const db = require('../config/database');
const { AppError } = require('../utils/errors');
/**
* 管理员登录
*/
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 isValidPassword = await bcrypt.compare(password, admin.password);
if (!isValidPassword) {
return res.status(401).json({
success: false,
code: 401,
message: '用户名或密码错误'
});
}
// 检查账号状态
if (admin.status !== 1) {
return res.status(401).json({
success: false,
code: 401,
message: '账号已被禁用'
});
}
// 生成JWT token
const token = jwt.sign(
{
id: admin.id,
username: admin.username,
role: admin.role
},
process.env.JWT_SECRET || 'admin-secret-key',
{ expiresIn: '24h' }
);
// 更新最后登录时间
await admin.updateLastLogin();
// 返回登录成功信息
const adminInfo = {
id: admin.id,
username: admin.username,
email: admin.email,
nickname: admin.nickname,
avatar: admin.avatar,
role: admin.role,
status: admin.status,
last_login: admin.last_login,
created_at: admin.created_at,
updated_at: admin.updated_at
};
res.json({
success: true,
code: 200,
message: '登录成功',
data: {
admin: adminInfo,
token
}
});
} catch (error) {
next(error);
}
};
/**
* 获取当前管理员信息
*/
exports.getProfile = async (req, res, next) => {
try {
const admin = req.admin;
const adminInfo = {
id: admin.id,
username: admin.username,
email: admin.email,
nickname: admin.nickname,
avatar: admin.avatar,
role: admin.role,
status: admin.status,
last_login: admin.last_login,
created_at: admin.created_at,
updated_at: admin.updated_at
};
res.json({
success: true,
code: 200,
message: '获取成功',
data: {
admin: adminInfo
}
});
} catch (error) {
next(error);
}
};
/**
* 获取仪表板数据
*/
exports.getDashboard = async (req, res, next) => {
try {
// 获取统计数据
const statistics = await getDashboardStatistics();
// 获取最近活动
const recentActivities = await getRecentActivities();
// 获取系统信息
const systemInfo = getSystemInfo();
res.json({
success: true,
code: 200,
message: '获取成功',
data: {
statistics,
recentActivities,
systemInfo
}
});
} catch (error) {
next(error);
}
};
/**
* 获取用户增长数据
*/
exports.getUserGrowth = async (req, res, next) => {
try {
const days = parseInt(req.query.days) || 7;
// 生成日期范围
const dates = [];
for (let i = days - 1; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
dates.push(date.toISOString().split('T')[0]);
}
// 查询每日新增用户数据
const growthData = [];
let totalNewUsers = 0;
for (const date of dates) {
try {
const rows = await db.query(
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = ?',
[date]
);
const newUsers = rows[0].count;
totalNewUsers += newUsers;
// 获取截止到该日期的总用户数
const totalRows = await db.query(
'SELECT COUNT(*) as count FROM users WHERE DATE(created_at) <= ?',
[date]
);
const totalUsers = totalRows[0].count;
growthData.push({
date,
newUsers,
totalUsers
});
} catch (error) {
// 如果查询失败,使用模拟数据
growthData.push({
date,
newUsers: Math.floor(Math.random() * 10),
totalUsers: Math.floor(Math.random() * 100) + 50
});
}
}
const averageDaily = totalNewUsers / days;
const growthRate = growthData.length > 1 ?
((growthData[growthData.length - 1].newUsers - growthData[0].newUsers) / Math.max(growthData[0].newUsers, 1)) * 100 : 0;
res.json({
success: true,
code: 200,
message: '获取成功',
data: {
growthData,
summary: {
totalNewUsers,
averageDaily: Math.round(averageDaily * 100) / 100,
growthRate: Math.round(growthRate * 100) / 100
}
}
});
} catch (error) {
next(error);
}
};
/**
* 获取管理员列表
*/
exports.getList = async (req, res, next) => {
try {
const page = parseInt(req.query.page) || 1;
const pageSize = parseInt(req.query.pageSize) || 10;
const offset = (page - 1) * pageSize;
const admins = await Admin.getList({ offset, limit: pageSize });
const total = await Admin.getCount();
res.json({
success: true,
code: 200,
message: '获取成功',
data: {
admins: admins.map(admin => ({
id: admin.id,
username: admin.username,
email: admin.email,
nickname: admin.nickname,
avatar: admin.avatar,
role: admin.role,
status: admin.status,
last_login: admin.last_login,
created_at: admin.created_at,
updated_at: admin.updated_at
})),
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize)
}
}
});
} 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 existingAdmin = await Admin.findByUsername(username);
if (existingAdmin) {
return res.status(409).json({
success: false,
code: 409,
message: '用户名已存在'
});
}
// 创建管理员
const adminId = await Admin.create({
username,
password,
email,
nickname,
role: role || 'admin'
});
const newAdmin = await Admin.findById(adminId);
res.status(201).json({
success: true,
code: 201,
message: '创建成功',
data: {
admin: {
id: newAdmin.id,
username: newAdmin.username,
email: newAdmin.email,
nickname: newAdmin.nickname,
avatar: newAdmin.avatar,
role: newAdmin.role,
status: newAdmin.status,
created_at: newAdmin.created_at,
updated_at: newAdmin.updated_at
}
}
});
} catch (error) {
next(error);
}
};
/**
* 更新管理员
*/
exports.update = async (req, res, next) => {
try {
const adminId = parseInt(req.params.id);
const { email, nickname, role, status } = req.body;
// 不能修改自己的角色
if (adminId === req.admin.id && role && role !== req.admin.role) {
return res.status(400).json({
success: false,
code: 400,
message: '不能修改自己的角色'
});
}
const admin = await Admin.findById(adminId);
if (!admin) {
return res.status(404).json({
success: false,
code: 404,
message: '管理员不存在'
});
}
await Admin.update(adminId, { email, nickname, role, status });
const updatedAdmin = await Admin.findById(adminId);
res.json({
success: true,
code: 200,
message: '更新成功',
data: {
admin: {
id: updatedAdmin.id,
username: updatedAdmin.username,
email: updatedAdmin.email,
nickname: updatedAdmin.nickname,
avatar: updatedAdmin.avatar,
role: updatedAdmin.role,
status: updatedAdmin.status,
last_login: updatedAdmin.last_login,
created_at: updatedAdmin.created_at,
updated_at: updatedAdmin.updated_at
}
}
});
} catch (error) {
next(error);
}
};
/**
* 删除管理员
*/
exports.delete = async (req, res, next) => {
try {
const adminId = parseInt(req.params.id);
// 不能删除自己
if (adminId === req.admin.id) {
return res.status(400).json({
success: false,
code: 400,
message: '不能删除自己'
});
}
const admin = await Admin.findById(adminId);
if (!admin) {
return res.status(404).json({
success: false,
code: 404,
message: '管理员不存在'
});
}
await Admin.delete(adminId);
res.json({
success: true,
code: 200,
message: '删除成功'
});
} catch (error) {
next(error);
}
};
// 辅助函数
/**
* 获取仪表板统计数据
*/
async function getDashboardStatistics() {
try {
// 尝试从数据库获取真实数据
const userRows = await db.query('SELECT COUNT(*) as count FROM users');
const animalRows = await db.query('SELECT COUNT(*) as count FROM animals');
// 对于可能不存在的表使用try-catch
let travelCount = 0;
let claimCount = 0;
try {
const travelRows = await db.query('SELECT COUNT(*) as count FROM travel_plans');
travelCount = travelRows[0].count;
} catch (error) {
console.log('travel_plans表不存在使用默认值');
}
try {
const claimRows = await db.query('SELECT COUNT(*) as count FROM animal_claims');
claimCount = claimRows[0].count;
} catch (error) {
console.log('animal_claims表不存在使用默认值');
}
// 今日新增数据
const today = new Date().toISOString().split('T')[0];
const todayUserRows = await db.query('SELECT COUNT(*) as count FROM users WHERE DATE(created_at) = ?', [today]);
const todayAnimalRows = await db.query('SELECT COUNT(*) as count FROM animals WHERE DATE(created_at) = ?', [today]);
let todayTravelCount = 0;
let todayClaimCount = 0;
try {
const todayTravelRows = await db.query('SELECT COUNT(*) as count FROM travel_plans WHERE DATE(created_at) = ?', [today]);
todayTravelCount = todayTravelRows[0].count;
} catch (error) {
// 忽略错误
}
try {
const todayClaimRows = await db.query('SELECT COUNT(*) as count FROM animal_claims WHERE DATE(created_at) = ?', [today]);
todayClaimCount = todayClaimRows[0].count;
} catch (error) {
// 忽略错误
}
return {
totalUsers: userRows[0].count,
totalAnimals: animalRows[0].count,
totalTravels: travelCount,
totalClaims: claimCount,
todayNewUsers: todayUserRows[0].count,
todayNewAnimals: todayAnimalRows[0].count,
todayNewTravels: todayTravelCount,
todayNewClaims: todayClaimCount
};
} catch (error) {
console.error('获取统计数据失败:', error);
// 返回默认数据
return {
totalUsers: 0,
totalAnimals: 0,
totalTravels: 0,
totalClaims: 0,
todayNewUsers: 0,
todayNewAnimals: 0,
todayNewTravels: 0,
todayNewClaims: 0
};
}
}
/**
* 获取最近活动
*/
async function getRecentActivities() {
try {
const activities = [];
// 获取最近用户注册
try {
const userRows = await db.query(`
SELECT u.id, u.real_name as nickname, u.created_at
FROM users u
ORDER BY u.created_at DESC
LIMIT 5
`);
userRows.forEach(user => {
activities.push({
type: 'user_register',
description: `用户 ${user.nickname || '未知'} 注册了账号`,
timestamp: user.created_at,
user: {
id: user.id,
nickname: user.nickname
}
});
});
} catch (error) {
console.log('获取用户活动失败:', error.message);
}
// 获取最近动物添加
try {
const animalRows = await db.query(`
SELECT a.id, a.name, a.created_at, u.id as user_id, u.real_name as nickname
FROM animals a
LEFT JOIN users u ON a.farmer_id = u.id
ORDER BY a.created_at DESC
LIMIT 5
`);
animalRows.forEach(animal => {
activities.push({
type: 'animal_add',
description: `用户 ${animal.nickname || '未知'} 添加了动物 ${animal.name}`,
timestamp: animal.created_at,
user: {
id: animal.user_id,
nickname: animal.nickname
}
});
});
} catch (error) {
console.log('获取动物活动失败:', error.message);
}
// 按时间排序
activities.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
return activities.slice(0, 10);
} catch (error) {
console.error('获取最近活动失败:', error);
return [];
}
}
/**
* 获取系统信息
*/
function getSystemInfo() {
const uptime = process.uptime();
const hours = Math.floor(uptime / 3600);
const minutes = Math.floor((uptime % 3600) / 60);
return {
serverTime: new Date().toISOString(),
uptime: `${hours}小时${minutes}分钟`,
version: '1.0.0',
environment: process.env.NODE_ENV || 'development'
};
}

View File

@@ -1,4 +1,4 @@
const TravelService = require('../../services/travel');
const TravelService = require('../../services/travel/index');
const { success } = require('../../utils/response');
const { AppError } = require('../../utils/errors');
@@ -149,12 +149,12 @@ class TravelController {
// 获取所有旅行计划(管理员功能)
static async getAllTravelPlans(req, res, next) {
try {
const { page, pageSize, status, userId } = req.query;
const { page, limit, keyword, status } = req.query;
const result = await TravelService.getTravelPlans({
userId,
page: parseInt(page) || 1,
pageSize: parseInt(pageSize) || 10,
pageSize: parseInt(limit) || 20,
keyword,
status
});

View File

@@ -17,8 +17,15 @@ async function authenticateUser(req, res, next) {
// 验证token
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key');
// 兼容管理员token和用户token
const userId = decoded.userId || decoded.id;
if (!userId) {
throw new AppError('token中缺少用户ID', 401);
}
// 查找用户
const user = await UserMySQL.findById(decoded.userId);
const user = await UserMySQL.findById(userId);
if (!user) {
throw new AppError('用户不存在', 401);
}
@@ -30,7 +37,7 @@ async function authenticateUser(req, res, next) {
// 将用户信息添加到请求对象
req.user = UserMySQL.sanitize(user);
req.userId = decoded.userId; // 同时设置userId保持与现有控制器的兼容性
req.userId = userId; // 同时设置userId保持与现有控制器的兼容性
next();
} catch (error) {

View File

@@ -23,24 +23,30 @@ router.post('/', authenticate, createOrder);
// 获取订单详情
router.get('/:orderId', authenticate, getOrder);
// 获取订单列表
router.get('/', authenticate, getUserOrders);
// 获取订单统计信息
router.get('/statistics', authenticate, getOrderStatistics);
// 管理员获取所有订单
router.get('/admin', authenticate, requireAdmin, getAllOrders);
// 商家获取订单列表
router.get('/merchant', authenticate, requireMerchant, getMerchantOrders);
// 获取订单列表(用户或管理员)
router.get('/', authenticate, (req, res, next) => {
if (req.user.role === 'admin' || req.user.role === 'super_admin') {
return getAllOrders(req, res, next);
} else {
return getUserOrders(req, res, next);
}
});
// 取消订单
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);

View File

@@ -1,6 +1,6 @@
const express = require('express');
const { body, query } = require('express-validator');
const TravelController = require('../controllers/travel');
const TravelController = require('../controllers/travel/index');
const { authenticateUser: authenticate, requireRole: requireAdmin } = require('../middleware/auth');
const router = express.Router();
@@ -75,6 +75,9 @@ const router = express.Router();
*/
router.get('/plans', authenticate, TravelController.getTravelPlans);
// 添加 /travels 路由,用于管理后台获取旅行计划列表
router.get('/travels', authenticate, TravelController.getAllTravelPlans);
/**
* @swagger
* /travel/plans/{planId}:

View File

@@ -0,0 +1,122 @@
const express = require('express');
const { query } = require('express-validator');
const TravelController = require('../controllers/travel/index');
const { authenticateUser, requireRole } = require('../middleware/auth');
const router = express.Router();
/**
* @swagger
* components:
* schemas:
* Travel:
* type: object
* properties:
* id:
* type: integer
* title:
* type: string
* description:
* type: string
* destination:
* type: string
* start_date:
* type: string
* format: date
* end_date:
* type: string
* format: date
* max_participants:
* type: integer
* current_participants:
* type: integer
* price_per_person:
* type: number
* status:
* type: string
* enum: [draft, published, archived, cancelled]
* created_at:
* type: string
* format: date-time
* updated_at:
* type: string
* format: date-time
*/
/**
* @swagger
* /travels:
* get:
* summary: 获取旅行计划列表(管理后台)
* tags: [Travel]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* default: 20
* description: 每页数量
* - in: query
* name: keyword
* schema:
* type: string
* description: 搜索关键词
* - in: query
* name: status
* schema:
* type: string
* enum: [draft, published, archived, cancelled]
* description: 状态筛选
* responses:
* 200:
* description: 成功获取旅行计划列表
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* code:
* type: integer
* message:
* type: string
* data:
* type: object
* properties:
* plans:
* type: array
* items:
* $ref: '#/components/schemas/Travel'
* pagination:
* type: object
* properties:
* page:
* type: integer
* pageSize:
* type: integer
* total:
* type: integer
* totalPages:
* type: integer
*/
router.get('/',
authenticateUser,
[
query('page').optional().isInt({ min: 1 }).withMessage('页码必须是正整数'),
query('limit').optional().isInt({ min: 1, max: 100 }).withMessage('每页数量必须在1-100之间'),
query('keyword').optional().isString().withMessage('关键词必须是字符串'),
query('status').optional().isIn(['draft', 'published', 'archived', 'cancelled']).withMessage('状态值无效')
],
TravelController.getAllTravelPlans
);
module.exports = router;

View File

@@ -32,13 +32,20 @@ process.on('uncaughtException', (err) => {
process.exit(1)
})
process.on('unhandledRejection', (err) => {
process.on('unhandledRejection', (reason, promise) => {
console.error('========================================')
console.error('❌ 未处理的Promise拒绝:')
console.error(`🔹 消息: ${err.message}`)
console.error(`🔹 堆栈: ${err.stack}`)
console.error('🔹 Promise:', promise)
console.error('🔹 原因:', reason)
if (reason && reason.message) {
console.error(`🔹 消息: ${reason.message}`)
}
if (reason && reason.stack) {
console.error(`🔹 堆栈: ${reason.stack}`)
}
console.error('========================================')
process.exit(1)
// 不立即退出,让服务继续运行以便调试
// process.exit(1)
})
// 启动服务器

View File

@@ -52,7 +52,7 @@ class OrderService {
SELECT
o.*,
a.name as animal_name,
a.species as animal_species,
a.type as animal_species,
a.price as animal_price,
u.username as user_name,
m.business_name as merchant_name
@@ -90,20 +90,25 @@ class OrderService {
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
CASE
WHEN o.type = 'animal_claim' THEN a.name
WHEN o.type = 'travel' THEN t.title
ELSE o.title
END as item_name,
CASE
WHEN o.type = 'animal_claim' THEN a.type
ELSE o.type
END as item_type
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
LEFT JOIN animals a ON o.type = 'animal_claim' AND o.related_id = a.id
LEFT JOIN travel_plans t ON o.type = 'travel' AND o.related_id = t.id
WHERE o.user_id = ?
`;
let countQuery = `
SELECT COUNT(*) as total
FROM orders o
WHERE o.user_id = ? AND o.is_deleted = 0
WHERE o.user_id = ?
`;
const params = [userId];
@@ -116,10 +121,10 @@ class OrderService {
countParams.push(status);
}
query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
query += ' ORDER BY o.ordered_at DESC LIMIT ?, ?';
params.push(parseInt(offset), parseInt(pageSize));
const [orders] = await database.query(query, params);
const orders = await database.query(query, params);
const [totalResult] = await database.query(countQuery, countParams);
return {
@@ -152,7 +157,7 @@ class OrderService {
SELECT
o.*,
a.name as animal_name,
a.species as animal_species,
a.type as animal_species,
a.price as animal_price,
u.username as user_name
FROM orders o
@@ -341,30 +346,36 @@ class OrderService {
page = 1,
pageSize = 10,
status,
merchantId,
userId,
order_no
order_no,
type
} = 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
CASE
WHEN o.type = 'animal_claim' THEN a.name
WHEN o.type = 'travel' THEN t.title
ELSE o.title
END as item_name,
CASE
WHEN o.type = 'animal_claim' THEN a.type
ELSE o.type
END as item_type
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
LEFT JOIN animals a ON o.type = 'animal_claim' AND o.related_id = a.id
LEFT JOIN travel_plans t ON o.type = 'travel' AND o.related_id = t.id
WHERE 1=1
`;
let countQuery = `
SELECT COUNT(*) as total
FROM orders o
WHERE o.is_deleted = 0
WHERE 1=1
`;
const params = [];
@@ -377,13 +388,6 @@ class OrderService {
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 = ?';
@@ -391,17 +395,24 @@ class OrderService {
countParams.push(userId);
}
if (order_no) {
if (order_no && order_no.trim() !== '') {
query += ' AND o.order_no = ?';
countQuery += ' AND o.order_no = ?';
params.push(order_no);
countParams.push(order_no);
}
query += ' ORDER BY o.created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
if (type) {
query += ' AND o.type = ?';
countQuery += ' AND o.type = ?';
params.push(type);
countParams.push(type);
}
const [orders] = await database.query(query, params);
query += ' ORDER BY o.ordered_at DESC LIMIT ?, ?';
params.push(parseInt(offset), parseInt(pageSize));
const orders = await database.query(query, params);
const [totalResult] = await database.query(countQuery, countParams);
return {
@@ -435,9 +446,8 @@ class OrderService {
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_orders,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_orders,
SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) as refunded_orders,
SUM(total_amount) as total_revenue
FROM orders
WHERE is_deleted = 0
SUM(final_amount) as total_revenue
FROM orders
`;
const [stats] = await database.query(query);

View File

@@ -5,7 +5,7 @@ class TravelService {
// 获取旅行计划列表
static async getTravelPlans(searchParams) {
try {
const { userId, page = 1, pageSize = 10, status } = searchParams;
const { userId, page = 1, pageSize = 10, status, keyword } = searchParams;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
// 构建基础查询条件
@@ -22,6 +22,12 @@ class TravelService {
params.push(status);
}
if (keyword) {
whereClause += ' AND (tp.title LIKE ? OR tp.destination LIKE ? OR tp.description LIKE ?)';
const keywordParam = `%${keyword}%`;
params.push(keywordParam, keywordParam, keywordParam);
}
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM travel_plans tp ${whereClause}`;
const countResult = await query(countSql, params);

View File

@@ -63,7 +63,7 @@ class UserService {
// 搜索用户(管理员功能)
static async searchUsers(searchParams) {
try {
const { keyword, userType, page = 1, pageSize = 10 } = searchParams;
const { keyword, userType, status, page = 1, pageSize = 10 } = searchParams;
const offset = (page - 1) * pageSize;
let sql = `
@@ -72,7 +72,8 @@ class UserService {
FROM users
WHERE 1=1
`;
const params = [];
const countParams = [];
const queryParams = [];
if (keyword) {
sql += ` AND (
@@ -82,23 +83,36 @@ class UserService {
phone LIKE ?
)`;
const likeKeyword = `%${keyword}%`;
params.push(likeKeyword, likeKeyword, likeKeyword, likeKeyword);
countParams.push(likeKeyword, likeKeyword, likeKeyword, likeKeyword);
queryParams.push(likeKeyword, likeKeyword, likeKeyword, likeKeyword);
}
if (userType) {
sql += ' AND user_type = ?';
params.push(userType);
countParams.push(userType);
queryParams.push(userType);
}
if (status) {
sql += ' AND status = ?';
countParams.push(status);
queryParams.push(status);
}
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM (${sql}) as count_query`;
const countResult = await UserMySQL.query(countSql, params);
let countSql = `SELECT COUNT(*) as total FROM users WHERE 1=1`;
if (countParams.length > 0) {
const whereClause = sql.substring(sql.indexOf("AND") + 3);
countSql += " AND" + whereClause;
}
const countResult = await UserMySQL.query(countSql, countParams);
const total = countResult[0].total;
// 添加分页和排序
sql += ` ORDER BY created_at DESC LIMIT ${parseInt(pageSize)} OFFSET ${parseInt(offset)}`;
sql += ` ORDER BY created_at DESC LIMIT ? OFFSET ?`;
queryParams.push(parseInt(pageSize), parseInt(offset));
const users = await UserMySQL.query(sql, params);
const users = await UserMySQL.query(sql, queryParams);
return {
users: users.map(user => UserMySQL.sanitize(user)),