重构动物模型和路由系统,优化查询逻辑并新增商户和促销活动功能

This commit is contained in:
ylweng
2025-09-22 02:04:07 +08:00
parent 5fc1a4fcb9
commit 47c816270d
54 changed files with 5384 additions and 4639 deletions

View File

@@ -0,0 +1,420 @@
# 商户管理API接口文档
## 概述
商户管理模块提供了完整的商户信息管理功能包括商户的增删改查、统计信息等操作。所有接口均遵循RESTful API设计规范。
## 基础信息
- **基础URL**: `/api/v1/merchants`
- **认证方式**: Bearer Token部分接口需要管理员权限
- **数据格式**: JSON
- **字符编码**: UTF-8
## 数据模型
### 商户信息 (Merchant)
```json
{
"id": 1,
"name": "示例商户",
"type": "company",
"contact_person": "张三",
"contact_phone": "13800138000",
"email": "merchant@example.com",
"address": "北京市朝阳区示例街道123号",
"description": "这是一个示例商户",
"status": "active",
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-01-01T00:00:00.000Z"
}
```
### 字段说明
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | integer | - | 商户ID系统自动生成 |
| name | string | ✓ | 商户名称 |
| type | string | ✓ | 商户类型:`individual`(个人)、`company`(企业) |
| contact_person | string | ✓ | 联系人姓名 |
| contact_phone | string | ✓ | 联系电话 |
| email | string | - | 邮箱地址 |
| address | string | - | 地址 |
| description | string | - | 商户描述 |
| status | string | - | 状态:`active`(活跃)、`inactive`(非活跃)、`banned`(禁用) |
| created_at | datetime | - | 创建时间 |
| updated_at | datetime | - | 更新时间 |
## 接口列表
### 1. 获取商户列表
**接口地址**: `GET /api/v1/merchants`
**接口描述**: 获取商户列表,支持分页、搜索和筛选
**请求参数**:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| page | integer | - | 1 | 页码最小值1 |
| limit | integer | - | 20 | 每页数量范围1-100 |
| keyword | string | - | - | 搜索关键词(匹配商户名称、联系人、电话) |
| status | string | - | - | 状态筛选:`active``inactive``banned` |
| type | string | - | - | 类型筛选:`individual``company` |
**请求示例**:
```http
GET /api/v1/merchants?page=1&limit=20&keyword=示例&status=active&type=company
```
**响应示例**:
```json
{
"success": true,
"data": [
{
"id": 1,
"name": "示例商户",
"type": "company",
"contact_person": "张三",
"contact_phone": "13800138000",
"email": "merchant@example.com",
"address": "北京市朝阳区示例街道123号",
"description": "这是一个示例商户",
"status": "active",
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-01-01T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}
```
**错误响应**:
```json
{
"success": false,
"message": "请求参数错误",
"errors": [
{
"field": "page",
"message": "页码必须是正整数"
}
]
}
```
### 2. 获取商户详情
**接口地址**: `GET /api/v1/merchants/{merchantId}`
**接口描述**: 根据商户ID获取商户详细信息
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| merchantId | integer | ✓ | 商户ID |
**请求示例**:
```http
GET /api/v1/merchants/1
```
**响应示例**:
```json
{
"success": true,
"data": {
"id": 1,
"name": "示例商户",
"type": "company",
"contact_person": "张三",
"contact_phone": "13800138000",
"email": "merchant@example.com",
"address": "北京市朝阳区示例街道123号",
"description": "这是一个示例商户",
"status": "active",
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-01-01T00:00:00.000Z",
"animal_count": 15,
"order_count": 128
}
}
```
**错误响应**:
```json
{
"success": false,
"message": "商户不存在"
}
```
### 3. 创建商户
**接口地址**: `POST /api/v1/merchants`
**接口描述**: 创建新商户(需要管理员权限)
**认证要求**: Bearer Token + 管理员权限
**请求体**:
```json
{
"name": "新商户",
"type": "company",
"contact_person": "李四",
"contact_phone": "13900139000",
"email": "newmerchant@example.com",
"address": "上海市浦东新区示例路456号",
"description": "这是一个新商户"
}
```
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | ✓ | 商户名称 |
| type | string | ✓ | 商户类型:`individual``company` |
| contact_person | string | ✓ | 联系人姓名 |
| contact_phone | string | ✓ | 联系电话 |
| email | string | - | 邮箱地址(需符合邮箱格式) |
| address | string | - | 地址 |
| description | string | - | 商户描述 |
**响应示例**:
```json
{
"success": true,
"data": {
"id": 2,
"name": "新商户",
"type": "company",
"contact_person": "李四",
"contact_phone": "13900139000",
"email": "newmerchant@example.com",
"address": "上海市浦东新区示例路456号",
"description": "这是一个新商户",
"status": "active",
"created_at": "2024-01-01T12:00:00.000Z",
"updated_at": "2024-01-01T12:00:00.000Z"
},
"message": "商户创建成功"
}
```
### 4. 更新商户信息
**接口地址**: `PUT /api/v1/merchants/{merchantId}`
**接口描述**: 更新商户信息(需要管理员权限)
**认证要求**: Bearer Token + 管理员权限
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| merchantId | integer | ✓ | 商户ID |
**请求体**:
```json
{
"name": "更新后的商户名称",
"contact_person": "王五",
"contact_phone": "13700137000",
"status": "inactive"
}
```
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | - | 商户名称 |
| type | string | - | 商户类型:`individual``company` |
| contact_person | string | - | 联系人姓名 |
| contact_phone | string | - | 联系电话 |
| email | string | - | 邮箱地址 |
| address | string | - | 地址 |
| description | string | - | 商户描述 |
| status | string | - | 状态:`active``inactive``banned` |
**响应示例**:
```json
{
"success": true,
"data": {
"id": 1,
"name": "更新后的商户名称",
"type": "company",
"contact_person": "王五",
"contact_phone": "13700137000",
"email": "merchant@example.com",
"address": "北京市朝阳区示例街道123号",
"description": "这是一个示例商户",
"status": "inactive",
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-01-01T15:30:00.000Z"
},
"message": "商户信息更新成功"
}
```
### 5. 删除商户
**接口地址**: `DELETE /api/v1/merchants/{merchantId}`
**接口描述**: 删除商户(需要管理员权限)
**认证要求**: Bearer Token + 管理员权限
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| merchantId | integer | ✓ | 商户ID |
**请求示例**:
```http
DELETE /api/v1/merchants/1
```
**响应示例**:
```json
{
"success": true,
"message": "商户删除成功"
}
```
### 6. 获取商户统计信息
**接口地址**: `GET /api/v1/merchants/statistics`
**接口描述**: 获取商户统计信息(需要管理员权限)
**认证要求**: Bearer Token + 管理员权限
**请求示例**:
```http
GET /api/v1/merchants/statistics
```
**响应示例**:
```json
{
"success": true,
"data": {
"total": 150,
"active": 120,
"inactive": 25,
"banned": 5,
"individual": 80,
"company": 70
}
}
```
## 错误码说明
| HTTP状态码 | 错误码 | 说明 |
|------------|--------|------|
| 400 | BAD_REQUEST | 请求参数错误 |
| 401 | UNAUTHORIZED | 未授权,需要登录 |
| 403 | FORBIDDEN | 权限不足,需要管理员权限 |
| 404 | NOT_FOUND | 商户不存在 |
| 409 | CONFLICT | 商户信息冲突(如名称重复) |
| 500 | INTERNAL_ERROR | 服务器内部错误 |
## 通用错误响应格式
```json
{
"success": false,
"message": "错误描述",
"code": "ERROR_CODE",
"timestamp": "2024-01-01T12:00:00.000Z",
"errors": [
{
"field": "字段名",
"message": "字段错误描述"
}
]
}
```
## 使用示例
### JavaScript (Axios)
```javascript
// 获取商户列表
const getMerchants = async (params = {}) => {
try {
const response = await axios.get('/api/v1/merchants', { params });
return response.data;
} catch (error) {
console.error('获取商户列表失败:', error.response.data);
throw error;
}
};
// 创建商户
const createMerchant = async (merchantData) => {
try {
const response = await axios.post('/api/v1/merchants', merchantData, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
return response.data;
} catch (error) {
console.error('创建商户失败:', error.response.data);
throw error;
}
};
```
### cURL
```bash
# 获取商户列表
curl -X GET "http://localhost:3200/api/v1/merchants?page=1&limit=20" \
-H "Content-Type: application/json"
# 创建商户
curl -X POST "http://localhost:3200/api/v1/merchants" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"name": "测试商户",
"type": "company",
"contact_person": "测试联系人",
"contact_phone": "13800138000"
}'
```
## 注意事项
1. **权限控制**: 创建、更新、删除商户以及获取统计信息需要管理员权限
2. **数据验证**: 所有输入数据都会进行严格验证,确保数据完整性
3. **分页限制**: 列表接口每页最多返回100条记录
4. **搜索功能**: 关键词搜索支持模糊匹配商户名称、联系人和电话
5. **状态管理**: 商户状态变更会影响相关业务功能的可用性
6. **数据关联**: 删除商户前请确保没有关联的动物或订单数据
## 更新日志
- **v1.0.0** (2024-01-01): 初始版本,包含基础的商户管理功能

View File

@@ -1,4 +1,4 @@
-- 解班客数据库完整结构创建脚本
-- 结伴客数据库完整结构创建脚本
-- 创建时间: 2024年
-- 数据库: jbkdata

View File

@@ -46,6 +46,44 @@ CREATE TABLE IF NOT EXISTS orders (
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- 创建促销活动表
CREATE TABLE IF NOT EXISTS promotion_activities (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
type ENUM('signup', 'invitation', 'purchase', 'custom') NOT NULL,
status ENUM('active', 'inactive', 'expired') DEFAULT 'active',
start_date DATE NOT NULL,
end_date DATE NOT NULL,
reward_type ENUM('cash', 'points', 'coupon') NOT NULL,
reward_amount DECIMAL(15,2) NOT NULL,
participation_limit INT DEFAULT 0 COMMENT '0表示无限制',
current_participants INT DEFAULT 0,
rules JSON COMMENT '活动规则配置',
created_by INT NOT NULL COMMENT '创建人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES admins(id) ON DELETE CASCADE
);
-- 创建奖励记录表
CREATE TABLE IF NOT EXISTS promotion_rewards (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
user_name VARCHAR(100) NOT NULL,
user_phone VARCHAR(20),
activity_id INT NOT NULL,
activity_name VARCHAR(100) NOT NULL,
reward_type ENUM('cash', 'points', 'coupon') NOT NULL,
reward_amount DECIMAL(15,2) NOT NULL,
status ENUM('pending', 'issued', 'failed') DEFAULT 'pending',
issued_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (activity_id) REFERENCES promotion_activities(id) ON DELETE CASCADE
);
-- 插入默认管理员账号
INSERT INTO admins (username, password, email, role) VALUES
('admin', '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin@jiebanke.com', 'super_admin'),
@@ -64,4 +102,11 @@ CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_phone ON users(phone);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_order_no ON orders(order_no);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_promotion_activities_status ON promotion_activities(status);
CREATE INDEX idx_promotion_activities_type ON promotion_activities(type);
CREATE INDEX idx_promotion_activities_dates ON promotion_activities(start_date, end_date);
CREATE INDEX idx_promotion_rewards_user_id ON promotion_rewards(user_id);
CREATE INDEX idx_promotion_rewards_activity_id ON promotion_rewards(activity_id);
CREATE INDEX idx_promotion_rewards_status ON promotion_rewards(status);
CREATE INDEX idx_promotion_rewards_created_at ON promotion_rewards(created_at);

View File

@@ -8,8 +8,9 @@
const mysql = require('mysql2/promise');
const config = require('../config/env');
// 引入database.js配置
const dbConfig = require('../src/config/database').pool.config;
// 引入环境配置
const envConfig = require('../config/env');
const dbConfig = envConfig.mysql;
// 数据库配置已从database.js导入

View File

@@ -15,13 +15,13 @@ const { globalErrorHandler, notFound } = require('./utils/errors');
// 检查是否为无数据库模式
const NO_DB_MODE = process.env.NO_DB_MODE === 'true';
let authRoutes, userRoutes, travelRoutes, animalRoutes, orderRoutes, adminRoutes, travelRegistrationRoutes;
let authRoutes, userRoutes, travelRoutes, animalRoutes, orderRoutes, adminRoutes, travelRegistrationRoutes, promotionRoutes, merchantRoutes;
// 路由导入 - 根据是否为无数据库模式决定是否导入实际路由
// 路由导入
if (NO_DB_MODE) {
console.log('⚠️ 无数据库模式:将使用模拟路由');
} else {
// 路由导入
console.log('✅ 数据库模式:加载实际路由');
authRoutes = require('./routes/auth');
userRoutes = require('./routes/user');
travelRoutes = require('./routes/travel');
@@ -31,6 +31,8 @@ if (NO_DB_MODE) {
travelRegistrationRoutes = require('./routes/travelRegistration'); // 旅行报名路由
paymentRoutes = require('./routes/payment-simple');
animalClaimRoutes = require('./routes/animalClaim-simple'); // 动物认领路由(简化版)
promotionRoutes = require('./routes/promotion'); // 促销活动路由
merchantRoutes = require('./routes/merchant'); // 商户路由
}
const app = express();
@@ -50,8 +52,10 @@ app.use(cors({
'https://webapi.jiebanke.com',
'http://localhost:3150', // 管理后台本地开发地址
'http://localhost:3000', // 备用端口
'http://localhost:3200', // 备用端口
'http://127.0.0.1:3150', // 备用地址
'http://127.0.0.1:3000' // 备用地址
'http://127.0.0.1:3000', // 备用地址
'http://127.0.0.1:3200' // 备用地址
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
@@ -132,7 +136,8 @@ app.get('/api/v1', (req, res) => {
payments: '/api/v1/payments',
animalClaims: '/api/v1/animal-claims',
admin: '/api/v1/admin',
travelRegistration: '/api/v1/travel-registration'
travelRegistration: '/api/v1/travel-registration',
promotion: '/api/v1/promotion'
},
documentation: 'https://webapi.jiebanke.com/api-docs'
});
@@ -239,6 +244,20 @@ if (NO_DB_MODE) {
message: '当前为无数据库模式,管理员功能不可用'
});
});
app.use('/api/v1/promotion', (req, res) => {
res.status(503).json({
success: false,
message: '当前为无数据库模式,促销活动功能不可用'
});
});
app.use('/api/v1/merchants', (req, res) => {
res.status(503).json({
success: false,
message: '当前为无数据库模式,商户功能不可用'
});
});
} else {
// API路由
app.use('/api/v1/auth', authRoutes);
@@ -253,6 +272,10 @@ if (NO_DB_MODE) {
app.use('/api/v1/admin', adminRoutes);
// 旅行报名路由
app.use('/api/v1/travel-registration', travelRegistrationRoutes);
// 促销活动路由
app.use('/api/v1/promotion', promotionRoutes);
// 商户路由
app.use('/api/v1/merchants', merchantRoutes);
}
// 404处理

View File

@@ -48,6 +48,12 @@ const query = async (sql, params = []) => {
let connection;
try {
connection = await pool.getConnection();
// 添加调试信息
console.log('执行SQL:', sql);
console.log('参数:', params);
console.log('参数类型:', params.map(p => typeof p));
const [results] = await connection.execute(sql, params);
connection.release();
return results;
@@ -55,6 +61,9 @@ const query = async (sql, params = []) => {
if (connection) {
connection.release();
}
console.error('SQL执行错误:', error);
console.error('SQL语句:', sql);
console.error('参数:', params);
throw error;
}
};

View File

@@ -249,45 +249,45 @@ const getDashboardStatistics = async () => {
const todayEnd = new Date(todayStart.getTime() + 24 * 60 * 60 * 1000);
// 总用户数
const [totalUsersResult] = await query('SELECT COUNT(*) as count FROM users WHERE deleted_at IS NULL');
const [totalUsersResult] = await query('SELECT COUNT(*) as count FROM users');
const totalUsers = totalUsersResult.count;
// 今日新增用户
const [todayUsersResult] = await query(
'SELECT COUNT(*) as count FROM users WHERE created_at >= ? AND created_at < ? AND deleted_at IS NULL',
'SELECT COUNT(*) as count FROM users WHERE created_at >= ? AND created_at < ?',
[todayStart, todayEnd]
);
const todayNewUsers = todayUsersResult.count;
// 总动物数
const [totalAnimalsResult] = await query('SELECT COUNT(*) as count FROM animals WHERE deleted_at IS NULL');
const [totalAnimalsResult] = await query('SELECT COUNT(*) as count FROM animals');
const totalAnimals = totalAnimalsResult.count;
// 今日新增动物
const [todayAnimalsResult] = await query(
'SELECT COUNT(*) as count FROM animals WHERE created_at >= ? AND created_at < ? AND deleted_at IS NULL',
'SELECT COUNT(*) as count FROM animals WHERE created_at >= ? AND created_at < ?',
[todayStart, todayEnd]
);
const todayNewAnimals = todayAnimalsResult.count;
// 总旅行数
const [totalTravelsResult] = await query('SELECT COUNT(*) as count FROM travels WHERE deleted_at IS NULL');
const [totalTravelsResult] = await query('SELECT COUNT(*) as count FROM travels');
const totalTravels = totalTravelsResult.count;
// 今日新增旅行
const [todayTravelsResult] = await query(
'SELECT COUNT(*) as count FROM travels WHERE created_at >= ? AND created_at < ? AND deleted_at IS NULL',
'SELECT COUNT(*) as count FROM travels WHERE created_at >= ? AND created_at < ?',
[todayStart, todayEnd]
);
const todayNewTravels = todayTravelsResult.count;
// 总认领数
const [totalClaimsResult] = await query('SELECT COUNT(*) as count FROM animal_claims WHERE deleted_at IS NULL');
const [totalClaimsResult] = await query('SELECT COUNT(*) as count FROM animal_claims');
const totalClaims = totalClaimsResult.count;
// 今日新增认领
const [todayClaimsResult] = await query(
'SELECT COUNT(*) as count FROM animal_claims WHERE created_at >= ? AND created_at < ? AND deleted_at IS NULL',
'SELECT COUNT(*) as count FROM animal_claims WHERE created_at >= ? AND created_at < ?',
[todayStart, todayEnd]
);
const todayNewClaims = todayClaimsResult.count;
@@ -327,7 +327,7 @@ const getRecentActivities = async () => {
const recentUsers = await query(`
SELECT id, nickname, created_at
FROM users
WHERE deleted_at IS NULL
WHERE status != 'banned'
ORDER BY created_at DESC
LIMIT 5
`);
@@ -348,8 +348,7 @@ const getRecentActivities = async () => {
const recentAnimals = await query(`
SELECT a.id, a.name, a.created_at, u.id as user_id, u.nickname as user_nickname
FROM animals a
LEFT JOIN users u ON a.user_id = u.id
WHERE a.deleted_at IS NULL
LEFT JOIN users u ON a.farmer_id = u.id
ORDER BY a.created_at DESC
LIMIT 5
`);

View File

@@ -6,13 +6,13 @@ class AnimalController {
// 获取动物列表
static async getAnimals(req, res, next) {
try {
const { page, pageSize, species, status } = req.query;
const { page, pageSize, type, status } = req.query;
const result = await AnimalService.getAnimals({
merchantId: req.userId,
page: parseInt(page) || 1,
pageSize: parseInt(pageSize) || 10,
species,
type,
status
});
@@ -43,7 +43,7 @@ class AnimalController {
try {
const {
name,
species,
type,
breed,
age,
gender,
@@ -51,26 +51,30 @@ class AnimalController {
description,
images,
health_status,
vaccination_status
vaccination_records,
farm_location,
contact_info
} = req.body;
// 验证必要字段
if (!name || !species || !price) {
throw new AppError('缺少必要字段: name, species, price', 400);
if (!name || !type || !price) {
throw new AppError('缺少必要字段: name, type, price', 400);
}
const animalData = {
merchant_id: req.userId,
name,
species,
type,
breed: breed || null,
age: age || null,
gender: gender || null,
price: parseFloat(price),
description: description || null,
images: images || null,
images: images || [],
health_status: health_status || null,
vaccination_status: vaccination_status || null,
vaccination_records: vaccination_records || [],
farm_location: farm_location || null,
contact_info: contact_info || {},
status: 'available'
};

View File

@@ -0,0 +1,276 @@
const Merchant = require('../models/Merchant');
/**
* 获取商户列表
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function getMerchantList(req, res, next) {
try {
const {
page = 1,
limit = 20,
keyword = '',
status = '',
type = ''
} = req.query;
// 参数验证
const pageNum = parseInt(page);
const limitNum = parseInt(limit);
if (pageNum < 1) {
return res.status(400).json({
success: false,
message: '页码必须大于0'
});
}
if (limitNum < 1 || limitNum > 100) {
return res.status(400).json({
success: false,
message: '每页数量必须在1-100之间'
});
}
const options = {
page: pageNum,
limit: limitNum,
keyword: keyword.trim(),
status,
type
};
const result = await Merchant.getMerchantList(options);
res.json({
success: true,
data: result.merchants,
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 getMerchantDetail(req, res, next) {
try {
const { merchantId } = req.params;
if (!merchantId || isNaN(merchantId)) {
return res.status(400).json({
success: false,
message: '商户ID无效'
});
}
const merchant = await Merchant.getMerchantDetail(parseInt(merchantId));
if (!merchant) {
return res.status(404).json({
success: false,
message: '商户不存在'
});
}
res.json({
success: true,
data: merchant
});
} catch (error) {
console.error('获取商户详情控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '获取商户详情失败'
});
}
}
/**
* 创建商户
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function createMerchant(req, res, next) {
try {
const merchantData = req.body;
// 验证必要字段
const requiredFields = ['name', 'type', 'contact_person', 'contact_phone'];
for (const field of requiredFields) {
if (!merchantData[field]) {
return res.status(400).json({
success: false,
message: `缺少必要字段: ${field}`
});
}
}
// 验证商户类型
if (!['individual', 'company'].includes(merchantData.type)) {
return res.status(400).json({
success: false,
message: '商户类型必须是 individual 或 company'
});
}
const merchant = await Merchant.create(merchantData);
res.status(201).json({
success: true,
data: merchant,
message: '商户创建成功'
});
} catch (error) {
console.error('创建商户控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '创建商户失败'
});
}
}
/**
* 更新商户信息
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function updateMerchant(req, res, next) {
try {
const { merchantId } = req.params;
const merchantData = req.body;
if (!merchantId || isNaN(merchantId)) {
return res.status(400).json({
success: false,
message: '商户ID无效'
});
}
// 检查商户是否存在
const existingMerchant = await Merchant.findById(parseInt(merchantId));
if (!existingMerchant) {
return res.status(404).json({
success: false,
message: '商户不存在'
});
}
// 验证商户类型(如果提供)
if (merchantData.type && !['individual', 'company'].includes(merchantData.type)) {
return res.status(400).json({
success: false,
message: '商户类型必须是 individual 或 company'
});
}
// 验证状态(如果提供)
if (merchantData.status && !['active', 'inactive', 'banned'].includes(merchantData.status)) {
return res.status(400).json({
success: false,
message: '商户状态必须是 active、inactive 或 banned'
});
}
const updatedMerchant = await Merchant.update(parseInt(merchantId), merchantData);
res.json({
success: true,
data: updatedMerchant,
message: '商户信息更新成功'
});
} catch (error) {
console.error('更新商户控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '更新商户失败'
});
}
}
/**
* 删除商户
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function deleteMerchant(req, res, next) {
try {
const { merchantId } = req.params;
if (!merchantId || isNaN(merchantId)) {
return res.status(400).json({
success: false,
message: '商户ID无效'
});
}
// 检查商户是否存在
const existingMerchant = await Merchant.findById(parseInt(merchantId));
if (!existingMerchant) {
return res.status(404).json({
success: false,
message: '商户不存在'
});
}
const deleted = await Merchant.delete(parseInt(merchantId));
if (deleted) {
res.json({
success: true,
message: '商户删除成功'
});
} else {
res.status(500).json({
success: false,
message: '删除商户失败'
});
}
} catch (error) {
console.error('删除商户控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '删除商户失败'
});
}
}
/**
* 获取商户统计信息
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
async function getMerchantStatistics(req, res, next) {
try {
const statistics = await Merchant.getStatistics();
res.json({
success: true,
data: statistics
});
} catch (error) {
console.error('获取商户统计控制器错误:', error);
res.status(500).json({
success: false,
message: error.message || '获取商户统计失败'
});
}
}
module.exports = {
getMerchantList,
getMerchantDetail,
createMerchant,
updateMerchant,
deleteMerchant,
getMerchantStatistics
};

View File

@@ -0,0 +1,461 @@
/**
* 推广活动控制器
* @module controllers/promotion/activityController
*/
const db = require('../../config/database');
/**
* 获取推广活动列表
* @function getActivities
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.getActivities = async (req, res, next) => {
try {
const {
page = 1,
pageSize = 20,
name = '',
status = ''
} = req.query;
const offset = (page - 1) * pageSize;
const limit = parseInt(pageSize);
let whereConditions = [];
let queryParams = [];
if (name) {
whereConditions.push('name LIKE ?');
queryParams.push(`%${name}%`);
}
if (status) {
whereConditions.push('status = ?');
queryParams.push(status);
}
const whereClause = whereConditions.length > 0
? `WHERE ${whereConditions.join(' AND ')}`
: '';
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM promotion_activities ${whereClause}`;
const countResult = await db.query(countSql, queryParams);
const total = countResult[0].total;
// 获取数据
const dataSql = `
SELECT
id, name, description, reward_type, reward_amount, status,
start_time, end_time, max_participants, current_participants,
created_at, updated_at
FROM promotion_activities
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`;
const dataParams = [...queryParams, limit, offset];
const activities = await db.query(dataSql, dataParams);
res.json({
success: true,
code: 200,
message: '获取成功',
data: activities,
pagination: {
current: parseInt(page),
pageSize: limit,
total,
totalPages: Math.ceil(total / limit)
}
});
} catch (error) {
next(error);
}
};
/**
* 获取推广活动详情
* @function getActivity
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.getActivity = async (req, res, next) => {
try {
const { id } = req.params;
const sql = `
SELECT
id, name, description, reward_type, reward_amount, status,
start_time, end_time, max_participants, current_participants,
created_at, updated_at
FROM promotion_activities
WHERE id = ?
`;
const activities = await db.query(sql, [id]);
if (activities.length === 0) {
return res.status(404).json({
success: false,
code: 404,
message: '推广活动不存在'
});
}
res.json({
success: true,
code: 200,
message: '获取成功',
data: activities[0]
});
} catch (error) {
next(error);
}
};
/**
* 创建推广活动
* @function createActivity
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.createActivity = async (req, res, next) => {
try {
const {
name,
description,
reward_type,
reward_amount,
status = 'upcoming',
start_time,
end_time,
max_participants = 0
} = req.body;
// 验证必填字段
if (!name || !reward_type || !reward_amount || !start_time || !end_time) {
return res.status(400).json({
success: false,
code: 400,
message: '缺少必填字段'
});
}
const sql = `
INSERT INTO promotion_activities
(name, description, reward_type, reward_amount, status, start_time, end_time, max_participants, current_participants)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)
`;
const result = await db.query(sql, [
name,
description,
reward_type,
reward_amount,
status,
start_time,
end_time,
max_participants
]);
// 获取新创建的活动
const newActivity = await db.query(
'SELECT * FROM promotion_activities WHERE id = ?',
[result.insertId]
);
res.status(201).json({
success: true,
code: 201,
message: '创建成功',
data: newActivity[0]
});
} catch (error) {
next(error);
}
};
/**
* 更新推广活动
* @function updateActivity
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.updateActivity = async (req, res, next) => {
try {
const { id } = req.params;
const updates = req.body;
// 检查活动是否存在
const existingActivity = await db.query(
'SELECT id FROM promotion_activities WHERE id = ?',
[id]
);
if (existingActivity.length === 0) {
return res.status(404).json({
success: false,
code: 404,
message: '推广活动不存在'
});
}
// 构建更新字段
const updateFields = [];
const updateValues = [];
const allowedFields = [
'name', 'description', 'reward_type', 'reward_amount',
'status', 'start_time', 'end_time', 'max_participants'
];
Object.keys(updates).forEach(key => {
if (allowedFields.includes(key) && updates[key] !== undefined) {
updateFields.push(`${key} = ?`);
updateValues.push(updates[key]);
}
});
if (updateFields.length === 0) {
return res.status(400).json({
success: false,
code: 400,
message: '没有有效的更新字段'
});
}
updateValues.push(id);
const sql = `
UPDATE promotion_activities
SET ${updateFields.join(', ')}, updated_at = NOW()
WHERE id = ?
`;
await db.query(sql, updateValues);
// 获取更新后的活动
const updatedActivity = await db.query(
'SELECT * FROM promotion_activities WHERE id = ?',
[id]
);
res.json({
success: true,
code: 200,
message: '更新成功',
data: updatedActivity[0]
});
} catch (error) {
next(error);
}
};
/**
* 删除推广活动
* @function deleteActivity
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.deleteActivity = async (req, res, next) => {
try {
const { id } = req.params;
// 检查活动是否存在
const existingActivity = await db.query(
'SELECT id FROM promotion_activities WHERE id = ?',
[id]
);
if (existingActivity.length === 0) {
return res.status(404).json({
success: false,
code: 404,
message: '推广活动不存在'
});
}
// 删除活动
await db.query('DELETE FROM promotion_activities WHERE id = ?', [id]);
res.json({
success: true,
code: 200,
message: '删除成功'
});
} catch (error) {
next(error);
}
};
/**
* 暂停推广活动
* @function pauseActivity
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.pauseActivity = async (req, res, next) => {
try {
const { id } = req.params;
// 检查活动是否存在
const existingActivity = await db.query(
'SELECT id, status FROM promotion_activities WHERE id = ?',
[id]
);
if (existingActivity.length === 0) {
return res.status(404).json({
success: false,
code: 404,
message: '推广活动不存在'
});
}
const currentStatus = existingActivity[0].status;
if (currentStatus !== 'active') {
return res.status(400).json({
success: false,
code: 400,
message: '只有活跃状态的活动可以暂停'
});
}
// 更新状态为暂停
await db.query(
'UPDATE promotion_activities SET status = "paused", updated_at = NOW() WHERE id = ?',
[id]
);
res.json({
success: true,
code: 200,
message: '暂停成功'
});
} catch (error) {
next(error);
}
};
/**
* 恢复推广活动
* @function resumeActivity
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.resumeActivity = async (req, res, next) => {
try {
const { id } = req.params;
// 检查活动是否存在
const existingActivity = await db.query(
'SELECT id, status FROM promotion_activities WHERE id = ?',
[id]
);
if (existingActivity.length === 0) {
return res.status(404).json({
success: false,
code: 404,
message: '推广活动不存在'
});
}
const currentStatus = existingActivity[0].status;
if (currentStatus !== 'paused') {
return res.status(400).json({
success: false,
code: 400,
message: '只有暂停状态的活动可以恢复'
});
}
// 更新状态为活跃
await db.query(
'UPDATE promotion_activities SET status = "active", updated_at = NOW() WHERE id = ?',
[id]
);
res.json({
success: true,
code: 200,
message: '恢复成功'
});
} catch (error) {
next(error);
}
};
/**
* 获取推广统计数据
* @function getStatistics
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.getStatistics = async (req, res, next) => {
try {
// 获取活动总数
const totalActivities = await db.query(
'SELECT COUNT(*) as count FROM promotion_activities'
);
// 获取活跃活动数
const activeActivities = await db.query(
'SELECT COUNT(*) as count FROM promotion_activities WHERE status = "active"'
);
// 获取奖励记录总数
const totalRewards = await db.query(
'SELECT COUNT(*) as count FROM promotion_rewards'
);
// 获取已发放奖励数
const issuedRewards = await db.query(
'SELECT COUNT(*) as count FROM promotion_rewards WHERE status = "issued"'
);
// 获取奖励总金额
const totalAmount = await db.query(`
SELECT COALESCE(SUM(
CASE
WHEN reward_type = 'cash' THEN reward_amount
WHEN reward_type = 'points' THEN reward_amount * 0.01 -- 假设1积分=0.01元
ELSE 0
END
), 0) as total_amount
FROM promotion_rewards
WHERE status = 'issued'
`);
const statistics = {
total_activities: totalActivities[0].count,
active_activities: activeActivities[0].count,
total_rewards: totalRewards[0].count,
issued_rewards: issuedRewards[0].count,
total_amount: parseFloat(totalAmount[0].total_amount)
};
res.json({
success: true,
code: 200,
message: '获取成功',
data: statistics
});
} catch (error) {
next(error);
}
};

View File

@@ -0,0 +1,175 @@
/**
* 奖励记录控制器
* @module controllers/promotion/rewardController
*/
const db = require('../../config/database');
/**
* 获取奖励记录列表
* @function getRewards
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.getRewards = async (req, res, next) => {
try {
const {
page = 1,
pageSize = 20,
user = '',
reward_type = '',
status = ''
} = req.query;
const offset = (page - 1) * pageSize;
const limit = parseInt(pageSize);
let whereConditions = [];
let queryParams = [];
if (user) {
whereConditions.push('(user_name LIKE ? OR user_phone LIKE ?)');
queryParams.push(`%${user}%`, `%${user}%`);
}
if (reward_type) {
whereConditions.push('reward_type = ?');
queryParams.push(reward_type);
}
if (status) {
whereConditions.push('status = ?');
queryParams.push(status);
}
const whereClause = whereConditions.length > 0
? `WHERE ${whereConditions.join(' AND ')}`
: '';
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM promotion_rewards ${whereClause}`;
const countResult = await db.query(countSql, queryParams);
const total = countResult[0].total;
// 获取数据
const dataSql = `
SELECT
id, user_id, user_name, user_phone, activity_id, activity_name,
reward_type, reward_amount, status, issued_at, created_at
FROM promotion_rewards
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`;
const dataParams = [...queryParams, limit, offset];
const rewards = await db.query(dataSql, dataParams);
res.json({
success: true,
code: 200,
message: '获取成功',
data: rewards,
pagination: {
current: parseInt(page),
pageSize: limit,
total,
totalPages: Math.ceil(total / limit)
}
});
} catch (error) {
next(error);
}
};
/**
* 发放奖励
* @function issueReward
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
* @param {Function} next - Express中间件next函数
*/
exports.issueReward = async (req, res, next) => {
try {
const { id } = req.params;
// 检查奖励记录是否存在
const reward = await db.query(
'SELECT * FROM promotion_rewards WHERE id = ?',
[id]
);
if (reward.length === 0) {
return res.status(404).json({
success: false,
code: 404,
message: '奖励记录不存在'
});
}
const rewardData = reward[0];
if (rewardData.status !== 'pending') {
return res.status(400).json({
success: false,
code: 400,
message: '只有待发放状态的奖励可以发放'
});
}
// 更新奖励状态为已发放
await db.query(
'UPDATE promotion_rewards SET status = "issued", issued_at = NOW() WHERE id = ?',
[id]
);
// 根据奖励类型执行相应的发放逻辑
try {
switch (rewardData.reward_type) {
case 'cash':
// 现金奖励发放逻辑
// 这里可以集成支付系统或记录到用户账户
console.log(`发放现金奖励: ${rewardData.reward_amount}元给用户 ${rewardData.user_name}`);
break;
case 'points':
// 积分奖励发放逻辑
// 这里可以更新用户积分
console.log(`发放积分奖励: ${rewardData.reward_amount}积分给用户 ${rewardData.user_name}`);
break;
case 'coupon':
// 优惠券发放逻辑
// 这里可以生成优惠券并关联到用户
console.log(`发放优惠券奖励给用户 ${rewardData.user_name}`);
break;
default:
console.log(`未知奖励类型: ${rewardData.reward_type}`);
}
} catch (distributionError) {
// 如果发放失败,回滚奖励状态
await db.query(
'UPDATE promotion_rewards SET status = "failed" WHERE id = ?',
[id]
);
console.error('奖励发放失败:', distributionError);
return res.status(500).json({
success: false,
code: 500,
message: '奖励发放失败'
});
}
res.json({
success: true,
code: 200,
message: '奖励发放成功'
});
} catch (error) {
next(error);
}
};

View File

@@ -41,33 +41,49 @@ class TravelController {
static async createTravelPlan(req, res, next) {
try {
const {
title,
description,
destination,
start_date,
end_date,
budget,
companions,
transportation,
accommodation,
activities,
notes
max_participants,
price_per_person,
itinerary,
requirements,
includes,
excludes,
images
} = req.body;
if (!destination || !start_date || !end_date) {
throw new AppError('目的地、开始日期结束日期不能为空', 400);
if (!title || !destination || !start_date || !end_date || !price_per_person) {
throw new AppError('标题、目的地、开始日期结束日期和价格不能为空', 400);
}
const planId = await TravelService.createTravelPlan(req.userId, {
const planData = {
title,
description: description || null,
destination,
start_date,
end_date,
budget,
companions,
transportation,
accommodation,
activities,
notes
max_participants: max_participants || null,
price_per_person: parseFloat(price_per_person),
itinerary: itinerary || [],
requirements: requirements || null,
includes: includes || [],
excludes: excludes || [],
images: images || []
};
// 调试:检查传递给服务层的数据
console.log('Plan Data:', planData);
Object.keys(planData).forEach(key => {
if (planData[key] === undefined) {
console.log(`Field ${key} is undefined`);
}
});
const planId = await TravelService.createTravelPlan(req.userId, planData);
const plan = await TravelService.getTravelPlanById(planId);
res.status(201).json(success({

View File

@@ -42,12 +42,13 @@ class Animal {
const query = `
SELECT
a.*,
type,
m.name as merchant_name,
m.contact_phone as merchant_phone
FROM animals a
LEFT JOIN merchants m ON a.merchant_id = m.id
WHERE 1=1 ${whereClause}
ORDER BY a.${sortBy} ${sortOrder}
WHERE ${whereClause}
ORDER BY a.created_at DESC
LIMIT ? OFFSET ?
`;
@@ -92,6 +93,7 @@ class Animal {
const query = `
SELECT
a.*,
a.type,
m.name as merchant_name,
m.contact_phone as merchant_phone,
m.address as merchant_address
@@ -194,27 +196,26 @@ class Animal {
}
/**
* 获取按物种分类的统计
* 获取动物统计信息(按类型分组)
* @returns {Array} 统计信息
*/
static async getAnimalStatsBySpecies() {
static async getAnimalStatsByType() {
try {
const query = `
SELECT
species,
type,
COUNT(*) as count,
COUNT(CASE WHEN status = 'available' THEN 1 END) as available_count,
COUNT(CASE WHEN status = 'claimed' THEN 1 END) as claimed_count,
AVG(price) as avg_price
FROM animals
GROUP BY species
WHERE status = 'available'
GROUP BY type
ORDER BY count DESC
`;
const [rows] = await db.execute(query);
return rows;
} catch (error) {
console.error('获取按物种分类的统计失败:', error);
console.error('获取动物统计信息失败:', error);
throw error;
}
}
@@ -341,28 +342,35 @@ class Animal {
try {
const {
name,
species,
type,
breed,
age,
gender,
weight,
price,
description,
image_url,
health_status,
vaccination_records,
images,
merchant_id,
farm_location,
contact_info,
status = 'available'
} = animalData;
const query = `
INSERT INTO animals (
name, species, breed, age, gender, weight, price,
description, image_url, merchant_id, status, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
name, type, breed, age, gender, weight, price,
description, health_status, vaccination_records, images,
merchant_id, farm_location, contact_info, status, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
`;
const [result] = await db.execute(query, [
name, species, breed, age, gender, weight, price,
description, image_url, merchant_id, status
name, type, breed, age, gender, weight, price,
description, health_status, JSON.stringify(vaccination_records || []),
JSON.stringify(images || []), merchant_id, farm_location,
JSON.stringify(contact_info || {}), status
]);
return { id: result.insertId, ...animalData };

View File

@@ -0,0 +1,217 @@
const { query } = require('../config/database');
/**
* 商户模型类
* 处理商户相关的数据库操作
*/
class Merchant {
// 根据ID查找商户
static async findById(id) {
try {
const sql = 'SELECT * FROM merchants WHERE id = ?';
const [rows] = await query(sql, [id]);
return rows.length > 0 ? rows[0] : null;
} catch (error) {
console.error('查找商户失败:', error);
throw error;
}
}
// 获取商户列表(支持分页和筛选)
static async getMerchantList(options = {}) {
try {
const {
page = 1,
limit = 20,
keyword = '',
status = '',
type = ''
} = options;
const offset = (page - 1) * limit;
let whereConditions = [];
let params = [];
// 构建查询条件
if (keyword) {
whereConditions.push('(name LIKE ? OR contact_person LIKE ? OR contact_phone LIKE ?)');
const keywordPattern = `%${keyword}%`;
params.push(keywordPattern, keywordPattern, keywordPattern);
}
if (status) {
whereConditions.push('status = ?');
params.push(status);
}
if (type) {
whereConditions.push('type = ?');
params.push(type);
}
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
// 查询总数
const countSql = `SELECT COUNT(*) as total FROM merchants ${whereClause}`;
const [countResult] = await query(countSql, params);
const total = countResult[0].total;
// 查询数据
const dataSql = `
SELECT * FROM merchants
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`;
const [rows] = await query(dataSql, [...params, limit, offset]);
return {
data: rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
totalPages: Math.ceil(total / limit)
}
};
} catch (error) {
console.error('获取商户列表失败:', error);
throw error;
}
}
// 创建商户
static async create(merchantData) {
try {
const {
name,
type,
contact_person,
contact_phone,
email = null,
address = null,
description = null
} = merchantData;
const sql = `
INSERT INTO merchants (name, type, contact_person, contact_phone, email, address, description, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, 'active', NOW(), NOW())
`;
const params = [name, type, contact_person, contact_phone, email, address, description];
const [result] = await query(sql, params);
// 返回创建的商户信息
return await this.findById(result.insertId);
} catch (error) {
console.error('创建商户失败:', error);
throw error;
}
}
// 更新商户信息
static async update(id, merchantData) {
try {
const updateFields = [];
const params = [];
// 动态构建更新字段
const allowedFields = ['name', 'type', 'contact_person', 'contact_phone', 'email', 'address', 'description', 'status'];
for (const field of allowedFields) {
if (merchantData[field] !== undefined) {
updateFields.push(`${field} = ?`);
params.push(merchantData[field]);
}
}
if (updateFields.length === 0) {
throw new Error('没有提供要更新的字段');
}
updateFields.push('updated_at = NOW()');
params.push(id);
const sql = `UPDATE merchants SET ${updateFields.join(', ')} WHERE id = ?`;
const [result] = await query(sql, params);
if (result.affectedRows === 0) {
throw new Error('商户不存在或更新失败');
}
// 返回更新后的商户信息
return await this.findById(id);
} catch (error) {
console.error('更新商户失败:', error);
throw error;
}
}
// 删除商户
static async delete(id) {
try {
const sql = 'DELETE FROM merchants WHERE id = ?';
const [result] = await query(sql, [id]);
if (result.affectedRows === 0) {
throw new Error('商户不存在或删除失败');
}
return true;
} catch (error) {
console.error('删除商户失败:', error);
throw error;
}
}
// 获取商户详情(包含统计信息)
static async getDetailWithStats(id) {
try {
const merchant = await this.findById(id);
if (!merchant) {
return null;
}
// 获取关联的动物数量
const animalCountSql = 'SELECT COUNT(*) as count FROM animals WHERE merchant_id = ?';
const [animalResult] = await query(animalCountSql, [id]);
// 获取关联的订单数量
const orderCountSql = 'SELECT COUNT(*) as count FROM orders WHERE merchant_id = ?';
const [orderResult] = await query(orderCountSql, [id]);
return {
...merchant,
animal_count: animalResult[0].count,
order_count: orderResult[0].count
};
} catch (error) {
console.error('获取商户详情失败:', error);
throw error;
}
}
// 获取商户统计信息
static async getStatistics() {
try {
const sql = `
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active,
SUM(CASE WHEN status = 'inactive' THEN 1 ELSE 0 END) as inactive,
SUM(CASE WHEN status = 'banned' THEN 1 ELSE 0 END) as banned,
SUM(CASE WHEN type = 'individual' THEN 1 ELSE 0 END) as individual,
SUM(CASE WHEN type = 'company' THEN 1 ELSE 0 END) as company
FROM merchants
`;
const [rows] = await query(sql);
return rows[0];
} catch (error) {
console.error('获取商户统计信息失败:', error);
throw error;
}
}
}
module.exports = Merchant;

View File

@@ -0,0 +1,427 @@
const { query } = require('../config/database');
/**
* 旅行计划数据模型
* 处理旅行计划相关的数据库操作
*/
class Travel {
/**
* 创建旅行计划
* @param {Object} travelData - 旅行计划数据
* @returns {Promise<Object>} 创建的旅行计划
*/
static async create(travelData) {
const {
title,
destination,
description,
start_date,
end_date,
max_participants,
price_per_person,
includes,
excludes,
itinerary,
images,
requirements,
created_by
} = travelData;
const query = `
INSERT INTO travel_plans
(title, destination, description, start_date, end_date, max_participants,
current_participants, price_per_person, includes, excludes, itinerary,
images, requirements, created_by, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, 'draft', NOW(), NOW())
`;
const [result] = await query(query, [
title,
destination,
description || null,
start_date,
end_date,
max_participants || 10,
price_per_person,
JSON.stringify(includes || []),
JSON.stringify(excludes || []),
JSON.stringify(itinerary || []),
JSON.stringify(images || []),
requirements || null,
created_by
]);
return this.findById(result.insertId);
}
/**
* 根据ID查找旅行计划
* @param {number} id - 旅行计划ID
* @returns {Promise<Object|null>} 旅行计划信息
*/
static async findById(id) {
const query = `
SELECT
tp.*,
u.username as creator_name,
u.avatar as creator_avatar,
u.phone as creator_phone
FROM travel_plans tp
LEFT JOIN users u ON tp.created_by = u.id
WHERE tp.id = ?
`;
const [rows] = await query(query, [id]);
if (rows.length === 0) {
return null;
}
const travel = rows[0];
// 解析JSON字段
if (travel.includes) {
travel.includes = JSON.parse(travel.includes);
}
if (travel.excludes) {
travel.excludes = JSON.parse(travel.excludes);
}
if (travel.itinerary) {
travel.itinerary = JSON.parse(travel.itinerary);
}
if (travel.images) {
travel.images = JSON.parse(travel.images);
}
return travel;
}
/**
* 获取旅行计划列表
* @param {Object} options - 查询选项
* @returns {Promise<Array>} 旅行计划列表
*/
static async findAll(options = {}) {
const {
page = 1,
limit = 10,
destination,
status,
created_by,
start_date,
end_date,
sort_by = 'created_at',
sort_order = 'DESC'
} = options;
let whereClause = 'WHERE 1=1';
const params = [];
if (destination) {
whereClause += ' AND tp.destination LIKE ?';
params.push(`%${destination}%`);
}
if (status) {
whereClause += ' AND tp.status = ?';
params.push(status);
}
if (created_by) {
whereClause += ' AND tp.created_by = ?';
params.push(created_by);
}
if (start_date) {
whereClause += ' AND tp.start_date >= ?';
params.push(start_date);
}
if (end_date) {
whereClause += ' AND tp.end_date <= ?';
params.push(end_date);
}
const offset = (page - 1) * limit;
const query = `
SELECT
tp.*,
u.username as creator_name,
u.avatar as creator_avatar
FROM travel_plans tp
LEFT JOIN users u ON tp.created_by = u.id
${whereClause}
ORDER BY tp.${sort_by} ${sort_order}
LIMIT ? OFFSET ?
`;
const [rows] = await query(query, [...params, limit, offset]);
// 解析JSON字段
return rows.map(travel => {
if (travel.includes) {
travel.includes = JSON.parse(travel.includes);
}
if (travel.excludes) {
travel.excludes = JSON.parse(travel.excludes);
}
if (travel.itinerary) {
travel.itinerary = JSON.parse(travel.itinerary);
}
if (travel.images) {
travel.images = JSON.parse(travel.images);
}
return travel;
});
}
/**
* 获取旅行计划总数
* @param {Object} options - 查询选项
* @returns {Promise<number>} 总数
*/
static async count(options = {}) {
const {
destination,
status,
created_by,
start_date,
end_date
} = options;
let whereClause = 'WHERE 1=1';
const params = [];
if (destination) {
whereClause += ' AND destination LIKE ?';
params.push(`%${destination}%`);
}
if (status) {
whereClause += ' AND status = ?';
params.push(status);
}
if (created_by) {
whereClause += ' AND created_by = ?';
params.push(created_by);
}
if (start_date) {
whereClause += ' AND start_date >= ?';
params.push(start_date);
}
if (end_date) {
whereClause += ' AND end_date <= ?';
params.push(end_date);
}
const query = `SELECT COUNT(*) as count FROM travel_plans ${whereClause}`;
const [rows] = await query(query, params);
return rows[0].count;
}
/**
* 更新旅行计划
* @param {number} id - 旅行计划ID
* @param {Object} updateData - 更新数据
* @returns {Promise<Object|null>} 更新后的旅行计划
*/
static async update(id, updateData) {
const {
title,
destination,
description,
start_date,
end_date,
max_participants,
price_per_person,
includes,
excludes,
itinerary,
images,
requirements,
status
} = updateData;
const fields = [];
const params = [];
if (title !== undefined) {
fields.push('title = ?');
params.push(title);
}
if (destination !== undefined) {
fields.push('destination = ?');
params.push(destination);
}
if (description !== undefined) {
fields.push('description = ?');
params.push(description);
}
if (start_date !== undefined) {
fields.push('start_date = ?');
params.push(start_date);
}
if (end_date !== undefined) {
fields.push('end_date = ?');
params.push(end_date);
}
if (max_participants !== undefined) {
fields.push('max_participants = ?');
params.push(max_participants);
}
if (price_per_person !== undefined) {
fields.push('price_per_person = ?');
params.push(price_per_person);
}
if (includes !== undefined) {
fields.push('includes = ?');
params.push(JSON.stringify(includes));
}
if (excludes !== undefined) {
fields.push('excludes = ?');
params.push(JSON.stringify(excludes));
}
if (itinerary !== undefined) {
fields.push('itinerary = ?');
params.push(JSON.stringify(itinerary));
}
if (images !== undefined) {
fields.push('images = ?');
params.push(JSON.stringify(images));
}
if (requirements !== undefined) {
fields.push('requirements = ?');
params.push(requirements);
}
if (status !== undefined) {
fields.push('status = ?');
params.push(status);
}
if (fields.length === 0) {
return this.findById(id);
}
fields.push('updated_at = NOW()');
params.push(id);
const query = `UPDATE travel_plans SET ${fields.join(', ')} WHERE id = ?`;
await query(query, params);
return this.findById(id);
}
/**
* 删除旅行计划
* @param {number} id - 旅行计划ID
* @returns {Promise<boolean>} 是否删除成功
*/
static async delete(id) {
const query = 'DELETE FROM travel_plans WHERE id = ?';
const [result] = await query(query, [id]);
return result.affectedRows > 0;
}
/**
* 增加参与人数
* @param {number} id - 旅行计划ID
* @param {number} count - 增加的人数
* @returns {Promise<boolean>} 是否更新成功
*/
static async incrementParticipants(id, count = 1) {
const query = `
UPDATE travel_plans
SET current_participants = current_participants + ?, updated_at = NOW()
WHERE id = ? AND current_participants + ? <= max_participants
`;
const [result] = await query(query, [count, id, count]);
return result.affectedRows > 0;
}
/**
* 减少参与人数
* @param {number} id - 旅行计划ID
* @param {number} count - 减少的人数
* @returns {Promise<boolean>} 是否更新成功
*/
static async decrementParticipants(id, count = 1) {
const query = `
UPDATE travel_plans
SET current_participants = GREATEST(0, current_participants - ?), updated_at = NOW()
WHERE id = ?
`;
const [result] = await query(sql, [count, id]);
return result.affectedRows > 0;
}
/**
* 检查是否可以报名
* @param {number} id - 旅行计划ID
* @returns {Promise<boolean>} 是否可以报名
*/
static async canRegister(id) {
const query = `
SELECT
current_participants < max_participants as can_register,
status = 'published' as is_published,
start_date > NOW() as not_started
FROM travel_plans
WHERE id = ?
`;
const [rows] = await query(sql, [id]);
if (rows.length === 0) {
return false;
}
const { can_register, is_published, not_started } = rows[0];
return can_register && is_published && not_started;
}
/**
* 获取热门旅行计划
* @param {number} limit - 限制数量
* @returns {Promise<Array>} 热门旅行计划列表
*/
static async getPopular(limit = 10) {
const query = `
SELECT
tp.*,
u.username as creator_name,
u.avatar as creator_avatar,
COUNT(tr.id) as registration_count
FROM travel_plans tp
LEFT JOIN users u ON tp.created_by = u.id
LEFT JOIN travel_registrations tr ON tp.id = tr.travel_plan_id AND tr.status = 'approved'
WHERE tp.status = 'published' AND tp.start_date > NOW()
GROUP BY tp.id
ORDER BY registration_count DESC, tp.created_at DESC
LIMIT ?
`;
const [rows] = await query(sql, [limit]);
// 解析JSON字段
return rows.map(travel => {
if (travel.includes) {
travel.includes = JSON.parse(travel.includes);
}
if (travel.excludes) {
travel.excludes = JSON.parse(travel.excludes);
}
if (travel.itinerary) {
travel.itinerary = JSON.parse(travel.itinerary);
}
if (travel.images) {
travel.images = JSON.parse(travel.images);
}
return travel;
});
}
}
module.exports = Travel;

View File

@@ -36,6 +36,9 @@ class UserMySQL {
// 根据ID查找用户
static async findById(id) {
if (id === undefined || id === null) {
throw new Error('User ID cannot be undefined or null');
}
const sql = 'SELECT * FROM users WHERE id = ?';
const rows = await query(sql, [id]);
return rows[0] || null;

View File

@@ -1,6 +1,7 @@
const express = require('express')
const { body } = require('express-validator')
const authController = require('../controllers/authControllerMySQL')
const { authenticateUser } = require('../middleware/auth')
const router = express.Router()
@@ -169,7 +170,7 @@ router.post(
* 500:
* description: 服务器内部错误
*/
router.get('/me', authController.getCurrentUser)
router.get('/me', authenticateUser, authController.getCurrentUser)
/**
* @swagger
@@ -224,7 +225,7 @@ router.get('/me', authController.getCurrentUser)
* 500:
* description: 服务器内部错误
*/
router.put('/profile', authController.updateProfile)
router.put('/profile', authenticateUser, authController.updateProfile)
/**
* @swagger
@@ -271,6 +272,7 @@ router.put('/profile', authController.updateProfile)
*/
router.put(
'/password',
authenticateUser,
[
body('currentPassword').notEmpty().withMessage('当前密码不能为空'),
body('newPassword').isLength({ min: 6 }).withMessage('新密码长度不能少于6位')

View File

@@ -0,0 +1,453 @@
const express = require('express');
const { body, query, param } = require('express-validator');
const MerchantController = require('../controllers/merchant');
const { authenticateUser, requireRole } = require('../middleware/auth');
const router = express.Router();
/**
* @swagger
* tags:
* name: Merchants
* description: 商户管理相关接口
*/
/**
* @swagger
* components:
* schemas:
* Merchant:
* type: object
* properties:
* id:
* type: integer
* description: 商户ID
* name:
* type: string
* description: 商户名称
* type:
* type: string
* enum: [individual, company]
* description: 商户类型
* contact_person:
* type: string
* description: 联系人
* contact_phone:
* type: string
* description: 联系电话
* email:
* type: string
* description: 邮箱
* address:
* type: string
* description: 地址
* description:
* type: string
* description: 描述
* status:
* type: string
* enum: [active, inactive, banned]
* description: 状态
* created_at:
* type: string
* format: date-time
* description: 创建时间
* updated_at:
* type: string
* format: date-time
* description: 更新时间
*/
/**
* @swagger
* /merchants:
* get:
* summary: 获取商户列表
* tags: [Merchants]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* minimum: 1
* maximum: 100
* default: 20
* description: 每页数量
* - in: query
* name: keyword
* schema:
* type: string
* description: 搜索关键词(商户名称、联系人、电话)
* - in: query
* name: status
* schema:
* type: string
* enum: [active, inactive, banned]
* description: 状态筛选
* - in: query
* name: type
* schema:
* type: string
* enum: [individual, company]
* description: 类型筛选
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: array
* items:
* $ref: '#/components/schemas/Merchant'
* pagination:
* type: object
* properties:
* page:
* type: integer
* limit:
* type: integer
* total:
* type: integer
* totalPages:
* type: integer
* 400:
* description: 请求参数错误
* 500:
* description: 服务器内部错误
*/
router.get('/',
[
query('page').optional().isInt({ min: 1 }).withMessage('页码必须是正整数'),
query('limit').optional().isInt({ min: 1, max: 100 }).withMessage('每页数量必须在1-100之间'),
query('status').optional().isIn(['active', 'inactive', 'banned']).withMessage('状态值无效'),
query('type').optional().isIn(['individual', 'company']).withMessage('类型值无效')
],
MerchantController.getMerchantList
);
/**
* @swagger
* /merchants/{merchantId}:
* get:
* summary: 获取商户详情
* tags: [Merchants]
* parameters:
* - in: path
* name: merchantId
* required: true
* schema:
* type: integer
* description: 商户ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* allOf:
* - $ref: '#/components/schemas/Merchant'
* - type: object
* properties:
* animal_count:
* type: integer
* description: 动物数量
* order_count:
* type: integer
* description: 订单数量
* 400:
* description: 商户ID无效
* 404:
* description: 商户不存在
* 500:
* description: 服务器内部错误
*/
router.get('/:merchantId',
[
param('merchantId').isInt({ min: 1 }).withMessage('商户ID必须是正整数')
],
MerchantController.getMerchantDetail
);
/**
* @swagger
* /merchants:
* post:
* summary: 创建商户
* tags: [Merchants]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - type
* - contact_person
* - contact_phone
* properties:
* name:
* type: string
* description: 商户名称
* type:
* type: string
* enum: [individual, company]
* description: 商户类型
* contact_person:
* type: string
* description: 联系人
* contact_phone:
* type: string
* description: 联系电话
* email:
* type: string
* description: 邮箱
* address:
* type: string
* description: 地址
* description:
* type: string
* description: 描述
* responses:
* 201:
* description: 创建成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* $ref: '#/components/schemas/Merchant'
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.post('/',
authenticateUser,
requireRole(['admin', 'super_admin']),
[
body('name').notEmpty().withMessage('商户名称不能为空'),
body('type').isIn(['individual', 'company']).withMessage('商户类型必须是 individual 或 company'),
body('contact_person').notEmpty().withMessage('联系人不能为空'),
body('contact_phone').notEmpty().withMessage('联系电话不能为空'),
body('email').optional().isEmail().withMessage('邮箱格式无效')
],
MerchantController.createMerchant
);
/**
* @swagger
* /merchants/{merchantId}:
* put:
* summary: 更新商户信息
* tags: [Merchants]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: merchantId
* required: true
* schema:
* type: integer
* description: 商户ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* description: 商户名称
* type:
* type: string
* enum: [individual, company]
* description: 商户类型
* contact_person:
* type: string
* description: 联系人
* contact_phone:
* type: string
* description: 联系电话
* email:
* type: string
* description: 邮箱
* address:
* type: string
* description: 地址
* description:
* type: string
* description: 描述
* status:
* type: string
* enum: [active, inactive, banned]
* description: 状态
* responses:
* 200:
* description: 更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* $ref: '#/components/schemas/Merchant'
* message:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 404:
* description: 商户不存在
* 500:
* description: 服务器内部错误
*/
router.put('/:merchantId',
authenticateUser,
requireRole(['admin', 'super_admin']),
[
param('merchantId').isInt({ min: 1 }).withMessage('商户ID必须是正整数'),
body('name').optional().notEmpty().withMessage('商户名称不能为空'),
body('type').optional().isIn(['individual', 'company']).withMessage('商户类型必须是 individual 或 company'),
body('contact_person').optional().notEmpty().withMessage('联系人不能为空'),
body('contact_phone').optional().notEmpty().withMessage('联系电话不能为空'),
body('email').optional().isEmail().withMessage('邮箱格式无效'),
body('status').optional().isIn(['active', 'inactive', 'banned']).withMessage('状态值无效')
],
MerchantController.updateMerchant
);
/**
* @swagger
* /merchants/{merchantId}:
* delete:
* summary: 删除商户
* tags: [Merchants]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: merchantId
* required: true
* schema:
* type: integer
* description: 商户ID
* responses:
* 200:
* description: 删除成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 400:
* description: 商户ID无效
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 404:
* description: 商户不存在
* 500:
* description: 服务器内部错误
*/
router.delete('/:merchantId',
authenticateUser,
requireRole(['admin', 'super_admin']),
[
param('merchantId').isInt({ min: 1 }).withMessage('商户ID必须是正整数')
],
MerchantController.deleteMerchant
);
/**
* @swagger
* /merchants/statistics:
* get:
* summary: 获取商户统计信息
* tags: [Merchants]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* total:
* type: integer
* description: 总商户数
* active:
* type: integer
* description: 活跃商户数
* inactive:
* type: integer
* description: 非活跃商户数
* banned:
* type: integer
* description: 被禁用商户数
* individual:
* type: integer
* description: 个人商户数
* company:
* type: integer
* description: 企业商户数
* 401:
* description: 未授权
* 403:
* description: 权限不足
* 500:
* description: 服务器内部错误
*/
router.get('/statistics',
authenticateUser,
requireRole(['admin', 'super_admin']),
MerchantController.getMerchantStatistics
);
module.exports = router;

View File

@@ -0,0 +1,61 @@
/**
* 推广活动路由
* @module routes/promotion
*/
const express = require('express');
const router = express.Router();
const activityController = require('../controllers/promotion/activityController');
const rewardController = require('../controllers/promotion/rewardController');
/**
* @route GET /api/v1/promotion/activities
* @description 获取推广活动列表
* @access Public
*/
router.get('/activities', activityController.getActivities);
/**
* @route GET /api/v1/promotion/activities/:id
* @description 获取推广活动详情
* @access Public
*/
router.get('/activities/:id', activityController.getActivity);
/**
* @route POST /api/v1/promotion/activities
* @description 创建推广活动
* @access Private
*/
router.post('/activities', activityController.createActivity);
/**
* @route PUT /api/v1/promotion/activities/:id
* @description 更新推广活动
* @access Private
*/
router.put('/activities/:id', activityController.updateActivity);
/**
* @route DELETE /api/v1/promotion/activities/:id
* @description 删除推广活动
* @access Private
*/
router.delete('/activities/:id', activityController.deleteActivity);
/**
* @route GET /api/v1/promotion/rewards
* @description 获取奖励记录列表
* @access Private
*/
router.get('/rewards', rewardController.getRewards);
/**
* @route POST /api/v1/promotion/rewards/:id/issue
* @description 发放奖励
* @access Private
*/
router.post('/rewards/:id/issue', rewardController.issueReward);
module.exports = router;

View File

@@ -1,7 +1,7 @@
const express = require('express');
const { body, query } = require('express-validator');
const UserController = require('../controllers/user');
const { authenticateUser, requireRole: requireAdmin } = require('../middleware/auth');
const { authenticateUser, authenticateAdmin, requireRole: requireAdmin } = require('../middleware/auth');
const router = express.Router();
@@ -179,7 +179,7 @@ router.put('/profile', authenticateUser, UserController.updateProfile);
* description: 服务器内部错误
*/
router.get('/',
authenticateUser,
authenticateAdmin,
requireAdmin(['admin', 'super_admin']),
UserController.getUsers
);
@@ -223,7 +223,7 @@ router.get('/',
* 500:
* description: 服务器内部错误
*/
router.get('/:userId', authenticateUser, requireAdmin(['admin', 'super_admin']), UserController.getUserById);
router.get('/:userId', authenticateAdmin, requireAdmin(['admin', 'super_admin']), UserController.getUserById);
/**
* @swagger
@@ -273,7 +273,7 @@ router.get('/:userId', authenticateUser, requireAdmin(['admin', 'super_admin']),
* 500:
* description: 服务器内部错误
*/
router.get('/statistics', authenticateUser, requireAdmin(['admin', 'super_admin']), UserController.getUserStatistics);
router.get('/statistics', authenticateAdmin, requireAdmin(['admin', 'super_admin']), UserController.getUserStatistics);
/**
* @swagger
@@ -329,7 +329,7 @@ router.get('/statistics', authenticateUser, requireAdmin(['admin', 'super_admin'
* description: 服务器内部错误
*/
router.post('/batch-status',
authenticateUser,
authenticateAdmin,
requireAdmin(['admin', 'super_admin']),
[
body('userIds').isArray().withMessage('userIds必须是数组'),
@@ -379,6 +379,6 @@ router.post('/batch-status',
* 500:
* description: 服务器内部错误
*/
router.delete('/:userId', authenticateUser, requireAdmin(['admin', 'super_admin']), UserController.deleteUser);
router.delete('/:userId', authenticateAdmin, requireAdmin(['admin', 'super_admin']), UserController.deleteUser);
module.exports = router;

View File

@@ -5,7 +5,7 @@ class AnimalService {
// 获取动物列表
static async getAnimals(searchParams) {
try {
const { merchantId, species, status, page = 1, pageSize = 10 } = searchParams;
const { merchantId, type, status, page = 1, pageSize = 10 } = searchParams;
const offset = (page - 1) * pageSize;
let sql = `
@@ -22,9 +22,9 @@ class AnimalService {
params.push(merchantId);
}
if (species) {
sql += ' AND a.species = ?';
params.push(species);
if (type) {
sql += ' AND a.type = ?';
params.push(type);
}
if (status) {
@@ -85,18 +85,26 @@ class AnimalService {
try {
const sql = `
INSERT INTO animals (
merchant_id, name, species, breed, birth_date, personality, farm_location, price, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
merchant_id, name, type, breed, age, gender, weight, price,
description, health_status, vaccination_records, images,
farm_location, contact_info, status, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
`;
const params = [
animalData.merchant_id,
animalData.name,
animalData.species,
animalData.type,
animalData.breed,
animalData.birth_date,
animalData.personality,
animalData.farm_location,
animalData.age,
animalData.gender,
animalData.weight,
animalData.price,
animalData.description,
animalData.health_status,
JSON.stringify(animalData.vaccination_records || []),
JSON.stringify(animalData.images || []),
animalData.farm_location,
JSON.stringify(animalData.contact_info || {}),
animalData.status || 'available'
];

View File

@@ -6,36 +6,41 @@ class TravelService {
static async getTravelPlans(searchParams) {
try {
const { userId, page = 1, pageSize = 10, status } = searchParams;
const offset = (page - 1) * pageSize;
const offset = (parseInt(page) - 1) * parseInt(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
`;
// 构建基础查询条件
let whereClause = 'WHERE 1=1';
const params = [];
if (userId) {
sql += ' AND tp.user_id = ?';
whereClause += ' AND tp.created_by = ?';
params.push(userId);
}
if (status) {
sql += ' AND tp.status = ?';
whereClause += ' AND tp.status = ?';
params.push(status);
}
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM (${sql}) as count_query`;
const countSql = `SELECT COUNT(*) as total FROM travel_plans tp ${whereClause}`;
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);
// 获取数据 - 使用简单的查询方式
const dataSql = `
SELECT tp.*, u.username, u.real_name, u.avatar_url
FROM travel_plans tp
INNER JOIN users u ON tp.created_by = u.id
${whereClause}
ORDER BY tp.created_at DESC
LIMIT ${parseInt(pageSize)} OFFSET ${parseInt(offset)}
`;
console.log('执行SQL:', dataSql);
console.log('参数:', params);
const plans = await query(dataSql, params);
return {
plans: plans.map(plan => this.sanitizePlan(plan)),
@@ -57,7 +62,7 @@ class TravelService {
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
INNER JOIN users u ON tp.created_by = u.id
WHERE tp.id = ?
`;
@@ -76,37 +81,53 @@ class TravelService {
static async createTravelPlan(userId, planData) {
try {
const {
title,
description,
destination,
start_date,
end_date,
budget,
companions,
transportation,
accommodation,
activities,
notes
max_participants,
price_per_person,
itinerary,
requirements,
includes,
excludes,
images
} = 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())
created_by, title, description, destination, start_date, end_date,
max_participants, price_per_person, itinerary,
requirements, includes, excludes, images,
status, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'published', NOW(), NOW())
`;
const params = [
userId,
title,
description || null,
destination,
start_date,
end_date,
budget,
companions,
transportation,
accommodation,
activities,
notes
max_participants || 20,
price_per_person,
JSON.stringify(itinerary || []),
requirements || null,
JSON.stringify(includes || []),
JSON.stringify(excludes || []),
JSON.stringify(images || [])
];
// 调试检查参数中是否有undefined
console.log('SQL Parameters:', params);
params.forEach((param, index) => {
if (param === undefined) {
console.log(`Parameter at index ${index} is undefined`);
}
});
const result = await query(sql, params);
return result.insertId;
} catch (error) {
@@ -118,8 +139,9 @@ class TravelService {
static async updateTravelPlan(planId, userId, updateData) {
try {
const allowedFields = [
'destination', 'start_date', 'end_date', 'budget', 'companions',
'transportation', 'accommodation', 'activities', 'notes', 'status'
'title', 'description', 'destination', 'start_date', 'end_date',
'max_participants', 'price_per_person', 'includes', 'excludes',
'itinerary', 'images', 'requirements', 'status'
];
const setClauses = [];
@@ -127,8 +149,13 @@ class TravelService {
for (const [key, value] of Object.entries(updateData)) {
if (allowedFields.includes(key) && value !== undefined) {
setClauses.push(`${key} = ?`);
params.push(value);
if (Array.isArray(value)) {
setClauses.push(`${key} = ?`);
params.push(JSON.stringify(value));
} else {
setClauses.push(`${key} = ?`);
params.push(value);
}
}
}
@@ -142,7 +169,7 @@ class TravelService {
const sql = `
UPDATE travel_plans
SET ${setClauses.join(', ')}
WHERE id = ? AND user_id = ?
WHERE id = ? AND created_by = ?
`;
const result = await query(sql, params);
@@ -159,7 +186,7 @@ class TravelService {
// 删除旅行计划
static async deleteTravelPlan(planId, userId) {
try {
const sql = 'DELETE FROM travel_plans WHERE id = ? AND user_id = ?';
const sql = 'DELETE FROM travel_plans WHERE id = ? AND created_by = ?';
const result = await query(sql, [planId, userId]);
if (result.affectedRows === 0) {
@@ -181,9 +208,9 @@ class TravelService {
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
SUM(price_per_person * max_participants) as total_budget
FROM travel_plans
WHERE user_id = ?
WHERE created_by = ?
`;
const stats = await query(sql, [userId]);

View File

@@ -96,8 +96,7 @@ class UserService {
const total = countResult[0].total;
// 添加分页和排序
sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
params.push(pageSize, offset);
sql += ` ORDER BY created_at DESC LIMIT ${parseInt(pageSize)} OFFSET ${parseInt(offset)}`;
const users = await UserMySQL.query(sql, params);