refactor(backend): 重构动物相关 API 接口
- 更新了动物数据结构和相关类型定义 - 优化了动物列表、详情、创建、更新和删除接口 - 新增了更新动物状态接口 - 移除了与认领记录相关的接口 -调整了 API 响应结构
This commit is contained in:
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