保险前后端,养殖端和保险端小程序
This commit is contained in:
228
backend/utils/apiResponse.js
Normal file
228
backend/utils/apiResponse.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* API响应格式标准化工具
|
||||
* 统一前后端接口返回格式
|
||||
*/
|
||||
|
||||
/**
|
||||
* 创建标准成功响应
|
||||
* @param {*} data - 响应数据
|
||||
* @param {string} message - 响应消息
|
||||
* @param {Object} options - 其他选项
|
||||
* @returns {Object} 标准响应格式
|
||||
*/
|
||||
const createSuccessResponse = (data = null, message = '操作成功', options = {}) => {
|
||||
const response = {
|
||||
success: true,
|
||||
message,
|
||||
data,
|
||||
timestamp: new Date().toISOString(),
|
||||
requestId: options.requestId || generateRequestId()
|
||||
};
|
||||
|
||||
// 添加分页信息
|
||||
if (options.total !== undefined) {
|
||||
response.total = options.total;
|
||||
}
|
||||
if (options.page !== undefined) {
|
||||
response.page = options.page;
|
||||
}
|
||||
if (options.limit !== undefined) {
|
||||
response.limit = options.limit;
|
||||
}
|
||||
|
||||
// 添加元数据
|
||||
if (options.meta) {
|
||||
response.meta = options.meta;
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建标准错误响应
|
||||
* @param {string} message - 错误消息
|
||||
* @param {string} code - 错误代码
|
||||
* @param {*} details - 错误详情
|
||||
* @param {Object} options - 其他选项
|
||||
* @returns {Object} 标准错误响应格式
|
||||
*/
|
||||
const createErrorResponse = (message = '操作失败', code = 'UNKNOWN_ERROR', details = null, options = {}) => {
|
||||
return {
|
||||
success: false,
|
||||
message,
|
||||
code,
|
||||
details,
|
||||
timestamp: new Date().toISOString(),
|
||||
requestId: options.requestId || generateRequestId()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建分页响应
|
||||
* @param {Array} data - 数据列表
|
||||
* @param {number} total - 总记录数
|
||||
* @param {number} page - 当前页码
|
||||
* @param {number} limit - 每页记录数
|
||||
* @param {string} message - 响应消息
|
||||
* @param {Object} options - 其他选项
|
||||
* @returns {Object} 分页响应格式
|
||||
*/
|
||||
const createPaginatedResponse = (data, total, page, limit, message = '获取数据成功', options = {}) => {
|
||||
return createSuccessResponse(data, message, {
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成请求ID
|
||||
* @returns {string} 请求ID
|
||||
*/
|
||||
const generateRequestId = () => {
|
||||
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 错误代码常量
|
||||
*/
|
||||
const ERROR_CODES = {
|
||||
// 认证相关
|
||||
UNAUTHORIZED: 'UNAUTHORIZED',
|
||||
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
|
||||
INVALID_TOKEN: 'INVALID_TOKEN',
|
||||
|
||||
// 权限相关
|
||||
FORBIDDEN: 'FORBIDDEN',
|
||||
INSUFFICIENT_PERMISSIONS: 'INSUFFICIENT_PERMISSIONS',
|
||||
|
||||
// 资源相关
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
RESOURCE_CONFLICT: 'RESOURCE_CONFLICT',
|
||||
RESOURCE_LOCKED: 'RESOURCE_LOCKED',
|
||||
|
||||
// 验证相关
|
||||
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
||||
INVALID_INPUT: 'INVALID_INPUT',
|
||||
MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
|
||||
|
||||
// 业务相关
|
||||
BUSINESS_ERROR: 'BUSINESS_ERROR',
|
||||
OPERATION_FAILED: 'OPERATION_FAILED',
|
||||
DUPLICATE_ENTRY: 'DUPLICATE_ENTRY',
|
||||
|
||||
// 系统相关
|
||||
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
||||
SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
|
||||
DATABASE_ERROR: 'DATABASE_ERROR',
|
||||
NETWORK_ERROR: 'NETWORK_ERROR',
|
||||
|
||||
// 未知错误
|
||||
UNKNOWN_ERROR: 'UNKNOWN_ERROR'
|
||||
};
|
||||
|
||||
/**
|
||||
* 成功消息常量
|
||||
*/
|
||||
const SUCCESS_MESSAGES = {
|
||||
// 通用操作
|
||||
OPERATION_SUCCESS: '操作成功',
|
||||
DATA_SAVED: '数据保存成功',
|
||||
DATA_UPDATED: '数据更新成功',
|
||||
DATA_DELETED: '数据删除成功',
|
||||
DATA_RETRIEVED: '数据获取成功',
|
||||
|
||||
// 认证相关
|
||||
LOGIN_SUCCESS: '登录成功',
|
||||
LOGOUT_SUCCESS: '登出成功',
|
||||
PASSWORD_CHANGED: '密码修改成功',
|
||||
|
||||
// 文件相关
|
||||
FILE_UPLOADED: '文件上传成功',
|
||||
FILE_DELETED: '文件删除成功',
|
||||
|
||||
// 导出相关
|
||||
EXPORT_SUCCESS: '数据导出成功',
|
||||
IMPORT_SUCCESS: '数据导入成功'
|
||||
};
|
||||
|
||||
/**
|
||||
* 统一错误处理中间件
|
||||
* @param {Error} err - 错误对象
|
||||
* @param {Object} req - 请求对象
|
||||
* @param {Object} res - 响应对象
|
||||
* @param {Function} next - 下一个中间件
|
||||
*/
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
console.error('API Error:', {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
body: req.body,
|
||||
query: req.query
|
||||
});
|
||||
|
||||
// 数据库错误
|
||||
if (err.name === 'SequelizeError' || err.name === 'SequelizeValidationError') {
|
||||
return res.status(400).json(createErrorResponse(
|
||||
'数据库操作失败',
|
||||
ERROR_CODES.DATABASE_ERROR,
|
||||
err.message
|
||||
));
|
||||
}
|
||||
|
||||
// 验证错误
|
||||
if (err.name === 'ValidationError') {
|
||||
return res.status(400).json(createErrorResponse(
|
||||
'数据验证失败',
|
||||
ERROR_CODES.VALIDATION_ERROR,
|
||||
err.message
|
||||
));
|
||||
}
|
||||
|
||||
// 认证错误
|
||||
if (err.name === 'UnauthorizedError' || err.status === 401) {
|
||||
return res.status(401).json(createErrorResponse(
|
||||
'认证失败',
|
||||
ERROR_CODES.UNAUTHORIZED,
|
||||
err.message
|
||||
));
|
||||
}
|
||||
|
||||
// 权限错误
|
||||
if (err.status === 403) {
|
||||
return res.status(403).json(createErrorResponse(
|
||||
'权限不足',
|
||||
ERROR_CODES.FORBIDDEN,
|
||||
err.message
|
||||
));
|
||||
}
|
||||
|
||||
// 资源不存在
|
||||
if (err.status === 404) {
|
||||
return res.status(404).json(createErrorResponse(
|
||||
'资源不存在',
|
||||
ERROR_CODES.NOT_FOUND,
|
||||
err.message
|
||||
));
|
||||
}
|
||||
|
||||
// 默认服务器错误
|
||||
res.status(500).json(createErrorResponse(
|
||||
'服务器内部错误',
|
||||
ERROR_CODES.INTERNAL_ERROR,
|
||||
process.env.NODE_ENV === 'development' ? err.message : '服务器繁忙,请稍后重试'
|
||||
));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createSuccessResponse,
|
||||
createErrorResponse,
|
||||
createPaginatedResponse,
|
||||
generateRequestId,
|
||||
ERROR_CODES,
|
||||
SUCCESS_MESSAGES,
|
||||
errorHandler
|
||||
};
|
||||
414
backend/utils/queryOptimizer.js
Normal file
414
backend/utils/queryOptimizer.js
Normal file
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* 数据库查询优化工具
|
||||
* 提供统一的查询构建和优化方法
|
||||
*/
|
||||
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user