Files
nxxmdata/backend/utils/queryOptimizer.js
2025-09-17 19:01:52 +08:00

415 lines
10 KiB
JavaScript

/**
* 数据库查询优化工具
* 提供统一的查询构建和优化方法
*/
const { Op } = require('sequelize');
/**
* 查询构建器类
*/
class QueryBuilder {
constructor() {
this.whereConditions = {};
this.orderConditions = [];
this.includeConditions = [];
this.attributes = null;
this.limit = null;
this.offset = null;
this.groupBy = null;
this.having = null;
}
/**
* 添加搜索条件
* @param {string} field - 字段名
* @param {*} value - 搜索值
* @param {string} operator - 操作符
* @returns {QueryBuilder} 查询构建器实例
*/
search(field, value, operator = 'like') {
if (!value || value.toString().trim() === '') {
return this;
}
switch (operator) {
case 'like':
this.whereConditions[field] = { [Op.like]: `%${value}%` };
break;
case 'eq':
this.whereConditions[field] = value;
break;
case 'ne':
this.whereConditions[field] = { [Op.ne]: value };
break;
case 'gt':
this.whereConditions[field] = { [Op.gt]: value };
break;
case 'gte':
this.whereConditions[field] = { [Op.gte]: value };
break;
case 'lt':
this.whereConditions[field] = { [Op.lt]: value };
break;
case 'lte':
this.whereConditions[field] = { [Op.lte]: value };
break;
case 'in':
this.whereConditions[field] = { [Op.in]: Array.isArray(value) ? value : [value] };
break;
case 'notIn':
this.whereConditions[field] = { [Op.notIn]: Array.isArray(value) ? value : [value] };
break;
case 'between':
if (Array.isArray(value) && value.length === 2) {
this.whereConditions[field] = { [Op.between]: value };
}
break;
case 'isNull':
this.whereConditions[field] = { [Op.is]: null };
break;
case 'notNull':
this.whereConditions[field] = { [Op.not]: null };
break;
}
return this;
}
/**
* 添加多字段搜索条件
* @param {Array} fields - 字段数组
* @param {*} value - 搜索值
* @param {string} operator - 操作符
* @returns {QueryBuilder} 查询构建器实例
*/
searchMultiple(fields, value, operator = 'like') {
if (!value || value.toString().trim() === '') {
return this;
}
const searchConditions = fields.map(field => {
switch (operator) {
case 'like':
return { [field]: { [Op.like]: `%${value}%` } };
case 'eq':
return { [field]: value };
default:
return { [field]: { [Op.like]: `%${value}%` } };
}
});
if (this.whereConditions[Op.or]) {
this.whereConditions[Op.or] = [...this.whereConditions[Op.or], ...searchConditions];
} else {
this.whereConditions[Op.or] = searchConditions;
}
return this;
}
/**
* 添加日期范围条件
* @param {string} field - 日期字段名
* @param {string} startDate - 开始日期
* @param {string} endDate - 结束日期
* @returns {QueryBuilder} 查询构建器实例
*/
dateRange(field, startDate, endDate) {
if (startDate || endDate) {
const dateCondition = {};
if (startDate) {
dateCondition[Op.gte] = new Date(startDate);
}
if (endDate) {
dateCondition[Op.lte] = new Date(endDate);
}
this.whereConditions[field] = dateCondition;
}
return this;
}
/**
* 添加状态条件
* @param {string} field - 状态字段名
* @param {*} status - 状态值
* @returns {QueryBuilder} 查询构建器实例
*/
status(field, status) {
if (status !== null && status !== undefined && status !== '') {
this.whereConditions[field] = status;
}
return this;
}
/**
* 添加排序条件
* @param {string} field - 排序字段
* @param {string} direction - 排序方向 ('ASC' 或 'DESC')
* @returns {QueryBuilder} 查询构建器实例
*/
orderBy(field, direction = 'DESC') {
this.orderConditions.push([field, direction.toUpperCase()]);
return this;
}
/**
* 添加分页条件
* @param {number} page - 页码
* @param {number} limit - 每页数量
* @returns {QueryBuilder} 查询构建器实例
*/
paginate(page = 1, limit = 10) {
this.limit = parseInt(limit);
this.offset = (parseInt(page) - 1) * this.limit;
return this;
}
/**
* 添加关联查询条件
* @param {Object} include - 关联查询配置
* @returns {QueryBuilder} 查询构建器实例
*/
include(include) {
this.includeConditions.push(include);
return this;
}
/**
* 设置查询字段
* @param {Array|Object} attributes - 查询字段
* @returns {QueryBuilder} 查询构建器实例
*/
select(attributes) {
this.attributes = attributes;
return this;
}
/**
* 添加分组条件
* @param {string|Array} groupBy - 分组字段
* @returns {QueryBuilder} 查询构建器实例
*/
group(groupBy) {
this.groupBy = groupBy;
return this;
}
/**
* 添加HAVING条件
* @param {Object} having - HAVING条件
* @returns {QueryBuilder} 查询构建器实例
*/
having(having) {
this.having = having;
return this;
}
/**
* 构建查询选项
* @returns {Object} Sequelize查询选项
*/
build() {
const options = {
where: this.whereConditions,
order: this.orderConditions.length > 0 ? this.orderConditions : undefined,
include: this.includeConditions.length > 0 ? this.includeConditions : undefined,
attributes: this.attributes,
limit: this.limit,
offset: this.offset,
group: this.groupBy,
having: this.having
};
// 移除undefined值
Object.keys(options).forEach(key => {
if (options[key] === undefined) {
delete options[key];
}
});
return options;
}
/**
* 重置查询构建器
* @returns {QueryBuilder} 查询构建器实例
*/
reset() {
this.whereConditions = {};
this.orderConditions = [];
this.includeConditions = [];
this.attributes = null;
this.limit = null;
this.offset = null;
this.groupBy = null;
this.having = null;
return this;
}
}
/**
* 创建查询构建器实例
* @returns {QueryBuilder} 查询构建器实例
*/
const createQueryBuilder = () => {
return new QueryBuilder();
};
/**
* 构建分页查询
* @param {Object} Model - Sequelize模型
* @param {Object} queryOptions - 查询选项
* @param {Object} pagination - 分页参数
* @returns {Promise<Object>} 分页查询结果
*/
const paginateQuery = async (Model, queryOptions, pagination = {}) => {
const { page = 1, limit = 10 } = pagination;
const offset = (page - 1) * limit;
const options = {
...queryOptions,
limit: parseInt(limit),
offset: parseInt(offset)
};
const { count, rows } = await Model.findAndCountAll(options);
return {
data: rows,
pagination: {
current: parseInt(page),
pageSize: parseInt(limit),
total: count,
totalPages: Math.ceil(count / limit)
}
};
};
/**
* 构建统计查询
* @param {Object} Model - Sequelize模型
* @param {Object} queryOptions - 查询选项
* @param {Array} groupFields - 分组字段
* @returns {Promise<Object>} 统计查询结果
*/
const statsQuery = async (Model, queryOptions, groupFields = []) {
const options = {
...queryOptions,
attributes: [
...groupFields,
[Model.sequelize.fn('COUNT', Model.sequelize.col('*')), 'count']
],
group: groupFields
};
const results = await Model.findAll(options);
return results.map(result => ({
...result.dataValues,
count: parseInt(result.dataValues.count)
}));
};
/**
* 构建搜索查询
* @param {Object} searchParams - 搜索参数
* @param {Object} searchConfig - 搜索配置
* @returns {Object} 搜索查询条件
*/
const buildSearchQuery = (searchParams, searchConfig) => {
const queryBuilder = createQueryBuilder();
// 处理搜索关键词
if (searchParams.search && searchConfig.searchFields) {
queryBuilder.searchMultiple(searchConfig.searchFields, searchParams.search);
}
// 处理状态筛选
if (searchParams.status && searchConfig.statusField) {
queryBuilder.status(searchConfig.statusField, searchParams.status);
}
// 处理日期范围
if (searchParams.dateRange && searchConfig.dateField) {
const { start, end } = searchParams.dateRange;
queryBuilder.dateRange(searchConfig.dateField, start, end);
}
// 处理排序
if (searchParams.sortBy && searchConfig.sortFields) {
const sortField = searchConfig.sortFields[searchParams.sortBy] || searchParams.sortBy;
queryBuilder.orderBy(sortField, searchParams.sortOrder || 'DESC');
}
// 处理分页
if (searchParams.page && searchParams.limit) {
queryBuilder.paginate(searchParams.page, searchParams.limit);
}
return queryBuilder.build();
};
/**
* 常用查询配置
*/
const QUERY_CONFIGS = {
// 智能设备查询配置
smartDevices: {
searchFields: ['deviceId', 'deviceName', 'serialNumber'],
statusField: 'status',
dateField: 'created_at',
sortFields: {
'created_at': 'created_at',
'updated_at': 'updated_at',
'device_name': 'deviceName',
'status': 'status'
}
},
// 养殖场查询配置
farms: {
searchFields: ['name', 'type', 'address'],
statusField: 'status',
dateField: 'created_at',
sortFields: {
'created_at': 'created_at',
'updated_at': 'updated_at',
'name': 'name',
'type': 'type'
}
},
// 预警查询配置
alerts: {
searchFields: ['deviceId', 'alertType', 'description'],
statusField: 'status',
dateField: 'alertTime',
sortFields: {
'alert_time': 'alertTime',
'created_at': 'created_at',
'alert_type': 'alertType',
'level': 'level'
}
}
};
/**
* 获取查询配置
* @param {string} type - 查询类型
* @returns {Object} 查询配置
*/
const getQueryConfig = (type) => {
return QUERY_CONFIGS[type] || {};
};
module.exports = {
QueryBuilder,
createQueryBuilder,
paginateQuery,
statsQuery,
buildSearchQuery,
getQueryConfig,
QUERY_CONFIGS
};