完善保险端前后端
This commit is contained in:
@@ -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('刷新令牌失败'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
421
insurance_backend/controllers/deviceAlertController.js
Normal file
421
insurance_backend/controllers/deviceAlertController.js
Normal 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;
|
||||
435
insurance_backend/controllers/deviceController.js
Normal file
435
insurance_backend/controllers/deviceController.js
Normal 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;
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
344
insurance_backend/controllers/operationLogController.js
Normal file
344
insurance_backend/controllers/operationLogController.js
Normal 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();
|
||||
@@ -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
|
||||
};
|
||||
Reference in New Issue
Block a user