修改管理后台
This commit is contained in:
@@ -65,6 +65,9 @@ publicRoutes.put('/:id/status', alertController.updateAlert);
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
// 根据养殖场名称搜索预警
|
||||
router.get('/search', verifyToken, alertController.searchAlertsByFarmName);
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
// 从请求头获取token
|
||||
const authHeader = req.headers['authorization'];
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcrypt');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { User, Role, UserRole } = require('../models');
|
||||
const { User, Role, UserRole, Permission, MenuPermission } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const { verifyToken, checkRole } = require('../middleware/auth');
|
||||
const { loginAttemptsLimiter, recordLoginFailure, loginRateLimiter } = require('../middleware/security');
|
||||
const { getRolePermissions, getAccessibleMenus } = require('../config/permissions');
|
||||
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
@@ -117,6 +119,9 @@ const { body, validationResult } = require('express-validator');
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.post('/login',
|
||||
loginRateLimiter, // 登录频率限制
|
||||
loginAttemptsLimiter, // 登录失败次数限制
|
||||
recordLoginFailure, // 记录登录失败
|
||||
[
|
||||
body('username').notEmpty().withMessage('用户名不能为空'),
|
||||
body('password').isLength({ min: 6 }).withMessage('密码长度至少6位')
|
||||
@@ -145,15 +150,22 @@ router.post('/login',
|
||||
|
||||
let user;
|
||||
try {
|
||||
// 查找用户(根据用户名或邮箱)
|
||||
user = await User.findOne({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ username: username },
|
||||
{ email: username }
|
||||
]
|
||||
}
|
||||
});
|
||||
// 查找用户(根据用户名或邮箱)
|
||||
console.log('查找用户:', username);
|
||||
user = await User.findOne({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ username: username },
|
||||
{ email: username }
|
||||
]
|
||||
},
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
attributes: ['id', 'name', 'description']
|
||||
}]
|
||||
});
|
||||
console.log('查询结果:', user ? '用户找到' : '用户未找到');
|
||||
} catch (error) {
|
||||
console.log('数据库查询失败,使用测试用户数据');
|
||||
// 数据库连接失败时的测试用户数据
|
||||
@@ -182,21 +194,32 @@ router.post('/login',
|
||||
}
|
||||
|
||||
// 比较密码
|
||||
console.log('开始密码验证...');
|
||||
console.log('输入密码:', password);
|
||||
console.log('存储密码哈希:', user.password.substring(0, 30) + '...');
|
||||
|
||||
let isPasswordValid;
|
||||
if (user.password.startsWith('$2b$')) {
|
||||
if (user.password.startsWith('$2b$') || user.password.startsWith('$2a$')) {
|
||||
// 使用bcrypt比较
|
||||
console.log('使用bcrypt比较密码');
|
||||
isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
console.log('bcrypt比较结果:', isPasswordValid);
|
||||
} else {
|
||||
// 直接比较(用于测试数据)
|
||||
console.log('使用明文比较密码');
|
||||
isPasswordValid = password === user.password;
|
||||
console.log('明文比较结果:', isPasswordValid);
|
||||
}
|
||||
|
||||
if (!isPasswordValid) {
|
||||
console.log('密码验证失败');
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('密码验证成功');
|
||||
|
||||
// 生成 JWT token
|
||||
const token = jwt.sign(
|
||||
@@ -216,11 +239,43 @@ router.post('/login',
|
||||
email: user.email
|
||||
};
|
||||
|
||||
// 获取用户权限信息 - 从数据库获取实际权限
|
||||
const userWithPermissions = await User.findByPk(user.id, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
include: [{
|
||||
model: Permission,
|
||||
as: 'permissions',
|
||||
through: { attributes: [] },
|
||||
attributes: ['permission_key']
|
||||
}, {
|
||||
model: MenuPermission,
|
||||
as: 'menuPermissions',
|
||||
through: { attributes: [] },
|
||||
attributes: ['menu_key']
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
// 从数据库获取功能权限
|
||||
const userPermissions = userWithPermissions.role && userWithPermissions.role.permissions
|
||||
? userWithPermissions.role.permissions.map(p => p.permission_key)
|
||||
: [];
|
||||
|
||||
// 从数据库获取菜单权限
|
||||
const accessibleMenus = userWithPermissions.role && userWithPermissions.role.menuPermissions
|
||||
? userWithPermissions.role.menuPermissions.map(m => m.menu_key)
|
||||
: [];
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
token,
|
||||
user: userData
|
||||
user: userData,
|
||||
role: user.role,
|
||||
permissions: userPermissions,
|
||||
accessibleMenus: accessibleMenus
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
@@ -443,9 +498,8 @@ router.get('/me', async (req, res) => {
|
||||
attributes: ['id', 'username', 'email'],
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'roles', // 添加as属性,指定关联别名
|
||||
attributes: ['name', 'description'],
|
||||
through: { attributes: [] } // 不包含中间表字段
|
||||
as: 'role', // 使用正确的关联别名
|
||||
attributes: ['name', 'description']
|
||||
}]
|
||||
});
|
||||
} catch (dbError) {
|
||||
@@ -527,135 +581,7 @@ router.get('/me', async (req, res) => {
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.get('/roles', async (req, res) => {
|
||||
try {
|
||||
// 用于测试500错误的参数
|
||||
if (req.query.forceError === 'true') {
|
||||
throw new Error('强制触发服务器错误');
|
||||
}
|
||||
// 为了测试,如果请求中包含test=true参数,则返回模拟数据
|
||||
if (req.query.test === 'true') {
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
roles: [
|
||||
{ id: 0, name: 'string', description: 'string' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 为了测试403权限不足的情况
|
||||
if (req.query.testForbidden === 'true') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足'
|
||||
});
|
||||
}
|
||||
|
||||
// 从请求头获取token
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证token
|
||||
let decoded;
|
||||
try {
|
||||
decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key');
|
||||
} catch (err) {
|
||||
if (err instanceof jwt.JsonWebTokenError || err instanceof jwt.TokenExpiredError) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '未授权'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是其他错误,返回模拟数据
|
||||
return res.json({
|
||||
success: true,
|
||||
roles: [
|
||||
{ id: 1, name: 'admin', description: '管理员' },
|
||||
{ id: 2, name: 'user', description: '普通用户' },
|
||||
{ id: 3, name: 'guest', description: '访客' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户角色
|
||||
let userRoles;
|
||||
try {
|
||||
const user = await User.findByPk(decoded.id, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'roles', // 添加as属性,指定关联别名
|
||||
attributes: ['name']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户或角色不存在'
|
||||
});
|
||||
}
|
||||
|
||||
userRoles = user.roles.map(role => role.name);
|
||||
|
||||
// 检查用户是否具有admin角色
|
||||
if (!userRoles.includes('admin')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足'
|
||||
});
|
||||
}
|
||||
} catch (dbError) {
|
||||
console.log('数据库连接失败,使用模拟数据');
|
||||
// 返回模拟数据
|
||||
return res.json({
|
||||
success: true,
|
||||
roles: [
|
||||
{ id: 1, name: 'admin', description: '管理员' },
|
||||
{ id: 2, name: 'user', description: '普通用户' },
|
||||
{ id: 3, name: 'guest', description: '访客' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 获取所有角色
|
||||
let roles;
|
||||
try {
|
||||
roles = await Role.findAll({
|
||||
attributes: ['id', 'name', 'description']
|
||||
});
|
||||
} catch (dbError) {
|
||||
console.log('数据库连接失败,使用模拟数据');
|
||||
// 返回模拟数据
|
||||
return res.json({
|
||||
success: true,
|
||||
roles: [
|
||||
{ id: 1, name: 'admin', description: '管理员' },
|
||||
{ id: 2, name: 'user', description: '普通用户' },
|
||||
{ id: 3, name: 'guest', description: '访客' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
roles
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取角色列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@@ -1131,6 +1057,58 @@ router.delete('/users/:userId/roles/:roleId', async (req, res) => {
|
||||
* 401:
|
||||
* description: Token无效或已过期
|
||||
*/
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/roles:
|
||||
* get:
|
||||
* summary: 获取所有角色列表
|
||||
* tags: [Authentication]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取角色列表成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* name:
|
||||
* type: string
|
||||
* description:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.get('/roles', async (req, res) => {
|
||||
try {
|
||||
const roles = await Role.findAll({
|
||||
attributes: ['id', 'name', 'description'],
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: roles,
|
||||
roles: roles // 兼容性,同时提供两种格式
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取角色列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取角色列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/validate', verifyToken, async (req, res) => {
|
||||
try {
|
||||
// 如果能到达这里,说明token是有效的
|
||||
@@ -1138,7 +1116,7 @@ router.get('/validate', verifyToken, async (req, res) => {
|
||||
attributes: ['id', 'username', 'email', 'status'],
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'roles',
|
||||
as: 'role',
|
||||
attributes: ['id', 'name']
|
||||
}]
|
||||
});
|
||||
@@ -1150,6 +1128,10 @@ router.get('/validate', verifyToken, async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户权限信息
|
||||
const userPermissions = user.role ? getRolePermissions(user.role.name) : [];
|
||||
const accessibleMenus = getAccessibleMenus(userPermissions);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Token有效',
|
||||
@@ -1158,7 +1140,9 @@ router.get('/validate', verifyToken, async (req, res) => {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
status: user.status,
|
||||
roles: user.roles
|
||||
role: user.role,
|
||||
permissions: userPermissions,
|
||||
accessibleMenus: accessibleMenus
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
319
backend/routes/backup.js
Normal file
319
backend/routes/backup.js
Normal file
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* 备份管理路由
|
||||
* @file backup.js
|
||||
* @description 处理数据备份和恢复请求
|
||||
*/
|
||||
const express = require('express');
|
||||
const { body } = require('express-validator');
|
||||
const { verifyToken, checkRole } = require('../middleware/auth');
|
||||
const backupController = require('../controllers/backupController');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Backup
|
||||
* description: 数据备份管理相关接口
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/create:
|
||||
* post:
|
||||
* summary: 创建数据备份
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [full, daily, weekly, monthly]
|
||||
* description: 备份类型
|
||||
* default: full
|
||||
* description:
|
||||
* type: string
|
||||
* description: 备份描述
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 备份创建成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ApiResponse'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.post('/create',
|
||||
verifyToken,
|
||||
checkRole(['admin']),
|
||||
[
|
||||
body('type').optional().isIn(['full', 'daily', 'weekly', 'monthly']).withMessage('备份类型无效'),
|
||||
body('description').optional().isLength({ max: 255 }).withMessage('描述长度不能超过255字符')
|
||||
],
|
||||
backupController.createBackup
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/list:
|
||||
* get:
|
||||
* summary: 获取备份列表
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [full, daily, weekly, monthly]
|
||||
* description: 备份类型过滤
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/list', verifyToken, checkRole(['admin']), backupController.getBackupList);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/stats:
|
||||
* get:
|
||||
* summary: 获取备份统计信息
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/stats', verifyToken, checkRole(['admin']), backupController.getBackupStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/health:
|
||||
* get:
|
||||
* summary: 获取备份系统健康状态
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/health', verifyToken, checkRole(['admin']), backupController.getBackupHealth);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/{id}:
|
||||
* delete:
|
||||
* summary: 删除备份
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 备份ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 备份不存在
|
||||
*/
|
||||
router.delete('/:id', verifyToken, checkRole(['admin']), backupController.deleteBackup);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/{id}/restore:
|
||||
* post:
|
||||
* summary: 恢复数据备份
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 备份ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - confirm
|
||||
* properties:
|
||||
* confirm:
|
||||
* type: boolean
|
||||
* description: 确认恢复操作
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 恢复成功
|
||||
* 400:
|
||||
* description: 参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 恢复失败
|
||||
*/
|
||||
router.post('/:id/restore',
|
||||
verifyToken,
|
||||
checkRole(['admin']),
|
||||
[
|
||||
body('confirm').isBoolean().withMessage('confirm必须是布尔值')
|
||||
],
|
||||
backupController.restoreBackup
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/{id}/download:
|
||||
* get:
|
||||
* summary: 下载备份文件
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 备份ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 文件下载
|
||||
* content:
|
||||
* application/zip:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 备份文件不存在
|
||||
*/
|
||||
router.get('/:id/download', verifyToken, checkRole(['admin']), backupController.downloadBackup);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/cleanup:
|
||||
* post:
|
||||
* summary: 清理过期备份
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 清理成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.post('/cleanup', verifyToken, checkRole(['admin']), backupController.cleanupBackups);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/schedule/start:
|
||||
* post:
|
||||
* summary: 启动自动备份调度
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 启动成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.post('/schedule/start', verifyToken, checkRole(['admin']), backupController.startScheduler);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/schedule/stop:
|
||||
* post:
|
||||
* summary: 停止自动备份调度
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 停止成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.post('/schedule/stop', verifyToken, checkRole(['admin']), backupController.stopScheduler);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/backup/schedule/status:
|
||||
* get:
|
||||
* summary: 获取自动备份调度状态
|
||||
* tags: [Backup]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/schedule/status', verifyToken, checkRole(['admin']), backupController.getSchedulerStatus);
|
||||
|
||||
module.exports = router;
|
||||
24
backend/routes/binding.js
Normal file
24
backend/routes/binding.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 绑定信息路由
|
||||
* @file binding.js
|
||||
* @description 处理耳标与牛只档案绑定的相关路由
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const bindingController = require('../controllers/bindingController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
|
||||
// 获取耳标绑定信息
|
||||
router.get('/info/:cid', verifyToken, bindingController.getBindingInfo);
|
||||
|
||||
// 获取绑定状态统计
|
||||
router.get('/stats', verifyToken, bindingController.getBindingStats);
|
||||
|
||||
// 手动绑定耳标与牛只档案
|
||||
router.post('/bind', verifyToken, bindingController.bindCattle);
|
||||
|
||||
// 解绑耳标与牛只档案
|
||||
router.delete('/unbind/:cid', verifyToken, bindingController.unbindCattle);
|
||||
|
||||
module.exports = router;
|
||||
349
backend/routes/cattle-batches.js
Normal file
349
backend/routes/cattle-batches.js
Normal file
@@ -0,0 +1,349 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const cattleBatchController = require('../controllers/cattleBatchController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permission');
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(verifyToken);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches:
|
||||
* get:
|
||||
* summary: 获取批次列表
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [进行中, 已完成, 已暂停]
|
||||
* description: 状态筛选
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [育成批次, 繁殖批次, 育肥批次, 隔离批次, 治疗批次]
|
||||
* description: 类型筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取批次列表
|
||||
*/
|
||||
router.get('/', requirePermission('cattle:batches:view'), cattleBatchController.getBatches);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches/{id}:
|
||||
* get:
|
||||
* summary: 获取批次详情
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 批次ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取批次详情
|
||||
* 404:
|
||||
* description: 批次不存在
|
||||
*/
|
||||
router.get('/:id', requirePermission('cattle:batches:view'), cattleBatchController.getBatchById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches:
|
||||
* post:
|
||||
* summary: 创建批次
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 批次名称
|
||||
* code:
|
||||
* type: string
|
||||
* description: 批次编号
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [育成批次, 繁殖批次, 育肥批次, 隔离批次, 治疗批次]
|
||||
* description: 批次类型
|
||||
* startDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* expectedEndDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 预计结束日期
|
||||
* targetCount:
|
||||
* type: integer
|
||||
* description: 目标牛只数量
|
||||
* manager:
|
||||
* type: string
|
||||
* description: 负责人
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 成功创建批次
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/', requirePermission('cattle:batches:create'), cattleBatchController.createBatch);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches/{id}:
|
||||
* put:
|
||||
* summary: 更新批次
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 批次ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 批次名称
|
||||
* code:
|
||||
* type: string
|
||||
* description: 批次编号
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [育成批次, 繁殖批次, 育肥批次, 隔离批次, 治疗批次]
|
||||
* description: 批次类型
|
||||
* startDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* expectedEndDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 预计结束日期
|
||||
* actualEndDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 实际结束日期
|
||||
* targetCount:
|
||||
* type: integer
|
||||
* description: 目标牛只数量
|
||||
* currentCount:
|
||||
* type: integer
|
||||
* description: 当前牛只数量
|
||||
* manager:
|
||||
* type: string
|
||||
* description: 负责人
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [进行中, 已完成, 已暂停]
|
||||
* description: 状态
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功更新批次
|
||||
* 404:
|
||||
* description: 批次不存在
|
||||
*/
|
||||
router.put('/:id', requirePermission('cattle:batches:update'), cattleBatchController.updateBatch);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches/{id}:
|
||||
* delete:
|
||||
* summary: 删除批次
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 批次ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功删除批次
|
||||
* 404:
|
||||
* description: 批次不存在
|
||||
* 400:
|
||||
* description: 批次中还有牛只,无法删除
|
||||
*/
|
||||
router.delete('/:id', requirePermission('cattle:batches:delete'), cattleBatchController.deleteBatch);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches/batch-delete:
|
||||
* post:
|
||||
* summary: 批量删除批次
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* ids:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 批次ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功批量删除批次
|
||||
* 400:
|
||||
* description: 请求参数错误或批次中还有牛只
|
||||
*/
|
||||
router.post('/batch-delete', requirePermission('cattle:batches:delete'), cattleBatchController.batchDeleteBatches);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches/{id}/animals:
|
||||
* get:
|
||||
* summary: 获取批次中的牛只
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 批次ID
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取批次牛只
|
||||
* 404:
|
||||
* description: 批次不存在
|
||||
*/
|
||||
router.get('/:id/animals', requirePermission('cattle:batches:view'), cattleBatchController.getBatchAnimals);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches/{id}/animals:
|
||||
* post:
|
||||
* summary: 添加牛只到批次
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 批次ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* animalIds:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 牛只ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功添加牛只到批次
|
||||
* 404:
|
||||
* description: 批次不存在
|
||||
* 400:
|
||||
* description: 部分牛只已在该批次中
|
||||
*/
|
||||
router.post('/:id/animals', requirePermission('cattle:batches:update'), cattleBatchController.addAnimalsToBatch);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-batches/{id}/animals/{animalId}:
|
||||
* delete:
|
||||
* summary: 从批次中移除牛只
|
||||
* tags: [批次管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 批次ID
|
||||
* - in: path
|
||||
* name: animalId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 牛只ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功从批次中移除牛只
|
||||
* 404:
|
||||
* description: 牛只不在该批次中
|
||||
*/
|
||||
router.delete('/:id/animals/:animalId', requirePermission('cattle:batches:update'), cattleBatchController.removeAnimalFromBatch);
|
||||
|
||||
module.exports = router;
|
||||
290
backend/routes/cattle-exit-records.js
Normal file
290
backend/routes/cattle-exit-records.js
Normal file
@@ -0,0 +1,290 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const cattleExitRecordController = require('../controllers/cattleExitRecordController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permission');
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(verifyToken);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-exit-records:
|
||||
* get:
|
||||
* summary: 获取离栏记录列表
|
||||
* tags: [离栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: exitReason
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [出售, 死亡, 淘汰, 转场, 其他]
|
||||
* description: 离栏原因筛选
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [已确认, 待确认, 已取消]
|
||||
* description: 状态筛选
|
||||
* - in: query
|
||||
* name: dateRange
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 日期范围
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取离栏记录列表
|
||||
*/
|
||||
router.get('/', requirePermission('cattle:exit:view'), cattleExitRecordController.getExitRecords);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-exit-records/{id}:
|
||||
* get:
|
||||
* summary: 获取离栏记录详情
|
||||
* tags: [离栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 离栏记录ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取离栏记录详情
|
||||
* 404:
|
||||
* description: 离栏记录不存在
|
||||
*/
|
||||
router.get('/:id', requirePermission('cattle:exit:view'), cattleExitRecordController.getExitRecordById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-exit-records:
|
||||
* post:
|
||||
* summary: 创建离栏记录
|
||||
* tags: [离栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* animalId:
|
||||
* type: integer
|
||||
* description: 动物ID
|
||||
* exitDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 离栏日期
|
||||
* exitReason:
|
||||
* type: string
|
||||
* enum: [出售, 死亡, 淘汰, 转场, 其他]
|
||||
* description: 离栏原因
|
||||
* originalPenId:
|
||||
* type: integer
|
||||
* description: 原栏舍ID
|
||||
* destination:
|
||||
* type: string
|
||||
* description: 去向
|
||||
* disposalMethod:
|
||||
* type: string
|
||||
* enum: [屠宰, 转售, 掩埋, 焚烧, 其他]
|
||||
* description: 处理方式
|
||||
* handler:
|
||||
* type: string
|
||||
* description: 处理人员
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [已确认, 待确认, 已取消]
|
||||
* description: 状态
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 成功创建离栏记录
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/', requirePermission('cattle:exit:create'), cattleExitRecordController.createExitRecord);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-exit-records/{id}:
|
||||
* put:
|
||||
* summary: 更新离栏记录
|
||||
* tags: [离栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 离栏记录ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* exitDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 离栏日期
|
||||
* exitReason:
|
||||
* type: string
|
||||
* enum: [出售, 死亡, 淘汰, 转场, 其他]
|
||||
* description: 离栏原因
|
||||
* destination:
|
||||
* type: string
|
||||
* description: 去向
|
||||
* disposalMethod:
|
||||
* type: string
|
||||
* enum: [屠宰, 转售, 掩埋, 焚烧, 其他]
|
||||
* description: 处理方式
|
||||
* handler:
|
||||
* type: string
|
||||
* description: 处理人员
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [已确认, 待确认, 已取消]
|
||||
* description: 状态
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功更新离栏记录
|
||||
* 404:
|
||||
* description: 离栏记录不存在
|
||||
*/
|
||||
router.put('/:id', requirePermission('cattle:exit:update'), cattleExitRecordController.updateExitRecord);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-exit-records/{id}:
|
||||
* delete:
|
||||
* summary: 删除离栏记录
|
||||
* tags: [离栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 离栏记录ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功删除离栏记录
|
||||
* 404:
|
||||
* description: 离栏记录不存在
|
||||
*/
|
||||
router.delete('/:id', requirePermission('cattle:exit:delete'), cattleExitRecordController.deleteExitRecord);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-exit-records/batch-delete:
|
||||
* post:
|
||||
* summary: 批量删除离栏记录
|
||||
* tags: [离栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* ids:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 离栏记录ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功批量删除离栏记录
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/batch-delete', requirePermission('cattle:exit:delete'), cattleExitRecordController.batchDeleteExitRecords);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-exit-records/{id}/confirm:
|
||||
* post:
|
||||
* summary: 确认离栏记录
|
||||
* tags: [离栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 离栏记录ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功确认离栏记录
|
||||
* 404:
|
||||
* description: 离栏记录不存在
|
||||
* 400:
|
||||
* description: 记录已确认,无需重复操作
|
||||
*/
|
||||
router.post('/:id/confirm', requirePermission('cattle:exit:update'), cattleExitRecordController.confirmExitRecord);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-exit-records/available-animals:
|
||||
* get:
|
||||
* summary: 获取可用的牛只列表
|
||||
* tags: [离栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取可用牛只列表
|
||||
*/
|
||||
router.get('/available-animals', requirePermission('cattle:exit:view'), cattleExitRecordController.getAvailableAnimals);
|
||||
|
||||
module.exports = router;
|
||||
248
backend/routes/cattle-pens.js
Normal file
248
backend/routes/cattle-pens.js
Normal file
@@ -0,0 +1,248 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const cattlePenController = require('../controllers/cattlePenController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permission');
|
||||
|
||||
// 公开API路由,不需要验证token
|
||||
const publicRoutes = express.Router();
|
||||
router.use('/public', publicRoutes);
|
||||
|
||||
// 公开获取栏舍列表
|
||||
publicRoutes.get('/', cattlePenController.getPens);
|
||||
|
||||
// 公开获取栏舍详情
|
||||
publicRoutes.get('/:id', cattlePenController.getPenById);
|
||||
|
||||
// 所有其他路由都需要认证
|
||||
router.use(verifyToken);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-pens:
|
||||
* get:
|
||||
* summary: 获取栏舍列表
|
||||
* tags: [栏舍管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [启用, 停用]
|
||||
* description: 状态筛选
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [育成栏, 产房, 配种栏, 隔离栏, 治疗栏]
|
||||
* description: 类型筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取栏舍列表
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* list:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/CattlePen'
|
||||
* total:
|
||||
* type: integer
|
||||
* page:
|
||||
* type: integer
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
*/
|
||||
router.get('/', requirePermission('cattle:pens:view'), cattlePenController.getPens);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-pens/{id}:
|
||||
* get:
|
||||
* summary: 获取栏舍详情
|
||||
* tags: [栏舍管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 栏舍ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取栏舍详情
|
||||
* 404:
|
||||
* description: 栏舍不存在
|
||||
*/
|
||||
router.get('/:id', requirePermission('cattle:pens:view'), cattlePenController.getPenById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-pens:
|
||||
* post:
|
||||
* summary: 创建栏舍
|
||||
* tags: [栏舍管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CattlePenInput'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 成功创建栏舍
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/', requirePermission('cattle:pens:create'), cattlePenController.createPen);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-pens/{id}:
|
||||
* put:
|
||||
* summary: 更新栏舍
|
||||
* tags: [栏舍管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 栏舍ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CattlePenInput'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功更新栏舍
|
||||
* 404:
|
||||
* description: 栏舍不存在
|
||||
*/
|
||||
router.put('/:id', requirePermission('cattle:pens:update'), cattlePenController.updatePen);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-pens/{id}:
|
||||
* delete:
|
||||
* summary: 删除栏舍
|
||||
* tags: [栏舍管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 栏舍ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功删除栏舍
|
||||
* 404:
|
||||
* description: 栏舍不存在
|
||||
* 400:
|
||||
* description: 栏舍中还有牛只,无法删除
|
||||
*/
|
||||
router.delete('/:id', requirePermission('cattle:pens:delete'), cattlePenController.deletePen);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-pens/batch-delete:
|
||||
* post:
|
||||
* summary: 批量删除栏舍
|
||||
* tags: [栏舍管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* ids:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 栏舍ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功批量删除栏舍
|
||||
* 400:
|
||||
* description: 请求参数错误或栏舍中还有牛只
|
||||
*/
|
||||
router.post('/batch-delete', requirePermission('cattle:pens:delete'), cattlePenController.batchDeletePens);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-pens/{id}/animals:
|
||||
* get:
|
||||
* summary: 获取栏舍中的牛只
|
||||
* tags: [栏舍管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 栏舍ID
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取栏舍牛只
|
||||
* 404:
|
||||
* description: 栏舍不存在
|
||||
*/
|
||||
router.get('/:id/animals', requirePermission('cattle:pens:view'), cattlePenController.getPenAnimals);
|
||||
|
||||
module.exports = router;
|
||||
258
backend/routes/cattle-transfer-records.js
Normal file
258
backend/routes/cattle-transfer-records.js
Normal file
@@ -0,0 +1,258 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const cattleTransferRecordController = require('../controllers/cattleTransferRecordController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permission');
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(verifyToken);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-transfer-records:
|
||||
* get:
|
||||
* summary: 获取转栏记录列表
|
||||
* tags: [转栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: fromPen
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 转出栏舍ID
|
||||
* - in: query
|
||||
* name: toPen
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 转入栏舍ID
|
||||
* - in: query
|
||||
* name: dateRange
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 日期范围
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取转栏记录列表
|
||||
*/
|
||||
router.get('/', requirePermission('cattle:transfer:view'), cattleTransferRecordController.getTransferRecords);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-transfer-records/{id}:
|
||||
* get:
|
||||
* summary: 获取转栏记录详情
|
||||
* tags: [转栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 转栏记录ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取转栏记录详情
|
||||
* 404:
|
||||
* description: 转栏记录不存在
|
||||
*/
|
||||
router.get('/:id', requirePermission('cattle:transfer:view'), cattleTransferRecordController.getTransferRecordById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-transfer-records:
|
||||
* post:
|
||||
* summary: 创建转栏记录
|
||||
* tags: [转栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* animalId:
|
||||
* type: integer
|
||||
* description: 动物ID
|
||||
* fromPenId:
|
||||
* type: integer
|
||||
* description: 转出栏舍ID
|
||||
* toPenId:
|
||||
* type: integer
|
||||
* description: 转入栏舍ID
|
||||
* transferDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 转栏日期
|
||||
* reason:
|
||||
* type: string
|
||||
* enum: [正常调栏, 疾病治疗, 配种需要, 产房准备, 隔离观察, 其他]
|
||||
* description: 转栏原因
|
||||
* operator:
|
||||
* type: string
|
||||
* description: 操作人员
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [已完成, 进行中]
|
||||
* description: 状态
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 成功创建转栏记录
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/', requirePermission('cattle:transfer:create'), cattleTransferRecordController.createTransferRecord);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-transfer-records/{id}:
|
||||
* put:
|
||||
* summary: 更新转栏记录
|
||||
* tags: [转栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 转栏记录ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* fromPenId:
|
||||
* type: integer
|
||||
* description: 转出栏舍ID
|
||||
* toPenId:
|
||||
* type: integer
|
||||
* description: 转入栏舍ID
|
||||
* transferDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 转栏日期
|
||||
* reason:
|
||||
* type: string
|
||||
* enum: [正常调栏, 疾病治疗, 配种需要, 产房准备, 隔离观察, 其他]
|
||||
* description: 转栏原因
|
||||
* operator:
|
||||
* type: string
|
||||
* description: 操作人员
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [已完成, 进行中]
|
||||
* description: 状态
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功更新转栏记录
|
||||
* 404:
|
||||
* description: 转栏记录不存在
|
||||
*/
|
||||
router.put('/:id', requirePermission('cattle:transfer:update'), cattleTransferRecordController.updateTransferRecord);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-transfer-records/{id}:
|
||||
* delete:
|
||||
* summary: 删除转栏记录
|
||||
* tags: [转栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 转栏记录ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功删除转栏记录
|
||||
* 404:
|
||||
* description: 转栏记录不存在
|
||||
*/
|
||||
router.delete('/:id', requirePermission('cattle:transfer:delete'), cattleTransferRecordController.deleteTransferRecord);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-transfer-records/batch-delete:
|
||||
* post:
|
||||
* summary: 批量删除转栏记录
|
||||
* tags: [转栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* ids:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 转栏记录ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功批量删除转栏记录
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/batch-delete', requirePermission('cattle:transfer:delete'), cattleTransferRecordController.batchDeleteTransferRecords);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/cattle-transfer-records/available-animals:
|
||||
* get:
|
||||
* summary: 获取可用的牛只列表
|
||||
* tags: [转栏记录管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取可用牛只列表
|
||||
*/
|
||||
router.get('/available-animals', requirePermission('cattle:transfer:view'), cattleTransferRecordController.getAvailableAnimals);
|
||||
|
||||
module.exports = router;
|
||||
@@ -71,6 +71,9 @@ publicRoutes.get('/', deviceController.getAllDevices);
|
||||
*/
|
||||
router.get('/', verifyToken, deviceController.getAllDevices);
|
||||
|
||||
// 根据设备名称搜索设备
|
||||
router.get('/search', verifyToken, deviceController.searchDevicesByName);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/devices/{id}:
|
||||
|
||||
460
backend/routes/electronic-fence-points.js
Normal file
460
backend/routes/electronic-fence-points.js
Normal file
@@ -0,0 +1,460 @@
|
||||
/**
|
||||
* 电子围栏坐标点路由
|
||||
* 处理围栏坐标点的API请求
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permission');
|
||||
const electronicFencePointController = require('../controllers/electronicFencePointController');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 应用认证中间件
|
||||
router.use(verifyToken);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: ElectronicFencePoints
|
||||
* description: 电子围栏坐标点管理
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/fence/{fenceId}:
|
||||
* get:
|
||||
* summary: 获取围栏的所有坐标点
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: fenceId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* - in: query
|
||||
* name: point_type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [corner, control, marker]
|
||||
* description: 坐标点类型
|
||||
* - in: query
|
||||
* name: is_active
|
||||
* schema:
|
||||
* type: boolean
|
||||
* description: 是否激活
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.get('/fence/:fenceId',
|
||||
requirePermission('smart_fence:view'),
|
||||
electronicFencePointController.getFencePoints
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/{id}:
|
||||
* get:
|
||||
* summary: 获取坐标点详情
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 坐标点ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 404:
|
||||
* description: 坐标点不存在
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.get('/:id',
|
||||
requirePermission('smart_fence:view'),
|
||||
electronicFencePointController.getPointById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points:
|
||||
* post:
|
||||
* summary: 创建坐标点
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - fence_id
|
||||
* - point_order
|
||||
* - longitude
|
||||
* - latitude
|
||||
* properties:
|
||||
* fence_id:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* point_order:
|
||||
* type: integer
|
||||
* description: 坐标点顺序
|
||||
* longitude:
|
||||
* type: number
|
||||
* description: 经度
|
||||
* latitude:
|
||||
* type: number
|
||||
* description: 纬度
|
||||
* point_type:
|
||||
* type: string
|
||||
* enum: [corner, control, marker]
|
||||
* default: corner
|
||||
* description: 坐标点类型
|
||||
* description:
|
||||
* type: string
|
||||
* description: 坐标点描述
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.post('/',
|
||||
requirePermission('smart_fence:create'),
|
||||
electronicFencePointController.createPoint
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/batch:
|
||||
* post:
|
||||
* summary: 批量创建坐标点
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - fence_id
|
||||
* - points
|
||||
* properties:
|
||||
* fence_id:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* points:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* lng:
|
||||
* type: number
|
||||
* description: 经度
|
||||
* lat:
|
||||
* type: number
|
||||
* description: 纬度
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [corner, control, marker]
|
||||
* description: 坐标点类型
|
||||
* description:
|
||||
* type: string
|
||||
* description: 坐标点描述
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.post('/batch',
|
||||
requirePermission('smart_fence:create'),
|
||||
electronicFencePointController.createPoints
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/{id}:
|
||||
* put:
|
||||
* summary: 更新坐标点
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 坐标点ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* point_order:
|
||||
* type: integer
|
||||
* description: 坐标点顺序
|
||||
* longitude:
|
||||
* type: number
|
||||
* description: 经度
|
||||
* latitude:
|
||||
* type: number
|
||||
* description: 纬度
|
||||
* point_type:
|
||||
* type: string
|
||||
* enum: [corner, control, marker]
|
||||
* description: 坐标点类型
|
||||
* description:
|
||||
* type: string
|
||||
* description: 坐标点描述
|
||||
* is_active:
|
||||
* type: boolean
|
||||
* description: 是否激活
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 404:
|
||||
* description: 坐标点不存在
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.put('/:id',
|
||||
requirePermission('smart_fence:update'),
|
||||
electronicFencePointController.updatePoint
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/fence/{fenceId}:
|
||||
* put:
|
||||
* summary: 更新围栏的所有坐标点
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: fenceId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - points
|
||||
* properties:
|
||||
* points:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* lng:
|
||||
* type: number
|
||||
* description: 经度
|
||||
* lat:
|
||||
* type: number
|
||||
* description: 纬度
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [corner, control, marker]
|
||||
* description: 坐标点类型
|
||||
* description:
|
||||
* type: string
|
||||
* description: 坐标点描述
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.put('/fence/:fenceId',
|
||||
requirePermission('smart_fence:update'),
|
||||
electronicFencePointController.updateFencePoints
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/{id}:
|
||||
* delete:
|
||||
* summary: 删除坐标点
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 坐标点ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 404:
|
||||
* description: 坐标点不存在
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.delete('/:id',
|
||||
requirePermission('smart_fence:delete'),
|
||||
electronicFencePointController.deletePoint
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/fence/{fenceId}:
|
||||
* delete:
|
||||
* summary: 删除围栏的所有坐标点
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: fenceId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.delete('/fence/:fenceId',
|
||||
requirePermission('smart_fence:delete'),
|
||||
electronicFencePointController.deleteFencePoints
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/fence/{fenceId}/bounds:
|
||||
* get:
|
||||
* summary: 获取围栏边界框
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: fenceId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 404:
|
||||
* description: 围栏没有坐标点
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.get('/fence/:fenceId/bounds',
|
||||
requirePermission('smart_fence:view'),
|
||||
electronicFencePointController.getFenceBounds
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence-points/search:
|
||||
* get:
|
||||
* summary: 搜索坐标点
|
||||
* tags: [ElectronicFencePoints]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: fence_id
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* - in: query
|
||||
* name: point_type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [corner, control, marker]
|
||||
* description: 坐标点类型
|
||||
* - in: query
|
||||
* name: longitude_min
|
||||
* schema:
|
||||
* type: number
|
||||
* description: 最小经度
|
||||
* - in: query
|
||||
* name: longitude_max
|
||||
* schema:
|
||||
* type: number
|
||||
* description: 最大经度
|
||||
* - in: query
|
||||
* name: latitude_min
|
||||
* schema:
|
||||
* type: number
|
||||
* description: 最小纬度
|
||||
* - in: query
|
||||
* name: latitude_max
|
||||
* schema:
|
||||
* type: number
|
||||
* description: 最大纬度
|
||||
* - in: query
|
||||
* name: description
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 描述关键词
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 搜索成功
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.get('/search',
|
||||
requirePermission('smart_fence:view'),
|
||||
electronicFencePointController.searchPoints
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
390
backend/routes/electronic-fence.js
Normal file
390
backend/routes/electronic-fence.js
Normal file
@@ -0,0 +1,390 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const electronicFenceController = require('../controllers/electronicFenceController')
|
||||
const { verifyToken } = require('../middleware/auth')
|
||||
const { requirePermission } = require('../middleware/permission')
|
||||
|
||||
// 应用认证中间件
|
||||
router.use(verifyToken)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: ElectronicFence
|
||||
* description: 电子围栏管理
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence:
|
||||
* get:
|
||||
* summary: 获取围栏列表
|
||||
* tags: [ElectronicFence]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [collector, grazing, safety]
|
||||
* description: 围栏类型
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [grazing, not_grazing]
|
||||
* description: 放牧状态
|
||||
* - in: query
|
||||
* name: farm_id
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 农场ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* name:
|
||||
* type: string
|
||||
* type:
|
||||
* type: string
|
||||
* coordinates:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* lng:
|
||||
* type: number
|
||||
* lat:
|
||||
* type: number
|
||||
* grazingStatus:
|
||||
* type: string
|
||||
* insideCount:
|
||||
* type: integer
|
||||
* outsideCount:
|
||||
* type: integer
|
||||
* total:
|
||||
* type: integer
|
||||
* page:
|
||||
* type: integer
|
||||
* limit:
|
||||
* type: integer
|
||||
*/
|
||||
router.get('/',
|
||||
requirePermission('smart_fence:view'),
|
||||
electronicFenceController.getFences
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence/{id}:
|
||||
* get:
|
||||
* summary: 获取围栏详情
|
||||
* tags: [ElectronicFence]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
*/
|
||||
router.get('/:id',
|
||||
requirePermission('smart_fence:view'),
|
||||
electronicFenceController.getFenceById
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence:
|
||||
* post:
|
||||
* summary: 创建围栏
|
||||
* tags: [ElectronicFence]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* - coordinates
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 围栏名称
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [collector, grazing, safety]
|
||||
* default: collector
|
||||
* description: 围栏类型
|
||||
* description:
|
||||
* type: string
|
||||
* description: 围栏描述
|
||||
* coordinates:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* lng:
|
||||
* type: number
|
||||
* lat:
|
||||
* type: number
|
||||
* description: 围栏坐标点数组
|
||||
* farm_id:
|
||||
* type: integer
|
||||
* description: 关联农场ID
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 参数错误
|
||||
*/
|
||||
router.post('/',
|
||||
requirePermission('smart_fence:create'),
|
||||
electronicFenceController.createFence
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence/{id}:
|
||||
* put:
|
||||
* summary: 更新围栏
|
||||
* tags: [ElectronicFence]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [collector, grazing, safety]
|
||||
* description:
|
||||
* type: string
|
||||
* coordinates:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* lng:
|
||||
* type: number
|
||||
* lat:
|
||||
* type: number
|
||||
* grazing_status:
|
||||
* type: string
|
||||
* enum: [grazing, not_grazing]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
*/
|
||||
router.put('/:id',
|
||||
requirePermission('smart_fence:update'),
|
||||
electronicFenceController.updateFence
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence/{id}:
|
||||
* delete:
|
||||
* summary: 删除围栏
|
||||
* tags: [ElectronicFence]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
*/
|
||||
router.delete('/:id',
|
||||
requirePermission('smart_fence:delete'),
|
||||
electronicFenceController.deleteFence
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence/{id}/stats:
|
||||
* put:
|
||||
* summary: 更新围栏统计信息
|
||||
* tags: [ElectronicFence]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* inside_count:
|
||||
* type: integer
|
||||
* description: 安全区域内动物数量
|
||||
* outside_count:
|
||||
* type: integer
|
||||
* description: 安全区域外动物数量
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
*/
|
||||
router.put('/:id/stats',
|
||||
requirePermission('smart_fence:update'),
|
||||
electronicFenceController.updateFenceStats
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence/{id}/check-point:
|
||||
* get:
|
||||
* summary: 检查点是否在围栏内
|
||||
* tags: [ElectronicFence]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 围栏ID
|
||||
* - in: query
|
||||
* name: lng
|
||||
* required: true
|
||||
* schema:
|
||||
* type: number
|
||||
* description: 经度
|
||||
* - in: query
|
||||
* name: lat
|
||||
* required: true
|
||||
* schema:
|
||||
* type: number
|
||||
* description: 纬度
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 检查完成
|
||||
* 400:
|
||||
* description: 参数错误
|
||||
* 404:
|
||||
* description: 围栏不存在
|
||||
*/
|
||||
router.get('/:id/check-point',
|
||||
requirePermission('smart_fence:view'),
|
||||
electronicFenceController.checkPointInFence
|
||||
)
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/electronic-fence/stats/overview:
|
||||
* get:
|
||||
* summary: 获取围栏统计概览
|
||||
* tags: [ElectronicFence]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* totalFences:
|
||||
* type: integer
|
||||
* totalInside:
|
||||
* type: integer
|
||||
* totalOutside:
|
||||
* type: integer
|
||||
* byType:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* type:
|
||||
* type: string
|
||||
* count:
|
||||
* type: integer
|
||||
* total_inside:
|
||||
* type: integer
|
||||
* total_outside:
|
||||
* type: integer
|
||||
*/
|
||||
router.get('/stats/overview',
|
||||
requirePermission('smart_fence:view'),
|
||||
electronicFenceController.getFenceStats
|
||||
)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const farmController = require('../controllers/farmController');
|
||||
const searchLogger = require('../middleware/search-logger');
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@@ -25,6 +26,9 @@ const farmController = require('../controllers/farmController');
|
||||
*/
|
||||
router.get('/', farmController.getAllFarms);
|
||||
|
||||
// 根据养殖场名称搜索养殖场(添加搜索监听中间件)
|
||||
router.get('/search', searchLogger, farmController.searchFarmsByName);
|
||||
|
||||
// 公共路由必须在参数路由之前定义
|
||||
router.get('/public', farmController.getAllFarms);
|
||||
|
||||
@@ -136,7 +140,7 @@ router.delete('/:id', farmController.deleteFarm);
|
||||
* 404:
|
||||
* description: 养殖场不存在
|
||||
*/
|
||||
router.get('/:id/animals', farmController.getFarmAnimals);
|
||||
// router.get('/:id/animals', farmController.getFarmAnimals); // 暂时注释,方法不存在
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@@ -157,7 +161,7 @@ router.get('/:id/animals', farmController.getFarmAnimals);
|
||||
* 404:
|
||||
* description: 养殖场不存在
|
||||
*/
|
||||
router.get('/:id/devices', farmController.getFarmDevices);
|
||||
// router.get('/:id/devices', farmController.getFarmDevices); // 暂时注释,方法不存在
|
||||
|
||||
// 公共农场数据接口(保留兼容性)
|
||||
module.exports = router;
|
||||
24
backend/routes/formLogs.js
Normal file
24
backend/routes/formLogs.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const FormLogController = require('../controllers/formLogController')
|
||||
const { verifyToken } = require('../middleware/auth')
|
||||
|
||||
// 添加表单日志
|
||||
router.post('/add', verifyToken, (req, res) => FormLogController.addFormLog(req, res))
|
||||
|
||||
// 获取表单日志列表
|
||||
router.get('/list', verifyToken, (req, res) => FormLogController.getFormLogs(req, res))
|
||||
|
||||
// 获取日志详情
|
||||
router.get('/detail/:id', verifyToken, (req, res) => FormLogController.getFormLogDetail(req, res))
|
||||
|
||||
// 删除日志
|
||||
router.delete('/delete/:id', verifyToken, (req, res) => FormLogController.deleteFormLog(req, res))
|
||||
|
||||
// 批量删除日志
|
||||
router.delete('/batch-delete', verifyToken, (req, res) => FormLogController.batchDeleteFormLogs(req, res))
|
||||
|
||||
// 获取日志统计信息
|
||||
router.get('/stats', verifyToken, (req, res) => FormLogController.getFormLogStats(req, res))
|
||||
|
||||
module.exports = router
|
||||
94
backend/routes/iot-cattle.js
Normal file
94
backend/routes/iot-cattle.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 牛只档案路由 - 基于iot_cattle表
|
||||
*/
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const router = express.Router();
|
||||
const iotCattleController = require('../controllers/iotCattleController');
|
||||
const { verifyToken, checkRole } = require('../middleware/auth');
|
||||
|
||||
// 配置文件上传
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, 'uploads/') // 上传文件保存目录
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
// 生成唯一文件名
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + '.' + file.originalname.split('.').pop());
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024 // 限制文件大小为10MB
|
||||
},
|
||||
fileFilter: function (req, file, cb) {
|
||||
// 只允许Excel文件
|
||||
const allowedTypes = [
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-excel'
|
||||
];
|
||||
|
||||
if (allowedTypes.includes(file.mimetype)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('只允许上传Excel文件(.xlsx或.xls格式)'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 公开API路由,不需要验证token
|
||||
const publicRoutes = express.Router();
|
||||
router.use('/public', publicRoutes);
|
||||
|
||||
// 公开获取牛只档案列表
|
||||
publicRoutes.get('/', iotCattleController.getCattleArchives);
|
||||
|
||||
// 公开获取单个牛只档案详情
|
||||
publicRoutes.get('/:id', iotCattleController.getCattleArchiveById);
|
||||
|
||||
// 公开下载导入模板
|
||||
publicRoutes.get('/import/template', iotCattleController.downloadImportTemplate);
|
||||
|
||||
// 公开获取栏舍列表
|
||||
publicRoutes.get('/pens/list', iotCattleController.getPens);
|
||||
|
||||
// 公开获取批次列表
|
||||
publicRoutes.get('/batches/list', iotCattleController.getBatches);
|
||||
|
||||
// 获取牛只档案列表
|
||||
router.get('/', verifyToken, iotCattleController.getCattleArchives);
|
||||
|
||||
// 获取单个牛只档案详情
|
||||
router.get('/:id', verifyToken, iotCattleController.getCattleArchiveById);
|
||||
|
||||
// 创建牛只档案
|
||||
router.post('/', verifyToken, checkRole(['admin', 'manager']), iotCattleController.createCattleArchive);
|
||||
|
||||
// 更新牛只档案
|
||||
router.put('/:id', verifyToken, checkRole(['admin', 'manager']), iotCattleController.updateCattleArchive);
|
||||
|
||||
// 删除牛只档案
|
||||
router.delete('/:id', verifyToken, checkRole(['admin', 'manager']), iotCattleController.deleteCattleArchive);
|
||||
|
||||
// 批量删除牛只档案
|
||||
router.delete('/', verifyToken, checkRole(['admin', 'manager']), iotCattleController.batchDeleteCattleArchives);
|
||||
|
||||
// 获取农场列表
|
||||
router.get('/farms/list', verifyToken, iotCattleController.getFarms);
|
||||
|
||||
// 获取栏舍列表
|
||||
router.get('/pens/list', verifyToken, iotCattleController.getPens);
|
||||
|
||||
// 获取批次列表
|
||||
router.get('/batches/list', verifyToken, iotCattleController.getBatches);
|
||||
|
||||
// 导入牛只档案数据
|
||||
router.post('/import', verifyToken, checkRole(['admin', 'manager']), upload.single('file'), iotCattleController.importCattleArchives);
|
||||
|
||||
// 下载导入模板
|
||||
router.get('/import/template', verifyToken, iotCattleController.downloadImportTemplate);
|
||||
|
||||
module.exports = router;
|
||||
317
backend/routes/menus.js
Normal file
317
backend/routes/menus.js
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* 菜单管理路由
|
||||
* @file menus.js
|
||||
* @description 定义菜单管理相关的API路由
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const menuController = require('../controllers/menuController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permission');
|
||||
|
||||
// 公开API路由,不需要验证token
|
||||
const publicRoutes = express.Router();
|
||||
router.use('/public', publicRoutes);
|
||||
|
||||
// 公开获取角色菜单权限(用于权限管理)
|
||||
publicRoutes.get('/role/:roleId', menuController.getRoleMenus);
|
||||
|
||||
// 所有其他路由都需要认证
|
||||
router.use(verifyToken);
|
||||
|
||||
// 需要认证的菜单API
|
||||
router.get('/', menuController.getAllMenus);
|
||||
router.get('/:id', menuController.getMenuById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* MenuPermission:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 菜单ID
|
||||
* name:
|
||||
* type: string
|
||||
* description: 菜单名称
|
||||
* path:
|
||||
* type: string
|
||||
* description: 菜单路径
|
||||
* component:
|
||||
* type: string
|
||||
* description: 组件路径
|
||||
* icon:
|
||||
* type: string
|
||||
* description: 图标
|
||||
* parent_id:
|
||||
* type: integer
|
||||
* description: 父菜单ID
|
||||
* sort_order:
|
||||
* type: integer
|
||||
* description: 排序
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 状态
|
||||
* description:
|
||||
* type: string
|
||||
* description: 描述
|
||||
* permission_code:
|
||||
* type: string
|
||||
* description: 权限代码
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
* updated_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 更新时间
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus:
|
||||
* get:
|
||||
* summary: 获取菜单列表
|
||||
* tags: [Menus]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: roleId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID(可选)
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取菜单列表
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/MenuPermission'
|
||||
* message:
|
||||
* type: string
|
||||
*/
|
||||
router.get('/', requirePermission('menu:view'), menuController.getAllMenus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus/{id}:
|
||||
* get:
|
||||
* summary: 获取菜单详情
|
||||
* tags: [Menus]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 菜单ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取菜单详情
|
||||
* 404:
|
||||
* description: 菜单不存在
|
||||
*/
|
||||
router.get('/:id', requirePermission('menu:view'), menuController.getMenuById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus:
|
||||
* post:
|
||||
* summary: 创建菜单
|
||||
* tags: [Menus]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* - path
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 菜单名称
|
||||
* path:
|
||||
* type: string
|
||||
* description: 菜单路径
|
||||
* component:
|
||||
* type: string
|
||||
* description: 组件路径
|
||||
* icon:
|
||||
* type: string
|
||||
* description: 图标
|
||||
* parent_id:
|
||||
* type: integer
|
||||
* description: 父菜单ID
|
||||
* sort_order:
|
||||
* type: integer
|
||||
* description: 排序
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 状态
|
||||
* description:
|
||||
* type: string
|
||||
* description: 描述
|
||||
* permission_code:
|
||||
* type: string
|
||||
* description: 权限代码
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 菜单创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/', requirePermission('menu:create'), menuController.createMenu);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus/{id}:
|
||||
* put:
|
||||
* summary: 更新菜单
|
||||
* tags: [Menus]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 菜单ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 菜单名称
|
||||
* path:
|
||||
* type: string
|
||||
* description: 菜单路径
|
||||
* component:
|
||||
* type: string
|
||||
* description: 组件路径
|
||||
* icon:
|
||||
* type: string
|
||||
* description: 图标
|
||||
* parent_id:
|
||||
* type: integer
|
||||
* description: 父菜单ID
|
||||
* sort_order:
|
||||
* type: integer
|
||||
* description: 排序
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 状态
|
||||
* description:
|
||||
* type: string
|
||||
* description: 描述
|
||||
* permission_code:
|
||||
* type: string
|
||||
* description: 权限代码
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 菜单更新成功
|
||||
* 404:
|
||||
* description: 菜单不存在
|
||||
*/
|
||||
router.put('/:id', requirePermission('menu:update'), menuController.updateMenu);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus/{id}:
|
||||
* delete:
|
||||
* summary: 删除菜单
|
||||
* tags: [Menus]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 菜单ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 菜单删除成功
|
||||
* 404:
|
||||
* description: 菜单不存在
|
||||
*/
|
||||
router.delete('/:id', requirePermission('menu:delete'), menuController.deleteMenu);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus/role/{roleId}:
|
||||
* get:
|
||||
* summary: 获取角色的菜单权限
|
||||
* tags: [Menus]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: roleId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取角色菜单权限
|
||||
* 404:
|
||||
* description: 角色不存在
|
||||
*/
|
||||
router.get('/role/:roleId', requirePermission('menu:view'), menuController.getRoleMenus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/menus/role/{roleId}:
|
||||
* post:
|
||||
* summary: 设置角色的菜单权限
|
||||
* tags: [Menus]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: roleId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* menuIds:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 菜单ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 设置角色菜单权限成功
|
||||
* 404:
|
||||
* description: 角色不存在
|
||||
*/
|
||||
router.post('/role/:roleId', requirePermission('menu:assign'), menuController.setRoleMenus);
|
||||
|
||||
module.exports = router;
|
||||
586
backend/routes/operationLogs-backup.js
Normal file
586
backend/routes/operationLogs-backup.js
Normal file
@@ -0,0 +1,586 @@
|
||||
/**
|
||||
* 操作日志路由
|
||||
* @file operationLogs.js
|
||||
* @description 定义操作日志相关的API路由
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, query, param } = require('express-validator');
|
||||
const operationLogController = require('../controllers/operationLogController');
|
||||
const auth = require('../middleware/auth');
|
||||
const { hasPermission } = require('../config/permissions');
|
||||
|
||||
// 验证中间件
|
||||
const validateRequest = (req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* OperationLog:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 主键ID
|
||||
* user_id:
|
||||
* type: integer
|
||||
* description: 操作用户ID
|
||||
* username:
|
||||
* type: string
|
||||
* description: 操作用户名
|
||||
* user_role:
|
||||
* type: string
|
||||
* description: 操作用户角色
|
||||
* operation_type:
|
||||
* type: string
|
||||
* enum: [CREATE, UPDATE, DELETE]
|
||||
* description: 操作类型
|
||||
* module_name:
|
||||
* type: string
|
||||
* description: 操作模块名称
|
||||
* table_name:
|
||||
* type: string
|
||||
* description: 操作的数据表名
|
||||
* record_id:
|
||||
* type: integer
|
||||
* description: 操作的记录ID
|
||||
* operation_desc:
|
||||
* type: string
|
||||
* description: 操作描述
|
||||
* old_data:
|
||||
* type: object
|
||||
* description: 操作前的数据
|
||||
* new_data:
|
||||
* type: object
|
||||
* description: 操作后的数据
|
||||
* ip_address:
|
||||
* type: string
|
||||
* description: 操作IP地址
|
||||
* user_agent:
|
||||
* type: string
|
||||
* description: 用户代理信息
|
||||
* request_url:
|
||||
* type: string
|
||||
* description: 请求URL
|
||||
* request_method:
|
||||
* type: string
|
||||
* description: 请求方法
|
||||
* response_status:
|
||||
* type: integer
|
||||
* description: 响应状态码
|
||||
* execution_time:
|
||||
* type: integer
|
||||
* description: 执行时间(毫秒)
|
||||
* error_message:
|
||||
* type: string
|
||||
* description: 错误信息
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs:
|
||||
* get:
|
||||
* summary: 获取操作日志列表
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 20
|
||||
* description: 每页记录数
|
||||
* - in: query
|
||||
* name: userId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 用户ID
|
||||
* - in: query
|
||||
* name: username
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* - in: query
|
||||
* name: operationType
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [CREATE, UPDATE, DELETE]
|
||||
* description: 操作类型
|
||||
* - in: query
|
||||
* name: moduleName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 模块名称
|
||||
* - in: query
|
||||
* name: tableName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 数据表名
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* - in: query
|
||||
* name: sortBy
|
||||
* schema:
|
||||
* type: string
|
||||
* default: created_at
|
||||
* description: 排序字段
|
||||
* - in: query
|
||||
* name: sortOrder
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [ASC, DESC]
|
||||
* default: DESC
|
||||
* description: 排序方向
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志列表成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/OperationLog'
|
||||
* pagination:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: integer
|
||||
* page:
|
||||
* type: integer
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* hasMore:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法访问操作日志'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
[
|
||||
query('page').optional().isInt({ min: 1 }).withMessage('页码必须是正整数'),
|
||||
query('pageSize').optional().isInt({ min: 1, max: 100 }).withMessage('每页记录数必须是1-100之间的整数'),
|
||||
query('userId').optional().isInt({ min: 1 }).withMessage('用户ID必须是正整数'),
|
||||
query('username').optional().isLength({ min: 1, max: 50 }).withMessage('用户名长度必须在1-50个字符之间'),
|
||||
query('operationType').optional().isIn(['CREATE', 'UPDATE', 'DELETE']).withMessage('操作类型必须是CREATE、UPDATE或DELETE'),
|
||||
query('moduleName').optional().isLength({ min: 1, max: 100 }).withMessage('模块名称长度必须在1-100个字符之间'),
|
||||
query('tableName').optional().isLength({ min: 1, max: 100 }).withMessage('数据表名长度必须在1-100个字符之间'),
|
||||
query('startDate').optional().isISO8601().withMessage('开始日期格式不正确'),
|
||||
query('endDate').optional().isISO8601().withMessage('结束日期格式不正确'),
|
||||
query('sortBy').optional().isIn(['id', 'user_id', 'username', 'operation_type', 'module_name', 'table_name', 'created_at']).withMessage('排序字段无效'),
|
||||
query('sortOrder').optional().isIn(['ASC', 'DESC']).withMessage('排序方向必须是ASC或DESC')
|
||||
],
|
||||
validateRequest,
|
||||
operationLogController.getOperationLogs
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/{id}:
|
||||
* get:
|
||||
* summary: 获取操作日志详情
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 操作日志ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志详情成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* $ref: '#/components/schemas/OperationLog'
|
||||
* message:
|
||||
* type: string
|
||||
* 404:
|
||||
* description: 操作日志不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/:id',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法访问操作日志'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
[
|
||||
param('id').isInt({ min: 1 }).withMessage('操作日志ID必须是正整数')
|
||||
],
|
||||
validateRequest,
|
||||
operationLogController.getOperationLogById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/stats/overview:
|
||||
* get:
|
||||
* summary: 获取操作统计
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [user, module, overall]
|
||||
* default: overall
|
||||
* description: 统计类型
|
||||
* - in: query
|
||||
* name: userId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 用户ID(type为user时必填)
|
||||
* - in: query
|
||||
* name: moduleName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 模块名称(type为module时必填)
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作统计成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* CREATE:
|
||||
* type: integer
|
||||
* UPDATE:
|
||||
* type: integer
|
||||
* DELETE:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/overview',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法访问操作统计'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
[
|
||||
query('type').optional().isIn(['user', 'module', 'overall']).withMessage('统计类型必须是user、module或overall'),
|
||||
query('userId').optional().isInt({ min: 1 }).withMessage('用户ID必须是正整数'),
|
||||
query('moduleName').optional().isLength({ min: 1, max: 100 }).withMessage('模块名称长度必须在1-100个字符之间'),
|
||||
query('startDate').optional().isISO8601().withMessage('开始日期格式不正确'),
|
||||
query('endDate').optional().isISO8601().withMessage('结束日期格式不正确')
|
||||
],
|
||||
validateRequest,
|
||||
operationLogController.getOperationStats
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/stats/chart:
|
||||
* get:
|
||||
* summary: 获取操作日志图表数据
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [hourly, daily, monthly]
|
||||
* default: daily
|
||||
* description: 图表类型
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志图表数据成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* dates:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* series:
|
||||
* type: object
|
||||
* properties:
|
||||
* CREATE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* UPDATE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* DELETE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/chart',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法访问操作统计'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
[
|
||||
query('type').optional().isIn(['hourly', 'daily', 'monthly']).withMessage('图表类型必须是hourly、daily或monthly'),
|
||||
query('startDate').optional().isISO8601().withMessage('开始日期格式不正确'),
|
||||
query('endDate').optional().isISO8601().withMessage('结束日期格式不正确')
|
||||
],
|
||||
validateRequest,
|
||||
operationLogController.getOperationChartData
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/clean:
|
||||
* post:
|
||||
* summary: 清理过期日志
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* daysToKeep:
|
||||
* type: integer
|
||||
* default: 90
|
||||
* description: 保留天数
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 清理过期日志成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* deletedCount:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/clean',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法清理操作日志'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
[
|
||||
body('daysToKeep').optional().isInt({ min: 1, max: 365 }).withMessage('保留天数必须是1-365之间的整数')
|
||||
],
|
||||
validateRequest,
|
||||
operationLogController.cleanExpiredLogs
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/export:
|
||||
* get:
|
||||
* summary: 导出操作日志
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: userId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 用户ID
|
||||
* - in: query
|
||||
* name: username
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* - in: query
|
||||
* name: operationType
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [CREATE, UPDATE, DELETE]
|
||||
* description: 操作类型
|
||||
* - in: query
|
||||
* name: moduleName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 模块名称
|
||||
* - in: query
|
||||
* name: tableName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 数据表名
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出操作日志成功
|
||||
* content:
|
||||
* text/csv:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/export',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法导出操作日志'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
[
|
||||
query('userId').optional().isInt({ min: 1 }).withMessage('用户ID必须是正整数'),
|
||||
query('username').optional().isLength({ min: 1, max: 50 }).withMessage('用户名长度必须在1-50个字符之间'),
|
||||
query('operationType').optional().isIn(['CREATE', 'UPDATE', 'DELETE']).withMessage('操作类型必须是CREATE、UPDATE或DELETE'),
|
||||
query('moduleName').optional().isLength({ min: 1, max: 100 }).withMessage('模块名称长度必须在1-100个字符之间'),
|
||||
query('tableName').optional().isLength({ min: 1, max: 100 }).withMessage('数据表名长度必须在1-100个字符之间'),
|
||||
query('startDate').optional().isISO8601().withMessage('开始日期格式不正确'),
|
||||
query('endDate').optional().isISO8601().withMessage('结束日期格式不正确')
|
||||
],
|
||||
validateRequest,
|
||||
operationLogController.exportOperationLogs
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
540
backend/routes/operationLogs-fixed.js
Normal file
540
backend/routes/operationLogs-fixed.js
Normal file
@@ -0,0 +1,540 @@
|
||||
/**
|
||||
* 操作日志路由
|
||||
* @file operationLogs.js
|
||||
* @description 定义操作日志相关的API路由
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, query, param } = require('express-validator');
|
||||
const operationLogController = require('../controllers/operationLogController');
|
||||
const auth = require('../middleware/auth');
|
||||
const { hasPermission } = require('../config/permissions');
|
||||
|
||||
// 验证中间件
|
||||
const validateRequest = (req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* OperationLog:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 主键ID
|
||||
* user_id:
|
||||
* type: integer
|
||||
* description: 操作用户ID
|
||||
* username:
|
||||
* type: string
|
||||
* description: 操作用户名
|
||||
* user_role:
|
||||
* type: string
|
||||
* description: 操作用户角色
|
||||
* operation_type:
|
||||
* type: string
|
||||
* enum: [CREATE, UPDATE, DELETE]
|
||||
* description: 操作类型
|
||||
* module_name:
|
||||
* type: string
|
||||
* description: 操作模块名称
|
||||
* table_name:
|
||||
* type: string
|
||||
* description: 操作的数据表名
|
||||
* record_id:
|
||||
* type: integer
|
||||
* description: 操作的记录ID
|
||||
* operation_desc:
|
||||
* type: string
|
||||
* description: 操作描述
|
||||
* old_data:
|
||||
* type: object
|
||||
* description: 操作前的数据
|
||||
* new_data:
|
||||
* type: object
|
||||
* description: 操作后的数据
|
||||
* ip_address:
|
||||
* type: string
|
||||
* description: 操作IP地址
|
||||
* user_agent:
|
||||
* type: string
|
||||
* description: 用户代理信息
|
||||
* request_url:
|
||||
* type: string
|
||||
* description: 请求URL
|
||||
* request_method:
|
||||
* type: string
|
||||
* description: 请求方法
|
||||
* response_status:
|
||||
* type: integer
|
||||
* description: 响应状态码
|
||||
* execution_time:
|
||||
* type: integer
|
||||
* description: 执行时间(毫秒)
|
||||
* error_message:
|
||||
* type: string
|
||||
* description: 错误信息
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs:
|
||||
* get:
|
||||
* summary: 获取操作日志列表
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 20
|
||||
* description: 每页记录数
|
||||
* - in: query
|
||||
* name: userId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 用户ID
|
||||
* - in: query
|
||||
* name: username
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* - in: query
|
||||
* name: operationType
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [CREATE, UPDATE, DELETE]
|
||||
* description: 操作类型
|
||||
* - in: query
|
||||
* name: moduleName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 模块名称
|
||||
* - in: query
|
||||
* name: tableName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 数据表名
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* - in: query
|
||||
* name: sortBy
|
||||
* schema:
|
||||
* type: string
|
||||
* default: created_at
|
||||
* description: 排序字段
|
||||
* - in: query
|
||||
* name: sortOrder
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [ASC, DESC]
|
||||
* default: DESC
|
||||
* description: 排序方向
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志列表成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/OperationLog'
|
||||
* pagination:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: integer
|
||||
* page:
|
||||
* type: integer
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* hasMore:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法访问操作日志'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
operationLogController.getOperationLogs
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/{id}:
|
||||
* get:
|
||||
* summary: 获取操作日志详情
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 操作日志ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志详情成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* $ref: '#/components/schemas/OperationLog'
|
||||
* message:
|
||||
* type: string
|
||||
* 404:
|
||||
* description: 操作日志不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/:id',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法访问操作日志'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
operationLogController.getOperationLogById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/stats/overview:
|
||||
* get:
|
||||
* summary: 获取操作统计
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [user, module, overall]
|
||||
* default: overall
|
||||
* description: 统计类型
|
||||
* - in: query
|
||||
* name: userId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 用户ID(type为user时必填)
|
||||
* - in: query
|
||||
* name: moduleName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 模块名称(type为module时必填)
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作统计成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* CREATE:
|
||||
* type: integer
|
||||
* UPDATE:
|
||||
* type: integer
|
||||
* DELETE:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/overview',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法访问操作统计'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
operationLogController.getOperationStats
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/stats/chart:
|
||||
* get:
|
||||
* summary: 获取操作日志图表数据
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [hourly, daily, monthly]
|
||||
* default: daily
|
||||
* description: 图表类型
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志图表数据成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* dates:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* series:
|
||||
* type: object
|
||||
* properties:
|
||||
* CREATE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* UPDATE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* DELETE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/chart',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法访问操作统计'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
operationLogController.getOperationChartData
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/clean:
|
||||
* post:
|
||||
* summary: 清理过期日志
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* daysToKeep:
|
||||
* type: integer
|
||||
* default: 90
|
||||
* description: 保留天数
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 清理过期日志成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* deletedCount:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/clean',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法清理操作日志'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
operationLogController.cleanExpiredLogs
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/export:
|
||||
* get:
|
||||
* summary: 导出操作日志
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: userId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 用户ID
|
||||
* - in: query
|
||||
* name: username
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* - in: query
|
||||
* name: operationType
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [CREATE, UPDATE, DELETE]
|
||||
* description: 操作类型
|
||||
* - in: query
|
||||
* name: moduleName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 模块名称
|
||||
* - in: query
|
||||
* name: tableName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 数据表名
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出操作日志成功
|
||||
* content:
|
||||
* text/csv:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/export',
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
if (!hasPermission(req.user.permissions, 'operation_log:view')) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足,无法导出操作日志'
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
operationLogController.exportOperationLogs
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
446
backend/routes/operationLogs.js
Normal file
446
backend/routes/operationLogs.js
Normal file
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* 操作日志路由
|
||||
* @file operationLogs.js
|
||||
* @description 定义操作日志相关的API路由
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, query, param, validationResult } = require('express-validator');
|
||||
const operationLogController = require('../controllers/operationLogController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { checkOperationLogPermission } = require('../middleware/operationLogAuth');
|
||||
|
||||
// 验证中间件
|
||||
const validateRequest = (req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* OperationLog:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 主键ID
|
||||
* user_id:
|
||||
* type: integer
|
||||
* description: 操作用户ID
|
||||
* username:
|
||||
* type: string
|
||||
* description: 操作用户名
|
||||
* user_role:
|
||||
* type: string
|
||||
* description: 操作用户角色
|
||||
* operation_type:
|
||||
* type: string
|
||||
* enum: [CREATE, UPDATE, DELETE]
|
||||
* description: 操作类型
|
||||
* module_name:
|
||||
* type: string
|
||||
* description: 操作模块名称
|
||||
* table_name:
|
||||
* type: string
|
||||
* description: 操作的数据表名
|
||||
* record_id:
|
||||
* type: integer
|
||||
* description: 操作的记录ID
|
||||
* operation_desc:
|
||||
* type: string
|
||||
* description: 操作描述
|
||||
* old_data:
|
||||
* type: object
|
||||
* description: 操作前的数据
|
||||
* new_data:
|
||||
* type: object
|
||||
* description: 操作后的数据
|
||||
* ip_address:
|
||||
* type: string
|
||||
* description: 操作IP地址
|
||||
* user_agent:
|
||||
* type: string
|
||||
* description: 用户代理信息
|
||||
* request_url:
|
||||
* type: string
|
||||
* description: 请求URL
|
||||
* request_method:
|
||||
* type: string
|
||||
* description: 请求方法
|
||||
* response_status:
|
||||
* type: integer
|
||||
* description: 响应状态码
|
||||
* execution_time:
|
||||
* type: integer
|
||||
* description: 执行时间(毫秒)
|
||||
* error_message:
|
||||
* type: string
|
||||
* description: 错误信息
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs:
|
||||
* get:
|
||||
* summary: 获取操作日志列表
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 20
|
||||
* description: 每页记录数
|
||||
* - in: query
|
||||
* name: userId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 用户ID
|
||||
* - in: query
|
||||
* name: username
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* - in: query
|
||||
* name: operationType
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [CREATE, UPDATE, DELETE]
|
||||
* description: 操作类型
|
||||
* - in: query
|
||||
* name: moduleName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 模块名称
|
||||
* - in: query
|
||||
* name: tableName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 数据表名
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* - in: query
|
||||
* name: sortBy
|
||||
* schema:
|
||||
* type: string
|
||||
* default: created_at
|
||||
* description: 排序字段
|
||||
* - in: query
|
||||
* name: sortOrder
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [ASC, DESC]
|
||||
* default: DESC
|
||||
* description: 排序方向
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志列表成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/OperationLog'
|
||||
* pagination:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: integer
|
||||
* page:
|
||||
* type: integer
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* hasMore:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/',
|
||||
verifyToken,
|
||||
checkOperationLogPermission,
|
||||
query('page').optional().isInt({ min: 1 }).withMessage('页码必须是正整数'),
|
||||
query('pageSize').optional().isInt({ min: 1, max: 100 }).withMessage('每页记录数必须是1-100之间的整数'),
|
||||
query('username').optional().isLength({ min: 0, max: 50 }).withMessage('用户名长度必须在0-50个字符之间'),
|
||||
query('operationType').optional().isIn(['CREATE', 'UPDATE', 'DELETE']).withMessage('操作类型必须是CREATE、UPDATE或DELETE'),
|
||||
query('moduleName').optional().isLength({ min: 1, max: 100 }).withMessage('模块名称长度必须在1-100个字符之间'),
|
||||
query('tableName').optional().isLength({ min: 1, max: 100 }).withMessage('数据表名长度必须在1-100个字符之间'),
|
||||
query('startDate').optional().isISO8601().withMessage('开始日期格式不正确'),
|
||||
query('endDate').optional().isISO8601().withMessage('结束日期格式不正确'),
|
||||
query('sortBy').optional().isIn(['id', 'user_id', 'username', 'operation_type', 'module_name', 'table_name', 'created_at']).withMessage('排序字段无效'),
|
||||
query('sortOrder').optional().isIn(['ASC', 'DESC']).withMessage('排序方向必须是ASC或DESC'),
|
||||
validateRequest,
|
||||
operationLogController.getOperationLogs
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/{id}:
|
||||
* get:
|
||||
* summary: 获取操作日志详情
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 操作日志ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志详情成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* $ref: '#/components/schemas/OperationLog'
|
||||
* message:
|
||||
* type: string
|
||||
* 404:
|
||||
* description: 操作日志不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/export',
|
||||
verifyToken,
|
||||
checkOperationLogPermission,
|
||||
operationLogController.exportOperationLogs
|
||||
);
|
||||
|
||||
router.get('/:id',
|
||||
verifyToken,
|
||||
checkOperationLogPermission,
|
||||
operationLogController.getOperationLogById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/stats/overview:
|
||||
* get:
|
||||
* summary: 获取操作统计
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [user, module, overall]
|
||||
* default: overall
|
||||
* description: 统计类型
|
||||
* - in: query
|
||||
* name: userId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 用户ID(type为user时必填)
|
||||
* - in: query
|
||||
* name: moduleName
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 模块名称(type为module时必填)
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作统计成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* CREATE:
|
||||
* type: integer
|
||||
* UPDATE:
|
||||
* type: integer
|
||||
* DELETE:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/overview',
|
||||
verifyToken,
|
||||
checkOperationLogPermission,
|
||||
operationLogController.getOperationStats
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/stats/chart:
|
||||
* get:
|
||||
* summary: 获取操作日志图表数据
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [hourly, daily, monthly]
|
||||
* default: daily
|
||||
* description: 图表类型
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取操作日志图表数据成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* dates:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* series:
|
||||
* type: object
|
||||
* properties:
|
||||
* CREATE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* UPDATE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* DELETE:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/chart',
|
||||
verifyToken,
|
||||
checkOperationLogPermission,
|
||||
operationLogController.getOperationChartData
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/operation-logs/clean:
|
||||
* post:
|
||||
* summary: 清理过期日志
|
||||
* tags: [OperationLogs]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* daysToKeep:
|
||||
* type: integer
|
||||
* default: 90
|
||||
* description: 保留天数
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 清理过期日志成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* deletedCount:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/clean',
|
||||
verifyToken,
|
||||
checkOperationLogPermission,
|
||||
operationLogController.cleanExpiredLogs
|
||||
);
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -69,6 +69,9 @@ const orderController = require('../controllers/orderController');
|
||||
// 获取所有订单
|
||||
router.get('/', orderController.getAllOrders);
|
||||
|
||||
// 根据用户名搜索订单
|
||||
router.get('/search', orderController.searchOrdersByUsername);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/orders/{id}:
|
||||
|
||||
352
backend/routes/pens.js
Normal file
352
backend/routes/pens.js
Normal file
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* 栏舍管理路由
|
||||
* @file pens.js
|
||||
* @description 定义栏舍管理相关的API路由
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const penController = require('../controllers/penController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
|
||||
// 公开API路由,不需要验证token
|
||||
const publicRoutes = express.Router();
|
||||
router.use('/public', publicRoutes);
|
||||
|
||||
// 公开获取栏舍列表
|
||||
publicRoutes.get('/', penController.getPens);
|
||||
|
||||
// 公开获取栏舍详情
|
||||
publicRoutes.get('/:id', penController.getPenById);
|
||||
|
||||
// 公开获取栏舍统计信息
|
||||
publicRoutes.get('/stats/summary', penController.getPenStats);
|
||||
|
||||
// 需要认证的路由
|
||||
router.use(verifyToken);
|
||||
|
||||
// 创建栏舍
|
||||
router.post('/', penController.createPen);
|
||||
|
||||
// 更新栏舍
|
||||
router.put('/:id', penController.updatePen);
|
||||
|
||||
// 删除栏舍
|
||||
router.delete('/:id', penController.deletePen);
|
||||
|
||||
// 批量删除栏舍
|
||||
router.delete('/batch', penController.batchDeletePens);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Pens
|
||||
* description: 栏舍管理API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/pens/public:
|
||||
* get:
|
||||
* summary: 获取栏舍列表
|
||||
* tags: [Pens]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: animalType
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [马, 牛, 羊, 家禽, 猪]
|
||||
* description: 动物类型
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [true, false]
|
||||
* description: 状态
|
||||
* - in: query
|
||||
* name: farmId
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 农场ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取栏舍列表
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* list:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Pen'
|
||||
* pagination:
|
||||
* $ref: '#/components/schemas/Pagination'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/pens/public/{id}:
|
||||
* get:
|
||||
* summary: 获取栏舍详情
|
||||
* tags: [Pens]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 栏舍ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取栏舍详情
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* $ref: '#/components/schemas/Pen'
|
||||
* 404:
|
||||
* description: 栏舍不存在
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/pens:
|
||||
* post:
|
||||
* summary: 创建栏舍
|
||||
* tags: [Pens]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* - animal_type
|
||||
* - responsible
|
||||
* - capacity
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 栏舍名称
|
||||
* animal_type:
|
||||
* type: string
|
||||
* enum: [马, 牛, 羊, 家禽, 猪]
|
||||
* description: 动物类型
|
||||
* pen_type:
|
||||
* type: string
|
||||
* description: 栏舍类型
|
||||
* responsible:
|
||||
* type: string
|
||||
* description: 负责人
|
||||
* capacity:
|
||||
* type: integer
|
||||
* description: 容量
|
||||
* status:
|
||||
* type: boolean
|
||||
* default: true
|
||||
* description: 状态
|
||||
* description:
|
||||
* type: string
|
||||
* description: 备注信息
|
||||
* farm_id:
|
||||
* type: integer
|
||||
* description: 所属农场ID
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 栏舍创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/pens/{id}:
|
||||
* put:
|
||||
* summary: 更新栏舍
|
||||
* tags: [Pens]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 栏舍ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 栏舍名称
|
||||
* animal_type:
|
||||
* type: string
|
||||
* enum: [马, 牛, 羊, 家禽, 猪]
|
||||
* description: 动物类型
|
||||
* pen_type:
|
||||
* type: string
|
||||
* description: 栏舍类型
|
||||
* responsible:
|
||||
* type: string
|
||||
* description: 负责人
|
||||
* capacity:
|
||||
* type: integer
|
||||
* description: 容量
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 状态
|
||||
* description:
|
||||
* type: string
|
||||
* description: 备注信息
|
||||
* farm_id:
|
||||
* type: integer
|
||||
* description: 所属农场ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 栏舍更新成功
|
||||
* 404:
|
||||
* description: 栏舍不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/pens/{id}:
|
||||
* delete:
|
||||
* summary: 删除栏舍
|
||||
* tags: [Pens]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 栏舍ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 栏舍删除成功
|
||||
* 404:
|
||||
* description: 栏舍不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/pens/batch:
|
||||
* delete:
|
||||
* summary: 批量删除栏舍
|
||||
* tags: [Pens]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* ids:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 栏舍ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 批量删除成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Pen:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 栏舍ID
|
||||
* name:
|
||||
* type: string
|
||||
* description: 栏舍名称
|
||||
* animal_type:
|
||||
* type: string
|
||||
* enum: [马, 牛, 羊, 家禽, 猪]
|
||||
* description: 动物类型
|
||||
* pen_type:
|
||||
* type: string
|
||||
* description: 栏舍类型
|
||||
* responsible:
|
||||
* type: string
|
||||
* description: 负责人
|
||||
* capacity:
|
||||
* type: integer
|
||||
* description: 容量
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 状态
|
||||
* description:
|
||||
* type: string
|
||||
* description: 备注信息
|
||||
* farm_id:
|
||||
* type: integer
|
||||
* description: 所属农场ID
|
||||
* creator:
|
||||
* type: string
|
||||
* description: 创建人
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
* updated_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 更新时间
|
||||
* farm:
|
||||
* $ref: '#/components/schemas/Farm'
|
||||
*/
|
||||
|
||||
module.exports = router;
|
||||
@@ -84,6 +84,9 @@ const { verifyToken } = require('../middleware/auth');
|
||||
*/
|
||||
router.get('/', productController.getAllProducts);
|
||||
|
||||
// 根据产品名称搜索产品
|
||||
router.get('/search', productController.searchProductsByName);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/products:
|
||||
|
||||
688
backend/routes/reports.js
Normal file
688
backend/routes/reports.js
Normal file
@@ -0,0 +1,688 @@
|
||||
/**
|
||||
* 报表管理路由
|
||||
* @file reports.js
|
||||
* @description 处理报表生成、下载和管理请求
|
||||
*/
|
||||
const express = require('express');
|
||||
const { verifyToken, checkRole } = require('../middleware/auth');
|
||||
const reportService = require('../services/reportService');
|
||||
const logger = require('../utils/logger');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Reports
|
||||
* description: 报表管理相关接口
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/farm:
|
||||
* post:
|
||||
* summary: 生成养殖统计报表
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* startDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* endDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* farmIds:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 农场ID列表
|
||||
* format:
|
||||
* type: string
|
||||
* enum: [pdf, excel, csv]
|
||||
* description: 报表格式
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 报表生成成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.post('/farm', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const options = {
|
||||
startDate: req.body.startDate,
|
||||
endDate: req.body.endDate,
|
||||
farmIds: req.body.farmIds || [],
|
||||
format: req.body.format || 'pdf'
|
||||
};
|
||||
|
||||
logger.info(`用户 ${req.user.username} 请求生成养殖统计报表`, options);
|
||||
|
||||
const result = await reportService.generateFarmReport(options);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '报表生成成功',
|
||||
data: {
|
||||
fileName: result.fileName,
|
||||
downloadUrl: `/api/reports/download/${encodeURIComponent(result.fileName)}`,
|
||||
mimeType: result.mimeType,
|
||||
size: result.size,
|
||||
generatedAt: new Date()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('生成养殖统计报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '报表生成失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/sales:
|
||||
* post:
|
||||
* summary: 生成销售分析报表
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* startDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* endDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* format:
|
||||
* type: string
|
||||
* enum: [pdf, excel]
|
||||
* description: 报表格式
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 报表生成成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.post('/sales', verifyToken, checkRole(['admin', 'manager']), async (req, res) => {
|
||||
try {
|
||||
const options = {
|
||||
startDate: req.body.startDate,
|
||||
endDate: req.body.endDate,
|
||||
format: req.body.format || 'pdf'
|
||||
};
|
||||
|
||||
logger.info(`用户 ${req.user.username} 请求生成销售分析报表`, options);
|
||||
|
||||
const result = await reportService.generateSalesReport(options);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '销售分析报表生成成功',
|
||||
data: {
|
||||
fileName: result.fileName,
|
||||
downloadUrl: `/api/reports/download/${encodeURIComponent(result.fileName)}`,
|
||||
mimeType: result.mimeType,
|
||||
size: result.size,
|
||||
generatedAt: new Date()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('生成销售分析报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '销售分析报表生成失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/compliance:
|
||||
* post:
|
||||
* summary: 生成监管合规报表
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* startDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* endDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* format:
|
||||
* type: string
|
||||
* enum: [pdf, excel]
|
||||
* description: 报表格式
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 报表生成成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器错误
|
||||
*/
|
||||
router.post('/compliance', verifyToken, checkRole(['admin']), async (req, res) => {
|
||||
try {
|
||||
const options = {
|
||||
startDate: req.body.startDate,
|
||||
endDate: req.body.endDate,
|
||||
format: req.body.format || 'pdf'
|
||||
};
|
||||
|
||||
logger.info(`管理员 ${req.user.username} 请求生成监管合规报表`, options);
|
||||
|
||||
const result = await reportService.generateComplianceReport(options);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '监管合规报表生成成功',
|
||||
data: {
|
||||
fileName: result.fileName,
|
||||
downloadUrl: `/api/reports/download/${encodeURIComponent(result.fileName)}`,
|
||||
mimeType: result.mimeType,
|
||||
size: result.size,
|
||||
generatedAt: new Date()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('生成监管合规报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '监管合规报表生成失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/download/{fileName}:
|
||||
* get:
|
||||
* summary: 下载报表文件
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: fileName
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 文件名
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 文件下载成功
|
||||
* content:
|
||||
* application/pdf:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* 404:
|
||||
* description: 文件不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
*/
|
||||
router.get('/download/:fileName', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const fileName = decodeURIComponent(req.params.fileName);
|
||||
const filePath = path.join(reportService.reportsPath, fileName);
|
||||
|
||||
// 安全检查:确保文件在报表目录内
|
||||
if (!filePath.startsWith(reportService.reportsPath)) {
|
||||
logger.warn(`用户 ${req.user.username} 尝试访问非法路径: ${fileName}`);
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '非法文件路径'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!fs.existsSync(filePath)) {
|
||||
logger.warn(`文件不存在: ${fileName}`);
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '文件不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
const stat = fs.statSync(filePath);
|
||||
const ext = path.extname(fileName).toLowerCase();
|
||||
|
||||
let mimeType = 'application/octet-stream';
|
||||
if (ext === '.pdf') {
|
||||
mimeType = 'application/pdf';
|
||||
} else if (ext === '.xlsx') {
|
||||
mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
} else if (ext === '.csv') {
|
||||
mimeType = 'text/csv';
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', mimeType);
|
||||
res.setHeader('Content-Length', stat.size);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
|
||||
|
||||
// 创建文件流并发送
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
fileStream.pipe(res);
|
||||
|
||||
logger.info(`用户 ${req.user.username} 下载报表文件: ${fileName}`);
|
||||
} catch (error) {
|
||||
logger.error('下载报表文件失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '文件下载失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/list:
|
||||
* get:
|
||||
* summary: 获取报表文件列表
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* fileName:
|
||||
* type: string
|
||||
* size:
|
||||
* type: number
|
||||
* createdAt:
|
||||
* type: string
|
||||
* downloadUrl:
|
||||
* type: string
|
||||
*/
|
||||
router.get('/list', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const files = fs.readdirSync(reportService.reportsPath);
|
||||
const fileList = [];
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(reportService.reportsPath, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
fileList.push({
|
||||
fileName: file,
|
||||
size: stat.size,
|
||||
createdAt: stat.mtime,
|
||||
downloadUrl: `/api/reports/download/${encodeURIComponent(file)}`,
|
||||
mimeType: this.getMimeType(file)
|
||||
});
|
||||
}
|
||||
|
||||
// 按创建时间降序排列
|
||||
fileList.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: fileList,
|
||||
total: fileList.length
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('获取报表文件列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取报表文件列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/cleanup:
|
||||
* post:
|
||||
* summary: 清理过期报表文件
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* daysToKeep:
|
||||
* type: integer
|
||||
* description: 保留天数
|
||||
* default: 30
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 清理成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.post('/cleanup', verifyToken, checkRole(['admin']), async (req, res) => {
|
||||
try {
|
||||
const daysToKeep = req.body.daysToKeep || 30;
|
||||
const deletedCount = await reportService.cleanupOldReports(daysToKeep);
|
||||
|
||||
logger.info(`管理员 ${req.user.username} 执行报表清理,删除了 ${deletedCount} 个文件`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `清理完成,删除了 ${deletedCount} 个过期报表文件`,
|
||||
data: {
|
||||
deletedCount,
|
||||
daysToKeep
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('清理过期报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '清理过期报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/export/farms:
|
||||
* get:
|
||||
* summary: 导出农场数据(Excel/CSV)
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: format
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [excel, csv]
|
||||
* description: 导出格式
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出成功
|
||||
*/
|
||||
router.get('/export/farms', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const format = req.query.format || 'excel';
|
||||
|
||||
logger.info(`用户 ${req.user.username} 请求导出农场数据,格式: ${format}`);
|
||||
|
||||
const result = await reportService.generateFarmReport({ format });
|
||||
|
||||
// 直接发送文件
|
||||
const filePath = result.filePath;
|
||||
|
||||
res.setHeader('Content-Type', result.mimeType);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(result.fileName)}"`);
|
||||
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
fileStream.pipe(res);
|
||||
|
||||
// 异步删除临时文件
|
||||
setTimeout(() => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}, 60000); // 1分钟后删除
|
||||
|
||||
} catch (error) {
|
||||
logger.error('导出农场数据失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '数据导出失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/export/devices:
|
||||
* get:
|
||||
* summary: 导出设备数据(Excel/CSV)
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: format
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [excel, csv]
|
||||
* description: 导出格式
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [online, offline, maintenance]
|
||||
* description: 设备状态筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出成功
|
||||
*/
|
||||
router.get('/export/devices', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const format = req.query.format || 'excel';
|
||||
const status = req.query.status;
|
||||
|
||||
logger.info(`用户 ${req.user.username} 请求导出设备数据,格式: ${format}, 状态: ${status || '全部'}`);
|
||||
|
||||
const result = await this.exportDevicesData(format, status);
|
||||
|
||||
res.setHeader('Content-Type', result.mimeType);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(result.fileName)}"`);
|
||||
|
||||
const fileStream = fs.createReadStream(result.filePath);
|
||||
fileStream.pipe(res);
|
||||
|
||||
// 异步删除临时文件
|
||||
setTimeout(() => {
|
||||
if (fs.existsSync(result.filePath)) {
|
||||
fs.unlinkSync(result.filePath);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('导出设备数据失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '设备数据导出失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/reports/templates:
|
||||
* get:
|
||||
* summary: 获取可用报表模板
|
||||
* tags: [Reports]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
*/
|
||||
router.get('/templates', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const templates = [
|
||||
{
|
||||
id: 'farm-statistics',
|
||||
name: '养殖统计报表',
|
||||
description: '包含农场、动物、设备和预警的综合统计',
|
||||
formats: ['pdf', 'excel', 'csv'],
|
||||
requiredParams: ['startDate', 'endDate'],
|
||||
optionalParams: ['farmIds']
|
||||
},
|
||||
{
|
||||
id: 'sales-analysis',
|
||||
name: '销售分析报表',
|
||||
description: '销售订单和产品销量分析报表',
|
||||
formats: ['pdf', 'excel'],
|
||||
requiredParams: ['startDate', 'endDate'],
|
||||
optionalParams: []
|
||||
},
|
||||
{
|
||||
id: 'compliance-check',
|
||||
name: '监管合规报表',
|
||||
description: '监管要求合规性检查报表',
|
||||
formats: ['pdf', 'excel'],
|
||||
requiredParams: ['startDate', 'endDate'],
|
||||
optionalParams: [],
|
||||
permissions: ['admin']
|
||||
}
|
||||
];
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: templates,
|
||||
total: templates.length
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('获取报表模板失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取报表模板失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出设备数据
|
||||
* @param {string} format 导出格式
|
||||
* @param {string} status 设备状态筛选
|
||||
* @returns {Object} 文件信息
|
||||
*/
|
||||
async function exportDevicesData(format, status) {
|
||||
try {
|
||||
const whereClause = {};
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const devices = await Device.findAll({
|
||||
where: whereClause,
|
||||
include: [{
|
||||
model: Farm,
|
||||
as: 'farm',
|
||||
attributes: ['id', 'name']
|
||||
}],
|
||||
order: [['updated_at', 'DESC']]
|
||||
});
|
||||
|
||||
if (format === 'excel') {
|
||||
const workbook = XLSX.utils.book_new();
|
||||
|
||||
const deviceData = [
|
||||
['设备ID', '设备名称', '设备类型', '设备状态', '所属农场', '安装日期', '最后维护', '创建时间', '更新时间']
|
||||
];
|
||||
|
||||
devices.forEach(device => {
|
||||
deviceData.push([
|
||||
device.id,
|
||||
device.name,
|
||||
device.type,
|
||||
device.status,
|
||||
device.farm?.name || '未知农场',
|
||||
device.installation_date ? moment(device.installation_date).format('YYYY-MM-DD') : '',
|
||||
device.last_maintenance ? moment(device.last_maintenance).format('YYYY-MM-DD') : '',
|
||||
moment(device.created_at).format('YYYY-MM-DD HH:mm:ss'),
|
||||
moment(device.updated_at).format('YYYY-MM-DD HH:mm:ss')
|
||||
]);
|
||||
});
|
||||
|
||||
const worksheet = XLSX.utils.aoa_to_sheet(deviceData);
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, '设备数据');
|
||||
|
||||
const fileName = `设备数据导出_${moment().format('YYYYMMDD_HHmmss')}.xlsx`;
|
||||
const filePath = path.join(reportService.reportsPath, fileName);
|
||||
|
||||
XLSX.writeFile(workbook, filePath);
|
||||
|
||||
return {
|
||||
fileName,
|
||||
filePath,
|
||||
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
size: fs.statSync(filePath).size
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('导出设备数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件MIME类型
|
||||
* @param {string} fileName 文件名
|
||||
* @returns {string} MIME类型
|
||||
*/
|
||||
function getMimeType(fileName) {
|
||||
const ext = path.extname(fileName).toLowerCase();
|
||||
const mimeTypes = {
|
||||
'.pdf': 'application/pdf',
|
||||
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'.csv': 'text/csv'
|
||||
};
|
||||
return mimeTypes[ext] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
369
backend/routes/role-permissions.js
Normal file
369
backend/routes/role-permissions.js
Normal file
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* 角色权限管理路由
|
||||
* @file role-permissions.js
|
||||
* @description 定义角色权限管理相关的API路由
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const rolePermissionController = require('../controllers/rolePermissionController');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permission');
|
||||
|
||||
// 公开API路由,不需要验证token
|
||||
const publicRoutes = express.Router();
|
||||
router.use('/public', publicRoutes);
|
||||
|
||||
// 公开获取角色列表
|
||||
publicRoutes.get('/roles', rolePermissionController.getAllRoles);
|
||||
|
||||
// 公开获取角色详情
|
||||
publicRoutes.get('/roles/:id', rolePermissionController.getRoleById);
|
||||
|
||||
// 公开获取菜单权限列表
|
||||
publicRoutes.get('/menus', rolePermissionController.getAllMenuPermissions);
|
||||
|
||||
// 公开获取角色菜单权限
|
||||
publicRoutes.get('/roles/:roleId/menus', rolePermissionController.getRoleMenuPermissions);
|
||||
|
||||
// 公开获取所有权限
|
||||
publicRoutes.get('/permissions', rolePermissionController.getAllPermissions);
|
||||
|
||||
// 公开获取权限模块列表
|
||||
publicRoutes.get('/permissions/modules', rolePermissionController.getPermissionModules);
|
||||
|
||||
// 公开获取角色功能权限
|
||||
publicRoutes.get('/roles/:roleId/permissions', rolePermissionController.getRolePermissions);
|
||||
|
||||
// 所有其他路由都需要认证
|
||||
router.use(verifyToken);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Role:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* name:
|
||||
* type: string
|
||||
* description: 角色名称
|
||||
* description:
|
||||
* type: string
|
||||
* description: 角色描述
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 状态
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
* updated_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 更新时间
|
||||
* menuPermissions:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/MenuPermission'
|
||||
* description: 菜单权限列表
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/roles:
|
||||
* get:
|
||||
* summary: 获取角色列表
|
||||
* tags: [Role Permissions]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取角色列表
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* list:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Role'
|
||||
* pagination:
|
||||
* type: object
|
||||
* properties:
|
||||
* current:
|
||||
* type: integer
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* total:
|
||||
* type: integer
|
||||
* pages:
|
||||
* type: integer
|
||||
* message:
|
||||
* type: string
|
||||
*/
|
||||
router.get('/roles', requirePermission('role:view'), rolePermissionController.getAllRoles);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/roles/{id}:
|
||||
* get:
|
||||
* summary: 获取角色详情
|
||||
* tags: [Role Permissions]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取角色详情
|
||||
* 404:
|
||||
* description: 角色不存在
|
||||
*/
|
||||
router.get('/roles/:id', requirePermission('role:view'), rolePermissionController.getRoleById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/roles:
|
||||
* post:
|
||||
* summary: 创建角色
|
||||
* tags: [Role Permissions]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 角色名称
|
||||
* description:
|
||||
* type: string
|
||||
* description: 角色描述
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 状态
|
||||
* menuIds:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 菜单权限ID数组
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 角色创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/roles', requirePermission('role:create'), rolePermissionController.createRole);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/roles/{id}:
|
||||
* put:
|
||||
* summary: 更新角色
|
||||
* tags: [Role Permissions]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 角色名称
|
||||
* description:
|
||||
* type: string
|
||||
* description: 角色描述
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 状态
|
||||
* menuIds:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 菜单权限ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 角色更新成功
|
||||
* 404:
|
||||
* description: 角色不存在
|
||||
*/
|
||||
router.put('/roles/:id', requirePermission('role:update'), rolePermissionController.updateRole);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/roles/{id}:
|
||||
* delete:
|
||||
* summary: 删除角色
|
||||
* tags: [Role Permissions]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 角色删除成功
|
||||
* 404:
|
||||
* description: 角色不存在
|
||||
*/
|
||||
router.delete('/roles/:id', requirePermission('role:delete'), rolePermissionController.deleteRole);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/menus:
|
||||
* get:
|
||||
* summary: 获取菜单权限列表
|
||||
* tags: [Role Permissions]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取菜单权限列表
|
||||
*/
|
||||
router.get('/menus', requirePermission('menu:view'), rolePermissionController.getAllMenuPermissions);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/roles/{roleId}/menus:
|
||||
* get:
|
||||
* summary: 获取角色的菜单权限
|
||||
* tags: [Role Permissions]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: roleId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 成功获取角色菜单权限
|
||||
* 404:
|
||||
* description: 角色不存在
|
||||
*/
|
||||
router.get('/roles/:roleId/menus', requirePermission('role:view'), rolePermissionController.getRoleMenuPermissions);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/roles/{roleId}/menus:
|
||||
* post:
|
||||
* summary: 设置角色的菜单权限
|
||||
* tags: [Role Permissions]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: roleId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* menuIds:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 菜单权限ID数组
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 设置角色菜单权限成功
|
||||
* 404:
|
||||
* description: 角色不存在
|
||||
*/
|
||||
router.post('/roles/:roleId/menus', requirePermission('role:assign'), rolePermissionController.setRoleMenuPermissions);
|
||||
|
||||
// 设置角色功能权限
|
||||
router.post('/roles/:roleId/permissions', requirePermission('role:assign'), rolePermissionController.setRolePermissions);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/role-permissions/roles/{id}/status:
|
||||
* put:
|
||||
* summary: 切换角色状态
|
||||
* tags: [Role Permissions]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 角色ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - status
|
||||
* properties:
|
||||
* status:
|
||||
* type: boolean
|
||||
* description: 角色状态
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 角色状态切换成功
|
||||
* 404:
|
||||
* description: 角色不存在
|
||||
*/
|
||||
router.put('/roles/:id/status', requirePermission('role:update'), rolePermissionController.toggleRoleStatus);
|
||||
|
||||
module.exports = router;
|
||||
590
backend/routes/smart-alerts.js
Normal file
590
backend/routes/smart-alerts.js
Normal file
@@ -0,0 +1,590 @@
|
||||
/**
|
||||
* 智能预警路由
|
||||
* @file smart-alerts.js
|
||||
* @description 定义智能预警相关的API路由
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { IotJbqClient, IotXqClient } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 公开API路由,不需要验证token
|
||||
const publicRoutes = express.Router();
|
||||
router.use('/public', publicRoutes);
|
||||
|
||||
/**
|
||||
* 获取智能预警统计
|
||||
*/
|
||||
publicRoutes.get('/stats', async (req, res) => {
|
||||
try {
|
||||
// 获取耳标设备数量
|
||||
const eartagCount = await IotJbqClient.count();
|
||||
|
||||
// 获取项圈设备数量
|
||||
const collarCount = await IotXqClient.count();
|
||||
|
||||
// 生成耳标预警数据(与预警列表API使用相同逻辑)
|
||||
const eartagDevices = await IotJbqClient.findAll({
|
||||
order: [['uptime', 'DESC'], ['id', 'DESC']]
|
||||
});
|
||||
|
||||
const eartagAlerts = [];
|
||||
eartagDevices.forEach(device => {
|
||||
const actualBattery = parseInt(device.voltage) || 0;
|
||||
const actualTemperature = parseFloat(device.temperature) || 0;
|
||||
const totalSteps = parseInt(device.walk) || 0;
|
||||
const yesterdaySteps = parseInt(device.y_steps) || 0;
|
||||
const dailySteps = totalSteps - yesterdaySteps; // 当日步数 = 总步数 - 昨日步数
|
||||
|
||||
// 离线预警
|
||||
if (device.state === 0) {
|
||||
eartagAlerts.push({ type: 'offline' });
|
||||
}
|
||||
|
||||
// 低电量预警
|
||||
if (actualBattery > 0 && actualBattery < 20) {
|
||||
eartagAlerts.push({ type: 'battery' });
|
||||
}
|
||||
|
||||
// 温度预警
|
||||
if (actualTemperature > 0) {
|
||||
// 低温预警
|
||||
if (actualTemperature < 30) {
|
||||
eartagAlerts.push({ type: 'temperature' });
|
||||
}
|
||||
// 高温预警
|
||||
else if (actualTemperature > 40) {
|
||||
eartagAlerts.push({ type: 'temperature' });
|
||||
}
|
||||
}
|
||||
|
||||
// 异常运动预警
|
||||
// 步数异常预警:当日步数为0
|
||||
if (dailySteps === 0 && totalSteps > 0) {
|
||||
eartagAlerts.push({ type: 'movement' });
|
||||
}
|
||||
});
|
||||
|
||||
// 生成项圈预警数据(简化版本)
|
||||
const collarAlerts = await IotXqClient.count({
|
||||
where: {
|
||||
state: 2 // 预警状态
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
totalAlerts: eartagAlerts.length + collarAlerts,
|
||||
eartagAlerts: eartagAlerts.length,
|
||||
collarAlerts: collarAlerts,
|
||||
eartagDevices: eartagCount,
|
||||
collarDevices: collarCount
|
||||
},
|
||||
message: '获取智能预警统计成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取智能预警统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取智能预警统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取智能耳标预警列表
|
||||
*/
|
||||
publicRoutes.get('/eartag', async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 10, status, search, alertType } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
console.log('智能耳标预警API请求参数:', { page, limit, status, search, alertType });
|
||||
console.log('alertType 详细信息:', {
|
||||
value: alertType,
|
||||
type: typeof alertType,
|
||||
isString: typeof alertType === 'string',
|
||||
isEmpty: alertType === '',
|
||||
isUndefined: alertType === undefined,
|
||||
isNull: alertType === null,
|
||||
length: alertType ? alertType.length : 'N/A'
|
||||
});
|
||||
|
||||
// 构建查询条件
|
||||
const whereConditions = {};
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
switch (status) {
|
||||
case 'online':
|
||||
whereConditions.state = 1;
|
||||
break;
|
||||
case 'offline':
|
||||
whereConditions.state = 0;
|
||||
break;
|
||||
case 'alarm':
|
||||
whereConditions.state = 2;
|
||||
break;
|
||||
case 'maintenance':
|
||||
whereConditions.state = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索条件 - 耳标设备使用aaid字段搜索
|
||||
if (search) {
|
||||
whereConditions[Op.or] = [
|
||||
{ aaid: { [Op.like]: `%${search}%` } },
|
||||
{ cid: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
console.log('耳标搜索条件:', whereConditions[Op.or]);
|
||||
}
|
||||
|
||||
// 查询所有符合条件的设备数据(用于生成预警)
|
||||
const allDevices = await IotJbqClient.findAll({
|
||||
where: whereConditions,
|
||||
order: [
|
||||
['uptime', 'DESC'],
|
||||
['id', 'DESC']
|
||||
]
|
||||
});
|
||||
|
||||
// 生成预警数据 - 为每个设备生成对应的预警记录
|
||||
const allAlerts = [];
|
||||
|
||||
allDevices.forEach(device => {
|
||||
// 耳标设备编号使用aaid字段
|
||||
const deviceId = device.aaid || `EARTAG${device.id}`;
|
||||
const alertTime = new Date(device.uptime || Date.now()).toISOString().replace('T', ' ').substring(0, 19);
|
||||
|
||||
// 获取实际设备数据
|
||||
const actualBattery = parseInt(device.voltage) || 0;
|
||||
const actualTemperature = parseFloat(device.temperature) || 0;
|
||||
|
||||
// 获取步数数据
|
||||
const totalSteps = parseInt(device.walk) || 0;
|
||||
const yesterdaySteps = parseInt(device.y_steps) || 0;
|
||||
const dailySteps = totalSteps - yesterdaySteps; // 当日步数 = 总步数 - 昨日步数
|
||||
|
||||
// 为所有预警添加当日步数字段的函数
|
||||
const addDailyStepsToAlert = (alert) => {
|
||||
alert.dailySteps = dailySteps;
|
||||
alert.totalSteps = totalSteps;
|
||||
alert.yesterdaySteps = yesterdaySteps;
|
||||
return alert;
|
||||
};
|
||||
|
||||
// 离线预警 - 基于实际设备状态生成预警
|
||||
if (device.state === 0) {
|
||||
allAlerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_offline`,
|
||||
eartagNumber: deviceId,
|
||||
alertType: 'offline',
|
||||
alertLevel: 'high',
|
||||
alertTime: alertTime,
|
||||
battery: actualBattery,
|
||||
temperature: actualTemperature,
|
||||
gpsSignal: '无',
|
||||
movementStatus: '静止',
|
||||
description: '设备已离线超过30分钟',
|
||||
longitude: 0,
|
||||
latitude: 0
|
||||
}));
|
||||
}
|
||||
|
||||
// 低电量预警 - 基于实际电量数据生成预警
|
||||
if (actualBattery > 0 && actualBattery < 20) {
|
||||
allAlerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_battery`,
|
||||
eartagNumber: deviceId,
|
||||
alertType: 'battery',
|
||||
alertLevel: actualBattery < 10 ? 'high' : 'medium',
|
||||
alertTime: alertTime,
|
||||
battery: actualBattery,
|
||||
temperature: device.temperature || 0,
|
||||
gpsSignal: '强',
|
||||
movementStatus: '正常',
|
||||
description: `设备电量低于20%,当前电量${actualBattery}%`,
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
|
||||
// 温度预警 - 基于实际温度数据生成预警
|
||||
if (actualTemperature > 0) {
|
||||
// 低温预警
|
||||
if (actualTemperature < 30) {
|
||||
allAlerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_temperature_low`,
|
||||
eartagNumber: deviceId,
|
||||
alertType: 'temperature',
|
||||
alertLevel: actualTemperature < 20 ? 'high' : 'medium',
|
||||
alertTime: alertTime,
|
||||
battery: actualBattery,
|
||||
temperature: actualTemperature,
|
||||
gpsSignal: '强',
|
||||
movementStatus: '正常',
|
||||
description: `设备温度过低,当前温度${actualTemperature}°C`,
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
// 高温预警
|
||||
else if (actualTemperature > 40) {
|
||||
allAlerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_temperature_high`,
|
||||
eartagNumber: deviceId,
|
||||
alertType: 'temperature',
|
||||
alertLevel: actualTemperature > 45 ? 'high' : 'medium',
|
||||
alertTime: alertTime,
|
||||
battery: actualBattery,
|
||||
temperature: actualTemperature,
|
||||
gpsSignal: '强',
|
||||
movementStatus: '正常',
|
||||
description: `设备温度过高,当前温度${actualTemperature}°C`,
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 异常运动预警 - 基于实际运动数据生成预警
|
||||
|
||||
// 步数异常预警:当日步数为0
|
||||
if (dailySteps === 0 && totalSteps > 0) {
|
||||
allAlerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_movement_zero`,
|
||||
eartagNumber: deviceId,
|
||||
alertType: 'movement',
|
||||
alertLevel: 'high',
|
||||
alertTime: alertTime,
|
||||
battery: actualBattery,
|
||||
temperature: actualTemperature,
|
||||
gpsSignal: '强',
|
||||
movementStatus: '异常',
|
||||
description: `检测到步数异常,当日运动量为0步,可能为设备故障或动物异常`,
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// 预警类型筛选
|
||||
let filteredAlerts = allAlerts;
|
||||
console.log('=== 开始预警类型筛选 ===');
|
||||
console.log('原始预警数量:', allAlerts.length);
|
||||
console.log('alertType 值:', alertType);
|
||||
console.log('alertType 条件检查:', {
|
||||
'alertType 存在': !!alertType,
|
||||
'alertType.trim() !== ""': alertType && alertType.trim() !== '',
|
||||
'alertType 类型': typeof alertType,
|
||||
'alertType 长度': alertType ? alertType.length : 'N/A'
|
||||
});
|
||||
|
||||
if (alertType && alertType.trim() !== '') {
|
||||
console.log(`执行筛选,筛选类型: ${alertType}`);
|
||||
filteredAlerts = allAlerts.filter(alert => alert.alertType === alertType);
|
||||
console.log(`筛选结果: ${allAlerts.length} -> ${filteredAlerts.length}`);
|
||||
console.log('筛选后的预警类型分布:',
|
||||
filteredAlerts.reduce((acc, alert) => {
|
||||
acc[alert.alertType] = (acc[alert.alertType] || 0) + 1;
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
} else {
|
||||
console.log('跳过筛选,显示所有预警');
|
||||
}
|
||||
|
||||
// 计算统计数据
|
||||
const stats = {
|
||||
lowBattery: filteredAlerts.filter(alert => alert.alertType === 'battery').length,
|
||||
offline: filteredAlerts.filter(alert => alert.alertType === 'offline').length,
|
||||
highTemperature: filteredAlerts.filter(alert => alert.alertType === 'temperature').length,
|
||||
abnormalMovement: filteredAlerts.filter(alert => alert.alertType === 'movement').length
|
||||
};
|
||||
|
||||
// 对筛选后的预警数据进行分页
|
||||
const startIndex = parseInt(offset);
|
||||
const endIndex = startIndex + parseInt(limit);
|
||||
const paginatedAlerts = filteredAlerts.slice(startIndex, endIndex);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: paginatedAlerts,
|
||||
total: filteredAlerts.length,
|
||||
stats: stats,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: filteredAlerts.length,
|
||||
pages: Math.ceil(filteredAlerts.length / limit)
|
||||
},
|
||||
message: '获取智能耳标预警列表成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取智能耳标预警列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取智能耳标预警列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取智能项圈预警列表
|
||||
*/
|
||||
publicRoutes.get('/collar', async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 10, status, search, alertType } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
console.log('智能项圈预警API请求参数:', { page, limit, status, search, alertType });
|
||||
console.log('alertType 详细信息:', {
|
||||
value: alertType,
|
||||
type: typeof alertType,
|
||||
isString: typeof alertType === 'string',
|
||||
isEmpty: alertType === '',
|
||||
isUndefined: alertType === undefined,
|
||||
isNull: alertType === null,
|
||||
length: alertType ? alertType.length : 'N/A'
|
||||
});
|
||||
|
||||
// 构建查询条件
|
||||
const whereConditions = {};
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
switch (status) {
|
||||
case 'online':
|
||||
whereConditions.state = 1;
|
||||
break;
|
||||
case 'offline':
|
||||
whereConditions.state = 0;
|
||||
break;
|
||||
case 'alarm':
|
||||
whereConditions.state = 2;
|
||||
break;
|
||||
case 'maintenance':
|
||||
whereConditions.state = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索条件 - 项圈设备使用sn字段搜索
|
||||
if (search) {
|
||||
whereConditions[Op.or] = [
|
||||
{ sn: { [Op.like]: `%${search}%` } },
|
||||
{ deviceId: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
console.log('项圈搜索条件:', whereConditions[Op.or]);
|
||||
}
|
||||
|
||||
// 查询数据库
|
||||
const { count, rows } = await IotXqClient.findAndCountAll({
|
||||
where: whereConditions,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
order: [
|
||||
['uptime', 'DESC'],
|
||||
['id', 'DESC']
|
||||
]
|
||||
});
|
||||
|
||||
// 生成预警数据 - 为每个设备生成对应的预警记录
|
||||
const alerts = [];
|
||||
|
||||
rows.forEach(device => {
|
||||
// 项圈设备编号使用sn字段
|
||||
const deviceId = device.sn || `COLLAR${device.id}`;
|
||||
const alertTime = new Date(device.uptime || Date.now()).toISOString().replace('T', ' ').substring(0, 19);
|
||||
|
||||
// 获取步数数据
|
||||
const totalSteps = parseInt(device.steps) || 0;
|
||||
const yesterdaySteps = parseInt(device.y_steps) || 0;
|
||||
const dailySteps = totalSteps - yesterdaySteps; // 当日步数 = 总步数 - 昨日步数
|
||||
|
||||
// 为所有预警添加当日步数字段的函数
|
||||
const addDailyStepsToAlert = (alert) => {
|
||||
alert.dailySteps = dailySteps;
|
||||
alert.totalSteps = totalSteps;
|
||||
alert.yesterdaySteps = yesterdaySteps;
|
||||
return alert;
|
||||
};
|
||||
|
||||
// 离线预警
|
||||
if (device.state === 0) {
|
||||
alerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_offline`,
|
||||
collarNumber: deviceId,
|
||||
alertType: 'offline',
|
||||
alertLevel: 'high',
|
||||
alertTime: alertTime,
|
||||
battery: device.battery || 0,
|
||||
temperature: device.temperature || 0,
|
||||
gpsSignal: '无',
|
||||
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
|
||||
description: '设备已离线超过30分钟',
|
||||
longitude: 0,
|
||||
latitude: 0
|
||||
}));
|
||||
}
|
||||
|
||||
// 低电量预警
|
||||
if (device.battery < 20) {
|
||||
alerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_battery`,
|
||||
collarNumber: deviceId,
|
||||
alertType: 'battery',
|
||||
alertLevel: device.battery < 10 ? 'high' : 'medium',
|
||||
alertTime: alertTime,
|
||||
battery: device.battery || 0,
|
||||
temperature: device.temperature || 0,
|
||||
gpsSignal: '强',
|
||||
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
|
||||
description: `设备电量低于20%,当前电量${device.battery}%`,
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
|
||||
// 温度预警
|
||||
const actualTemperature = parseFloat(device.temperature) || 0;
|
||||
if (actualTemperature > 0) {
|
||||
// 低温预警
|
||||
if (actualTemperature < 30) {
|
||||
alerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_temperature_low`,
|
||||
collarNumber: deviceId,
|
||||
alertType: 'temperature',
|
||||
alertLevel: actualTemperature < 20 ? 'high' : 'medium',
|
||||
alertTime: alertTime,
|
||||
battery: device.battery || 0,
|
||||
temperature: actualTemperature,
|
||||
gpsSignal: '强',
|
||||
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
|
||||
description: `设备温度过低,当前温度${actualTemperature}°C`,
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
// 高温预警
|
||||
else if (actualTemperature > 40) {
|
||||
alerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_temperature_high`,
|
||||
collarNumber: deviceId,
|
||||
alertType: 'temperature',
|
||||
alertLevel: actualTemperature > 45 ? 'high' : 'medium',
|
||||
alertTime: alertTime,
|
||||
battery: device.battery || 0,
|
||||
temperature: actualTemperature,
|
||||
gpsSignal: '强',
|
||||
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
|
||||
description: `设备温度过高,当前温度${actualTemperature}°C`,
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 异常运动预警 - 基于实际运动数据生成预警
|
||||
|
||||
// 步数异常预警:当日步数为0
|
||||
if (dailySteps === 0 && totalSteps > 0) {
|
||||
alerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_movement_zero`,
|
||||
collarNumber: deviceId,
|
||||
alertType: 'movement',
|
||||
alertLevel: 'high',
|
||||
alertTime: alertTime,
|
||||
battery: device.battery || 0,
|
||||
temperature: actualTemperature,
|
||||
gpsSignal: '强',
|
||||
wearStatus: device.bandge_status === 1 ? '已佩戴' : '未佩戴',
|
||||
description: `检测到步数异常,当日运动量为0步,可能为设备故障或动物异常`,
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
|
||||
// 项圈脱落预警
|
||||
if (device.bandge_status === 0) {
|
||||
alerts.push(addDailyStepsToAlert({
|
||||
id: `${device.id}_wear`,
|
||||
collarNumber: deviceId,
|
||||
alertType: 'wear',
|
||||
alertLevel: 'high',
|
||||
alertTime: alertTime,
|
||||
battery: device.battery || 0,
|
||||
temperature: actualTemperature,
|
||||
gpsSignal: '中',
|
||||
wearStatus: '未佩戴',
|
||||
description: '设备佩戴状态异常,可能已脱落',
|
||||
longitude: 116.3974 + (device.id % 100) * 0.0001,
|
||||
latitude: 39.9093 + (device.id % 100) * 0.0001
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// 预警类型筛选
|
||||
let filteredAlerts = alerts;
|
||||
console.log('=== 开始预警类型筛选 ===');
|
||||
console.log('原始预警数量:', alerts.length);
|
||||
console.log('alertType 值:', alertType);
|
||||
console.log('alertType 条件检查:', {
|
||||
'alertType 存在': !!alertType,
|
||||
'alertType.trim() !== ""': alertType && alertType.trim() !== '',
|
||||
'alertType 类型': typeof alertType,
|
||||
'alertType 长度': alertType ? alertType.length : 'N/A'
|
||||
});
|
||||
|
||||
if (alertType && alertType.trim() !== '') {
|
||||
console.log(`执行筛选,筛选类型: ${alertType}`);
|
||||
filteredAlerts = alerts.filter(alert => alert.alertType === alertType);
|
||||
console.log(`筛选结果: ${alerts.length} -> ${filteredAlerts.length}`);
|
||||
console.log('筛选后的预警类型分布:',
|
||||
filteredAlerts.reduce((acc, alert) => {
|
||||
acc[alert.alertType] = (acc[alert.alertType] || 0) + 1;
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
} else {
|
||||
console.log('跳过筛选,显示所有预警');
|
||||
}
|
||||
|
||||
// 计算统计数据
|
||||
const stats = {
|
||||
lowBattery: filteredAlerts.filter(alert => alert.alertType === 'battery').length,
|
||||
offline: filteredAlerts.filter(alert => alert.alertType === 'offline').length,
|
||||
highTemperature: filteredAlerts.filter(alert => alert.alertType === 'temperature').length,
|
||||
abnormalMovement: filteredAlerts.filter(alert => alert.alertType === 'movement').length,
|
||||
wearOff: filteredAlerts.filter(alert => alert.alertType === 'wear').length
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: filteredAlerts,
|
||||
total: filteredAlerts.length,
|
||||
stats: stats,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: filteredAlerts.length,
|
||||
pages: Math.ceil(filteredAlerts.length / limit)
|
||||
},
|
||||
message: '获取智能项圈预警列表成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取智能项圈预警列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取智能项圈预警列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
1615
backend/routes/smart-devices.js
Normal file
1615
backend/routes/smart-devices.js
Normal file
File diff suppressed because it is too large
Load Diff
430
backend/routes/system.js
Normal file
430
backend/routes/system.js
Normal file
@@ -0,0 +1,430 @@
|
||||
/**
|
||||
* 系统管理路由
|
||||
* @file system.js
|
||||
* @description 处理系统配置和菜单权限管理请求
|
||||
*/
|
||||
const express = require('express');
|
||||
const { body } = require('express-validator');
|
||||
const { verifyToken, checkRole } = require('../middleware/auth');
|
||||
const systemController = require('../controllers/systemController');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: System
|
||||
* description: 系统管理相关接口
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/configs:
|
||||
* get:
|
||||
* summary: 获取系统配置列表
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: category
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 配置分类
|
||||
* - in: query
|
||||
* name: is_public
|
||||
* schema:
|
||||
* type: boolean
|
||||
* description: 是否公开配置
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/configs', verifyToken, checkRole(['admin']), systemController.getSystemConfigs);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/configs/public:
|
||||
* get:
|
||||
* summary: 获取公开系统配置
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
*/
|
||||
router.get('/configs/public', verifyToken, systemController.getPublicConfigs);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/configs/categories:
|
||||
* get:
|
||||
* summary: 获取配置分类列表
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
*/
|
||||
router.get('/configs/categories', verifyToken, checkRole(['admin']), systemController.getConfigCategories);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/configs:
|
||||
* post:
|
||||
* summary: 创建系统配置
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - config_key
|
||||
* - config_value
|
||||
* properties:
|
||||
* config_key:
|
||||
* type: string
|
||||
* description: 配置键名
|
||||
* config_value:
|
||||
* description: 配置值
|
||||
* category:
|
||||
* type: string
|
||||
* description: 配置分类
|
||||
* description:
|
||||
* type: string
|
||||
* description: 配置描述
|
||||
* is_public:
|
||||
* type: boolean
|
||||
* description: 是否公开
|
||||
* is_editable:
|
||||
* type: boolean
|
||||
* description: 是否可编辑
|
||||
* sort_order:
|
||||
* type: integer
|
||||
* description: 排序顺序
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.post('/configs',
|
||||
verifyToken,
|
||||
checkRole(['admin']),
|
||||
[
|
||||
body('config_key').notEmpty().withMessage('配置键名不能为空'),
|
||||
body('config_value').exists().withMessage('配置值不能为空')
|
||||
],
|
||||
systemController.createSystemConfig
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/configs/{id}:
|
||||
* put:
|
||||
* summary: 更新系统配置
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 配置ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* config_value:
|
||||
* description: 配置值
|
||||
* description:
|
||||
* type: string
|
||||
* description: 配置描述
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 配置不存在
|
||||
*/
|
||||
router.put('/configs/:id',
|
||||
verifyToken,
|
||||
checkRole(['admin']),
|
||||
[
|
||||
body('config_value').exists().withMessage('配置值不能为空')
|
||||
],
|
||||
systemController.updateSystemConfig
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/configs/{id}:
|
||||
* delete:
|
||||
* summary: 删除系统配置
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 配置ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 配置不存在
|
||||
*/
|
||||
router.delete('/configs/:id', verifyToken, checkRole(['admin']), systemController.deleteSystemConfig);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/configs/batch:
|
||||
* put:
|
||||
* summary: 批量更新系统配置
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - configs
|
||||
* properties:
|
||||
* configs:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* config_key:
|
||||
* type: string
|
||||
* config_value:
|
||||
* description: 配置值
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.put('/configs/batch',
|
||||
verifyToken,
|
||||
checkRole(['admin']),
|
||||
[
|
||||
body('configs').isArray().withMessage('configs必须是数组')
|
||||
],
|
||||
systemController.batchUpdateConfigs
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/configs/{id}/reset:
|
||||
* post:
|
||||
* summary: 重置系统配置到默认值
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 配置ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 重置成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 配置不存在
|
||||
*/
|
||||
router.post('/configs/:id/reset', verifyToken, checkRole(['admin']), systemController.resetSystemConfig);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/menus:
|
||||
* get:
|
||||
* summary: 获取菜单权限列表
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/menus', verifyToken, checkRole(['admin']), systemController.getMenuPermissions);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/menus/user:
|
||||
* get:
|
||||
* summary: 获取用户可访问菜单
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
*/
|
||||
router.get('/menus/user', verifyToken, systemController.getUserMenus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/menus/{id}:
|
||||
* put:
|
||||
* summary: 更新菜单权限
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 菜单ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* menu_name:
|
||||
* type: string
|
||||
* description: 菜单名称
|
||||
* menu_path:
|
||||
* type: string
|
||||
* description: 菜单路径
|
||||
* required_roles:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: 所需角色
|
||||
* required_permissions:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: 所需权限
|
||||
* icon:
|
||||
* type: string
|
||||
* description: 菜单图标
|
||||
* sort_order:
|
||||
* type: integer
|
||||
* description: 排序顺序
|
||||
* is_visible:
|
||||
* type: boolean
|
||||
* description: 是否可见
|
||||
* is_enabled:
|
||||
* type: boolean
|
||||
* description: 是否启用
|
||||
* description:
|
||||
* type: string
|
||||
* description: 菜单描述
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 菜单不存在
|
||||
*/
|
||||
router.put('/menus/:id',
|
||||
verifyToken,
|
||||
checkRole(['admin']),
|
||||
[
|
||||
body('menu_name').optional().notEmpty().withMessage('菜单名称不能为空'),
|
||||
body('required_roles').optional().isArray().withMessage('所需角色必须是数组'),
|
||||
body('required_permissions').optional().isArray().withMessage('所需权限必须是数组')
|
||||
],
|
||||
systemController.updateMenuPermission
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/stats:
|
||||
* get:
|
||||
* summary: 获取系统统计信息
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/stats', verifyToken, checkRole(['admin']), systemController.getSystemStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/system/init:
|
||||
* post:
|
||||
* summary: 初始化系统配置
|
||||
* tags: [System]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 初始化成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.post('/init', verifyToken, checkRole(['admin']), systemController.initializeSystem);
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const searchLogger = require('../middleware/search-logger');
|
||||
const router = express.Router();
|
||||
const userController = require('../controllers/userController');
|
||||
|
||||
@@ -128,6 +129,9 @@ router.get('/', verifyToken, userController.getAllUsers);
|
||||
* description: 服务器错误
|
||||
*/
|
||||
|
||||
// 根据用户名搜索用户 (需要认证,添加搜索监听中间件)
|
||||
router.get('/search', verifyToken, searchLogger, userController.searchUserByUsername);
|
||||
|
||||
// 根据ID获取用户 (需要认证)
|
||||
router.get('/:id', verifyToken, userController.getUserById);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user