Initial commit: 宁夏智慧养殖监管平台
This commit is contained in:
270
backend/config/database-pool.js
Normal file
270
backend/config/database-pool.js
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* 数据库连接池配置
|
||||
* @file database-pool.js
|
||||
* @description 配置和管理Sequelize数据库连接池
|
||||
*/
|
||||
const { Sequelize } = require('sequelize');
|
||||
const { EventEmitter } = require('events');
|
||||
const logger = require('../utils/logger');
|
||||
const ormConfig = require('./orm-config');
|
||||
|
||||
// 从环境变量获取数据库连接参数
|
||||
const DB_DIALECT = process.env.DB_DIALECT || 'mysql';
|
||||
const DB_STORAGE = process.env.DB_STORAGE || './database.sqlite';
|
||||
const DB_NAME = process.env.DB_NAME || 'nxTest';
|
||||
const DB_USER = process.env.DB_USER || 'root';
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD || 'Aiotagro@741';
|
||||
const DB_HOST = process.env.DB_HOST || '129.211.213.226';
|
||||
const DB_PORT = process.env.DB_PORT || 3306;
|
||||
|
||||
// 数据库连接池事件发射器
|
||||
class DatabasePoolEmitter extends EventEmitter {}
|
||||
const poolEvents = new DatabasePoolEmitter();
|
||||
|
||||
// 默认连接池配置
|
||||
const DEFAULT_POOL_CONFIG = {
|
||||
max: parseInt(process.env.DB_POOL_MAX || '10'), // 最大连接数
|
||||
min: parseInt(process.env.DB_POOL_MIN || '2'), // 最小连接数
|
||||
acquire: parseInt(process.env.DB_POOL_ACQUIRE || '30000'), // 获取连接超时时间(毫秒)
|
||||
idle: parseInt(process.env.DB_POOL_IDLE || '10000'), // 连接空闲多久后释放(毫秒)
|
||||
evict: parseInt(process.env.DB_POOL_EVICT || '1000'), // 多久检查一次空闲连接(毫秒)
|
||||
};
|
||||
|
||||
// 创建Sequelize实例
|
||||
let sequelize;
|
||||
if (DB_DIALECT === 'sqlite') {
|
||||
sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: DB_STORAGE,
|
||||
logging: (msg) => logger.debug(msg),
|
||||
benchmark: process.env.NODE_ENV !== 'production',
|
||||
pool: DEFAULT_POOL_CONFIG,
|
||||
define: ormConfig.defaultModelOptions
|
||||
});
|
||||
} else {
|
||||
sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
|
||||
host: DB_HOST,
|
||||
port: DB_PORT,
|
||||
dialect: DB_DIALECT,
|
||||
logging: (msg) => logger.debug(msg),
|
||||
benchmark: process.env.NODE_ENV !== 'production',
|
||||
pool: DEFAULT_POOL_CONFIG,
|
||||
define: ormConfig.defaultModelOptions,
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4',
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: true,
|
||||
dateStrings: true,
|
||||
multipleStatements: process.env.DB_MULTIPLE_STATEMENTS === 'true'
|
||||
},
|
||||
timezone: '+08:00'
|
||||
});
|
||||
}
|
||||
|
||||
// 监听连接池事件 - 使用Sequelize实例的hooks
|
||||
sequelize.addHook('afterConnect', (connection, config) => {
|
||||
logger.info(`数据库连接已建立`);
|
||||
poolEvents.emit('connect', connection);
|
||||
});
|
||||
|
||||
sequelize.addHook('beforeDisconnect', (connection) => {
|
||||
logger.info(`数据库连接即将断开`);
|
||||
poolEvents.emit('disconnect', connection);
|
||||
});
|
||||
|
||||
// 注意:acquire和release事件在新版Sequelize中需要通过其他方式监听
|
||||
// 这里我们使用定时器来监控连接池状态
|
||||
setInterval(() => {
|
||||
if (sequelize.connectionManager && sequelize.connectionManager.pool) {
|
||||
const pool = sequelize.connectionManager.pool;
|
||||
poolEvents.emit('poolStatus', {
|
||||
size: pool.size || 0,
|
||||
available: pool.available || 0,
|
||||
using: pool.using || 0,
|
||||
waiting: pool.waiting || 0
|
||||
});
|
||||
}
|
||||
}, 30000); // 每30秒检查一次连接池状态
|
||||
|
||||
// 测试数据库连接
|
||||
async function testConnection() {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
logger.info('数据库连接测试成功');
|
||||
poolEvents.emit('connectionSuccess');
|
||||
return { success: true, message: '数据库连接测试成功' };
|
||||
} catch (error) {
|
||||
logger.error('数据库连接测试失败:', error);
|
||||
poolEvents.emit('connectionError', error);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取连接池状态
|
||||
async function getPoolStatus() {
|
||||
try {
|
||||
const pool = sequelize.connectionManager.pool;
|
||||
if (!pool) {
|
||||
return { error: '连接池未初始化' };
|
||||
}
|
||||
|
||||
// 获取连接池统计信息
|
||||
const status = {
|
||||
all: pool.size, // 所有连接数
|
||||
idle: pool.idleCount, // 空闲连接数
|
||||
used: pool.size - pool.idleCount, // 使用中的连接数
|
||||
waiting: pool.pending, // 等待连接的请求数
|
||||
max: pool.options.max, // 最大连接数
|
||||
min: pool.options.min, // 最小连接数
|
||||
acquire: pool.options.acquire, // 获取连接超时时间
|
||||
idle: pool.options.idle, // 空闲超时时间
|
||||
created: new Date().toISOString(), // 状态创建时间
|
||||
utilization: (pool.size > 0) ? ((pool.size - pool.idleCount) / pool.size) * 100 : 0 // 利用率
|
||||
};
|
||||
|
||||
poolEvents.emit('poolStatus', status);
|
||||
return status;
|
||||
} catch (error) {
|
||||
logger.error('获取连接池状态失败:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 监控连接池
|
||||
async function monitorPool(interval = 60000) {
|
||||
try {
|
||||
const status = await getPoolStatus();
|
||||
logger.debug('连接池状态:', status);
|
||||
|
||||
// 检查连接池利用率
|
||||
if (status.utilization > 80) {
|
||||
logger.warn(`连接池利用率过高: ${status.utilization.toFixed(2)}%`);
|
||||
poolEvents.emit('highUtilization', status);
|
||||
}
|
||||
|
||||
// 检查等待连接的请求数
|
||||
if (status.waiting > 5) {
|
||||
logger.warn(`连接池等待请求过多: ${status.waiting}`);
|
||||
poolEvents.emit('highWaiting', status);
|
||||
}
|
||||
|
||||
return status;
|
||||
} catch (error) {
|
||||
logger.error('监控连接池失败:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭连接池
|
||||
async function closePool() {
|
||||
try {
|
||||
await sequelize.close();
|
||||
logger.info('数据库连接池已关闭');
|
||||
poolEvents.emit('poolClosed');
|
||||
return { success: true, message: '数据库连接池已关闭' };
|
||||
} catch (error) {
|
||||
logger.error('关闭数据库连接池失败:', error);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 重置连接池
|
||||
async function resetPool() {
|
||||
try {
|
||||
await closePool();
|
||||
|
||||
// 重新初始化连接池
|
||||
sequelize.connectionManager.initPools();
|
||||
|
||||
// 测试新的连接池
|
||||
const testResult = await testConnection();
|
||||
|
||||
if (testResult.success) {
|
||||
logger.info('数据库连接池已重置');
|
||||
poolEvents.emit('poolReset');
|
||||
return { success: true, message: '数据库连接池已重置' };
|
||||
} else {
|
||||
throw new Error(testResult.message);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('重置数据库连接池失败:', error);
|
||||
poolEvents.emit('poolResetError', error);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 优化连接池配置
|
||||
async function optimizePool(config = {}) {
|
||||
try {
|
||||
// 获取当前状态
|
||||
const currentStatus = await getPoolStatus();
|
||||
|
||||
// 计算新的配置
|
||||
const newConfig = {
|
||||
max: config.max || Math.max(currentStatus.max, Math.ceil(currentStatus.used * 1.5)),
|
||||
min: config.min || Math.min(currentStatus.min, Math.floor(currentStatus.used * 0.5)),
|
||||
acquire: config.acquire || currentStatus.acquire,
|
||||
idle: config.idle || currentStatus.idle
|
||||
};
|
||||
|
||||
// 确保最小连接数不小于1
|
||||
newConfig.min = Math.max(newConfig.min, 1);
|
||||
|
||||
// 确保最大连接数不小于最小连接数
|
||||
newConfig.max = Math.max(newConfig.max, newConfig.min);
|
||||
|
||||
// 应用新配置
|
||||
await closePool();
|
||||
|
||||
// 更新连接池配置
|
||||
sequelize.options.pool = newConfig;
|
||||
|
||||
// 重新初始化连接池
|
||||
sequelize.connectionManager.initPools();
|
||||
|
||||
// 测试新的连接池
|
||||
const testResult = await testConnection();
|
||||
|
||||
if (testResult.success) {
|
||||
logger.info('数据库连接池已优化:', newConfig);
|
||||
poolEvents.emit('poolOptimized', newConfig);
|
||||
return { success: true, message: '数据库连接池已优化', config: newConfig };
|
||||
} else {
|
||||
throw new Error(testResult.message);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('优化数据库连接池失败:', error);
|
||||
poolEvents.emit('poolOptimizationError', error);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据库表列表
|
||||
async function getTablesList() {
|
||||
try {
|
||||
const [results] = await sequelize.query(
|
||||
`SELECT TABLE_NAME FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
ORDER BY TABLE_NAME`,
|
||||
{ replacements: [DB_NAME] }
|
||||
);
|
||||
|
||||
return results.map(row => row.TABLE_NAME);
|
||||
} catch (error) {
|
||||
logger.error('获取数据库表列表失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 导出模块
|
||||
module.exports = {
|
||||
sequelize,
|
||||
testConnection,
|
||||
getPoolStatus,
|
||||
monitorPool,
|
||||
closePool,
|
||||
resetPool,
|
||||
optimizePool,
|
||||
getTablesList,
|
||||
events: poolEvents
|
||||
};
|
||||
46
backend/config/database-simple.js
Normal file
46
backend/config/database-simple.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const { Sequelize } = require('sequelize');
|
||||
require('dotenv').config();
|
||||
|
||||
// 从环境变量获取数据库配置
|
||||
const DB_DIALECT = process.env.DB_DIALECT || 'mysql';
|
||||
const DB_HOST = process.env.DB_HOST || '129.211.213.226';
|
||||
const DB_PORT = process.env.DB_PORT || 3306;
|
||||
const DB_NAME = process.env.DB_NAME || 'nxTest';
|
||||
const DB_USER = process.env.DB_USER || 'root';
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD || 'Aiotagro@741';
|
||||
|
||||
// 创建Sequelize实例
|
||||
const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
|
||||
host: DB_HOST,
|
||||
port: DB_PORT,
|
||||
dialect: DB_DIALECT,
|
||||
logging: false,
|
||||
pool: {
|
||||
max: 5,
|
||||
min: 0,
|
||||
acquire: 30000,
|
||||
idle: 10000
|
||||
},
|
||||
define: {
|
||||
timestamps: true,
|
||||
charset: 'utf8mb4'
|
||||
},
|
||||
timezone: '+08:00'
|
||||
});
|
||||
|
||||
// 测试数据库连接
|
||||
const testConnection = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('❌ 数据库连接失败:', err.message);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
sequelize,
|
||||
testConnection
|
||||
};
|
||||
68
backend/config/database.js
Normal file
68
backend/config/database.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const { Sequelize } = require('sequelize');
|
||||
|
||||
// 从环境变量获取数据库配置
|
||||
const dialect = process.env.DB_DIALECT || 'mysql';
|
||||
const config = {
|
||||
logging: false,
|
||||
define: {
|
||||
timestamps: true
|
||||
}
|
||||
};
|
||||
|
||||
// 根据数据库类型配置不同的选项
|
||||
if (dialect === 'sqlite') {
|
||||
config.storage = process.env.DB_STORAGE || './database.sqlite';
|
||||
config.dialect = 'sqlite';
|
||||
} else {
|
||||
config.host = process.env.DB_HOST || '129.211.213.226';
|
||||
config.port = process.env.DB_PORT || 3306;
|
||||
config.dialect = 'mysql';
|
||||
config.timezone = '+08:00';
|
||||
config.define.charset = 'utf8mb4';
|
||||
config.define.collate = 'utf8mb4_unicode_ci';
|
||||
config.pool = {
|
||||
max: 5,
|
||||
min: 0,
|
||||
acquire: 30000,
|
||||
idle: 10000
|
||||
};
|
||||
}
|
||||
|
||||
let sequelize;
|
||||
if (dialect === 'sqlite') {
|
||||
sequelize = new Sequelize(config);
|
||||
} else {
|
||||
sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'nxTest',
|
||||
process.env.DB_USER || 'root',
|
||||
process.env.DB_PASSWORD || 'Aiotagro@741',
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
// 测试数据库连接(最多重试3次)
|
||||
const MAX_RETRIES = 3;
|
||||
let retryCount = 0;
|
||||
|
||||
const testConnection = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
} catch (err) {
|
||||
console.error('数据库连接失败:', err);
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
retryCount++;
|
||||
console.log(`正在重试连接 (${retryCount}/${MAX_RETRIES})...`);
|
||||
setTimeout(testConnection, 5000); // 5秒后重试
|
||||
} else {
|
||||
console.error('数据库连接失败,应用将使用模拟数据运行');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 异步测试连接,不阻塞应用启动
|
||||
testConnection().catch(() => {
|
||||
console.log('数据库连接测试完成,应用继续启动');
|
||||
});
|
||||
|
||||
module.exports = sequelize;
|
||||
218
backend/config/db-monitor.js
Normal file
218
backend/config/db-monitor.js
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* 数据库连接监控工具
|
||||
* @file db-monitor.js
|
||||
* @description 实时监控数据库连接状态
|
||||
*/
|
||||
const { sequelize } = require('./database-pool');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
// 创建事件发射器
|
||||
const dbEvents = new EventEmitter();
|
||||
|
||||
/**
|
||||
* 检查数据库连接状态
|
||||
* @returns {Promise<Object>} 连接状态信息
|
||||
*/
|
||||
async function checkConnectionStatus() {
|
||||
try {
|
||||
// 测试连接
|
||||
await sequelize.authenticate();
|
||||
|
||||
// 获取连接信息
|
||||
const processlist = await sequelize.query(
|
||||
'SHOW PROCESSLIST',
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
// 获取连接统计信息
|
||||
const connectionStats = await sequelize.query(
|
||||
'SELECT * FROM performance_schema.host_cache',
|
||||
{ type: QueryTypes.SELECT }
|
||||
).catch(() => []);
|
||||
|
||||
// 获取等待事件
|
||||
const waitEvents = await sequelize.query(
|
||||
'SELECT * FROM performance_schema.events_waits_current LIMIT 10',
|
||||
{ type: QueryTypes.SELECT }
|
||||
).catch(() => []);
|
||||
|
||||
// 返回状态信息
|
||||
const status = {
|
||||
connected: true,
|
||||
connections: processlist.length,
|
||||
connectionStats: connectionStats,
|
||||
waitEvents: waitEvents,
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
// 触发状态更新事件
|
||||
dbEvents.emit('status_update', status);
|
||||
|
||||
return status;
|
||||
} catch (error) {
|
||||
// 连接失败
|
||||
const errorStatus = {
|
||||
connected: false,
|
||||
error: error.message,
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
// 触发错误事件
|
||||
dbEvents.emit('connection_error', errorStatus);
|
||||
|
||||
return errorStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接池状态
|
||||
* @returns {Promise<Object>} 连接池状态
|
||||
*/
|
||||
async function getPoolStatus() {
|
||||
try {
|
||||
const pool = sequelize.connectionManager.pool;
|
||||
|
||||
if (!pool) {
|
||||
return { error: '连接池未初始化' };
|
||||
}
|
||||
|
||||
const status = {
|
||||
total: pool.size,
|
||||
available: pool.available,
|
||||
borrowed: pool.borrowed,
|
||||
pending: pool.pending,
|
||||
max: pool.max,
|
||||
min: pool.min,
|
||||
idle: pool.idleTimeoutMillis,
|
||||
acquire: pool.acquireTimeoutMillis
|
||||
};
|
||||
|
||||
// 触发连接池状态更新事件
|
||||
dbEvents.emit('pool_status_update', status);
|
||||
|
||||
return status;
|
||||
} catch (error) {
|
||||
console.error('获取连接池状态失败:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别慢查询
|
||||
* @param {number} threshold 慢查询阈值(毫秒)
|
||||
* @returns {Promise<Array>} 慢查询列表
|
||||
*/
|
||||
async function identifySlowQueries(threshold = 1000) {
|
||||
try {
|
||||
const slowQueries = await sequelize.query(
|
||||
'SELECT * FROM information_schema.PROCESSLIST WHERE TIME > ?',
|
||||
{
|
||||
replacements: [threshold / 1000], // 转换为秒
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
if (slowQueries.length > 0) {
|
||||
// 触发慢查询事件
|
||||
dbEvents.emit('slow_queries_detected', slowQueries);
|
||||
}
|
||||
|
||||
return slowQueries;
|
||||
} catch (error) {
|
||||
console.error('识别慢查询失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录数据库错误
|
||||
* @param {Error} error 错误对象
|
||||
*/
|
||||
function logDatabaseError(error) {
|
||||
const errorLog = {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
// 触发错误日志事件
|
||||
dbEvents.emit('error_logged', errorLog);
|
||||
|
||||
console.error('数据库错误:', error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定期监控
|
||||
* @param {number} interval 监控间隔(毫秒)
|
||||
* @returns {Object} 监控器对象
|
||||
*/
|
||||
function startMonitoring(interval = 60000) {
|
||||
// 创建监控器
|
||||
const monitor = {
|
||||
interval: null,
|
||||
isRunning: false,
|
||||
lastStatus: null,
|
||||
start: function() {
|
||||
if (this.isRunning) return;
|
||||
|
||||
this.isRunning = true;
|
||||
this.interval = setInterval(async () => {
|
||||
try {
|
||||
// 检查连接状态
|
||||
this.lastStatus = await checkConnectionStatus();
|
||||
|
||||
// 检查连接池状态
|
||||
await getPoolStatus();
|
||||
|
||||
// 检查慢查询
|
||||
await identifySlowQueries();
|
||||
} catch (error) {
|
||||
logDatabaseError(error);
|
||||
}
|
||||
}, interval);
|
||||
|
||||
dbEvents.emit('monitoring_started', { interval });
|
||||
return this;
|
||||
},
|
||||
stop: function() {
|
||||
if (!this.isRunning) return;
|
||||
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
this.isRunning = false;
|
||||
|
||||
dbEvents.emit('monitoring_stopped');
|
||||
return this;
|
||||
},
|
||||
getStatus: function() {
|
||||
return {
|
||||
isRunning: this.isRunning,
|
||||
interval: interval,
|
||||
lastStatus: this.lastStatus,
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return monitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听数据库事件
|
||||
* @param {string} event 事件名称
|
||||
* @param {Function} listener 监听器函数
|
||||
*/
|
||||
function onDatabaseEvent(event, listener) {
|
||||
dbEvents.on(event, listener);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkConnectionStatus,
|
||||
getPoolStatus,
|
||||
identifySlowQueries,
|
||||
logDatabaseError,
|
||||
startMonitoring,
|
||||
onDatabaseEvent,
|
||||
dbEvents
|
||||
};
|
||||
123
backend/config/orm-config.js
Normal file
123
backend/config/orm-config.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* ORM配置文件
|
||||
* @file orm-config.js
|
||||
* @description 提供统一的ORM配置和扩展功能
|
||||
*/
|
||||
const { Sequelize, DataTypes, Op } = require('sequelize');
|
||||
const sequelize = require('./database-pool').sequelize;
|
||||
const queryOptimizer = require('./query-optimizer');
|
||||
|
||||
/**
|
||||
* 默认模型选项
|
||||
*/
|
||||
const defaultModelOptions = {
|
||||
timestamps: true, // 默认添加 createdAt 和 updatedAt
|
||||
paranoid: true, // 软删除(添加 deletedAt 而不是真正删除数据)
|
||||
underscored: true, // 使用下划线命名法 (例如: created_at 而不是 createdAt)
|
||||
freezeTableName: false, // 使用模型名称的复数形式作为表名
|
||||
charset: 'utf8mb4', // 字符集
|
||||
collate: 'utf8mb4_unicode_ci', // 排序规则
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建模型时的默认字段
|
||||
*/
|
||||
const defaultFields = {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
comment: '创建时间'
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
onUpdate: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
comment: '更新时间'
|
||||
},
|
||||
deleted_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '删除时间'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 扩展Sequelize模型功能
|
||||
* @param {Object} modelClass Sequelize模型类
|
||||
*/
|
||||
function extendModel(modelClass) {
|
||||
// 添加通用的查询方法
|
||||
modelClass.findAllActive = function(options = {}) {
|
||||
return this.findAll({
|
||||
...options,
|
||||
where: {
|
||||
...options.where,
|
||||
deleted_at: null
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 添加分页查询方法
|
||||
modelClass.findAllPaginated = function({ page = 1, pageSize = 10, ...options } = {}) {
|
||||
const offset = (page - 1) * pageSize;
|
||||
return this.findAndCountAll({
|
||||
...options,
|
||||
limit: pageSize,
|
||||
offset
|
||||
}).then(result => ({
|
||||
rows: result.rows,
|
||||
total: result.count,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.ceil(result.count / pageSize)
|
||||
}));
|
||||
};
|
||||
|
||||
// 添加批量更新方法
|
||||
modelClass.bulkUpdateById = async function(records, options = {}) {
|
||||
if (!Array.isArray(records) || records.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (const record of records) {
|
||||
if (!record.id) continue;
|
||||
const [affectedCount, affectedRows] = await this.update(record, {
|
||||
where: { id: record.id },
|
||||
returning: true,
|
||||
...options
|
||||
});
|
||||
if (affectedCount > 0 && affectedRows.length > 0) {
|
||||
results.push(affectedRows[0]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化ORM
|
||||
* @returns {Object} Sequelize实例和工具函数
|
||||
*/
|
||||
function initORM() {
|
||||
// 扩展Sequelize.Model
|
||||
extendModel(Sequelize.Model);
|
||||
|
||||
return {
|
||||
sequelize,
|
||||
Sequelize,
|
||||
DataTypes,
|
||||
Op,
|
||||
defaultModelOptions,
|
||||
defaultFields,
|
||||
queryOptimizer
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = initORM();
|
||||
122
backend/config/performance-config.js
Normal file
122
backend/config/performance-config.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 性能监控配置
|
||||
* @file performance-config.js
|
||||
* @description 性能监控系统的配置和集成
|
||||
*/
|
||||
const { performanceMonitor, events: perfEvents } = require('../utils/performance-monitor');
|
||||
const { apiPerformanceMonitor, apiErrorMonitor } = require('../middleware/performance-middleware');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* 初始化性能监控系统
|
||||
* @param {Object} app Express应用实例
|
||||
* @param {Object} options 配置选项
|
||||
* @param {boolean} options.autoStart 是否自动启动监控
|
||||
* @param {number} options.interval 监控间隔(毫秒)
|
||||
* @param {Object} options.thresholds 警报阈值
|
||||
* @param {boolean} options.logToConsole 是否将性能日志输出到控制台
|
||||
* @returns {Object} 性能监控实例
|
||||
*/
|
||||
function initPerformanceMonitoring(app, options = {}) {
|
||||
const {
|
||||
autoStart = true,
|
||||
interval = 60000, // 默认1分钟
|
||||
thresholds = {},
|
||||
logToConsole = false
|
||||
} = options;
|
||||
|
||||
// 设置警报阈值
|
||||
if (Object.keys(thresholds).length > 0) {
|
||||
performanceMonitor.setAlertThresholds(thresholds);
|
||||
}
|
||||
|
||||
// 应用API性能监控中间件
|
||||
app.use(apiPerformanceMonitor);
|
||||
|
||||
// 应用API错误监控中间件(应在路由之后应用)
|
||||
app.use(apiErrorMonitor);
|
||||
|
||||
// 设置事件监听
|
||||
setupEventListeners(logToConsole);
|
||||
|
||||
// 自动启动监控
|
||||
if (autoStart) {
|
||||
performanceMonitor.startMonitoring(interval);
|
||||
logger.info(`性能监控系统已自动启动,监控间隔: ${interval}ms`);
|
||||
}
|
||||
|
||||
return performanceMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置性能监控事件监听
|
||||
* @param {boolean} logToConsole 是否将性能日志输出到控制台
|
||||
*/
|
||||
function setupEventListeners(logToConsole = false) {
|
||||
// 监控启动事件
|
||||
perfEvents.on('monitoringStarted', (data) => {
|
||||
logger.info(`性能监控已启动,间隔: ${data.interval}ms`);
|
||||
});
|
||||
|
||||
// 监控停止事件
|
||||
perfEvents.on('monitoringStopped', () => {
|
||||
logger.info('性能监控已停止');
|
||||
});
|
||||
|
||||
// 数据库状态变化事件
|
||||
perfEvents.on('databaseStatus', (status) => {
|
||||
if (logToConsole) {
|
||||
logger.info('数据库状态更新:', status);
|
||||
}
|
||||
});
|
||||
|
||||
// 数据库错误事件
|
||||
perfEvents.on('databaseError', (error) => {
|
||||
logger.error('数据库错误:', error);
|
||||
});
|
||||
|
||||
// 慢查询事件
|
||||
perfEvents.on('slowQuery', (query) => {
|
||||
logger.warn(`检测到慢查询: ${query.duration}ms - ${query.query.substring(0, 100)}...`);
|
||||
});
|
||||
|
||||
// API错误事件
|
||||
perfEvents.on('apiError', (data) => {
|
||||
logger.error(`API错误: ${data.method} ${data.path} - ${data.error}`);
|
||||
});
|
||||
|
||||
// 慢API请求事件
|
||||
perfEvents.on('slowApiRequest', (data) => {
|
||||
logger.warn(`慢API请求: ${data.endpoint} - ${data.duration}ms (阈值: ${data.threshold}ms)`);
|
||||
});
|
||||
|
||||
// 高CPU使用率事件
|
||||
perfEvents.on('highCpuUsage', (data) => {
|
||||
logger.warn(`高CPU使用率: ${data.usage}% (阈值: ${data.threshold}%)`);
|
||||
});
|
||||
|
||||
// 高内存使用率事件
|
||||
perfEvents.on('highMemoryUsage', (data) => {
|
||||
logger.warn(`高内存使用率: ${data.usage}% (阈值: ${data.threshold}%)`);
|
||||
});
|
||||
|
||||
// 高磁盘使用率事件
|
||||
perfEvents.on('highDiskUsage', (data) => {
|
||||
logger.warn(`高磁盘使用率: ${data.usage}% (阈值: ${data.threshold}%)`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性能监控路由
|
||||
* @returns {Object} Express路由
|
||||
*/
|
||||
function getPerformanceRoutes() {
|
||||
return require('../routes/performance-routes');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initPerformanceMonitoring,
|
||||
getPerformanceRoutes,
|
||||
performanceMonitor,
|
||||
perfEvents
|
||||
};
|
||||
246
backend/config/query-optimizer.js
Normal file
246
backend/config/query-optimizer.js
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* 数据库查询优化器
|
||||
* @file query-optimizer.js
|
||||
* @description 监控和优化SQL查询性能
|
||||
*/
|
||||
const { sequelize } = require('./database-pool');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 记录查询性能
|
||||
* @param {string} query SQL查询语句
|
||||
* @param {number} executionTime 执行时间(毫秒)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function logQueryPerformance(query, executionTime) {
|
||||
try {
|
||||
// 简化查询语句(移除参数值)
|
||||
const simplifiedQuery = query.replace(/('([^']*)'|"([^"]*)"|`([^`]*)`)/g, '?');
|
||||
|
||||
// 记录到性能日志表
|
||||
await sequelize.query(
|
||||
'INSERT INTO query_performance_logs (query, execution_time, timestamp) VALUES (?, ?, NOW())',
|
||||
{
|
||||
replacements: [simplifiedQuery, executionTime],
|
||||
type: QueryTypes.INSERT
|
||||
}
|
||||
).catch(() => {
|
||||
// 如果表不存在,创建表
|
||||
return sequelize.query(
|
||||
`CREATE TABLE IF NOT EXISTS query_performance_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
query TEXT NOT NULL,
|
||||
execution_time FLOAT NOT NULL,
|
||||
timestamp DATETIME NOT NULL,
|
||||
INDEX (timestamp),
|
||||
INDEX (execution_time)
|
||||
)`,
|
||||
{ type: QueryTypes.RAW }
|
||||
).then(() => {
|
||||
// 重新尝试插入
|
||||
return sequelize.query(
|
||||
'INSERT INTO query_performance_logs (query, execution_time, timestamp) VALUES (?, ?, NOW())',
|
||||
{
|
||||
replacements: [simplifiedQuery, executionTime],
|
||||
type: QueryTypes.INSERT
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('记录查询性能失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别慢查询
|
||||
* @param {number} threshold 慢查询阈值(毫秒),默认为500ms
|
||||
* @returns {Promise<Array>} 慢查询列表
|
||||
*/
|
||||
async function identifySlowQueries(threshold = 500) {
|
||||
try {
|
||||
// 查询性能日志表中的慢查询
|
||||
const slowQueries = await sequelize.query(
|
||||
'SELECT query, AVG(execution_time) as avg_time, COUNT(*) as count, MAX(timestamp) as last_seen ' +
|
||||
'FROM query_performance_logs ' +
|
||||
'WHERE execution_time > ? ' +
|
||||
'GROUP BY query ' +
|
||||
'ORDER BY avg_time DESC',
|
||||
{
|
||||
replacements: [threshold],
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
).catch(() => {
|
||||
// 如果表不存在,返回空数组
|
||||
return [];
|
||||
});
|
||||
|
||||
return slowQueries;
|
||||
} catch (error) {
|
||||
console.error('识别慢查询失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析和优化表
|
||||
* @param {string} tableName 表名
|
||||
* @returns {Promise<Object>} 优化结果
|
||||
*/
|
||||
async function analyzeAndOptimizeTable(tableName) {
|
||||
try {
|
||||
// 分析表
|
||||
await sequelize.query(`ANALYZE TABLE ${tableName}`, { type: QueryTypes.RAW });
|
||||
|
||||
// 优化表
|
||||
const optimizeResult = await sequelize.query(`OPTIMIZE TABLE ${tableName}`, { type: QueryTypes.RAW });
|
||||
|
||||
return optimizeResult[0];
|
||||
} catch (error) {
|
||||
console.error(`分析和优化表 ${tableName} 失败:`, error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表的索引信息
|
||||
* @param {string} tableName 表名
|
||||
* @returns {Promise<Array>} 索引信息
|
||||
*/
|
||||
async function getIndexInfo(tableName) {
|
||||
try {
|
||||
const indexInfo = await sequelize.query(
|
||||
'SHOW INDEX FROM ??',
|
||||
{
|
||||
replacements: [tableName],
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
return indexInfo;
|
||||
} catch (error) {
|
||||
console.error(`获取表 ${tableName} 的索引信息失败:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表信息
|
||||
* @param {string} tableName 表名
|
||||
* @returns {Promise<Object>} 表信息
|
||||
*/
|
||||
async function getTableInfo(tableName) {
|
||||
try {
|
||||
// 获取表状态
|
||||
const tableStatus = await sequelize.query(
|
||||
'SHOW TABLE STATUS LIKE ?',
|
||||
{
|
||||
replacements: [tableName],
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
// 获取表结构
|
||||
const tableStructure = await sequelize.query(
|
||||
'DESCRIBE ??',
|
||||
{
|
||||
replacements: [tableName],
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
status: tableStatus[0] || {},
|
||||
structure: tableStructure
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`获取表 ${tableName} 信息失败:`, error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解释查询计划
|
||||
* @param {Object} query Sequelize查询对象
|
||||
* @returns {Promise<Array>} 查询计划
|
||||
*/
|
||||
async function explainQuery(query) {
|
||||
try {
|
||||
// 获取SQL语句
|
||||
const sql = query.getQueryString();
|
||||
|
||||
// 执行EXPLAIN
|
||||
const explainResult = await sequelize.query(
|
||||
`EXPLAIN ${sql}`,
|
||||
{
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
return explainResult;
|
||||
} catch (error) {
|
||||
console.error('解释查询计划失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库状态
|
||||
* @returns {Promise<Object>} 数据库状态
|
||||
*/
|
||||
async function getDatabaseStatus() {
|
||||
try {
|
||||
// 获取全局状态
|
||||
const globalStatus = await sequelize.query(
|
||||
'SHOW GLOBAL STATUS',
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
// 转换为对象格式
|
||||
const status = {};
|
||||
globalStatus.forEach(item => {
|
||||
if (item.Variable_name && item.Value) {
|
||||
status[item.Variable_name] = item.Value;
|
||||
}
|
||||
});
|
||||
|
||||
// 提取关键指标
|
||||
return {
|
||||
connections: {
|
||||
max_used: status.Max_used_connections,
|
||||
current: status.Threads_connected,
|
||||
running: status.Threads_running,
|
||||
created: status.Threads_created,
|
||||
cached: status.Threads_cached
|
||||
},
|
||||
queries: {
|
||||
total: status.Questions,
|
||||
slow: status.Slow_queries,
|
||||
qps: status.Queries
|
||||
},
|
||||
buffer_pool: {
|
||||
size: status.Innodb_buffer_pool_pages_total,
|
||||
free: status.Innodb_buffer_pool_pages_free,
|
||||
dirty: status.Innodb_buffer_pool_pages_dirty,
|
||||
reads: status.Innodb_buffer_pool_reads,
|
||||
read_requests: status.Innodb_buffer_pool_read_requests,
|
||||
hit_rate: status.Innodb_buffer_pool_read_requests && status.Innodb_buffer_pool_reads
|
||||
? (1 - parseInt(status.Innodb_buffer_pool_reads) / parseInt(status.Innodb_buffer_pool_read_requests)) * 100
|
||||
: 0
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取数据库状态失败:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logQueryPerformance,
|
||||
identifySlowQueries,
|
||||
analyzeAndOptimizeTable,
|
||||
getIndexInfo,
|
||||
getTableInfo,
|
||||
explainQuery,
|
||||
getDatabaseStatus
|
||||
};
|
||||
320
backend/config/swagger.js
Normal file
320
backend/config/swagger.js
Normal file
@@ -0,0 +1,320 @@
|
||||
const swaggerJsdoc = require('swagger-jsdoc');
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: '宁夏智慧养殖监管平台 API',
|
||||
version: '1.0.0',
|
||||
description: '宁夏智慧养殖监管平台后端 API 文档',
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: 'http://localhost:5350',
|
||||
description: '开发服务器',
|
||||
},
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
}
|
||||
},
|
||||
schemas: {
|
||||
MapGeocode: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
address: {
|
||||
type: 'string',
|
||||
description: '地址'
|
||||
},
|
||||
result: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
location: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
lng: {
|
||||
type: 'number',
|
||||
description: '经度'
|
||||
},
|
||||
lat: {
|
||||
type: 'number',
|
||||
description: '纬度'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MapReverseGeocode: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
lat: {
|
||||
type: 'number',
|
||||
description: '纬度'
|
||||
},
|
||||
lng: {
|
||||
type: 'number',
|
||||
description: '经度'
|
||||
},
|
||||
result: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
formatted_address: {
|
||||
type: 'string',
|
||||
description: '结构化地址'
|
||||
},
|
||||
addressComponent: {
|
||||
type: 'object',
|
||||
description: '地址组成部分'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MapDirection: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
origin: {
|
||||
type: 'string',
|
||||
description: '起点坐标,格式:纬度,经度'
|
||||
},
|
||||
destination: {
|
||||
type: 'string',
|
||||
description: '终点坐标,格式:纬度,经度'
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
enum: ['driving', 'walking', 'riding', 'transit'],
|
||||
description: '交通方式'
|
||||
}
|
||||
}
|
||||
},
|
||||
Farm: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: '养殖场ID'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '养殖场名称'
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: '养殖场类型'
|
||||
},
|
||||
location: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
latitude: {
|
||||
type: 'number',
|
||||
format: 'float',
|
||||
description: '纬度'
|
||||
},
|
||||
longitude: {
|
||||
type: 'number',
|
||||
format: 'float',
|
||||
description: '经度'
|
||||
}
|
||||
},
|
||||
description: '地理位置'
|
||||
},
|
||||
address: {
|
||||
type: 'string',
|
||||
description: '详细地址'
|
||||
},
|
||||
contact: {
|
||||
type: 'string',
|
||||
description: '联系人'
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
description: '联系电话'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive', 'maintenance'],
|
||||
description: '养殖场状态'
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间'
|
||||
}
|
||||
}
|
||||
},
|
||||
Animal: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: '动物ID'
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: '动物类型'
|
||||
},
|
||||
count: {
|
||||
type: 'integer',
|
||||
description: '数量'
|
||||
},
|
||||
farmId: {
|
||||
type: 'integer',
|
||||
description: '所属养殖场ID'
|
||||
},
|
||||
health_status: {
|
||||
type: 'string',
|
||||
enum: ['healthy', 'sick', 'quarantined'],
|
||||
description: '健康状态'
|
||||
},
|
||||
last_check_time: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '上次检查时间'
|
||||
},
|
||||
notes: {
|
||||
type: 'string',
|
||||
description: '备注'
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间'
|
||||
}
|
||||
}
|
||||
},
|
||||
Device: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: '设备ID'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '设备名称'
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: '设备类型'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['online', 'offline', 'maintenance'],
|
||||
description: '设备状态'
|
||||
},
|
||||
farmId: {
|
||||
type: 'integer',
|
||||
description: '所属养殖场ID'
|
||||
},
|
||||
last_maintenance: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '上次维护时间'
|
||||
},
|
||||
installation_date: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '安装日期'
|
||||
},
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: '设备指标'
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间'
|
||||
}
|
||||
}
|
||||
},
|
||||
Alert: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: '预警ID'
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: '预警类型'
|
||||
},
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['low', 'medium', 'high', 'critical'],
|
||||
description: '预警级别'
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: '预警消息'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'acknowledged', 'resolved'],
|
||||
description: '预警状态'
|
||||
},
|
||||
farmId: {
|
||||
type: 'integer',
|
||||
description: '所属养殖场ID'
|
||||
},
|
||||
deviceId: {
|
||||
type: 'integer',
|
||||
description: '关联设备ID'
|
||||
},
|
||||
resolved_at: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '解决时间'
|
||||
},
|
||||
resolved_by: {
|
||||
type: 'integer',
|
||||
description: '解决人ID'
|
||||
},
|
||||
resolution_notes: {
|
||||
type: 'string',
|
||||
description: '解决备注'
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
security: [{
|
||||
bearerAuth: []
|
||||
}]
|
||||
},
|
||||
apis: ['./routes/*.js'], // 指定包含 API 注释的文件路径
|
||||
};
|
||||
|
||||
const specs = swaggerJsdoc(options);
|
||||
module.exports = specs;
|
||||
Reference in New Issue
Block a user