Initial commit: 宁夏智慧养殖监管平台
This commit is contained in:
147
backend/models/Alert.js
Normal file
147
backend/models/Alert.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Alert 模型定义
|
||||
* @file Alert.js
|
||||
* @description 定义预警模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
/**
|
||||
* 预警模型
|
||||
* @typedef {Object} Alert
|
||||
* @property {number} id - 预警唯一标识
|
||||
* @property {string} type - 预警类型
|
||||
* @property {string} level - 预警级别
|
||||
* @property {string} message - 预警消息
|
||||
* @property {string} status - 预警状态
|
||||
* @property {number} farmId - 所属养殖场ID
|
||||
* @property {number} deviceId - 关联设备ID
|
||||
* @property {Date} created_at - 创建时间
|
||||
* @property {Date} updated_at - 更新时间
|
||||
*/
|
||||
class Alert extends BaseModel {
|
||||
/**
|
||||
* 获取预警所属的养殖场
|
||||
* @returns {Promise<Object>} 养殖场信息
|
||||
*/
|
||||
async getFarm() {
|
||||
return await this.getFarm();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预警关联的设备
|
||||
* @returns {Promise<Object>} 设备信息
|
||||
*/
|
||||
async getDevice() {
|
||||
return await this.getDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新预警状态
|
||||
* @param {String} status 新状态
|
||||
* @returns {Promise<Boolean>} 更新结果
|
||||
*/
|
||||
async updateStatus(status) {
|
||||
try {
|
||||
this.status = status;
|
||||
|
||||
if (status === 'resolved') {
|
||||
this.resolved_at = new Date();
|
||||
}
|
||||
|
||||
await this.save();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('更新预警状态失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决预警
|
||||
* @param {Number} userId 解决人ID
|
||||
* @param {String} notes 解决说明
|
||||
* @returns {Promise<Boolean>} 解决结果
|
||||
*/
|
||||
async resolve(userId, notes) {
|
||||
try {
|
||||
this.status = 'resolved';
|
||||
this.resolved_at = new Date();
|
||||
this.resolved_by = userId;
|
||||
this.resolution_notes = notes;
|
||||
|
||||
await this.save();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('解决预警失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化Alert模型
|
||||
Alert.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false
|
||||
},
|
||||
level: {
|
||||
type: DataTypes.ENUM('low', 'medium', 'high', 'critical'),
|
||||
defaultValue: 'medium'
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'acknowledged', 'resolved'),
|
||||
defaultValue: 'active'
|
||||
},
|
||||
farm_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
device_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'devices',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
resolved_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
resolved_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
},
|
||||
resolution_notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'alerts',
|
||||
modelName: 'Alert',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出预警模型
|
||||
* @exports Alert
|
||||
*/
|
||||
module.exports = Alert;
|
||||
115
backend/models/Animal.js
Normal file
115
backend/models/Animal.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Animal 模型定义
|
||||
* @file Animal.js
|
||||
* @description 定义动物模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
/**
|
||||
* 动物模型
|
||||
* @typedef {Object} Animal
|
||||
* @property {number} id - 动物唯一标识
|
||||
* @property {string} type - 动物类型
|
||||
* @property {number} count - 数量
|
||||
* @property {number} farmId - 所属养殖场ID
|
||||
* @property {Date} created_at - 创建时间
|
||||
* @property {Date} updated_at - 更新时间
|
||||
*/
|
||||
class Animal extends BaseModel {
|
||||
/**
|
||||
* 获取动物所属的养殖场
|
||||
* @returns {Promise<Object>} 养殖场信息
|
||||
*/
|
||||
async getFarm() {
|
||||
return await this.getFarm();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新动物数量
|
||||
* @param {Number} count 新数量
|
||||
* @returns {Promise<Boolean>} 更新结果
|
||||
*/
|
||||
async updateCount(count) {
|
||||
try {
|
||||
if (count < 0) {
|
||||
throw new Error('数量不能为负数');
|
||||
}
|
||||
|
||||
this.count = count;
|
||||
await this.save();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('更新动物数量失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新健康状态
|
||||
* @param {String} status 新状态
|
||||
* @returns {Promise<Boolean>} 更新结果
|
||||
*/
|
||||
async updateHealthStatus(status) {
|
||||
try {
|
||||
this.health_status = status;
|
||||
await this.save();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('更新健康状态失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化Animal模型
|
||||
Animal.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false
|
||||
},
|
||||
count: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
farm_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
health_status: {
|
||||
type: DataTypes.ENUM('healthy', 'sick', 'quarantine', 'treatment'),
|
||||
defaultValue: 'healthy'
|
||||
},
|
||||
last_inspection: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'animals',
|
||||
modelName: 'Animal',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出动物模型
|
||||
* @exports Animal
|
||||
*/
|
||||
module.exports = Animal;
|
||||
187
backend/models/BaseModel.js
Normal file
187
backend/models/BaseModel.js
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* 数据库模型基类
|
||||
* @file BaseModel.js
|
||||
* @description 提供所有模型的基础功能和通用方法
|
||||
*/
|
||||
const { Model, DataTypes } = require('sequelize');
|
||||
const queryOptimizer = require('../utils/query-optimizer');
|
||||
|
||||
class BaseModel extends Model {
|
||||
/**
|
||||
* 初始化模型
|
||||
* @param {Object} sequelize Sequelize实例
|
||||
* @param {Object} attributes 模型属性
|
||||
* @param {Object} options 模型选项
|
||||
*/
|
||||
static init(attributes, options = {}) {
|
||||
// 确保options中包含sequelize实例
|
||||
if (!options.sequelize) {
|
||||
throw new Error('必须提供sequelize实例');
|
||||
}
|
||||
|
||||
// 默认配置
|
||||
const defaultOptions = {
|
||||
// 使用下划线命名法
|
||||
underscored: true,
|
||||
// 默认时间戳字段
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
// 默认不使用软删除
|
||||
paranoid: false,
|
||||
// 字符集和排序规则
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci'
|
||||
};
|
||||
|
||||
// 合并选项
|
||||
const mergedOptions = { ...defaultOptions, ...options };
|
||||
|
||||
// 调用父类的init方法
|
||||
return super.init(attributes, mergedOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param {Object} options 查询选项
|
||||
* @param {Number} page 页码
|
||||
* @param {Number} pageSize 每页记录数
|
||||
* @returns {Promise<Object>} 分页结果
|
||||
*/
|
||||
static async paginate(options = {}, page = 1, pageSize = 10) {
|
||||
// 确保页码和每页记录数为正整数
|
||||
page = Math.max(1, parseInt(page));
|
||||
pageSize = Math.max(1, parseInt(pageSize));
|
||||
|
||||
// 计算偏移量
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
// 合并分页参数
|
||||
const paginateOptions = {
|
||||
...options,
|
||||
limit: pageSize,
|
||||
offset
|
||||
};
|
||||
|
||||
// 执行查询
|
||||
const { count, rows } = await this.findAndCountAll(paginateOptions);
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = Math.ceil(count / pageSize);
|
||||
|
||||
// 返回分页结果
|
||||
return {
|
||||
data: rows,
|
||||
pagination: {
|
||||
total: count,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages,
|
||||
hasMore: page < totalPages
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建或更新记录
|
||||
* @param {Array} records 记录数组
|
||||
* @param {Array|String} uniqueFields 唯一字段或字段数组
|
||||
* @returns {Promise<Array>} 创建或更新的记录
|
||||
*/
|
||||
static async bulkUpsert(records, uniqueFields) {
|
||||
if (!Array.isArray(records) || records.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 确保uniqueFields是数组
|
||||
const fields = Array.isArray(uniqueFields) ? uniqueFields : [uniqueFields];
|
||||
|
||||
// 获取所有字段
|
||||
const allFields = Object.keys(this.rawAttributes);
|
||||
|
||||
// 批量创建或更新
|
||||
const result = await this.bulkCreate(records, {
|
||||
updateOnDuplicate: allFields.filter(field => {
|
||||
// 排除主键和时间戳字段
|
||||
return (
|
||||
field !== 'id' &&
|
||||
field !== 'created_at' &&
|
||||
field !== 'updated_at' &&
|
||||
field !== 'deleted_at'
|
||||
);
|
||||
}),
|
||||
// 返回创建或更新后的记录
|
||||
returning: true
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行原始SQL查询
|
||||
* @param {String} sql SQL语句
|
||||
* @param {Object} replacements 替换参数
|
||||
* @param {String} type 查询类型
|
||||
* @returns {Promise<Array|Object>} 查询结果
|
||||
*/
|
||||
static async rawQuery(sql, replacements = {}, type = 'SELECT') {
|
||||
const sequelize = this.sequelize;
|
||||
const QueryTypes = sequelize.QueryTypes;
|
||||
|
||||
// 执行查询前分析查询性能
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
try {
|
||||
const explainResult = await queryOptimizer.explainQuery(sql, replacements);
|
||||
console.log('查询分析结果:', explainResult);
|
||||
} catch (error) {
|
||||
console.warn('查询分析失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
return sequelize.query(sql, {
|
||||
replacements,
|
||||
type: QueryTypes[type],
|
||||
model: this,
|
||||
mapToModel: type === 'SELECT'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型的表信息
|
||||
* @returns {Promise<Object>} 表信息
|
||||
*/
|
||||
static async getTableInfo() {
|
||||
const tableName = this.getTableName();
|
||||
return queryOptimizer.getTableInfo(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型的索引信息
|
||||
* @returns {Promise<Array>} 索引信息
|
||||
*/
|
||||
static async getIndexes() {
|
||||
const tableName = this.getTableName();
|
||||
return queryOptimizer.getTableIndexes(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析表结构
|
||||
* @returns {Promise<Object>} 分析结果
|
||||
*/
|
||||
static async analyzeTable() {
|
||||
const tableName = this.getTableName();
|
||||
return queryOptimizer.analyzeTable(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化表
|
||||
* @returns {Promise<Object>} 优化结果
|
||||
*/
|
||||
static async optimizeTable() {
|
||||
const tableName = this.getTableName();
|
||||
return queryOptimizer.optimizeTable(tableName);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseModel;
|
||||
123
backend/models/Device.js
Normal file
123
backend/models/Device.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Device 模型定义
|
||||
* @file Device.js
|
||||
* @description 定义设备模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
/**
|
||||
* 设备模型
|
||||
* @typedef {Object} Device
|
||||
* @property {number} id - 设备唯一标识
|
||||
* @property {string} type - 设备类型
|
||||
* @property {string} status - 设备状态
|
||||
* @property {number} farmId - 所属养殖场ID
|
||||
* @property {Date} created_at - 创建时间
|
||||
* @property {Date} updated_at - 更新时间
|
||||
*/
|
||||
class Device extends BaseModel {
|
||||
/**
|
||||
* 获取设备所属的养殖场
|
||||
* @returns {Promise<Object>} 养殖场信息
|
||||
*/
|
||||
async getFarm() {
|
||||
return await this.getFarm();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备的所有预警
|
||||
* @returns {Promise<Array>} 预警列表
|
||||
*/
|
||||
async getAlerts() {
|
||||
return await this.getAlerts();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备状态
|
||||
* @param {String} status 新状态
|
||||
* @returns {Promise<Boolean>} 更新结果
|
||||
*/
|
||||
async updateStatus(status) {
|
||||
try {
|
||||
this.status = status;
|
||||
await this.save();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('更新设备状态失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录设备维护
|
||||
* @returns {Promise<Boolean>} 记录结果
|
||||
*/
|
||||
async recordMaintenance() {
|
||||
try {
|
||||
this.last_maintenance = new Date();
|
||||
this.status = 'online';
|
||||
await this.save();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('记录设备维护失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化Device模型
|
||||
Device.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('online', 'offline', 'maintenance'),
|
||||
defaultValue: 'offline'
|
||||
},
|
||||
farm_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
last_maintenance: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
installation_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
},
|
||||
metrics: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
defaultValue: {}
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'devices',
|
||||
modelName: 'Device',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出设备模型
|
||||
* @exports Device
|
||||
*/
|
||||
module.exports = Device;
|
||||
97
backend/models/Farm.js
Normal file
97
backend/models/Farm.js
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Farm 模型定义
|
||||
* @file Farm.js
|
||||
* @description 定义养殖场模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
/**
|
||||
* 养殖场模型
|
||||
* @typedef {Object} Farm
|
||||
* @property {number} id - 养殖场唯一标识
|
||||
* @property {string} name - 养殖场名称
|
||||
* @property {string} type - 养殖场类型
|
||||
* @property {Object} location - 地理位置
|
||||
* @property {number} location.lat - 纬度
|
||||
* @property {number} location.lng - 经度
|
||||
* @property {Date} created_at - 创建时间
|
||||
* @property {Date} updated_at - 更新时间
|
||||
*/
|
||||
class Farm extends BaseModel {
|
||||
/**
|
||||
* 获取养殖场的所有动物
|
||||
* @returns {Promise<Array>} 动物列表
|
||||
*/
|
||||
async getAnimals() {
|
||||
return await this.getAnimals();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取养殖场的所有设备
|
||||
* @returns {Promise<Array>} 设备列表
|
||||
*/
|
||||
async getDevices() {
|
||||
return await this.getDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取养殖场的所有预警
|
||||
* @returns {Promise<Array>} 预警列表
|
||||
*/
|
||||
async getAlerts() {
|
||||
return await this.getAlerts();
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化Farm模型
|
||||
Farm.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false
|
||||
},
|
||||
location: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: false,
|
||||
defaultValue: {}
|
||||
},
|
||||
address: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true
|
||||
},
|
||||
contact: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive', 'maintenance'),
|
||||
defaultValue: 'active'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'farms',
|
||||
modelName: 'Farm',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出养殖场模型
|
||||
* @exports Farm
|
||||
*/
|
||||
module.exports = Farm;
|
||||
146
backend/models/Order.js
Normal file
146
backend/models/Order.js
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Order 模型定义
|
||||
* @file Order.js
|
||||
* @description 定义订单模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
/**
|
||||
* 订单模型
|
||||
* @typedef {Object} Order
|
||||
* @property {number} id - 订单唯一标识
|
||||
* @property {number} user_id - 用户ID
|
||||
* @property {number} total_amount - 订单总金额(单位:分)
|
||||
* @property {string} status - 订单状态
|
||||
* @property {string} payment_status - 支付状态
|
||||
* @property {string} shipping_address - 收货地址
|
||||
* @property {Date} created_at - 创建时间
|
||||
* @property {Date} updated_at - 更新时间
|
||||
*/
|
||||
class Order extends BaseModel {
|
||||
/**
|
||||
* 获取用户的所有订单
|
||||
* @param {Number} userId 用户ID
|
||||
* @param {Object} options 查询选项
|
||||
* @returns {Promise<Array>} 订单列表
|
||||
*/
|
||||
static async getUserOrders(userId, options = {}) {
|
||||
return await this.findAll({
|
||||
where: { user_id: userId },
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情,包括订单项
|
||||
* @returns {Promise<Object>} 订单详情
|
||||
*/
|
||||
async getOrderDetails() {
|
||||
return await Order.findByPk(this.id, {
|
||||
include: [{ model: sequelize.models.OrderItem }]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算订单总金额
|
||||
* @returns {Promise<Number>} 订单总金额
|
||||
*/
|
||||
async calculateTotal() {
|
||||
const orderItems = await this.getOrderItems();
|
||||
let total = 0;
|
||||
|
||||
for (const item of orderItems) {
|
||||
total += item.price * item.quantity;
|
||||
}
|
||||
|
||||
this.total_amount = total;
|
||||
await this.save();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新订单状态
|
||||
* @param {String} status 新状态
|
||||
* @returns {Promise<Boolean>} 更新结果
|
||||
*/
|
||||
async updateStatus(status) {
|
||||
try {
|
||||
this.status = status;
|
||||
await this.save();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('更新订单状态失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新支付状态
|
||||
* @param {String} paymentStatus 新支付状态
|
||||
* @returns {Promise<Boolean>} 更新结果
|
||||
*/
|
||||
async updatePaymentStatus(paymentStatus) {
|
||||
try {
|
||||
this.payment_status = paymentStatus;
|
||||
await this.save();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('更新支付状态失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化Order模型
|
||||
Order.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
total_amount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '单位:分'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending'
|
||||
},
|
||||
payment_status: {
|
||||
type: DataTypes.ENUM('unpaid', 'paid', 'refunded'),
|
||||
allowNull: false,
|
||||
defaultValue: 'unpaid'
|
||||
},
|
||||
shipping_address: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'orders',
|
||||
modelName: 'Order',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出订单模型
|
||||
* @exports Order
|
||||
*/
|
||||
module.exports = Order;
|
||||
140
backend/models/OrderItem.js
Normal file
140
backend/models/OrderItem.js
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* OrderItem 模型定义
|
||||
* @file OrderItem.js
|
||||
* @description 定义订单项模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
/**
|
||||
* 订单项模型
|
||||
* @typedef {Object} OrderItem
|
||||
* @property {number} id - 订单项唯一标识
|
||||
* @property {number} order_id - 订单ID
|
||||
* @property {number} product_id - 产品ID
|
||||
* @property {number} quantity - 数量
|
||||
* @property {number} price - 单价(单位:分)
|
||||
* @property {Date} created_at - 创建时间
|
||||
*/
|
||||
class OrderItem extends BaseModel {
|
||||
/**
|
||||
* 获取订单的所有订单项
|
||||
* @param {Number} orderId 订单ID
|
||||
* @returns {Promise<Array>} 订单项列表
|
||||
*/
|
||||
static async getOrderItems(orderId) {
|
||||
return await this.findAll({
|
||||
where: { order_id: orderId },
|
||||
include: [{ model: sequelize.models.Product }]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算订单项总金额
|
||||
* @returns {Number} 总金额
|
||||
*/
|
||||
getTotalPrice() {
|
||||
return this.price * this.quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新订单项数量
|
||||
* @param {Number} quantity 新数量
|
||||
* @returns {Promise<Boolean>} 更新结果
|
||||
*/
|
||||
async updateQuantity(quantity) {
|
||||
try {
|
||||
if (quantity <= 0) {
|
||||
throw new Error('数量必须大于0');
|
||||
}
|
||||
|
||||
// 检查产品库存
|
||||
const product = await sequelize.models.Product.findByPk(this.product_id);
|
||||
|
||||
if (!product) {
|
||||
throw new Error('产品不存在');
|
||||
}
|
||||
|
||||
const quantityDiff = quantity - this.quantity;
|
||||
|
||||
if (quantityDiff > 0 && !product.hasEnoughStock(quantityDiff)) {
|
||||
throw new Error('产品库存不足');
|
||||
}
|
||||
|
||||
// 使用事务确保数据一致性
|
||||
const result = await sequelize.transaction(async (t) => {
|
||||
// 更新订单项数量
|
||||
this.quantity = quantity;
|
||||
await this.save({ transaction: t });
|
||||
|
||||
// 更新产品库存
|
||||
await product.updateStock(-quantityDiff, { transaction: t });
|
||||
|
||||
// 更新订单总金额
|
||||
const order = await sequelize.models.Order.findByPk(this.order_id, { transaction: t });
|
||||
await order.calculateTotal({ transaction: t });
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('更新订单项数量失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化OrderItem模型
|
||||
OrderItem.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
order_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'orders',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
product_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'products',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'RESTRICT'
|
||||
},
|
||||
quantity: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
validate: {
|
||||
min: 1
|
||||
}
|
||||
},
|
||||
price: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '单位:分'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'order_items',
|
||||
modelName: 'OrderItem',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: false
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出订单项模型
|
||||
* @exports OrderItem
|
||||
*/
|
||||
module.exports = OrderItem;
|
||||
127
backend/models/Product.js
Normal file
127
backend/models/Product.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Product 模型定义
|
||||
* @file Product.js
|
||||
* @description 定义产品模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
/**
|
||||
* 产品模型
|
||||
* @typedef {Object} Product
|
||||
* @property {number} id - 产品唯一标识
|
||||
* @property {string} name - 产品名称
|
||||
* @property {string} description - 产品描述
|
||||
* @property {number} price - 产品价格(单位:分)
|
||||
* @property {number} stock - 库存数量
|
||||
* @property {string} image_url - 产品图片URL
|
||||
* @property {boolean} is_active - 是否激活
|
||||
* @property {Date} created_at - 创建时间
|
||||
* @property {Date} updated_at - 更新时间
|
||||
*/
|
||||
class Product extends BaseModel {
|
||||
/**
|
||||
* 获取激活的产品列表
|
||||
* @param {Object} options 查询选项
|
||||
* @returns {Promise<Array>} 产品列表
|
||||
*/
|
||||
static async getActiveProducts(options = {}) {
|
||||
return await this.findAll({
|
||||
where: { is_active: true },
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新产品库存
|
||||
* @param {Number} quantity 变更数量,正数增加库存,负数减少库存
|
||||
* @returns {Promise<Boolean>} 更新结果
|
||||
*/
|
||||
async updateStock(quantity) {
|
||||
try {
|
||||
// 使用事务和乐观锁确保库存操作的原子性
|
||||
const result = await sequelize.transaction(async (t) => {
|
||||
const product = await Product.findByPk(this.id, { transaction: t, lock: true });
|
||||
|
||||
if (!product) {
|
||||
throw new Error('产品不存在');
|
||||
}
|
||||
|
||||
const newStock = product.stock + quantity;
|
||||
|
||||
if (newStock < 0) {
|
||||
throw new Error('库存不足');
|
||||
}
|
||||
|
||||
product.stock = newStock;
|
||||
await product.save({ transaction: t });
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('更新库存失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查产品库存是否充足
|
||||
* @param {Number} quantity 需要的数量
|
||||
* @returns {Boolean} 是否充足
|
||||
*/
|
||||
hasEnoughStock(quantity) {
|
||||
return this.stock >= quantity;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化Product模型
|
||||
Product.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
price: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '单位:分'
|
||||
},
|
||||
stock: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
image_url: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true
|
||||
},
|
||||
is_active: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'products',
|
||||
modelName: 'Product',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出产品模型
|
||||
* @exports Product
|
||||
*/
|
||||
module.exports = Product;
|
||||
105
backend/models/Role.js
Normal file
105
backend/models/Role.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Role 模型定义
|
||||
* @file Role.js
|
||||
* @description 定义角色模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
/**
|
||||
* 角色模型
|
||||
* @typedef {Object} Role
|
||||
* @property {number} id - 角色唯一标识
|
||||
* @property {string} name - 角色名称,唯一
|
||||
* @property {string} description - 角色描述
|
||||
* @property {Date} created_at - 创建时间
|
||||
*/
|
||||
class Role extends BaseModel {
|
||||
/**
|
||||
* 获取具有此角色的所有用户
|
||||
* @returns {Promise<Array>} 用户列表
|
||||
*/
|
||||
async getUsers() {
|
||||
return await this.getUsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查角色是否已分配给指定用户
|
||||
* @param {Number} userId 用户ID
|
||||
* @returns {Promise<Boolean>} 检查结果
|
||||
*/
|
||||
async isAssignedToUser(userId) {
|
||||
const users = await this.getUsers({ where: { id: userId } });
|
||||
return users.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为角色分配用户
|
||||
* @param {Number|Array} userId 用户ID或用户ID数组
|
||||
* @returns {Promise<Boolean>} 分配结果
|
||||
*/
|
||||
async assignToUser(userId) {
|
||||
try {
|
||||
if (Array.isArray(userId)) {
|
||||
await this.addUsers(userId);
|
||||
} else {
|
||||
await this.addUser(userId);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('分配用户失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从用户中移除此角色
|
||||
* @param {Number|Array} userId 用户ID或用户ID数组
|
||||
* @returns {Promise<Boolean>} 移除结果
|
||||
*/
|
||||
async removeFromUser(userId) {
|
||||
try {
|
||||
if (Array.isArray(userId)) {
|
||||
await this.removeUsers(userId);
|
||||
} else {
|
||||
await this.removeUser(userId);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('移除用户失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化Role模型
|
||||
Role.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'roles',
|
||||
modelName: 'Role',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: false
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出角色模型
|
||||
* @exports Role
|
||||
*/
|
||||
module.exports = Role;
|
||||
214
backend/models/SensorData.js
Normal file
214
backend/models/SensorData.js
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* 传感器数据模型
|
||||
* @file SensorData.js
|
||||
* @description 环境监控数据表模型,存储温度、湿度等传感器数据
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
|
||||
class SensorData extends BaseModel {
|
||||
static init(sequelize) {
|
||||
return super.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '传感器数据ID'
|
||||
},
|
||||
device_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '设备ID',
|
||||
references: {
|
||||
model: 'devices',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
farm_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '养殖场ID',
|
||||
references: {
|
||||
model: 'farms',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
sensor_type: {
|
||||
type: DataTypes.ENUM('temperature', 'humidity', 'ph', 'oxygen', 'ammonia', 'light'),
|
||||
allowNull: false,
|
||||
comment: '传感器类型:temperature-温度, humidity-湿度, ph-酸碱度, oxygen-氧气, ammonia-氨气, light-光照'
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
comment: '传感器数值'
|
||||
},
|
||||
unit: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '数值单位(如:°C, %, ppm等)'
|
||||
},
|
||||
location: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '传感器位置描述'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('normal', 'warning', 'error'),
|
||||
defaultValue: 'normal',
|
||||
comment: '数据状态:normal-正常, warning-警告, error-异常'
|
||||
},
|
||||
recorded_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '数据记录时间'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'SensorData',
|
||||
tableName: 'sensor_data',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
indexes: [
|
||||
{
|
||||
fields: ['device_id']
|
||||
},
|
||||
{
|
||||
fields: ['farm_id']
|
||||
},
|
||||
{
|
||||
fields: ['sensor_type']
|
||||
},
|
||||
{
|
||||
fields: ['recorded_at']
|
||||
},
|
||||
{
|
||||
fields: ['farm_id', 'sensor_type', 'recorded_at']
|
||||
}
|
||||
],
|
||||
comment: '传感器环境监控数据表'
|
||||
});
|
||||
}
|
||||
|
||||
static associate(models) {
|
||||
// 传感器数据属于某个设备
|
||||
this.belongsTo(models.Device, {
|
||||
foreignKey: 'device_id',
|
||||
as: 'device'
|
||||
});
|
||||
|
||||
// 传感器数据属于某个养殖场
|
||||
this.belongsTo(models.Farm, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'farm'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定养殖场的最新传感器数据
|
||||
* @param {number} farmId - 养殖场ID
|
||||
* @param {string} sensorType - 传感器类型
|
||||
* @param {number} limit - 数据条数限制
|
||||
* @returns {Promise<Array>} 传感器数据列表
|
||||
*/
|
||||
static async getLatestData(farmId, sensorType, limit = 24) {
|
||||
try {
|
||||
const whereClause = {
|
||||
farm_id: farmId
|
||||
};
|
||||
|
||||
if (sensorType) {
|
||||
whereClause.sensor_type = sensorType;
|
||||
}
|
||||
|
||||
return await this.findAll({
|
||||
where: whereClause,
|
||||
order: [['recorded_at', 'DESC']],
|
||||
limit: limit,
|
||||
include: [{
|
||||
model: this.sequelize.models.Device,
|
||||
as: 'device',
|
||||
attributes: ['id', 'name', 'type']
|
||||
}]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取最新传感器数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定时间范围内的传感器数据
|
||||
* @param {number} farmId - 养殖场ID
|
||||
* @param {string} sensorType - 传感器类型
|
||||
* @param {Date} startTime - 开始时间
|
||||
* @param {Date} endTime - 结束时间
|
||||
* @returns {Promise<Array>} 传感器数据列表
|
||||
*/
|
||||
static async getDataByTimeRange(farmId, sensorType, startTime, endTime) {
|
||||
try {
|
||||
return await this.findAll({
|
||||
where: {
|
||||
farm_id: farmId,
|
||||
sensor_type: sensorType,
|
||||
recorded_at: {
|
||||
[this.sequelize.Sequelize.Op.between]: [startTime, endTime]
|
||||
}
|
||||
},
|
||||
order: [['recorded_at', 'ASC']],
|
||||
include: [{
|
||||
model: this.sequelize.models.Device,
|
||||
as: 'device',
|
||||
attributes: ['id', 'name', 'type']
|
||||
}]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取时间范围内传感器数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取传感器数据统计信息
|
||||
* @param {number} farmId - 养殖场ID
|
||||
* @param {string} sensorType - 传感器类型
|
||||
* @param {Date} startTime - 开始时间
|
||||
* @param {Date} endTime - 结束时间
|
||||
* @returns {Promise<Object>} 统计信息
|
||||
*/
|
||||
static async getDataStats(farmId, sensorType, startTime, endTime) {
|
||||
const { Op, fn, col } = require('sequelize');
|
||||
|
||||
const stats = await this.findOne({
|
||||
where: {
|
||||
farm_id: farmId,
|
||||
sensor_type: sensorType,
|
||||
recorded_at: {
|
||||
[Op.between]: [startTime, endTime]
|
||||
}
|
||||
},
|
||||
attributes: [
|
||||
[fn('AVG', col('value')), 'avgValue'],
|
||||
[fn('MIN', col('value')), 'minValue'],
|
||||
[fn('MAX', col('value')), 'maxValue'],
|
||||
[fn('COUNT', col('id')), 'dataCount']
|
||||
],
|
||||
raw: true
|
||||
});
|
||||
|
||||
return {
|
||||
average: parseFloat(stats.avgValue || 0).toFixed(2),
|
||||
minimum: parseFloat(stats.minValue || 0).toFixed(2),
|
||||
maximum: parseFloat(stats.maxValue || 0).toFixed(2),
|
||||
count: parseInt(stats.dataCount || 0)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化模型
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
SensorData.init(sequelize);
|
||||
|
||||
module.exports = SensorData;
|
||||
147
backend/models/User.js
Normal file
147
backend/models/User.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* User 模型定义
|
||||
* @file User.js
|
||||
* @description 定义用户模型,用于数据库操作
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const bcrypt = require('bcrypt');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-pool');
|
||||
|
||||
class User extends BaseModel {
|
||||
/**
|
||||
* 验证密码
|
||||
* @param {String} password 待验证的密码
|
||||
* @returns {Promise<Boolean>} 验证结果
|
||||
*/
|
||||
async validPassword(password) {
|
||||
return await bcrypt.compare(password, this.password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户角色
|
||||
* @returns {Promise<Array>} 用户角色列表
|
||||
*/
|
||||
async getRoles() {
|
||||
return await this.getRoles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否具有指定角色
|
||||
* @param {String|Array} roleName 角色名称或角色名称数组
|
||||
* @returns {Promise<Boolean>} 检查结果
|
||||
*/
|
||||
async hasRole(roleName) {
|
||||
const roles = await this.getRoles();
|
||||
const roleNames = roles.map(role => role.name);
|
||||
|
||||
if (Array.isArray(roleName)) {
|
||||
return roleName.some(name => roleNames.includes(name));
|
||||
}
|
||||
|
||||
return roleNames.includes(roleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为用户分配角色
|
||||
* @param {Number|Array} roleId 角色ID或角色ID数组
|
||||
* @returns {Promise<Boolean>} 分配结果
|
||||
*/
|
||||
async assignRole(roleId) {
|
||||
try {
|
||||
if (Array.isArray(roleId)) {
|
||||
await this.addRoles(roleId);
|
||||
} else {
|
||||
await this.addRole(roleId);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('分配角色失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除用户角色
|
||||
* @param {Number|Array} roleId 角色ID或角色ID数组
|
||||
* @returns {Promise<Boolean>} 移除结果
|
||||
*/
|
||||
async removeRole(roleId) {
|
||||
try {
|
||||
if (Array.isArray(roleId)) {
|
||||
await this.removeRoles(roleId);
|
||||
} else {
|
||||
await this.removeRole(roleId);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('移除角色失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户安全信息(不包含密码)
|
||||
* @returns {Object} 用户安全信息
|
||||
*/
|
||||
getSafeInfo() {
|
||||
const { password, ...safeInfo } = this.get({ plain: true });
|
||||
return safeInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化User模型
|
||||
User.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
isEmail: true
|
||||
}
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true
|
||||
},
|
||||
avatar: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive', 'suspended'),
|
||||
defaultValue: 'active'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'users',
|
||||
modelName: 'User',
|
||||
hooks: {
|
||||
beforeCreate: async (user) => {
|
||||
if (user.password) {
|
||||
user.password = await bcrypt.hash(user.password, 10);
|
||||
}
|
||||
},
|
||||
beforeUpdate: async (user) => {
|
||||
if (user.changed('password')) {
|
||||
user.password = await bcrypt.hash(user.password, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = User;
|
||||
123
backend/models/UserRole.js
Normal file
123
backend/models/UserRole.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* UserRole 模型定义
|
||||
* @file UserRole.js
|
||||
* @description 定义用户角色关联模型,用于实现用户和角色的多对多关系
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
|
||||
class UserRole extends BaseModel {
|
||||
/**
|
||||
* 获取用户角色分配记录
|
||||
* @param {Number} userId 用户ID
|
||||
* @param {Number} roleId 角色ID
|
||||
* @returns {Promise<UserRole|null>} 用户角色分配记录
|
||||
*/
|
||||
static async findUserRole(userId, roleId) {
|
||||
return await this.findOne({
|
||||
where: {
|
||||
user_id: userId,
|
||||
role_id: roleId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有角色分配记录
|
||||
* @param {Number} userId 用户ID
|
||||
* @returns {Promise<Array>} 用户角色分配记录列表
|
||||
*/
|
||||
static async findUserRoles(userId) {
|
||||
return await this.findAll({
|
||||
where: {
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的所有用户分配记录
|
||||
* @param {Number} roleId 角色ID
|
||||
* @returns {Promise<Array>} 角色用户分配记录列表
|
||||
*/
|
||||
static async findRoleUsers(roleId) {
|
||||
return await this.findAll({
|
||||
where: {
|
||||
role_id: roleId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配用户角色
|
||||
* @param {Number} userId 用户ID
|
||||
* @param {Number} roleId 角色ID
|
||||
* @returns {Promise<UserRole>} 用户角色分配记录
|
||||
*/
|
||||
static async assignRole(userId, roleId) {
|
||||
const [userRole, created] = await this.findOrCreate({
|
||||
where: {
|
||||
user_id: userId,
|
||||
role_id: roleId
|
||||
},
|
||||
defaults: {
|
||||
assigned_at: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
return { userRole, created };
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除用户角色
|
||||
* @param {Number} userId 用户ID
|
||||
* @param {Number} roleId 角色ID
|
||||
* @returns {Promise<Boolean>} 移除结果
|
||||
*/
|
||||
static async removeRole(userId, roleId) {
|
||||
const deleted = await this.destroy({
|
||||
where: {
|
||||
user_id: userId,
|
||||
role_id: roleId
|
||||
}
|
||||
});
|
||||
|
||||
return deleted > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化UserRole模型
|
||||
UserRole.init({
|
||||
user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
role_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
model: 'roles',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
assigned_at: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'user_roles',
|
||||
modelName: 'UserRole',
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
module.exports = UserRole;
|
||||
171
backend/models/index.js
Normal file
171
backend/models/index.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 模型索引文件
|
||||
* @file index.js
|
||||
* @description 导出所有模型并建立关联关系
|
||||
*/
|
||||
const { sequelize } = require('../config/database-simple');
|
||||
const BaseModel = require('./BaseModel');
|
||||
const Farm = require('./Farm');
|
||||
const Animal = require('./Animal');
|
||||
const Device = require('./Device');
|
||||
const Alert = require('./Alert');
|
||||
const User = require('./User');
|
||||
const Role = require('./Role');
|
||||
const UserRole = require('./UserRole');
|
||||
const Product = require('./Product');
|
||||
const Order = require('./Order');
|
||||
const OrderItem = require('./OrderItem');
|
||||
const SensorData = require('./SensorData');
|
||||
|
||||
// 建立模型之间的关联关系
|
||||
|
||||
// 养殖场与动物的一对多关系
|
||||
Farm.hasMany(Animal, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'animals',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
Animal.belongsTo(Farm, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'farm'
|
||||
});
|
||||
|
||||
// 养殖场与设备的一对多关系
|
||||
Farm.hasMany(Device, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'devices',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
Device.belongsTo(Farm, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'farm'
|
||||
});
|
||||
|
||||
// 养殖场与预警的一对多关系
|
||||
Farm.hasMany(Alert, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'alerts',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
Alert.belongsTo(Farm, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'farm'
|
||||
});
|
||||
|
||||
// 设备与预警的一对多关系
|
||||
Device.hasMany(Alert, {
|
||||
foreignKey: 'device_id',
|
||||
as: 'alerts',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
Alert.belongsTo(Device, {
|
||||
foreignKey: 'device_id',
|
||||
as: 'device'
|
||||
});
|
||||
|
||||
// 设备与传感器数据的一对多关系
|
||||
Device.hasMany(SensorData, {
|
||||
foreignKey: 'device_id',
|
||||
as: 'sensorData',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
SensorData.belongsTo(Device, {
|
||||
foreignKey: 'device_id',
|
||||
as: 'device'
|
||||
});
|
||||
|
||||
// 养殖场与传感器数据的一对多关系
|
||||
Farm.hasMany(SensorData, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'sensorData',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
SensorData.belongsTo(Farm, {
|
||||
foreignKey: 'farm_id',
|
||||
as: 'farm'
|
||||
});
|
||||
|
||||
// 用户与角色的多对多关系
|
||||
User.belongsToMany(Role, {
|
||||
through: UserRole,
|
||||
foreignKey: 'user_id',
|
||||
otherKey: 'role_id',
|
||||
as: 'roles'
|
||||
});
|
||||
Role.belongsToMany(User, {
|
||||
through: UserRole,
|
||||
foreignKey: 'role_id',
|
||||
otherKey: 'user_id',
|
||||
as: 'users'
|
||||
});
|
||||
|
||||
// 同步所有模型
|
||||
const syncModels = async (options = {}) => {
|
||||
try {
|
||||
await sequelize.sync(options);
|
||||
console.log('所有模型已同步到数据库');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('模型同步失败:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 用户与订单的一对多关系
|
||||
User.hasMany(Order, {
|
||||
foreignKey: 'user_id',
|
||||
as: 'orders',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
Order.belongsTo(User, {
|
||||
foreignKey: 'user_id',
|
||||
as: 'user'
|
||||
});
|
||||
|
||||
// 订单与订单项的一对多关系
|
||||
Order.hasMany(OrderItem, {
|
||||
foreignKey: 'order_id',
|
||||
as: 'orderItems',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
OrderItem.belongsTo(Order, {
|
||||
foreignKey: 'order_id',
|
||||
as: 'order'
|
||||
});
|
||||
|
||||
// 产品与订单项的一对多关系
|
||||
Product.hasMany(OrderItem, {
|
||||
foreignKey: 'product_id',
|
||||
as: 'orderItems',
|
||||
onDelete: 'RESTRICT',
|
||||
onUpdate: 'CASCADE'
|
||||
});
|
||||
OrderItem.belongsTo(Product, {
|
||||
foreignKey: 'product_id',
|
||||
as: 'product'
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
sequelize,
|
||||
BaseModel,
|
||||
Farm,
|
||||
Animal,
|
||||
Device,
|
||||
Alert,
|
||||
User,
|
||||
Role,
|
||||
UserRole,
|
||||
Product,
|
||||
Order,
|
||||
OrderItem,
|
||||
SensorData,
|
||||
syncModels
|
||||
};
|
||||
Reference in New Issue
Block a user