修改文件结构,统一文档格式
This commit is contained in:
81
backend/tools/README.md
Normal file
81
backend/tools/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 后端工具目录
|
||||
|
||||
本目录包含用于开发、测试、数据管理和系统维护的各种工具脚本。
|
||||
|
||||
## 📂 目录结构
|
||||
|
||||
```
|
||||
tools/
|
||||
├── data-management/ # 数据管理和维护脚本
|
||||
├── testing/ # 测试相关脚本
|
||||
├── verification/ # 数据验证和检查脚本
|
||||
└── README.md # 本文档
|
||||
```
|
||||
|
||||
## 🔧 数据管理脚本 (`data-management/`)
|
||||
|
||||
包含用于数据库操作、数据导入导出、数据修复等的脚本:
|
||||
|
||||
- **创建和生成**: `create-*.js`, `generate-*.js`
|
||||
- **导入和插入**: `import-*.js`, `insert-*.js`
|
||||
- **修复和更新**: `fix-*.js`, `update-*.js`, `reset_*.js`
|
||||
- **重新排序**: `reorder-*.js`, `simple-*.js`
|
||||
- **数据恢复**: `restore-*.js`
|
||||
- **清理操作**: `cleanup-*.js`
|
||||
|
||||
## 🧪 测试脚本 (`testing/`)
|
||||
|
||||
包含各种功能测试、API测试、集成测试脚本:
|
||||
|
||||
- **API测试**: `test-*-api.js`
|
||||
- **数据库测试**: `test-db-*.js`, `test-simple-db.js`
|
||||
- **功能测试**: `test-*-flow.js`, `test-*-binding.js`
|
||||
- **工具测试**: `test_*.js`
|
||||
|
||||
## ✅ 验证脚本 (`verification/`)
|
||||
|
||||
包含数据验证、系统检查、分析工具:
|
||||
|
||||
- **数据检查**: `check-*.js`, `check_*.js`
|
||||
- **数据验证**: `verify-*.js`
|
||||
- **系统分析**: `analyze-*.js`
|
||||
- **统计计数**: `count-*.js`
|
||||
|
||||
## 📝 使用说明
|
||||
|
||||
### 运行脚本
|
||||
```bash
|
||||
# 从backend目录运行
|
||||
cd backend
|
||||
|
||||
# 运行数据管理脚本
|
||||
node tools/data-management/import-farms-static-data.js
|
||||
|
||||
# 运行测试脚本
|
||||
node tools/testing/test-db-connection.js
|
||||
|
||||
# 运行验证脚本
|
||||
node tools/verification/check-current-data.js
|
||||
```
|
||||
|
||||
### 环境要求
|
||||
- Node.js 18.0+
|
||||
- 正确配置的 .env 文件
|
||||
- MySQL 数据库连接
|
||||
|
||||
### ⚠️ 注意事项
|
||||
|
||||
1. **生产环境谨慎**: 数据管理脚本可能会修改数据库,在生产环境使用前请务必备份
|
||||
2. **依赖检查**: 运行前确保所有依赖已安装 (`npm install`)
|
||||
3. **权限要求**: 某些脚本需要数据库管理员权限
|
||||
4. **日志记录**: 重要操作会生成日志,请注意查看
|
||||
|
||||
### 🔗 相关文档
|
||||
|
||||
- [开发指南](../../DEVELOPMENT.md)
|
||||
- [故障排除](../../TROUBLESHOOTING.md)
|
||||
- [API文档](../../API.md)
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2025年1月*
|
||||
36
backend/tools/data-management/cleanup-temp-tables.js
Normal file
36
backend/tools/data-management/cleanup-temp-tables.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 清理临时表
|
||||
* @file cleanup-temp-tables.js
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
async function cleanupTempTables() {
|
||||
try {
|
||||
console.log('清理临时表...');
|
||||
|
||||
const tables = await sequelize.query(
|
||||
"SHOW TABLES LIKE '%_temp_reorder'",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
for (const table of tables) {
|
||||
const tableName = Object.values(table)[0];
|
||||
console.log('删除临时表:', tableName);
|
||||
await sequelize.query(`DROP TABLE ${tableName}`);
|
||||
}
|
||||
|
||||
console.log('清理完成');
|
||||
} catch (error) {
|
||||
console.error('清理失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
cleanupTempTables();
|
||||
}
|
||||
|
||||
module.exports = { cleanupTempTables };
|
||||
156
backend/tools/data-management/create-environment-schedule.js
Normal file
156
backend/tools/data-management/create-environment-schedule.js
Normal file
@@ -0,0 +1,156 @@
|
||||
const sequelize = require('./config/database');
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
// 定义环境监测时刻表模型
|
||||
const EnvironmentSchedule = sequelize.define('EnvironmentSchedule', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
farm_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '农场ID'
|
||||
},
|
||||
device_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '设备ID'
|
||||
},
|
||||
schedule_time: {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: false,
|
||||
comment: '监测时刻(HH:MM:SS)'
|
||||
},
|
||||
temperature: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
comment: '温度值(摄氏度)'
|
||||
},
|
||||
humidity: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
comment: '湿度值(百分比)'
|
||||
},
|
||||
monitoring_date: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '监测日期'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive', 'maintenance'),
|
||||
defaultValue: 'active',
|
||||
comment: '监测状态'
|
||||
},
|
||||
notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注信息'
|
||||
}
|
||||
}, {
|
||||
tableName: 'environment_schedules',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['farm_id', 'monitoring_date', 'schedule_time']
|
||||
},
|
||||
{
|
||||
fields: ['device_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
async function createEnvironmentScheduleTable() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 创建表
|
||||
await EnvironmentSchedule.sync({ force: true });
|
||||
console.log('环境监测时刻表创建成功');
|
||||
|
||||
// 生成示例数据
|
||||
const scheduleData = [];
|
||||
const today = new Date();
|
||||
const schedules = [
|
||||
'06:00:00', '08:00:00', '10:00:00', '12:00:00',
|
||||
'14:00:00', '16:00:00', '18:00:00', '20:00:00'
|
||||
];
|
||||
|
||||
// 为过去7天生成数据
|
||||
for (let day = 0; day < 7; day++) {
|
||||
const monitoringDate = new Date(today);
|
||||
monitoringDate.setDate(today.getDate() - day);
|
||||
|
||||
schedules.forEach(time => {
|
||||
// 农场1的数据
|
||||
scheduleData.push({
|
||||
farm_id: 1,
|
||||
device_id: 1,
|
||||
schedule_time: time,
|
||||
temperature: (18 + Math.random() * 15).toFixed(2), // 18-33度
|
||||
humidity: (45 + Math.random() * 35).toFixed(2), // 45-80%
|
||||
monitoring_date: monitoringDate.toISOString().split('T')[0],
|
||||
status: 'active',
|
||||
notes: `定时监测数据 - ${time}`
|
||||
});
|
||||
|
||||
// 农场2的数据(如果存在)
|
||||
scheduleData.push({
|
||||
farm_id: 2,
|
||||
device_id: 2,
|
||||
schedule_time: time,
|
||||
temperature: (16 + Math.random() * 18).toFixed(2), // 16-34度
|
||||
humidity: (40 + Math.random() * 40).toFixed(2), // 40-80%
|
||||
monitoring_date: monitoringDate.toISOString().split('T')[0],
|
||||
status: 'active',
|
||||
notes: `定时监测数据 - ${time}`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 批量插入数据
|
||||
await EnvironmentSchedule.bulkCreate(scheduleData);
|
||||
console.log(`成功插入 ${scheduleData.length} 条环境监测时刻数据`);
|
||||
|
||||
// 验证数据
|
||||
const totalCount = await EnvironmentSchedule.count();
|
||||
console.log(`环境监测时刻表总记录数: ${totalCount}`);
|
||||
|
||||
const todayCount = await EnvironmentSchedule.count({
|
||||
where: {
|
||||
monitoring_date: today.toISOString().split('T')[0]
|
||||
}
|
||||
});
|
||||
console.log(`今日监测记录数: ${todayCount}`);
|
||||
|
||||
// 显示部分数据样例
|
||||
const sampleData = await EnvironmentSchedule.findAll({
|
||||
limit: 5,
|
||||
order: [['monitoring_date', 'DESC'], ['schedule_time', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('\n数据样例:');
|
||||
sampleData.forEach(record => {
|
||||
console.log(`日期: ${record.monitoring_date}, 时间: ${record.schedule_time}, 温度: ${record.temperature}°C, 湿度: ${record.humidity}%`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建环境监测时刻表失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 导出模型和创建函数
|
||||
module.exports = {
|
||||
EnvironmentSchedule,
|
||||
createEnvironmentScheduleTable
|
||||
};
|
||||
|
||||
// 如果直接运行此文件,则执行创建操作
|
||||
if (require.main === module) {
|
||||
createEnvironmentScheduleTable();
|
||||
}
|
||||
69
backend/tools/data-management/create-sensor-data.js
Normal file
69
backend/tools/data-management/create-sensor-data.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const { SensorData } = require('./models');
|
||||
const sequelize = require('./config/database');
|
||||
|
||||
async function createSensorData() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 创建过去24小时的温度和湿度数据
|
||||
const now = new Date();
|
||||
const sensorDataList = [];
|
||||
|
||||
// 生成过去24小时的数据,每小时一条记录
|
||||
for (let i = 23; i >= 0; i--) {
|
||||
const timestamp = new Date(now.getTime() - i * 60 * 60 * 1000);
|
||||
|
||||
// 温度数据 (20-30度之间随机)
|
||||
const temperature = 20 + Math.random() * 10;
|
||||
sensorDataList.push({
|
||||
device_id: 1,
|
||||
farm_id: 1,
|
||||
sensor_type: 'temperature',
|
||||
value: parseFloat(temperature.toFixed(1)),
|
||||
unit: '°C',
|
||||
timestamp: timestamp,
|
||||
created_at: timestamp,
|
||||
updated_at: timestamp
|
||||
});
|
||||
|
||||
// 湿度数据 (50-80%之间随机)
|
||||
const humidity = 50 + Math.random() * 30;
|
||||
sensorDataList.push({
|
||||
device_id: 1,
|
||||
farm_id: 1,
|
||||
sensor_type: 'humidity',
|
||||
value: parseFloat(humidity.toFixed(1)),
|
||||
unit: '%',
|
||||
timestamp: timestamp,
|
||||
created_at: timestamp,
|
||||
updated_at: timestamp
|
||||
});
|
||||
}
|
||||
|
||||
// 批量插入数据
|
||||
await SensorData.bulkCreate(sensorDataList);
|
||||
console.log(`成功创建 ${sensorDataList.length} 条传感器数据`);
|
||||
|
||||
// 验证数据
|
||||
const count = await SensorData.count();
|
||||
console.log(`传感器数据总数: ${count}`);
|
||||
|
||||
const temperatureCount = await SensorData.count({
|
||||
where: { sensor_type: 'temperature' }
|
||||
});
|
||||
console.log(`温度数据条数: ${temperatureCount}`);
|
||||
|
||||
const humidityCount = await SensorData.count({
|
||||
where: { sensor_type: 'humidity' }
|
||||
});
|
||||
console.log(`湿度数据条数: ${humidityCount}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建传感器数据失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
createSensorData();
|
||||
@@ -0,0 +1,121 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
// 数据库配置
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_NAME || 'nxxmdata'
|
||||
};
|
||||
|
||||
async function createEnvironmentScheduleTable() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
// 创建数据库连接
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 创建环境监测时刻表
|
||||
const createTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS environment_schedules (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
farm_id INT NOT NULL COMMENT '农场ID',
|
||||
device_id INT NOT NULL COMMENT '设备ID',
|
||||
schedule_time TIME NOT NULL COMMENT '监测时刻(HH:MM:SS)',
|
||||
temperature DECIMAL(5,2) NULL COMMENT '温度值(摄氏度)',
|
||||
humidity DECIMAL(5,2) NULL COMMENT '湿度值(百分比)',
|
||||
monitoring_date DATE NOT NULL DEFAULT (CURRENT_DATE) COMMENT '监测日期',
|
||||
status ENUM('active', 'inactive', 'maintenance') DEFAULT 'active' COMMENT '监测状态',
|
||||
notes TEXT NULL COMMENT '备注信息',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_farm_date_time (farm_id, monitoring_date, schedule_time),
|
||||
INDEX idx_device (device_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='环境监测时刻表';
|
||||
`;
|
||||
|
||||
await connection.execute(createTableSQL);
|
||||
console.log('环境监测时刻表创建成功');
|
||||
|
||||
// 生成示例数据
|
||||
const schedules = [
|
||||
'06:00:00', '08:00:00', '10:00:00', '12:00:00',
|
||||
'14:00:00', '16:00:00', '18:00:00', '20:00:00'
|
||||
];
|
||||
|
||||
const insertSQL = `
|
||||
INSERT INTO environment_schedules
|
||||
(farm_id, device_id, schedule_time, temperature, humidity, monitoring_date, status, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
let totalInserted = 0;
|
||||
const today = new Date();
|
||||
|
||||
// 为过去7天生成数据
|
||||
for (let day = 0; day < 7; day++) {
|
||||
const monitoringDate = new Date(today);
|
||||
monitoringDate.setDate(today.getDate() - day);
|
||||
const dateStr = monitoringDate.toISOString().split('T')[0];
|
||||
|
||||
for (const time of schedules) {
|
||||
// 农场1的数据
|
||||
const temp1 = (18 + Math.random() * 15).toFixed(2);
|
||||
const humidity1 = (45 + Math.random() * 35).toFixed(2);
|
||||
|
||||
await connection.execute(insertSQL, [
|
||||
1, 1, time, temp1, humidity1, dateStr, 'active', `定时监测数据 - ${time}`
|
||||
]);
|
||||
totalInserted++;
|
||||
|
||||
// 农场2的数据
|
||||
const temp2 = (16 + Math.random() * 18).toFixed(2);
|
||||
const humidity2 = (40 + Math.random() * 40).toFixed(2);
|
||||
|
||||
await connection.execute(insertSQL, [
|
||||
2, 2, time, temp2, humidity2, dateStr, 'active', `定时监测数据 - ${time}`
|
||||
]);
|
||||
totalInserted++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`成功插入 ${totalInserted} 条环境监测时刻数据`);
|
||||
|
||||
// 验证数据
|
||||
const [countResult] = await connection.execute(
|
||||
'SELECT COUNT(*) as total FROM environment_schedules'
|
||||
);
|
||||
console.log(`环境监测时刻表总记录数: ${countResult[0].total}`);
|
||||
|
||||
const [todayResult] = await connection.execute(
|
||||
'SELECT COUNT(*) as today_count FROM environment_schedules WHERE monitoring_date = CURDATE()'
|
||||
);
|
||||
console.log(`今日监测记录数: ${todayResult[0].today_count}`);
|
||||
|
||||
// 显示部分数据样例
|
||||
const [sampleData] = await connection.execute(`
|
||||
SELECT monitoring_date, schedule_time, temperature, humidity
|
||||
FROM environment_schedules
|
||||
ORDER BY monitoring_date DESC, schedule_time ASC
|
||||
LIMIT 5
|
||||
`);
|
||||
|
||||
console.log('\n数据样例:');
|
||||
sampleData.forEach(record => {
|
||||
console.log(`日期: ${record.monitoring_date}, 时间: ${record.schedule_time}, 温度: ${record.temperature}°C, 湿度: ${record.humidity}%`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建环境监测时刻表失败:', error.message);
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
console.log('数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行创建操作
|
||||
createEnvironmentScheduleTable();
|
||||
@@ -0,0 +1,171 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
|
||||
// SQLite数据库文件路径
|
||||
const dbPath = path.join(__dirname, 'environment_schedule.db');
|
||||
|
||||
async function createEnvironmentScheduleTable() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建或连接到SQLite数据库
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error('数据库连接失败:', err.message);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
console.log('SQLite数据库连接成功');
|
||||
});
|
||||
|
||||
// 创建环境监测时刻表
|
||||
const createTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS environment_schedules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
farm_id INTEGER NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
schedule_time TEXT NOT NULL,
|
||||
temperature REAL,
|
||||
humidity REAL,
|
||||
monitoring_date TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'active',
|
||||
notes TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`;
|
||||
|
||||
db.run(createTableSQL, (err) => {
|
||||
if (err) {
|
||||
console.error('创建表失败:', err.message);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
console.log('环境监测时刻表创建成功');
|
||||
|
||||
// 生成示例数据
|
||||
const schedules = [
|
||||
'06:00:00', '08:00:00', '10:00:00', '12:00:00',
|
||||
'14:00:00', '16:00:00', '18:00:00', '20:00:00'
|
||||
];
|
||||
|
||||
const insertSQL = `
|
||||
INSERT INTO environment_schedules
|
||||
(farm_id, device_id, schedule_time, temperature, humidity, monitoring_date, status, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
let totalInserted = 0;
|
||||
const today = new Date();
|
||||
const insertPromises = [];
|
||||
|
||||
// 为过去7天生成数据
|
||||
for (let day = 0; day < 7; day++) {
|
||||
const monitoringDate = new Date(today);
|
||||
monitoringDate.setDate(today.getDate() - day);
|
||||
const dateStr = monitoringDate.toISOString().split('T')[0];
|
||||
|
||||
for (const time of schedules) {
|
||||
// 农场1的数据
|
||||
const temp1 = (18 + Math.random() * 15).toFixed(2);
|
||||
const humidity1 = (45 + Math.random() * 35).toFixed(2);
|
||||
|
||||
insertPromises.push(new Promise((resolve, reject) => {
|
||||
db.run(insertSQL, [
|
||||
1, 1, time, temp1, humidity1, dateStr, 'active', `定时监测数据 - ${time}`
|
||||
], function(err) {
|
||||
if (err) reject(err);
|
||||
else {
|
||||
totalInserted++;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
// 农场2的数据
|
||||
const temp2 = (16 + Math.random() * 18).toFixed(2);
|
||||
const humidity2 = (40 + Math.random() * 40).toFixed(2);
|
||||
|
||||
insertPromises.push(new Promise((resolve, reject) => {
|
||||
db.run(insertSQL, [
|
||||
2, 2, time, temp2, humidity2, dateStr, 'active', `定时监测数据 - ${time}`
|
||||
], function(err) {
|
||||
if (err) reject(err);
|
||||
else {
|
||||
totalInserted++;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有插入操作完成
|
||||
Promise.all(insertPromises)
|
||||
.then(() => {
|
||||
console.log(`成功插入 ${totalInserted} 条环境监测时刻数据`);
|
||||
|
||||
// 验证数据
|
||||
db.get('SELECT COUNT(*) as total FROM environment_schedules', (err, row) => {
|
||||
if (err) {
|
||||
console.error('查询总数失败:', err.message);
|
||||
} else {
|
||||
console.log(`环境监测时刻表总记录数: ${row.total}`);
|
||||
}
|
||||
|
||||
// 查询今日数据
|
||||
const todayStr = today.toISOString().split('T')[0];
|
||||
db.get(
|
||||
'SELECT COUNT(*) as today_count FROM environment_schedules WHERE monitoring_date = ?',
|
||||
[todayStr],
|
||||
(err, row) => {
|
||||
if (err) {
|
||||
console.error('查询今日数据失败:', err.message);
|
||||
} else {
|
||||
console.log(`今日监测记录数: ${row.today_count}`);
|
||||
}
|
||||
|
||||
// 显示部分数据样例
|
||||
db.all(`
|
||||
SELECT monitoring_date, schedule_time, temperature, humidity
|
||||
FROM environment_schedules
|
||||
ORDER BY monitoring_date DESC, schedule_time ASC
|
||||
LIMIT 5
|
||||
`, (err, rows) => {
|
||||
if (err) {
|
||||
console.error('查询样例数据失败:', err.message);
|
||||
} else {
|
||||
console.log('\n数据样例:');
|
||||
rows.forEach(record => {
|
||||
console.log(`日期: ${record.monitoring_date}, 时间: ${record.schedule_time}, 温度: ${record.temperature}°C, 湿度: ${record.humidity}%`);
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭数据库连接
|
||||
db.close((err) => {
|
||||
if (err) {
|
||||
console.error('关闭数据库失败:', err.message);
|
||||
} else {
|
||||
console.log('数据库连接已关闭');
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('插入数据失败:', err.message);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 执行创建操作
|
||||
createEnvironmentScheduleTable()
|
||||
.then(() => {
|
||||
console.log('环境监测时刻表创建完成');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('操作失败:', err.message);
|
||||
});
|
||||
71
backend/tools/data-management/create-test-sensor-data.js
Normal file
71
backend/tools/data-management/create-test-sensor-data.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const { SensorData } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log('开始创建测试传感器数据...');
|
||||
|
||||
// 清除现有数据
|
||||
await SensorData.destroy({ where: {} });
|
||||
console.log('已清除现有传感器数据');
|
||||
|
||||
// 创建过去24小时的测试数据
|
||||
const testData = [];
|
||||
const now = new Date();
|
||||
|
||||
// 生成24小时的数据,每小时一个数据点
|
||||
for (let i = 23; i >= 0; i--) {
|
||||
const time = new Date(now.getTime() - i * 60 * 60 * 1000);
|
||||
|
||||
// 温度数据 (20-30度之间波动)
|
||||
testData.push({
|
||||
device_id: 2,
|
||||
farm_id: 1,
|
||||
sensor_type: 'temperature',
|
||||
value: 20 + Math.random() * 10,
|
||||
unit: '°C',
|
||||
status: 'normal',
|
||||
recorded_at: time
|
||||
});
|
||||
|
||||
// 湿度数据 (50-80%之间波动)
|
||||
testData.push({
|
||||
device_id: 3,
|
||||
farm_id: 1,
|
||||
sensor_type: 'humidity',
|
||||
value: 50 + Math.random() * 30,
|
||||
unit: '%',
|
||||
status: 'normal',
|
||||
recorded_at: time
|
||||
});
|
||||
}
|
||||
|
||||
// 批量插入数据
|
||||
await SensorData.bulkCreate(testData);
|
||||
console.log(`成功创建 ${testData.length} 条测试传感器数据`);
|
||||
|
||||
// 验证数据
|
||||
const temperatureCount = await SensorData.count({ where: { sensor_type: 'temperature' } });
|
||||
const humidityCount = await SensorData.count({ where: { sensor_type: 'humidity' } });
|
||||
|
||||
console.log(`温度数据条数: ${temperatureCount}`);
|
||||
console.log(`湿度数据条数: ${humidityCount}`);
|
||||
|
||||
// 显示最新的几条数据
|
||||
const latestData = await SensorData.findAll({
|
||||
limit: 5,
|
||||
order: [['recorded_at', 'DESC']],
|
||||
attributes: ['sensor_type', 'value', 'unit', 'recorded_at']
|
||||
});
|
||||
|
||||
console.log('\n最新的5条数据:');
|
||||
latestData.forEach(data => {
|
||||
console.log(`${data.sensor_type}: ${data.value}${data.unit} at ${data.recorded_at}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建测试数据时出错:', error);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
})();
|
||||
48
backend/tools/data-management/create-test-user.js
Normal file
48
backend/tools/data-management/create-test-user.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
const { User } = require('./models');
|
||||
|
||||
async function createTestUser() {
|
||||
try {
|
||||
console.log('连接数据库...');
|
||||
|
||||
// 手动创建用户,直接设置加密密码
|
||||
const hashedPassword = await bcrypt.hash('123456', 10);
|
||||
console.log('生成的密码哈希:', hashedPassword);
|
||||
|
||||
// 删除现有的testuser3(如果存在)
|
||||
await User.destroy({
|
||||
where: { username: 'testuser3' }
|
||||
});
|
||||
|
||||
// 直接插入数据库,绕过模型钩子
|
||||
const user = await User.create({
|
||||
username: 'testuser3',
|
||||
email: 'test3@example.com',
|
||||
password: hashedPassword,
|
||||
status: 'active'
|
||||
}, {
|
||||
hooks: false // 禁用钩子
|
||||
});
|
||||
|
||||
console.log('用户创建成功:', {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
// 验证密码
|
||||
const isValid = await bcrypt.compare('123456', user.password);
|
||||
console.log('密码验证结果:', isValid);
|
||||
|
||||
// 使用模型方法验证
|
||||
const isValidModel = await user.validPassword('123456');
|
||||
console.log('模型方法验证结果:', isValidModel);
|
||||
|
||||
} catch (error) {
|
||||
console.error('错误:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
createTestUser();
|
||||
115
backend/tools/data-management/fix-location-data.js
Normal file
115
backend/tools/data-management/fix-location-data.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 修复数据库中的location字段格式
|
||||
* 将JSON字符串转换为JSON对象
|
||||
* @file fix-location-data.js
|
||||
*/
|
||||
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function fixLocationData() {
|
||||
try {
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
|
||||
console.log('\n查询所有养殖场记录...');
|
||||
const farms = await Farm.findAll({
|
||||
attributes: ['id', 'name', 'location']
|
||||
});
|
||||
|
||||
console.log(`找到 ${farms.length} 条记录`);
|
||||
|
||||
let fixedCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for (const farm of farms) {
|
||||
try {
|
||||
console.log(`\n处理 ID: ${farm.id}, 名称: ${farm.name}`);
|
||||
console.log(`原始 location: ${JSON.stringify(farm.location)}`);
|
||||
console.log(`location 类型: ${typeof farm.location}`);
|
||||
|
||||
let needsUpdate = false;
|
||||
let newLocation = {};
|
||||
|
||||
// 检查location字段的格式
|
||||
if (typeof farm.location === 'string') {
|
||||
// 如果是字符串,尝试解析为JSON
|
||||
try {
|
||||
newLocation = JSON.parse(farm.location);
|
||||
needsUpdate = true;
|
||||
console.log(` ✅ 解析JSON字符串成功: ${JSON.stringify(newLocation)}`);
|
||||
} catch (parseError) {
|
||||
console.log(` ❌ 解析JSON字符串失败: ${parseError.message}`);
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
} else if (farm.location && typeof farm.location === 'object') {
|
||||
// 如果已经是对象,检查是否需要格式化
|
||||
newLocation = farm.location;
|
||||
console.log(` ✅ 已经是对象格式`);
|
||||
} else {
|
||||
// 如果为空或其他类型,设置为空对象
|
||||
newLocation = {};
|
||||
needsUpdate = true;
|
||||
console.log(` ⚠️ 设置为空对象`);
|
||||
}
|
||||
|
||||
// 验证经纬度数据
|
||||
if (newLocation.lng !== undefined) {
|
||||
newLocation.lng = parseFloat(newLocation.lng);
|
||||
}
|
||||
if (newLocation.lat !== undefined) {
|
||||
newLocation.lat = parseFloat(newLocation.lat);
|
||||
}
|
||||
|
||||
// 更新数据库记录
|
||||
if (needsUpdate) {
|
||||
await farm.update({ location: newLocation });
|
||||
console.log(` ✅ 更新成功: ${JSON.stringify(newLocation)}`);
|
||||
fixedCount++;
|
||||
} else {
|
||||
console.log(` ⏭️ 无需更新`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(` ❌ 处理记录 ${farm.id} 时出错:`, error.message);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log(`修复完成!`);
|
||||
console.log(`总记录数: ${farms.length}`);
|
||||
console.log(`修复成功: ${fixedCount}`);
|
||||
console.log(`修复失败: ${errorCount}`);
|
||||
console.log(`无需修复: ${farms.length - fixedCount - errorCount}`);
|
||||
|
||||
// 验证修复结果
|
||||
console.log('\n验证修复结果...');
|
||||
const verifyFarms = await Farm.findAll({
|
||||
attributes: ['id', 'name', 'location'],
|
||||
limit: 5
|
||||
});
|
||||
|
||||
verifyFarms.forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Location类型: ${typeof farm.location}, 值: ${JSON.stringify(farm.location)}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('修复失败:', error.message);
|
||||
if (error.sql) {
|
||||
console.error('SQL:', error.sql);
|
||||
}
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行修复
|
||||
if (require.main === module) {
|
||||
console.log('开始修复数据库中的location字段格式...');
|
||||
fixLocationData();
|
||||
}
|
||||
|
||||
module.exports = { fixLocationData };
|
||||
23
backend/tools/data-management/fix-migration-types.js
Normal file
23
backend/tools/data-management/fix-migration-types.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const migrationFile = path.join(__dirname, 'migrations/20230101000000_initial_schema.js');
|
||||
|
||||
// 读取迁移文件
|
||||
let content = fs.readFileSync(migrationFile, 'utf8');
|
||||
|
||||
// 替换所有的 Sequelize. 为 DataTypes.
|
||||
content = content.replace(/Sequelize\.STRING/g, 'DataTypes.STRING');
|
||||
content = content.replace(/Sequelize\.INTEGER/g, 'DataTypes.INTEGER');
|
||||
content = content.replace(/Sequelize\.TEXT/g, 'DataTypes.TEXT');
|
||||
content = content.replace(/Sequelize\.DECIMAL/g, 'DataTypes.DECIMAL');
|
||||
content = content.replace(/Sequelize\.ENUM/g, 'DataTypes.ENUM');
|
||||
content = content.replace(/Sequelize\.DATE/g, 'DataTypes.DATE');
|
||||
|
||||
// 修复literal函数
|
||||
content = content.replace(/DataTypes\.literal/g, 'literal');
|
||||
|
||||
// 写入修复后的文件
|
||||
fs.writeFileSync(migrationFile, content, 'utf8');
|
||||
|
||||
console.log('✅ 迁移文件数据类型已修复');
|
||||
95
backend/tools/data-management/fix-orphaned-foreign-keys.js
Normal file
95
backend/tools/data-management/fix-orphaned-foreign-keys.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function fixOrphanedForeignKeys() {
|
||||
try {
|
||||
console.log('修复孤立外键数据...');
|
||||
|
||||
// 首先检查是否已存在ID为12的农场
|
||||
const [existingFarm] = await sequelize.query(
|
||||
'SELECT id FROM farms WHERE id = 12'
|
||||
);
|
||||
|
||||
if (existingFarm.length === 0) {
|
||||
console.log('创建ID为12的农场记录...');
|
||||
|
||||
// 创建ID为12的农场记录
|
||||
await sequelize.query(`
|
||||
INSERT INTO farms (id, name, type, location, address, contact, phone, status, created_at, updated_at)
|
||||
VALUES (
|
||||
12,
|
||||
'西部牧场',
|
||||
'综合养殖场',
|
||||
'{"lat":36.0611,"lng":103.8343}',
|
||||
'兰州市城关区西部路12号',
|
||||
'李十二',
|
||||
'13800138012',
|
||||
'active',
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
console.log('✓ 成功创建ID为12的农场记录');
|
||||
} else {
|
||||
console.log('ID为12的农场记录已存在');
|
||||
}
|
||||
|
||||
// 验证修复结果
|
||||
const [devicesCount] = await sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM devices WHERE farm_id = 12'
|
||||
);
|
||||
|
||||
const [alertsCount] = await sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM alerts WHERE farm_id = 12'
|
||||
);
|
||||
|
||||
const [animalsCount] = await sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM animals WHERE farm_id = 12'
|
||||
);
|
||||
|
||||
console.log('\n修复后的数据统计:');
|
||||
console.log('farm_id=12的devices记录:', devicesCount[0].count);
|
||||
console.log('farm_id=12的alerts记录:', alertsCount[0].count);
|
||||
console.log('farm_id=12的animals记录:', animalsCount[0].count);
|
||||
|
||||
// 检查外键完整性
|
||||
const [orphanedDevices] = await sequelize.query(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM devices d
|
||||
LEFT JOIN farms f ON d.farm_id = f.id
|
||||
WHERE f.id IS NULL
|
||||
`);
|
||||
|
||||
const [orphanedAlerts] = await sequelize.query(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM alerts a
|
||||
LEFT JOIN farms f ON a.farm_id = f.id
|
||||
WHERE f.id IS NULL
|
||||
`);
|
||||
|
||||
const [orphanedAnimals] = await sequelize.query(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM animals an
|
||||
LEFT JOIN farms f ON an.farm_id = f.id
|
||||
WHERE f.id IS NULL
|
||||
`);
|
||||
|
||||
console.log('\n外键完整性检查:');
|
||||
console.log('孤立的devices记录:', orphanedDevices[0].count);
|
||||
console.log('孤立的alerts记录:', orphanedAlerts[0].count);
|
||||
console.log('孤立的animals记录:', orphanedAnimals[0].count);
|
||||
|
||||
if (orphanedDevices[0].count === 0 && orphanedAlerts[0].count === 0 && orphanedAnimals[0].count === 0) {
|
||||
console.log('\n✓ 外键完整性修复成功!');
|
||||
} else {
|
||||
console.log('\n⚠ 仍存在孤立外键记录,需要进一步检查');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('修复失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
fixOrphanedForeignKeys();
|
||||
35
backend/tools/data-management/fix_admin_password.js
Normal file
35
backend/tools/data-management/fix_admin_password.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
async function fixAdminPassword() {
|
||||
try {
|
||||
// 查找 admin 用户
|
||||
const admin = await User.findOne({ where: { username: 'admin' } });
|
||||
|
||||
if (!admin) {
|
||||
console.log('未找到 admin 用户');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成正确的密码哈希
|
||||
const hashedPassword = await bcrypt.hash('123456', 10);
|
||||
|
||||
// 更新密码
|
||||
await admin.update({ password: hashedPassword });
|
||||
|
||||
console.log('Admin 密码已更新为正确的哈希值');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: 123456');
|
||||
|
||||
// 验证密码
|
||||
const isValid = await bcrypt.compare('123456', hashedPassword);
|
||||
console.log('密码验证:', isValid ? '成功' : '失败');
|
||||
|
||||
} catch (error) {
|
||||
console.error('修复密码失败:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
fixAdminPassword();
|
||||
272
backend/tools/data-management/generate-test-data.js
Normal file
272
backend/tools/data-management/generate-test-data.js
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* 生成产品管理和订单管理测试数据
|
||||
* @file generate-test-data.js
|
||||
* @description 为产品和订单模块生成测试数据并插入数据库
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { Product, Order, OrderItem, User } = require('./models');
|
||||
|
||||
// 产品测试数据
|
||||
const productData = [
|
||||
{
|
||||
name: '有机苹果',
|
||||
description: '新鲜有机苹果,来自山东烟台,口感甜脆,营养丰富',
|
||||
price: 1200, // 12.00元,以分为单位
|
||||
stock: 500,
|
||||
image_url: '/images/products/apple.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '优质大米',
|
||||
description: '东北优质大米,粒粒饱满,口感香甜,5kg装',
|
||||
price: 3500, // 35.00元
|
||||
stock: 200,
|
||||
image_url: '/images/products/rice.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '新鲜鸡蛋',
|
||||
description: '农场散养鸡蛋,30枚装,营养价值高',
|
||||
price: 2800, // 28.00元
|
||||
stock: 150,
|
||||
image_url: '/images/products/eggs.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '有机蔬菜礼盒',
|
||||
description: '精选有机蔬菜组合,包含白菜、萝卜、青菜等',
|
||||
price: 4500, // 45.00元
|
||||
stock: 80,
|
||||
image_url: '/images/products/vegetable-box.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '纯天然蜂蜜',
|
||||
description: '纯天然野花蜜,500g装,无添加剂',
|
||||
price: 6800, // 68.00元
|
||||
stock: 120,
|
||||
image_url: '/images/products/honey.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '有机牛奶',
|
||||
description: '有机牧场牛奶,1L装,营养丰富',
|
||||
price: 1800, // 18.00元
|
||||
stock: 300,
|
||||
image_url: '/images/products/milk.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '农家土鸡',
|
||||
description: '农家散养土鸡,约2kg,肉质鲜美',
|
||||
price: 8500, // 85.00元
|
||||
stock: 50,
|
||||
image_url: '/images/products/chicken.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '新鲜玉米',
|
||||
description: '甜玉米,10根装,口感甜嫩',
|
||||
price: 1500, // 15.00元
|
||||
stock: 200,
|
||||
image_url: '/images/products/corn.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '有机胡萝卜',
|
||||
description: '有机胡萝卜,2kg装,营养丰富',
|
||||
price: 1200, // 12.00元
|
||||
stock: 180,
|
||||
image_url: '/images/products/carrot.jpg',
|
||||
is_active: true
|
||||
},
|
||||
{
|
||||
name: '精品茶叶',
|
||||
description: '高山绿茶,250g装,香气清雅',
|
||||
price: 12800, // 128.00元
|
||||
stock: 60,
|
||||
image_url: '/images/products/tea.jpg',
|
||||
is_active: true
|
||||
}
|
||||
];
|
||||
|
||||
// 生成随机订单数据的辅助函数
|
||||
function generateRandomOrders(users, products, orderCount = 20) {
|
||||
const orders = [];
|
||||
const orderStatuses = ['pending', 'processing', 'shipped', 'delivered', 'cancelled'];
|
||||
const paymentStatuses = ['unpaid', 'paid', 'refunded'];
|
||||
const addresses = [
|
||||
'北京市朝阳区建国路88号',
|
||||
'上海市浦东新区陆家嘴环路1000号',
|
||||
'广州市天河区珠江新城花城大道123号',
|
||||
'深圳市南山区科技园南区456号',
|
||||
'杭州市西湖区文三路789号',
|
||||
'成都市锦江区春熙路321号',
|
||||
'武汉市江汉区中山大道654号',
|
||||
'西安市雁塔区高新路987号'
|
||||
];
|
||||
|
||||
for (let i = 0; i < orderCount; i++) {
|
||||
const user = users[Math.floor(Math.random() * users.length)];
|
||||
const status = orderStatuses[Math.floor(Math.random() * orderStatuses.length)];
|
||||
const paymentStatus = paymentStatuses[Math.floor(Math.random() * paymentStatuses.length)];
|
||||
const address = addresses[Math.floor(Math.random() * addresses.length)];
|
||||
|
||||
// 创建订单基本信息
|
||||
const order = {
|
||||
user_id: user.id,
|
||||
total_amount: 0, // 稍后计算
|
||||
status: status,
|
||||
payment_status: paymentStatus,
|
||||
shipping_address: address,
|
||||
created_at: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000) // 最近30天内的随机时间
|
||||
};
|
||||
|
||||
// 为每个订单生成1-5个订单项
|
||||
const itemCount = Math.floor(Math.random() * 5) + 1;
|
||||
const orderItems = [];
|
||||
let totalAmount = 0;
|
||||
|
||||
for (let j = 0; j < itemCount; j++) {
|
||||
const product = products[Math.floor(Math.random() * products.length)];
|
||||
const quantity = Math.floor(Math.random() * 3) + 1; // 1-3个
|
||||
const price = product.price;
|
||||
|
||||
orderItems.push({
|
||||
product_id: product.id,
|
||||
quantity: quantity,
|
||||
price: price
|
||||
});
|
||||
|
||||
totalAmount += price * quantity;
|
||||
}
|
||||
|
||||
order.total_amount = totalAmount;
|
||||
order.items = orderItems;
|
||||
orders.push(order);
|
||||
}
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
// 主函数:生成并插入测试数据
|
||||
async function generateTestData() {
|
||||
try {
|
||||
console.log('开始生成测试数据...');
|
||||
|
||||
// 连接数据库
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 获取现有用户
|
||||
const users = await User.findAll();
|
||||
if (users.length === 0) {
|
||||
console.log('警告:数据库中没有用户数据,请先创建用户');
|
||||
return;
|
||||
}
|
||||
console.log(`找到 ${users.length} 个用户`);
|
||||
|
||||
// 清理现有的测试数据(可选)
|
||||
console.log('清理现有测试数据...');
|
||||
await OrderItem.destroy({ where: {} });
|
||||
await Order.destroy({ where: {} });
|
||||
await Product.destroy({ where: {} });
|
||||
console.log('清理完成');
|
||||
|
||||
// 插入产品数据
|
||||
console.log('插入产品数据...');
|
||||
const createdProducts = await Product.bulkCreate(productData);
|
||||
console.log(`成功插入 ${createdProducts.length} 个产品`);
|
||||
|
||||
// 生成订单数据
|
||||
console.log('生成订单数据...');
|
||||
const orderData = generateRandomOrders(users, createdProducts, 25);
|
||||
|
||||
// 使用事务插入订单和订单项
|
||||
console.log('插入订单和订单项数据...');
|
||||
let orderCount = 0;
|
||||
let orderItemCount = 0;
|
||||
|
||||
for (const orderInfo of orderData) {
|
||||
await sequelize.transaction(async (t) => {
|
||||
// 创建订单
|
||||
const order = await Order.create({
|
||||
user_id: orderInfo.user_id,
|
||||
total_amount: orderInfo.total_amount,
|
||||
status: orderInfo.status,
|
||||
payment_status: orderInfo.payment_status,
|
||||
shipping_address: orderInfo.shipping_address,
|
||||
created_at: orderInfo.created_at
|
||||
}, { transaction: t });
|
||||
|
||||
// 创建订单项
|
||||
const orderItems = orderInfo.items.map(item => ({
|
||||
order_id: order.id,
|
||||
product_id: item.product_id,
|
||||
quantity: item.quantity,
|
||||
price: item.price
|
||||
}));
|
||||
|
||||
await OrderItem.bulkCreate(orderItems, { transaction: t });
|
||||
|
||||
orderCount++;
|
||||
orderItemCount += orderItems.length;
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`成功插入 ${orderCount} 个订单`);
|
||||
console.log(`成功插入 ${orderItemCount} 个订单项`);
|
||||
|
||||
// 显示统计信息
|
||||
console.log('\n=== 数据统计 ===');
|
||||
const productCount = await Product.count();
|
||||
const totalOrderCount = await Order.count();
|
||||
const totalOrderItemCount = await OrderItem.count();
|
||||
|
||||
console.log(`产品总数: ${productCount}`);
|
||||
console.log(`订单总数: ${totalOrderCount}`);
|
||||
console.log(`订单项总数: ${totalOrderItemCount}`);
|
||||
|
||||
// 显示一些示例数据
|
||||
console.log('\n=== 示例产品 ===');
|
||||
const sampleProducts = await Product.findAll({ limit: 3 });
|
||||
sampleProducts.forEach(product => {
|
||||
console.log(`${product.name} - ¥${(product.price / 100).toFixed(2)} - 库存: ${product.stock}`);
|
||||
});
|
||||
|
||||
console.log('\n=== 示例订单 ===');
|
||||
const sampleOrders = await Order.findAll({
|
||||
limit: 3,
|
||||
include: [
|
||||
{ model: User, as: 'user', attributes: ['username'] },
|
||||
{
|
||||
model: OrderItem,
|
||||
as: 'orderItems',
|
||||
include: [{ model: Product, as: 'product', attributes: ['name'] }]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
sampleOrders.forEach(order => {
|
||||
console.log(`订单 #${order.id} - 用户: ${order.user.username} - 总额: ¥${(order.total_amount / 100).toFixed(2)} - 状态: ${order.status}`);
|
||||
order.orderItems.forEach(item => {
|
||||
console.log(` - ${item.product.name} x ${item.quantity}`);
|
||||
});
|
||||
});
|
||||
|
||||
console.log('\n测试数据生成完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成测试数据失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行脚本
|
||||
if (require.main === module) {
|
||||
generateTestData();
|
||||
}
|
||||
|
||||
module.exports = { generateTestData, productData };
|
||||
133
backend/tools/data-management/import-alerts-sensors.js
Normal file
133
backend/tools/data-management/import-alerts-sensors.js
Normal file
@@ -0,0 +1,133 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
async function importAlertsAndSensors() {
|
||||
try {
|
||||
console.log('开始导入预警和传感器数据...');
|
||||
|
||||
// 连接数据库
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 获取所有设备
|
||||
const devices = await sequelize.query('SELECT id, farm_id, type FROM devices', { type: QueryTypes.SELECT });
|
||||
console.log(`获取到 ${devices.length} 个设备`);
|
||||
|
||||
// 1. 插入预警数据
|
||||
const alertData = [];
|
||||
const alertTypes = ['温度异常', '湿度异常', '设备故障', '动物健康', '饲料不足', '水源问题'];
|
||||
const alertLevels = ['low', 'medium', 'high', 'critical'];
|
||||
const alertStatuses = ['active', 'acknowledged', 'resolved'];
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const device = devices[Math.floor(Math.random() * devices.length)];
|
||||
const type = alertTypes[Math.floor(Math.random() * alertTypes.length)];
|
||||
const level = alertLevels[Math.floor(Math.random() * alertLevels.length)];
|
||||
const status = alertStatuses[Math.floor(Math.random() * alertStatuses.length)];
|
||||
const createdAt = new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000); // 过去30天内
|
||||
|
||||
alertData.push({
|
||||
type: type,
|
||||
level: level,
|
||||
message: `设备 ${device.type} 发生 ${type},需要及时处理`,
|
||||
status: status,
|
||||
farm_id: device.farm_id,
|
||||
device_id: device.id,
|
||||
resolved_at: status === 'resolved' ? new Date(createdAt.getTime() + Math.random() * 7 * 24 * 60 * 60 * 1000) : null,
|
||||
resolved_by: status === 'resolved' ? 1 : null,
|
||||
resolution_notes: status === 'resolved' ? '问题已解决,设备运行正常' : null,
|
||||
created_at: createdAt,
|
||||
updated_at: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
if (alertData.length > 0) {
|
||||
await sequelize.query(
|
||||
`INSERT INTO alerts (type, level, message, status, farm_id, device_id, resolved_at, resolved_by, resolution_notes, created_at, updated_at) VALUES ${alertData.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').join(', ')}`,
|
||||
{
|
||||
replacements: alertData.flatMap(alert => [
|
||||
alert.type, alert.level, alert.message, alert.status, alert.farm_id,
|
||||
alert.device_id, alert.resolved_at, alert.resolved_by, alert.resolution_notes,
|
||||
alert.created_at, alert.updated_at
|
||||
]),
|
||||
type: QueryTypes.INSERT
|
||||
}
|
||||
);
|
||||
console.log('预警数据导入成功');
|
||||
}
|
||||
|
||||
// 2. 插入传感器数据
|
||||
const sensorData = [];
|
||||
|
||||
// 为每个设备生成过去7天的传感器数据
|
||||
for (const device of devices) {
|
||||
for (let day = 0; day < 7; day++) {
|
||||
for (let hour = 0; hour < 24; hour += 2) { // 每2小时一条记录
|
||||
const timestamp = new Date();
|
||||
timestamp.setDate(timestamp.getDate() - day);
|
||||
timestamp.setHours(hour, 0, 0, 0);
|
||||
|
||||
// 为每个设备生成多个传感器数据记录
|
||||
const sensorTypes = [
|
||||
{ type: 'temperature', unit: '°C', min: 10, max: 30 },
|
||||
{ type: 'humidity', unit: '%', min: 30, max: 80 },
|
||||
{ type: 'light_intensity', unit: 'lux', min: 0, max: 1000 },
|
||||
{ type: 'air_quality', unit: 'AQI', min: 50, max: 100 },
|
||||
{ type: 'noise_level', unit: 'dB', min: 20, max: 50 },
|
||||
{ type: 'co2_level', unit: 'ppm', min: 300, max: 800 }
|
||||
];
|
||||
|
||||
for (const sensor of sensorTypes) {
|
||||
const value = Math.round((Math.random() * (sensor.max - sensor.min) + sensor.min) * 10) / 10;
|
||||
sensorData.push({
|
||||
device_id: device.id,
|
||||
farm_id: device.farm_id,
|
||||
sensor_type: sensor.type,
|
||||
value: value,
|
||||
unit: sensor.unit,
|
||||
status: Math.random() > 0.9 ? (Math.random() > 0.5 ? 'warning' : 'error') : 'normal',
|
||||
recorded_at: timestamp,
|
||||
created_at: timestamp,
|
||||
updated_at: timestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分批插入传感器数据(每次100条)
|
||||
const batchSize = 100;
|
||||
for (let i = 0; i < sensorData.length; i += batchSize) {
|
||||
const batch = sensorData.slice(i, i + batchSize);
|
||||
|
||||
await sequelize.query(
|
||||
`INSERT INTO sensor_data (device_id, farm_id, sensor_type, value, unit, status, recorded_at, created_at, updated_at) VALUES ${batch.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?)').join(', ')}`,
|
||||
{
|
||||
replacements: batch.flatMap(sensor => [
|
||||
sensor.device_id, sensor.farm_id, sensor.sensor_type, sensor.value, sensor.unit,
|
||||
sensor.status, sensor.recorded_at, sensor.created_at, sensor.updated_at
|
||||
]),
|
||||
type: QueryTypes.INSERT
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`传感器数据批次 ${Math.floor(i / batchSize) + 1} 导入成功 (${batch.length} 条记录)`);
|
||||
}
|
||||
|
||||
// 3. 验证导入结果
|
||||
const [alertCount] = await sequelize.query('SELECT COUNT(*) as count FROM alerts', { type: QueryTypes.SELECT });
|
||||
const [sensorCount] = await sequelize.query('SELECT COUNT(*) as count FROM sensor_data', { type: QueryTypes.SELECT });
|
||||
|
||||
console.log('\n=== 预警和传感器数据导入完成 ===');
|
||||
console.log(`预警总数: ${alertCount.count}`);
|
||||
console.log(`传感器数据总数: ${sensorCount.count}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('数据导入失败:', error.message);
|
||||
console.error('详细错误:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
importAlertsAndSensors();
|
||||
165
backend/tools/data-management/import-data.js
Normal file
165
backend/tools/data-management/import-data.js
Normal file
@@ -0,0 +1,165 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
async function importData() {
|
||||
try {
|
||||
console.log('开始导入数据...');
|
||||
|
||||
// 连接数据库
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 1. 插入养殖场数据
|
||||
const farmData = [
|
||||
{
|
||||
name: '东方养殖场',
|
||||
type: 'pig',
|
||||
location: JSON.stringify({ lat: 39.9042, lng: 116.4074, address: '北京市朝阳区' }),
|
||||
address: '北京市朝阳区东三环北路',
|
||||
contact: '张三',
|
||||
phone: '13800138001',
|
||||
status: 'active',
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
},
|
||||
{
|
||||
name: '西部牧场',
|
||||
type: 'cattle',
|
||||
location: JSON.stringify({ lat: 30.5728, lng: 104.0668, address: '四川省成都市' }),
|
||||
address: '四川省成都市高新区天府大道',
|
||||
contact: '李四',
|
||||
phone: '13800138002',
|
||||
status: 'active',
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
},
|
||||
{
|
||||
name: '南方羊场',
|
||||
type: 'sheep',
|
||||
location: JSON.stringify({ lat: 23.1291, lng: 113.2644, address: '广东省广州市' }),
|
||||
address: '广东省广州市天河区珠江新城',
|
||||
contact: '王五',
|
||||
phone: '13800138003',
|
||||
status: 'active',
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
}
|
||||
];
|
||||
|
||||
await sequelize.query(
|
||||
`INSERT INTO farms (name, type, location, address, contact, phone, status, created_at, updated_at) VALUES ${farmData.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?)').join(', ')}`,
|
||||
{
|
||||
replacements: farmData.flatMap(farm => [
|
||||
farm.name, farm.type, farm.location, farm.address, farm.contact,
|
||||
farm.phone, farm.status, farm.created_at, farm.updated_at
|
||||
]),
|
||||
type: QueryTypes.INSERT
|
||||
}
|
||||
);
|
||||
console.log('养殖场数据导入成功');
|
||||
|
||||
// 获取刚插入的养殖场ID
|
||||
const farms = await sequelize.query('SELECT id, name, type FROM farms WHERE name IN ("东方养殖场", "西部牧场", "南方羊场")', { type: QueryTypes.SELECT });
|
||||
console.log('获取到养殖场:', farms);
|
||||
|
||||
// 2. 插入动物数据
|
||||
const animalData = [];
|
||||
const animalTypes = {
|
||||
'pig': ['猪', '母猪', '仔猪'],
|
||||
'cattle': ['肉牛', '奶牛', '小牛'],
|
||||
'sheep': ['山羊', '绵羊', '羔羊']
|
||||
};
|
||||
|
||||
for (const farm of farms) {
|
||||
const types = animalTypes[farm.type] || ['未知'];
|
||||
for (const type of types) {
|
||||
animalData.push({
|
||||
type: type,
|
||||
count: Math.floor(Math.random() * 100) + 50,
|
||||
farm_id: farm.id,
|
||||
health_status: Math.random() > 0.2 ? 'healthy' : 'sick',
|
||||
last_inspection: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000),
|
||||
notes: `${type}群体健康状况良好`,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (animalData.length > 0) {
|
||||
await sequelize.query(
|
||||
`INSERT INTO animals (type, count, farm_id, health_status, last_inspection, notes, created_at, updated_at) VALUES ${animalData.map(() => '(?, ?, ?, ?, ?, ?, ?, ?)').join(', ')}`,
|
||||
{
|
||||
replacements: animalData.flatMap(animal => [
|
||||
animal.type, animal.count, animal.farm_id, animal.health_status,
|
||||
animal.last_inspection, animal.notes, animal.created_at, animal.updated_at
|
||||
]),
|
||||
type: QueryTypes.INSERT
|
||||
}
|
||||
);
|
||||
console.log('动物数据导入成功');
|
||||
}
|
||||
|
||||
// 3. 插入设备数据
|
||||
const deviceData = [];
|
||||
const deviceTypes = ['温度传感器', '湿度传感器', '摄像头', '喂食器', '饮水器'];
|
||||
const deviceStatuses = ['online', 'offline', 'maintenance'];
|
||||
|
||||
for (const farm of farms) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const type = deviceTypes[Math.floor(Math.random() * deviceTypes.length)];
|
||||
const status = deviceStatuses[Math.floor(Math.random() * deviceStatuses.length)];
|
||||
|
||||
deviceData.push({
|
||||
name: `${type}_${farm.name}_${String(i + 1).padStart(3, '0')}`,
|
||||
type: type,
|
||||
status: status,
|
||||
farm_id: farm.id,
|
||||
last_maintenance: new Date(Date.now() - Math.random() * 90 * 24 * 60 * 60 * 1000),
|
||||
installation_date: new Date(Date.now() - Math.random() * 730 * 24 * 60 * 60 * 1000),
|
||||
metrics: JSON.stringify({
|
||||
temperature: Math.round((Math.random() * 15 + 15) * 10) / 10,
|
||||
humidity: Math.round((Math.random() * 40 + 40) * 10) / 10,
|
||||
battery: Math.round(Math.random() * 100),
|
||||
signal_strength: Math.round(Math.random() * 100)
|
||||
}),
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceData.length > 0) {
|
||||
await sequelize.query(
|
||||
`INSERT INTO devices (name, type, status, farm_id, last_maintenance, installation_date, metrics, created_at, updated_at) VALUES ${deviceData.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?)').join(', ')}`,
|
||||
{
|
||||
replacements: deviceData.flatMap(device => [
|
||||
device.name, device.type, device.status, device.farm_id,
|
||||
device.last_maintenance, device.installation_date, device.metrics,
|
||||
device.created_at, device.updated_at
|
||||
]),
|
||||
type: QueryTypes.INSERT
|
||||
}
|
||||
);
|
||||
console.log('设备数据导入成功');
|
||||
}
|
||||
|
||||
// 4. 验证导入结果
|
||||
const [farmCount] = await sequelize.query('SELECT COUNT(*) as count FROM farms', { type: QueryTypes.SELECT });
|
||||
const [animalCount] = await sequelize.query('SELECT COUNT(*) as count FROM animals', { type: QueryTypes.SELECT });
|
||||
const [deviceCount] = await sequelize.query('SELECT COUNT(*) as count FROM devices', { type: QueryTypes.SELECT });
|
||||
|
||||
console.log('\n=== 数据导入完成 ===');
|
||||
console.log(`养殖场总数: ${farmCount.count}`);
|
||||
console.log(`动物总数: ${animalCount.count}`);
|
||||
console.log(`设备总数: ${deviceCount.count}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('数据导入失败:', error.message);
|
||||
console.error('详细错误:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
importData();
|
||||
164
backend/tools/data-management/import-farms-static-data.js
Normal file
164
backend/tools/data-management/import-farms-static-data.js
Normal file
@@ -0,0 +1,164 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { Farm } = require('./models');
|
||||
|
||||
/**
|
||||
* 导入farms静态数据到数据库
|
||||
* 包含API路由中的静态数据和种子文件中的详细数据
|
||||
*/
|
||||
async function importFarmsStaticData() {
|
||||
try {
|
||||
console.log('开始导入farms静态数据到数据库...');
|
||||
|
||||
// 检查当前farms表状态
|
||||
const existingFarms = await Farm.findAll();
|
||||
console.log(`当前farms表中有 ${existingFarms.length} 条记录`);
|
||||
|
||||
// API路由中的静态数据
|
||||
const apiStaticData = [
|
||||
{ id: 1, name: '宁夏农场1', location: '银川市' },
|
||||
{ id: 2, name: '宁夏农场2', location: '石嘴山市' },
|
||||
{ id: 3, name: '宁夏农场3', location: '吴忠市' }
|
||||
];
|
||||
|
||||
// 种子文件中的详细数据
|
||||
const seedData = [
|
||||
{
|
||||
name: '阳光农场',
|
||||
type: '养猪场',
|
||||
location: JSON.stringify({ lat: 39.9042, lng: 116.4074 }),
|
||||
address: '北京市朝阳区农场路1号',
|
||||
contact: '张三',
|
||||
phone: '13800138001',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '绿野牧场',
|
||||
type: '养牛场',
|
||||
location: JSON.stringify({ lat: 31.2304, lng: 121.4737 }),
|
||||
address: '上海市浦东新区牧场路2号',
|
||||
contact: '李四',
|
||||
phone: '13800138002',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '山谷羊场',
|
||||
type: '养羊场',
|
||||
location: JSON.stringify({ lat: 23.1291, lng: 113.2644 }),
|
||||
address: '广州市天河区山谷路3号',
|
||||
contact: '王五',
|
||||
phone: '13800138003',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '蓝天养鸡场',
|
||||
type: '养鸡场',
|
||||
location: JSON.stringify({ lat: 30.2741, lng: 120.1551 }),
|
||||
address: '杭州市西湖区蓝天路4号',
|
||||
contact: '赵六',
|
||||
phone: '13800138004',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '金山养鸭场',
|
||||
type: '养鸭场',
|
||||
location: JSON.stringify({ lat: 36.0611, lng: 103.8343 }),
|
||||
address: '兰州市城关区金山路5号',
|
||||
contact: '孙七',
|
||||
phone: '13800138005',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '银河渔场',
|
||||
type: '渔场',
|
||||
location: JSON.stringify({ lat: 29.5647, lng: 106.5507 }),
|
||||
address: '重庆市渝中区银河路6号',
|
||||
contact: '周八',
|
||||
phone: '13800138006',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '星空牧场',
|
||||
type: '综合养殖场',
|
||||
location: JSON.stringify({ lat: 34.3416, lng: 108.9398 }),
|
||||
address: '西安市雁塔区星空路7号',
|
||||
contact: '吴九',
|
||||
phone: '13800138007',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '彩虹农庄',
|
||||
type: '有机农场',
|
||||
location: JSON.stringify({ lat: 25.0478, lng: 102.7123 }),
|
||||
address: '昆明市五华区彩虹路8号',
|
||||
contact: '郑十',
|
||||
phone: '13800138008',
|
||||
status: 'active'
|
||||
}
|
||||
];
|
||||
|
||||
// 合并API静态数据和种子数据
|
||||
const allFarmsData = [];
|
||||
|
||||
// 首先添加API静态数据(转换为完整格式)
|
||||
for (const apiData of apiStaticData) {
|
||||
allFarmsData.push({
|
||||
name: apiData.name,
|
||||
type: '综合农场', // 默认类型
|
||||
location: JSON.stringify({ lat: 38.4872, lng: 106.2309 }), // 宁夏地区坐标
|
||||
address: `宁夏回族自治区${apiData.location}`,
|
||||
contact: '管理员',
|
||||
phone: '400-000-0000',
|
||||
status: 'active'
|
||||
});
|
||||
}
|
||||
|
||||
// 然后添加种子数据
|
||||
allFarmsData.push(...seedData);
|
||||
|
||||
console.log(`准备导入 ${allFarmsData.length} 条farms数据`);
|
||||
|
||||
// 开始事务
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 清空现有数据
|
||||
await Farm.destroy({ where: {}, transaction });
|
||||
console.log('已清空现有farms数据');
|
||||
|
||||
// 重置自增ID
|
||||
await sequelize.query('ALTER TABLE farms AUTO_INCREMENT = 1', { transaction });
|
||||
console.log('已重置farms表自增ID');
|
||||
|
||||
// 批量插入新数据
|
||||
const createdFarms = await Farm.bulkCreate(allFarmsData, { transaction });
|
||||
console.log(`成功插入 ${createdFarms.length} 条farms数据`);
|
||||
|
||||
// 提交事务
|
||||
await transaction.commit();
|
||||
|
||||
// 验证导入结果
|
||||
const finalFarms = await Farm.findAll({ order: [['id', 'ASC']] });
|
||||
console.log('\n导入后的farms数据:');
|
||||
finalFarms.forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}, Type: ${farm.type}, Location: ${farm.address}`);
|
||||
});
|
||||
|
||||
console.log(`\n✅ 成功导入 ${finalFarms.length} 条farms静态数据到数据库`);
|
||||
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 导入farms静态数据失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 执行导入
|
||||
importFarmsStaticData();
|
||||
124
backend/tools/data-management/insert-environment-data.js
Normal file
124
backend/tools/data-management/insert-environment-data.js
Normal file
@@ -0,0 +1,124 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
require('dotenv').config();
|
||||
|
||||
async function insertEnvironmentData() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
// 创建数据库连接
|
||||
connection = await mysql.createConnection({
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME
|
||||
});
|
||||
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 创建环境监测时刻表
|
||||
const createTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS environment_schedules (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
farm_id INT NOT NULL,
|
||||
device_id VARCHAR(50),
|
||||
schedule_time TIME NOT NULL,
|
||||
temperature DECIMAL(5,2),
|
||||
humidity DECIMAL(5,2),
|
||||
monitoring_date DATE NOT NULL,
|
||||
status ENUM('active', 'inactive', 'maintenance') DEFAULT 'active',
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_farm_date (farm_id, monitoring_date),
|
||||
INDEX idx_schedule_time (schedule_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`;
|
||||
|
||||
await connection.execute(createTableSQL);
|
||||
console.log('环境监测时刻表创建成功');
|
||||
|
||||
// 生成过去7天的数据
|
||||
const scheduleData = [];
|
||||
const currentDate = new Date();
|
||||
|
||||
// 每天的监测时间点
|
||||
const scheduleTimes = ['06:00:00', '12:00:00', '18:00:00', '22:00:00'];
|
||||
|
||||
for (let day = 0; day < 7; day++) {
|
||||
const date = new Date(currentDate);
|
||||
date.setDate(date.getDate() - day);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
|
||||
for (const time of scheduleTimes) {
|
||||
// 农场1的数据
|
||||
scheduleData.push([
|
||||
1, // farm_id
|
||||
'TEMP_001', // device_id
|
||||
time,
|
||||
(20 + Math.random() * 15).toFixed(2), // 温度 20-35°C
|
||||
(40 + Math.random() * 40).toFixed(2), // 湿度 40-80%
|
||||
dateStr,
|
||||
'active',
|
||||
`农场1 ${dateStr} ${time} 环境监测数据`
|
||||
]);
|
||||
|
||||
// 农场2的数据
|
||||
scheduleData.push([
|
||||
2, // farm_id
|
||||
'TEMP_002', // device_id
|
||||
time,
|
||||
(18 + Math.random() * 17).toFixed(2), // 温度 18-35°C
|
||||
(35 + Math.random() * 45).toFixed(2), // 湿度 35-80%
|
||||
dateStr,
|
||||
'active',
|
||||
`农场2 ${dateStr} ${time} 环境监测数据`
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量插入数据
|
||||
const insertSQL = `
|
||||
INSERT INTO environment_schedules
|
||||
(farm_id, device_id, schedule_time, temperature, humidity, monitoring_date, status, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
for (const data of scheduleData) {
|
||||
await connection.execute(insertSQL, data);
|
||||
}
|
||||
|
||||
console.log(`成功插入 ${scheduleData.length} 条环境监测数据`);
|
||||
|
||||
// 验证插入的数据
|
||||
const [rows] = await connection.execute(
|
||||
'SELECT COUNT(*) as count FROM environment_schedules'
|
||||
);
|
||||
console.log(`环境监测表总记录数: ${rows[0].count}`);
|
||||
|
||||
// 显示最近的几条记录
|
||||
const [recentRows] = await connection.execute(
|
||||
`SELECT farm_id, device_id, schedule_time, temperature, humidity,
|
||||
monitoring_date, status
|
||||
FROM environment_schedules
|
||||
ORDER BY monitoring_date DESC, schedule_time DESC
|
||||
LIMIT 5`
|
||||
);
|
||||
|
||||
console.log('\n最近的环境监测记录:');
|
||||
recentRows.forEach(row => {
|
||||
console.log(`农场${row.farm_id} | ${row.device_id} | ${row.monitoring_date} ${row.schedule_time} | 温度:${row.temperature}°C | 湿度:${row.humidity}% | 状态:${row.status}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error.message);
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行脚本
|
||||
insertEnvironmentData();
|
||||
105
backend/tools/data-management/reorder-farms-id.js
Normal file
105
backend/tools/data-management/reorder-farms-id.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const { Farm, Animal, Device, Alert } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function reorderFarmsId() {
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
console.log('开始重新排序farms表ID...');
|
||||
|
||||
// 1. 获取所有farms数据,按当前ID排序
|
||||
const farms = await Farm.findAll({
|
||||
order: [['id', 'ASC']],
|
||||
transaction
|
||||
});
|
||||
|
||||
console.log(`找到 ${farms.length} 个养殖场`);
|
||||
|
||||
// 2. 创建ID映射表
|
||||
const idMapping = {};
|
||||
farms.forEach((farm, index) => {
|
||||
const oldId = farm.id;
|
||||
const newId = index + 1;
|
||||
idMapping[oldId] = newId;
|
||||
console.log(`养殖场 "${farm.name}": ${oldId} -> ${newId}`);
|
||||
});
|
||||
|
||||
// 3. 临时禁用外键约束
|
||||
await sequelize.query('SET FOREIGN_KEY_CHECKS = 0', { transaction });
|
||||
|
||||
// 4. 创建临时表存储farms数据
|
||||
await sequelize.query(`
|
||||
CREATE TEMPORARY TABLE farms_temp AS
|
||||
SELECT * FROM farms ORDER BY id ASC
|
||||
`, { transaction });
|
||||
|
||||
// 5. 清空farms表
|
||||
await sequelize.query('DELETE FROM farms', { transaction });
|
||||
|
||||
// 6. 重置自增ID
|
||||
await sequelize.query('ALTER TABLE farms AUTO_INCREMENT = 1', { transaction });
|
||||
|
||||
// 7. 按新顺序插入数据
|
||||
for (let i = 0; i < farms.length; i++) {
|
||||
const farm = farms[i];
|
||||
const newId = i + 1;
|
||||
|
||||
await sequelize.query(`
|
||||
INSERT INTO farms (id, name, type, location, address, contact, phone, status, created_at, updated_at)
|
||||
SELECT ${newId}, name, type, location, address, contact, phone, status, created_at, updated_at
|
||||
FROM farms_temp WHERE id = ${farm.id}
|
||||
`, { transaction });
|
||||
}
|
||||
|
||||
// 8. 更新animals表的farm_id
|
||||
console.log('\n更新animals表的farm_id...');
|
||||
for (const [oldId, newId] of Object.entries(idMapping)) {
|
||||
const result = await sequelize.query(
|
||||
'UPDATE animals SET farm_id = ? WHERE farm_id = ?',
|
||||
{ replacements: [newId, oldId], transaction }
|
||||
);
|
||||
console.log(`Animals: farm_id ${oldId} -> ${newId}, 影响 ${result[0].affectedRows || 0} 行`);
|
||||
}
|
||||
|
||||
// 9. 更新devices表的farm_id
|
||||
console.log('\n更新devices表的farm_id...');
|
||||
for (const [oldId, newId] of Object.entries(idMapping)) {
|
||||
const result = await sequelize.query(
|
||||
'UPDATE devices SET farm_id = ? WHERE farm_id = ?',
|
||||
{ replacements: [newId, oldId], transaction }
|
||||
);
|
||||
console.log(`Devices: farm_id ${oldId} -> ${newId}, 影响 ${result[0].affectedRows || 0} 行`);
|
||||
}
|
||||
|
||||
// 10. 更新alerts表的farm_id
|
||||
console.log('\n更新alerts表的farm_id...');
|
||||
for (const [oldId, newId] of Object.entries(idMapping)) {
|
||||
const result = await sequelize.query(
|
||||
'UPDATE alerts SET farm_id = ? WHERE farm_id = ?',
|
||||
{ replacements: [newId, oldId], transaction }
|
||||
);
|
||||
console.log(`Alerts: farm_id ${oldId} -> ${newId}, 影响 ${result[0].affectedRows || 0} 行`);
|
||||
}
|
||||
|
||||
// 11. 重新启用外键约束
|
||||
await sequelize.query('SET FOREIGN_KEY_CHECKS = 1', { transaction });
|
||||
|
||||
// 12. 验证数据完整性
|
||||
console.log('\n验证数据完整性...');
|
||||
const newFarms = await Farm.findAll({ order: [['id', 'ASC']], transaction });
|
||||
console.log('更新后的farms表:');
|
||||
newFarms.forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}`);
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
console.log('\n✅ farms表ID重新排序完成!');
|
||||
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
console.error('❌ 重新排序失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
reorderFarmsId().catch(console.error);
|
||||
340
backend/tools/data-management/reorder-primary-keys.js
Normal file
340
backend/tools/data-management/reorder-primary-keys.js
Normal file
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* 重新排序数据库中所有表的主键ID
|
||||
* @file reorder-primary-keys.js
|
||||
* @description 将所有表的主键ID重新排序,从1开始升序,同时更新外键引用
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
// 根据依赖关系确定的处理顺序(被引用的表优先)
|
||||
const TABLE_ORDER = [
|
||||
'roles',
|
||||
'users',
|
||||
'farms',
|
||||
'devices',
|
||||
'products',
|
||||
'animals',
|
||||
'alerts',
|
||||
'orders',
|
||||
'sensor_data',
|
||||
'order_items',
|
||||
'user_roles',
|
||||
'seeds'
|
||||
];
|
||||
|
||||
// 外键映射关系
|
||||
const FOREIGN_KEY_MAPPINGS = {
|
||||
'animals': [{ column: 'farm_id', referencedTable: 'farms' }],
|
||||
'alerts': [
|
||||
{ column: 'device_id', referencedTable: 'devices' },
|
||||
{ column: 'farm_id', referencedTable: 'farms' }
|
||||
],
|
||||
'devices': [{ column: 'farm_id', referencedTable: 'farms' }],
|
||||
'order_items': [
|
||||
{ column: 'order_id', referencedTable: 'orders' },
|
||||
{ column: 'product_id', referencedTable: 'products' }
|
||||
],
|
||||
'orders': [{ column: 'user_id', referencedTable: 'users' }],
|
||||
'sensor_data': [
|
||||
{ column: 'device_id', referencedTable: 'devices' },
|
||||
{ column: 'farm_id', referencedTable: 'farms' }
|
||||
],
|
||||
'user_roles': [
|
||||
{ column: 'role_id', referencedTable: 'roles' },
|
||||
{ column: 'user_id', referencedTable: 'users' }
|
||||
]
|
||||
};
|
||||
|
||||
class IDReorderManager {
|
||||
constructor() {
|
||||
this.idMappings = new Map(); // 存储旧ID到新ID的映射
|
||||
this.transaction = null;
|
||||
}
|
||||
|
||||
async reorderAllTables() {
|
||||
this.transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
console.log('=== 开始重新排序所有表的主键ID ===\n');
|
||||
|
||||
// 临时禁用外键检查
|
||||
await sequelize.query('SET FOREIGN_KEY_CHECKS = 0', { transaction: this.transaction });
|
||||
|
||||
// 按顺序处理每个表
|
||||
for (const tableName of TABLE_ORDER) {
|
||||
await this.reorderTableIDs(tableName);
|
||||
}
|
||||
|
||||
// 重新启用外键检查
|
||||
await sequelize.query('SET FOREIGN_KEY_CHECKS = 1', { transaction: this.transaction });
|
||||
|
||||
// 验证数据完整性
|
||||
await this.verifyIntegrity();
|
||||
|
||||
await this.transaction.commit();
|
||||
console.log('\n✅ 所有表的ID重新排序完成!');
|
||||
|
||||
} catch (error) {
|
||||
await this.transaction.rollback();
|
||||
console.error('❌ ID重新排序失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async reorderTableIDs(tableName) {
|
||||
try {
|
||||
console.log(`\n🔄 处理表: ${tableName}`);
|
||||
|
||||
// 检查表是否存在且有数据
|
||||
const tableExists = await this.checkTableExists(tableName);
|
||||
if (!tableExists) {
|
||||
console.log(`⚠️ 表 ${tableName} 不存在,跳过`);
|
||||
return;
|
||||
}
|
||||
|
||||
const recordCount = await this.getRecordCount(tableName);
|
||||
if (recordCount === 0) {
|
||||
console.log(`ℹ️ 表 ${tableName} 无数据,跳过`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` 记录数: ${recordCount}`);
|
||||
|
||||
// 获取当前所有记录(按ID排序)
|
||||
const records = await sequelize.query(
|
||||
`SELECT * FROM ${tableName} ORDER BY id`,
|
||||
{ type: QueryTypes.SELECT, transaction: this.transaction }
|
||||
);
|
||||
|
||||
if (records.length === 0) {
|
||||
console.log(` 无记录需要处理`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建ID映射
|
||||
const oldToNewIdMap = new Map();
|
||||
records.forEach((record, index) => {
|
||||
const oldId = record.id;
|
||||
const newId = index + 1;
|
||||
oldToNewIdMap.set(oldId, newId);
|
||||
});
|
||||
|
||||
// 存储映射供其他表使用
|
||||
this.idMappings.set(tableName, oldToNewIdMap);
|
||||
|
||||
// 检查是否需要重新排序
|
||||
const needsReorder = records.some((record, index) => record.id !== index + 1);
|
||||
|
||||
if (!needsReorder) {
|
||||
console.log(` ✅ ID已经是连续的,无需重新排序`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` 🔧 重新排序ID: ${records[0].id}-${records[records.length-1].id} -> 1-${records.length}`);
|
||||
|
||||
// 更新外键引用(如果有的话)
|
||||
await this.updateForeignKeyReferences(tableName, records);
|
||||
|
||||
// 创建临时表
|
||||
const tempTableName = `${tableName}_temp_reorder`;
|
||||
await this.createTempTable(tableName, tempTableName);
|
||||
|
||||
// 将数据插入临时表(使用新ID)
|
||||
await this.insertDataWithNewIDs(tableName, tempTableName, records, oldToNewIdMap);
|
||||
|
||||
// 删除原表数据
|
||||
await sequelize.query(
|
||||
`DELETE FROM ${tableName}`,
|
||||
{ transaction: this.transaction }
|
||||
);
|
||||
|
||||
// 重置自增ID
|
||||
await sequelize.query(
|
||||
`ALTER TABLE ${tableName} AUTO_INCREMENT = 1`,
|
||||
{ transaction: this.transaction }
|
||||
);
|
||||
|
||||
// 将临时表数据复制回原表
|
||||
await this.copyDataFromTempTable(tableName, tempTableName);
|
||||
|
||||
// 删除临时表
|
||||
await sequelize.query(
|
||||
`DROP TABLE ${tempTableName}`,
|
||||
{ transaction: this.transaction }
|
||||
);
|
||||
|
||||
console.log(` ✅ 表 ${tableName} ID重新排序完成`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 处理表 ${tableName} 失败:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async checkTableExists(tableName) {
|
||||
const result = await sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE() AND table_name = '${tableName}'`,
|
||||
{ type: QueryTypes.SELECT, transaction: this.transaction }
|
||||
);
|
||||
return result[0].count > 0;
|
||||
}
|
||||
|
||||
async getRecordCount(tableName) {
|
||||
const result = await sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM ${tableName}`,
|
||||
{ type: QueryTypes.SELECT, transaction: this.transaction }
|
||||
);
|
||||
return parseInt(result[0].count);
|
||||
}
|
||||
|
||||
async updateForeignKeyReferences(tableName, records) {
|
||||
const foreignKeys = FOREIGN_KEY_MAPPINGS[tableName];
|
||||
if (!foreignKeys) return;
|
||||
|
||||
console.log(` 🔗 更新外键引用...`);
|
||||
|
||||
for (const fk of foreignKeys) {
|
||||
const referencedTableMapping = this.idMappings.get(fk.referencedTable);
|
||||
if (!referencedTableMapping) {
|
||||
console.log(` ⚠️ 引用表 ${fk.referencedTable} 的映射不存在,跳过外键 ${fk.column}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 更新外键值
|
||||
for (const record of records) {
|
||||
const oldForeignKeyValue = record[fk.column];
|
||||
if (oldForeignKeyValue && referencedTableMapping.has(oldForeignKeyValue)) {
|
||||
const newForeignKeyValue = referencedTableMapping.get(oldForeignKeyValue);
|
||||
record[fk.column] = newForeignKeyValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createTempTable(originalTable, tempTable) {
|
||||
// 先删除临时表(如果存在)
|
||||
try {
|
||||
await sequelize.query(
|
||||
`DROP TABLE IF EXISTS ${tempTable}`,
|
||||
{ transaction: this.transaction }
|
||||
);
|
||||
} catch (error) {
|
||||
// 忽略删除错误
|
||||
}
|
||||
|
||||
await sequelize.query(
|
||||
`CREATE TABLE ${tempTable} LIKE ${originalTable}`,
|
||||
{ transaction: this.transaction }
|
||||
);
|
||||
}
|
||||
|
||||
async insertDataWithNewIDs(originalTable, tempTable, records, idMapping) {
|
||||
if (records.length === 0) return;
|
||||
|
||||
// 获取表结构
|
||||
const columns = await sequelize.query(
|
||||
`SELECT COLUMN_NAME FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = '${originalTable}'
|
||||
ORDER BY ordinal_position`,
|
||||
{ type: QueryTypes.SELECT, transaction: this.transaction }
|
||||
);
|
||||
|
||||
const columnNames = columns.map(col => col.COLUMN_NAME);
|
||||
|
||||
// 批量插入数据
|
||||
for (let i = 0; i < records.length; i += 100) {
|
||||
const batch = records.slice(i, i + 100);
|
||||
const values = batch.map((record, batchIndex) => {
|
||||
const newId = i + batchIndex + 1;
|
||||
const values = columnNames.map(col => {
|
||||
if (col === 'id') {
|
||||
return newId;
|
||||
}
|
||||
const value = record[col];
|
||||
if (value === null || value === undefined) {
|
||||
return 'NULL';
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return `'${value.replace(/'/g, "''")}'`;
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
|
||||
}
|
||||
return value;
|
||||
});
|
||||
return `(${values.join(', ')})`;
|
||||
});
|
||||
|
||||
await sequelize.query(
|
||||
`INSERT INTO ${tempTable} (${columnNames.join(', ')}) VALUES ${values.join(', ')}`,
|
||||
{ transaction: this.transaction }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async copyDataFromTempTable(originalTable, tempTable) {
|
||||
await sequelize.query(
|
||||
`INSERT INTO ${originalTable} SELECT * FROM ${tempTable}`,
|
||||
{ transaction: this.transaction }
|
||||
);
|
||||
}
|
||||
|
||||
async verifyIntegrity() {
|
||||
console.log('\n🔍 验证数据完整性...');
|
||||
|
||||
for (const [tableName, foreignKeys] of Object.entries(FOREIGN_KEY_MAPPINGS)) {
|
||||
if (!foreignKeys) continue;
|
||||
|
||||
for (const fk of foreignKeys) {
|
||||
try {
|
||||
const result = await sequelize.query(`
|
||||
SELECT COUNT(*) as invalid_count
|
||||
FROM ${tableName} t
|
||||
WHERE t.${fk.column} IS NOT NULL
|
||||
AND t.${fk.column} NOT IN (
|
||||
SELECT id FROM ${fk.referencedTable} WHERE id IS NOT NULL
|
||||
)
|
||||
`, { type: QueryTypes.SELECT, transaction: this.transaction });
|
||||
|
||||
const invalidCount = parseInt(result[0].invalid_count);
|
||||
if (invalidCount > 0) {
|
||||
throw new Error(`表 ${tableName}.${fk.column} 有 ${invalidCount} 个无效的外键引用`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ 完整性检查失败: ${tableName}.${fk.column}`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ 数据完整性验证通过');
|
||||
}
|
||||
}
|
||||
|
||||
async function reorderPrimaryKeys() {
|
||||
const manager = new IDReorderManager();
|
||||
await manager.reorderAllTables();
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
reorderPrimaryKeys()
|
||||
.then(() => {
|
||||
console.log('\n🎉 主键ID重新排序完成!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('💥 主键ID重新排序失败:', error);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => {
|
||||
sequelize.close();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { reorderPrimaryKeys, IDReorderManager };
|
||||
44
backend/tools/data-management/reset_admin_password.js
Normal file
44
backend/tools/data-management/reset_admin_password.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
async function resetAdminPassword() {
|
||||
try {
|
||||
// 查找 admin 用户
|
||||
const admin = await User.findOne({ where: { username: 'admin' } });
|
||||
|
||||
if (!admin) {
|
||||
console.log('未找到 admin 用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('重置前的密码哈希:', admin.password);
|
||||
|
||||
// 直接生成新的哈希并更新
|
||||
const newPassword = '123456';
|
||||
const newHash = await bcrypt.hash(newPassword, 10);
|
||||
|
||||
// 直接更新数据库,绕过模型钩子
|
||||
await User.update(
|
||||
{ password: newHash },
|
||||
{ where: { username: 'admin' } }
|
||||
);
|
||||
|
||||
console.log('新的密码哈希:', newHash);
|
||||
|
||||
// 验证新密码
|
||||
const isValid = await bcrypt.compare(newPassword, newHash);
|
||||
console.log('新密码验证:', isValid ? '成功' : '失败');
|
||||
|
||||
// 重新查询用户验证
|
||||
const updatedAdmin = await User.findOne({ where: { username: 'admin' } });
|
||||
const finalCheck = await bcrypt.compare(newPassword, updatedAdmin.password);
|
||||
console.log('数据库中的密码验证:', finalCheck ? '成功' : '失败');
|
||||
|
||||
} catch (error) {
|
||||
console.error('重置密码失败:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
resetAdminPassword();
|
||||
41
backend/tools/data-management/restore-farms-data.js
Normal file
41
backend/tools/data-management/restore-farms-data.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function restoreFarmsData() {
|
||||
try {
|
||||
console.log('恢复farms数据...');
|
||||
|
||||
// 清空现有数据
|
||||
await sequelize.query('DELETE FROM farms');
|
||||
await sequelize.query('ALTER TABLE farms AUTO_INCREMENT = 1');
|
||||
|
||||
// 插入农场数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO farms (name, type, location, address, contact, phone, status, created_at, updated_at) VALUES
|
||||
('阳光农场', '养猪场', JSON_OBJECT('lat', 39.9042, 'lng', 116.4074), '北京市朝阳区农场路1号', '张三', '13800138001', 'active', NOW(), NOW()),
|
||||
('绿野牧场', '养牛场', JSON_OBJECT('lat', 31.2304, 'lng', 121.4737), '上海市浦东新区牧场路2号', '李四', '13800138002', 'active', NOW(), NOW()),
|
||||
('山谷羊场', '养羊场', JSON_OBJECT('lat', 23.1291, 'lng', 113.2644), '广州市天河区山谷路3号', '王五', '13800138003', 'active', NOW(), NOW()),
|
||||
('蓝天养鸡场', '养鸡场', JSON_OBJECT('lat', 30.5728, 'lng', 104.0668), '成都市锦江区蓝天路4号', '赵六', '13800138004', 'active', NOW(), NOW()),
|
||||
('金山养鸭场', '养鸭场', JSON_OBJECT('lat', 36.0611, 'lng', 120.3785), '青岛市市南区金山路5号', '钱七', '13800138005', 'active', NOW(), NOW()),
|
||||
('银河渔场', '渔场', JSON_OBJECT('lat', 22.3193, 'lng', 114.1694), '深圳市福田区银河路6号', '孙八', '13800138006', 'active', NOW(), NOW()),
|
||||
('星空牧场', '综合农场', JSON_OBJECT('lat', 29.5630, 'lng', 106.5516), '重庆市渝中区星空路7号', '周九', '13800138007', 'active', NOW(), NOW()),
|
||||
('彩虹农庄', '有机农场', JSON_OBJECT('lat', 34.3416, 'lng', 108.9398), '西安市雁塔区彩虹路8号', '吴十', '13800138008', 'active', NOW(), NOW()),
|
||||
('东方养殖场', '养猪场', JSON_OBJECT('lat', 26.0745, 'lng', 119.2965), '福州市鼓楼区东方路9号', '郑一', '13800138009', 'active', NOW(), NOW()),
|
||||
('西部牧场', '养牛场', JSON_OBJECT('lat', 43.8256, 'lng', 87.6168), '乌鲁木齐市天山区西部路10号', '王二', '13800138010', 'active', NOW(), NOW()),
|
||||
('南方羊场', '养羊场', JSON_OBJECT('lat', 25.0478, 'lng', 102.7123), '昆明市五华区南方路11号', '李三', '13800138011', 'active', NOW(), NOW())
|
||||
`);
|
||||
|
||||
// 验证数据
|
||||
const farms = await sequelize.query('SELECT id, name FROM farms ORDER BY id ASC');
|
||||
console.log('恢复的farms数据:');
|
||||
farms[0].forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}`);
|
||||
});
|
||||
|
||||
console.log(`\n✅ 成功恢复 ${farms[0].length} 个养殖场数据`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 恢复数据失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
restoreFarmsData();
|
||||
161
backend/tools/data-management/simple-reorder-farms.js
Normal file
161
backend/tools/data-management/simple-reorder-farms.js
Normal file
@@ -0,0 +1,161 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
async function simpleReorderFarms() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
// 创建数据库连接
|
||||
connection = await mysql.createConnection({
|
||||
host: 'localhost',
|
||||
user: 'root',
|
||||
password: 'root123',
|
||||
database: 'nxxmdata'
|
||||
});
|
||||
|
||||
console.log('连接数据库成功');
|
||||
|
||||
// 1. 检查当前farms数据
|
||||
const [farms] = await connection.execute('SELECT * FROM farms ORDER BY id ASC');
|
||||
console.log(`找到 ${farms.length} 个养殖场:`);
|
||||
farms.forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}`);
|
||||
});
|
||||
|
||||
if (farms.length === 0) {
|
||||
console.log('farms表为空,先恢复数据...');
|
||||
|
||||
// 插入基础数据
|
||||
await connection.execute(`
|
||||
INSERT INTO farms (name, type, location, address, contact, phone, status, created_at, updated_at) VALUES
|
||||
('蓝天养鸡场', '养鸡场', '{}', '成都市锦江区蓝天路4号', '赵六', '13800138004', 'active', NOW(), NOW()),
|
||||
('金山养鸭场', '养鸭场', '{}', '青岛市市南区金山路5号', '钱七', '13800138005', 'active', NOW(), NOW()),
|
||||
('银河渔场', '渔场', '{}', '深圳市福田区银河路6号', '孙八', '13800138006', 'active', NOW(), NOW()),
|
||||
('星空牧场', '综合农场', '{}', '重庆市渝中区星空路7号', '周九', '13800138007', 'active', NOW(), NOW()),
|
||||
('彩虹农庄', '有机农场', '{}', '西安市雁塔区彩虹路8号', '吴十', '13800138008', 'active', NOW(), NOW()),
|
||||
('东方养殖场', '养猪场', '{}', '福州市鼓楼区东方路9号', '郑一', '13800138009', 'active', NOW(), NOW()),
|
||||
('西部牧场', '养牛场', '{}', '乌鲁木齐市天山区西部路10号', '王二', '13800138010', 'active', NOW(), NOW()),
|
||||
('南方羊场', '养羊场', '{}', '昆明市五华区南方路11号', '李三', '13800138011', 'active', NOW(), NOW())
|
||||
`);
|
||||
|
||||
console.log('数据恢复完成');
|
||||
|
||||
// 重新获取数据
|
||||
const [newFarms] = await connection.execute('SELECT * FROM farms ORDER BY id ASC');
|
||||
console.log('恢复后的数据:');
|
||||
newFarms.forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}`);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 开始事务
|
||||
await connection.beginTransaction();
|
||||
|
||||
// 3. 禁用外键检查
|
||||
await connection.execute('SET FOREIGN_KEY_CHECKS = 0');
|
||||
|
||||
// 4. 创建ID映射
|
||||
const idMapping = {};
|
||||
farms.forEach((farm, index) => {
|
||||
idMapping[farm.id] = index + 1;
|
||||
});
|
||||
|
||||
console.log('\nID映射:');
|
||||
Object.entries(idMapping).forEach(([oldId, newId]) => {
|
||||
console.log(`${oldId} -> ${newId}`);
|
||||
});
|
||||
|
||||
// 5. 更新farms表ID
|
||||
console.log('\n更新farms表ID...');
|
||||
for (let i = 0; i < farms.length; i++) {
|
||||
const farm = farms[i];
|
||||
const newId = i + 1;
|
||||
|
||||
if (farm.id !== newId) {
|
||||
// 先更新为临时ID避免冲突
|
||||
const tempId = 1000 + newId;
|
||||
await connection.execute(
|
||||
'UPDATE farms SET id = ? WHERE id = ?',
|
||||
[tempId, farm.id]
|
||||
);
|
||||
console.log(`临时更新: ${farm.id} -> ${tempId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 更新为最终ID
|
||||
for (let i = 0; i < farms.length; i++) {
|
||||
const newId = i + 1;
|
||||
const tempId = 1000 + newId;
|
||||
|
||||
await connection.execute(
|
||||
'UPDATE farms SET id = ? WHERE id = ?',
|
||||
[newId, tempId]
|
||||
);
|
||||
console.log(`最终更新: ${tempId} -> ${newId}`);
|
||||
}
|
||||
|
||||
// 7. 更新相关表的外键
|
||||
console.log('\n更新外键...');
|
||||
|
||||
// 更新animals表
|
||||
for (const [oldId, newId] of Object.entries(idMapping)) {
|
||||
if (oldId !== newId.toString()) {
|
||||
const [result] = await connection.execute(
|
||||
'UPDATE animals SET farm_id = ? WHERE farm_id = ?',
|
||||
[newId, oldId]
|
||||
);
|
||||
console.log(`Animals: farm_id ${oldId} -> ${newId}, 影响 ${result.affectedRows} 行`);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新devices表
|
||||
for (const [oldId, newId] of Object.entries(idMapping)) {
|
||||
if (oldId !== newId.toString()) {
|
||||
const [result] = await connection.execute(
|
||||
'UPDATE devices SET farm_id = ? WHERE farm_id = ?',
|
||||
[newId, oldId]
|
||||
);
|
||||
console.log(`Devices: farm_id ${oldId} -> ${newId}, 影响 ${result.affectedRows} 行`);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新alerts表
|
||||
for (const [oldId, newId] of Object.entries(idMapping)) {
|
||||
if (oldId !== newId.toString()) {
|
||||
const [result] = await connection.execute(
|
||||
'UPDATE alerts SET farm_id = ? WHERE farm_id = ?',
|
||||
[newId, oldId]
|
||||
);
|
||||
console.log(`Alerts: farm_id ${oldId} -> ${newId}, 影响 ${result.affectedRows} 行`);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 重新启用外键检查
|
||||
await connection.execute('SET FOREIGN_KEY_CHECKS = 1');
|
||||
|
||||
// 9. 提交事务
|
||||
await connection.commit();
|
||||
|
||||
// 10. 验证结果
|
||||
const [finalFarms] = await connection.execute('SELECT * FROM farms ORDER BY id ASC');
|
||||
console.log('\n最终结果:');
|
||||
finalFarms.forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}`);
|
||||
});
|
||||
|
||||
console.log('\n✅ farms表ID重新排序完成!');
|
||||
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
await connection.rollback();
|
||||
}
|
||||
console.error('❌ 操作失败:', error.message);
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
simpleReorderFarms();
|
||||
88
backend/tools/data-management/update-device-status.js
Normal file
88
backend/tools/data-management/update-device-status.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 更新设备状态脚本
|
||||
* 将设备状态更新为online、maintenance、offline三种状态
|
||||
*/
|
||||
|
||||
const { Device } = require('./models');
|
||||
|
||||
async function updateDeviceStatus() {
|
||||
try {
|
||||
console.log('开始更新设备状态...');
|
||||
|
||||
// 获取所有设备
|
||||
const devices = await Device.findAll();
|
||||
console.log(`找到 ${devices.length} 个设备`);
|
||||
|
||||
// 计算每种状态的设备数量
|
||||
const totalDevices = devices.length;
|
||||
const onlineCount = Math.floor(totalDevices * 0.6); // 60% 在线
|
||||
const maintenanceCount = Math.floor(totalDevices * 0.15); // 15% 维护
|
||||
const offlineCount = totalDevices - onlineCount - maintenanceCount; // 剩余离线
|
||||
|
||||
console.log(`计划分配: ${onlineCount} 在线, ${maintenanceCount} 维护, ${offlineCount} 离线`);
|
||||
|
||||
// 随机打乱设备数组
|
||||
const shuffledDevices = devices.sort(() => Math.random() - 0.5);
|
||||
|
||||
// 批量更新设备状态
|
||||
const updates = [];
|
||||
|
||||
// 设置在线设备
|
||||
for (let i = 0; i < onlineCount; i++) {
|
||||
updates.push(
|
||||
Device.update(
|
||||
{ status: 'online' },
|
||||
{ where: { id: shuffledDevices[i].id } }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 设置维护设备
|
||||
for (let i = onlineCount; i < onlineCount + maintenanceCount; i++) {
|
||||
updates.push(
|
||||
Device.update(
|
||||
{ status: 'maintenance' },
|
||||
{ where: { id: shuffledDevices[i].id } }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 设置离线设备
|
||||
for (let i = onlineCount + maintenanceCount; i < totalDevices; i++) {
|
||||
updates.push(
|
||||
Device.update(
|
||||
{ status: 'offline' },
|
||||
{ where: { id: shuffledDevices[i].id } }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 执行所有更新
|
||||
await Promise.all(updates);
|
||||
|
||||
// 验证更新结果
|
||||
const statusCount = await Device.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[Device.sequelize.fn('COUNT', Device.sequelize.col('status')), 'count']
|
||||
],
|
||||
group: ['status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
console.log('\n更新完成!当前设备状态分布:');
|
||||
statusCount.forEach(item => {
|
||||
console.log(`${item.status}: ${item.count} 个设备`);
|
||||
});
|
||||
|
||||
console.log('\n设备状态更新成功!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新设备状态失败:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
updateDeviceStatus();
|
||||
44
backend/tools/data-management/update-health-status-enum.js
Normal file
44
backend/tools/data-management/update-health-status-enum.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 更新动物健康状态枚举值
|
||||
* 添加 'treatment' 状态到现有的 ENUM 类型
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function updateHealthStatusEnum() {
|
||||
try {
|
||||
console.log('开始更新 health_status 枚举值...');
|
||||
|
||||
// 更新 ENUM 类型,添加 'treatment' 选项
|
||||
await sequelize.query(`
|
||||
ALTER TABLE animals
|
||||
MODIFY COLUMN health_status
|
||||
ENUM('healthy', 'sick', 'quarantine', 'treatment')
|
||||
DEFAULT 'healthy'
|
||||
`);
|
||||
|
||||
console.log('health_status 枚举值更新成功!');
|
||||
console.log('现在支持的状态: healthy, sick, quarantine, treatment');
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新 health_status 枚举值失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
updateHealthStatusEnum()
|
||||
.then(() => {
|
||||
console.log('数据库更新完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('数据库更新失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = updateHealthStatusEnum;
|
||||
248
backend/tools/testing/test-api-binding.js
Normal file
248
backend/tools/testing/test-api-binding.js
Normal file
@@ -0,0 +1,248 @@
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database');
|
||||
const farmController = require('./controllers/farmController');
|
||||
|
||||
// 模拟Express请求和响应对象
|
||||
function createMockReq(body, params = {}) {
|
||||
return {
|
||||
body,
|
||||
params
|
||||
};
|
||||
}
|
||||
|
||||
function createMockRes() {
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
data: null,
|
||||
status: function(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
json: function(data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
// 测试API绑定的完整流程
|
||||
async function testApiBinding() {
|
||||
console.log('=== API数据绑定测试 ===\n');
|
||||
|
||||
let testFarmId = null;
|
||||
|
||||
try {
|
||||
// 1. 测试创建养殖场API
|
||||
console.log('1. 测试创建养殖场API...');
|
||||
const createReq = createMockReq({
|
||||
name: 'API测试农场',
|
||||
owner: 'API测试负责人',
|
||||
phone: '13800138001',
|
||||
address: 'API测试地址',
|
||||
longitude: 106.2309,
|
||||
latitude: 38.4872,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
const createRes = createMockRes();
|
||||
await farmController.createFarm(createReq, createRes);
|
||||
|
||||
console.log('✓ 创建API响应:', {
|
||||
status: createRes.statusCode,
|
||||
success: createRes.data?.success,
|
||||
farm_id: createRes.data?.data?.id,
|
||||
location: createRes.data?.data?.location
|
||||
});
|
||||
|
||||
if (!createRes.data?.success) {
|
||||
throw new Error('创建养殖场失败: ' + createRes.data?.message);
|
||||
}
|
||||
|
||||
testFarmId = createRes.data.data.id;
|
||||
|
||||
// 2. 测试获取养殖场API
|
||||
console.log('\n2. 测试获取养殖场API...');
|
||||
const getReq = createMockReq({}, { id: testFarmId });
|
||||
const getRes = createMockRes();
|
||||
await farmController.getFarmById(getReq, getRes);
|
||||
|
||||
console.log('✓ 获取API响应:', {
|
||||
status: getRes.statusCode,
|
||||
success: getRes.data?.success,
|
||||
location: getRes.data?.data?.location,
|
||||
location_type: typeof getRes.data?.data?.location
|
||||
});
|
||||
|
||||
const farmData = getRes.data?.data;
|
||||
if (!farmData) {
|
||||
throw new Error('获取养殖场数据失败');
|
||||
}
|
||||
|
||||
// 3. 模拟前端editFarm函数的数据解析
|
||||
console.log('\n3. 模拟前端editFarm数据解析...');
|
||||
const record = farmData;
|
||||
|
||||
// 前端解析逻辑
|
||||
const longitude = record.location?.lng || undefined;
|
||||
const latitude = record.location?.lat || undefined;
|
||||
|
||||
console.log('✓ 前端解析结果:', {
|
||||
original_location: record.location,
|
||||
parsed_longitude: longitude,
|
||||
parsed_latitude: latitude,
|
||||
longitude_type: typeof longitude,
|
||||
latitude_type: typeof latitude
|
||||
});
|
||||
|
||||
// 4. 模拟前端formData绑定
|
||||
console.log('\n4. 模拟前端formData绑定...');
|
||||
const formData = {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
owner: record.contact || '',
|
||||
phone: record.phone,
|
||||
address: record.address,
|
||||
longitude: longitude,
|
||||
latitude: latitude,
|
||||
status: record.status
|
||||
};
|
||||
|
||||
console.log('✓ formData绑定结果:', {
|
||||
longitude: formData.longitude,
|
||||
latitude: formData.latitude,
|
||||
longitude_type: typeof formData.longitude,
|
||||
latitude_type: typeof formData.latitude
|
||||
});
|
||||
|
||||
// 5. 模拟用户修改经纬度
|
||||
console.log('\n5. 模拟用户修改经纬度...');
|
||||
const modifiedFormData = {
|
||||
...formData,
|
||||
longitude: 106.2400,
|
||||
latitude: 38.4900
|
||||
};
|
||||
|
||||
console.log('✓ 修改后的formData:', {
|
||||
longitude: modifiedFormData.longitude,
|
||||
latitude: modifiedFormData.latitude,
|
||||
longitude_type: typeof modifiedFormData.longitude,
|
||||
latitude_type: typeof modifiedFormData.latitude
|
||||
});
|
||||
|
||||
// 6. 测试更新养殖场API
|
||||
console.log('\n6. 测试更新养殖场API...');
|
||||
const updateReq = createMockReq({
|
||||
name: modifiedFormData.name,
|
||||
owner: modifiedFormData.owner,
|
||||
phone: modifiedFormData.phone,
|
||||
address: modifiedFormData.address,
|
||||
longitude: modifiedFormData.longitude,
|
||||
latitude: modifiedFormData.latitude,
|
||||
status: modifiedFormData.status
|
||||
}, { id: testFarmId });
|
||||
|
||||
const updateRes = createMockRes();
|
||||
await farmController.updateFarm(updateReq, updateRes);
|
||||
|
||||
console.log('✓ 更新API响应:', {
|
||||
status: updateRes.statusCode,
|
||||
success: updateRes.data?.success,
|
||||
location: updateRes.data?.data?.location
|
||||
});
|
||||
|
||||
// 7. 验证更新结果
|
||||
console.log('\n7. 验证更新结果...');
|
||||
const verifyReq = createMockReq({}, { id: testFarmId });
|
||||
const verifyRes = createMockRes();
|
||||
await farmController.getFarmById(verifyReq, verifyRes);
|
||||
|
||||
const updatedFarm = verifyRes.data?.data;
|
||||
console.log('✓ 更新后的数据:', {
|
||||
location: updatedFarm?.location,
|
||||
location_lng: updatedFarm?.location?.lng,
|
||||
location_lat: updatedFarm?.location?.lat,
|
||||
expected_lng: 106.2400,
|
||||
expected_lat: 38.4900,
|
||||
lng_match: updatedFarm?.location?.lng === 106.2400,
|
||||
lat_match: updatedFarm?.location?.lat === 38.4900
|
||||
});
|
||||
|
||||
// 8. 测试边界情况 - 清空经纬度
|
||||
console.log('\n8. 测试边界情况 - 清空经纬度...');
|
||||
const clearReq = createMockReq({
|
||||
name: modifiedFormData.name,
|
||||
owner: modifiedFormData.owner,
|
||||
phone: modifiedFormData.phone,
|
||||
address: modifiedFormData.address,
|
||||
longitude: undefined,
|
||||
latitude: undefined,
|
||||
status: modifiedFormData.status
|
||||
}, { id: testFarmId });
|
||||
|
||||
const clearRes = createMockRes();
|
||||
await farmController.updateFarm(clearReq, clearRes);
|
||||
|
||||
console.log('✓ 清空经纬度API响应:', {
|
||||
status: clearRes.statusCode,
|
||||
success: clearRes.data?.success,
|
||||
location: clearRes.data?.data?.location
|
||||
});
|
||||
|
||||
// 9. 验证清空结果
|
||||
console.log('\n9. 验证清空结果...');
|
||||
const verifyClearReq = createMockReq({}, { id: testFarmId });
|
||||
const verifyClearRes = createMockRes();
|
||||
await farmController.getFarmById(verifyClearReq, verifyClearRes);
|
||||
|
||||
const clearedFarm = verifyClearRes.data?.data;
|
||||
console.log('✓ 清空后的数据:', {
|
||||
location: clearedFarm?.location,
|
||||
location_lng: clearedFarm?.location?.lng,
|
||||
location_lat: clearedFarm?.location?.lat,
|
||||
has_lng: 'lng' in (clearedFarm?.location || {}),
|
||||
has_lat: 'lat' in (clearedFarm?.location || {})
|
||||
});
|
||||
|
||||
console.log('\n=== API数据绑定测试完成 ===');
|
||||
console.log('\n📋 测试总结:');
|
||||
console.log('1. ✅ 创建API正确处理经纬度数据');
|
||||
console.log('2. ✅ 获取API正确返回location对象');
|
||||
console.log('3. ✅ 前端数据解析逻辑正确');
|
||||
console.log('4. ✅ formData绑定逻辑正确');
|
||||
console.log('5. ✅ 用户修改数据处理正确');
|
||||
console.log('6. ✅ 更新API正确处理经纬度数据');
|
||||
console.log('7. ✅ 数据更新验证正确');
|
||||
console.log('8. ✅ 边界情况处理正确');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试过程中出现错误:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 清理测试数据
|
||||
if (testFarmId) {
|
||||
console.log('\n10. 清理测试数据...');
|
||||
try {
|
||||
await Farm.destroy({ where: { id: testFarmId } });
|
||||
console.log('✓ 测试数据已清理');
|
||||
} catch (error) {
|
||||
console.error('清理测试数据失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
testApiBinding()
|
||||
.then(() => {
|
||||
console.log('\n🎉 所有API绑定测试通过!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('\n💥 API绑定测试失败:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { testApiBinding };
|
||||
211
backend/tools/testing/test-clear-coordinates.js
Normal file
211
backend/tools/testing/test-clear-coordinates.js
Normal file
@@ -0,0 +1,211 @@
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database');
|
||||
const farmController = require('./controllers/farmController');
|
||||
|
||||
// 模拟Express请求和响应对象
|
||||
function createMockReq(body, params = {}) {
|
||||
return {
|
||||
body,
|
||||
params
|
||||
};
|
||||
}
|
||||
|
||||
function createMockRes() {
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
data: null,
|
||||
status: function(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
json: function(data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
// 测试清空经纬度的不同场景
|
||||
async function testClearCoordinates() {
|
||||
console.log('=== 清空经纬度测试 ===\n');
|
||||
|
||||
let testFarmId = null;
|
||||
|
||||
try {
|
||||
// 1. 创建带有经纬度的测试记录
|
||||
console.log('1. 创建带有经纬度的测试记录...');
|
||||
const createReq = createMockReq({
|
||||
name: '清空测试农场',
|
||||
owner: '测试负责人',
|
||||
phone: '13800138002',
|
||||
address: '测试地址',
|
||||
longitude: 106.2309,
|
||||
latitude: 38.4872,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
const createRes = createMockRes();
|
||||
await farmController.createFarm(createReq, createRes);
|
||||
|
||||
testFarmId = createRes.data.data.id;
|
||||
console.log('✓ 创建成功,location:', createRes.data.data.location);
|
||||
|
||||
// 2. 测试传入null值清空
|
||||
console.log('\n2. 测试传入null值清空...');
|
||||
const clearNullReq = createMockReq({
|
||||
name: '清空测试农场',
|
||||
owner: '测试负责人',
|
||||
phone: '13800138002',
|
||||
address: '测试地址',
|
||||
longitude: null,
|
||||
latitude: null,
|
||||
status: 'active'
|
||||
}, { id: testFarmId });
|
||||
|
||||
const clearNullRes = createMockRes();
|
||||
await farmController.updateFarm(clearNullReq, clearNullRes);
|
||||
|
||||
console.log('✓ null值清空结果:', {
|
||||
location: clearNullRes.data.data.location,
|
||||
has_lng: 'lng' in (clearNullRes.data.data.location || {}),
|
||||
has_lat: 'lat' in (clearNullRes.data.data.location || {})
|
||||
});
|
||||
|
||||
// 3. 重新设置经纬度
|
||||
console.log('\n3. 重新设置经纬度...');
|
||||
const resetReq = createMockReq({
|
||||
name: '清空测试农场',
|
||||
owner: '测试负责人',
|
||||
phone: '13800138002',
|
||||
address: '测试地址',
|
||||
longitude: 106.2400,
|
||||
latitude: 38.4900,
|
||||
status: 'active'
|
||||
}, { id: testFarmId });
|
||||
|
||||
const resetRes = createMockRes();
|
||||
await farmController.updateFarm(resetReq, resetRes);
|
||||
|
||||
console.log('✓ 重新设置结果:', resetRes.data.data.location);
|
||||
|
||||
// 4. 测试传入空字符串清空
|
||||
console.log('\n4. 测试传入空字符串清空...');
|
||||
const clearEmptyReq = createMockReq({
|
||||
name: '清空测试农场',
|
||||
owner: '测试负责人',
|
||||
phone: '13800138002',
|
||||
address: '测试地址',
|
||||
longitude: '',
|
||||
latitude: '',
|
||||
status: 'active'
|
||||
}, { id: testFarmId });
|
||||
|
||||
const clearEmptyRes = createMockRes();
|
||||
await farmController.updateFarm(clearEmptyReq, clearEmptyRes);
|
||||
|
||||
console.log('✓ 空字符串清空结果:', {
|
||||
location: clearEmptyRes.data.data.location,
|
||||
has_lng: 'lng' in (clearEmptyRes.data.data.location || {}),
|
||||
has_lat: 'lat' in (clearEmptyRes.data.data.location || {})
|
||||
});
|
||||
|
||||
// 5. 重新设置经纬度
|
||||
console.log('\n5. 再次重新设置经纬度...');
|
||||
const reset2Req = createMockReq({
|
||||
name: '清空测试农场',
|
||||
owner: '测试负责人',
|
||||
phone: '13800138002',
|
||||
address: '测试地址',
|
||||
longitude: 106.2500,
|
||||
latitude: 38.5000,
|
||||
status: 'active'
|
||||
}, { id: testFarmId });
|
||||
|
||||
const reset2Res = createMockRes();
|
||||
await farmController.updateFarm(reset2Req, reset2Res);
|
||||
|
||||
console.log('✓ 再次设置结果:', reset2Res.data.data.location);
|
||||
|
||||
// 6. 测试不传入经纬度字段(undefined)
|
||||
console.log('\n6. 测试不传入经纬度字段(undefined)...');
|
||||
const noCoordReq = createMockReq({
|
||||
name: '清空测试农场-修改',
|
||||
owner: '测试负责人-修改',
|
||||
phone: '13800138003',
|
||||
address: '测试地址-修改',
|
||||
status: 'active'
|
||||
// 注意:这里没有longitude和latitude字段
|
||||
}, { id: testFarmId });
|
||||
|
||||
const noCoordRes = createMockRes();
|
||||
await farmController.updateFarm(noCoordReq, noCoordRes);
|
||||
|
||||
console.log('✓ 不传入经纬度字段结果:', {
|
||||
location: noCoordRes.data.data.location,
|
||||
has_lng: 'lng' in (noCoordRes.data.data.location || {}),
|
||||
has_lat: 'lat' in (noCoordRes.data.data.location || {}),
|
||||
name: noCoordRes.data.data.name
|
||||
});
|
||||
|
||||
// 7. 测试只清空其中一个坐标
|
||||
console.log('\n7. 测试只清空经度,保留纬度...');
|
||||
const clearLngReq = createMockReq({
|
||||
name: '清空测试农场-修改',
|
||||
owner: '测试负责人-修改',
|
||||
phone: '13800138003',
|
||||
address: '测试地址-修改',
|
||||
longitude: null,
|
||||
latitude: 38.5100,
|
||||
status: 'active'
|
||||
}, { id: testFarmId });
|
||||
|
||||
const clearLngRes = createMockRes();
|
||||
await farmController.updateFarm(clearLngReq, clearLngRes);
|
||||
|
||||
console.log('✓ 只清空经度结果:', {
|
||||
location: clearLngRes.data.data.location,
|
||||
has_lng: 'lng' in (clearLngRes.data.data.location || {}),
|
||||
has_lat: 'lat' in (clearLngRes.data.data.location || {}),
|
||||
lng_value: clearLngRes.data.data.location?.lng,
|
||||
lat_value: clearLngRes.data.data.location?.lat
|
||||
});
|
||||
|
||||
console.log('\n=== 清空经纬度测试完成 ===');
|
||||
console.log('\n📋 测试总结:');
|
||||
console.log('1. ✅ null值可以正确清空经纬度');
|
||||
console.log('2. ✅ 空字符串可以正确清空经纬度');
|
||||
console.log('3. ✅ 不传入字段时保持原有值');
|
||||
console.log('4. ✅ 可以单独清空其中一个坐标');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试过程中出现错误:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 清理测试数据
|
||||
if (testFarmId) {
|
||||
console.log('\n8. 清理测试数据...');
|
||||
try {
|
||||
await Farm.destroy({ where: { id: testFarmId } });
|
||||
console.log('✓ 测试数据已清理');
|
||||
} catch (error) {
|
||||
console.error('清理测试数据失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
testClearCoordinates()
|
||||
.then(() => {
|
||||
console.log('\n🎉 所有清空经纬度测试通过!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('\n💥 清空经纬度测试失败:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { testClearCoordinates };
|
||||
191
backend/tools/testing/test-coordinate-edit-flow.js
Normal file
191
backend/tools/testing/test-coordinate-edit-flow.js
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 测试经纬度编辑功能的完整流程
|
||||
* 验证数据显示和更新的准确性
|
||||
* @file test-coordinate-edit-flow.js
|
||||
*/
|
||||
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function testCoordinateEditFlow() {
|
||||
try {
|
||||
console.log('开始测试经纬度编辑功能...');
|
||||
await sequelize.authenticate();
|
||||
|
||||
// 1. 创建测试记录
|
||||
console.log('\n=== 步骤1: 创建测试记录 ===');
|
||||
const testFarm = await Farm.create({
|
||||
name: '经纬度编辑测试农场',
|
||||
type: 'farm',
|
||||
location: {
|
||||
lng: 106.2309,
|
||||
lat: 38.4872
|
||||
},
|
||||
address: '宁夏回族自治区银川市',
|
||||
contact: '测试用户',
|
||||
phone: '13800138000',
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
console.log(`✅ 创建成功 - ID: ${testFarm.id}`);
|
||||
console.log(` 初始经度: ${testFarm.location.lng}`);
|
||||
console.log(` 初始纬度: ${testFarm.location.lat}`);
|
||||
|
||||
// 2. 模拟前端获取数据进行编辑
|
||||
console.log('\n=== 步骤2: 模拟前端获取编辑数据 ===');
|
||||
const farmForEdit = await Farm.findByPk(testFarm.id);
|
||||
|
||||
// 模拟前端editFarm函数的数据解析
|
||||
const editFormData = {
|
||||
id: farmForEdit.id,
|
||||
name: farmForEdit.name,
|
||||
address: farmForEdit.address,
|
||||
contact: farmForEdit.contact,
|
||||
phone: farmForEdit.phone,
|
||||
status: farmForEdit.status,
|
||||
longitude: farmForEdit.location?.lng,
|
||||
latitude: farmForEdit.location?.lat
|
||||
};
|
||||
|
||||
console.log('前端编辑表单数据:');
|
||||
console.log(` 经度输入框值: ${editFormData.longitude} (类型: ${typeof editFormData.longitude})`);
|
||||
console.log(` 纬度输入框值: ${editFormData.latitude} (类型: ${typeof editFormData.latitude})`);
|
||||
|
||||
// 验证数据正确性
|
||||
const isDataCorrect = (
|
||||
editFormData.longitude === testFarm.location.lng &&
|
||||
editFormData.latitude === testFarm.location.lat
|
||||
);
|
||||
|
||||
console.log(`数据显示正确性: ${isDataCorrect ? '✅ 正确' : '❌ 错误'}`);
|
||||
|
||||
// 3. 模拟用户修改经纬度值
|
||||
console.log('\n=== 步骤3: 模拟用户修改经纬度值 ===');
|
||||
const modifiedData = {
|
||||
...editFormData,
|
||||
longitude: 106.5507, // 修改经度
|
||||
latitude: 38.7123 // 修改纬度
|
||||
};
|
||||
|
||||
console.log('用户修改后的值:');
|
||||
console.log(` 新经度: ${modifiedData.longitude}`);
|
||||
console.log(` 新纬度: ${modifiedData.latitude}`);
|
||||
|
||||
// 4. 模拟后端更新操作
|
||||
console.log('\n=== 步骤4: 模拟后端更新操作 ===');
|
||||
|
||||
// 构建新的location对象(模拟updateFarm函数逻辑)
|
||||
const newLocation = {};
|
||||
if (modifiedData.longitude !== undefined && modifiedData.longitude !== null) {
|
||||
newLocation.lng = parseFloat(modifiedData.longitude);
|
||||
}
|
||||
if (modifiedData.latitude !== undefined && modifiedData.latitude !== null) {
|
||||
newLocation.lat = parseFloat(modifiedData.latitude);
|
||||
}
|
||||
|
||||
console.log('构建的location对象:', JSON.stringify(newLocation));
|
||||
|
||||
// 执行更新
|
||||
await farmForEdit.update({ location: newLocation });
|
||||
console.log('✅ 数据库更新完成');
|
||||
|
||||
// 5. 验证更新结果
|
||||
console.log('\n=== 步骤5: 验证更新结果 ===');
|
||||
const updatedFarm = await Farm.findByPk(testFarm.id);
|
||||
|
||||
console.log('更新后的数据库记录:');
|
||||
console.log(` 经度: ${updatedFarm.location.lng} (类型: ${typeof updatedFarm.location.lng})`);
|
||||
console.log(` 纬度: ${updatedFarm.location.lat} (类型: ${typeof updatedFarm.location.lat})`);
|
||||
|
||||
// 验证更新准确性
|
||||
const isUpdateCorrect = (
|
||||
updatedFarm.location.lng === modifiedData.longitude &&
|
||||
updatedFarm.location.lat === modifiedData.latitude
|
||||
);
|
||||
|
||||
console.log(`更新准确性: ${isUpdateCorrect ? '✅ 正确' : '❌ 错误'}`);
|
||||
|
||||
// 6. 模拟再次编辑(验证修改后的值能正确显示)
|
||||
console.log('\n=== 步骤6: 验证修改后的值能正确显示 ===');
|
||||
const farmForSecondEdit = await Farm.findByPk(testFarm.id);
|
||||
|
||||
const secondEditFormData = {
|
||||
id: farmForSecondEdit.id,
|
||||
name: farmForSecondEdit.name,
|
||||
longitude: farmForSecondEdit.location?.lng,
|
||||
latitude: farmForSecondEdit.location?.lat
|
||||
};
|
||||
|
||||
console.log('第二次编辑时的表单数据:');
|
||||
console.log(` 经度输入框值: ${secondEditFormData.longitude}`);
|
||||
console.log(` 纬度输入框值: ${secondEditFormData.latitude}`);
|
||||
|
||||
// 验证显示的是最新修改的值
|
||||
const isSecondDisplayCorrect = (
|
||||
secondEditFormData.longitude === modifiedData.longitude &&
|
||||
secondEditFormData.latitude === modifiedData.latitude
|
||||
);
|
||||
|
||||
console.log(`修改后值显示正确性: ${isSecondDisplayCorrect ? '✅ 正确' : '❌ 错误'}`);
|
||||
|
||||
// 7. 测试边界情况
|
||||
console.log('\n=== 步骤7: 测试边界情况 ===');
|
||||
|
||||
// 测试清空经纬度
|
||||
console.log('测试清空经纬度...');
|
||||
await farmForSecondEdit.update({
|
||||
location: {}
|
||||
});
|
||||
|
||||
const farmWithEmptyLocation = await Farm.findByPk(testFarm.id);
|
||||
const emptyEditFormData = {
|
||||
longitude: farmWithEmptyLocation.location?.lng,
|
||||
latitude: farmWithEmptyLocation.location?.lat
|
||||
};
|
||||
|
||||
console.log('清空后的表单数据:');
|
||||
console.log(` 经度: ${emptyEditFormData.longitude} (${typeof emptyEditFormData.longitude})`);
|
||||
console.log(` 纬度: ${emptyEditFormData.latitude} (${typeof emptyEditFormData.latitude})`);
|
||||
|
||||
// 8. 生成测试报告
|
||||
console.log('\n=== 测试报告 ===');
|
||||
const allTestsPassed = isDataCorrect && isUpdateCorrect && isSecondDisplayCorrect;
|
||||
|
||||
console.log(`总体结果: ${allTestsPassed ? '✅ 所有测试通过' : '❌ 存在问题'}`);
|
||||
console.log('详细结果:');
|
||||
console.log(` - 初始数据显示: ${isDataCorrect ? '✅' : '❌'}`);
|
||||
console.log(` - 数据更新准确性: ${isUpdateCorrect ? '✅' : '❌'}`);
|
||||
console.log(` - 修改后值显示: ${isSecondDisplayCorrect ? '✅' : '❌'}`);
|
||||
|
||||
// 9. 清理测试数据
|
||||
console.log('\n=== 清理测试数据 ===');
|
||||
await testFarm.destroy();
|
||||
console.log('✅ 测试数据已清理');
|
||||
|
||||
return {
|
||||
success: allTestsPassed,
|
||||
results: {
|
||||
initialDisplay: isDataCorrect,
|
||||
updateAccuracy: isUpdateCorrect,
|
||||
modifiedDisplay: isSecondDisplayCorrect
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
if (error.sql) {
|
||||
console.error('SQL:', error.sql);
|
||||
}
|
||||
return { success: false, error: error.message };
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
testCoordinateEditFlow();
|
||||
}
|
||||
|
||||
module.exports = { testCoordinateEditFlow };
|
||||
229
backend/tools/testing/test-coordinate-input-flow.js
Normal file
229
backend/tools/testing/test-coordinate-input-flow.js
Normal file
@@ -0,0 +1,229 @@
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database');
|
||||
const farmController = require('./controllers/farmController');
|
||||
|
||||
// 模拟Express请求和响应对象
|
||||
function createMockReq(body, params = {}) {
|
||||
return {
|
||||
body,
|
||||
params
|
||||
};
|
||||
}
|
||||
|
||||
function createMockRes() {
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
data: null,
|
||||
status: function(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
json: function(data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
// 模拟前端输入处理逻辑
|
||||
function simulateFrontendInputProcessing(userInput) {
|
||||
console.log(`\n🔍 模拟用户输入: "${userInput}"`);
|
||||
|
||||
// 模拟 a-input-number 的 parser 函数
|
||||
const parser = (value) => {
|
||||
if (!value) return value;
|
||||
// 移除非数字字符,保留小数点和负号
|
||||
const cleaned = value.toString().replace(/[^\d.-]/g, '');
|
||||
// 确保只有一个小数点和负号在开头
|
||||
const parts = cleaned.split('.');
|
||||
if (parts.length > 2) {
|
||||
return parts[0] + '.' + parts.slice(1).join('');
|
||||
}
|
||||
return cleaned;
|
||||
};
|
||||
|
||||
const parsedValue = parser(userInput);
|
||||
console.log(` 📝 Parser处理后: "${parsedValue}"`);
|
||||
|
||||
// 模拟 v-model 的数值转换
|
||||
let finalValue;
|
||||
if (parsedValue === '' || parsedValue === undefined || parsedValue === null) {
|
||||
finalValue = undefined;
|
||||
} else {
|
||||
const numValue = parseFloat(parsedValue);
|
||||
finalValue = isNaN(numValue) ? undefined : numValue;
|
||||
}
|
||||
|
||||
console.log(` 🔢 最终绑定值: ${finalValue} (类型: ${typeof finalValue})`);
|
||||
|
||||
return finalValue;
|
||||
}
|
||||
|
||||
// 测试各种用户输入场景
|
||||
async function testCoordinateInputFlow() {
|
||||
console.log('=== 经纬度输入流程测试 ===\n');
|
||||
|
||||
let testFarmId = null;
|
||||
|
||||
try {
|
||||
// 1. 创建测试记录
|
||||
console.log('1. 创建测试养殖场...');
|
||||
const createReq = createMockReq({
|
||||
name: '输入测试农场',
|
||||
owner: '测试负责人',
|
||||
phone: '13800138003',
|
||||
address: '测试地址',
|
||||
longitude: 106.2309,
|
||||
latitude: 38.4872,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
const createRes = createMockRes();
|
||||
await farmController.createFarm(createReq, createRes);
|
||||
|
||||
testFarmId = createRes.data.data.id;
|
||||
console.log('✓ 创建成功,初始location:', createRes.data.data.location);
|
||||
|
||||
// 2. 测试各种用户输入场景
|
||||
const testCases = [
|
||||
{
|
||||
name: '正常小数输入',
|
||||
longitude: '106.2400',
|
||||
latitude: '38.4900'
|
||||
},
|
||||
{
|
||||
name: '整数输入',
|
||||
longitude: '106',
|
||||
latitude: '38'
|
||||
},
|
||||
{
|
||||
name: '高精度小数输入',
|
||||
longitude: '106.234567',
|
||||
latitude: '38.487654'
|
||||
},
|
||||
{
|
||||
name: '负数输入',
|
||||
longitude: '-106.2400',
|
||||
latitude: '-38.4900'
|
||||
},
|
||||
{
|
||||
name: '包含非法字符的输入',
|
||||
longitude: '106.24abc',
|
||||
latitude: '38.49xyz'
|
||||
},
|
||||
{
|
||||
name: '多个小数点的输入',
|
||||
longitude: '106.24.56',
|
||||
latitude: '38.49.78'
|
||||
},
|
||||
{
|
||||
name: '空字符串输入',
|
||||
longitude: '',
|
||||
latitude: ''
|
||||
},
|
||||
{
|
||||
name: '只输入小数点',
|
||||
longitude: '.',
|
||||
latitude: '.'
|
||||
},
|
||||
{
|
||||
name: '边界值输入',
|
||||
longitude: '180.0',
|
||||
latitude: '90.0'
|
||||
},
|
||||
{
|
||||
name: '超出范围的输入',
|
||||
longitude: '200.0',
|
||||
latitude: '100.0'
|
||||
}
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const testCase = testCases[i];
|
||||
console.log(`\n${i + 2}. 测试场景: ${testCase.name}`);
|
||||
|
||||
// 模拟前端输入处理
|
||||
const processedLongitude = simulateFrontendInputProcessing(testCase.longitude);
|
||||
const processedLatitude = simulateFrontendInputProcessing(testCase.latitude);
|
||||
|
||||
// 构建提交数据
|
||||
const submitData = {
|
||||
name: '输入测试农场',
|
||||
owner: '测试负责人',
|
||||
phone: '13800138003',
|
||||
address: '测试地址',
|
||||
longitude: processedLongitude,
|
||||
latitude: processedLatitude,
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
console.log(` 📤 提交数据:`, {
|
||||
longitude: submitData.longitude,
|
||||
latitude: submitData.latitude
|
||||
});
|
||||
|
||||
// 调用后端API
|
||||
const updateReq = createMockReq(submitData, { id: testFarmId });
|
||||
const updateRes = createMockRes();
|
||||
|
||||
try {
|
||||
await farmController.updateFarm(updateReq, updateRes);
|
||||
|
||||
console.log(` ✅ 更新成功:`, {
|
||||
location: updateRes.data.data.location,
|
||||
has_lng: 'lng' in (updateRes.data.data.location || {}),
|
||||
has_lat: 'lat' in (updateRes.data.data.location || {})
|
||||
});
|
||||
|
||||
// 验证数据范围
|
||||
const location = updateRes.data.data.location || {};
|
||||
if (location.lng !== undefined) {
|
||||
if (location.lng < -180 || location.lng > 180) {
|
||||
console.log(` ⚠️ 警告: 经度超出有效范围 (-180~180): ${location.lng}`);
|
||||
}
|
||||
}
|
||||
if (location.lat !== undefined) {
|
||||
if (location.lat < -90 || location.lat > 90) {
|
||||
console.log(` ⚠️ 警告: 纬度超出有效范围 (-90~90): ${location.lat}`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ 更新失败:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== 输入流程测试完成 ===');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试过程中出现错误:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 清理测试数据
|
||||
if (testFarmId) {
|
||||
console.log('\n🧹 清理测试数据...');
|
||||
try {
|
||||
await Farm.destroy({ where: { id: testFarmId } });
|
||||
console.log('✓ 测试数据已清理');
|
||||
} catch (error) {
|
||||
console.error('清理测试数据失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
testCoordinateInputFlow()
|
||||
.then(() => {
|
||||
console.log('\n🎉 所有输入流程测试完成!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('\n💥 输入流程测试失败:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { testCoordinateInputFlow };
|
||||
142
backend/tools/testing/test-data-sync.js
Normal file
142
backend/tools/testing/test-data-sync.js
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 测试前端和后端数据同步
|
||||
* 验证经纬度数据的完整流程
|
||||
* @file test-data-sync.js
|
||||
*/
|
||||
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function testDataSync() {
|
||||
try {
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
|
||||
console.log('\n=== 测试数据同步 ===');
|
||||
|
||||
// 1. 创建测试记录
|
||||
console.log('\n1. 创建测试记录...');
|
||||
const testFarm = await Farm.create({
|
||||
name: '数据同步测试农场',
|
||||
type: 'farm',
|
||||
location: {
|
||||
lng: 106.28,
|
||||
lat: 38.47
|
||||
},
|
||||
address: '宁夏回族自治区银川市测试区',
|
||||
contact: '测试管理员',
|
||||
phone: '13800138000',
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
console.log(`✅ 创建成功 - ID: ${testFarm.id}`);
|
||||
console.log(` 经度: ${testFarm.location.lng}`);
|
||||
console.log(` 纬度: ${testFarm.location.lat}`);
|
||||
|
||||
// 2. 查询记录验证
|
||||
console.log('\n2. 查询记录验证...');
|
||||
const retrievedFarm = await Farm.findByPk(testFarm.id);
|
||||
console.log(`查询结果:`);
|
||||
console.log(` ID: ${retrievedFarm.id}`);
|
||||
console.log(` 名称: ${retrievedFarm.name}`);
|
||||
console.log(` Location类型: ${typeof retrievedFarm.location}`);
|
||||
console.log(` Location值: ${JSON.stringify(retrievedFarm.location)}`);
|
||||
console.log(` 经度: ${retrievedFarm.location.lng} (类型: ${typeof retrievedFarm.location.lng})`);
|
||||
console.log(` 纬度: ${retrievedFarm.location.lat} (类型: ${typeof retrievedFarm.location.lat})`);
|
||||
|
||||
// 3. 模拟API响应格式
|
||||
console.log('\n3. 模拟API响应格式...');
|
||||
const apiResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
id: retrievedFarm.id,
|
||||
name: retrievedFarm.name,
|
||||
location: retrievedFarm.location,
|
||||
address: retrievedFarm.address,
|
||||
contact: retrievedFarm.contact,
|
||||
phone: retrievedFarm.phone,
|
||||
status: retrievedFarm.status
|
||||
}
|
||||
};
|
||||
|
||||
console.log('API响应格式:');
|
||||
console.log(JSON.stringify(apiResponse, null, 2));
|
||||
|
||||
// 4. 模拟前端数据解析
|
||||
console.log('\n4. 模拟前端数据解析...');
|
||||
const frontendData = apiResponse.data;
|
||||
const formData = {
|
||||
id: frontendData.id,
|
||||
name: frontendData.name,
|
||||
address: frontendData.address,
|
||||
contact: frontendData.contact,
|
||||
phone: frontendData.phone,
|
||||
status: frontendData.status,
|
||||
longitude: frontendData.location?.lng,
|
||||
latitude: frontendData.location?.lat
|
||||
};
|
||||
|
||||
console.log('前端表单数据:');
|
||||
console.log(` 经度: ${formData.longitude} (类型: ${typeof formData.longitude})`);
|
||||
console.log(` 纬度: ${formData.latitude} (类型: ${typeof formData.latitude})`);
|
||||
|
||||
// 5. 模拟更新操作
|
||||
console.log('\n5. 模拟更新操作...');
|
||||
const updateData = {
|
||||
longitude: 106.30,
|
||||
latitude: 38.50
|
||||
};
|
||||
|
||||
// 构建新的location对象
|
||||
const newLocation = {
|
||||
lng: parseFloat(updateData.longitude),
|
||||
lat: parseFloat(updateData.latitude)
|
||||
};
|
||||
|
||||
await retrievedFarm.update({ location: newLocation });
|
||||
|
||||
// 验证更新结果
|
||||
const updatedFarm = await Farm.findByPk(testFarm.id);
|
||||
console.log('更新后的数据:');
|
||||
console.log(` 经度: ${updatedFarm.location.lng}`);
|
||||
console.log(` 纬度: ${updatedFarm.location.lat}`);
|
||||
|
||||
// 6. 验证数据一致性
|
||||
console.log('\n6. 验证数据一致性...');
|
||||
const isConsistent = (
|
||||
updatedFarm.location.lng === updateData.longitude &&
|
||||
updatedFarm.location.lat === updateData.latitude
|
||||
);
|
||||
|
||||
console.log(`数据一致性检查: ${isConsistent ? '✅ 通过' : '❌ 失败'}`);
|
||||
console.log(` 期望经度: ${updateData.longitude}`);
|
||||
console.log(` 实际经度: ${updatedFarm.location.lng}`);
|
||||
console.log(` 期望纬度: ${updateData.latitude}`);
|
||||
console.log(` 实际纬度: ${updatedFarm.location.lat}`);
|
||||
|
||||
// 7. 清理测试数据
|
||||
console.log('\n7. 清理测试数据...');
|
||||
await testFarm.destroy();
|
||||
console.log('✅ 测试数据已清理');
|
||||
|
||||
console.log('\n=== 测试完成 ===');
|
||||
console.log(`结果: ${isConsistent ? '数据同步正常' : '数据同步异常'}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
if (error.sql) {
|
||||
console.error('SQL:', error.sql);
|
||||
}
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
console.log('开始测试数据同步...');
|
||||
testDataSync();
|
||||
}
|
||||
|
||||
module.exports = { testDataSync };
|
||||
63
backend/tools/testing/test-db-connection.js
Normal file
63
backend/tools/testing/test-db-connection.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
require('dotenv').config();
|
||||
|
||||
async function testConnection() {
|
||||
const config = {
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'Aiotagro@741',
|
||||
database: process.env.DB_NAME || 'nxTest',
|
||||
connectTimeout: 10000,
|
||||
acquireTimeout: 10000,
|
||||
timeout: 10000
|
||||
};
|
||||
|
||||
console.log('尝试连接数据库...');
|
||||
console.log('配置:', {
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
user: config.user,
|
||||
database: config.database
|
||||
});
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(config);
|
||||
console.log('✅ 数据库连接成功!');
|
||||
|
||||
// 测试查询
|
||||
const [rows] = await connection.execute('SELECT 1 as test');
|
||||
console.log('✅ 查询测试成功:', rows);
|
||||
|
||||
// 获取数据库信息
|
||||
const [dbInfo] = await connection.execute('SELECT DATABASE() as current_db, VERSION() as version');
|
||||
console.log('✅ 数据库信息:', dbInfo);
|
||||
|
||||
await connection.end();
|
||||
console.log('✅ 连接已关闭');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接失败:');
|
||||
console.error('错误代码:', error.code);
|
||||
console.error('错误消息:', error.message);
|
||||
console.error('完整错误:', error);
|
||||
|
||||
// 提供一些常见错误的解决方案
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
console.log('\n💡 可能的解决方案:');
|
||||
console.log('1. 检查MySQL服务是否正在运行');
|
||||
console.log('2. 检查端口3306是否开放');
|
||||
console.log('3. 检查防火墙设置');
|
||||
} else if (error.code === 'ER_ACCESS_DENIED_ERROR') {
|
||||
console.log('\n💡 可能的解决方案:');
|
||||
console.log('1. 检查用户名和密码是否正确');
|
||||
console.log('2. 检查用户是否有远程访问权限');
|
||||
} else if (error.code === 'ER_BAD_DB_ERROR') {
|
||||
console.log('\n💡 可能的解决方案:');
|
||||
console.log('1. 检查数据库名称是否正确');
|
||||
console.log('2. 检查数据库是否存在');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testConnection();
|
||||
115
backend/tools/testing/test-devices-api.js
Normal file
115
backend/tools/testing/test-devices-api.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 测试设备API和数据导入功能
|
||||
*/
|
||||
|
||||
const { Device, Farm } = require('./models');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
async function testDevicesData() {
|
||||
try {
|
||||
console.log('=== 测试设备数据导入功能 ===\n');
|
||||
|
||||
// 1. 检查数据库中的设备数据
|
||||
console.log('1. 检查数据库中的设备数据:');
|
||||
const devices = await Device.findAll({
|
||||
include: [{
|
||||
model: Farm,
|
||||
as: 'farm',
|
||||
attributes: ['id', 'name', 'location']
|
||||
}],
|
||||
limit: 10
|
||||
});
|
||||
|
||||
console.log(` - 数据库中共有 ${await Device.count()} 个设备`);
|
||||
console.log(' - 前10个设备信息:');
|
||||
devices.forEach((device, index) => {
|
||||
console.log(` ${index + 1}. ID: ${device.id}, 名称: ${device.name}, 类型: ${device.type}, 状态: ${device.status}`);
|
||||
if (device.farm) {
|
||||
console.log(` 所属农场: ${device.farm.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 测试设备API响应格式
|
||||
console.log('\n2. 测试设备API响应格式:');
|
||||
const deviceController = require('./controllers/deviceController');
|
||||
|
||||
// 模拟API请求
|
||||
const mockReq = {
|
||||
query: { page: 1, limit: 5 }
|
||||
};
|
||||
|
||||
const mockRes = {
|
||||
json: (data) => {
|
||||
console.log(' - API响应格式正确');
|
||||
console.log(` - 返回设备数量: ${data.data ? data.data.length : 0}`);
|
||||
if (data.data && data.data.length > 0) {
|
||||
console.log(' - 第一个设备数据结构:');
|
||||
const firstDevice = data.data[0];
|
||||
console.log(` * ID: ${firstDevice.id}`);
|
||||
console.log(` * 名称: ${firstDevice.name}`);
|
||||
console.log(` * 类型: ${firstDevice.type}`);
|
||||
console.log(` * 状态: ${firstDevice.status}`);
|
||||
console.log(` * 农场: ${firstDevice.farm ? firstDevice.farm.name : '未关联'}`);
|
||||
console.log(` * 安装日期: ${firstDevice.installation_date}`);
|
||||
console.log(` * 最后维护: ${firstDevice.last_maintenance}`);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
status: (code) => ({
|
||||
json: (data) => {
|
||||
console.log(` - API返回状态码: ${code}`);
|
||||
if (code !== 200) {
|
||||
console.log(` - 错误信息: ${data.message}`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
await deviceController.getAllDevices(mockReq, mockRes);
|
||||
|
||||
// 3. 检查数据完整性
|
||||
console.log('\n3. 检查数据完整性:');
|
||||
const deviceTypes = await Device.findAll({
|
||||
attributes: ['type'],
|
||||
group: ['type']
|
||||
});
|
||||
|
||||
console.log(' - 设备类型统计:');
|
||||
for (const deviceType of deviceTypes) {
|
||||
const count = await Device.count({ where: { type: deviceType.type } });
|
||||
console.log(` * ${deviceType.type}: ${count} 个`);
|
||||
}
|
||||
|
||||
const statusStats = await Device.findAll({
|
||||
attributes: ['status'],
|
||||
group: ['status']
|
||||
});
|
||||
|
||||
console.log(' - 设备状态统计:');
|
||||
for (const status of statusStats) {
|
||||
const count = await Device.count({ where: { status: status.status } });
|
||||
console.log(` * ${status.status}: ${count} 个`);
|
||||
}
|
||||
|
||||
console.log('\n=== 设备数据导入功能测试完成 ===');
|
||||
console.log('✅ 数据库中的设备数据已成功准备好,可以在前端设备管理模块中正常显示');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试过程中出现错误:', error.message);
|
||||
console.error(error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
testDevicesData().then(() => {
|
||||
process.exit(0);
|
||||
}).catch((error) => {
|
||||
console.error('测试失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { testDevicesData };
|
||||
193
backend/tools/testing/test-frontend-binding.js
Normal file
193
backend/tools/testing/test-frontend-binding.js
Normal file
@@ -0,0 +1,193 @@
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
// 测试前端数据绑定的完整流程
|
||||
async function testFrontendBinding() {
|
||||
console.log('=== 前端数据绑定测试 ===\n');
|
||||
|
||||
try {
|
||||
// 1. 创建测试记录
|
||||
console.log('1. 创建测试记录...');
|
||||
const testFarm = await Farm.create({
|
||||
name: '绑定测试农场',
|
||||
type: '养殖场',
|
||||
contact: '测试负责人',
|
||||
phone: '13800138000',
|
||||
address: '测试地址',
|
||||
longitude: 106.2309,
|
||||
latitude: 38.4872,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
console.log('✓ 测试记录创建成功:', {
|
||||
id: testFarm.id,
|
||||
name: testFarm.name,
|
||||
location: testFarm.location
|
||||
});
|
||||
|
||||
// 2. 模拟API查询返回的数据格式
|
||||
console.log('\n2. 模拟API查询返回数据...');
|
||||
const apiResponse = await Farm.findByPk(testFarm.id, {
|
||||
attributes: ['id', 'name', 'type', 'contact', 'phone', 'address', 'location', 'status', 'created_at']
|
||||
});
|
||||
|
||||
console.log('✓ API返回数据格式:', {
|
||||
id: apiResponse.id,
|
||||
name: apiResponse.name,
|
||||
location: apiResponse.location,
|
||||
location_type: typeof apiResponse.location,
|
||||
location_structure: apiResponse.location ? Object.keys(apiResponse.location) : 'null'
|
||||
});
|
||||
|
||||
// 3. 模拟前端editFarm函数的数据解析
|
||||
console.log('\n3. 模拟前端editFarm数据解析...');
|
||||
const record = apiResponse.toJSON();
|
||||
|
||||
// 前端解析逻辑
|
||||
const longitude = record.location?.lng || undefined;
|
||||
const latitude = record.location?.lat || undefined;
|
||||
|
||||
console.log('✓ 前端解析结果:', {
|
||||
original_location: record.location,
|
||||
parsed_longitude: longitude,
|
||||
parsed_latitude: latitude,
|
||||
longitude_type: typeof longitude,
|
||||
latitude_type: typeof latitude
|
||||
});
|
||||
|
||||
// 4. 模拟前端formData绑定
|
||||
console.log('\n4. 模拟前端formData绑定...');
|
||||
const formData = {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
owner: record.contact || '',
|
||||
phone: record.phone,
|
||||
address: record.address,
|
||||
longitude: longitude,
|
||||
latitude: latitude,
|
||||
status: record.status
|
||||
};
|
||||
|
||||
console.log('✓ formData绑定结果:', {
|
||||
longitude: formData.longitude,
|
||||
latitude: formData.latitude,
|
||||
longitude_type: typeof formData.longitude,
|
||||
latitude_type: typeof formData.latitude
|
||||
});
|
||||
|
||||
// 5. 测试数据类型转换
|
||||
console.log('\n5. 测试数据类型转换...');
|
||||
|
||||
// 模拟用户输入修改
|
||||
const userInputLongitude = '106.2400';
|
||||
const userInputLatitude = '38.4900';
|
||||
|
||||
// 模拟前端输入框的数据处理
|
||||
const processedLongitude = parseFloat(userInputLongitude);
|
||||
const processedLatitude = parseFloat(userInputLatitude);
|
||||
|
||||
console.log('✓ 用户输入处理:', {
|
||||
input_longitude: userInputLongitude,
|
||||
input_latitude: userInputLatitude,
|
||||
processed_longitude: processedLongitude,
|
||||
processed_latitude: processedLatitude,
|
||||
processed_longitude_type: typeof processedLongitude,
|
||||
processed_latitude_type: typeof processedLatitude
|
||||
});
|
||||
|
||||
// 6. 模拟提交数据
|
||||
console.log('\n6. 模拟提交更新...');
|
||||
const submitData = {
|
||||
...formData,
|
||||
longitude: processedLongitude,
|
||||
latitude: processedLatitude
|
||||
};
|
||||
|
||||
// 更新记录
|
||||
await testFarm.update(submitData);
|
||||
|
||||
// 验证更新结果
|
||||
const updatedFarm = await Farm.findByPk(testFarm.id);
|
||||
console.log('✓ 更新后的数据:', {
|
||||
location: updatedFarm.location,
|
||||
location_lng: updatedFarm.location?.lng,
|
||||
location_lat: updatedFarm.location?.lat
|
||||
});
|
||||
|
||||
// 7. 测试边界情况
|
||||
console.log('\n7. 测试边界情况...');
|
||||
|
||||
// 测试undefined值
|
||||
const testUndefined = {
|
||||
longitude: undefined,
|
||||
latitude: undefined
|
||||
};
|
||||
|
||||
console.log('✓ undefined值测试:', {
|
||||
longitude_undefined: testUndefined.longitude,
|
||||
latitude_undefined: testUndefined.latitude,
|
||||
longitude_type: typeof testUndefined.longitude,
|
||||
latitude_type: typeof testUndefined.latitude
|
||||
});
|
||||
|
||||
// 测试null值
|
||||
const testNull = {
|
||||
longitude: null,
|
||||
latitude: null
|
||||
};
|
||||
|
||||
console.log('✓ null值测试:', {
|
||||
longitude_null: testNull.longitude,
|
||||
latitude_null: testNull.latitude,
|
||||
longitude_type: typeof testNull.longitude,
|
||||
latitude_type: typeof testNull.latitude
|
||||
});
|
||||
|
||||
// 测试空字符串
|
||||
const testEmpty = {
|
||||
longitude: '',
|
||||
latitude: ''
|
||||
};
|
||||
|
||||
console.log('✓ 空字符串测试:', {
|
||||
longitude_empty: testEmpty.longitude,
|
||||
latitude_empty: testEmpty.latitude,
|
||||
longitude_type: typeof testEmpty.longitude,
|
||||
latitude_type: typeof testEmpty.latitude
|
||||
});
|
||||
|
||||
// 8. 清理测试数据
|
||||
console.log('\n8. 清理测试数据...');
|
||||
await testFarm.destroy();
|
||||
console.log('✓ 测试数据已清理');
|
||||
|
||||
console.log('\n=== 前端数据绑定测试完成 ===');
|
||||
console.log('\n📋 测试总结:');
|
||||
console.log('1. ✅ 数据库存储格式正确 (JSON对象)');
|
||||
console.log('2. ✅ API返回数据格式正确');
|
||||
console.log('3. ✅ 前端数据解析逻辑正确');
|
||||
console.log('4. ✅ formData绑定逻辑正确');
|
||||
console.log('5. ✅ 数据类型转换正确');
|
||||
console.log('6. ✅ 更新操作正确');
|
||||
console.log('7. ✅ 边界情况处理正确');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试过程中出现错误:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
testFrontendBinding()
|
||||
.then(() => {
|
||||
console.log('\n🎉 所有测试通过!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('\n💥 测试失败:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { testFrontendBinding };
|
||||
216
backend/tools/testing/test-location-data.js
Normal file
216
backend/tools/testing/test-location-data.js
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 测试经纬度数据传递和存储
|
||||
* @file test-location-data.js
|
||||
* @description 验证经纬度输入值是否正确传递到数据库操作层
|
||||
*/
|
||||
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
// 测试数据
|
||||
const testData = {
|
||||
name: '测试养殖场_经纬度验证',
|
||||
owner: '测试负责人',
|
||||
phone: '13800138000',
|
||||
address: '测试地址',
|
||||
longitude: 106.123456,
|
||||
latitude: 38.654321,
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
async function testLocationDataFlow() {
|
||||
try {
|
||||
console.log('开始测试经纬度数据传递流程...');
|
||||
|
||||
// 1. 测试创建养殖场时的经纬度处理
|
||||
console.log('\n1. 测试创建养殖场时的经纬度处理');
|
||||
console.log('输入数据:', {
|
||||
longitude: testData.longitude,
|
||||
latitude: testData.latitude
|
||||
});
|
||||
|
||||
// 模拟后端控制器的处理逻辑
|
||||
const { longitude, latitude } = testData;
|
||||
const location = {};
|
||||
if (longitude !== undefined && longitude !== null) {
|
||||
location.lng = parseFloat(longitude);
|
||||
}
|
||||
if (latitude !== undefined && latitude !== null) {
|
||||
location.lat = parseFloat(latitude);
|
||||
}
|
||||
|
||||
console.log('处理后的location对象:', location);
|
||||
|
||||
// 创建养殖场记录
|
||||
const farm = await Farm.create({
|
||||
name: testData.name,
|
||||
type: 'farm',
|
||||
location,
|
||||
address: testData.address,
|
||||
contact: testData.owner,
|
||||
phone: testData.phone,
|
||||
status: testData.status
|
||||
});
|
||||
|
||||
console.log('创建成功,数据库中的记录:');
|
||||
console.log('- ID:', farm.id);
|
||||
console.log('- Name:', farm.name);
|
||||
console.log('- Location:', JSON.stringify(farm.location));
|
||||
console.log('- Location.lng:', farm.location.lng);
|
||||
console.log('- Location.lat:', farm.location.lat);
|
||||
|
||||
// 2. 测试更新养殖场时的经纬度处理
|
||||
console.log('\n2. 测试更新养殖场时的经纬度处理');
|
||||
const newLongitude = 107.987654;
|
||||
const newLatitude = 39.123456;
|
||||
|
||||
console.log('新的输入数据:', {
|
||||
longitude: newLongitude,
|
||||
latitude: newLatitude
|
||||
});
|
||||
|
||||
// 模拟更新逻辑
|
||||
const updateLocation = farm.location || {};
|
||||
if (newLongitude !== undefined && newLongitude !== null) {
|
||||
updateLocation.lng = parseFloat(newLongitude);
|
||||
}
|
||||
if (newLatitude !== undefined && newLatitude !== null) {
|
||||
updateLocation.lat = parseFloat(newLatitude);
|
||||
}
|
||||
|
||||
console.log('处理后的location对象:', updateLocation);
|
||||
|
||||
await farm.update({
|
||||
location: updateLocation
|
||||
});
|
||||
|
||||
// 重新查询验证
|
||||
const updatedFarm = await Farm.findByPk(farm.id);
|
||||
console.log('更新后数据库中的记录:');
|
||||
console.log('- Location:', JSON.stringify(updatedFarm.location));
|
||||
console.log('- Location.lng:', updatedFarm.location.lng);
|
||||
console.log('- Location.lat:', updatedFarm.location.lat);
|
||||
|
||||
// 3. 测试数据类型验证
|
||||
console.log('\n3. 测试数据类型验证');
|
||||
console.log('原始输入类型:', typeof testData.longitude, typeof testData.latitude);
|
||||
console.log('parseFloat后类型:', typeof parseFloat(testData.longitude), typeof parseFloat(testData.latitude));
|
||||
console.log('数据库存储值类型:', typeof updatedFarm.location.lng, typeof updatedFarm.location.lat);
|
||||
|
||||
// 4. 测试边界值
|
||||
console.log('\n4. 测试边界值处理');
|
||||
const boundaryTests = [
|
||||
{ lng: -180, lat: -90, desc: '最小边界值' },
|
||||
{ lng: 180, lat: 90, desc: '最大边界值' },
|
||||
{ lng: 0, lat: 0, desc: '零值' },
|
||||
{ lng: 106.123456789, lat: 38.987654321, desc: '高精度值' }
|
||||
];
|
||||
|
||||
for (const test of boundaryTests) {
|
||||
const testLocation = {
|
||||
lng: parseFloat(test.lng),
|
||||
lat: parseFloat(test.lat)
|
||||
};
|
||||
|
||||
await farm.update({ location: testLocation });
|
||||
const verifyFarm = await Farm.findByPk(farm.id);
|
||||
|
||||
console.log(`${test.desc}:`);
|
||||
console.log(` 输入: lng=${test.lng}, lat=${test.lat}`);
|
||||
console.log(` 存储: lng=${verifyFarm.location.lng}, lat=${verifyFarm.location.lat}`);
|
||||
console.log(` 精度保持: ${test.lng === verifyFarm.location.lng && test.lat === verifyFarm.location.lat}`);
|
||||
}
|
||||
|
||||
// 5. 清理测试数据
|
||||
console.log('\n5. 清理测试数据');
|
||||
await farm.destroy();
|
||||
console.log('测试数据已清理');
|
||||
|
||||
console.log('\n✅ 经纬度数据传递流程测试完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试过程中发生错误:', error);
|
||||
console.error('错误详情:', error.message);
|
||||
if (error.sql) {
|
||||
console.error('SQL语句:', error.sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试空值处理
|
||||
async function testNullValues() {
|
||||
try {
|
||||
console.log('\n=== 测试空值处理 ===');
|
||||
|
||||
const testCases = [
|
||||
{ longitude: undefined, latitude: undefined, desc: 'undefined值' },
|
||||
{ longitude: null, latitude: null, desc: 'null值' },
|
||||
{ longitude: '', latitude: '', desc: '空字符串' },
|
||||
{ longitude: 106.123, latitude: undefined, desc: '仅经度有值' },
|
||||
{ longitude: undefined, latitude: 38.456, desc: '仅纬度有值' }
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const testCase = testCases[i];
|
||||
console.log(`\n测试案例 ${i + 1}: ${testCase.desc}`);
|
||||
console.log('输入:', { longitude: testCase.longitude, latitude: testCase.latitude });
|
||||
|
||||
// 模拟控制器处理逻辑
|
||||
const location = {};
|
||||
if (testCase.longitude !== undefined && testCase.longitude !== null && testCase.longitude !== '') {
|
||||
location.lng = parseFloat(testCase.longitude);
|
||||
}
|
||||
if (testCase.latitude !== undefined && testCase.latitude !== null && testCase.latitude !== '') {
|
||||
location.lat = parseFloat(testCase.latitude);
|
||||
}
|
||||
|
||||
console.log('处理后location:', location);
|
||||
|
||||
try {
|
||||
const farm = await Farm.create({
|
||||
name: `测试空值_${i + 1}`,
|
||||
type: 'farm',
|
||||
location,
|
||||
address: '测试地址',
|
||||
contact: '测试联系人',
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
console.log('创建成功,存储的location:', JSON.stringify(farm.location));
|
||||
|
||||
// 清理
|
||||
await farm.destroy();
|
||||
} catch (error) {
|
||||
console.log('创建失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('空值测试失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
try {
|
||||
// 确保数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
await testLocationDataFlow();
|
||||
await testNullValues();
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { testLocationDataFlow, testNullValues };
|
||||
13
backend/tools/testing/test-simple-db.js
Normal file
13
backend/tools/testing/test-simple-db.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const { testConnection } = require('./config/database-simple');
|
||||
|
||||
async function test() {
|
||||
console.log('测试数据库连接...');
|
||||
const result = await testConnection();
|
||||
if (result) {
|
||||
console.log('✅ 数据库连接测试成功');
|
||||
} else {
|
||||
console.log('❌ 数据库连接测试失败');
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
||||
251
backend/tools/testing/test-update-location.js
Normal file
251
backend/tools/testing/test-update-location.js
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* 测试经纬度更新功能
|
||||
* @file test-update-location.js
|
||||
* @description 专门测试更新操作中经纬度数据的正确处理
|
||||
*/
|
||||
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function testLocationUpdate() {
|
||||
try {
|
||||
console.log('开始测试经纬度更新功能...');
|
||||
|
||||
// 1. 创建初始记录
|
||||
console.log('\n1. 创建初始记录');
|
||||
const initialData = {
|
||||
name: '更新测试养殖场',
|
||||
type: 'farm',
|
||||
location: { lng: 106.123456, lat: 38.654321 },
|
||||
address: '初始地址',
|
||||
contact: '初始联系人',
|
||||
phone: '13800138000',
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
const farm = await Farm.create(initialData);
|
||||
console.log('初始记录创建成功:');
|
||||
console.log('- ID:', farm.id);
|
||||
console.log('- Location:', JSON.stringify(farm.location));
|
||||
|
||||
// 2. 测试更新经纬度
|
||||
console.log('\n2. 测试更新经纬度');
|
||||
const newLongitude = 107.987654;
|
||||
const newLatitude = 39.123456;
|
||||
|
||||
console.log('新的经纬度值:', { longitude: newLongitude, latitude: newLatitude });
|
||||
|
||||
// 模拟控制器的更新逻辑
|
||||
const location = { ...(farm.location || {}) };
|
||||
if (newLongitude !== undefined && newLongitude !== null) {
|
||||
location.lng = parseFloat(newLongitude);
|
||||
}
|
||||
if (newLatitude !== undefined && newLatitude !== null) {
|
||||
location.lat = parseFloat(newLatitude);
|
||||
}
|
||||
|
||||
console.log('处理后的location对象:', location);
|
||||
|
||||
// 执行更新
|
||||
await farm.update({
|
||||
location
|
||||
});
|
||||
|
||||
// 重新查询验证
|
||||
const updatedFarm = await Farm.findByPk(farm.id);
|
||||
console.log('\n更新后的记录:');
|
||||
console.log('- Location:', JSON.stringify(updatedFarm.location));
|
||||
console.log('- Location.lng:', updatedFarm.location.lng);
|
||||
console.log('- Location.lat:', updatedFarm.location.lat);
|
||||
|
||||
// 验证更新是否成功
|
||||
const updateSuccess = (
|
||||
updatedFarm.location.lng === newLongitude &&
|
||||
updatedFarm.location.lat === newLatitude
|
||||
);
|
||||
|
||||
console.log('\n更新验证结果:');
|
||||
console.log('- 期望经度:', newLongitude);
|
||||
console.log('- 实际经度:', updatedFarm.location.lng);
|
||||
console.log('- 期望纬度:', newLatitude);
|
||||
console.log('- 实际纬度:', updatedFarm.location.lat);
|
||||
console.log('- 更新成功:', updateSuccess ? '✅' : '❌');
|
||||
|
||||
// 3. 测试部分更新(只更新经度)
|
||||
console.log('\n3. 测试部分更新(只更新经度)');
|
||||
const partialLongitude = 108.555555;
|
||||
|
||||
const partialLocation = { ...(updatedFarm.location || {}) };
|
||||
if (partialLongitude !== undefined && partialLongitude !== null) {
|
||||
partialLocation.lng = parseFloat(partialLongitude);
|
||||
}
|
||||
// 注意:这里不更新纬度
|
||||
|
||||
await farm.update({ location: partialLocation });
|
||||
|
||||
const partialUpdatedFarm = await Farm.findByPk(farm.id);
|
||||
console.log('部分更新后的记录:');
|
||||
console.log('- Location:', JSON.stringify(partialUpdatedFarm.location));
|
||||
console.log('- 经度是否更新:', partialUpdatedFarm.location.lng === partialLongitude ? '✅' : '❌');
|
||||
console.log('- 纬度是否保持:', partialUpdatedFarm.location.lat === newLatitude ? '✅' : '❌');
|
||||
|
||||
// 4. 测试清空经纬度
|
||||
console.log('\n4. 测试清空经纬度');
|
||||
const emptyLocation = {};
|
||||
|
||||
await farm.update({ location: emptyLocation });
|
||||
|
||||
const emptyUpdatedFarm = await Farm.findByPk(farm.id);
|
||||
console.log('清空后的记录:');
|
||||
console.log('- Location:', JSON.stringify(emptyUpdatedFarm.location));
|
||||
console.log('- 是否为空对象:', Object.keys(emptyUpdatedFarm.location).length === 0 ? '✅' : '❌');
|
||||
|
||||
// 5. 清理测试数据
|
||||
console.log('\n5. 清理测试数据');
|
||||
await farm.destroy();
|
||||
console.log('测试数据已清理');
|
||||
|
||||
console.log('\n✅ 经纬度更新功能测试完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试过程中发生错误:', error);
|
||||
console.error('错误详情:', error.message);
|
||||
if (error.sql) {
|
||||
console.error('SQL语句:', error.sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试通过API接口的完整流程
|
||||
async function testAPIFlow() {
|
||||
try {
|
||||
console.log('\n=== 测试API接口流程 ===');
|
||||
|
||||
// 模拟前端发送的请求数据
|
||||
const createRequest = {
|
||||
body: {
|
||||
name: 'API测试养殖场',
|
||||
owner: 'API测试负责人',
|
||||
phone: '13900139000',
|
||||
address: 'API测试地址',
|
||||
longitude: 106.789123,
|
||||
latitude: 38.456789,
|
||||
status: 'active'
|
||||
}
|
||||
};
|
||||
|
||||
console.log('\n1. 模拟创建请求');
|
||||
console.log('请求数据:', {
|
||||
longitude: createRequest.body.longitude,
|
||||
latitude: createRequest.body.latitude
|
||||
});
|
||||
|
||||
// 模拟createFarm控制器逻辑
|
||||
const { name, owner, longitude, latitude, address, phone, status } = createRequest.body;
|
||||
|
||||
const location = {};
|
||||
if (longitude !== undefined && longitude !== null) {
|
||||
location.lng = parseFloat(longitude);
|
||||
}
|
||||
if (latitude !== undefined && latitude !== null) {
|
||||
location.lat = parseFloat(latitude);
|
||||
}
|
||||
|
||||
const farm = await Farm.create({
|
||||
name,
|
||||
type: 'farm',
|
||||
location,
|
||||
address,
|
||||
contact: owner,
|
||||
phone,
|
||||
status: status || 'active'
|
||||
});
|
||||
|
||||
console.log('创建结果:', {
|
||||
id: farm.id,
|
||||
location: farm.location
|
||||
});
|
||||
|
||||
// 模拟更新请求
|
||||
const updateRequest = {
|
||||
params: { id: farm.id },
|
||||
body: {
|
||||
name: farm.name,
|
||||
owner: farm.contact,
|
||||
phone: farm.phone,
|
||||
address: farm.address,
|
||||
longitude: 107.111222,
|
||||
latitude: 39.333444,
|
||||
status: farm.status
|
||||
}
|
||||
};
|
||||
|
||||
console.log('\n2. 模拟更新请求');
|
||||
console.log('更新数据:', {
|
||||
longitude: updateRequest.body.longitude,
|
||||
latitude: updateRequest.body.latitude
|
||||
});
|
||||
|
||||
// 模拟updateFarm控制器逻辑
|
||||
const updateData = updateRequest.body;
|
||||
const updateLocation = { ...(farm.location || {}) };
|
||||
if (updateData.longitude !== undefined && updateData.longitude !== null) {
|
||||
updateLocation.lng = parseFloat(updateData.longitude);
|
||||
}
|
||||
if (updateData.latitude !== undefined && updateData.latitude !== null) {
|
||||
updateLocation.lat = parseFloat(updateData.latitude);
|
||||
}
|
||||
|
||||
await farm.update({
|
||||
name: updateData.name,
|
||||
location: updateLocation,
|
||||
address: updateData.address,
|
||||
contact: updateData.owner,
|
||||
phone: updateData.phone,
|
||||
status: updateData.status
|
||||
});
|
||||
|
||||
// 验证更新结果
|
||||
const finalFarm = await Farm.findByPk(farm.id);
|
||||
console.log('更新结果:', {
|
||||
location: finalFarm.location
|
||||
});
|
||||
|
||||
const apiUpdateSuccess = (
|
||||
finalFarm.location.lng === updateData.longitude &&
|
||||
finalFarm.location.lat === updateData.latitude
|
||||
);
|
||||
|
||||
console.log('API流程验证:', apiUpdateSuccess ? '✅ 成功' : '❌ 失败');
|
||||
|
||||
// 清理
|
||||
await farm.destroy();
|
||||
|
||||
} catch (error) {
|
||||
console.error('API流程测试失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
await testLocationUpdate();
|
||||
await testAPIFlow();
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { testLocationUpdate, testAPIFlow };
|
||||
118
backend/tools/testing/test-users-api.js
Normal file
118
backend/tools/testing/test-users-api.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 测试用户管理API
|
||||
*/
|
||||
const axios = require('axios');
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5350/api';
|
||||
|
||||
// 测试用户登录并获取token
|
||||
async function testLogin() {
|
||||
try {
|
||||
console.log('测试用户登录...');
|
||||
const response = await axios.post(`${API_BASE_URL}/auth/login`, {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('✓ 登录成功');
|
||||
console.log('Token:', response.data.token);
|
||||
return response.data.token;
|
||||
} else {
|
||||
console.log('✗ 登录失败:', response.data.message);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('✗ 登录请求失败:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取用户列表
|
||||
async function testGetUsers(token) {
|
||||
try {
|
||||
console.log('\n测试获取用户列表...');
|
||||
const response = await axios.get(`${API_BASE_URL}/users`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('✓ 获取用户列表成功');
|
||||
console.log('用户数量:', response.data.data.length);
|
||||
response.data.data.forEach(user => {
|
||||
console.log(`- ${user.username} (${user.email}) - 角色: ${user.role}`);
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
console.log('✗ 获取用户列表失败:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('✗ 获取用户列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试创建用户
|
||||
async function testCreateUser(token) {
|
||||
try {
|
||||
console.log('\n测试创建用户...');
|
||||
const newUser = {
|
||||
username: 'testuser_' + Date.now(),
|
||||
email: `test_${Date.now()}@example.com`,
|
||||
password: '123456',
|
||||
role: 'user'
|
||||
};
|
||||
|
||||
const response = await axios.post(`${API_BASE_URL}/users`, newUser, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('✓ 创建用户成功');
|
||||
console.log('新用户ID:', response.data.data.id);
|
||||
return response.data.data.id;
|
||||
} else {
|
||||
console.log('✗ 创建用户失败:', response.data.message);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('✗ 创建用户请求失败:', error.response?.data?.message || error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runTests() {
|
||||
console.log('开始测试用户管理API...');
|
||||
console.log('=' .repeat(50));
|
||||
|
||||
// 1. 测试登录
|
||||
const token = await testLogin();
|
||||
if (!token) {
|
||||
console.log('\n测试终止:无法获取认证token');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 测试获取用户列表
|
||||
await testGetUsers(token);
|
||||
|
||||
// 3. 测试创建用户
|
||||
const newUserId = await testCreateUser(token);
|
||||
|
||||
// 4. 再次获取用户列表,验证新用户是否创建成功
|
||||
if (newUserId) {
|
||||
console.log('\n验证新用户是否创建成功...');
|
||||
await testGetUsers(token);
|
||||
}
|
||||
|
||||
console.log('\n测试完成!');
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTests().catch(console.error);
|
||||
31
backend/tools/testing/test_bcrypt.js
Normal file
31
backend/tools/testing/test_bcrypt.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
async function testBcrypt() {
|
||||
try {
|
||||
const password = '123456';
|
||||
const storedHash = '$2b$10$yTdFpkw5MPU5OprOE7xWJ.arvesmRxKm2MpjwdbzNpEUIR2lq4C9S';
|
||||
|
||||
console.log('测试密码:', password);
|
||||
console.log('存储的哈希:', storedHash);
|
||||
|
||||
// 直接使用 bcrypt.compare
|
||||
const result1 = await bcrypt.compare(password, storedHash);
|
||||
console.log('bcrypt.compare 结果:', result1);
|
||||
|
||||
// 生成新的哈希并测试
|
||||
const newHash = await bcrypt.hash(password, 10);
|
||||
console.log('新生成的哈希:', newHash);
|
||||
|
||||
const result2 = await bcrypt.compare(password, newHash);
|
||||
console.log('新哈希验证结果:', result2);
|
||||
|
||||
// 测试同步方法
|
||||
const result3 = bcrypt.compareSync(password, storedHash);
|
||||
console.log('bcrypt.compareSync 结果:', result3);
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
testBcrypt();
|
||||
225
backend/tools/verification/analyze-foreign-keys.js
Normal file
225
backend/tools/verification/analyze-foreign-keys.js
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 分析数据库中表之间的外键关系
|
||||
* @file analyze-foreign-keys.js
|
||||
* @description 识别所有外键约束和引用关系,为ID重排做准备
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
async function analyzeForeignKeys() {
|
||||
try {
|
||||
console.log('=== 分析数据库外键关系 ===\n');
|
||||
|
||||
// 获取所有外键约束
|
||||
const foreignKeys = await sequelize.query(`
|
||||
SELECT
|
||||
kcu.TABLE_NAME as table_name,
|
||||
kcu.COLUMN_NAME as column_name,
|
||||
kcu.REFERENCED_TABLE_NAME as referenced_table,
|
||||
kcu.REFERENCED_COLUMN_NAME as referenced_column,
|
||||
rc.CONSTRAINT_NAME as constraint_name,
|
||||
rc.UPDATE_RULE as update_rule,
|
||||
rc.DELETE_RULE as delete_rule
|
||||
FROM information_schema.KEY_COLUMN_USAGE kcu
|
||||
JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
|
||||
ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
||||
AND kcu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA
|
||||
WHERE kcu.TABLE_SCHEMA = DATABASE()
|
||||
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
||||
ORDER BY kcu.TABLE_NAME, kcu.COLUMN_NAME
|
||||
`, { type: QueryTypes.SELECT });
|
||||
|
||||
console.log(`发现 ${foreignKeys.length} 个外键关系:\n`);
|
||||
|
||||
const relationshipMap = new Map();
|
||||
const tablesWithForeignKeys = new Set();
|
||||
const referencedTables = new Set();
|
||||
|
||||
foreignKeys.forEach(fk => {
|
||||
const key = `${fk.table_name}.${fk.column_name}`;
|
||||
const reference = `${fk.referenced_table}.${fk.referenced_column}`;
|
||||
|
||||
relationshipMap.set(key, {
|
||||
table: fk.table_name,
|
||||
column: fk.column_name,
|
||||
referencedTable: fk.referenced_table,
|
||||
referencedColumn: fk.referenced_column,
|
||||
constraintName: fk.constraint_name,
|
||||
updateRule: fk.update_rule,
|
||||
deleteRule: fk.delete_rule
|
||||
});
|
||||
|
||||
tablesWithForeignKeys.add(fk.table_name);
|
||||
referencedTables.add(fk.referenced_table);
|
||||
|
||||
console.log(`🔗 ${fk.table_name}.${fk.column_name} -> ${fk.referenced_table}.${fk.referenced_column}`);
|
||||
console.log(` 约束名: ${fk.constraint_name}`);
|
||||
console.log(` 更新规则: ${fk.update_rule}`);
|
||||
console.log(` 删除规则: ${fk.delete_rule}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// 分析每个外键字段的数据分布
|
||||
console.log('\n=== 外键字段数据分布 ===\n');
|
||||
|
||||
const foreignKeyStats = [];
|
||||
|
||||
for (const [key, relationship] of relationshipMap) {
|
||||
const { table, column, referencedTable, referencedColumn } = relationship;
|
||||
|
||||
try {
|
||||
// 获取外键字段的统计信息
|
||||
const stats = await sequelize.query(`
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COUNT(DISTINCT ${column}) as unique_count,
|
||||
MIN(${column}) as min_value,
|
||||
MAX(${column}) as max_value,
|
||||
COUNT(CASE WHEN ${column} IS NULL THEN 1 END) as null_count
|
||||
FROM ${table}
|
||||
`, { type: QueryTypes.SELECT });
|
||||
|
||||
const stat = stats[0];
|
||||
|
||||
// 检查引用完整性
|
||||
const integrityCheck = await sequelize.query(`
|
||||
SELECT COUNT(*) as invalid_references
|
||||
FROM ${table} t
|
||||
WHERE t.${column} IS NOT NULL
|
||||
AND t.${column} NOT IN (
|
||||
SELECT ${referencedColumn}
|
||||
FROM ${referencedTable}
|
||||
WHERE ${referencedColumn} IS NOT NULL
|
||||
)
|
||||
`, { type: QueryTypes.SELECT });
|
||||
|
||||
const invalidRefs = parseInt(integrityCheck[0].invalid_references);
|
||||
|
||||
const fkStat = {
|
||||
table,
|
||||
column,
|
||||
referencedTable,
|
||||
referencedColumn,
|
||||
totalCount: parseInt(stat.total_count),
|
||||
uniqueCount: parseInt(stat.unique_count),
|
||||
minValue: stat.min_value,
|
||||
maxValue: stat.max_value,
|
||||
nullCount: parseInt(stat.null_count),
|
||||
invalidReferences: invalidRefs,
|
||||
hasIntegrityIssues: invalidRefs > 0
|
||||
};
|
||||
|
||||
foreignKeyStats.push(fkStat);
|
||||
|
||||
console.log(`📊 ${table}.${column} -> ${referencedTable}.${referencedColumn}:`);
|
||||
console.log(` - 总记录数: ${fkStat.totalCount}`);
|
||||
console.log(` - 唯一值数: ${fkStat.uniqueCount}`);
|
||||
console.log(` - 值范围: ${fkStat.minValue} - ${fkStat.maxValue}`);
|
||||
console.log(` - NULL值数: ${fkStat.nullCount}`);
|
||||
console.log(` - 无效引用: ${fkStat.invalidReferences}`);
|
||||
console.log(` - 完整性问题: ${fkStat.hasIntegrityIssues ? '是' : '否'}`);
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.log(`❌ ${table}.${column}: 分析失败 - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成依赖关系图
|
||||
console.log('\n=== 表依赖关系 ===\n');
|
||||
|
||||
const dependencyGraph = new Map();
|
||||
|
||||
foreignKeys.forEach(fk => {
|
||||
if (!dependencyGraph.has(fk.table_name)) {
|
||||
dependencyGraph.set(fk.table_name, new Set());
|
||||
}
|
||||
dependencyGraph.get(fk.table_name).add(fk.referenced_table);
|
||||
});
|
||||
|
||||
// 计算更新顺序(拓扑排序)
|
||||
const updateOrder = [];
|
||||
const visited = new Set();
|
||||
const visiting = new Set();
|
||||
|
||||
function topologicalSort(table) {
|
||||
if (visiting.has(table)) {
|
||||
console.log(`⚠️ 检测到循环依赖: ${table}`);
|
||||
return;
|
||||
}
|
||||
if (visited.has(table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
visiting.add(table);
|
||||
|
||||
const dependencies = dependencyGraph.get(table) || new Set();
|
||||
for (const dep of dependencies) {
|
||||
topologicalSort(dep);
|
||||
}
|
||||
|
||||
visiting.delete(table);
|
||||
visited.add(table);
|
||||
updateOrder.push(table);
|
||||
}
|
||||
|
||||
// 对所有表进行拓扑排序
|
||||
const allTables = new Set([...tablesWithForeignKeys, ...referencedTables]);
|
||||
for (const table of allTables) {
|
||||
topologicalSort(table);
|
||||
}
|
||||
|
||||
console.log('建议的ID重排顺序(被引用的表优先):');
|
||||
updateOrder.reverse().forEach((table, index) => {
|
||||
const deps = dependencyGraph.get(table);
|
||||
const depList = deps ? Array.from(deps).join(', ') : '无';
|
||||
console.log(`${index + 1}. ${table} (依赖: ${depList})`);
|
||||
});
|
||||
|
||||
// 汇总报告
|
||||
console.log('\n=== 汇总报告 ===');
|
||||
console.log(`外键关系总数: ${foreignKeys.length}`);
|
||||
console.log(`涉及外键的表: ${tablesWithForeignKeys.size}`);
|
||||
console.log(`被引用的表: ${referencedTables.size}`);
|
||||
|
||||
const tablesWithIssues = foreignKeyStats.filter(stat => stat.hasIntegrityIssues);
|
||||
if (tablesWithIssues.length > 0) {
|
||||
console.log(`\n⚠️ 发现完整性问题的表:`);
|
||||
tablesWithIssues.forEach(stat => {
|
||||
console.log(`- ${stat.table}.${stat.column}: ${stat.invalidReferences} 个无效引用`);
|
||||
});
|
||||
} else {
|
||||
console.log('\n✅ 所有外键关系完整性正常');
|
||||
}
|
||||
|
||||
return {
|
||||
foreignKeys,
|
||||
foreignKeyStats,
|
||||
updateOrder: updateOrder.reverse(),
|
||||
relationshipMap,
|
||||
tablesWithIssues
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('分析外键关系失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
analyzeForeignKeys()
|
||||
.then(() => {
|
||||
console.log('\n分析完成!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('分析失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { analyzeForeignKeys };
|
||||
19
backend/tools/verification/check-alerts-status.js
Normal file
19
backend/tools/verification/check-alerts-status.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function checkAlertsStatus() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
const [results] = await sequelize.query('SHOW COLUMNS FROM alerts LIKE "status"');
|
||||
console.log('Alerts表status字段信息:');
|
||||
console.table(results);
|
||||
|
||||
} catch (error) {
|
||||
console.error('查询失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkAlertsStatus();
|
||||
0
backend/tools/verification/check-animal-count.js
Normal file
0
backend/tools/verification/check-animal-count.js
Normal file
80
backend/tools/verification/check-current-data.js
Normal file
80
backend/tools/verification/check-current-data.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 检查当前数据库中的经纬度数据
|
||||
* @file check-current-data.js
|
||||
*/
|
||||
|
||||
const { Farm } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function checkCurrentData() {
|
||||
try {
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
|
||||
console.log('\n查询最近的养殖场记录...');
|
||||
const farms = await Farm.findAll({
|
||||
attributes: ['id', 'name', 'location', 'created_at'],
|
||||
order: [['id', 'DESC']],
|
||||
limit: 10
|
||||
});
|
||||
|
||||
console.log(`\n找到 ${farms.length} 条记录:`);
|
||||
console.log('=' .repeat(80));
|
||||
|
||||
farms.forEach((farm, index) => {
|
||||
console.log(`${index + 1}. ID: ${farm.id}`);
|
||||
console.log(` 名称: ${farm.name}`);
|
||||
console.log(` Location对象: ${JSON.stringify(farm.location)}`);
|
||||
|
||||
if (farm.location && typeof farm.location === 'object') {
|
||||
const lng = farm.location.lng;
|
||||
const lat = farm.location.lat;
|
||||
console.log(` 经度 (lng): ${lng} (类型: ${typeof lng})`);
|
||||
console.log(` 纬度 (lat): ${lat} (类型: ${typeof lat})`);
|
||||
|
||||
if (lng !== undefined || lat !== undefined) {
|
||||
console.log(` ✅ 有经纬度数据`);
|
||||
} else {
|
||||
console.log(` ❌ 无经纬度数据`);
|
||||
}
|
||||
} else {
|
||||
console.log(` ❌ Location字段为空或格式错误`);
|
||||
}
|
||||
|
||||
console.log(` 创建时间: ${farm.created_at}`);
|
||||
console.log('-'.repeat(60));
|
||||
});
|
||||
|
||||
// 查找包含经纬度数据的记录
|
||||
console.log('\n查找包含经纬度数据的记录...');
|
||||
const farmsWithLocation = await Farm.findAll({
|
||||
where: sequelize.literal("JSON_EXTRACT(location, '$.lng') IS NOT NULL OR JSON_EXTRACT(location, '$.lat') IS NOT NULL"),
|
||||
attributes: ['id', 'name', 'location'],
|
||||
order: [['id', 'DESC']],
|
||||
limit: 5
|
||||
});
|
||||
|
||||
console.log(`\n包含经纬度数据的记录 (${farmsWithLocation.length} 条):`);
|
||||
farmsWithLocation.forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, 名称: ${farm.name}`);
|
||||
console.log(`经度: ${farm.location.lng}, 纬度: ${farm.location.lat}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('查询失败:', error.message);
|
||||
if (error.sql) {
|
||||
console.error('SQL:', error.sql);
|
||||
}
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
if (require.main === module) {
|
||||
checkCurrentData();
|
||||
}
|
||||
|
||||
module.exports = { checkCurrentData };
|
||||
23
backend/tools/verification/check-devices.js
Normal file
23
backend/tools/verification/check-devices.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const { Device } = require('./models');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const devices = await Device.findAll({
|
||||
limit: 5,
|
||||
attributes: ['id', 'name', 'type']
|
||||
});
|
||||
|
||||
console.log('前5个设备:');
|
||||
devices.forEach(d => {
|
||||
console.log(`ID: ${d.id}, 名称: ${d.name}, 类型: ${d.type}`);
|
||||
});
|
||||
|
||||
const totalCount = await Device.count();
|
||||
console.log(`\n设备总数: ${totalCount}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查设备时出错:', error);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
})();
|
||||
56
backend/tools/verification/check-farm-foreign-keys.js
Normal file
56
backend/tools/verification/check-farm-foreign-keys.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const { Animal, Device, Alert, Order } = require('./models');
|
||||
|
||||
async function checkFarmForeignKeys() {
|
||||
try {
|
||||
console.log('检查引用farms表的外键情况...');
|
||||
|
||||
// 检查animals表
|
||||
const animals = await Animal.findAll({
|
||||
attributes: ['id', 'farmId'],
|
||||
order: [['farmId', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('\nAnimals表中的farmId分布:');
|
||||
const animalFarmIds = [...new Set(animals.map(a => a.farmId))].sort((a, b) => a - b);
|
||||
console.log('引用的farmId:', animalFarmIds);
|
||||
console.log(`总共 ${animals.length} 条动物记录`);
|
||||
|
||||
// 检查devices表
|
||||
const devices = await Device.findAll({
|
||||
attributes: ['id', 'farmId'],
|
||||
order: [['farmId', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('\nDevices表中的farmId分布:');
|
||||
const deviceFarmIds = [...new Set(devices.map(d => d.farmId))].sort((a, b) => a - b);
|
||||
console.log('引用的farmId:', deviceFarmIds);
|
||||
console.log(`总共 ${devices.length} 条设备记录`);
|
||||
|
||||
// 检查alerts表
|
||||
const alerts = await Alert.findAll({
|
||||
attributes: ['id', 'farmId'],
|
||||
order: [['farmId', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('\nAlerts表中的farmId分布:');
|
||||
const alertFarmIds = [...new Set(alerts.map(a => a.farmId))].sort((a, b) => a - b);
|
||||
console.log('引用的farmId:', alertFarmIds);
|
||||
console.log(`总共 ${alerts.length} 条警报记录`);
|
||||
|
||||
// 检查orders表
|
||||
const orders = await Order.findAll({
|
||||
attributes: ['id', 'farmId'],
|
||||
order: [['farmId', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('\nOrders表中的farmId分布:');
|
||||
const orderFarmIds = [...new Set(orders.map(o => o.farmId))].sort((a, b) => a - b);
|
||||
console.log('引用的farmId:', orderFarmIds);
|
||||
console.log(`总共 ${orders.length} 条订单记录`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
checkFarmForeignKeys();
|
||||
28
backend/tools/verification/check-farms-id.js
Normal file
28
backend/tools/verification/check-farms-id.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const { Farm } = require('./models');
|
||||
|
||||
async function checkFarmsId() {
|
||||
try {
|
||||
console.log('检查farms表ID分布情况...');
|
||||
|
||||
const farms = await Farm.findAll({
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
|
||||
console.log('当前farms表ID分布:');
|
||||
farms.forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}`);
|
||||
});
|
||||
|
||||
console.log(`\n总共有 ${farms.length} 个养殖场`);
|
||||
|
||||
if (farms.length > 0) {
|
||||
console.log(`最小ID: ${farms[0].id}`);
|
||||
console.log(`最大ID: ${farms[farms.length - 1].id}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
checkFarmsId();
|
||||
48
backend/tools/verification/check-farms-sql.js
Normal file
48
backend/tools/verification/check-farms-sql.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function checkFarmsSQL() {
|
||||
try {
|
||||
console.log('检查farms表状态...');
|
||||
|
||||
// 检查表是否存在
|
||||
const tables = await sequelize.query("SHOW TABLES LIKE 'farms'");
|
||||
console.log('farms表存在:', tables[0].length > 0);
|
||||
|
||||
if (tables[0].length > 0) {
|
||||
// 检查记录数
|
||||
const count = await sequelize.query('SELECT COUNT(*) as count FROM farms');
|
||||
console.log('farms表记录数:', count[0][0].count);
|
||||
|
||||
// 如果有记录,显示所有记录
|
||||
if (count[0][0].count > 0) {
|
||||
const farms = await sequelize.query('SELECT * FROM farms ORDER BY id ASC');
|
||||
console.log('farms表数据:');
|
||||
farms[0].forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 检查临时表是否还存在
|
||||
const tempTables = await sequelize.query("SHOW TABLES LIKE 'farms_temp'");
|
||||
console.log('farms_temp表存在:', tempTables[0].length > 0);
|
||||
|
||||
if (tempTables[0].length > 0) {
|
||||
const tempCount = await sequelize.query('SELECT COUNT(*) as count FROM farms_temp');
|
||||
console.log('farms_temp表记录数:', tempCount[0][0].count);
|
||||
|
||||
if (tempCount[0][0].count > 0) {
|
||||
const tempFarms = await sequelize.query('SELECT * FROM farms_temp ORDER BY id ASC');
|
||||
console.log('farms_temp表数据:');
|
||||
tempFarms[0].forEach(farm => {
|
||||
console.log(`ID: ${farm.id}, Name: ${farm.name}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
checkFarmsSQL();
|
||||
69
backend/tools/verification/check-orphaned-foreign-keys.js
Normal file
69
backend/tools/verification/check-orphaned-foreign-keys.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function checkOrphanedForeignKeys() {
|
||||
try {
|
||||
console.log('检查孤立外键数据...');
|
||||
|
||||
// 检查farm_id=12的记录数量
|
||||
const [devicesResult] = await sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM devices WHERE farm_id = 12'
|
||||
);
|
||||
|
||||
const [alertsResult] = await sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM alerts WHERE farm_id = 12'
|
||||
);
|
||||
|
||||
const [animalsResult] = await sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM animals WHERE farm_id = 12'
|
||||
);
|
||||
|
||||
console.log('\nfarm_id=12的孤立记录数量:');
|
||||
console.log('devices表:', devicesResult[0].count);
|
||||
console.log('alerts表:', alertsResult[0].count);
|
||||
console.log('animals表:', animalsResult[0].count);
|
||||
|
||||
// 检查具体的孤立记录
|
||||
if (devicesResult[0].count > 0) {
|
||||
const [devices] = await sequelize.query(
|
||||
'SELECT id, name, type FROM devices WHERE farm_id = 12'
|
||||
);
|
||||
console.log('\ndevices表中farm_id=12的记录:');
|
||||
devices.forEach(device => {
|
||||
console.log(`- ID: ${device.id}, 名称: ${device.name}, 类型: ${device.type}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (alertsResult[0].count > 0) {
|
||||
const [alerts] = await sequelize.query(
|
||||
'SELECT id, type, level FROM alerts WHERE farm_id = 12'
|
||||
);
|
||||
console.log('\nalerts表中farm_id=12的记录:');
|
||||
alerts.forEach(alert => {
|
||||
console.log(`- ID: ${alert.id}, 类型: ${alert.type}, 级别: ${alert.level}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (animalsResult[0].count > 0) {
|
||||
const [animals] = await sequelize.query(
|
||||
'SELECT id, type, count FROM animals WHERE farm_id = 12'
|
||||
);
|
||||
console.log('\nanimals表中farm_id=12的记录:');
|
||||
animals.forEach(animal => {
|
||||
console.log(`- ID: ${animal.id}, 类型: ${animal.type}, 数量: ${animal.count}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 检查farms表的最大ID
|
||||
const [farmsMaxId] = await sequelize.query(
|
||||
'SELECT MAX(id) as max_id FROM farms'
|
||||
);
|
||||
console.log('\nfarms表最大ID:', farmsMaxId[0].max_id);
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkOrphanedForeignKeys();
|
||||
128
backend/tools/verification/check-primary-keys.js
Normal file
128
backend/tools/verification/check-primary-keys.js
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 检查数据库中所有表的主键ID分布情况
|
||||
* @file check-primary-keys.js
|
||||
* @description 分析各表的主键ID范围,为重新排序做准备
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
async function checkPrimaryKeys() {
|
||||
try {
|
||||
console.log('=== 检查数据库表主键ID分布情况 ===\n');
|
||||
|
||||
// 获取所有表名
|
||||
const tables = await sequelize.query(
|
||||
"SELECT TABLE_NAME as table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE'",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
console.log(`发现 ${tables.length} 个表:\n`);
|
||||
|
||||
const tableStats = [];
|
||||
|
||||
for (const table of tables) {
|
||||
const tableName = table.table_name;
|
||||
|
||||
try {
|
||||
// 检查表是否有id字段
|
||||
const columns = await sequelize.query(
|
||||
`SELECT COLUMN_NAME as column_name, DATA_TYPE as data_type, IS_NULLABLE as is_nullable, COLUMN_KEY as column_key
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND TABLE_NAME = '${tableName}' AND COLUMN_NAME = 'id'`,
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (columns.length === 0) {
|
||||
console.log(`❌ ${tableName}: 没有id字段`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取ID统计信息
|
||||
const stats = await sequelize.query(
|
||||
`SELECT
|
||||
COUNT(*) as total_count,
|
||||
MIN(id) as min_id,
|
||||
MAX(id) as max_id,
|
||||
COUNT(DISTINCT id) as unique_count
|
||||
FROM ${tableName}`,
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const stat = stats[0];
|
||||
|
||||
// 检查ID连续性
|
||||
const gapCheck = await sequelize.query(
|
||||
`SELECT COUNT(*) as gap_count
|
||||
FROM (
|
||||
SELECT id + 1 as next_id
|
||||
FROM ${tableName}
|
||||
WHERE id + 1 NOT IN (SELECT id FROM ${tableName})
|
||||
AND id < (SELECT MAX(id) FROM ${tableName})
|
||||
) as gaps`,
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const hasGaps = gapCheck[0].gap_count > 0;
|
||||
|
||||
const tableInfo = {
|
||||
tableName,
|
||||
totalCount: parseInt(stat.total_count),
|
||||
minId: stat.min_id,
|
||||
maxId: stat.max_id,
|
||||
uniqueCount: parseInt(stat.unique_count),
|
||||
hasGaps,
|
||||
needsReorder: stat.min_id !== 1 || hasGaps
|
||||
};
|
||||
|
||||
tableStats.push(tableInfo);
|
||||
|
||||
console.log(`✅ ${tableName}:`);
|
||||
console.log(` - 记录数: ${tableInfo.totalCount}`);
|
||||
console.log(` - ID范围: ${tableInfo.minId} - ${tableInfo.maxId}`);
|
||||
console.log(` - 唯一ID数: ${tableInfo.uniqueCount}`);
|
||||
console.log(` - 有间隙: ${hasGaps ? '是' : '否'}`);
|
||||
console.log(` - 需要重排: ${tableInfo.needsReorder ? '是' : '否'}`);
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.log(`❌ ${tableName}: 检查失败 - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 汇总统计
|
||||
console.log('\n=== 汇总统计 ===');
|
||||
const needReorderTables = tableStats.filter(t => t.needsReorder);
|
||||
console.log(`需要重新排序的表: ${needReorderTables.length}/${tableStats.length}`);
|
||||
|
||||
if (needReorderTables.length > 0) {
|
||||
console.log('\n需要重新排序的表:');
|
||||
needReorderTables.forEach(table => {
|
||||
console.log(`- ${table.tableName} (${table.minId}-${table.maxId}, ${table.totalCount}条记录)`);
|
||||
});
|
||||
}
|
||||
|
||||
return tableStats;
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查主键失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
checkPrimaryKeys()
|
||||
.then(() => {
|
||||
console.log('\n检查完成!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('检查失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { checkPrimaryKeys };
|
||||
48
backend/tools/verification/check-sensor-data.js
Normal file
48
backend/tools/verification/check-sensor-data.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const db = require('./config/database');
|
||||
const SensorData = require('./models/SensorData');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
// 检查传感器数据总数
|
||||
const count = await SensorData.count();
|
||||
console.log('传感器数据总数:', count);
|
||||
|
||||
// 检查最近的温度数据
|
||||
const temperatureData = await SensorData.findAll({
|
||||
where: { sensor_type: 'temperature' },
|
||||
limit: 10,
|
||||
order: [['recorded_at', 'DESC']]
|
||||
});
|
||||
console.log('\n最近10条温度数据:');
|
||||
temperatureData.forEach(r => {
|
||||
console.log(`${r.sensor_type}: ${r.value}${r.unit} at ${r.recorded_at}`);
|
||||
});
|
||||
|
||||
// 检查最近的湿度数据
|
||||
const humidityData = await SensorData.findAll({
|
||||
where: { sensor_type: 'humidity' },
|
||||
limit: 10,
|
||||
order: [['recorded_at', 'DESC']]
|
||||
});
|
||||
console.log('\n最近10条湿度数据:');
|
||||
humidityData.forEach(r => {
|
||||
console.log(`${r.sensor_type}: ${r.value}${r.unit} at ${r.recorded_at}`);
|
||||
});
|
||||
|
||||
// 检查24小时内的数据
|
||||
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
const recentCount = await SensorData.count({
|
||||
where: {
|
||||
recorded_at: {
|
||||
[require('sequelize').Op.gte]: twentyFourHoursAgo
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log('\n24小时内的传感器数据总数:', recentCount);
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查数据时出错:', error);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
})();
|
||||
19
backend/tools/verification/check-sensor-table.js
Normal file
19
backend/tools/verification/check-sensor-table.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function checkSensorTable() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
const [results] = await sequelize.query('SHOW COLUMNS FROM sensor_data');
|
||||
console.log('sensor_data表结构:');
|
||||
console.table(results);
|
||||
|
||||
} catch (error) {
|
||||
console.error('查询失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkSensorTable();
|
||||
47
backend/tools/verification/check-table-columns.js
Normal file
47
backend/tools/verification/check-table-columns.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const { Animal, Device, Alert, Order, Farm } = require('./models');
|
||||
|
||||
async function checkTableColumns() {
|
||||
try {
|
||||
console.log('检查各表的列结构...');
|
||||
|
||||
// 检查farms表结构
|
||||
console.log('\n=== Farms表结构 ===');
|
||||
const farmAttrs = await Farm.describe();
|
||||
Object.keys(farmAttrs).forEach(col => {
|
||||
console.log(`${col}: ${farmAttrs[col].type}`);
|
||||
});
|
||||
|
||||
// 检查animals表结构
|
||||
console.log('\n=== Animals表结构 ===');
|
||||
const animalAttrs = await Animal.describe();
|
||||
Object.keys(animalAttrs).forEach(col => {
|
||||
console.log(`${col}: ${animalAttrs[col].type}`);
|
||||
});
|
||||
|
||||
// 检查devices表结构
|
||||
console.log('\n=== Devices表结构 ===');
|
||||
const deviceAttrs = await Device.describe();
|
||||
Object.keys(deviceAttrs).forEach(col => {
|
||||
console.log(`${col}: ${deviceAttrs[col].type}`);
|
||||
});
|
||||
|
||||
// 检查alerts表结构
|
||||
console.log('\n=== Alerts表结构 ===');
|
||||
const alertAttrs = await Alert.describe();
|
||||
Object.keys(alertAttrs).forEach(col => {
|
||||
console.log(`${col}: ${alertAttrs[col].type}`);
|
||||
});
|
||||
|
||||
// 检查orders表结构
|
||||
console.log('\n=== Orders表结构 ===');
|
||||
const orderAttrs = await Order.describe();
|
||||
Object.keys(orderAttrs).forEach(col => {
|
||||
console.log(`${col}: ${orderAttrs[col].type}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
checkTableColumns();
|
||||
53
backend/tools/verification/check-table-structure.js
Normal file
53
backend/tools/verification/check-table-structure.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 检查并同步数据库表结构脚本
|
||||
*/
|
||||
const { sequelize } = require('./models/index');
|
||||
|
||||
async function checkAndSyncDatabase() {
|
||||
try {
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 强制同步数据库(这会删除现有表并重新创建)
|
||||
console.log('\n开始同步数据库模型...');
|
||||
await sequelize.sync({ force: true });
|
||||
console.log('数据库模型同步完成');
|
||||
|
||||
// 检查创建的表
|
||||
console.log('\n=== 检查创建的表 ===');
|
||||
const [tables] = await sequelize.query("SHOW TABLES");
|
||||
console.log('已创建的表:', tables.map(row => Object.values(row)[0]));
|
||||
|
||||
// 检查devices表结构
|
||||
console.log('\n=== devices表结构 ===');
|
||||
const [devicesFields] = await sequelize.query("DESCRIBE devices");
|
||||
console.log('devices表字段:');
|
||||
devicesFields.forEach(field => {
|
||||
console.log(`- ${field.Field}: ${field.Type} (${field.Null === 'YES' ? 'NULL' : 'NOT NULL'})`);
|
||||
});
|
||||
|
||||
// 检查animals表结构
|
||||
console.log('\n=== animals表结构 ===');
|
||||
const [animalsFields] = await sequelize.query("DESCRIBE animals");
|
||||
console.log('animals表字段:');
|
||||
animalsFields.forEach(field => {
|
||||
console.log(`- ${field.Field}: ${field.Type} (${field.Null === 'YES' ? 'NULL' : 'NOT NULL'})`);
|
||||
});
|
||||
|
||||
// 检查alerts表结构
|
||||
console.log('\n=== alerts表结构 ===');
|
||||
const [alertsFields] = await sequelize.query("DESCRIBE alerts");
|
||||
console.log('alerts表字段:');
|
||||
alertsFields.forEach(field => {
|
||||
console.log(`- ${field.Field}: ${field.Type} (${field.Null === 'YES' ? 'NULL' : 'NOT NULL'})`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkAndSyncDatabase();
|
||||
50
backend/tools/verification/check-users.js
Normal file
50
backend/tools/verification/check-users.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const { User, Role } = require('./models');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
async function checkUsers() {
|
||||
try {
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 查看所有用户
|
||||
console.log('\n=== 查看所有用户 ===');
|
||||
const users = await User.findAll({
|
||||
attributes: ['id', 'username', 'email', 'password', 'status']
|
||||
});
|
||||
|
||||
console.log('用户数量:', users.length);
|
||||
users.forEach(user => {
|
||||
console.log(`ID: ${user.id}, 用户名: ${user.username}, 邮箱: ${user.email}, 状态: ${user.status}`);
|
||||
console.log(`密码哈希: ${user.password}`);
|
||||
});
|
||||
|
||||
// 测试密码验证
|
||||
console.log('\n=== 测试密码验证 ===');
|
||||
const testPassword = '123456';
|
||||
|
||||
// 测试testuser的密码
|
||||
const testHash1 = '$2b$10$CT0Uk9ueBFN4jc/5vnKGguDfr4cAyV3NUXKVKG6GrFJVsbcJakXLy'; // testuser的哈希
|
||||
const isValid1 = await bcrypt.compare(testPassword, testHash1);
|
||||
console.log('testuser密码验证结果:', isValid1);
|
||||
|
||||
// 测试testuser2的密码
|
||||
const testHash2 = '$2b$10$KJAf.o1ItgiTeff9dAJqyeLQ.f2QCRCR2cUlU/DLn6ifXcBLM3FvK'; // testuser2的哈希
|
||||
const isValid2 = await bcrypt.compare(testPassword, testHash2);
|
||||
console.log('testuser2密码验证结果:', isValid2);
|
||||
|
||||
// 测试手动生成的哈希
|
||||
const manualHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('手动生成的哈希:', manualHash);
|
||||
const isValid3 = await bcrypt.compare(testPassword, manualHash);
|
||||
console.log('手动哈希验证结果:', isValid3);
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查用户失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkUsers();
|
||||
27
backend/tools/verification/check_password.js
Normal file
27
backend/tools/verification/check_password.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
User.findOne({ where: { username: 'admin' } })
|
||||
.then(user => {
|
||||
if (user) {
|
||||
console.log('Admin 用户信息:');
|
||||
console.log(`用户名: ${user.username}`);
|
||||
console.log(`邮箱: ${user.email}`);
|
||||
console.log(`密码哈希: ${user.password}`);
|
||||
|
||||
// 测试常见密码
|
||||
const testPasswords = ['123456', 'admin', 'password', 'admin123'];
|
||||
|
||||
testPasswords.forEach(pwd => {
|
||||
const isMatch = bcrypt.compareSync(pwd, user.password);
|
||||
console.log(`密码 '${pwd}': ${isMatch ? '匹配' : '不匹配'}`);
|
||||
});
|
||||
} else {
|
||||
console.log('未找到 admin 用户');
|
||||
}
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('查询失败:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
14
backend/tools/verification/check_users.js
Normal file
14
backend/tools/verification/check_users.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { User } = require('./models');
|
||||
|
||||
User.findAll({ attributes: ['username', 'email'] })
|
||||
.then(users => {
|
||||
console.log('数据库中的用户:');
|
||||
users.forEach(user => {
|
||||
console.log(`用户名: ${user.username}, 邮箱: ${user.email}`);
|
||||
});
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('查询失败:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
26
backend/tools/verification/count-data.js
Normal file
26
backend/tools/verification/count-data.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function countData() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功\n');
|
||||
|
||||
// 检查各表的数据量
|
||||
const tables = ['farms', 'animals', 'devices', 'alerts', 'sensor_data'];
|
||||
|
||||
console.log('=== 数据统计 ===');
|
||||
for (const table of tables) {
|
||||
const [results] = await sequelize.query(`SELECT COUNT(*) as count FROM ${table}`);
|
||||
console.log(`${table.padEnd(12)}: ${results[0].count.toString().padStart(6)} 条记录`);
|
||||
}
|
||||
|
||||
console.log('\n数据导入完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('统计失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
countData();
|
||||
32
backend/tools/verification/verify-data.js
Normal file
32
backend/tools/verification/verify-data.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function verifyData() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 检查各表的数据量
|
||||
const tables = ['farms', 'animals', 'devices', 'alerts', 'sensor_data'];
|
||||
|
||||
for (const table of tables) {
|
||||
const [results] = await sequelize.query(`SELECT COUNT(*) as count FROM ${table}`);
|
||||
console.log(`${table} 表: ${results[0].count} 条记录`);
|
||||
}
|
||||
|
||||
// 检查最新的一些数据
|
||||
console.log('\n=== 最新的预警数据 ===');
|
||||
const [alerts] = await sequelize.query('SELECT * FROM alerts ORDER BY created_at DESC LIMIT 5');
|
||||
console.table(alerts);
|
||||
|
||||
console.log('\n=== 最新的传感器数据 ===');
|
||||
const [sensors] = await sequelize.query('SELECT * FROM sensor_data ORDER BY recorded_at DESC LIMIT 10');
|
||||
console.table(sensors);
|
||||
|
||||
} catch (error) {
|
||||
console.error('验证失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
verifyData();
|
||||
105
backend/tools/verification/verify-environment-data.js
Normal file
105
backend/tools/verification/verify-environment-data.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
require('dotenv').config();
|
||||
|
||||
async function verifyEnvironmentData() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
// 创建数据库连接
|
||||
connection = await mysql.createConnection({
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME
|
||||
});
|
||||
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 检查表是否存在
|
||||
const [tables] = await connection.execute(
|
||||
"SHOW TABLES LIKE 'environment_schedules'"
|
||||
);
|
||||
|
||||
if (tables.length === 0) {
|
||||
console.log('环境监测时刻表不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('环境监测时刻表存在');
|
||||
|
||||
// 获取总记录数
|
||||
const [countResult] = await connection.execute(
|
||||
'SELECT COUNT(*) as total_records FROM environment_schedules'
|
||||
);
|
||||
console.log(`\n总记录数: ${countResult[0].total_records}`);
|
||||
|
||||
// 按农场分组统计
|
||||
const [farmStats] = await connection.execute(
|
||||
`SELECT farm_id, COUNT(*) as record_count,
|
||||
MIN(monitoring_date) as earliest_date,
|
||||
MAX(monitoring_date) as latest_date
|
||||
FROM environment_schedules
|
||||
GROUP BY farm_id
|
||||
ORDER BY farm_id`
|
||||
);
|
||||
|
||||
console.log('\n按农场统计:');
|
||||
farmStats.forEach(stat => {
|
||||
console.log(`农场${stat.farm_id}: ${stat.record_count}条记录, 日期范围: ${stat.earliest_date} 到 ${stat.latest_date}`);
|
||||
});
|
||||
|
||||
// 获取最新的10条记录
|
||||
const [recentRecords] = await connection.execute(
|
||||
`SELECT farm_id, device_id, schedule_time, temperature, humidity,
|
||||
DATE(monitoring_date) as date, status
|
||||
FROM environment_schedules
|
||||
ORDER BY monitoring_date DESC, schedule_time DESC
|
||||
LIMIT 10`
|
||||
);
|
||||
|
||||
console.log('\n最新的10条环境监测记录:');
|
||||
console.log('农场ID | 设备ID | 日期 | 时间 | 温度(°C) | 湿度(%) | 状态');
|
||||
console.log('-------|----------|------------|----------|----------|---------|--------');
|
||||
recentRecords.forEach(record => {
|
||||
console.log(`${record.farm_id.toString().padEnd(6)} | ${record.device_id.padEnd(8)} | ${record.date} | ${record.schedule_time} | ${record.temperature.toString().padEnd(8)} | ${record.humidity.toString().padEnd(7)} | ${record.status}`);
|
||||
});
|
||||
|
||||
// 检查数据分布
|
||||
const [timeStats] = await connection.execute(
|
||||
`SELECT schedule_time, COUNT(*) as count
|
||||
FROM environment_schedules
|
||||
GROUP BY schedule_time
|
||||
ORDER BY schedule_time`
|
||||
);
|
||||
|
||||
console.log('\n按监测时间统计:');
|
||||
timeStats.forEach(stat => {
|
||||
console.log(`${stat.schedule_time}: ${stat.count}条记录`);
|
||||
});
|
||||
|
||||
// 温度湿度范围统计
|
||||
const [rangeStats] = await connection.execute(
|
||||
`SELECT
|
||||
MIN(temperature) as min_temp, MAX(temperature) as max_temp, AVG(temperature) as avg_temp,
|
||||
MIN(humidity) as min_humidity, MAX(humidity) as max_humidity, AVG(humidity) as avg_humidity
|
||||
FROM environment_schedules`
|
||||
);
|
||||
|
||||
console.log('\n温度湿度统计:');
|
||||
const stats = rangeStats[0];
|
||||
console.log(`温度范围: ${parseFloat(stats.min_temp).toFixed(2)}°C - ${parseFloat(stats.max_temp).toFixed(2)}°C (平均: ${parseFloat(stats.avg_temp).toFixed(2)}°C)`);
|
||||
console.log(`湿度范围: ${parseFloat(stats.min_humidity).toFixed(2)}% - ${parseFloat(stats.max_humidity).toFixed(2)}% (平均: ${parseFloat(stats.avg_humidity).toFixed(2)}%)`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('验证失败:', error.message);
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行验证脚本
|
||||
verifyEnvironmentData();
|
||||
110
backend/tools/verification/verify-farms-import.js
Normal file
110
backend/tools/verification/verify-farms-import.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const { Farm } = require('./models');
|
||||
|
||||
/**
|
||||
* 验证farms数据导入结果
|
||||
*/
|
||||
async function verifyFarmsImport() {
|
||||
try {
|
||||
console.log('验证farms数据导入结果...');
|
||||
|
||||
// 获取所有farms数据
|
||||
const farms = await Farm.findAll({
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
|
||||
console.log(`\n✅ 数据库中共有 ${farms.length} 个农场`);
|
||||
|
||||
// 验证API静态数据(前3个)
|
||||
const apiStaticFarms = farms.slice(0, 3);
|
||||
console.log('\n📊 API静态数据验证:');
|
||||
apiStaticFarms.forEach(farm => {
|
||||
console.log(` ID: ${farm.id}, Name: ${farm.name}, Type: ${farm.type}, Address: ${farm.address}`);
|
||||
});
|
||||
|
||||
// 验证种子数据(后8个)
|
||||
const seedFarms = farms.slice(3);
|
||||
console.log('\n🌱 种子数据验证:');
|
||||
seedFarms.forEach(farm => {
|
||||
console.log(` ID: ${farm.id}, Name: ${farm.name}, Type: ${farm.type}, Address: ${farm.address}`);
|
||||
});
|
||||
|
||||
// 验证数据完整性
|
||||
console.log('\n🔍 数据完整性检查:');
|
||||
|
||||
const missingFields = [];
|
||||
farms.forEach(farm => {
|
||||
if (!farm.name) missingFields.push(`Farm ${farm.id}: 缺少name字段`);
|
||||
if (!farm.type) missingFields.push(`Farm ${farm.id}: 缺少type字段`);
|
||||
if (!farm.location) missingFields.push(`Farm ${farm.id}: 缺少location字段`);
|
||||
if (!farm.status) missingFields.push(`Farm ${farm.id}: 缺少status字段`);
|
||||
});
|
||||
|
||||
if (missingFields.length === 0) {
|
||||
console.log(' ✅ 所有农场数据字段完整');
|
||||
} else {
|
||||
console.log(' ❌ 发现缺失字段:');
|
||||
missingFields.forEach(field => console.log(` ${field}`));
|
||||
}
|
||||
|
||||
// 验证ID连续性
|
||||
const ids = farms.map(farm => farm.id);
|
||||
const expectedIds = Array.from({length: farms.length}, (_, i) => i + 1);
|
||||
const idsMatch = JSON.stringify(ids) === JSON.stringify(expectedIds);
|
||||
|
||||
if (idsMatch) {
|
||||
console.log(' ✅ ID序列连续 (1 到 ' + farms.length + ')');
|
||||
} else {
|
||||
console.log(' ❌ ID序列不连续');
|
||||
console.log(` 实际ID: [${ids.join(', ')}]`);
|
||||
console.log(` 期望ID: [${expectedIds.join(', ')}]`);
|
||||
}
|
||||
|
||||
// 统计农场类型
|
||||
const typeStats = {};
|
||||
farms.forEach(farm => {
|
||||
typeStats[farm.type] = (typeStats[farm.type] || 0) + 1;
|
||||
});
|
||||
|
||||
console.log('\n📈 农场类型统计:');
|
||||
Object.entries(typeStats).forEach(([type, count]) => {
|
||||
console.log(` ${type}: ${count} 个`);
|
||||
});
|
||||
|
||||
// 验证地理位置数据
|
||||
console.log('\n🗺️ 地理位置数据验证:');
|
||||
const locationErrors = [];
|
||||
farms.forEach(farm => {
|
||||
try {
|
||||
if (typeof farm.location === 'string') {
|
||||
const location = JSON.parse(farm.location);
|
||||
if (!location.lat || !location.lng) {
|
||||
locationErrors.push(`Farm ${farm.id} (${farm.name}): 缺少经纬度信息`);
|
||||
}
|
||||
} else if (typeof farm.location === 'object') {
|
||||
if (!farm.location.lat || !farm.location.lng) {
|
||||
locationErrors.push(`Farm ${farm.id} (${farm.name}): 缺少经纬度信息`);
|
||||
}
|
||||
} else {
|
||||
locationErrors.push(`Farm ${farm.id} (${farm.name}): location字段格式错误`);
|
||||
}
|
||||
} catch (error) {
|
||||
locationErrors.push(`Farm ${farm.id} (${farm.name}): location JSON解析失败`);
|
||||
}
|
||||
});
|
||||
|
||||
if (locationErrors.length === 0) {
|
||||
console.log(' ✅ 所有农场地理位置数据有效');
|
||||
} else {
|
||||
console.log(' ❌ 发现地理位置数据问题:');
|
||||
locationErrors.forEach(error => console.log(` ${error}`));
|
||||
}
|
||||
|
||||
console.log('\n🎉 farms数据导入验证完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 验证失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行验证
|
||||
verifyFarmsImport();
|
||||
Reference in New Issue
Block a user