修改管理后台

This commit is contained in:
shenquanyi
2025-09-12 20:08:42 +08:00
parent 39d61c6f9b
commit 80a24c2d60
286 changed files with 75316 additions and 9452 deletions

View File

@@ -1,115 +1,215 @@
/**
* Animal 模型定义
* @file Animal.js
* @description 定义动物模型,用于数据库操作
* 动物信息模型
*/
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
const { sequelize } = require('../config/database-simple');
const { DataTypes, Model } = require('sequelize');
const sequelize = require('../config/database');
/**
* 动物模型
* @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();
class Animal extends Model {
// 获取动物类型文本
getAnimalTypeText() {
const typeMap = {
1: '牛',
2: '羊',
3: '猪',
4: '马'
};
return typeMap[this.animal_type] || '未知';
}
/**
* 更新动物数量
* @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;
}
// 获取品种文本
getBreedText() {
const breedMap = {
1: '西藏高山牦牛',
2: '荷斯坦奶牛',
3: '西门塔尔牛',
4: '安格斯牛',
5: '小尾寒羊',
6: '波尔山羊'
};
return breedMap[this.breed] || '未知品种';
}
/**
* 更新健康状态
* @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;
// 获取品类文本
getCategoryText() {
const categoryMap = {
1: '乳肉兼用',
2: '肉用',
3: '乳用',
4: '种用'
};
return categoryMap[this.category] || '未知品类';
}
// 获取来源类型文本
getSourceTypeText() {
const sourceMap = {
1: '合作社',
2: '农户',
3: '养殖场',
4: '进口'
};
return sourceMap[this.source_type] || '未知来源';
}
// 格式化出生日期
getBirthDateFormatted() {
if (this.birth_date) {
const date = new Date(this.birth_date);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(/\//g, '-');
}
return '';
}
// 格式化入场日期
getEntryDateFormatted() {
if (this.entry_date) {
const date = new Date(this.entry_date);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(/\//g, '-');
}
return '';
}
}
// 初始化Animal模型
Animal.init({
id: {
type: DataTypes.INTEGER,
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true
autoIncrement: true,
comment: '动物ID'
},
type: {
collar_number: {
type: DataTypes.STRING(50),
allowNull: false
allowNull: false,
comment: '项圈编号'
},
count: {
ear_tag: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '动物耳号'
},
animal_type: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
defaultValue: 1,
comment: '动物类型1-牛2-羊3-猪4-马'
},
breed: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
comment: '品种1-西藏高山牦牛2-荷斯坦奶牛3-西门塔尔牛4-安格斯牛5-小尾寒羊6-波尔山羊'
},
category: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
comment: '品类1-乳肉兼用2-肉用3-乳用4-种用'
},
source_type: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
comment: '来源类型1-合作社2-农户3-养殖场4-进口'
},
birth_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '出生日期'
},
birth_weight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: true,
defaultValue: 0.00,
comment: '出生体重'
},
weaning_weight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: true,
defaultValue: 0.00,
comment: '断奶体重'
},
weaning_age: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '断奶日龄'
},
entry_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '入场日期'
},
calving_count: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '历史已产胎次'
},
left_teat_count: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '乳头数(左)'
},
right_teat_count: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '乳头数(右)'
},
farm_id: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '农场ID'
},
status: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'farms',
key: 'id'
}
defaultValue: 1,
comment: '状态1-正常2-生病3-死亡'
},
health_status: {
type: DataTypes.ENUM('healthy', 'sick', 'quarantine', 'treatment'),
defaultValue: 'healthy'
},
last_inspection: {
created_at: {
type: DataTypes.DATE,
allowNull: true
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '创建时间'
},
notes: {
type: DataTypes.TEXT,
allowNull: true
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '更新时间'
}
}, {
sequelize,
tableName: 'animals',
modelName: 'Animal',
tableName: 'animals',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
updatedAt: 'updated_at',
indexes: [
{
fields: ['collar_number']
},
{
fields: ['ear_tag']
},
{
fields: ['farm_id']
}
]
});
/**
* 导出动物模型
* @exports Animal
*/
module.exports = Animal;

View File

@@ -0,0 +1,105 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const CattleBatch = sequelize.define('CattleBatch', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
name: {
type: DataTypes.STRING(200),
allowNull: false,
comment: '批次名称'
},
code: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '批次编号'
},
type: {
type: DataTypes.ENUM('育成批次', '繁殖批次', '育肥批次', '隔离批次', '治疗批次'),
allowNull: false,
comment: '批次类型'
},
startDate: {
type: DataTypes.DATE,
allowNull: false,
field: 'start_date',
comment: '开始日期'
},
expectedEndDate: {
type: DataTypes.DATE,
allowNull: true,
field: 'expected_end_date',
comment: '预计结束日期'
},
actualEndDate: {
type: DataTypes.DATE,
allowNull: true,
field: 'actual_end_date',
comment: '实际结束日期'
},
targetCount: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'target_count',
comment: '目标牛只数量'
},
currentCount: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'current_count',
comment: '当前牛只数量'
},
manager: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '负责人'
},
status: {
type: DataTypes.ENUM('进行中', '已完成', '已暂停'),
allowNull: false,
defaultValue: '进行中',
comment: '状态'
},
remark: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注'
},
farmId: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'farm_id',
comment: '所属农场ID'
}
}, {
tableName: 'cattle_batches',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '批次设置表'
});
// 定义关联关系
CattleBatch.associate = (models) => {
// 批次属于农场
CattleBatch.belongsTo(models.Farm, {
foreignKey: 'farmId',
as: 'farm'
});
// 批次与动物的多对多关系
CattleBatch.belongsToMany(models.Animal, {
through: 'cattle_batch_animals',
foreignKey: 'batch_id',
otherKey: 'animal_id',
as: 'animals'
});
};
module.exports = CattleBatch;

View File

@@ -0,0 +1,65 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const CattleBatchAnimal = sequelize.define('CattleBatchAnimal', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
batchId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'batch_id',
comment: '批次ID'
},
animalId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'animal_id',
comment: '动物ID'
},
addedDate: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
field: 'added_date',
comment: '添加日期'
},
addedBy: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'added_by',
comment: '添加人ID'
}
}, {
tableName: 'cattle_batch_animals',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '批次牛只关联表'
});
// 定义关联关系
CattleBatchAnimal.associate = (models) => {
// 关联到批次
CattleBatchAnimal.belongsTo(models.CattleBatch, {
foreignKey: 'batchId',
as: 'batch'
});
// 关联到牛只
CattleBatchAnimal.belongsTo(models.IotCattle, {
foreignKey: 'animalId',
as: 'cattle'
});
// 关联到添加人
CattleBatchAnimal.belongsTo(models.User, {
foreignKey: 'addedBy',
as: 'adder'
});
};
module.exports = CattleBatchAnimal;

View File

@@ -0,0 +1,110 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const CattleExitRecord = sequelize.define('CattleExitRecord', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
recordId: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
field: 'record_id',
comment: '记录编号'
},
animalId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'animal_id',
comment: '动物ID'
},
exitDate: {
type: DataTypes.DATE,
allowNull: false,
field: 'exit_date',
comment: '离栏日期'
},
exitReason: {
type: DataTypes.ENUM('出售', '死亡', '淘汰', '转场', '其他'),
allowNull: false,
field: 'exit_reason',
comment: '离栏原因'
},
originalPenId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'original_pen_id',
comment: '原栏舍ID'
},
destination: {
type: DataTypes.STRING(200),
allowNull: false,
comment: '去向'
},
disposalMethod: {
type: DataTypes.ENUM('屠宰', '转售', '掩埋', '焚烧', '其他'),
allowNull: false,
field: 'disposal_method',
comment: '处理方式'
},
handler: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '处理人员'
},
status: {
type: DataTypes.ENUM('已确认', '待确认', '已取消'),
allowNull: false,
defaultValue: '待确认',
comment: '状态'
},
remark: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注'
},
farmId: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'farm_id',
comment: '所属农场ID'
},
earNumber: {
type: DataTypes.STRING(50),
allowNull: true,
field: 'ear_number',
comment: '牛只耳号'
}
}, {
tableName: 'cattle_exit_records',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '离栏记录表'
});
// 定义关联关系
CattleExitRecord.associate = (models) => {
// 离栏记录属于牛只
CattleExitRecord.belongsTo(models.IotCattle, {
foreignKey: 'animalId',
as: 'cattle'
});
// 离栏记录属于原栏舍
CattleExitRecord.belongsTo(models.CattlePen, {
foreignKey: 'originalPenId',
as: 'originalPen'
});
// 离栏记录属于农场
CattleExitRecord.belongsTo(models.Farm, {
foreignKey: 'farmId',
as: 'farm'
});
};
module.exports = CattleExitRecord;

View File

@@ -0,0 +1,89 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const CattlePen = sequelize.define('CattlePen', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '栏舍名称'
},
code: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '栏舍编号'
},
type: {
type: DataTypes.ENUM('育成栏', '产房', '配种栏', '隔离栏', '治疗栏'),
allowNull: false,
comment: '栏舍类型'
},
capacity: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '栏舍容量'
},
currentCount: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'current_count',
comment: '当前牛只数量'
},
area: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
comment: '面积(平方米)'
},
location: {
type: DataTypes.TEXT,
allowNull: true,
comment: '位置描述'
},
status: {
type: DataTypes.ENUM('启用', '停用'),
allowNull: false,
defaultValue: '启用',
comment: '状态'
},
remark: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注'
},
farmId: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'farm_id',
comment: '所属农场ID'
}
}, {
tableName: 'cattle_pens',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '栏舍设置表'
});
// 定义关联关系
CattlePen.associate = (models) => {
// 栏舍属于农场
CattlePen.belongsTo(models.Farm, {
foreignKey: 'farmId',
as: 'farm'
});
// 栏舍有多个动物
CattlePen.hasMany(models.Animal, {
foreignKey: 'penId',
as: 'animals'
});
};
module.exports = CattlePen;

View File

@@ -0,0 +1,110 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const CattleTransferRecord = sequelize.define('CattleTransferRecord', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
recordId: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
field: 'record_id',
comment: '记录编号'
},
animalId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'animal_id',
comment: '动物ID'
},
fromPenId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'from_pen_id',
comment: '转出栏舍ID'
},
toPenId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'to_pen_id',
comment: '转入栏舍ID'
},
transferDate: {
type: DataTypes.DATE,
allowNull: false,
field: 'transfer_date',
comment: '转栏日期'
},
reason: {
type: DataTypes.ENUM('正常调栏', '疾病治疗', '配种需要', '产房准备', '隔离观察', '其他'),
allowNull: false,
comment: '转栏原因'
},
operator: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '操作人员'
},
status: {
type: DataTypes.ENUM('已完成', '进行中'),
allowNull: false,
defaultValue: '已完成',
comment: '状态'
},
remark: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注'
},
farmId: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'farm_id',
comment: '所属农场ID'
},
earNumber: {
type: DataTypes.STRING(50),
allowNull: true,
field: 'ear_number',
comment: '牛只耳号'
}
}, {
tableName: 'cattle_transfer_records',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '转栏记录表'
});
// 定义关联关系
CattleTransferRecord.associate = (models) => {
// 转栏记录属于牛只
CattleTransferRecord.belongsTo(models.IotCattle, {
foreignKey: 'animalId',
as: 'cattle'
});
// 转栏记录属于转出栏舍
CattleTransferRecord.belongsTo(models.CattlePen, {
foreignKey: 'fromPenId',
as: 'fromPen'
});
// 转栏记录属于转入栏舍
CattleTransferRecord.belongsTo(models.CattlePen, {
foreignKey: 'toPenId',
as: 'toPen'
});
// 转栏记录属于农场
CattleTransferRecord.belongsTo(models.Farm, {
foreignKey: 'farmId',
as: 'farm'
});
};
module.exports = CattleTransferRecord;

View File

@@ -0,0 +1,45 @@
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
/**
* 牛只品种模型
*/
class CattleType extends BaseModel {
static init(sequelize) {
return super.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
comment: '品种ID'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '品种名称'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '品种描述'
}
}, {
sequelize,
modelName: 'CattleType',
tableName: 'cattle_type',
comment: '牛只品种表',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
});
}
static associate(models) {
// 一个品种可以有多个牛只
this.hasMany(models.IotCattle, {
foreignKey: 'varieties',
as: 'cattle'
});
}
}
module.exports = CattleType;

View File

@@ -0,0 +1,45 @@
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
/**
* 牛只用途模型
*/
class CattleUser extends BaseModel {
static init(sequelize) {
return super.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
comment: '用途ID'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '用途名称'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '用途描述'
}
}, {
sequelize,
modelName: 'CattleUser',
tableName: 'cattle_user',
comment: '牛只用途表',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
});
}
static associate(models) {
// 一个用途可以有多个牛只
this.hasMany(models.IotCattle, {
foreignKey: 'user_id',
as: 'cattle'
});
}
}
module.exports = CattleUser;

View File

@@ -0,0 +1,243 @@
const { DataTypes } = require('sequelize')
const BaseModel = require('./BaseModel')
const { sequelize } = require('../config/database-simple')
/**
* 电子围栏模型
*/
class ElectronicFence extends BaseModel {
static init(sequelize) {
return super.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '围栏ID'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '围栏名称'
},
type: {
type: DataTypes.ENUM('collector', 'grazing', 'safety'),
allowNull: false,
defaultValue: 'collector',
comment: '围栏类型: collector-采集器电子围栏, grazing-放牧围栏, safety-安全围栏'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '围栏描述'
},
coordinates: {
type: DataTypes.JSON,
allowNull: false,
comment: '围栏坐标点数组'
},
center_lng: {
type: DataTypes.DECIMAL(10, 7),
allowNull: false,
comment: '围栏中心经度'
},
center_lat: {
type: DataTypes.DECIMAL(10, 7),
allowNull: false,
comment: '围栏中心纬度'
},
area: {
type: DataTypes.DECIMAL(10, 4),
allowNull: true,
comment: '围栏面积(平方米)'
},
grazing_status: {
type: DataTypes.ENUM('grazing', 'not_grazing'),
allowNull: false,
defaultValue: 'not_grazing',
comment: '放牧状态: grazing-放牧中, not_grazing-未放牧'
},
inside_count: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '安全区域内动物数量'
},
outside_count: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '安全区域外动物数量'
},
is_active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '是否启用'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID'
}
}, {
sequelize,
modelName: 'ElectronicFence',
tableName: 'electronic_fences',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '电子围栏表',
indexes: [
{
name: 'idx_fence_name',
fields: ['name']
},
{
name: 'idx_fence_type',
fields: ['type']
},
{
name: 'idx_fence_center',
fields: ['center_lng', 'center_lat']
},
{
name: 'idx_fence_active',
fields: ['is_active']
}
]
})
}
/**
* 定义关联关系
*/
static associate(models) {
// 围栏与农场关联(可选)
this.belongsTo(models.Farm, {
foreignKey: 'farm_id',
as: 'farm'
})
}
/**
* 获取围栏类型文本
*/
getTypeText() {
const typeMap = {
'collector': '采集器电子围栏',
'grazing': '放牧围栏',
'safety': '安全围栏'
}
return typeMap[this.type] || '未知类型'
}
/**
* 获取放牧状态文本
*/
getGrazingStatusText() {
const statusMap = {
'grazing': '放牧中',
'not_grazing': '未放牧'
}
return statusMap[this.grazing_status] || '未知状态'
}
/**
* 计算围栏面积(简化计算)
*/
calculateArea() {
if (!this.coordinates || this.coordinates.length < 3) {
return 0
}
// 使用Shoelace公式计算多边形面积
let area = 0
const coords = this.coordinates
for (let i = 0; i < coords.length; i++) {
const j = (i + 1) % coords.length
area += coords[i].lng * coords[j].lat
area -= coords[j].lng * coords[i].lat
}
// 转换为平方米(粗略计算)
return Math.abs(area) * 111000 * 111000 / 2
}
/**
* 计算围栏中心点
*/
calculateCenter() {
if (!this.coordinates || this.coordinates.length === 0) {
return { lng: 0, lat: 0 }
}
let lngSum = 0
let latSum = 0
this.coordinates.forEach(coord => {
lngSum += coord.lng
latSum += coord.lat
})
return {
lng: lngSum / this.coordinates.length,
lat: latSum / this.coordinates.length
}
}
/**
* 检查点是否在围栏内
*/
isPointInside(lng, lat) {
if (!this.coordinates || this.coordinates.length < 3) {
return false
}
let inside = false
const coords = this.coordinates
for (let i = 0, j = coords.length - 1; i < coords.length; j = i++) {
if (((coords[i].lat > lat) !== (coords[j].lat > lat)) &&
(lng < (coords[j].lng - coords[i].lng) * (lat - coords[i].lat) / (coords[j].lat - coords[i].lat) + coords[i].lng)) {
inside = !inside
}
}
return inside
}
/**
* 转换为前端格式
*/
toFrontendFormat() {
return {
id: this.id,
name: this.name,
type: this.getTypeText(),
description: this.description,
coordinates: this.coordinates,
center: {
lng: this.center_lng,
lat: this.center_lat
},
area: this.area,
grazingStatus: this.getGrazingStatusText(),
insideCount: this.inside_count,
outsideCount: this.outside_count,
isActive: this.is_active,
createdAt: this.created_at,
updatedAt: this.updated_at
}
}
}
// 初始化模型
ElectronicFence.init(sequelize)
module.exports = ElectronicFence

View File

@@ -0,0 +1,298 @@
/**
* 电子围栏坐标点模型
* 用于存储围栏绘制过程中用户选定的经纬度坐标点
*/
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const BaseModel = require('./BaseModel');
class ElectronicFencePoint extends BaseModel {
// 模型属性定义
static attributes = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '主键ID'
},
fence_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '关联的围栏ID',
references: {
model: 'electronic_fences',
key: 'id'
}
},
point_order: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '坐标点在围栏中的顺序从0开始'
},
longitude: {
type: DataTypes.DECIMAL(10, 7),
allowNull: false,
comment: '经度'
},
latitude: {
type: DataTypes.DECIMAL(10, 7),
allowNull: false,
comment: '纬度'
},
point_type: {
type: DataTypes.ENUM('corner', 'control', 'marker'),
allowNull: false,
defaultValue: 'corner',
comment: '坐标点类型corner-拐角点control-控制点marker-标记点'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '坐标点描述信息'
},
is_active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '是否激活'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID'
}
};
// 模型选项
static options = {
tableName: 'electronic_fence_points',
comment: '电子围栏坐标点表',
indexes: [
{
fields: ['fence_id']
},
{
fields: ['fence_id', 'point_order']
},
{
fields: ['longitude', 'latitude']
},
{
fields: ['point_type']
},
{
fields: ['is_active']
}
],
hooks: {
beforeCreate: (point, options) => {
// 创建前钩子
if (!point.point_order && point.point_order !== 0) {
// 如果没有指定顺序,自动计算
return ElectronicFencePoint.count({
where: { fence_id: point.fence_id }
}).then(count => {
point.point_order = count;
});
}
}
}
};
// 实例方法
/**
* 获取坐标点的经纬度对象
* @returns {Object} 包含lng和lat的对象
*/
getCoordinates() {
return {
lng: parseFloat(this.longitude),
lat: parseFloat(this.latitude)
};
}
/**
* 设置坐标点
* @param {number} lng - 经度
* @param {number} lat - 纬度
*/
setCoordinates(lng, lat) {
this.longitude = lng;
this.latitude = lat;
}
/**
* 转换为前端格式
* @returns {Object} 前端使用的坐标点格式
*/
toFrontendFormat() {
return {
id: this.id,
fenceId: this.fence_id,
pointOrder: this.point_order,
lng: parseFloat(this.longitude),
lat: parseFloat(this.latitude),
pointType: this.point_type,
description: this.description,
isActive: this.is_active,
createdAt: this.created_at,
updatedAt: this.updated_at
};
}
/**
* 计算到另一个点的距离(米)
* @param {ElectronicFencePoint} otherPoint - 另一个坐标点
* @returns {number} 距离(米)
*/
distanceTo(otherPoint) {
const R = 6371000; // 地球半径(米)
const lat1 = this.latitude * Math.PI / 180;
const lat2 = otherPoint.latitude * Math.PI / 180;
const deltaLat = (otherPoint.latitude - this.latitude) * Math.PI / 180;
const deltaLng = (otherPoint.longitude - this.longitude) * Math.PI / 180;
const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
/**
* 检查坐标点是否在指定范围内
* @param {number} centerLng - 中心点经度
* @param {number} centerLat - 中心点纬度
* @param {number} radius - 半径(米)
* @returns {boolean} 是否在范围内
*/
isWithinRadius(centerLng, centerLat, radius) {
const centerPoint = {
longitude: centerLng,
latitude: centerLat
};
return this.distanceTo(centerPoint) <= radius;
}
// 静态方法
/**
* 根据围栏ID获取所有坐标点
* @param {number} fenceId - 围栏ID
* @param {Object} options - 查询选项
* @returns {Promise<Array>} 坐标点数组
*/
static async getByFenceId(fenceId, options = {}) {
const defaultOptions = {
where: {
fence_id: fenceId,
is_active: true
},
order: [['point_order', 'ASC']]
};
return this.findAll({
...defaultOptions,
...options
});
}
/**
* 批量创建坐标点
* @param {number} fenceId - 围栏ID
* @param {Array} points - 坐标点数组
* @param {Object} options - 创建选项
* @returns {Promise<Array>} 创建的坐标点数组
*/
static async createPoints(fenceId, points, options = {}) {
const pointsData = points.map((point, index) => ({
fence_id: fenceId,
point_order: index,
longitude: point.lng,
latitude: point.lat,
point_type: point.type || 'corner',
description: point.description || null,
created_by: options.createdBy || null
}));
return this.bulkCreate(pointsData, options);
}
/**
* 更新围栏的所有坐标点
* @param {number} fenceId - 围栏ID
* @param {Array} points - 新的坐标点数组
* @param {Object} options - 更新选项
* @returns {Promise<Array>} 更新后的坐标点数组
*/
static async updateFencePoints(fenceId, points, options = {}) {
const transaction = options.transaction || await sequelize.transaction();
try {
// 删除现有坐标点
await this.destroy({
where: { fence_id: fenceId },
transaction
});
// 创建新的坐标点
const newPoints = await this.createPoints(fenceId, points, {
...options,
transaction
});
if (!options.transaction) {
await transaction.commit();
}
return newPoints;
} catch (error) {
if (!options.transaction) {
await transaction.rollback();
}
throw error;
}
}
/**
* 获取围栏的边界框
* @param {number} fenceId - 围栏ID
* @returns {Promise<Object>} 边界框对象
*/
static async getFenceBounds(fenceId) {
const points = await this.getByFenceId(fenceId);
if (points.length === 0) {
return null;
}
const lngs = points.map(p => parseFloat(p.longitude));
const lats = points.map(p => parseFloat(p.latitude));
return {
minLng: Math.min(...lngs),
maxLng: Math.max(...lngs),
minLat: Math.min(...lats),
maxLat: Math.max(...lats),
center: {
lng: (Math.min(...lngs) + Math.max(...lngs)) / 2,
lat: (Math.min(...lats) + Math.max(...lats)) / 2
}
};
}
}
// 初始化模型
ElectronicFencePoint.init(ElectronicFencePoint.attributes, {
...ElectronicFencePoint.options,
sequelize,
modelName: 'ElectronicFencePoint'
});
module.exports = ElectronicFencePoint;

125
backend/models/FormLog.js Normal file
View File

@@ -0,0 +1,125 @@
const { DataTypes } = require('sequelize')
const BaseModel = require('./BaseModel')
class FormLog extends BaseModel {
static init(sequelize) {
return super.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '日志ID'
},
module: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '模块名称'
},
action: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '操作类型'
},
userId: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'userId',
comment: '用户ID'
},
username: {
type: DataTypes.STRING(100),
allowNull: true,
field: 'username',
comment: '用户名'
},
sessionId: {
type: DataTypes.STRING(255),
allowNull: true,
field: 'sessionId',
comment: '会话ID'
},
userAgent: {
type: DataTypes.TEXT,
allowNull: true,
field: 'userAgent',
comment: '用户代理'
},
screenResolution: {
type: DataTypes.STRING(50),
allowNull: true,
field: 'screenResolution',
comment: '屏幕分辨率'
},
currentUrl: {
type: DataTypes.TEXT,
allowNull: true,
field: 'currentUrl',
comment: '当前URL'
},
formData: {
type: DataTypes.JSON,
allowNull: true,
field: 'formData',
comment: '表单数据'
},
additionalData: {
type: DataTypes.JSON,
allowNull: true,
field: 'additionalData',
comment: '附加数据'
},
ipAddress: {
type: DataTypes.STRING(45),
allowNull: true,
field: 'ipAddress',
comment: 'IP地址'
},
timestamp: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
field: 'timestamp',
comment: '时间戳'
},
status: {
type: DataTypes.ENUM('success', 'error', 'warning'),
allowNull: false,
defaultValue: 'success',
field: 'status',
comment: '状态'
},
errorMessage: {
type: DataTypes.TEXT,
allowNull: true,
field: 'errorMessage',
comment: '错误信息'
}
}, {
sequelize,
modelName: 'FormLog',
tableName: 'form_logs',
comment: '表单操作日志表',
timestamps: false, // 禁用自动时间戳
indexes: [
{
fields: ['module', 'action']
},
{
fields: ['userId']
},
{
fields: ['timestamp']
},
{
fields: ['status']
}
]
})
}
static associate(models) {
// 可以添加与其他模型的关联
}
}
module.exports = FormLog

333
backend/models/IotCattle.js Normal file
View File

@@ -0,0 +1,333 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const IotCattle = sequelize.define('IotCattle', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
field: 'id'
},
orgId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'org_id',
comment: '组织ID'
},
earNumber: {
type: DataTypes.BIGINT,
allowNull: false,
field: 'ear_number',
comment: '耳标号'
},
sex: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'sex',
comment: '性别'
},
strain: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'strain',
comment: '品系'
},
varieties: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'varieties',
comment: '品种'
},
cate: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'cate',
comment: '类别'
},
birthWeight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
field: 'birth_weight',
comment: '出生体重'
},
birthday: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'birthday',
comment: '出生日期'
},
penId: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'pen_id',
comment: '栏舍ID'
},
intoTime: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'into_time',
comment: '入栏时间'
},
parity: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'parity',
comment: '胎次'
},
source: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'source',
comment: '来源'
},
sourceDay: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'source_day',
comment: '来源天数'
},
sourceWeight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
field: 'source_weight',
comment: '来源体重'
},
weight: {
type: DataTypes.DOUBLE(11, 2),
allowNull: false,
field: 'weight',
comment: '当前体重'
},
event: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'event',
comment: '事件'
},
eventTime: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'event_time',
comment: '事件时间'
},
lactationDay: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'lactation_day',
comment: '泌乳天数'
},
semenNum: {
type: DataTypes.STRING(30),
allowNull: false,
field: 'semen_num',
comment: '精液编号'
},
isWear: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'is_wear',
comment: '是否佩戴设备'
},
batchId: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'batch_id',
comment: '批次ID'
},
imgs: {
type: DataTypes.STRING(800),
allowNull: false,
field: 'imgs',
comment: '图片'
},
isEleAuth: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'is_ele_auth',
comment: '是否电子认证'
},
isQuaAuth: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'is_qua_auth',
comment: '是否质量认证'
},
isDelete: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'is_delete',
comment: '是否删除'
},
isOut: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'is_out',
comment: '是否出栏'
},
createUid: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'create_uid',
comment: '创建人ID'
},
createTime: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'create_time',
comment: '创建时间'
},
algebra: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'algebra',
comment: '代数'
},
colour: {
type: DataTypes.TEXT,
allowNull: false,
field: 'colour',
comment: '毛色'
},
infoWeight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
field: 'info_weight',
comment: '信息体重'
},
descent: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'descent',
comment: '血统'
},
isVaccin: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'is_vaccin',
comment: '是否接种疫苗'
},
isInsemination: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'is_insemination',
comment: '是否人工授精'
},
isInsure: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'is_insure',
comment: '是否投保'
},
isMortgage: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'is_mortgage',
comment: '是否抵押'
},
updateTime: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'update_time',
comment: '更新时间'
},
breedBullTime: {
type: DataTypes.INTEGER,
allowNull: false,
field: 'breed_bull_time',
comment: '配种时间'
},
level: {
type: DataTypes.TINYINT,
allowNull: false,
field: 'level',
comment: '等级'
},
sixWeight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
field: 'six_weight',
comment: '6月龄体重'
},
eighteenWeight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
field: 'eighteen_weight',
comment: '18月龄体重'
},
twelveDayWeight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
field: 'twelve_day_weight',
comment: '12日龄体重'
},
eighteenDayWeight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
field: 'eighteen_day_weight',
comment: '18日龄体重'
},
xxivDayWeight: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
field: 'xxiv_day_weight',
comment: '24日龄体重'
},
semenBreedImgs: {
type: DataTypes.STRING(800),
allowNull: false,
field: 'semen_breed_imgs',
comment: '精液配种图片'
},
sellStatus: {
type: DataTypes.SMALLINT,
allowNull: false,
field: 'sell_status',
comment: '销售状态'
},
weightCalculateTime: {
type: DataTypes.DATE,
allowNull: true,
field: 'weight_calculate_time',
comment: '体重计算时间'
},
dayOfBirthday: {
type: DataTypes.INTEGER,
allowNull: true,
field: 'day_of_birthday',
comment: '出生天数'
}
}, {
tableName: 'iot_cattle',
timestamps: false, // iot_cattle表没有created_at和updated_at字段
comment: '物联网牛只表'
});
// 定义关联关系
IotCattle.associate = (models) => {
// 关联到农场
IotCattle.belongsTo(models.Farm, {
foreignKey: 'orgId',
as: 'farm'
});
// 关联到批次
IotCattle.belongsTo(models.CattleBatch, {
foreignKey: 'batchId',
as: 'batch'
});
// 关联到栏舍
IotCattle.belongsTo(models.CattlePen, {
foreignKey: 'penId',
as: 'pen'
});
// 关联到围栏
IotCattle.belongsTo(models.ElectronicFence, {
foreignKey: 'fenceId',
as: 'fence'
});
};
module.exports = IotCattle;

View File

@@ -0,0 +1,391 @@
const { Model, DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
class IotJbqClient extends Model {
// 获取设备状态文本
getStatusText() {
const statusMap = {
0: '离线',
1: '在线',
2: '报警',
3: '维护'
};
return statusMap[this.state] || '未知';
}
// 获取设备状态颜色
getStatusColor() {
const colorMap = {
0: 'red', // 离线
1: 'green', // 在线
2: 'orange', // 报警
3: 'blue' // 维护
};
return colorMap[this.state] || 'default';
}
// 获取电量百分比
getBatteryPercent() {
const voltage = parseFloat(this.voltage) || 0;
// 假设电压范围是0-100转换为百分比
return Math.min(100, Math.max(0, voltage));
}
// 获取温度值
getTemperatureValue() {
return parseFloat(this.temperature) || 0;
}
// 获取GPS状态文本
getGpsStatusText() {
const gpsMap = {
'A': '有效',
'V': '无效',
'N': '无信号'
};
return gpsMap[this.gps_state] || '未知';
}
// 获取最后更新时间
getLastUpdateTime() {
if (!this.uptime) return '-';
const date = new Date(this.uptime * 1000);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
// 获取绑带状态文本
getBandgeStatusText() {
const statusMap = {
0: '未绑定',
1: '已绑定',
2: '松动',
3: '脱落'
};
return statusMap[this.bandge_status] || '未知';
}
// 获取绑带状态颜色
getBandgeStatusColor() {
const colorMap = {
0: 'default', // 未绑定
1: 'green', // 已绑定
2: 'orange', // 松动
3: 'red' // 脱落
};
return colorMap[this.bandge_status] || 'default';
}
// 检查是否有定位信息
hasLocation() {
return this.lat && this.lon && this.lat !== '0' && this.lon !== '0';
}
// 获取耳标编号使用aaid字段
getEartagNumber() {
return this.aaid ? this.aaid.toString() : '-';
}
// 获取被采集主机使用sid字段
getHostId() {
return this.sid || '-';
}
// 获取总运动量
getTotalMovement() {
return this.walk || 0;
}
// 获取当日运动量
getDailyMovement() {
return this.y_steps || 0;
}
// 获取佩戴状态文本
getWearStatusText() {
return this.is_wear ? '已佩戴' : '未佩戴';
}
// 获取佩戴状态颜色
getWearStatusColor() {
return this.is_wear ? 'green' : 'default';
}
// 获取GPS信号等级
getGpsSignalLevel() {
const gpsState = this.gps_state;
if (gpsState === 'A') {
return '强';
} else if (gpsState === 'V') {
return '弱';
} else {
return '无';
}
}
}
IotJbqClient.init({
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
comment: '耳标设备ID'
},
org_id: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '组织ID'
},
cid: {
type: DataTypes.BIGINT,
allowNull: false,
comment: '设备CID'
},
aaid: {
type: DataTypes.BIGINT,
allowNull: false,
comment: '耳标编号'
},
uid: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '用户ID'
},
time: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '时间戳'
},
uptime: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '更新时间戳'
},
sid: {
type: DataTypes.STRING(16),
allowNull: false,
comment: '被采集主机ID'
},
walk: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '总运动量'
},
y_steps: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '当日运动量'
},
r_walk: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '剩余运动量'
},
lat: {
type: DataTypes.STRING(50),
allowNull: false,
defaultValue: '0',
comment: '纬度'
},
lon: {
type: DataTypes.STRING(50),
allowNull: false,
defaultValue: '0',
comment: '经度'
},
gps_state: {
type: DataTypes.STRING(5),
allowNull: false,
defaultValue: 'V',
comment: 'GPS状态'
},
voltage: {
type: DataTypes.STRING(10),
allowNull: true,
comment: '电压/电量'
},
temperature: {
type: DataTypes.STRING(10),
allowNull: true,
comment: '温度'
},
temperature_two: {
type: DataTypes.STRING(10),
allowNull: false,
defaultValue: '0',
comment: '温度2'
},
state: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '设备状态0-离线1-在线2-报警3-维护'
},
type: {
type: DataTypes.INTEGER(5),
allowNull: true,
defaultValue: 1,
comment: '设备类型'
},
sort: {
type: DataTypes.INTEGER(5),
allowNull: true,
defaultValue: 4,
comment: '排序'
},
ver: {
type: DataTypes.STRING(10),
allowNull: false,
defaultValue: '0',
comment: '固件版本'
},
weight: {
type: DataTypes.INTEGER(5),
allowNull: false,
defaultValue: 0,
comment: '重量'
},
start_time: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '开始时间'
},
run_days: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 240,
comment: '运行天数'
},
zenowalk: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '零步数'
},
zenotime: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '零时间'
},
is_read: {
type: DataTypes.INTEGER(5),
allowNull: true,
defaultValue: 0,
comment: '是否已读'
},
read_end_time: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '读取结束时间'
},
bank_userid: {
type: DataTypes.INTEGER(5),
allowNull: true,
comment: '银行用户ID'
},
bank_item_id: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '银行项目ID'
},
bank_house: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '银行房屋'
},
bank_lanwei: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '银行栏位'
},
bank_place: {
type: DataTypes.TINYINT(2),
allowNull: true,
defaultValue: 0,
comment: '银行地点'
},
is_home: {
type: DataTypes.INTEGER(5),
allowNull: false,
defaultValue: 1,
comment: '是否在家'
},
distribute_time: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: true,
defaultValue: 0,
comment: '分发时间'
},
bandge_status: {
type: DataTypes.INTEGER(5),
allowNull: false,
defaultValue: 0,
comment: '绑带状态0-未绑定1-已绑定2-松动3-脱落'
},
is_wear: {
type: DataTypes.TINYINT(1).UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '是否佩戴'
},
is_temperature: {
type: DataTypes.TINYINT(1).UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '是否有温度'
},
source_id: {
type: DataTypes.BIGINT,
allowNull: true,
comment: '来源ID'
},
expire_time: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '过期时间'
}
}, {
sequelize,
modelName: 'IotJbqClient',
tableName: 'iot_jbq_client',
timestamps: false,
indexes: [
{ fields: ['org_id'] },
{ fields: ['aaid'] },
{ fields: ['uid'] },
{ fields: ['uptime'] },
{ fields: ['sid'] },
{ fields: ['type'] },
{ fields: ['is_wear'] },
{ fields: ['source_id'] }
]
});
// 定义关联关系
IotJbqClient.associate = (models) => {
// 与牛只档案的关联关系通过cid和earNumber匹配
IotJbqClient.hasOne(models.IotCattle, {
foreignKey: 'earNumber',
sourceKey: 'cid',
as: 'cattle'
});
};
module.exports = IotJbqClient;

View File

@@ -0,0 +1,310 @@
/**
* 智能主机设备模型
* @file IotJbqServer.js
* @description 智能主机设备数据模型对应iot_jbq_server表
*/
const { DataTypes, Model } = require('sequelize');
const { sequelize } = require('../config/database-simple');
class IotJbqServer extends Model {
/**
* 获取设备状态文本
*/
getStatusText() {
const statusMap = {
0: '离线',
1: '在线',
2: '报警',
3: '维护'
};
return statusMap[this.state] || '未知';
}
/**
* 获取设备状态颜色
*/
getStatusColor() {
const colorMap = {
0: 'red', // 离线
1: 'green', // 在线
2: 'orange', // 报警
3: 'blue' // 维护
};
return colorMap[this.state] || 'default';
}
/**
* 获取GPS状态文本
*/
getGpsStatusText() {
const gpsMap = {
'A': '已定位',
'V': '未定位'
};
return gpsMap[this.gps_state] || '未知';
}
/**
* 获取GPS状态颜色
*/
getGpsStatusColor() {
const colorMap = {
'A': 'green', // 已定位
'V': 'red' // 未定位
};
return colorMap[this.gps_state] || 'default';
}
/**
* 获取电池电量百分比
*/
getBatteryPercent() {
const voltage = parseFloat(this.voltage) || 0;
// 假设电池电压范围是3.0V-4.2V
if (voltage >= 4.2) return 100;
if (voltage <= 3.0) return 0;
return Math.round(((voltage - 3.0) / 1.2) * 100);
}
/**
* 获取温度数值
*/
getTemperatureValue() {
return parseFloat(this.temperature) || 0;
}
/**
* 获取信号强度文本
*/
getSignalText() {
const signal = parseInt(this.signa) || 0;
if (signal >= 20) return '强';
if (signal >= 10) return '中';
if (signal >= 5) return '弱';
return '无信号';
}
/**
* 获取信号强度颜色
*/
getSignalColor() {
const signal = parseInt(this.signa) || 0;
if (signal >= 20) return 'green';
if (signal >= 10) return 'orange';
if (signal >= 5) return 'red';
return 'red';
}
/**
* 判断是否低电量
*/
isLowBattery() {
return this.getBatteryPercent() < 20;
}
/**
* 判断是否需要维护
*/
needsMaintenance() {
return this.state === 3 || this.isLowBattery() || this.gps_state === 'V';
}
/**
* 获取最后更新时间
*/
getLastUpdateTime() {
if (this.uptime) {
const date = new Date(this.uptime * 1000);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(/\//g, '-');
}
return '未知';
}
/**
* 获取设备编号使用sid字段
*/
getDeviceNumber() {
return this.sid || '无编号';
}
/**
* 判断设备是否联网
* 通过 simId 字段是否为空来判断联网状态
*/
isOnline() {
return this.simId && this.simId.trim() !== '';
}
/**
* 获取联网状态文本
*/
getNetworkStatusText() {
return this.isOnline() ? '已联网' : '未联网';
}
/**
* 获取联网状态颜色
*/
getNetworkStatusColor() {
return this.isOnline() ? 'green' : 'red';
}
}
// 初始化模型
IotJbqServer.init({
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
comment: '主机设备ID'
},
org_id: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
comment: '组织ID'
},
title: {
type: DataTypes.STRING(50),
allowNull: false,
defaultValue: '',
comment: '设备标题'
},
uid: {
type: DataTypes.BIGINT,
allowNull: false,
comment: '用户ID'
},
time: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '设备时间戳'
},
uptime: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '上传时间戳'
},
distribute_time: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '分发时间'
},
state: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '设备状态'
},
sid: {
type: DataTypes.STRING(30),
allowNull: false,
comment: '设备序列号'
},
gps_state: {
type: DataTypes.STRING(5),
allowNull: false,
comment: 'GPS状态'
},
lat: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '纬度'
},
lon: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '经度'
},
signa: {
type: DataTypes.STRING(200),
allowNull: false,
comment: '信号强度'
},
voltage: {
type: DataTypes.STRING(10),
allowNull: false,
comment: '电池电压'
},
temperature: {
type: DataTypes.STRING(10),
allowNull: false,
comment: '设备温度'
},
ver: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '固件版本'
},
macsid: {
type: DataTypes.BIGINT,
allowNull: true,
comment: 'MAC序列号'
},
ctwing: {
type: DataTypes.TEXT,
allowNull: true,
comment: 'CTWing信息'
},
bank_lanwei: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '栏位编号'
},
bank_house: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '栏舍编号'
},
bank_item_id: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '栏位项目ID'
},
fence_id: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '围栏ID'
},
source_id: {
type: DataTypes.BIGINT,
allowNull: true,
comment: '数据源ID'
},
simId: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'SIM卡ID用于判断联网状态'
}
}, {
sequelize,
modelName: 'IotJbqServer',
tableName: 'iot_jbq_server',
timestamps: false, // 该表没有created_at和updated_at字段
indexes: [
{
fields: ['uid']
},
{
fields: ['sid']
},
{
fields: ['state']
},
{
fields: ['fence_id']
},
{
fields: ['source_id']
}
]
});
module.exports = IotJbqServer;

View File

@@ -0,0 +1,338 @@
/**
* 智能项圈设备模型
* @file IotXqClient.js
* @description 智能项圈设备数据模型对应iot_xq_client表
*/
const { DataTypes, Model } = require('sequelize');
const { sequelize } = require('../config/database-simple');
class IotXqClient extends Model {
/**
* 获取设备状态文本
*/
getStatusText() {
const statusMap = {
0: '离线',
1: '在线',
2: '报警',
3: '维护'
};
return statusMap[this.state] || '未知';
}
/**
* 获取设备状态颜色
*/
getStatusColor() {
const colorMap = {
0: 'red', // 离线
1: 'green', // 在线
2: 'orange', // 报警
3: 'blue' // 维护
};
return colorMap[this.state] || 'default';
}
/**
* 获取GPS信号强度等级1-5星
*/
getGpsSignalLevel() {
const nsat = parseInt(this.nsat) || 0;
if (nsat >= 8) return 5;
if (nsat >= 6) return 4;
if (nsat >= 4) return 3;
if (nsat >= 2) return 2;
if (nsat >= 1) return 1;
return 0;
}
/**
* 获取电池电量百分比
*/
getBatteryPercent() {
const battery = parseFloat(this.battery) || 0;
// 假设电池电压范围是3.0V-4.2V
if (battery >= 4.2) return 100;
if (battery <= 3.0) return 0;
return Math.round(((battery - 3.0) / 1.2) * 100);
}
/**
* 获取体温数值
*/
getTemperatureValue() {
return parseFloat(this.temperature) || 0;
}
/**
* 判断是否低电量
*/
isLowBattery() {
return this.getBatteryPercent() < 20;
}
/**
* 判断是否需要维护
*/
needsMaintenance() {
return this.state === 3 || !this.is_connect || this.isLowBattery();
}
/**
* 获取最后更新时间
*/
getLastUpdateTime() {
if (this.uptime) {
return new Date(this.uptime * 1000).toLocaleString('zh-CN');
}
return '未知';
}
}
// 初始化模型
IotXqClient.init({
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
comment: '项圈设备ID'
},
org_id: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '组织ID'
},
uid: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '用户ID'
},
deviceId: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '设备编号'
},
sn: {
type: DataTypes.BIGINT,
allowNull: true,
comment: '设备序列号'
},
sort: {
type: DataTypes.TINYINT.UNSIGNED,
allowNull: false,
defaultValue: 1,
comment: '排序'
},
state: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '设备状态'
},
longitude: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '经度'
},
latitude: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '纬度'
},
altitude: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '海拔'
},
gps_state: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'GPS状态'
},
nsat: {
type: DataTypes.INTEGER,
allowNull: true,
comment: 'GPS卫星数量'
},
rsrp: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '信号强度'
},
battery: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '电池电量'
},
temperature: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '体温'
},
steps: {
type: DataTypes.BIGINT,
allowNull: true,
defaultValue: 0,
comment: '步数'
},
acc_x: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'X轴加速度'
},
acc_y: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'Y轴加速度'
},
acc_z: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'Z轴加速度'
},
bandge_status: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '设备佩戴状态'
},
ver: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '固件版本'
},
time: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '设备时间戳'
},
uptime: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '上传时间戳'
},
distribute_time: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: true,
defaultValue: 0,
comment: '分发时间'
},
zenowalk: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '零步数'
},
zenotime: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '零时间'
},
bank_item_id: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '栏位项目ID'
},
bank_house: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '栏舍编号'
},
bank_lanwei: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 0,
comment: '栏位编号'
},
bank_place: {
type: DataTypes.TINYINT,
allowNull: true,
defaultValue: 0,
comment: '位置编号'
},
is_home: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
comment: '是否在家'
},
fence_id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '围栏ID'
},
y_steps: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '昨日步数'
},
is_wear: {
type: DataTypes.TINYINT.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '是否佩戴'
},
is_temperature: {
type: DataTypes.TINYINT.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '是否测温'
},
is_connect: {
type: DataTypes.TINYINT.UNSIGNED,
allowNull: false,
defaultValue: 1,
comment: '是否连接'
},
source_id: {
type: DataTypes.BIGINT,
allowNull: true,
comment: '数据源ID'
},
loctime: {
type: DataTypes.STRING(20),
allowNull: true,
comment: '定位时间'
},
expire_time: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
comment: '过期时间'
},
subType: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '子类型'
}
}, {
sequelize,
modelName: 'IotXqClient',
tableName: 'iot_xq_client',
timestamps: false, // 该表没有created_at和updated_at字段
indexes: [
{
fields: ['uid']
},
{
fields: ['sn']
},
{
fields: ['ver']
},
{
fields: ['fence_id']
},
{
fields: ['source_id']
}
]
});
module.exports = IotXqClient;

View File

@@ -0,0 +1,384 @@
/**
* 菜单权限模型
* @file MenuPermission.js
* @description 菜单权限配置数据模型
*/
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
const { sequelize } = require('../config/database-simple');
class MenuPermission extends BaseModel {
/**
* 获取用户可访问的菜单
* @param {Array} userRoles 用户角色数组
* @param {Array} userPermissions 用户权限数组
* @returns {Array} 菜单树结构
*/
static async getUserMenus(userRoles = [], userPermissions = []) {
try {
const allMenus = await this.findAll({
where: {
is_visible: true,
is_enabled: true
},
order: [['sort_order', 'ASC']]
});
// 过滤用户有权限的菜单
const accessibleMenus = allMenus.filter(menu => {
return this.checkMenuAccess(menu, userRoles, userPermissions);
});
// 构建菜单树
return this.buildMenuTree(accessibleMenus);
} catch (error) {
console.error('获取用户菜单失败:', error);
return [];
}
}
/**
* 检查菜单访问权限
* @param {Object} menu 菜单对象
* @param {Array} userRoles 用户角色
* @param {Array} userPermissions 用户权限
* @returns {boolean} 是否有权限
*/
static checkMenuAccess(menu, userRoles, userPermissions) {
// 解析所需角色
let requiredRoles = [];
if (menu.required_roles) {
try {
requiredRoles = JSON.parse(menu.required_roles);
} catch (error) {
console.error('解析菜单所需角色失败:', error);
}
}
// 解析所需权限
let requiredPermissions = [];
if (menu.required_permissions) {
try {
requiredPermissions = JSON.parse(menu.required_permissions);
} catch (error) {
console.error('解析菜单所需权限失败:', error);
}
}
// 如果没有配置权限要求,则默认允许访问
if (requiredRoles.length === 0 && requiredPermissions.length === 0) {
return true;
}
// 检查角色权限
if (requiredRoles.length > 0) {
const hasRole = requiredRoles.some(role => userRoles.includes(role));
if (!hasRole) {
return false;
}
}
// 检查具体权限
if (requiredPermissions.length > 0) {
const hasPermission = requiredPermissions.some(permission =>
userPermissions.includes(permission)
);
if (!hasPermission) {
return false;
}
}
return true;
}
/**
* 构建菜单树结构
* @param {Array} menus 菜单数组
* @returns {Array} 菜单树
*/
static buildMenuTree(menus) {
const menuMap = new Map();
const roots = [];
// 创建菜单映射
menus.forEach(menu => {
menuMap.set(menu.id, {
...menu.dataValues,
children: []
});
});
// 构建树结构
menus.forEach(menu => {
const menuNode = menuMap.get(menu.id);
if (menu.parent_id && menuMap.has(menu.parent_id)) {
const parent = menuMap.get(menu.parent_id);
parent.children.push(menuNode);
} else {
roots.push(menuNode);
}
});
return roots;
}
/**
* 初始化默认菜单权限
*/
static async initDefaultMenus() {
try {
const defaultMenus = [
// 主要功能模块
{
menu_key: 'home',
menu_name: '首页',
menu_path: '/',
menu_type: 'page',
icon: 'home-outlined',
sort_order: 1,
required_roles: '["user", "admin", "manager"]'
},
{
menu_key: 'dashboard',
menu_name: '系统概览',
menu_path: '/dashboard',
menu_type: 'page',
icon: 'dashboard-outlined',
sort_order: 2,
required_roles: '["user", "admin", "manager"]'
},
{
menu_key: 'monitor',
menu_name: '实时监控',
menu_path: '/monitor',
menu_type: 'page',
icon: 'line-chart-outlined',
sort_order: 3,
required_roles: '["user", "admin", "manager"]'
},
{
menu_key: 'analytics',
menu_name: '数据分析',
menu_path: '/analytics',
menu_type: 'page',
icon: 'bar-chart-outlined',
sort_order: 4,
required_roles: '["user", "admin", "manager"]'
},
// 管理功能模块
{
menu_key: 'farms',
menu_name: '养殖场管理',
menu_path: '/farms',
menu_type: 'page',
icon: 'home-outlined',
sort_order: 10,
required_roles: '["admin", "manager"]'
},
{
menu_key: 'animals',
menu_name: '动物管理',
menu_path: '/animals',
menu_type: 'page',
icon: 'bug-outlined',
sort_order: 11,
required_roles: '["admin", "manager"]'
},
{
menu_key: 'devices',
menu_name: '设备管理',
menu_path: '/devices',
menu_type: 'page',
icon: 'desktop-outlined',
sort_order: 12,
required_roles: '["admin", "manager"]'
},
{
menu_key: 'alerts',
menu_name: '预警管理',
menu_path: '/alerts',
menu_type: 'page',
icon: 'alert-outlined',
sort_order: 13,
required_roles: '["admin", "manager"]'
},
// 业务功能模块
{
menu_key: 'products',
menu_name: '产品管理',
menu_path: '/products',
menu_type: 'page',
icon: 'shopping-outlined',
sort_order: 20,
required_roles: '["admin", "manager"]'
},
{
menu_key: 'orders',
menu_name: '订单管理',
menu_path: '/orders',
menu_type: 'page',
icon: 'shopping-cart-outlined',
sort_order: 21,
required_roles: '["admin", "manager"]'
},
{
menu_key: 'reports',
menu_name: '报表管理',
menu_path: '/reports',
menu_type: 'page',
icon: 'file-text-outlined',
sort_order: 22,
required_roles: '["admin", "manager"]'
},
// 系统管理模块
{
menu_key: 'users',
menu_name: '用户管理',
menu_path: '/users',
menu_type: 'page',
icon: 'user-outlined',
sort_order: 30,
required_roles: '["admin"]'
},
{
menu_key: 'system',
menu_name: '系统管理',
menu_path: '/system',
menu_type: 'page',
icon: 'setting-outlined',
sort_order: 31,
required_roles: '["admin"]'
}
];
for (const menuData of defaultMenus) {
const existing = await this.findOne({
where: { menu_key: menuData.menu_key }
});
if (!existing) {
await this.create(menuData);
}
}
console.log('默认菜单权限初始化完成');
} catch (error) {
console.error('初始化默认菜单权限失败:', error);
throw error;
}
}
}
// 初始化MenuPermission模型
MenuPermission.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '权限ID'
},
menu_key: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '菜单标识'
},
menu_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '菜单名称'
},
menu_path: {
type: DataTypes.STRING(200),
allowNull: true,
comment: '菜单路径'
},
parent_id: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '父菜单ID'
},
menu_type: {
type: DataTypes.ENUM('page', 'button', 'api'),
allowNull: false,
defaultValue: 'page',
comment: '菜单类型'
},
required_roles: {
type: DataTypes.TEXT,
allowNull: true,
comment: '所需角色JSON数组'
},
required_permissions: {
type: DataTypes.TEXT,
allowNull: true,
comment: '所需权限JSON数组'
},
icon: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '菜单图标'
},
sort_order: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '排序顺序'
},
is_visible: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '是否可见'
},
is_enabled: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '是否启用'
},
description: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '菜单描述'
}
}, {
sequelize,
tableName: 'menu_permissions',
modelName: 'MenuPermission',
comment: '菜单权限表',
indexes: [
{
fields: ['menu_key'],
unique: true
},
{
fields: ['parent_id']
},
{
fields: ['menu_type']
},
{
fields: ['sort_order']
}
]
});
// 定义自关联关系
MenuPermission.associate = function(models) {
// 自关联:父子菜单关系
MenuPermission.hasMany(MenuPermission, {
as: 'children',
foreignKey: 'parent_id'
});
MenuPermission.belongsTo(MenuPermission, {
as: 'parent',
foreignKey: 'parent_id'
});
};
module.exports = MenuPermission;

View File

@@ -0,0 +1,303 @@
/**
* 操作日志模型
* @file OperationLog.js
* @description 记录系统用户的操作日志,包括新增、编辑、删除操作
*/
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
class OperationLog extends BaseModel {
static init(sequelize) {
return super.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '主键ID'
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '操作用户ID'
},
username: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '操作用户名'
},
user_role: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '操作用户角色'
},
operation_type: {
type: DataTypes.ENUM('CREATE', 'UPDATE', 'DELETE', 'READ', 'LOGIN', 'LOGOUT', 'EXPORT', 'IMPORT', 'BATCH_DELETE', 'BATCH_UPDATE'),
allowNull: false,
comment: '操作类型CREATE-新增UPDATE-编辑DELETE-删除READ-查看LOGIN-登录LOGOUT-登出EXPORT-导出IMPORT-导入BATCH_DELETE-批量删除BATCH_UPDATE-批量更新'
},
module_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '操作模块名称'
},
table_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '操作的数据表名'
},
record_id: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '操作的记录ID'
},
operation_desc: {
type: DataTypes.TEXT,
allowNull: false,
comment: '操作描述'
},
old_data: {
type: DataTypes.JSON,
allowNull: true,
comment: '操作前的数据(编辑和删除时记录)'
},
new_data: {
type: DataTypes.JSON,
allowNull: true,
comment: '操作后的数据(新增和编辑时记录)'
},
ip_address: {
type: DataTypes.STRING(45),
allowNull: true,
comment: '操作IP地址'
},
user_agent: {
type: DataTypes.TEXT,
allowNull: true,
comment: '用户代理信息'
},
request_url: {
type: DataTypes.STRING(500),
allowNull: true,
comment: '请求URL'
},
request_method: {
type: DataTypes.STRING(10),
allowNull: true,
comment: '请求方法'
},
response_status: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '响应状态码'
},
execution_time: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '执行时间(毫秒)'
},
error_message: {
type: DataTypes.TEXT,
allowNull: true,
comment: '错误信息(如果有)'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '创建时间'
}
}, {
sequelize,
tableName: 'operation_logs',
comment: '操作日志表',
indexes: [
{
name: 'idx_user_id',
fields: ['user_id']
},
{
name: 'idx_operation_type',
fields: ['operation_type']
},
{
name: 'idx_module_name',
fields: ['module_name']
},
{
name: 'idx_table_name',
fields: ['table_name']
},
{
name: 'idx_created_at',
fields: ['created_at']
},
{
name: 'idx_user_operation',
fields: ['user_id', 'operation_type']
},
{
name: 'idx_module_operation',
fields: ['module_name', 'operation_type']
}
]
});
}
/**
* 记录操作日志
* @param {Object} logData 日志数据
* @returns {Promise<OperationLog>} 创建的日志记录
*/
static async recordOperation(logData) {
try {
const {
userId,
username,
userRole,
operationType,
moduleName,
tableName,
recordId,
operationDesc,
oldData,
newData,
ipAddress,
userAgent,
requestUrl,
requestMethod,
responseStatus,
executionTime,
errorMessage
} = logData;
return await this.create({
user_id: userId,
username: username,
user_role: userRole,
operation_type: operationType,
module_name: moduleName,
table_name: tableName,
record_id: recordId,
operation_desc: operationDesc,
old_data: oldData,
new_data: newData,
ip_address: ipAddress,
user_agent: userAgent,
request_url: requestUrl,
request_method: requestMethod,
response_status: responseStatus,
execution_time: executionTime,
error_message: errorMessage
});
} catch (error) {
console.error('记录操作日志失败:', error);
throw error;
}
}
/**
* 获取用户操作统计
* @param {number} userId 用户ID
* @param {string} startDate 开始日期
* @param {string} endDate 结束日期
* @returns {Promise<Object>} 统计结果
*/
static async getUserOperationStats(userId, startDate, endDate) {
try {
const whereClause = {
user_id: userId
};
if (startDate && endDate) {
whereClause.created_at = {
[this.sequelize.Sequelize.Op.between]: [startDate, endDate]
};
}
const stats = await this.findAll({
where: whereClause,
attributes: [
'operation_type',
[this.sequelize.fn('COUNT', this.sequelize.col('id')), 'count']
],
group: ['operation_type'],
raw: true
});
return stats.reduce((acc, stat) => {
acc[stat.operation_type] = parseInt(stat.count);
return acc;
}, {});
} catch (error) {
console.error('获取用户操作统计失败:', error);
throw error;
}
}
/**
* 获取模块操作统计
* @param {string} moduleName 模块名称
* @param {string} startDate 开始日期
* @param {string} endDate 结束日期
* @returns {Promise<Object>} 统计结果
*/
static async getModuleOperationStats(moduleName, startDate, endDate) {
try {
const whereClause = {
module_name: moduleName
};
if (startDate && endDate) {
whereClause.created_at = {
[this.sequelize.Sequelize.Op.between]: [startDate, endDate]
};
}
const stats = await this.findAll({
where: whereClause,
attributes: [
'operation_type',
[this.sequelize.fn('COUNT', this.sequelize.col('id')), 'count']
],
group: ['operation_type'],
raw: true
});
return stats.reduce((acc, stat) => {
acc[stat.operation_type] = parseInt(stat.count);
return acc;
}, {});
} catch (error) {
console.error('获取模块操作统计失败:', error);
throw error;
}
}
/**
* 清理过期日志
* @param {number} daysToKeep 保留天数
* @returns {Promise<number>} 删除的记录数
*/
static async cleanExpiredLogs(daysToKeep = 90) {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const deletedCount = await this.destroy({
where: {
created_at: {
[this.sequelize.Sequelize.Op.lt]: cutoffDate
}
}
});
console.log(`清理了 ${deletedCount} 条过期操作日志`);
return deletedCount;
} catch (error) {
console.error('清理过期日志失败:', error);
throw error;
}
}
}
module.exports = OperationLog;

View File

@@ -121,15 +121,7 @@ Order.init({
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',

View File

@@ -128,9 +128,7 @@ OrderItem.init({
sequelize,
tableName: 'order_items',
modelName: 'OrderItem',
timestamps: true,
createdAt: 'created_at',
updatedAt: false
timestamps: false
});
/**

178
backend/models/Pen.js Normal file
View File

@@ -0,0 +1,178 @@
/**
* 栏舍模型
* @file Pen.js
* @description 栏舍信息数据模型
*/
const { DataTypes, Model } = require('sequelize');
const sequelize = require('../config/database');
class Pen extends Model {
// 获取动物类型文本
getAnimalTypeText() {
return this.animal_type || '未知';
}
// 获取状态文本
getStatusText() {
return this.status ? '开启' : '关闭';
}
// 获取容量使用率(如果有当前动物数量的话)
getCapacityUsageRate() {
// 这里可以根据实际业务需求计算
return 0;
}
// 检查容量是否足够
isCapacitySufficient(requiredCapacity) {
return (this.capacity - this.getCurrentAnimalCount()) >= requiredCapacity;
}
// 获取当前动物数量(需要根据实际业务实现)
getCurrentAnimalCount() {
// 这里应该查询当前栏舍中的动物数量
// 暂时返回0实际实现时需要关联查询
return 0;
}
}
Pen.init({
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
comment: '栏舍ID'
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '栏舍名称',
validate: {
notEmpty: {
msg: '栏舍名称不能为空'
},
len: {
args: [1, 50],
msg: '栏舍名称长度应在1-50个字符之间'
}
}
},
animal_type: {
type: DataTypes.ENUM('马', '牛', '羊', '家禽', '猪'),
allowNull: false,
comment: '动物类型',
validate: {
notEmpty: {
msg: '动物类型不能为空'
},
isIn: {
args: [['马', '牛', '羊', '家禽', '猪']],
msg: '动物类型必须是:马、牛、羊、家禽、猪中的一个'
}
}
},
pen_type: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '栏舍类型',
validate: {
len: {
args: [0, 50],
msg: '栏舍类型长度不能超过50个字符'
}
}
},
responsible: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '负责人',
validate: {
notEmpty: {
msg: '负责人不能为空'
},
len: {
args: [1, 20],
msg: '负责人姓名长度应在1-20个字符之间'
}
}
},
capacity: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
comment: '容量',
validate: {
min: {
args: 1,
msg: '容量不能小于1'
},
max: {
args: 10000,
msg: '容量不能超过10000'
},
isInt: {
msg: '容量必须是整数'
}
}
},
status: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '状态true-开启false-关闭'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注信息',
validate: {
len: {
args: [0, 1000],
msg: '备注信息长度不能超过1000个字符'
}
}
},
farm_id: {
type: DataTypes.BIGINT,
allowNull: true,
comment: '所属农场ID',
references: {
model: 'farms',
key: 'id'
}
},
creator: {
type: DataTypes.STRING(50),
allowNull: false,
defaultValue: 'admin',
comment: '创建人'
}
}, {
sequelize,
modelName: 'Pen',
tableName: 'pens',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '栏舍管理表',
indexes: [
{
fields: ['name']
},
{
fields: ['animal_type']
},
{
fields: ['farm_id']
},
{
fields: ['status']
},
{
fields: ['created_at']
}
]
});
module.exports = Pen;

View File

@@ -0,0 +1,66 @@
/**
* 权限模型
* @file Permission.js
* @description 权限定义模型
*/
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const BaseModel = require('./BaseModel');
const Permission = sequelize.define('Permission', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '权限ID'
},
permission_key: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true,
comment: '权限标识'
},
permission_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '权限名称'
},
permission_desc: {
type: DataTypes.TEXT,
allowNull: true,
comment: '权限描述'
},
module: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '所属模块'
},
action: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '操作类型'
}
}, {
tableName: 'permissions',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '权限定义表',
indexes: [
{
fields: ['module'],
name: 'idx_module'
},
{
fields: ['action'],
name: 'idx_action'
},
{
fields: ['permission_key'],
name: 'idx_permission_key'
}
]
});
module.exports = Permission;

View File

@@ -15,8 +15,7 @@ const { sequelize } = require('../config/database-simple');
* @property {string} description - 产品描述
* @property {number} price - 产品价格(单位:分)
* @property {number} stock - 库存数量
* @property {string} image_url - 产品图片URL
* @property {boolean} is_active - 是否激活
* @property {string} status - 产品状态
* @property {Date} created_at - 创建时间
* @property {Date} updated_at - 更新时间
*/
@@ -28,7 +27,7 @@ class Product extends BaseModel {
*/
static async getActiveProducts(options = {}) {
return await this.findAll({
where: { is_active: true },
where: { status: 'active' },
...options
});
}
@@ -93,31 +92,24 @@ Product.init({
allowNull: true
},
price: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '单位:分'
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
stock: {
type: DataTypes.INTEGER,
allowNull: false,
allowNull: true,
defaultValue: 0
},
image_url: {
type: DataTypes.STRING(255),
allowNull: true
},
is_active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
status: {
type: DataTypes.ENUM('active', 'inactive'),
allowNull: true,
defaultValue: 'active'
}
}, {
sequelize,
tableName: 'products',
modelName: 'Product',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
timestamps: true
});
/**

View File

@@ -88,6 +88,12 @@ Role.init({
description: {
type: DataTypes.TEXT,
allowNull: true
},
status: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '角色状态true-启用false-禁用'
}
}, {
sequelize,

View File

@@ -0,0 +1,54 @@
/**
* 角色菜单权限关联模型
* @file RoleMenuPermission.js
* @description 角色和菜单权限的多对多关联表
*/
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database-simple');
const BaseModel = require('./BaseModel');
const RoleMenuPermission = sequelize.define('RoleMenuPermission', {
role_id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
comment: '角色ID',
references: {
model: 'roles',
key: 'id'
}
},
menu_permission_id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
comment: '菜单权限ID',
references: {
model: 'menu_permissions',
key: 'id'
}
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '创建时间'
}
}, {
tableName: 'RoleMenuPermissions',
timestamps: false, // 手动管理时间戳
comment: '角色菜单权限关联表',
indexes: [
{
fields: ['role_id'],
name: 'idx_role_id'
},
{
fields: ['menu_permission_id'],
name: 'idx_menu_permission_id'
}
]
});
module.exports = RoleMenuPermission;

View File

@@ -93,17 +93,7 @@ class SensorData extends BaseModel {
}
static associate(models) {
// 传感器数据属于某个设备
this.belongsTo(models.Device, {
foreignKey: 'device_id',
as: 'device'
});
// 传感器数据属于某个养殖场
this.belongsTo(models.Farm, {
foreignKey: 'farm_id',
as: 'farm'
});
// 关联关系已在index.js中定义这里不需要重复定义
}
/**

View File

@@ -0,0 +1,262 @@
/**
* 系统配置模型
* @file SystemConfig.js
* @description 系统参数配置数据模型
*/
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
const { sequelize } = require('../config/database-simple');
class SystemConfig extends BaseModel {
/**
* 获取配置值并自动转换类型
* @param {string} key 配置键名
* @returns {*} 转换后的配置值
*/
static async getValue(key) {
try {
const config = await this.findOne({
where: { config_key: key }
});
if (!config) {
return null;
}
return this.parseValue(config.config_value, config.config_type);
} catch (error) {
console.error(`获取配置 ${key} 失败:`, error);
return null;
}
}
/**
* 设置配置值
* @param {string} key 配置键名
* @param {*} value 配置值
* @param {number} userId 操作用户ID
* @returns {Object} 配置对象
*/
static async setValue(key, value, userId = null) {
try {
const existingConfig = await this.findOne({
where: { config_key: key }
});
const stringValue = this.stringifyValue(value);
const configType = this.detectType(value);
if (existingConfig) {
await existingConfig.update({
config_value: stringValue,
config_type: configType,
updated_by: userId
});
return existingConfig;
} else {
return await this.create({
config_key: key,
config_value: stringValue,
config_type: configType,
updated_by: userId
});
}
} catch (error) {
console.error(`设置配置 ${key} 失败:`, error);
throw error;
}
}
/**
* 获取公开配置(前端可访问)
* @returns {Object} 公开配置对象
*/
static async getPublicConfigs() {
try {
const configs = await this.findAll({
where: { is_public: true },
order: [['category', 'ASC'], ['sort_order', 'ASC']]
});
const result = {};
configs.forEach(config => {
result[config.config_key] = this.parseValue(config.config_value, config.config_type);
});
return result;
} catch (error) {
console.error('获取公开配置失败:', error);
return {};
}
}
/**
* 获取分类配置
* @param {string} category 配置分类
* @returns {Array} 配置列表
*/
static async getByCategory(category) {
try {
const configs = await this.findAll({
where: { category },
order: [['sort_order', 'ASC']]
});
return configs.map(config => ({
...config.dataValues,
parsed_value: this.parseValue(config.config_value, config.config_type)
}));
} catch (error) {
console.error(`获取分类 ${category} 配置失败:`, error);
return [];
}
}
/**
* 解析配置值
* @param {string} value 字符串值
* @param {string} type 数据类型
* @returns {*} 解析后的值
*/
static parseValue(value, type) {
if (value === null || value === undefined) {
return null;
}
try {
switch (type) {
case 'number':
return Number(value);
case 'boolean':
return value === 'true' || value === '1' || value === 1;
case 'json':
return JSON.parse(value);
case 'array':
return JSON.parse(value);
default:
return value;
}
} catch (error) {
console.error(`解析配置值失败: ${value}, type: ${type}`, error);
return value;
}
}
/**
* 转换值为字符串
* @param {*} value 任意类型的值
* @returns {string} 字符串值
*/
static stringifyValue(value) {
if (value === null || value === undefined) {
return null;
}
if (typeof value === 'object') {
return JSON.stringify(value);
}
return String(value);
}
/**
* 检测值的类型
* @param {*} value 任意类型的值
* @returns {string} 数据类型
*/
static detectType(value) {
if (typeof value === 'number') {
return 'number';
}
if (typeof value === 'boolean') {
return 'boolean';
}
if (Array.isArray(value)) {
return 'array';
}
if (typeof value === 'object' && value !== null) {
return 'json';
}
return 'string';
}
}
// 初始化SystemConfig模型
SystemConfig.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '配置ID'
},
config_key: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true,
comment: '配置键名'
},
config_value: {
type: DataTypes.TEXT,
allowNull: true,
comment: '配置值'
},
config_type: {
type: DataTypes.ENUM('string', 'number', 'boolean', 'json', 'array'),
allowNull: false,
defaultValue: 'string',
comment: '配置类型'
},
category: {
type: DataTypes.STRING(50),
allowNull: false,
defaultValue: 'general',
comment: '配置分类'
},
description: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '配置描述'
},
is_public: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
comment: '是否公开(前端可访问)'
},
is_editable: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '是否可编辑'
},
sort_order: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '排序顺序'
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '最后更新人ID'
}
}, {
sequelize,
tableName: 'system_configs',
modelName: 'SystemConfig',
comment: '系统配置表',
indexes: [
{
fields: ['config_key'],
unique: true
},
{
fields: ['category']
},
{
fields: ['is_public']
}
]
});
module.exports = SystemConfig;

View File

@@ -4,9 +4,9 @@
* @description 定义用户模型,用于数据库操作
*/
const { DataTypes } = require('sequelize');
const bcrypt = require('bcrypt');
const bcrypt = require('bcryptjs');
const BaseModel = require('./BaseModel');
const { sequelize } = require('../config/database-pool');
const { sequelize } = require('../config/database-simple');
class User extends BaseModel {
/**
@@ -23,7 +23,58 @@ class User extends BaseModel {
* @returns {Promise<Array>} 用户角色列表
*/
async getRoles() {
return await this.getRoles();
// 简化实现,直接返回当前角色
try {
const { Role } = require('./index');
const role = await Role.findByPk(this.roles);
return role ? [role] : [];
} catch (error) {
console.error('获取用户角色失败:', error);
return [];
}
}
/**
* 获取用户权限列表
* @returns {Promise<Array>} 用户权限列表
*/
async getPermissions() {
try {
const { getRolePermissions } = require('../config/permissions');
const roles = await this.getRoles();
if (roles.length === 0) {
return [];
}
// 获取角色的所有权限
const allPermissions = new Set();
for (const role of roles) {
const rolePermissions = getRolePermissions(role.name);
rolePermissions.forEach(permission => allPermissions.add(permission));
}
return Array.from(allPermissions);
} catch (error) {
console.error('获取用户权限失败:', error);
return [];
}
}
/**
* 检查用户是否具有指定权限
* @param {string|Array} permissions 权限名称或权限数组
* @returns {Promise<boolean>} 是否有权限
*/
async hasPermission(permissions) {
try {
const { hasPermission } = require('../config/permissions');
const userPermissions = await this.getPermissions();
return hasPermission(userPermissions, permissions);
} catch (error) {
console.error('检查用户权限失败:', error);
return false;
}
}
/**
@@ -44,16 +95,13 @@ class User extends BaseModel {
/**
* 为用户分配角色
* @param {Number|Array} roleId 角色ID或角色ID数组
* @param {Number} roleId 角色ID
* @returns {Promise<Boolean>} 分配结果
*/
async assignRole(roleId) {
try {
if (Array.isArray(roleId)) {
await this.addRoles(roleId);
} else {
await this.addRole(roleId);
}
this.roles = roleId;
await this.save();
return true;
} catch (error) {
console.error('分配角色失败:', error);
@@ -63,16 +111,12 @@ class User extends BaseModel {
/**
* 移除用户角色
* @param {Number|Array} roleId 角色ID或角色ID数组
* @returns {Promise<Boolean>} 移除结果
*/
async removeRole(roleId) {
async removeRole() {
try {
if (Array.isArray(roleId)) {
await this.removeRoles(roleId);
} else {
await this.removeRole(roleId);
}
this.roles = null;
await this.save();
return true;
} catch (error) {
console.error('移除角色失败:', error);
@@ -122,6 +166,15 @@ User.init({
type: DataTypes.STRING(255),
allowNull: true
},
roles: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 2, // 默认为user角色ID
references: {
model: 'roles',
key: 'id'
}
},
status: {
type: DataTypes.ENUM('active', 'inactive', 'suspended'),
defaultValue: 'active'

View File

@@ -16,6 +16,28 @@ const Product = require('./Product');
const Order = require('./Order');
const OrderItem = require('./OrderItem');
const SensorData = require('./SensorData');
const SystemConfig = require('./SystemConfig');
const MenuPermission = require('./MenuPermission');
const RoleMenuPermission = require('./RoleMenuPermission');
const Permission = require('./Permission');
const IotXqClient = require('./IotXqClient');
const IotJbqServer = require('./IotJbqServer');
const IotJbqClient = require('./IotJbqClient');
const ElectronicFence = require('./ElectronicFence');
const ElectronicFencePoint = require('./ElectronicFencePoint');
const Pen = require('./Pen');
const CattlePen = require('./CattlePen');
const CattleBatch = require('./CattleBatch');
const CattleBatchAnimal = require('./CattleBatchAnimal');
const CattleTransferRecord = require('./CattleTransferRecord');
const CattleExitRecord = require('./CattleExitRecord');
const IotCattle = require('./IotCattle');
const CattleType = require('./CattleType');
const CattleUser = require('./CattleUser');
const FormLog = require('./FormLog');
const OperationLog = require('./OperationLog');
// 注意:模型初始化在各自的模型文件中完成
// 建立模型之间的关联关系
@@ -31,6 +53,20 @@ Animal.belongsTo(Farm, {
as: 'farm'
});
// 养殖场与牛只的一对多关系
Farm.hasMany(IotCattle, {
foreignKey: 'orgId',
as: 'cattle',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
IotCattle.belongsTo(Farm, {
foreignKey: 'orgId',
as: 'farm'
});
// 牛只品种与牛只的一对多关系(在模型初始化后定义)
// 养殖场与设备的一对多关系
Farm.hasMany(Device, {
foreignKey: 'farm_id',
@@ -91,31 +127,29 @@ SensorData.belongsTo(Farm, {
as: 'farm'
});
// 用户与角色的多对多关系
User.belongsToMany(Role, {
through: UserRole,
foreignKey: 'user_id',
otherKey: 'role_id',
as: 'roles'
// 用户与角色的直接关联关系通过roles字段
User.belongsTo(Role, {
foreignKey: 'roles',
as: 'role'
});
Role.belongsToMany(User, {
through: UserRole,
foreignKey: 'role_id',
otherKey: 'user_id',
Role.hasMany(User, {
foreignKey: 'roles',
as: 'users'
});
// 同步所有模型
const syncModels = async (options = {}) => {
try {
await sequelize.sync(options);
console.log('所有模型已同步到数据库');
return true;
} catch (error) {
console.error('模型同步失败:', error);
return false;
}
};
// 用户与角色的多对多关系(暂时注释掉,当前使用直接关联)
// User.belongsToMany(Role, {
// through: UserRole,
// foreignKey: 'user_id',
// otherKey: 'role_id',
// as: 'userRoles'
// });
// Role.belongsToMany(User, {
// through: UserRole,
// foreignKey: 'role_id',
// otherKey: 'user_id',
// as: 'roleUsers'
// });
// 用户与订单的一对多关系
User.hasMany(Order, {
@@ -153,6 +187,304 @@ OrderItem.belongsTo(Product, {
as: 'product'
});
// 菜单权限的自关联关系已在associate方法中定义
// 角色与菜单权限的多对多关系
Role.belongsToMany(MenuPermission, {
through: RoleMenuPermission,
foreignKey: 'role_id',
otherKey: 'menu_permission_id',
as: 'menuPermissions'
});
MenuPermission.belongsToMany(Role, {
through: RoleMenuPermission,
foreignKey: 'menu_permission_id',
otherKey: 'role_id',
as: 'roles'
});
// 角色与权限的多对多关系
Role.belongsToMany(Permission, {
through: 'role_permissions',
as: 'permissions',
foreignKey: 'role_id',
otherKey: 'permission_id'
});
Permission.belongsToMany(Role, {
through: 'role_permissions',
as: 'roles',
foreignKey: 'permission_id',
otherKey: 'role_id'
});
// 农场与栏舍的一对多关系
Farm.hasMany(Pen, {
foreignKey: 'farm_id',
as: 'pens',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
Pen.belongsTo(Farm, {
foreignKey: 'farm_id',
as: 'farm'
});
// 农场与电子围栏的一对多关系
Farm.hasMany(ElectronicFence, {
foreignKey: 'farm_id',
as: 'electronicFences',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
ElectronicFence.belongsTo(Farm, {
foreignKey: 'farm_id',
as: 'farm'
});
// 电子围栏与坐标点的一对多关系
ElectronicFence.hasMany(ElectronicFencePoint, {
foreignKey: 'fence_id',
as: 'points',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
ElectronicFencePoint.belongsTo(ElectronicFence, {
foreignKey: 'fence_id',
as: 'fence'
});
// 用户与坐标点的关联关系
User.hasMany(ElectronicFencePoint, {
foreignKey: 'created_by',
as: 'createdFencePoints',
onDelete: 'SET NULL',
onUpdate: 'CASCADE'
});
ElectronicFencePoint.belongsTo(User, {
foreignKey: 'created_by',
as: 'creator'
});
User.hasMany(ElectronicFencePoint, {
foreignKey: 'updated_by',
as: 'updatedFencePoints',
onDelete: 'SET NULL',
onUpdate: 'CASCADE'
});
ElectronicFencePoint.belongsTo(User, {
foreignKey: 'updated_by',
as: 'updater'
});
// 农场与栏舍设置的一对多关系
Farm.hasMany(CattlePen, {
foreignKey: 'farm_id',
as: 'cattlePens',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattlePen.belongsTo(Farm, {
foreignKey: 'farm_id',
as: 'farm'
});
// 栏舍设置与动物的一对多关系
CattlePen.hasMany(Animal, {
foreignKey: 'pen_id',
as: 'animals',
onDelete: 'SET NULL',
onUpdate: 'CASCADE'
});
Animal.belongsTo(CattlePen, {
foreignKey: 'pen_id',
as: 'pen'
});
// 农场与批次设置的一对多关系
Farm.hasMany(CattleBatch, {
foreignKey: 'farm_id',
as: 'cattleBatches',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattleBatch.belongsTo(Farm, {
foreignKey: 'farm_id',
as: 'farm'
});
// 栏舍设置与牛只的一对多关系
CattlePen.hasMany(IotCattle, {
foreignKey: 'penId',
as: 'cattle',
onDelete: 'SET NULL',
onUpdate: 'CASCADE'
});
IotCattle.belongsTo(CattlePen, {
foreignKey: 'penId',
as: 'pen'
});
// 批次设置与牛只的一对多关系
CattleBatch.hasMany(IotCattle, {
foreignKey: 'batchId',
as: 'cattle',
onDelete: 'SET NULL',
onUpdate: 'CASCADE'
});
IotCattle.belongsTo(CattleBatch, {
foreignKey: 'batchId',
as: 'batch'
});
// 牛只与转栏记录的一对多关系
IotCattle.hasMany(CattleTransferRecord, {
foreignKey: 'animalId',
as: 'transferRecords',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattleTransferRecord.belongsTo(IotCattle, {
foreignKey: 'animalId',
as: 'cattle'
});
// 栏舍设置与转栏记录的一对多关系(转出)
CattlePen.hasMany(CattleTransferRecord, {
foreignKey: 'fromPenId',
as: 'fromTransferRecords',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattleTransferRecord.belongsTo(CattlePen, {
foreignKey: 'fromPenId',
as: 'fromPen'
});
// 栏舍设置与转栏记录的一对多关系(转入)
CattlePen.hasMany(CattleTransferRecord, {
foreignKey: 'toPenId',
as: 'toTransferRecords',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattleTransferRecord.belongsTo(CattlePen, {
foreignKey: 'toPenId',
as: 'toPen'
});
// 农场与转栏记录的一对多关系
Farm.hasMany(CattleTransferRecord, {
foreignKey: 'farmId',
as: 'transferRecords',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattleTransferRecord.belongsTo(Farm, {
foreignKey: 'farmId',
as: 'farm'
});
// 牛只与离栏记录的一对多关系
IotCattle.hasMany(CattleExitRecord, {
foreignKey: 'animalId',
as: 'exitRecords',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattleExitRecord.belongsTo(IotCattle, {
foreignKey: 'animalId',
as: 'cattle'
});
// 栏舍设置与离栏记录的一对多关系
CattlePen.hasMany(CattleExitRecord, {
foreignKey: 'originalPenId',
as: 'exitRecords',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattleExitRecord.belongsTo(CattlePen, {
foreignKey: 'originalPenId',
as: 'originalPen'
});
// 农场与离栏记录的一对多关系
Farm.hasMany(CattleExitRecord, {
foreignKey: 'farmId',
as: 'exitRecords',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
CattleExitRecord.belongsTo(Farm, {
foreignKey: 'farmId',
as: 'farm'
});
// 初始化所有模型
const initModels = () => {
// 初始化CattleType模型
CattleType.init(sequelize);
// 初始化CattleUser模型
CattleUser.init(sequelize);
// 初始化FormLog模型
FormLog.init(sequelize);
// 初始化OperationLog模型
OperationLog.init(sequelize);
};
// 初始化模型
initModels();
// 在模型初始化后定义CattleType的关联关系
CattleType.hasMany(IotCattle, {
foreignKey: 'varieties',
as: 'cattle',
onDelete: 'RESTRICT',
onUpdate: 'CASCADE'
});
IotCattle.belongsTo(CattleType, {
foreignKey: 'varieties',
as: 'cattleType'
});
// 在模型初始化后定义CattleUser的关联关系
CattleUser.hasMany(IotCattle, {
foreignKey: 'strain',
as: 'cattle',
onDelete: 'RESTRICT',
onUpdate: 'CASCADE'
});
IotCattle.belongsTo(CattleUser, {
foreignKey: 'strain',
as: 'cattleUser'
});
// 用户与操作日志的一对多关系
User.hasMany(OperationLog, {
foreignKey: 'user_id',
as: 'operationLogs',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
OperationLog.belongsTo(User, {
foreignKey: 'user_id',
as: 'user'
});
// 同步所有模型
const syncModels = async (options = {}) => {
try {
await sequelize.sync(options);
console.log('所有模型已同步到数据库');
return true;
} catch (error) {
console.error('模型同步失败:', error);
return false;
}
};
module.exports = {
sequelize,
BaseModel,
@@ -167,5 +499,67 @@ module.exports = {
Order,
OrderItem,
SensorData,
SystemConfig,
MenuPermission,
RoleMenuPermission,
Permission,
IotXqClient,
IotJbqServer,
IotJbqClient,
ElectronicFence,
ElectronicFencePoint,
Pen,
CattlePen,
CattleBatch,
CattleBatchAnimal,
CattleTransferRecord,
CattleExitRecord,
IotCattle,
CattleType,
CattleUser,
FormLog,
OperationLog,
syncModels
};
};
// 调用模型的associate方法建立关联关系
const models = {
Farm,
Animal,
Device,
Alert,
User,
Role,
UserRole,
Product,
Order,
OrderItem,
SensorData,
SystemConfig,
MenuPermission,
RoleMenuPermission,
Permission,
IotXqClient,
IotJbqServer,
IotJbqClient,
ElectronicFence,
ElectronicFencePoint,
Pen,
CattlePen,
CattleBatch,
CattleBatchAnimal,
CattleTransferRecord,
CattleExitRecord,
IotCattle,
CattleType,
CattleUser,
FormLog,
OperationLog
};
// 建立关联关系(暂时禁用,避免冲突)
// Object.keys(models).forEach(modelName => {
// if (models[modelName].associate) {
// models[modelName].associate(models);
// }
// });