完善保险端前后端

This commit is contained in:
shenquanyi
2025-09-24 18:12:37 +08:00
parent 111ebaec84
commit b17bdcc24c
56 changed files with 9862 additions and 1111 deletions

View File

@@ -91,23 +91,64 @@ const login = async (req, res) => {
// 更新最后登录时间
await user.update({ last_login: new Date() });
// 生成JWT令牌
const token = jwt.sign(
// 生成访问令牌(短期有效)
const accessToken = jwt.sign(
{
id: user.id,
username: user.username,
role_id: user.role_id,
permissions: user.role?.permissions || []
permissions: user.role?.permissions || [],
type: 'access'
},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE || '7d' }
{ expiresIn: '15m' } // 15分钟
);
res.json(responseFormat.success({
user: user.toJSON(),
token,
expires_in: 7 * 24 * 60 * 60 // 7天
}, '登录成功'));
// 生成刷新令牌(长期有效)
const refreshToken = jwt.sign(
{
id: user.id,
username: user.username,
type: 'refresh'
},
process.env.JWT_SECRET,
{ expiresIn: '7d' } // 7天
);
const userJson = user.toJSON();
console.log('用户JSON数据:', userJson);
// 确保用户数据是纯JSON对象包含角色信息
const userData = {
id: user.id,
username: user.username,
real_name: user.real_name,
email: user.email,
phone: user.phone,
role_id: user.role_id,
status: user.status,
last_login: user.last_login,
avatar: user.avatar,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
role: user.role ? {
id: user.role.id,
name: user.role.name,
permissions: user.role.permissions
} : null
};
const responseData = {
user: userData,
accessToken,
refreshToken,
accessTokenExpiresIn: 15 * 60, // 15分钟
refreshTokenExpiresIn: 7 * 24 * 60 * 60 // 7天
};
console.log('响应数据:', responseData);
res.json(responseFormat.success(responseData, '登录成功'));
} catch (error) {
console.error('登录错误详情:', {
message: error.message,
@@ -174,20 +215,30 @@ const changePassword = async (req, res) => {
// 刷新令牌
const refreshToken = async (req, res) => {
try {
const { refresh_token } = req.body;
const { refreshToken: refresh_token } = req.body;
if (!refresh_token) {
return res.status(400).json(responseFormat.error('刷新令牌不能为空'));
}
// 验证刷新令牌(这里简化处理,实际应该使用专门的刷新令牌机制)
const decoded = jwt.verify(refresh_token, process.env.JWT_SECRET);
// 验证刷新令牌
let decoded;
try {
decoded = jwt.verify(refresh_token, process.env.JWT_SECRET);
} catch (error) {
return res.status(401).json(responseFormat.error('刷新令牌无效或已过期'));
}
// 检查令牌类型
if (decoded.type !== 'refresh') {
return res.status(401).json(responseFormat.error('无效的令牌类型'));
}
const user = await User.findByPk(decoded.id, {
include: [{
model: Role,
as: 'role',
attributes: ['permissions']
attributes: ['id', 'name', 'permissions']
}]
});
@@ -196,24 +247,39 @@ const refreshToken = async (req, res) => {
}
// 生成新的访问令牌
const newToken = jwt.sign(
const newAccessToken = jwt.sign(
{
id: user.id,
username: user.username,
role_id: user.role_id,
permissions: user.role?.permissions || []
permissions: user.role?.permissions || [],
type: 'access'
},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE || '7d' }
{ expiresIn: '15m' } // 15分钟
);
// 可选:生成新的刷新令牌(滚动刷新)
const newRefreshToken = jwt.sign(
{
id: user.id,
username: user.username,
type: 'refresh'
},
process.env.JWT_SECRET,
{ expiresIn: '7d' } // 7天
);
res.json(responseFormat.success({
token: newToken,
expires_in: 7 * 24 * 60 * 60
accessToken: newAccessToken,
refreshToken: newRefreshToken,
accessTokenExpiresIn: 15 * 60, // 15分钟
refreshTokenExpiresIn: 7 * 24 * 60 * 60, // 7天
user: user.toJSON()
}, '令牌刷新成功'));
} catch (error) {
console.error('刷新令牌错误:', error);
res.status(401).json(responseFormat.error('刷新令牌无效'));
res.status(401).json(responseFormat.error('刷新令牌失败'));
}
};

View File

@@ -1,206 +1,307 @@
const { User, Role, InsuranceApplication, Policy, Claim, InsuranceType } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
const { InsuranceApplication, Policy, Claim, InsuranceType, User } = require('../models');
const { Op, Sequelize } = require('sequelize');
// 获取数据览仓概览数据
// 获取数据仓库概
const getOverview = async (req, res) => {
try {
const [
totalUsers,
totalApplications,
totalPolicies,
totalClaims,
activePolicies,
approvedClaims,
pendingClaims
] = await Promise.all([
User.count(),
InsuranceApplication.count(),
Policy.count(),
Claim.count(),
Policy.count({ where: { policy_status: 'active' } }),
Claim.count({ where: { claim_status: 'approved' } }),
Claim.count({ where: { claim_status: 'pending' } })
]);
// 获取总申请数
const totalApplications = await InsuranceApplication.count();
res.json(responseFormat.success({
totalUsers,
totalApplications,
totalPolicies,
totalClaims,
activePolicies,
approvedClaims,
pendingClaims
}, '获取数据览仓概览成功'));
// 获取总保单数
const totalPolicies = await Policy.count();
// 获取总理赔数
const totalClaims = await Claim.count();
// 获取总保费收入
const totalPremium = await Policy.sum('premium_amount') || 0;
// 获取总理赔支出
const totalClaimAmount = await Claim.sum('claim_amount') || 0;
// 获取活跃保单数
const activePolicies = await Policy.count({
where: {
policy_status: 'active'
}
});
// 获取待处理申请数
const pendingApplications = await InsuranceApplication.count({
where: {
status: 'pending'
}
});
// 获取待处理理赔数
const pendingClaims = await Claim.count({
where: {
claim_status: 'pending'
}
});
res.json({
success: true,
data: {
totalApplications,
totalPolicies,
totalClaims,
totalPremium: parseFloat(totalPremium),
totalClaimAmount: parseFloat(totalClaimAmount),
activePolicies,
pendingApplications,
pendingClaims,
profitLoss: parseFloat(totalPremium) - parseFloat(totalClaimAmount)
}
});
} catch (error) {
console.error('获取数据览仓概览错误:', error);
res.status(500).json(responseFormat.error('获取数据览仓概览失败'));
console.error('获取数据仓库概览失败:', error);
res.status(500).json({
success: false,
message: '获取数据仓库概览失败',
error: error.message
});
}
};
// 获取保险类型分布数据
const getInsuranceTypeDistribution = async (req, res) => {
try {
const types = await InsuranceType.findAll({
attributes: ['id', 'name', 'description'],
where: { status: 'active' }
const distribution = await InsuranceApplication.findAll({
attributes: [
'insurance_type_id',
[Sequelize.fn('COUNT', Sequelize.col('InsuranceApplication.id')), 'count']
],
include: [{
model: InsuranceType,
as: 'insurance_type',
attributes: ['name']
}],
group: ['insurance_type_id', 'insurance_type.id'],
order: [[Sequelize.fn('COUNT', Sequelize.col('InsuranceApplication.id')), 'DESC']]
});
const distribution = await Promise.all(
types.map(async type => {
const count = await InsuranceApplication.count({
where: { insurance_type_id: type.id }
});
// 计算总数用于百分比计算
const totalCount = distribution.reduce((sum, item) => sum + parseInt(item.dataValues.count), 0);
res.json({
success: true,
data: distribution.map(item => {
const count = parseInt(item.dataValues.count);
return {
id: type.id,
name: type.name,
description: type.description,
count
type: item.insurance_type.name,
count: count,
percentage: totalCount > 0 ? ((count / totalCount) * 100).toFixed(2) : 0
};
})
);
res.json(responseFormat.success(distribution, '获取保险类型分布成功'));
});
} catch (error) {
console.error('获取保险类型分布错误:', error);
res.status(500).json(responseFormat.error('获取保险类型分布失败'));
console.error('获取保险类型分布失败:', error);
res.status(500).json({
success: false,
message: '获取保险类型分布失败',
error: error.message
});
}
};
// 获取申请状态分布数据
const getApplicationStatusDistribution = async (req, res) => {
try {
const [
pendingCount,
approvedCount,
rejectedCount,
underReviewCount
] = await Promise.all([
InsuranceApplication.count({ where: { status: 'pending' } }),
InsuranceApplication.count({ where: { status: 'approved' } }),
InsuranceApplication.count({ where: { status: 'rejected' } }),
InsuranceApplication.count({ where: { status: 'under_review' } })
]);
res.json(responseFormat.success([
{ status: 'pending', name: '待处理', count: pendingCount },
{ status: 'under_review', name: '审核中', count: underReviewCount },
{ status: 'approved', name: '已批准', count: approvedCount },
{ status: 'rejected', name: '已拒绝', count: rejectedCount }
], '获取申请状态分布成功'));
const distribution = await InsuranceApplication.findAll({
attributes: [
'status',
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']
],
group: ['status'],
order: [[Sequelize.fn('COUNT', Sequelize.col('id')), 'DESC']]
});
// 计算总数用于百分比计算
const totalCount = distribution.reduce((sum, item) => sum + parseInt(item.dataValues.count), 0);
res.json({
success: true,
data: distribution.map(item => {
const count = parseInt(item.dataValues.count);
return {
status: item.status,
label: getStatusLabel(item.status),
count: count,
percentage: totalCount > 0 ? ((count / totalCount) * 100).toFixed(2) : 0
};
})
});
} catch (error) {
console.error('获取申请状态分布错误:', error);
res.status(500).json(responseFormat.error('获取申请状态分布失败'));
console.error('获取申请状态分布失败:', error);
res.status(500).json({
success: false,
message: '获取申请状态分布失败',
error: error.message
});
}
};
// 状态标签映射
function getStatusLabel(status) {
const statusMap = {
'pending': '待初审',
'initial_approved': '初审通过待复核',
'under_review': '已支付待复核',
'approved': '已批准',
'rejected': '已拒绝',
'paid': '已支付'
};
return statusMap[status] || status;
}
// 获取近7天趋势数据
const getTrendData = async (req, res) => {
try {
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const { days = 7 } = req.query;
const endDate = new Date();
const startDate = new Date();
startDate.setDate(endDate.getDate() - parseInt(days));
// 生成近7天的日期数组
const dateArray = [];
for (let i = 6; i >= 0; i--) {
const date = new Date(sevenDaysAgo);
date.setDate(date.getDate() + i);
dateArray.push(date.toISOString().split('T')[0]); // 格式化为YYYY-MM-DD
}
// 获取每日申请数据
const applicationTrend = await InsuranceApplication.findAll({
attributes: [
[Sequelize.fn('DATE', Sequelize.col('application_date')), 'date'],
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']
],
where: {
application_date: {
[Op.between]: [startDate, endDate]
}
},
group: [Sequelize.fn('DATE', Sequelize.col('application_date'))],
order: [[Sequelize.fn('DATE', Sequelize.col('application_date')), 'ASC']]
});
// 获取每天的新增数据
const dailyData = await Promise.all(
dateArray.map(async date => {
const startDate = new Date(`${date} 00:00:00`);
const endDate = new Date(`${date} 23:59:59`);
const [newApplications, newPolicies, newClaims] = await Promise.all([
InsuranceApplication.count({
where: {
created_at: {
[Op.between]: [startDate, endDate]
}
}
}),
Policy.count({
where: {
created_at: {
[Op.between]: [startDate, endDate]
}
}
}),
Claim.count({
where: {
created_at: {
[Op.between]: [startDate, endDate]
}
}
})
]);
return {
date,
newApplications,
newPolicies,
newClaims
};
})
);
// 获取每日保单数据
const policyTrend = await Policy.findAll({
attributes: [
[Sequelize.fn('DATE', Sequelize.col('created_at')), 'date'],
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']
],
where: {
created_at: {
[Op.between]: [startDate, endDate]
}
},
group: [Sequelize.fn('DATE', Sequelize.col('created_at'))],
order: [[Sequelize.fn('DATE', Sequelize.col('created_at')), 'ASC']]
});
res.json(responseFormat.success(dailyData, '获取趋势数据成功'));
// 获取每日理赔数据
const claimTrend = await Claim.findAll({
attributes: [
[Sequelize.fn('DATE', Sequelize.col('claim_date')), 'date'],
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']
],
where: {
claim_date: {
[Op.between]: [startDate, endDate]
}
},
group: [Sequelize.fn('DATE', Sequelize.col('claim_date'))],
order: [[Sequelize.fn('DATE', Sequelize.col('claim_date')), 'ASC']]
});
res.json({
success: true,
data: {
applications: applicationTrend.map(item => ({
date: item.dataValues.date,
count: parseInt(item.dataValues.count)
})),
policies: policyTrend.map(item => ({
date: item.dataValues.date,
count: parseInt(item.dataValues.count)
})),
claims: claimTrend.map(item => ({
date: item.dataValues.date,
count: parseInt(item.dataValues.count)
}))
}
});
} catch (error) {
console.error('获取趋势数据错误:', error);
res.status(500).json(responseFormat.error('获取趋势数据失败'));
console.error('获取趋势数据失败:', error);
res.status(500).json({
success: false,
message: '获取趋势数据失败',
error: error.message
});
}
};
// 获取赔付统计数据
// 获取理赔统计
const getClaimStats = async (req, res) => {
try {
const claims = await Claim.findAll({
attributes: ['id', 'claim_amount', 'policy_id'],
include: [{
model: Policy,
as: 'policy',
attributes: ['policy_no', 'insurance_type_id']
}]
// 获取理赔状态分布
const claimStatusDistribution = await Claim.findAll({
attributes: [
'claim_status',
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count'],
[Sequelize.fn('SUM', Sequelize.col('claim_amount')), 'total_amount']
],
group: ['claim_status']
});
// 按保险类型分组统计赔付金额
const typeStats = {};
claims.forEach(claim => {
const typeId = claim.policy?.insurance_type_id;
if (typeId) {
if (!typeStats[typeId]) {
typeStats[typeId] = { id: typeId, totalAmount: 0, count: 0 };
// 获取月度理赔趋势
const monthlyClaimTrend = await Claim.findAll({
attributes: [
[Sequelize.fn('DATE_FORMAT', Sequelize.col('claim_date'), '%Y-%m'), 'month'],
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count'],
[Sequelize.fn('SUM', Sequelize.col('claim_amount')), 'total_amount']
],
where: {
claim_date: {
[Op.gte]: Sequelize.literal('DATE_SUB(NOW(), INTERVAL 12 MONTH)')
}
typeStats[typeId].totalAmount += parseFloat(claim.claim_amount || 0);
typeStats[typeId].count += 1;
},
group: [Sequelize.fn('DATE_FORMAT', Sequelize.col('claim_date'), '%Y-%m')],
order: [[Sequelize.fn('DATE_FORMAT', Sequelize.col('claim_date'), '%Y-%m'), 'ASC']]
});
res.json({
success: true,
data: {
statusDistribution: claimStatusDistribution.map(item => ({
status: item.claim_status,
label: getClaimStatusLabel(item.claim_status),
count: parseInt(item.dataValues.count),
totalAmount: parseFloat(item.dataValues.total_amount || 0)
})),
monthlyTrend: monthlyClaimTrend.map(item => ({
month: item.dataValues.month,
count: parseInt(item.dataValues.count),
totalAmount: parseFloat(item.dataValues.total_amount || 0)
}))
}
});
// 获取保险类型名称
const typeIds = Object.keys(typeStats).map(id => parseInt(id));
const types = await InsuranceType.findAll({
attributes: ['id', 'name'],
where: { id: typeIds }
});
types.forEach(type => {
if (typeStats[type.id]) {
typeStats[type.id].name = type.name;
}
});
const result = Object.values(typeStats);
res.json(responseFormat.success(result, '获取赔付统计成功'));
} catch (error) {
console.error('获取赔付统计错误:', error);
res.status(500).json(responseFormat.error('获取赔付统计失败'));
console.error('获取理赔统计失败:', error);
res.status(500).json({
success: false,
message: '获取理赔统计失败',
error: error.message
});
}
};
// 理赔状态标签映射
function getClaimStatusLabel(status) {
const statusMap = {
'pending': '待审核',
'approved': '已批准',
'rejected': '已拒绝',
'processing': '处理中',
'paid': '已支付'
};
return statusMap[status] || status;
}
module.exports = {
getOverview,
getInsuranceTypeDistribution,

View File

@@ -0,0 +1,421 @@
const { DeviceAlert, Device, User } = require('../models');
const { Op } = require('sequelize');
const logger = require('../utils/logger');
/**
* 设备预警控制器
*/
class DeviceAlertController {
/**
* 获取预警统计信息
*/
static async getAlertStats(req, res) {
try {
const { farm_id, start_date, end_date } = req.query;
// 构建查询条件
const whereCondition = {};
if (farm_id) {
whereCondition.farm_id = farm_id;
}
if (start_date && end_date) {
whereCondition.alert_time = {
[Op.between]: [new Date(start_date), new Date(end_date)]
};
}
// 获取总预警数
const totalAlerts = await DeviceAlert.count({
where: whereCondition
});
// 按级别统计
const alertsByLevel = await DeviceAlert.findAll({
attributes: [
'alert_level',
[DeviceAlert.sequelize.fn('COUNT', DeviceAlert.sequelize.col('id')), 'count']
],
where: whereCondition,
group: ['alert_level'],
raw: true
});
// 按状态统计
const alertsByStatus = await DeviceAlert.findAll({
attributes: [
'status',
[DeviceAlert.sequelize.fn('COUNT', DeviceAlert.sequelize.col('id')), 'count']
],
where: whereCondition,
group: ['status'],
raw: true
});
// 按类型统计
const alertsByType = await DeviceAlert.findAll({
attributes: [
'alert_type',
[DeviceAlert.sequelize.fn('COUNT', DeviceAlert.sequelize.col('id')), 'count']
],
where: whereCondition,
group: ['alert_type'],
raw: true
});
// 未读预警数
const unreadAlerts = await DeviceAlert.count({
where: {
...whereCondition,
is_read: false
}
});
// 今日新增预警
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const todayAlerts = await DeviceAlert.count({
where: {
...whereCondition,
alert_time: {
[Op.gte]: today,
[Op.lt]: tomorrow
}
}
});
res.json({
success: true,
data: {
total_alerts: totalAlerts,
unread_alerts: unreadAlerts,
today_alerts: todayAlerts,
alerts_by_level: alertsByLevel,
alerts_by_status: alertsByStatus,
alerts_by_type: alertsByType
}
});
} catch (error) {
logger.error('获取预警统计失败:', error);
res.status(500).json({
success: false,
message: '获取预警统计失败',
error: error.message
});
}
}
/**
* 获取预警列表
*/
static async getAlertList(req, res) {
try {
const {
page = 1,
limit = 20,
alert_level,
status,
alert_type,
farm_id,
start_date,
end_date,
is_read,
device_code,
keyword
} = req.query;
// 构建查询条件
const whereCondition = {};
if (alert_level) {
whereCondition.alert_level = alert_level;
}
if (status) {
whereCondition.status = status;
}
if (alert_type) {
whereCondition.alert_type = alert_type;
}
if (farm_id) {
whereCondition.farm_id = farm_id;
}
if (is_read !== undefined) {
whereCondition.is_read = is_read === 'true';
}
if (start_date && end_date) {
whereCondition.alert_time = {
[Op.between]: [new Date(start_date), new Date(end_date)]
};
}
if (keyword) {
whereCondition[Op.or] = [
{ alert_title: { [Op.like]: `%${keyword}%` } },
{ alert_content: { [Op.like]: `%${keyword}%` } }
];
}
// 设备查询条件
const deviceWhere = {};
if (device_code) {
deviceWhere.device_number = { [Op.like]: `%${device_code}%` };
}
const offset = (page - 1) * limit;
const { count, rows } = await DeviceAlert.findAndCountAll({
where: whereCondition,
include: [
{
model: Device,
as: 'device',
where: Object.keys(deviceWhere).length > 0 ? deviceWhere : undefined,
attributes: ['id', 'device_number', 'device_name', 'device_type', 'installation_location']
},
{
model: User,
as: 'handler',
attributes: ['id', 'username', 'real_name'],
required: false
}
],
order: [['alert_time', 'DESC']],
limit: parseInt(limit),
offset: offset
});
res.json({
success: true,
data: {
alerts: rows,
pagination: {
current_page: parseInt(page),
per_page: parseInt(limit),
total: count,
total_pages: Math.ceil(count / limit)
}
}
});
} catch (error) {
logger.error('获取预警列表失败:', error);
res.status(500).json({
success: false,
message: '获取预警列表失败',
error: error.message
});
}
}
/**
* 获取预警详情
*/
static async getAlertDetail(req, res) {
try {
const { id } = req.params;
const alert = await DeviceAlert.findByPk(id, {
include: [
{
model: Device,
as: 'device',
attributes: ['id', 'device_number', 'device_name', 'device_type', 'device_model', 'manufacturer', 'installation_location']
},
{
model: User,
as: 'handler',
attributes: ['id', 'username', 'real_name'],
required: false
}
]
});
if (!alert) {
return res.status(404).json({
success: false,
message: '预警信息不存在'
});
}
res.json({
success: true,
data: alert
});
} catch (error) {
logger.error('获取预警详情失败:', error);
res.status(500).json({
success: false,
message: '获取预警详情失败',
error: error.message
});
}
}
/**
* 标记预警为已读
*/
static async markAsRead(req, res) {
try {
const { id } = req.params;
const userId = req.user.id;
const alert = await DeviceAlert.findByPk(id);
if (!alert) {
return res.status(404).json({
success: false,
message: '预警信息不存在'
});
}
await alert.update({
is_read: true,
read_time: new Date()
});
res.json({
success: true,
message: '标记已读成功'
});
} catch (error) {
logger.error('标记预警已读失败:', error);
res.status(500).json({
success: false,
message: '标记预警已读失败',
error: error.message
});
}
}
/**
* 批量标记预警为已读
*/
static async batchMarkAsRead(req, res) {
try {
const { alert_ids } = req.body;
const userId = req.user.id;
if (!alert_ids || !Array.isArray(alert_ids) || alert_ids.length === 0) {
return res.status(400).json({
success: false,
message: '请提供有效的预警ID列表'
});
}
await DeviceAlert.update(
{
is_read: true,
read_time: new Date()
},
{
where: {
id: {
[Op.in]: alert_ids
}
}
}
);
res.json({
success: true,
message: '批量标记已读成功'
});
} catch (error) {
logger.error('批量标记预警已读失败:', error);
res.status(500).json({
success: false,
message: '批量标记预警已读失败',
error: error.message
});
}
}
/**
* 处理预警
*/
static async handleAlert(req, res) {
try {
const { id } = req.params;
const { status, handle_note } = req.body;
const userId = req.user.id;
const alert = await DeviceAlert.findByPk(id);
if (!alert) {
return res.status(404).json({
success: false,
message: '预警信息不存在'
});
}
await alert.update({
status,
handler_id: userId,
handle_time: new Date(),
handle_note,
is_read: true,
read_time: alert.read_time || new Date()
});
res.json({
success: true,
message: '预警处理成功'
});
} catch (error) {
logger.error('处理预警失败:', error);
res.status(500).json({
success: false,
message: '处理预警失败',
error: error.message
});
}
}
/**
* 创建预警(系统内部使用)
*/
static async createAlert(req, res) {
try {
const {
device_id,
alert_type,
alert_level,
alert_title,
alert_content,
farm_id,
barn_id
} = req.body;
const alert = await DeviceAlert.create({
device_id,
alert_type,
alert_level,
alert_title,
alert_content,
farm_id,
barn_id,
alert_time: new Date()
});
res.json({
success: true,
message: '预警创建成功',
data: alert
});
} catch (error) {
logger.error('创建预警失败:', error);
res.status(500).json({
success: false,
message: '创建预警失败',
error: error.message
});
}
}
}
module.exports = DeviceAlertController;

View File

@@ -0,0 +1,435 @@
const { Device, DeviceAlert, User } = require('../models');
const { Op } = require('sequelize');
const logger = require('../utils/logger');
/**
* 设备控制器
*/
class DeviceController {
/**
* 获取设备列表
*/
static async getDeviceList(req, res) {
try {
const {
page = 1,
limit = 20,
device_type,
status,
farm_id,
pen_id,
keyword
} = req.query;
// 构建查询条件
const whereCondition = {};
if (device_type) {
whereCondition.device_type = device_type;
}
if (status) {
whereCondition.status = status;
}
if (farm_id) {
whereCondition.farm_id = farm_id;
}
if (pen_id) {
whereCondition.pen_id = pen_id;
}
if (keyword) {
whereCondition[Op.or] = [
{ device_code: { [Op.like]: `%${keyword}%` } },
{ device_name: { [Op.like]: `%${keyword}%` } },
{ device_model: { [Op.like]: `%${keyword}%` } },
{ manufacturer: { [Op.like]: `%${keyword}%` } }
];
}
const offset = (page - 1) * limit;
const { count, rows } = await Device.findAndCountAll({
where: whereCondition,
include: [
{
model: User,
as: 'creator',
attributes: ['id', 'username', 'real_name'],
required: false
}
],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: offset
});
res.json({
success: true,
data: {
devices: rows,
pagination: {
current_page: parseInt(page),
per_page: parseInt(limit),
total: count,
total_pages: Math.ceil(count / limit)
}
}
});
} catch (error) {
logger.error('获取设备列表失败:', error);
res.status(500).json({
success: false,
message: '获取设备列表失败',
error: error.message
});
}
}
/**
* 获取设备详情
*/
static async getDeviceDetail(req, res) {
try {
const { id } = req.params;
const device = await Device.findByPk(id, {
include: [
{
model: User,
as: 'creator',
attributes: ['id', 'username', 'real_name'],
required: false
},
{
model: User,
as: 'updater',
attributes: ['id', 'username', 'real_name'],
required: false
}
]
});
if (!device) {
return res.status(404).json({
success: false,
message: '设备不存在'
});
}
// 获取设备相关的预警统计
const alertStats = await DeviceAlert.findAll({
attributes: [
'alert_level',
[DeviceAlert.sequelize.fn('COUNT', DeviceAlert.sequelize.col('id')), 'count']
],
where: {
device_id: id
},
group: ['alert_level'],
raw: true
});
// 获取最近的预警记录
const recentAlerts = await DeviceAlert.findAll({
where: {
device_id: id
},
order: [['alert_time', 'DESC']],
limit: 5,
attributes: ['id', 'alert_type', 'alert_level', 'alert_title', 'alert_time', 'status']
});
res.json({
success: true,
data: {
device,
alert_stats: alertStats,
recent_alerts: recentAlerts
}
});
} catch (error) {
logger.error('获取设备详情失败:', error);
res.status(500).json({
success: false,
message: '获取设备详情失败',
error: error.message
});
}
}
/**
* 创建设备
*/
static async createDevice(req, res) {
try {
const {
device_code,
device_name,
device_type,
device_model,
manufacturer,
installation_location,
installation_date,
farm_id,
pen_id,
status = 'normal'
} = req.body;
const userId = req.user.id;
// 检查设备编号是否已存在
const existingDevice = await Device.findOne({
where: { device_code }
});
if (existingDevice) {
return res.status(400).json({
success: false,
message: '设备编号已存在'
});
}
const device = await Device.create({
device_code,
device_name,
device_type,
device_model,
manufacturer,
installation_location,
installation_date,
farm_id,
pen_id,
status,
created_by: userId,
updated_by: userId
});
res.json({
success: true,
message: '设备创建成功',
data: device
});
} catch (error) {
logger.error('创建设备失败:', error);
res.status(500).json({
success: false,
message: '创建设备失败',
error: error.message
});
}
}
/**
* 更新设备
*/
static async updateDevice(req, res) {
try {
const { id } = req.params;
const {
device_code,
device_name,
device_type,
device_model,
manufacturer,
installation_location,
installation_date,
farm_id,
pen_id,
status
} = req.body;
const userId = req.user.id;
const device = await Device.findByPk(id);
if (!device) {
return res.status(404).json({
success: false,
message: '设备不存在'
});
}
// 如果修改了设备编号,检查是否与其他设备重复
if (device_code && device_code !== device.device_code) {
const existingDevice = await Device.findOne({
where: {
device_code,
id: { [Op.ne]: id }
}
});
if (existingDevice) {
return res.status(400).json({
success: false,
message: '设备编号已存在'
});
}
}
await device.update({
device_code,
device_name,
device_type,
device_model,
manufacturer,
installation_location,
installation_date,
farm_id,
pen_id,
status,
updated_by: userId
});
res.json({
success: true,
message: '设备更新成功',
data: device
});
} catch (error) {
logger.error('更新设备失败:', error);
res.status(500).json({
success: false,
message: '更新设备失败',
error: error.message
});
}
}
/**
* 删除设备
*/
static async deleteDevice(req, res) {
try {
const { id } = req.params;
const device = await Device.findByPk(id);
if (!device) {
return res.status(404).json({
success: false,
message: '设备不存在'
});
}
// 检查是否有关联的预警记录
const alertCount = await DeviceAlert.count({
where: { device_id: id }
});
if (alertCount > 0) {
return res.status(400).json({
success: false,
message: '该设备存在预警记录,无法删除'
});
}
await device.destroy();
res.json({
success: true,
message: '设备删除成功'
});
} catch (error) {
logger.error('删除设备失败:', error);
res.status(500).json({
success: false,
message: '删除设备失败',
error: error.message
});
}
}
/**
* 获取设备类型列表
*/
static async getDeviceTypes(req, res) {
try {
const deviceTypes = await Device.findAll({
attributes: [
[Device.sequelize.fn('DISTINCT', Device.sequelize.col('device_type')), 'device_type']
],
where: {
device_type: {
[Op.ne]: null
}
},
raw: true
});
res.json({
success: true,
data: deviceTypes.map(item => item.device_type)
});
} catch (error) {
logger.error('获取设备类型失败:', error);
res.status(500).json({
success: false,
message: '获取设备类型失败',
error: error.message
});
}
}
/**
* 获取设备状态统计
*/
static async getDeviceStats(req, res) {
try {
const { farm_id } = req.query;
const whereCondition = {};
if (farm_id) {
whereCondition.farm_id = farm_id;
}
// 按状态统计
const devicesByStatus = await Device.findAll({
attributes: [
'status',
[Device.sequelize.fn('COUNT', Device.sequelize.col('id')), 'count']
],
where: whereCondition,
group: ['status'],
raw: true
});
// 按类型统计
const devicesByType = await Device.findAll({
attributes: [
'device_type',
[Device.sequelize.fn('COUNT', Device.sequelize.col('id')), 'count']
],
where: whereCondition,
group: ['device_type'],
raw: true
});
// 总设备数
const totalDevices = await Device.count({
where: whereCondition
});
res.json({
success: true,
data: {
total_devices: totalDevices,
devices_by_status: devicesByStatus,
devices_by_type: devicesByType
}
});
} catch (error) {
logger.error('获取设备统计失败:', error);
res.status(500).json({
success: false,
message: '获取设备统计失败',
error: error.message
});
}
}
}
module.exports = DeviceController;

View File

@@ -7,11 +7,16 @@ const { User, Role, Menu } = require('../models');
*/
exports.getMenus = async (req, res) => {
try {
console.log('开始获取菜单,用户信息:', req.user);
// 获取用户ID从JWT中解析或通过其他方式获取
const userId = req.user?.id; // 假设通过认证中间件解析后存在
console.log('用户ID:', userId);
// 如果没有用户ID返回基础菜单
if (!userId) {
console.log('没有用户ID返回基础菜单');
const menus = await Menu.findAll({
where: {
parent_id: null,
@@ -33,6 +38,8 @@ exports.getMenus = async (req, res) => {
order: [['order', 'ASC']]
});
console.log('基础菜单查询结果:', menus.length);
return res.json({
code: 200,
status: 'success',
@@ -41,16 +48,20 @@ exports.getMenus = async (req, res) => {
});
}
console.log('查询用户信息...');
// 获取用户信息和角色
const user = await User.findByPk(userId, {
include: [
{
model: Role,
as: 'role',
attributes: ['id', 'name', 'permissions']
}
]
});
console.log('用户查询结果:', user ? '找到用户' : '用户不存在');
if (!user) {
return res.status(404).json({
code: 404,
@@ -60,8 +71,10 @@ exports.getMenus = async (req, res) => {
}
// 获取角色的权限列表
const userPermissions = user.Role?.permissions || [];
const userPermissions = user.role?.permissions || [];
console.log('用户权限:', userPermissions);
console.log('查询菜单数据...');
// 查询菜单,这里简化处理,实际应用中可能需要根据权限过滤
const menus = await Menu.findAll({
where: {
@@ -84,6 +97,8 @@ exports.getMenus = async (req, res) => {
order: [['order', 'ASC']]
});
console.log('菜单查询结果:', menus.length);
// 这里可以添加根据权限过滤菜单的逻辑
// 简化示例,假设所有用户都能看到所有激活的菜单
@@ -95,10 +110,12 @@ exports.getMenus = async (req, res) => {
});
} catch (error) {
console.error('获取菜单失败:', error);
console.error('错误堆栈:', error.stack);
return res.status(500).json({
code: 500,
status: 'error',
message: '服务器内部错误'
message: '服务器内部错误',
error: error.message
});
}
};

View File

@@ -0,0 +1,344 @@
const { OperationLog, User } = require('../models');
const { Op } = require('sequelize');
const ExcelJS = require('exceljs');
/**
* 操作日志控制器
*/
class OperationLogController {
/**
* 获取操作日志列表
*/
async getOperationLogs(req, res) {
try {
const {
page = 1,
limit = 20,
user_id,
operation_type,
operation_module,
status,
start_date,
end_date,
keyword
} = req.query;
// 构建查询条件
const whereConditions = {};
if (user_id) {
whereConditions.user_id = user_id;
}
if (operation_type) {
whereConditions.operation_type = operation_type;
}
if (operation_module) {
whereConditions.operation_module = operation_module;
}
if (status) {
whereConditions.status = status;
}
// 时间范围查询
if (start_date || end_date) {
whereConditions.created_at = {};
if (start_date) {
whereConditions.created_at[Op.gte] = new Date(start_date);
}
if (end_date) {
const endDateTime = new Date(end_date);
endDateTime.setHours(23, 59, 59, 999);
whereConditions.created_at[Op.lte] = endDateTime;
}
}
// 关键词搜索
if (keyword) {
whereConditions[Op.or] = [
{ operation_content: { [Op.like]: `%${keyword}%` } },
{ operation_target: { [Op.like]: `%${keyword}%` } },
{ request_url: { [Op.like]: `%${keyword}%` } },
{ ip_address: { [Op.like]: `%${keyword}%` } }
];
}
// 分页参数
const offset = (parseInt(page) - 1) * parseInt(limit);
// 查询操作日志
const { count, rows } = await OperationLog.findAndCountAll({
where: whereConditions,
include: [
{
model: User,
as: 'user',
attributes: ['id', 'username', 'real_name', 'email']
}
],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: offset
});
const totalPages = Math.ceil(count / parseInt(limit));
res.json({
status: 'success',
message: '获取操作日志列表成功',
data: {
logs: rows,
total: count,
page: parseInt(page),
limit: parseInt(limit),
totalPages: totalPages
}
});
} catch (error) {
console.error('获取操作日志列表失败:', error);
res.status(500).json({
status: 'error',
message: '获取操作日志列表失败',
error: error.message
});
}
}
/**
* 获取操作日志统计信息
*/
async getOperationStats(req, res) {
try {
const {
user_id,
start_date,
end_date
} = req.query;
// 构建查询条件
const whereConditions = {};
if (user_id) {
whereConditions.user_id = user_id;
}
// 时间范围查询
if (start_date || end_date) {
whereConditions.created_at = {};
if (start_date) {
whereConditions.created_at[Op.gte] = new Date(start_date);
}
if (end_date) {
const endDateTime = new Date(end_date);
endDateTime.setHours(23, 59, 59, 999);
whereConditions.created_at[Op.lte] = endDateTime;
}
}
// 获取统计数据
const stats = await OperationLog.getOperationStats(whereConditions);
res.json({
status: 'success',
message: '获取操作日志统计成功',
data: stats
});
} catch (error) {
console.error('获取操作日志统计失败:', error);
res.status(500).json({
status: 'error',
message: '获取操作日志统计失败',
error: error.message
});
}
}
/**
* 获取操作日志详情
*/
async getOperationLogById(req, res) {
try {
const { id } = req.params;
const log = await OperationLog.findByPk(id, {
include: [
{
model: User,
as: 'user',
attributes: ['id', 'username', 'real_name', 'email']
}
]
});
if (!log) {
return res.status(404).json({
status: 'error',
message: '操作日志不存在'
});
}
res.json({
status: 'success',
message: '获取操作日志详情成功',
data: log
});
} catch (error) {
console.error('获取操作日志详情失败:', error);
res.status(500).json({
status: 'error',
message: '获取操作日志详情失败',
error: error.message
});
}
}
/**
* 导出操作日志
*/
async exportOperationLogs(req, res) {
try {
const {
user_id,
operation_type,
operation_module,
status,
start_date,
end_date,
keyword
} = req.body;
// 构建查询条件
const whereConditions = {};
if (user_id) {
whereConditions.user_id = user_id;
}
if (operation_type) {
whereConditions.operation_type = operation_type;
}
if (operation_module) {
whereConditions.operation_module = operation_module;
}
if (status) {
whereConditions.status = status;
}
// 时间范围查询
if (start_date || end_date) {
whereConditions.created_at = {};
if (start_date) {
whereConditions.created_at[Op.gte] = new Date(start_date);
}
if (end_date) {
const endDateTime = new Date(end_date);
endDateTime.setHours(23, 59, 59, 999);
whereConditions.created_at[Op.lte] = endDateTime;
}
}
// 关键词搜索
if (keyword) {
whereConditions[Op.or] = [
{ operation_content: { [Op.like]: `%${keyword}%` } },
{ operation_target: { [Op.like]: `%${keyword}%` } },
{ request_url: { [Op.like]: `%${keyword}%` } },
{ ip_address: { [Op.like]: `%${keyword}%` } }
];
}
// 查询所有符合条件的日志
const logs = await OperationLog.findAll({
where: whereConditions,
include: [
{
model: User,
as: 'user',
attributes: ['id', 'username', 'real_name', 'email']
}
],
order: [['created_at', 'DESC']]
});
// 创建Excel工作簿
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('操作日志');
// 设置表头
worksheet.columns = [
{ header: 'ID', key: 'id', width: 10 },
{ header: '操作用户', key: 'username', width: 15 },
{ header: '真实姓名', key: 'real_name', width: 15 },
{ header: '操作类型', key: 'operation_type', width: 15 },
{ header: '操作模块', key: 'operation_module', width: 15 },
{ header: '操作内容', key: 'operation_content', width: 30 },
{ header: '操作目标', key: 'operation_target', width: 20 },
{ header: '请求方法', key: 'request_method', width: 10 },
{ header: '请求URL', key: 'request_url', width: 30 },
{ header: 'IP地址', key: 'ip_address', width: 15 },
{ header: '执行时间(ms)', key: 'execution_time', width: 12 },
{ header: '状态', key: 'status', width: 10 },
{ header: '错误信息', key: 'error_message', width: 30 },
{ header: '创建时间', key: 'created_at', width: 20 }
];
// 添加数据
logs.forEach(log => {
worksheet.addRow({
id: log.id,
username: log.user ? log.user.username : '',
real_name: log.user ? log.user.real_name : '',
operation_type: log.operation_type,
operation_module: log.operation_module,
operation_content: log.operation_content,
operation_target: log.operation_target,
request_method: log.request_method,
request_url: log.request_url,
ip_address: log.ip_address,
execution_time: log.execution_time,
status: log.status,
error_message: log.error_message,
created_at: log.created_at
});
});
// 设置响应头
const filename = `操作日志_${new Date().toISOString().slice(0, 10)}.xlsx`;
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
// 写入响应
await workbook.xlsx.write(res);
res.end();
// 记录导出操作日志
await OperationLog.logOperation({
user_id: req.user.id,
operation_type: 'export',
operation_module: 'operation_logs',
operation_content: '导出操作日志',
operation_target: `导出${logs.length}条记录`,
request_method: 'POST',
request_url: '/api/operation-logs/export',
request_params: req.body,
ip_address: req.ip,
user_agent: req.get('User-Agent'),
status: 'success'
});
} catch (error) {
console.error('导出操作日志失败:', error);
res.status(500).json({
status: 'error',
message: '导出操作日志失败',
error: error.message
});
}
}
}
module.exports = new OperationLogController();

View File

@@ -1,4 +1,5 @@
const { User, Role } = require('../models');
const { Op } = require('sequelize');
const responseFormat = require('../utils/response');
// 获取用户列表
@@ -222,11 +223,171 @@ const updateUserStatus = async (req, res) => {
}
};
// 获取个人资料
const getProfile = async (req, res) => {
try {
const userId = req.user.id;
const user = await User.findByPk(userId, {
include: [{
model: Role,
as: 'role'
}]
});
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
// 手动排除密码字段
const userProfile = {
id: user.id,
username: user.username,
real_name: user.real_name,
email: user.email,
phone: user.phone,
role_id: user.role_id,
status: user.status,
last_login: user.last_login,
avatar: user.avatar,
created_at: user.created_at,
updated_at: user.updated_at,
role: user.role
};
res.json(responseFormat.success(userProfile, '获取个人资料成功'));
} catch (error) {
console.error('获取个人资料错误:', error);
res.status(500).json(responseFormat.error('获取个人资料失败'));
}
};
// 更新个人资料
const updateProfile = async (req, res) => {
try {
const userId = req.user.id;
const { real_name, email, phone } = req.body;
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
// 检查邮箱是否已被其他用户使用
if (email && email !== user.email) {
const existingEmail = await User.findOne({
where: { email, id: { [Op.ne]: userId } }
});
if (existingEmail) {
return res.status(400).json(responseFormat.error('邮箱已被其他用户使用'));
}
}
// 检查手机号是否已被其他用户使用
if (phone && phone !== user.phone) {
const existingPhone = await User.findOne({
where: { phone, id: { [Op.ne]: userId } }
});
if (existingPhone) {
return res.status(400).json(responseFormat.error('手机号已被其他用户使用'));
}
}
// 更新个人资料
await user.update({
real_name: real_name || user.real_name,
email: email || user.email,
phone: phone || user.phone
});
// 返回更新后的用户信息(不包含密码)
const updatedUser = await User.findByPk(userId, {
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name']
}],
attributes: { exclude: ['password'] }
});
res.json(responseFormat.success(updatedUser, '个人资料更新成功'));
} catch (error) {
console.error('更新个人资料错误:', error);
res.status(500).json(responseFormat.error('更新个人资料失败'));
}
};
// 修改密码
const changePassword = async (req, res) => {
try {
const userId = req.user.id;
const { currentPassword, newPassword } = req.body;
if (!currentPassword || !newPassword) {
return res.status(400).json(responseFormat.error('当前密码和新密码不能为空'));
}
if (newPassword.length < 6) {
return res.status(400).json(responseFormat.error('新密码长度至少6位'));
}
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
// 验证当前密码
const isValidPassword = await user.validatePassword(currentPassword);
if (!isValidPassword) {
return res.status(400).json(responseFormat.error('当前密码错误'));
}
// 更新密码
await user.update({ password: newPassword });
res.json(responseFormat.success(null, '密码修改成功'));
} catch (error) {
console.error('修改密码错误:', error);
res.status(500).json(responseFormat.error('修改密码失败'));
}
};
// 上传头像
const uploadAvatar = async (req, res) => {
try {
const userId = req.user.id;
if (!req.file) {
return res.status(400).json(responseFormat.error('请选择头像文件'));
}
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
// 构建头像URL这里假设文件已经通过multer中间件处理
const avatarUrl = `/uploads/avatars/${req.file.filename}`;
// 更新用户头像
await user.update({ avatar: avatarUrl });
res.json(responseFormat.success({ avatar: avatarUrl }, '头像上传成功'));
} catch (error) {
console.error('上传头像错误:', error);
res.status(500).json(responseFormat.error('头像上传失败'));
}
};
module.exports = {
getUsers,
getUser,
createUser,
updateUser,
deleteUser,
updateUserStatus
updateUserStatus,
getProfile,
updateProfile,
changePassword,
uploadAvatar
};