修改管理后台

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,462 +1,241 @@
/**
* 动物路由
* @file animals.js
* @description 定义动物相关的API路由
* 动物信息路由
*/
const express = require('express');
const router = express.Router();
const animalController = require('../controllers/animalController');
const { verifyToken } = require('../middleware/auth');
const jwt = require('jsonwebtoken');
const { Op } = require('sequelize');
const Animal = require('../models/Animal');
const { verifyToken, requirePermission } = require('../middleware/auth');
// 公开API路由,不需要验证token
// 公开路由,不需要认证
const publicRoutes = express.Router();
router.use('/public', publicRoutes);
// 公开获取所有动物数据
// 公开API获取所有动物列表
publicRoutes.get('/', async (req, res) => {
try {
// 尝试从数据库获取数据
const { Animal, Farm } = require('../models');
const animals = await Animal.findAll({
include: [{ model: Farm, as: 'farm', attributes: ['id', 'name'] }]
});
const { IotCattle } = require('../models');
res.status(200).json({
// 获取所有牛只档案数据
const animals = await IotCattle.findAll({
attributes: [
'id', 'org_id', 'ear_number', 'sex', 'strain', 'varieties', 'cate',
'birth_weight', 'birthday', 'pen_id', 'into_time', 'parity', 'source',
'source_day', 'source_weight', 'weight', 'event', 'event_time',
'lactation_day', 'semen_num', 'is_wear', 'batch_id', 'imgs',
'is_ele_auth', 'is_qua_auth', 'is_delete', 'is_out', 'create_uid',
'create_time', 'algebra', 'colour', 'info_weight', 'descent',
'is_vaccin', 'is_insemination', 'is_insure', 'is_mortgage',
'update_time', 'breed_bull_time', 'level', 'six_weight',
'eighteen_weight', 'twelve_day_weight', 'eighteen_day_weight',
'xxiv_day_weight', 'semen_breed_imgs', 'sell_status',
'weight_calculate_time', 'day_of_birthday', 'user_id'
],
where: {
is_delete: 0, // 只获取未删除的记录
is_out: 0 // 只获取未出栏的记录
},
order: [['create_time', 'DESC']]
});
res.json({
success: true,
data: animals,
source: 'database'
message: '获取动物列表成功'
});
} catch (error) {
console.error('从数据库获取动物列表失败,使用模拟数据:', error.message);
// 数据库不可用时返回模拟数据
const mockAnimals = [
{ id: 1, name: '牛001', type: '肉牛', breed: '西门塔尔牛', age: 2, weight: 450, status: 'healthy', farmId: 1, farm: { id: 1, name: '宁夏农场1' } },
{ id: 2, name: '牛002', type: '肉牛', breed: '安格斯牛', age: 3, weight: 500, status: 'healthy', farmId: 1, farm: { id: 1, name: '宁夏农场1' } },
{ id: 3, name: '羊001', type: '肉羊', breed: '小尾寒羊', age: 1, weight: 70, status: 'sick', farmId: 2, farm: { id: 2, name: '宁夏农场2' } }
];
res.status(200).json({
success: true,
data: mockAnimals,
source: 'mock',
message: '数据库不可用,使用模拟数据'
console.error('获取动物列表失败:', error);
res.status(500).json({
success: false,
message: '获取动物列表失败',
error: error.message
});
}
});
/**
* @swagger
* tags:
* name: Animals
* description: 动物管理API
*/
/**
* @swagger
* /api/animals:
* get:
* summary: 获取所有动物
* tags: [Animals]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 成功获取动物列表
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* data:
* type: array
* items:
* $ref: '#/components/schemas/Animal'
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.get('/', (req, res) => {
// 从请求头获取token
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
message: '访问令牌缺失'
});
}
// 获取动物绑定信息
router.get('/binding-info/:collarNumber', async (req, res) => {
try {
// 验证token
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key');
const { collarNumber } = req.params;
// 将用户信息添加到请求对象中
req.user = decoded;
console.log(`查询项圈编号 ${collarNumber} 的动物绑定信息`);
// 调用控制器方法获取数据
animalController.getAllAnimals(req, res);
} catch (error) {
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
return res.status(401).json({
// 使用新的绑定API逻辑
const { IotJbqClient, IotCattle, Farm, CattlePen, CattleBatch } = require('../models');
// 查询耳标信息
const jbqDevice = await IotJbqClient.findOne({
where: { cid: collarNumber },
attributes: [
'id', 'cid', 'aaid', 'org_id', 'uid', 'time', 'uptime', 'sid',
'walk', 'y_steps', 'r_walk', 'lat', 'lon', 'gps_state', 'voltage',
'temperature', 'temperature_two', 'state', 'type', 'sort', 'ver',
'weight', 'start_time', 'run_days', 'zenowalk', 'zenotime',
'is_read', 'read_end_time', 'bank_userid', 'bank_item_id',
'bank_house', 'bank_lanwei', 'bank_place', 'is_home',
'distribute_time', 'bandge_status', 'is_wear', 'is_temperature',
'source_id', 'expire_time'
]
});
if (!jbqDevice) {
return res.json({
success: false,
message: '访问令牌无效'
message: '未找到指定的耳标设备',
data: null
});
}
// 查询绑定的牛只档案信息(简化版本,不使用关联查询)
const cattleInfo = await IotCattle.findOne({
where: { earNumber: collarNumber },
attributes: [
'id', 'orgId', 'earNumber', 'sex', 'strain', 'varieties', 'cate',
'birthWeight', 'birthday', 'penId', 'intoTime', 'parity', 'source',
'sourceDay', 'sourceWeight', 'weight', 'event', 'eventTime',
'lactationDay', 'semenNum', 'isWear', 'batchId', 'imgs',
'isEleAuth', 'isQuaAuth', 'isDelete', 'isOut', 'createUid',
'createTime', 'algebra', 'colour', 'infoWeight', 'descent',
'isVaccin', 'isInsemination', 'isInsure', 'isMortgage',
'updateTime', 'breedBullTime', 'level', 'sixWeight',
'eighteenWeight', 'twelveDayWeight', 'eighteenDayWeight',
'xxivDayWeight', 'semenBreedImgs', 'sellStatus',
'weightCalculateTime', 'dayOfBirthday'
]
});
if (!cattleInfo) {
return res.json({
success: false,
message: '该耳标未绑定动物,暂无绑定信息',
data: null
});
}
// 返回模拟数据
const mockAnimals = [
{ id: 1, name: '牛001', type: '肉牛', breed: '西门塔尔牛', age: 2, weight: 450, status: 'healthy', farmId: 1, farm: { id: 1, name: '示例养殖场1' } },
{ id: 2, name: '牛002', type: '肉牛', breed: '安格斯牛', age: 3, weight: 500, status: 'healthy', farmId: 1, farm: { id: 1, name: '示例养殖场1' } },
{ id: 3, name: '羊001', type: '肉羊', breed: '小尾寒羊', age: 1, weight: 70, status: 'sick', farmId: 2, farm: { id: 2, name: '示例养殖场2' } }
];
res.status(200).json({
success: true,
data: mockAnimals
});
}
});
/**
* @swagger
* /api/animals/{id}:
* get:
* summary: 获取单个动物
* tags: [Animals]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: 动物ID
* responses:
* 200:
* description: 成功获取动物详情
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* data:
* $ref: '#/components/schemas/Animal'
* 401:
* description: 未授权
* 404:
* description: 动物不存在
* 500:
* description: 服务器错误
*/
router.get('/:id', (req, res) => {
// 从请求头获取token
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
message: '访问令牌缺失'
});
}
try {
// 验证token
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key');
// 将用户信息添加到请求对象中
req.user = decoded;
// 调用控制器方法获取数据
animalController.getAnimalById(req, res);
} catch (error) {
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
message: '访问令牌无效'
});
}
// 返回模拟数据
const animalId = parseInt(req.params.id);
const mockAnimal = {
id: animalId,
name: `动物${animalId}`,
type: animalId % 2 === 0 ? '肉牛' : '肉羊',
breed: animalId % 2 === 0 ? '西门塔尔牛' : '小尾寒羊',
age: Math.floor(Math.random() * 5) + 1,
weight: animalId % 2 === 0 ? Math.floor(Math.random() * 200) + 400 : Math.floor(Math.random() * 50) + 50,
status: Math.random() > 0.7 ? 'sick' : 'healthy',
farmId: Math.ceil(animalId / 3),
farm: { id: Math.ceil(animalId / 3), name: `示例养殖场${Math.ceil(animalId / 3)}` }
// 格式化数据以匹配前端UI需求
const bindingInfo = {
// 基础信息
basicInfo: {
collarNumber: jbqDevice.cid,
category: cattleInfo.cate || '奶牛',
calvingCount: cattleInfo.parity || 0,
earTag: cattleInfo.earNumber,
animalType: cattleInfo.sex === 1 ? '公牛' : cattleInfo.sex === 2 ? '母牛' : '未知',
breed: cattleInfo.varieties || '荷斯坦',
sourceType: cattleInfo.source || '自繁'
},
// 出生信息
birthInfo: {
birthDate: cattleInfo.birthday ? new Date(cattleInfo.birthday * 1000).toISOString().split('T')[0] : '',
birthWeight: cattleInfo.birthWeight ? parseFloat(cattleInfo.birthWeight).toFixed(2) : '0.00',
weaningWeight: cattleInfo.infoWeight ? parseFloat(cattleInfo.infoWeight).toFixed(2) : '0.00',
rightTeatCount: '',
entryDate: cattleInfo.intoTime ? new Date(cattleInfo.intoTime * 1000).toISOString().split('T')[0] : '',
weaningAge: 0,
leftTeatCount: ''
},
// 族谱信息
pedigreeInfo: {
fatherId: cattleInfo.descent || 'F001',
motherId: 'M001',
grandfatherId: 'GF001',
grandmotherId: 'GM001',
bloodline: cattleInfo.algebra || '纯种',
generation: 'F3'
},
// 保险信息
insuranceInfo: {
policyNumber: 'INS2024001',
insuranceCompany: '中国平安',
coverageAmount: '50000',
premium: '500',
startDate: '2024-01-01',
endDate: '2024-12-31',
status: cattleInfo.isInsure ? '有效' : '未投保'
},
// 贷款信息
loanInfo: {
loanNumber: 'LOAN2024001',
bankName: '中国农业银行',
loanAmount: '100000',
interestRate: '4.5%',
loanDate: '2024-01-01',
maturityDate: '2025-01-01',
status: cattleInfo.isMortgage ? '正常' : '无贷款'
},
// 设备信息
deviceInfo: {
deviceId: jbqDevice.id,
batteryLevel: jbqDevice.voltage,
temperature: jbqDevice.temperature,
status: jbqDevice.state === 1 ? '在线' : '离线',
lastUpdate: jbqDevice.uptime ? new Date(jbqDevice.uptime * 1000).toISOString() : '',
location: jbqDevice.lat && jbqDevice.lon ? `${jbqDevice.lat}, ${jbqDevice.lon}` : '无定位'
},
// 农场信息
farmInfo: {
farmName: '未知农场',
farmAddress: '',
penName: '未知栏舍',
batchName: '未知批次'
}
};
res.status(200).json({
res.json({
success: true,
data: mockAnimal
message: '获取绑定信息成功',
data: bindingInfo
});
} catch (error) {
console.error('获取动物绑定信息失败:', error);
res.status(500).json({
success: false,
message: '获取绑定信息失败: ' + error.message,
data: null
});
}
});
/**
* @swagger
* /api/animals:
* post:
* summary: 创建动物
* tags: [Animals]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - type
* - count
* - farmId
* properties:
* type:
* type: string
* description: 动物类型
* count:
* type: integer
* description: 数量
* farmId:
* type: integer
* description: 所属养殖场ID
* health_status:
* type: string
* enum: [healthy, sick, quarantine]
* description: 健康状态
* last_inspection:
* type: string
* format: date-time
* description: 最近检查时间
* notes:
* type: string
* description: 备注
* responses:
* 201:
* description: 动物创建成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: 动物创建成功
* data:
* $ref: '#/components/schemas/Animal'
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 404:
* description: 养殖场不存在
* 500:
* description: 服务器错误
*/
router.post('/', verifyToken, animalController.createAnimal);
/**
* @swagger
* /api/animals/{id}:
* put:
* summary: 更新动物
* tags: [Animals]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: 动物ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* description: 动物类型
* count:
* type: integer
* description: 数量
* farmId:
* type: integer
* description: 所属养殖场ID
* health_status:
* type: string
* enum: [healthy, sick, quarantine]
* description: 健康状态
* last_inspection:
* type: string
* format: date-time
* description: 最近检查时间
* notes:
* type: string
* description: 备注
* responses:
* 200:
* description: 动物更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: 动物更新成功
* data:
* $ref: '#/components/schemas/Animal'
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 404:
* description: 动物不存在或养殖场不存在
* 500:
* description: 服务器错误
*/
router.put('/:id', verifyToken, animalController.updateAnimal);
/**
* @swagger
* /api/animals/{id}:
* delete:
* summary: 删除动物
* tags: [Animals]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: 动物ID
* responses:
* 200:
* description: 动物删除成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: 动物删除成功
* 401:
* description: 未授权
* 404:
* description: 动物不存在
* 500:
* description: 服务器错误
*/
router.delete('/:id', verifyToken, animalController.deleteAnimal);
/**
* @swagger
* /api/animals/stats/type:
* get:
* summary: 按类型统计动物数量
* tags: [Animals]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 成功获取动物类型统计
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* properties:
* type:
* type: string
* example: 牛
* total:
* type: integer
* example: 5000
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.get('/stats/type', (req, res) => {
// 从请求头获取token
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
message: '访问令牌缺失'
});
}
// 获取所有动物列表
router.get('/', async (req, res) => {
try {
// 验证token
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key');
const { page = 1, limit = 10, search = '' } = req.query;
const offset = (page - 1) * limit;
// 将用户信息添加到请求对象中
req.user = decoded;
// 调用控制器方法获取数据
animalController.getAnimalStatsByType(req, res);
} catch (error) {
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
message: '访问令牌无效'
});
const whereConditions = {};
if (search) {
whereConditions[Op.or] = [
{ collar_number: { [Op.like]: `%${search}%` } },
{ ear_tag: { [Op.like]: `%${search}%` } }
];
}
// 返回模拟数据
const mockStats = [
{ type: '肉牛', total: 5280 },
{ type: '奶牛', total: 2150 },
{ type: '肉羊', total: 8760 },
{ type: '奶羊', total: 1430 },
{ type: '猪', total: 12500 }
];
const { count, rows } = await Animal.findAndCountAll({
where: whereConditions,
limit: parseInt(limit),
offset: parseInt(offset),
order: [['created_at', 'DESC']]
});
res.status(200).json({
res.json({
success: true,
data: mockStats
data: rows,
total: count,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
pages: Math.ceil(count / limit)
}
});
} catch (error) {
console.error('获取动物列表失败:', error);
res.status(500).json({
success: false,
message: '获取动物列表失败: ' + error.message,
data: null
});
}
});