完善保险端前后端

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

@@ -0,0 +1,55 @@
const mysql = require('mysql2/promise');
async function checkDatabase() {
const connection = await mysql.createConnection({
host: '129.211.213.226',
port: 9527,
user: 'root',
password: 'aiotAiot123!',
database: 'insurance_data'
});
try {
console.log('=== 检查数据库表结构 ===');
// 查看所有表
const [tables] = await connection.execute('SHOW TABLES');
console.log('\n当前数据库中的表:');
tables.forEach(table => {
console.log(`- ${Object.values(table)[0]}`);
});
// 检查每个表的结构和数据量
for (const table of tables) {
const tableName = Object.values(table)[0];
console.log(`\n=== 表: ${tableName} ===`);
// 查看表结构
const [structure] = await connection.execute(`DESCRIBE ${tableName}`);
console.log('表结构:');
structure.forEach(col => {
console.log(` ${col.Field}: ${col.Type} ${col.Null === 'NO' ? 'NOT NULL' : 'NULL'} ${col.Key ? `(${col.Key})` : ''}`);
});
// 查看数据量
const [count] = await connection.execute(`SELECT COUNT(*) as count FROM ${tableName}`);
console.log(`数据量: ${count[0].count} 条记录`);
// 如果有数据,显示前几条
if (count[0].count > 0) {
const [sample] = await connection.execute(`SELECT * FROM ${tableName} LIMIT 3`);
console.log('示例数据:');
sample.forEach((row, index) => {
console.log(` 记录${index + 1}:`, JSON.stringify(row, null, 2));
});
}
}
} catch (error) {
console.error('检查数据库错误:', error);
} finally {
await connection.end();
}
}
checkDatabase();

View File

@@ -0,0 +1,110 @@
const axios = require('axios');
// 模拟前端可能遇到的各种token问题
async function checkFrontendIssues() {
console.log('=== 前端Token问题排查 ===\n');
const browserAPI = axios.create({
baseURL: 'http://localhost:3001',
timeout: 10000
});
try {
// 1. 测试无token访问
console.log('1. 测试无token访问数据仓库接口...');
try {
await browserAPI.get('/api/data-warehouse/overview');
console.log('✅ 无token访问成功 (这不应该发生)');
} catch (error) {
console.log('❌ 无token访问失败:', error.response?.status, error.response?.data?.message);
}
// 2. 测试错误token
console.log('\n2. 测试错误token...');
browserAPI.defaults.headers.common['Authorization'] = 'Bearer invalid_token';
try {
await browserAPI.get('/api/data-warehouse/overview');
console.log('✅ 错误token访问成功 (这不应该发生)');
} catch (error) {
console.log('❌ 错误token访问失败:', error.response?.status, error.response?.data?.message);
}
// 3. 测试过期token
console.log('\n3. 测试过期token...');
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGVfaWQiOjEsInBlcm1pc3Npb25zIjpbXSwiaWF0IjoxNjAwMDAwMDAwLCJleHAiOjE2MDAwMDAwMDB9.invalid';
browserAPI.defaults.headers.common['Authorization'] = `Bearer ${expiredToken}`;
try {
await browserAPI.get('/api/data-warehouse/overview');
console.log('✅ 过期token访问成功 (这不应该发生)');
} catch (error) {
console.log('❌ 过期token访问失败:', error.response?.status, error.response?.data?.message);
}
// 4. 测试正确登录流程
console.log('\n4. 测试正确登录流程...');
const loginResponse = await browserAPI.post('/api/auth/login', {
username: 'admin',
password: '123456'
});
if (loginResponse.data?.code === 200) {
const token = loginResponse.data.data.token;
console.log('✅ 登录成功获取token');
// 5. 测试正确token
console.log('\n5. 测试正确token...');
browserAPI.defaults.headers.common['Authorization'] = `Bearer ${token}`;
try {
const response = await browserAPI.get('/api/data-warehouse/overview');
console.log('✅ 正确token访问成功:', response.status);
} catch (error) {
console.log('❌ 正确token访问失败:', error.response?.status, error.response?.data?.message);
}
// 6. 测试token格式问题
console.log('\n6. 测试各种token格式问题...');
// 测试没有Bearer前缀
browserAPI.defaults.headers.common['Authorization'] = token;
try {
await browserAPI.get('/api/data-warehouse/overview');
console.log('✅ 无Bearer前缀访问成功 (这不应该发生)');
} catch (error) {
console.log('❌ 无Bearer前缀访问失败:', error.response?.status);
}
// 测试错误的Bearer格式
browserAPI.defaults.headers.common['Authorization'] = `bearer ${token}`;
try {
await browserAPI.get('/api/data-warehouse/overview');
console.log('✅ 小写bearer访问成功');
} catch (error) {
console.log('❌ 小写bearer访问失败:', error.response?.status);
}
// 测试多余空格
browserAPI.defaults.headers.common['Authorization'] = `Bearer ${token}`;
try {
await browserAPI.get('/api/data-warehouse/overview');
console.log('✅ 多余空格访问成功');
} catch (error) {
console.log('❌ 多余空格访问失败:', error.response?.status);
}
}
// 7. 检查中间件处理
console.log('\n7. 检查认证中间件...');
console.log('建议检查以下几点:');
console.log('- 浏览器开发者工具中的Network标签页');
console.log('- 请求头中是否包含正确的Authorization');
console.log('- 响应头中是否有CORS相关错误');
console.log('- localStorage中是否正确存储了token');
console.log('- 前端代码中token获取逻辑是否正确');
} catch (error) {
console.log('❌ 测试过程中出错:', error.message);
}
}
checkFrontendIssues();

View File

@@ -0,0 +1,29 @@
const { Menu } = require('./models');
const { sequelize } = require('./models');
async function checkMenus() {
try {
await sequelize.authenticate();
console.log('数据库连接成功');
const menus = await Menu.findAll({
order: [['order', 'ASC']]
});
console.log('菜单数据总数:', menus.length);
if (menus.length > 0) {
console.log('前5条菜单数据:');
menus.slice(0, 5).forEach(menu => {
console.log(`- ID: ${menu.id}, Name: ${menu.name}, Key: ${menu.key}, Path: ${menu.path}`);
});
} else {
console.log('数据库中没有菜单数据');
}
} catch (error) {
console.error('检查菜单数据失败:', error.message);
} finally {
await sequelize.close();
}
}
checkMenus();

View File

@@ -0,0 +1,37 @@
const mysql = require('mysql2/promise');
async function checkTableStructure() {
const connection = await mysql.createConnection({
host: '129.211.213.226',
port: 9527,
user: 'root',
password: 'aiotAiot123!',
database: 'insurance_data'
});
try {
console.log('=== 检查表结构 ===');
const tables = ['insurance_types', 'insurance_applications', 'policies', 'claims'];
for (const table of tables) {
try {
console.log(`\n=== 表: ${table} ===`);
const [columns] = await connection.execute(`DESCRIBE ${table}`);
console.log('字段结构:');
columns.forEach(col => {
console.log(` ${col.Field}: ${col.Type} ${col.Null === 'NO' ? 'NOT NULL' : 'NULL'} ${col.Key ? `(${col.Key})` : ''}`);
});
} catch (error) {
console.log(`${table} 不存在或有错误:`, error.message);
}
}
} catch (error) {
console.error('检查表结构错误:', error);
} finally {
await connection.end();
}
}
checkTableStructure();

View File

@@ -0,0 +1,61 @@
const { User, Role } = require('./models');
async function checkUsers() {
try {
console.log('=== 检查数据库中的用户数据 ===\n');
// 1. 查询所有用户
const users = await User.findAll({
include: [{
model: Role,
as: 'role'
}]
});
console.log(`数据库中共有 ${users.length} 个用户:`);
users.forEach(user => {
console.log(`- ID: ${user.id}, 用户名: ${user.username}, 姓名: ${user.real_name}, 状态: ${user.status}, 角色: ${user.role?.name || '无角色'}`);
});
// 2. 特别检查ID为1的用户
console.log('\n=== 检查ID为1的用户 ===');
const user1 = await User.findByPk(1, {
include: [{
model: Role,
as: 'role'
}]
});
if (user1) {
console.log('✅ 找到ID为1的用户:');
console.log(JSON.stringify(user1.toJSON(), null, 2));
} else {
console.log('❌ 没有找到ID为1的用户');
}
// 3. 检查admin用户
console.log('\n=== 检查admin用户 ===');
const adminUser = await User.findOne({
where: { username: 'admin' },
include: [{
model: Role,
as: 'role'
}]
});
if (adminUser) {
console.log('✅ 找到admin用户:');
console.log(`ID: ${adminUser.id}, 用户名: ${adminUser.username}, 状态: ${adminUser.status}`);
} else {
console.log('❌ 没有找到admin用户');
}
} catch (error) {
console.error('检查用户数据时出错:', error);
} finally {
process.exit(0);
}
}
checkUsers();

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
};

View File

@@ -0,0 +1,112 @@
const mysql = require('mysql2/promise');
async function createMissingTables() {
const connection = await mysql.createConnection({
host: '129.211.213.226',
port: 9527,
user: 'root',
password: 'aiotAiot123!',
database: 'insurance_data'
});
try {
console.log('=== 创建缺失的数据库表 ===');
// 1. 创建保险类型表
await connection.execute(`
CREATE TABLE IF NOT EXISTS insurance_types (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '保险类型ID',
name VARCHAR(100) NOT NULL UNIQUE COMMENT '保险类型名称',
description TEXT NULL COMMENT '保险类型描述',
coverage_amount_min DECIMAL(15,2) NOT NULL DEFAULT 0.00 COMMENT '最小保额',
coverage_amount_max DECIMAL(15,2) NOT NULL DEFAULT 1000000.00 COMMENT '最大保额',
premium_rate DECIMAL(5,4) NOT NULL DEFAULT 0.001 COMMENT '保费费率',
status ENUM('active', 'inactive') NOT NULL DEFAULT 'active' COMMENT '状态',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险类型表'
`);
console.log('✓ 保险类型表创建成功');
// 2. 创建保险申请表
await connection.execute(`
CREATE TABLE IF NOT EXISTS insurance_applications (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '申请ID',
application_no VARCHAR(50) NOT NULL UNIQUE COMMENT '申请编号',
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户手机号',
customer_address VARCHAR(255) NOT NULL COMMENT '客户地址',
insurance_type_id INT NOT NULL COMMENT '保险类型ID',
insurance_category VARCHAR(50) NULL COMMENT '保险类别',
livestock_type VARCHAR(50) NULL COMMENT '牲畜类型',
livestock_count INT NULL DEFAULT 0 COMMENT '牲畜数量',
application_amount DECIMAL(15,2) NOT NULL COMMENT '申请金额',
status ENUM('pending', 'approved', 'rejected', 'under_review') NOT NULL DEFAULT 'pending' COMMENT '状态',
application_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '申请日期',
review_notes TEXT NULL COMMENT '审核备注',
reviewer_id INT NULL COMMENT '审核人ID',
review_date TIMESTAMP NULL COMMENT '审核日期',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险申请表'
`);
console.log('✓ 保险申请表创建成功');
// 3. 创建保单表
await connection.execute(`
CREATE TABLE IF NOT EXISTS policies (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '保单ID',
policy_no VARCHAR(50) NOT NULL UNIQUE COMMENT '保单编号',
application_id INT NOT NULL COMMENT '关联的保险申请ID',
insurance_type_id INT NOT NULL COMMENT '保险类型ID',
customer_id INT NOT NULL COMMENT '客户ID',
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户手机号',
coverage_amount DECIMAL(15,2) NOT NULL COMMENT '保额',
premium_amount DECIMAL(15,2) NOT NULL COMMENT '保费金额',
start_date DATE NOT NULL COMMENT '保险开始日期',
end_date DATE NOT NULL COMMENT '保险结束日期',
policy_status ENUM('active', 'expired', 'cancelled', 'suspended') NOT NULL DEFAULT 'active' COMMENT '保单状态',
payment_status ENUM('paid', 'unpaid', 'partial') NOT NULL DEFAULT 'unpaid' COMMENT '支付状态',
payment_date DATE NULL COMMENT '支付日期',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保单表'
`);
console.log('✓ 保单表创建成功');
// 4. 创建理赔表
await connection.execute(`
CREATE TABLE IF NOT EXISTS claims (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '理赔ID',
claim_no VARCHAR(50) NOT NULL UNIQUE COMMENT '理赔编号',
policy_id INT NOT NULL COMMENT '关联的保单ID',
customer_id INT NOT NULL COMMENT '客户ID',
claim_amount DECIMAL(15,2) NOT NULL COMMENT '理赔金额',
claim_date DATE NOT NULL COMMENT '理赔发生日期',
incident_description TEXT NOT NULL COMMENT '事故描述',
claim_status ENUM('pending', 'approved', 'rejected', 'processing', 'paid') NOT NULL DEFAULT 'pending' COMMENT '理赔状态',
review_notes TEXT NULL COMMENT '审核备注',
reviewer_id INT NULL COMMENT '审核人ID',
review_date DATE NULL COMMENT '审核日期',
payment_date DATE NULL COMMENT '支付日期',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='理赔表'
`);
console.log('✓ 理赔表创建成功');
console.log('\n=== 检查创建的表 ===');
const [tables] = await connection.execute('SHOW TABLES');
console.log('当前数据库表:', tables.map(t => Object.values(t)[0]));
} catch (error) {
console.error('创建表错误:', error);
} finally {
await connection.end();
}
}
createMissingTables();

View File

@@ -0,0 +1,54 @@
const express = require('express');
const cors = require('cors');
// 创建简单的测试应用
const app = express();
const PORT = 3002;
app.use(cors());
app.use(express.json());
// 请求日志
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
// 测试路由
app.get('/health', (req, res) => {
res.json({ status: 'ok', message: '测试服务器运行正常' });
});
// 导入认证路由
try {
const authRoutes = require('./routes/auth');
app.use('/api/auth', authRoutes);
console.log('✅ 认证路由加载成功');
} catch (error) {
console.error('❌ 认证路由加载失败:', error.message);
}
// 导入设备路由
try {
const deviceRoutes = require('./routes/devices');
app.use('/api/devices', deviceRoutes);
console.log('✅ 设备路由加载成功');
} catch (error) {
console.error('❌ 设备路由加载失败:', error.message);
}
// 404处理
app.use('*', (req, res) => {
console.log(`404 - 未找到路由: ${req.method} ${req.originalUrl}`);
res.status(404).json({
code: 404,
status: 'error',
message: '接口不存在',
path: req.originalUrl
});
});
app.listen(PORT, () => {
console.log(`🚀 调试服务器启动在端口 ${PORT}`);
console.log(`📍 测试地址: http://localhost:${PORT}`);
});

View File

@@ -0,0 +1,77 @@
const axios = require('axios');
// 模拟前端登录和API调用流程
async function debugFrontendToken() {
console.log('=== 调试前端Token问题 ===\n');
try {
// 1. 模拟前端登录
console.log('1. 模拟前端登录...');
const loginResponse = await axios.post('http://localhost:3001/api/auth/login', {
username: 'admin',
password: '123456'
});
console.log('登录响应状态:', loginResponse.status);
console.log('登录响应数据:', JSON.stringify(loginResponse.data, null, 2));
if (!loginResponse.data || loginResponse.data.code !== 200) {
console.log('❌ 登录失败');
return;
}
const token = loginResponse.data.data.token;
console.log('✅ 获取到Token:', token.substring(0, 50) + '...');
// 2. 模拟前端API调用 - 使用前端的baseURL
console.log('\n2. 模拟前端API调用...');
// 前端使用的是 /api 作为baseURL实际请求会被代理到 localhost:3000
// 但我们直接测试 localhost:3001 的代理
try {
const apiResponse = await axios.get('http://localhost:3001/api/data-warehouse/overview', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log('✅ API调用成功!');
console.log('状态码:', apiResponse.status);
console.log('响应数据:', JSON.stringify(apiResponse.data, null, 2));
} catch (apiError) {
console.log('❌ API调用失败:', apiError.response?.status, apiError.response?.statusText);
console.log('错误详情:', apiError.response?.data);
// 3. 尝试直接调用后端API
console.log('\n3. 尝试直接调用后端API...');
try {
const directResponse = await axios.get('http://localhost:3000/api/data-warehouse/overview', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log('✅ 直接调用后端成功!');
console.log('状态码:', directResponse.status);
console.log('响应数据:', JSON.stringify(directResponse.data, null, 2));
} catch (directError) {
console.log('❌ 直接调用后端也失败:', directError.response?.status, directError.response?.statusText);
console.log('错误详情:', directError.response?.data);
}
}
// 4. 检查前端代理配置
console.log('\n4. 检查前端代理配置...');
console.log('前端应该配置代理将 /api/* 请求转发到 http://localhost:3000');
console.log('请检查 vite.config.js 或类似的代理配置文件');
} catch (error) {
console.log('❌ 登录失败:', error.response?.data || error.message);
}
}
debugFrontendToken();

View File

@@ -0,0 +1,376 @@
openapi: 3.0.0
info:
title: 保险管理系统 - 数据仓库API
description: 保险管理系统数据仓库模块的API接口文档
version: 1.0.0
contact:
name: 开发团队
email: dev@example.com
servers:
- url: http://localhost:3000/api
description: 本地开发环境
paths:
/data-warehouse/overview:
get:
tags:
- 数据仓库
summary: 获取数据仓库概览
description: 获取保险业务的总体概览数据,包括申请数、保单数、理赔数、保费收入等关键指标
security:
- bearerAuth: []
responses:
'200':
description: 成功获取概览数据
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
data:
type: object
properties:
totalApplications:
type: integer
description: 总申请数
example: 137
totalPolicies:
type: integer
description: 总保单数
example: 26
totalClaims:
type: integer
description: 总理赔数
example: 7
totalPremium:
type: number
format: float
description: 总保费收入
example: 125000.50
totalClaimAmount:
type: number
format: float
description: 总理赔支出
example: 35000.00
activePolicies:
type: integer
description: 活跃保单数
example: 20
pendingApplications:
type: integer
description: 待处理申请数
example: 15
pendingClaims:
type: integer
description: 待处理理赔数
example: 3
profitLoss:
type: number
format: float
description: 盈亏情况(保费收入-理赔支出)
example: 90000.50
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
/data-warehouse/insurance-type-distribution:
get:
tags:
- 数据仓库
summary: 获取保险类型分布
description: 获取各保险类型的申请数量分布统计
security:
- bearerAuth: []
responses:
'200':
description: 成功获取保险类型分布数据
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
data:
type: array
items:
type: object
properties:
type:
type: string
description: 保险类型名称
example: "牛保险"
count:
type: integer
description: 申请数量
example: 45
percentage:
type: string
description: 占比百分比
example: "32.85"
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
/data-warehouse/application-status-distribution:
get:
tags:
- 数据仓库
summary: 获取申请状态分布
description: 获取保险申请各状态的数量分布统计
security:
- bearerAuth: []
responses:
'200':
description: 成功获取申请状态分布数据
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
data:
type: array
items:
type: object
properties:
status:
type: string
description: 申请状态
enum: [pending, initial_approved, under_review, approved, rejected, paid]
example: "pending"
label:
type: string
description: 状态标签
example: "待初审"
count:
type: integer
description: 数量
example: 25
percentage:
type: string
description: 占比百分比
example: "18.25"
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
/data-warehouse/trend-data:
get:
tags:
- 数据仓库
summary: 获取趋势数据
description: 获取指定天数内的申请、保单、理赔趋势数据
security:
- bearerAuth: []
parameters:
- name: days
in: query
description: 查询天数默认7天
required: false
schema:
type: integer
default: 7
minimum: 1
maximum: 365
example: 7
responses:
'200':
description: 成功获取趋势数据
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
data:
type: object
properties:
applications:
type: array
description: 申请趋势数据
items:
type: object
properties:
date:
type: string
format: date
description: 日期
example: "2024-01-15"
count:
type: integer
description: 当日数量
example: 5
policies:
type: array
description: 保单趋势数据
items:
type: object
properties:
date:
type: string
format: date
description: 日期
example: "2024-01-15"
count:
type: integer
description: 当日数量
example: 3
claims:
type: array
description: 理赔趋势数据
items:
type: object
properties:
date:
type: string
format: date
description: 日期
example: "2024-01-15"
count:
type: integer
description: 当日数量
example: 1
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
/data-warehouse/claim-stats:
get:
tags:
- 数据仓库
summary: 获取理赔统计
description: 获取理赔状态分布和月度理赔趋势统计数据
security:
- bearerAuth: []
responses:
'200':
description: 成功获取理赔统计数据
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
data:
type: object
properties:
statusDistribution:
type: array
description: 理赔状态分布
items:
type: object
properties:
status:
type: string
description: 理赔状态
enum: [pending, approved, rejected, processing, paid]
example: "pending"
label:
type: string
description: 状态标签
example: "待审核"
count:
type: integer
description: 数量
example: 5
totalAmount:
type: number
format: float
description: 总金额
example: 15000.00
monthlyTrend:
type: array
description: 月度理赔趋势
items:
type: object
properties:
month:
type: string
description: 月份YYYY-MM格式
example: "2024-01"
count:
type: integer
description: 理赔数量
example: 8
totalAmount:
type: number
format: float
description: 理赔总金额
example: 25000.00
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalServerError'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
responses:
Unauthorized:
description: 未授权访问
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "未授权访问"
InternalServerError:
description: 服务器内部错误
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "服务器内部错误"
error:
type: string
example: "具体错误信息"
schemas:
ErrorResponse:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
description: 错误消息
error:
type: string
description: 详细错误信息
tags:
- name: 数据仓库
description: 数据仓库相关接口,提供业务数据的统计分析功能

View File

@@ -0,0 +1,160 @@
const mysql = require('mysql2/promise');
async function generateTestData() {
const connection = await mysql.createConnection({
host: '129.211.213.226',
port: 9527,
user: 'root',
password: 'aiotAiot123!',
database: 'insurance_data'
});
try {
console.log('=== 生成测试数据 ===');
// 1. 插入保险类型数据
console.log('插入保险类型数据...');
await connection.execute(`
INSERT IGNORE INTO insurance_types (name, description, coverage_amount_min, coverage_amount_max, premium_rate, status) VALUES
('生猪养殖保险', '针对生猪养殖的综合保险,覆盖疾病、意外死亡等风险', 1000.00, 500000.00, 0.0350, 'active'),
('肉牛养殖保险', '针对肉牛养殖的保险产品,保障牲畜健康和意外损失', 2000.00, 800000.00, 0.0280, 'active'),
('奶牛养殖保险', '专为奶牛养殖设计的保险,包含产奶量保障', 3000.00, 1000000.00, 0.0320, 'active'),
('羊群养殖保险', '山羊、绵羊等小型反刍动物的养殖保险', 500.00, 200000.00, 0.0400, 'active'),
('家禽养殖保险', '鸡、鸭、鹅等家禽的养殖风险保险', 300.00, 100000.00, 0.0450, 'active'),
('水产养殖保险', '鱼类、虾类等水产品的养殖保险', 1000.00, 300000.00, 0.0380, 'active'),
('综合农业保险', '涵盖多种农业生产活动的综合性保险产品', 5000.00, 2000000.00, 0.0250, 'active')
`);
// 2. 插入保险申请数据
console.log('插入保险申请数据...');
const applications = [];
const statuses = ['pending', 'approved', 'rejected', 'under_review'];
const insuranceTypes = [1, 2, 3, 4, 5, 6, 7];
const livestockTypes = ['生猪', '肉牛', '奶牛', '山羊', '绵羊', '鸡', '鸭', '鹅', '鱼', '虾'];
for (let i = 1; i <= 150; i++) {
const applicationNo = `APP${new Date().getFullYear()}${String(i).padStart(6, '0')}`;
const customerName = `客户${i}`;
const customerIdCard = `${Math.floor(Math.random() * 900000) + 100000}${Math.floor(Math.random() * 90000000) + 10000000}`;
const customerPhone = `1${Math.floor(Math.random() * 9) + 3}${Math.floor(Math.random() * 900000000) + 100000000}`;
const customerAddress = `某省某市某区某街道${i}`;
const insuranceTypeId = insuranceTypes[Math.floor(Math.random() * insuranceTypes.length)];
const livestockType = livestockTypes[Math.floor(Math.random() * livestockTypes.length)];
const livestockCount = Math.floor(Math.random() * 500) + 10;
const applicationAmount = (Math.random() * 400000 + 10000).toFixed(2);
const status = statuses[Math.floor(Math.random() * statuses.length)];
const applicationDate = new Date(Date.now() - Math.random() * 90 * 24 * 60 * 60 * 1000);
const reviewerId = Math.random() > 0.5 ? Math.floor(Math.random() * 13) + 1 : null;
applications.push([
applicationNo, customerName, customerIdCard, customerPhone, customerAddress,
insuranceTypeId, '畜牧养殖', livestockType, livestockCount, applicationAmount,
status, applicationDate, '系统生成测试数据', reviewerId,
status !== 'pending' ? new Date(applicationDate.getTime() + Math.random() * 7 * 24 * 60 * 60 * 1000) : null
]);
}
for (const app of applications) {
await connection.execute(`
INSERT IGNORE INTO insurance_applications
(application_no, customer_name, customer_id_card, customer_phone, customer_address,
insurance_type_id, insurance_category, application_quantity, application_amount,
status, application_date, review_notes, reviewer_id, review_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
app[0], app[1], app[2], app[3], app[4], app[5], app[6], app[8], app[9],
app[10], app[11], app[12], app[13], app[14]
]);
}
// 3. 插入保单数据(基于已批准的申请)
console.log('插入保单数据...');
const [approvedApps] = await connection.execute(`
SELECT id, application_no, customer_name, customer_id_card, customer_phone,
insurance_type_id, application_amount, application_date
FROM insurance_applications
WHERE status = 'approved'
`);
for (const app of approvedApps) {
const policyNo = `POL${new Date().getFullYear()}${String(app.id).padStart(6, '0')}`;
const coverageAmount = app.application_amount;
const premiumAmount = (app.application_amount * 0.035).toFixed(2);
const startDate = new Date(app.application_date);
startDate.setDate(startDate.getDate() + Math.floor(Math.random() * 30) + 1);
const endDate = new Date(startDate);
endDate.setFullYear(endDate.getFullYear() + 1);
const policyStatus = Math.random() > 0.1 ? 'active' : 'expired';
const paymentStatus = Math.random() > 0.2 ? 'paid' : 'unpaid';
const paymentDate = paymentStatus === 'paid' ? startDate : null;
await connection.execute(`
INSERT IGNORE INTO policies
(policy_no, application_id, insurance_type_id, customer_id,
coverage_amount, premium_amount, start_date, end_date,
policy_status, payment_status, payment_date, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
policyNo, app.id, app.insurance_type_id, 1,
coverageAmount, premiumAmount, startDate, endDate,
policyStatus, paymentStatus, paymentDate, 1
]);
}
// 4. 插入理赔数据(基于有效保单)
console.log('插入理赔数据...');
const [activePolicies] = await connection.execute(`
SELECT id, policy_no, customer_id, coverage_amount, start_date, end_date
FROM policies
WHERE policy_status = 'active' AND payment_status = 'paid'
`);
const claimStatuses = ['pending', 'approved', 'rejected', 'processing', 'paid'];
const incidents = [
'牲畜疾病导致死亡', '自然灾害造成损失', '意外事故导致伤亡',
'饲料中毒事件', '设备故障造成损失', '盗窃事件', '火灾事故'
];
for (let i = 0; i < Math.min(activePolicies.length * 0.3, 80); i++) {
const policy = activePolicies[Math.floor(Math.random() * activePolicies.length)];
const claimNo = `CLM${new Date().getFullYear()}${String(i + 1).padStart(6, '0')}`;
const claimAmount = (Math.random() * policy.coverage_amount * 0.8).toFixed(2);
const claimDate = new Date(policy.start_date.getTime() + Math.random() * (policy.end_date.getTime() - policy.start_date.getTime()));
const incidentDescription = incidents[Math.floor(Math.random() * incidents.length)];
const claimStatus = claimStatuses[Math.floor(Math.random() * claimStatuses.length)];
const reviewerId = Math.random() > 0.3 ? Math.floor(Math.random() * 13) + 1 : null;
const reviewDate = claimStatus !== 'pending' ? new Date(claimDate.getTime() + Math.random() * 14 * 24 * 60 * 60 * 1000) : null;
const paymentDate = claimStatus === 'paid' ? new Date(reviewDate.getTime() + Math.random() * 7 * 24 * 60 * 60 * 1000) : null;
await connection.execute(`
INSERT IGNORE INTO claims
(claim_no, policy_id, customer_id, claim_amount, claim_date,
claim_status, review_notes, reviewer_id, review_date, payment_date, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
claimNo, policy.id, policy.customer_id, claimAmount, claimDate,
claimStatus, '系统生成测试数据', reviewerId, reviewDate, paymentDate, 1
]);
}
console.log('\n=== 测试数据生成完成 ===');
// 显示统计信息
const [typeCount] = await connection.execute('SELECT COUNT(*) as count FROM insurance_types');
const [appCount] = await connection.execute('SELECT COUNT(*) as count FROM insurance_applications');
const [policyCount] = await connection.execute('SELECT COUNT(*) as count FROM policies');
const [claimCount] = await connection.execute('SELECT COUNT(*) as count FROM claims');
console.log(`保险类型: ${typeCount[0].count}`);
console.log(`保险申请: ${appCount[0].count}`);
console.log(`保单: ${policyCount[0].count}`);
console.log(`理赔: ${claimCount[0].count}`);
} catch (error) {
console.error('生成测试数据错误:', error);
} finally {
await connection.end();
}
}
generateTestData();

View File

@@ -11,10 +11,22 @@ const jwtAuth = (req, res, next) => {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 检查Token类型只接受访问令牌
if (decoded.type && decoded.type !== 'access') {
return res.status(401).json(responseFormat.error('无效的令牌类型'));
}
req.user = decoded;
next();
} catch (error) {
return res.status(401).json(responseFormat.error('认证令牌无效或已过期'));
if (error.name === 'TokenExpiredError') {
return res.status(401).json(responseFormat.error('认证令牌已过期', 'TOKEN_EXPIRED'));
} else if (error.name === 'JsonWebTokenError') {
return res.status(401).json(responseFormat.error('认证令牌无效', 'TOKEN_INVALID'));
} else {
return res.status(401).json(responseFormat.error('认证失败', 'AUTH_FAILED'));
}
}
};

View File

@@ -0,0 +1,302 @@
const { OperationLog } = require('../models');
/**
* 操作日志记录中间件
* 自动记录用户的操作行为
*/
class OperationLogger {
/**
* 记录操作日志的中间件
*/
static logOperation(options = {}) {
return async (req, res, next) => {
const startTime = Date.now();
// 保存原始的res.json方法
const originalJson = res.json;
// 重写res.json方法以捕获响应数据
res.json = function(data) {
const endTime = Date.now();
const executionTime = endTime - startTime;
// 异步记录操作日志,不阻塞响应
setImmediate(async () => {
try {
await OperationLogger.recordLog(req, res, data, executionTime, options);
} catch (error) {
console.error('记录操作日志失败:', error);
}
});
// 调用原始的json方法
return originalJson.call(this, data);
};
next();
};
}
/**
* 记录操作日志
*/
static async recordLog(req, res, responseData, executionTime, options) {
try {
// 如果用户未登录,不记录日志
if (!req.user || !req.user.id) {
return;
}
// 获取操作类型
const operationType = OperationLogger.getOperationType(req.method, req.url, options);
// 获取操作模块
const operationModule = OperationLogger.getOperationModule(req.url, options);
// 获取操作内容
const operationContent = OperationLogger.getOperationContent(req, operationType, operationModule, options);
// 获取操作目标
const operationTarget = OperationLogger.getOperationTarget(req, responseData, options);
// 获取操作状态
const status = OperationLogger.getOperationStatus(res.statusCode, responseData);
// 获取错误信息
const errorMessage = OperationLogger.getErrorMessage(responseData, status);
// 记录操作日志
await OperationLog.logOperation({
user_id: req.user.id,
operation_type: operationType,
operation_module: operationModule,
operation_content: operationContent,
operation_target: operationTarget,
request_method: req.method,
request_url: req.originalUrl || req.url,
request_params: {
query: req.query,
body: OperationLogger.sanitizeRequestBody(req.body),
params: req.params
},
response_status: res.statusCode,
response_data: OperationLogger.sanitizeResponseData(responseData),
ip_address: OperationLogger.getClientIP(req),
user_agent: req.get('User-Agent') || '',
execution_time: executionTime,
status: status,
error_message: errorMessage
});
} catch (error) {
console.error('记录操作日志时发生错误:', error);
}
}
/**
* 获取操作类型
*/
static getOperationType(method, url, options) {
if (options.operation_type) {
return options.operation_type;
}
// 根据URL和HTTP方法推断操作类型
if (url.includes('/login')) return 'login';
if (url.includes('/logout')) return 'logout';
if (url.includes('/export')) return 'export';
if (url.includes('/import')) return 'import';
if (url.includes('/approve')) return 'approve';
if (url.includes('/reject')) return 'reject';
switch (method.toUpperCase()) {
case 'GET':
return 'view';
case 'POST':
return 'create';
case 'PUT':
case 'PATCH':
return 'update';
case 'DELETE':
return 'delete';
default:
return 'other';
}
}
/**
* 获取操作模块
*/
static getOperationModule(url, options) {
if (options.operation_module) {
return options.operation_module;
}
// 从URL中提取模块名
const pathSegments = url.split('/').filter(segment => segment && segment !== 'api');
if (pathSegments.length > 0) {
return pathSegments[0].replace(/-/g, '_');
}
return 'unknown';
}
/**
* 获取操作内容
*/
static getOperationContent(req, operationType, operationModule, options) {
if (options.operation_content) {
return options.operation_content;
}
const actionMap = {
'login': '用户登录',
'logout': '用户退出',
'view': '查看',
'create': '创建',
'update': '更新',
'delete': '删除',
'export': '导出',
'import': '导入',
'approve': '审批通过',
'reject': '审批拒绝'
};
const moduleMap = {
'users': '用户',
'roles': '角色',
'insurance': '保险',
'policies': '保单',
'claims': '理赔',
'system': '系统',
'operation_logs': '操作日志',
'devices': '设备',
'device_alerts': '设备告警'
};
const action = actionMap[operationType] || operationType;
const module = moduleMap[operationModule] || operationModule;
return `${action}${module}`;
}
/**
* 获取操作目标
*/
static getOperationTarget(req, responseData, options) {
if (options.operation_target) {
return options.operation_target;
}
// 尝试从请求参数中获取ID
if (req.params.id) {
return `ID: ${req.params.id}`;
}
// 尝试从响应数据中获取信息
if (responseData && responseData.data) {
if (responseData.data.id) {
return `ID: ${responseData.data.id}`;
}
if (responseData.data.name) {
return `名称: ${responseData.data.name}`;
}
if (responseData.data.username) {
return `用户: ${responseData.data.username}`;
}
}
return '';
}
/**
* 获取操作状态
*/
static getOperationStatus(statusCode, responseData) {
if (statusCode >= 200 && statusCode < 300) {
return 'success';
} else if (statusCode >= 400 && statusCode < 500) {
return 'failed';
} else {
return 'error';
}
}
/**
* 获取错误信息
*/
static getErrorMessage(responseData, status) {
if (status === 'success') {
return null;
}
if (responseData && responseData.message) {
return responseData.message;
}
if (responseData && responseData.error) {
return responseData.error;
}
return null;
}
/**
* 清理请求体数据(移除敏感信息)
*/
static sanitizeRequestBody(body) {
if (!body || typeof body !== 'object') {
return body;
}
const sanitized = { ...body };
const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth'];
sensitiveFields.forEach(field => {
if (sanitized[field]) {
sanitized[field] = '***';
}
});
return sanitized;
}
/**
* 清理响应数据(移除敏感信息)
*/
static sanitizeResponseData(data) {
if (!data || typeof data !== 'object') {
return data;
}
// 只保留基本的响应信息,不保存完整的响应数据
return {
status: data.status,
message: data.message,
code: data.code
};
}
/**
* 获取客户端IP地址
*/
static getClientIP(req) {
return req.ip ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
(req.connection.socket ? req.connection.socket.remoteAddress : null) ||
'127.0.0.1';
}
/**
* 创建特定操作的日志记录器
*/
static createLogger(operationType, operationModule, operationContent) {
return OperationLogger.logOperation({
operation_type: operationType,
operation_module: operationModule,
operation_content: operationContent
});
}
}
module.exports = OperationLogger;

View File

@@ -0,0 +1,146 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('operation_logs', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
operation_type: {
type: Sequelize.ENUM(
'login', // 登录
'logout', // 登出
'create', // 创建
'update', // 更新
'delete', // 删除
'view', // 查看
'export', // 导出
'import', // 导入
'approve', // 审批
'reject', // 拒绝
'system_config', // 系统配置
'user_manage', // 用户管理
'role_manage', // 角色管理
'other' // 其他
),
allowNull: false,
comment: '操作类型'
},
operation_module: {
type: Sequelize.STRING(50),
allowNull: false,
comment: '操作模块(如:用户管理、设备管理、预警管理等)'
},
operation_content: {
type: Sequelize.TEXT,
allowNull: false,
comment: '操作内容描述'
},
operation_target: {
type: Sequelize.STRING(100),
allowNull: true,
comment: '操作目标用户ID、设备ID等'
},
request_method: {
type: Sequelize.ENUM('GET', 'POST', 'PUT', 'DELETE', 'PATCH'),
allowNull: true,
comment: 'HTTP请求方法'
},
request_url: {
type: Sequelize.STRING(500),
allowNull: true,
comment: '请求URL'
},
request_params: {
type: Sequelize.TEXT,
allowNull: true,
comment: '请求参数JSON格式'
},
response_status: {
type: Sequelize.INTEGER,
allowNull: true,
comment: '响应状态码'
},
ip_address: {
type: Sequelize.STRING(45),
allowNull: true,
comment: 'IP地址支持IPv6'
},
user_agent: {
type: Sequelize.TEXT,
allowNull: true,
comment: '用户代理信息'
},
execution_time: {
type: Sequelize.INTEGER,
allowNull: true,
comment: '执行时间(毫秒)'
},
status: {
type: Sequelize.ENUM('success', 'failed', 'error'),
defaultValue: 'success',
comment: '操作状态'
},
error_message: {
type: Sequelize.TEXT,
allowNull: true,
comment: '错误信息'
},
created_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
},
updated_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
}
}, {
charset: 'utf8mb4',
collate: 'utf8mb4_unicode_ci',
comment: '系统操作日志表'
});
// 创建索引
await queryInterface.addIndex('operation_logs', ['user_id'], {
name: 'idx_operation_logs_user_id'
});
await queryInterface.addIndex('operation_logs', ['operation_type'], {
name: 'idx_operation_logs_operation_type'
});
await queryInterface.addIndex('operation_logs', ['operation_module'], {
name: 'idx_operation_logs_operation_module'
});
await queryInterface.addIndex('operation_logs', ['created_at'], {
name: 'idx_operation_logs_created_at'
});
await queryInterface.addIndex('operation_logs', ['status'], {
name: 'idx_operation_logs_status'
});
await queryInterface.addIndex('operation_logs', ['ip_address'], {
name: 'idx_operation_logs_ip_address'
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('operation_logs');
}
};

View File

@@ -0,0 +1,190 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
// 创建设备表
await queryInterface.createTable('devices', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '设备ID'
},
device_code: {
type: Sequelize.STRING(50),
allowNull: false,
unique: true,
comment: '设备编号'
},
device_name: {
type: Sequelize.STRING(100),
allowNull: false,
comment: '设备名称'
},
device_type: {
type: Sequelize.STRING(50),
allowNull: false,
comment: '设备类型'
},
device_model: {
type: Sequelize.STRING(100),
comment: '设备型号'
},
manufacturer: {
type: Sequelize.STRING(100),
comment: '制造商'
},
installation_location: {
type: Sequelize.STRING(200),
comment: '安装位置'
},
installation_date: {
type: Sequelize.DATE,
comment: '安装日期'
},
status: {
type: Sequelize.ENUM('normal', 'warning', 'error', 'offline'),
defaultValue: 'normal',
comment: '设备状态'
},
farm_id: {
type: Sequelize.INTEGER,
comment: '所属养殖场ID'
},
pen_id: {
type: Sequelize.INTEGER,
comment: '所属栏舍ID'
},
created_by: {
type: Sequelize.INTEGER,
comment: '创建人ID'
},
updated_by: {
type: Sequelize.INTEGER,
comment: '更新人ID'
},
created_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
comment: '创建时间'
},
updated_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
comment: '更新时间'
}
}, {
comment: '设备信息表'
});
// 创建设备预警表
await queryInterface.createTable('device_alerts', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '预警ID'
},
device_id: {
type: Sequelize.INTEGER,
allowNull: false,
comment: '设备ID',
references: {
model: 'devices',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
alert_type: {
type: Sequelize.STRING(50),
allowNull: false,
comment: '预警类型'
},
alert_level: {
type: Sequelize.ENUM('low', 'medium', 'high', 'critical'),
allowNull: false,
comment: '预警级别'
},
alert_title: {
type: Sequelize.STRING(200),
allowNull: false,
comment: '预警标题'
},
alert_content: {
type: Sequelize.TEXT,
comment: '预警内容'
},
alert_time: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
comment: '预警时间'
},
status: {
type: Sequelize.ENUM('pending', 'processing', 'resolved', 'ignored'),
defaultValue: 'pending',
comment: '处理状态'
},
handler_id: {
type: Sequelize.INTEGER,
comment: '处理人ID',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
handle_time: {
type: Sequelize.DATE,
comment: '处理时间'
},
handle_remark: {
type: Sequelize.TEXT,
comment: '处理备注'
},
farm_id: {
type: Sequelize.INTEGER,
comment: '所属养殖场ID'
},
pen_id: {
type: Sequelize.INTEGER,
comment: '所属栏舍ID'
},
is_read: {
type: Sequelize.BOOLEAN,
defaultValue: false,
comment: '是否已读'
},
read_time: {
type: Sequelize.DATE,
comment: '阅读时间'
},
created_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
comment: '创建时间'
},
updated_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
comment: '更新时间'
}
}, {
comment: '设备预警表'
});
// 添加索引
await queryInterface.addIndex('device_alerts', ['device_id']);
await queryInterface.addIndex('device_alerts', ['alert_level']);
await queryInterface.addIndex('device_alerts', ['status']);
await queryInterface.addIndex('device_alerts', ['alert_time']);
await queryInterface.addIndex('device_alerts', ['farm_id']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('device_alerts');
await queryInterface.dropTable('devices');
}
};

View File

@@ -0,0 +1,86 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* 设备模型
* 用于管理保险相关的设备信息
*/
const Device = sequelize.define('Device', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '设备ID'
},
device_number: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '设备编号'
},
device_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '设备名称'
},
device_type: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '设备类型'
},
device_model: {
type: DataTypes.STRING(100),
comment: '设备型号'
},
manufacturer: {
type: DataTypes.STRING(100),
comment: '制造商'
},
installation_location: {
type: DataTypes.STRING(200),
comment: '安装位置'
},
installation_date: {
type: DataTypes.DATE,
comment: '安装日期'
},
status: {
type: DataTypes.ENUM('normal', 'warning', 'error', 'offline'),
defaultValue: 'normal',
comment: '设备状态normal-正常warning-警告error-故障offline-离线'
},
farm_id: {
type: DataTypes.INTEGER,
comment: '所属养殖场ID'
},
barn_id: {
type: DataTypes.INTEGER,
comment: '所属栏舍ID'
},
created_by: {
type: DataTypes.INTEGER,
comment: '创建人ID'
},
updated_by: {
type: DataTypes.INTEGER,
comment: '更新人ID'
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
comment: '创建时间'
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
comment: '更新时间'
}
}, {
tableName: 'devices',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '设备信息表'
});
module.exports = Device;

View File

@@ -0,0 +1,114 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* 设备预警模型
* 用于管理设备预警信息
*/
const DeviceAlert = sequelize.define('DeviceAlert', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '预警ID'
},
device_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '设备ID'
},
alert_type: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '预警类型'
},
alert_level: {
type: DataTypes.ENUM('info', 'warning', 'critical'),
allowNull: false,
comment: '预警级别info-信息warning-警告critical-严重'
},
alert_title: {
type: DataTypes.STRING(200),
allowNull: false,
comment: '预警标题'
},
alert_content: {
type: DataTypes.TEXT,
comment: '预警内容'
},
alert_time: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '预警时间'
},
status: {
type: DataTypes.ENUM('pending', 'processing', 'resolved', 'ignored'),
defaultValue: 'pending',
comment: '处理状态pending-待处理processing-处理中resolved-已解决ignored-已忽略'
},
handler_id: {
type: DataTypes.INTEGER,
comment: '处理人ID'
},
handle_time: {
type: DataTypes.DATE,
comment: '处理时间'
},
handle_note: {
type: DataTypes.TEXT,
comment: '处理备注'
},
farm_id: {
type: DataTypes.INTEGER,
comment: '所属养殖场ID'
},
barn_id: {
type: DataTypes.INTEGER,
comment: '所属栏舍ID'
},
is_read: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: '是否已读'
},
read_time: {
type: DataTypes.DATE,
comment: '阅读时间'
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
comment: '创建时间'
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
comment: '更新时间'
}
}, {
tableName: 'device_alerts',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
comment: '设备预警表',
indexes: [
{
fields: ['device_id']
},
{
fields: ['alert_level']
},
{
fields: ['status']
},
{
fields: ['alert_time']
},
{
fields: ['farm_id']
}
]
});
module.exports = DeviceAlert;

View File

@@ -0,0 +1,270 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const OperationLog = sequelize.define('OperationLog', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'users',
key: 'id'
},
comment: '操作用户ID'
},
operation_type: {
type: DataTypes.ENUM(
'login', // 登录
'logout', // 登出
'create', // 创建
'update', // 更新
'delete', // 删除
'view', // 查看
'export', // 导出
'import', // 导入
'approve', // 审批
'reject', // 拒绝
'system_config', // 系统配置
'user_manage', // 用户管理
'role_manage', // 角色管理
'other' // 其他
),
allowNull: false,
comment: '操作类型'
},
operation_module: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '操作模块(如:用户管理、设备管理、预警管理等)'
},
operation_content: {
type: DataTypes.TEXT,
allowNull: false,
comment: '操作内容描述'
},
operation_target: {
type: DataTypes.STRING(100),
allowNull: true,
comment: '操作目标用户ID、设备ID等'
},
request_method: {
type: DataTypes.ENUM('GET', 'POST', 'PUT', 'DELETE', 'PATCH'),
allowNull: true,
comment: 'HTTP请求方法'
},
request_url: {
type: DataTypes.STRING(500),
allowNull: true,
comment: '请求URL'
},
request_params: {
type: DataTypes.TEXT,
allowNull: true,
comment: '请求参数JSON格式',
get() {
const value = this.getDataValue('request_params');
return value ? JSON.parse(value) : null;
},
set(value) {
this.setDataValue('request_params', value ? JSON.stringify(value) : null);
}
},
response_status: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '响应状态码'
},
ip_address: {
type: DataTypes.STRING(45),
allowNull: true,
comment: 'IP地址支持IPv6'
},
user_agent: {
type: DataTypes.TEXT,
allowNull: true,
comment: '用户代理信息'
},
execution_time: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '执行时间(毫秒)'
},
status: {
type: DataTypes.ENUM('success', 'failed', 'error'),
defaultValue: 'success',
comment: '操作状态'
},
error_message: {
type: DataTypes.TEXT,
allowNull: true,
comment: '错误信息'
}
}, {
tableName: 'operation_logs',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['user_id'] },
{ fields: ['operation_type'] },
{ fields: ['operation_module'] },
{ fields: ['created_at'] },
{ fields: ['status'] },
{ fields: ['ip_address'] }
]
});
// 定义关联关系
OperationLog.associate = function(models) {
// 操作日志属于用户
OperationLog.belongsTo(models.User, {
foreignKey: 'user_id',
as: 'user',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
};
// 静态方法:记录操作日志
OperationLog.logOperation = async function(logData) {
try {
const log = await this.create({
user_id: logData.userId,
operation_type: logData.operationType,
operation_module: logData.operationModule,
operation_content: logData.operationContent,
operation_target: logData.operationTarget,
request_method: logData.requestMethod,
request_url: logData.requestUrl,
request_params: logData.requestParams,
response_status: logData.responseStatus,
ip_address: logData.ipAddress,
user_agent: logData.userAgent,
execution_time: logData.executionTime,
status: logData.status || 'success',
error_message: logData.errorMessage
});
return log;
} catch (error) {
console.error('记录操作日志失败:', error);
throw error;
}
};
// 静态方法:获取操作日志列表
OperationLog.getLogsList = async function(options = {}) {
const {
page = 1,
limit = 20,
userId,
operationType,
operationModule,
status,
startDate,
endDate,
keyword
} = options;
const where = {};
// 构建查询条件
if (userId) where.user_id = userId;
if (operationType) where.operation_type = operationType;
if (operationModule) where.operation_module = operationModule;
if (status) where.status = status;
// 时间范围查询
if (startDate || endDate) {
where.created_at = {};
if (startDate) where.created_at[sequelize.Op.gte] = new Date(startDate);
if (endDate) where.created_at[sequelize.Op.lte] = new Date(endDate);
}
// 关键词搜索
if (keyword) {
where[sequelize.Op.or] = [
{ operation_content: { [sequelize.Op.like]: `%${keyword}%` } },
{ operation_target: { [sequelize.Op.like]: `%${keyword}%` } }
];
}
const offset = (page - 1) * limit;
const result = await this.findAndCountAll({
where,
include: [{
model: sequelize.models.User,
as: 'user',
attributes: ['id', 'username', 'real_name']
}],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: parseInt(offset)
});
return {
logs: result.rows,
total: result.count,
page: parseInt(page),
limit: parseInt(limit),
totalPages: Math.ceil(result.count / limit)
};
};
// 静态方法:获取操作统计
OperationLog.getOperationStats = async function(options = {}) {
const { startDate, endDate, userId } = options;
const where = {};
if (userId) where.user_id = userId;
if (startDate || endDate) {
where.created_at = {};
if (startDate) where.created_at[sequelize.Op.gte] = new Date(startDate);
if (endDate) where.created_at[sequelize.Op.lte] = new Date(endDate);
}
// 按操作类型统计
const typeStats = await this.findAll({
where,
attributes: [
'operation_type',
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
],
group: ['operation_type'],
raw: true
});
// 按操作模块统计
const moduleStats = await this.findAll({
where,
attributes: [
'operation_module',
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
],
group: ['operation_module'],
raw: true
});
// 按状态统计
const statusStats = await this.findAll({
where,
attributes: [
'status',
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
],
group: ['status'],
raw: true
});
return {
typeStats,
moduleStats,
statusStats
};
};
module.exports = OperationLog;

View File

@@ -12,6 +12,9 @@ const InstallationTask = require('./InstallationTask');
const LivestockType = require('./LivestockType');
const LivestockPolicy = require('./LivestockPolicy');
const LivestockClaim = require('./LivestockClaim');
const Device = require('./Device');
const DeviceAlert = require('./DeviceAlert');
const OperationLog = require('./OperationLog');
// 定义模型关联关系
@@ -150,6 +153,46 @@ LivestockClaim.belongsTo(User, {
as: 'reviewer'
});
// 设备和用户关联
Device.belongsTo(User, {
foreignKey: 'created_by',
as: 'creator'
});
Device.belongsTo(User, {
foreignKey: 'updated_by',
as: 'updater'
});
// 设备预警和设备关联
DeviceAlert.belongsTo(Device, {
foreignKey: 'device_id',
as: 'device'
});
Device.hasMany(DeviceAlert, {
foreignKey: 'device_id',
as: 'alerts'
});
// 设备预警和用户关联
DeviceAlert.belongsTo(User, {
foreignKey: 'handler_id',
as: 'handler'
});
User.hasMany(DeviceAlert, {
foreignKey: 'handler_id',
as: 'handled_alerts'
});
// 操作日志和用户关联
OperationLog.belongsTo(User, {
foreignKey: 'user_id',
as: 'user'
});
User.hasMany(OperationLog, {
foreignKey: 'user_id',
as: 'operation_logs'
});
// 导出所有模型
module.exports = {
sequelize,
@@ -164,5 +207,8 @@ module.exports = {
InstallationTask,
LivestockType,
LivestockPolicy,
LivestockClaim
LivestockClaim,
Device,
DeviceAlert,
OperationLog
};

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,7 @@
"bcrypt": "^5.1.0",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"exceljs": "^4.4.0",
"express": "^4.18.2",
"express-rate-limit": "^8.1.0",
"helmet": "^8.1.0",

View File

@@ -2,6 +2,7 @@ const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const { jwtAuth } = require('../middleware/auth');
const OperationLogger = require('../middleware/operationLogger');
/**
* @swagger
@@ -92,7 +93,7 @@ router.post('/register', authController.register);
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
router.post('/login', authController.login);
router.post('/login', OperationLogger.createLogger('login', 'auth', '用户登录'), authController.login);
/**
* @swagger
@@ -168,7 +169,7 @@ router.post('/refresh', authController.refreshToken);
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
router.post('/logout', jwtAuth, authController.logout);
router.post('/logout', jwtAuth, OperationLogger.createLogger('logout', 'auth', '用户退出'), authController.logout);
/**
* @swagger

View File

@@ -0,0 +1,485 @@
const express = require('express');
const router = express.Router();
const deviceAlertController = require('../controllers/deviceAlertController');
const { jwtAuth } = require('../middleware/auth');
/**
* @swagger
* /api/device-alerts/stats:
* get:
* tags:
* - 设备预警
* summary: 获取预警统计信息
* description: 获取设备预警的统计数据,包括总数、按级别分类、按状态分类等
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: farm_id
* schema:
* type: integer
* description: 养殖场ID
* - in: query
* name: start_date
* schema:
* type: string
* format: date
* description: 开始日期
* - in: query
* name: end_date
* schema:
* type: string
* format: date
* description: 结束日期
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* total_alerts:
* type: integer
* description: 总预警数
* unread_alerts:
* type: integer
* description: 未读预警数
* today_alerts:
* type: integer
* description: 今日新增预警数
* alerts_by_level:
* type: array
* items:
* type: object
* properties:
* alert_level:
* type: string
* count:
* type: integer
* alerts_by_status:
* type: array
* items:
* type: object
* properties:
* status:
* type: string
* count:
* type: integer
* alerts_by_type:
* type: array
* items:
* type: object
* properties:
* alert_type:
* type: string
* count:
* type: integer
*/
router.get('/stats', jwtAuth, deviceAlertController.getAlertStats);
/**
* @swagger
* /api/device-alerts:
* get:
* tags:
* - 设备预警
* summary: 获取预警列表
* description: 分页获取设备预警列表,支持多种筛选条件
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* default: 20
* description: 每页数量
* - in: query
* name: alert_level
* schema:
* type: string
* enum: [low, medium, high, critical]
* description: 预警级别
* - in: query
* name: status
* schema:
* type: string
* enum: [pending, processing, resolved, ignored]
* description: 处理状态
* - in: query
* name: alert_type
* schema:
* type: string
* description: 预警类型
* - in: query
* name: farm_id
* schema:
* type: integer
* description: 养殖场ID
* - in: query
* name: start_date
* schema:
* type: string
* format: date
* description: 开始日期
* - in: query
* name: end_date
* schema:
* type: string
* format: date
* description: 结束日期
* - in: query
* name: is_read
* schema:
* type: boolean
* description: 是否已读
* - in: query
* name: device_code
* schema:
* type: string
* description: 设备编号
* - in: query
* name: keyword
* schema:
* type: string
* description: 关键词搜索(标题或内容)
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* alerts:
* type: array
* items:
* $ref: '#/components/schemas/DeviceAlert'
* pagination:
* $ref: '#/components/schemas/Pagination'
*/
router.get('/', jwtAuth, deviceAlertController.getAlertList);
/**
* @swagger
* /api/device-alerts/{id}:
* get:
* tags:
* - 设备预警
* summary: 获取预警详情
* description: 根据ID获取设备预警的详细信息
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 预警ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* $ref: '#/components/schemas/DeviceAlert'
* 404:
* description: 预警信息不存在
*/
router.get('/:id', jwtAuth, deviceAlertController.getAlertDetail);
/**
* @swagger
* /api/device-alerts/{id}/read:
* put:
* tags:
* - 设备预警
* summary: 标记预警为已读
* description: 将指定的预警标记为已读状态
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 预警ID
* responses:
* 200:
* description: 标记成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 404:
* description: 预警信息不存在
*/
router.put('/:id/read', jwtAuth, deviceAlertController.markAsRead);
/**
* @swagger
* /api/device-alerts/batch/read:
* put:
* tags:
* - 设备预警
* summary: 批量标记预警为已读
* description: 批量将多个预警标记为已读状态
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - alert_ids
* properties:
* alert_ids:
* type: array
* items:
* type: integer
* description: 预警ID列表
* responses:
* 200:
* description: 批量标记成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
*/
router.put('/batch/read', jwtAuth, deviceAlertController.batchMarkAsRead);
/**
* @swagger
* /api/device-alerts/{id}/handle:
* put:
* tags:
* - 设备预警
* summary: 处理预警
* description: 处理指定的设备预警,更新处理状态和备注
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 预警ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - status
* properties:
* status:
* type: string
* enum: [processing, resolved, ignored]
* description: 处理状态
* handle_remark:
* type: string
* description: 处理备注
* responses:
* 200:
* description: 处理成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 404:
* description: 预警信息不存在
*/
router.put('/:id/handle', jwtAuth, deviceAlertController.handleAlert);
/**
* @swagger
* /api/device-alerts:
* post:
* tags:
* - 设备预警
* summary: 创建预警
* description: 创建新的设备预警(系统内部使用)
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - device_id
* - alert_type
* - alert_level
* - alert_title
* - alert_content
* properties:
* device_id:
* type: integer
* description: 设备ID
* alert_type:
* type: string
* description: 预警类型
* alert_level:
* type: string
* enum: [low, medium, high, critical]
* description: 预警级别
* alert_title:
* type: string
* description: 预警标题
* alert_content:
* type: string
* description: 预警内容
* farm_id:
* type: integer
* description: 养殖场ID
* pen_id:
* type: integer
* description: 栏舍ID
* responses:
* 200:
* description: 创建成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* data:
* $ref: '#/components/schemas/DeviceAlert'
*/
router.post('/', jwtAuth, deviceAlertController.createAlert);
/**
* @swagger
* components:
* schemas:
* DeviceAlert:
* type: object
* properties:
* id:
* type: integer
* description: 预警ID
* device_id:
* type: integer
* description: 设备ID
* alert_type:
* type: string
* description: 预警类型
* alert_level:
* type: string
* enum: [low, medium, high, critical]
* description: 预警级别
* alert_title:
* type: string
* description: 预警标题
* alert_content:
* type: string
* description: 预警内容
* alert_time:
* type: string
* format: date-time
* description: 预警时间
* status:
* type: string
* enum: [pending, processing, resolved, ignored]
* description: 处理状态
* handler_id:
* type: integer
* description: 处理人ID
* handle_time:
* type: string
* format: date-time
* description: 处理时间
* handle_remark:
* type: string
* description: 处理备注
* farm_id:
* type: integer
* description: 养殖场ID
* pen_id:
* type: integer
* description: 栏舍ID
* is_read:
* type: boolean
* description: 是否已读
* read_time:
* type: string
* format: date-time
* description: 阅读时间
* created_at:
* type: string
* format: date-time
* description: 创建时间
* updated_at:
* type: string
* format: date-time
* description: 更新时间
* device:
* $ref: '#/components/schemas/Device'
* handler:
* $ref: '#/components/schemas/User'
* Pagination:
* type: object
* properties:
* current_page:
* type: integer
* description: 当前页码
* per_page:
* type: integer
* description: 每页数量
* total:
* type: integer
* description: 总记录数
* total_pages:
* type: integer
* description: 总页数
*/
module.exports = router;

View File

@@ -0,0 +1,467 @@
const express = require('express');
const router = express.Router();
const deviceController = require('../controllers/deviceController');
const { jwtAuth } = require('../middleware/auth');
/**
* @swagger
* /api/devices:
* get:
* tags:
* - 设备管理
* summary: 获取设备列表
* description: 分页获取设备列表,支持多种筛选条件
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* default: 20
* description: 每页数量
* - in: query
* name: device_type
* schema:
* type: string
* description: 设备类型
* - in: query
* name: status
* schema:
* type: string
* enum: [normal, maintenance, fault, offline]
* description: 设备状态
* - in: query
* name: farm_id
* schema:
* type: integer
* description: 养殖场ID
* - in: query
* name: pen_id
* schema:
* type: integer
* description: 栏舍ID
* - in: query
* name: keyword
* schema:
* type: string
* description: 关键词搜索(设备编号、名称、型号、制造商)
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* devices:
* type: array
* items:
* $ref: '#/components/schemas/Device'
* pagination:
* $ref: '#/components/schemas/Pagination'
*/
router.get('/', jwtAuth, deviceController.getDeviceList);
/**
* @swagger
* /api/devices/stats:
* get:
* tags:
* - 设备管理
* summary: 获取设备统计信息
* description: 获取设备的统计数据,包括总数、按状态分类、按类型分类等
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: farm_id
* schema:
* type: integer
* description: 养殖场ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* total_devices:
* type: integer
* description: 总设备数
* devices_by_status:
* type: array
* items:
* type: object
* properties:
* status:
* type: string
* count:
* type: integer
* devices_by_type:
* type: array
* items:
* type: object
* properties:
* device_type:
* type: string
* count:
* type: integer
*/
router.get('/stats', jwtAuth, deviceController.getDeviceStats);
/**
* @swagger
* /api/devices/types:
* get:
* tags:
* - 设备管理
* summary: 获取设备类型列表
* description: 获取系统中所有的设备类型
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: array
* items:
* type: string
*/
router.get('/types', jwtAuth, deviceController.getDeviceTypes);
/**
* @swagger
* /api/devices/{id}:
* get:
* tags:
* - 设备管理
* summary: 获取设备详情
* description: 根据ID获取设备的详细信息包括预警统计和最近预警记录
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 设备ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* device:
* $ref: '#/components/schemas/Device'
* alert_stats:
* type: array
* items:
* type: object
* properties:
* alert_level:
* type: string
* count:
* type: integer
* recent_alerts:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* alert_type:
* type: string
* alert_level:
* type: string
* alert_title:
* type: string
* alert_time:
* type: string
* format: date-time
* status:
* type: string
* 404:
* description: 设备不存在
*/
router.get('/:id', jwtAuth, deviceController.getDeviceDetail);
/**
* @swagger
* /api/devices:
* post:
* tags:
* - 设备管理
* summary: 创建设备
* description: 创建新的设备记录
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - device_code
* - device_name
* - device_type
* properties:
* device_code:
* type: string
* description: 设备编号
* device_name:
* type: string
* description: 设备名称
* device_type:
* type: string
* description: 设备类型
* device_model:
* type: string
* description: 设备型号
* manufacturer:
* type: string
* description: 制造商
* installation_location:
* type: string
* description: 安装位置
* installation_date:
* type: string
* format: date
* description: 安装日期
* farm_id:
* type: integer
* description: 养殖场ID
* pen_id:
* type: integer
* description: 栏舍ID
* status:
* type: string
* enum: [normal, maintenance, fault, offline]
* default: normal
* description: 设备状态
* responses:
* 200:
* description: 创建成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* data:
* $ref: '#/components/schemas/Device'
* 400:
* description: 设备编号已存在
*/
router.post('/', jwtAuth, deviceController.createDevice);
/**
* @swagger
* /api/devices/{id}:
* put:
* tags:
* - 设备管理
* summary: 更新设备
* description: 更新指定设备的信息
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 设备ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* device_code:
* type: string
* description: 设备编号
* device_name:
* type: string
* description: 设备名称
* device_type:
* type: string
* description: 设备类型
* device_model:
* type: string
* description: 设备型号
* manufacturer:
* type: string
* description: 制造商
* installation_location:
* type: string
* description: 安装位置
* installation_date:
* type: string
* format: date
* description: 安装日期
* farm_id:
* type: integer
* description: 养殖场ID
* pen_id:
* type: integer
* description: 栏舍ID
* status:
* type: string
* enum: [normal, maintenance, fault, offline]
* description: 设备状态
* responses:
* 200:
* description: 更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* data:
* $ref: '#/components/schemas/Device'
* 404:
* description: 设备不存在
* 400:
* description: 设备编号已存在
*/
router.put('/:id', jwtAuth, deviceController.updateDevice);
/**
* @swagger
* /api/devices/{id}:
* delete:
* tags:
* - 设备管理
* summary: 删除设备
* description: 删除指定的设备记录
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 设备ID
* responses:
* 200:
* description: 删除成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 404:
* description: 设备不存在
* 400:
* description: 该设备存在预警记录,无法删除
*/
router.delete('/:id', jwtAuth, deviceController.deleteDevice);
/**
* @swagger
* components:
* schemas:
* Device:
* type: object
* properties:
* id:
* type: integer
* description: 设备ID
* device_code:
* type: string
* description: 设备编号
* device_name:
* type: string
* description: 设备名称
* device_type:
* type: string
* description: 设备类型
* device_model:
* type: string
* description: 设备型号
* manufacturer:
* type: string
* description: 制造商
* installation_location:
* type: string
* description: 安装位置
* installation_date:
* type: string
* format: date
* description: 安装日期
* status:
* type: string
* enum: [normal, maintenance, fault, offline]
* description: 设备状态
* farm_id:
* type: integer
* description: 养殖场ID
* pen_id:
* type: integer
* description: 栏舍ID
* created_by:
* type: integer
* description: 创建人ID
* updated_by:
* type: integer
* description: 更新人ID
* created_at:
* type: string
* format: date-time
* description: 创建时间
* updated_at:
* type: string
* format: date-time
* description: 更新时间
* creator:
* $ref: '#/components/schemas/User'
*/
module.exports = router;

View File

@@ -0,0 +1,319 @@
const express = require('express');
const router = express.Router();
const operationLogController = require('../controllers/operationLogController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
/**
* @swagger
* tags:
* name: OperationLogs
* description: 系统操作日志管理
*/
/**
* @swagger
* /api/operation-logs:
* get:
* summary: 获取操作日志列表
* tags: [OperationLogs]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* default: 20
* description: 每页数量
* - in: query
* name: user_id
* schema:
* type: integer
* description: 用户ID
* - in: query
* name: operation_type
* schema:
* type: string
* enum: [login, logout, create, update, delete, view, export, import, approve, reject, system_config, user_manage, role_manage, other]
* description: 操作类型
* - in: query
* name: operation_module
* schema:
* type: string
* description: 操作模块
* - in: query
* name: status
* schema:
* type: string
* enum: [success, failed, error]
* description: 操作状态
* - in: query
* name: start_date
* schema:
* type: string
* format: date
* description: 开始日期
* - in: query
* name: end_date
* schema:
* type: string
* format: date
* description: 结束日期
* - in: query
* name: keyword
* schema:
* type: string
* description: 关键词搜索
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: success
* data:
* type: object
* properties:
* logs:
* type: array
* items:
* $ref: '#/components/schemas/OperationLog'
* total:
* type: integer
* page:
* type: integer
* limit:
* type: integer
* totalPages:
* type: integer
*/
router.get('/', jwtAuth, checkPermission('system', 'read'), operationLogController.getOperationLogs);
/**
* @swagger
* /api/operation-logs/stats:
* get:
* summary: 获取操作日志统计信息
* tags: [OperationLogs]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: user_id
* schema:
* type: integer
* description: 用户ID
* - in: query
* name: start_date
* schema:
* type: string
* format: date
* description: 开始日期
* - in: query
* name: end_date
* schema:
* type: string
* format: date
* description: 结束日期
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: success
* data:
* type: object
* properties:
* typeStats:
* type: array
* items:
* type: object
* properties:
* operation_type:
* type: string
* count:
* type: integer
* moduleStats:
* type: array
* items:
* type: object
* properties:
* operation_module:
* type: string
* count:
* type: integer
* statusStats:
* type: array
* items:
* type: object
* properties:
* status:
* type: string
* count:
* type: integer
*/
router.get('/stats', jwtAuth, checkPermission('system', 'read'), operationLogController.getOperationStats);
/**
* @swagger
* /api/operation-logs/{id}:
* get:
* summary: 获取操作日志详情
* tags: [OperationLogs]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 操作日志ID
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: success
* data:
* $ref: '#/components/schemas/OperationLog'
*/
router.get('/:id', jwtAuth, checkPermission('system', 'read'), operationLogController.getOperationLogById);
/**
* @swagger
* /api/operation-logs/export:
* post:
* summary: 导出操作日志
* tags: [OperationLogs]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* user_id:
* type: integer
* operation_type:
* type: string
* operation_module:
* type: string
* status:
* type: string
* start_date:
* type: string
* format: date
* end_date:
* type: string
* format: date
* keyword:
* type: string
* responses:
* 200:
* description: 导出成功
* content:
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
* schema:
* type: string
* format: binary
*/
router.post('/export', jwtAuth, checkPermission('system', 'export'), operationLogController.exportOperationLogs);
/**
* @swagger
* components:
* schemas:
* OperationLog:
* type: object
* properties:
* id:
* type: integer
* description: 日志ID
* user_id:
* type: integer
* description: 操作用户ID
* operation_type:
* type: string
* enum: [login, logout, create, update, delete, view, export, import, approve, reject, system_config, user_manage, role_manage, other]
* description: 操作类型
* operation_module:
* type: string
* description: 操作模块
* operation_content:
* type: string
* description: 操作内容描述
* operation_target:
* type: string
* description: 操作目标
* request_method:
* type: string
* enum: [GET, POST, PUT, DELETE, PATCH]
* description: HTTP请求方法
* request_url:
* type: string
* description: 请求URL
* request_params:
* type: object
* description: 请求参数
* response_status:
* type: integer
* description: 响应状态码
* ip_address:
* type: string
* description: IP地址
* user_agent:
* type: string
* description: 用户代理信息
* execution_time:
* type: integer
* description: 执行时间(毫秒)
* status:
* type: string
* enum: [success, failed, error]
* description: 操作状态
* error_message:
* type: string
* description: 错误信息
* created_at:
* type: string
* format: date-time
* description: 创建时间
* updated_at:
* type: string
* format: date-time
* description: 更新时间
* user:
* type: object
* properties:
* id:
* type: integer
* username:
* type: string
* real_name:
* type: string
*/
module.exports = router;

View File

@@ -6,6 +6,13 @@ const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取用户列表(需要管理员权限)
router.get('/', jwtAuth, checkPermission('user', 'read'), userController.getUsers);
// 个人中心相关路由(必须放在 /:id 路由之前)
// 获取个人资料(不需要特殊权限,用户可以查看自己的资料)
router.get('/profile', jwtAuth, userController.getProfile);
// 更新个人资料(不需要特殊权限,用户可以更新自己的资料)
router.put('/profile', jwtAuth, userController.updateProfile);
// 获取单个用户信息
router.get('/:id', jwtAuth, checkPermission('user', 'read'), userController.getUser);
@@ -21,4 +28,10 @@ router.delete('/:id', jwtAuth, checkPermission('user', 'delete'), userController
// 更新用户状态
router.patch('/:id/status', jwtAuth, checkPermission('user', 'update'), userController.updateUserStatus);
// 修改密码(不需要特殊权限,用户可以修改自己的密码)
router.put('/change-password', jwtAuth, userController.changePassword);
// 上传头像(不需要特殊权限,用户可以上传自己的头像)
router.post('/avatar', jwtAuth, userController.uploadAvatar);
module.exports = router;

View File

@@ -0,0 +1,43 @@
const mysql = require('mysql2/promise');
async function checkTableStructure() {
try {
// 创建数据库连接
const connection = await mysql.createConnection({
host: '129.211.213.226',
port: 9527,
user: 'root',
password: 'aiotAiot123!',
database: 'insurance_data'
});
console.log('✅ 数据库连接成功');
// 查看devices表结构
console.log('\n📋 devices表结构:');
const [devicesColumns] = await connection.execute('DESCRIBE devices');
console.table(devicesColumns);
// 查看device_alerts表结构
console.log('\n📋 device_alerts表结构:');
const [alertsColumns] = await connection.execute('DESCRIBE device_alerts');
console.table(alertsColumns);
await connection.end();
console.log('\n✅ 检查完成');
} catch (error) {
console.error('❌ 检查表结构失败:', error);
}
}
if (require.main === module) {
checkTableStructure().then(() => {
process.exit(0);
}).catch(error => {
console.error('❌ 脚本执行失败:', error);
process.exit(1);
});
}
module.exports = checkTableStructure;

View File

@@ -0,0 +1,96 @@
const mysql = require('mysql2/promise');
const jwt = require('jsonwebtoken');
async function checkUserPermissions() {
const connection = await mysql.createConnection({
host: '129.211.213.226',
port: 9527,
user: 'root',
password: 'aiotAiot123!',
database: 'insurance_data'
});
try {
console.log('=== 检查用户权限和JWT Token ===');
// 1. 查询admin用户信息
const [adminUsers] = await connection.execute(
'SELECT * FROM users WHERE username = ?',
['admin']
);
if (adminUsers.length === 0) {
console.log('❌ Admin用户不存在');
return;
}
const adminUser = adminUsers[0];
console.log('\n1. Admin用户信息:');
console.log(`- ID: ${adminUser.id}`);
console.log(`- 用户名: ${adminUser.username}`);
console.log(`- 角色ID: ${adminUser.role_id}`);
console.log(`- 状态: ${adminUser.status}`);
// 2. 查询admin角色权限
const [roles] = await connection.execute(
'SELECT * FROM roles WHERE id = ?',
[adminUser.role_id]
);
if (roles.length === 0) {
console.log('❌ Admin角色不存在');
return;
}
const adminRole = roles[0];
console.log('\n2. Admin角色信息:');
console.log(`- 角色名: ${adminRole.name}`);
console.log(`- 权限类型: ${typeof adminRole.permissions}`);
console.log(`- 权限内容: ${JSON.stringify(adminRole.permissions, null, 2)}`);
// 3. 模拟JWT token生成
console.log('\n3. 模拟JWT Token生成:');
const tokenPayload = {
id: adminUser.id,
username: adminUser.username,
role_id: adminUser.role_id,
permissions: adminRole.permissions || []
};
console.log('Token Payload:', JSON.stringify(tokenPayload, null, 2));
// 使用默认密钥生成token实际应用中应该从环境变量获取
const jwtSecret = process.env.JWT_SECRET || 'your_jwt_secret_key';
const token = jwt.sign(tokenPayload, jwtSecret, { expiresIn: '7d' });
console.log('\n4. 生成的JWT Token:');
console.log(token);
// 5. 验证token
console.log('\n5. 验证JWT Token:');
try {
const decoded = jwt.verify(token, jwtSecret);
console.log('解码后的Token:', JSON.stringify(decoded, null, 2));
// 检查权限
const hasDataRead = decoded.permissions &&
(Array.isArray(decoded.permissions) ?
decoded.permissions.includes('data:read') :
decoded.permissions.includes && decoded.permissions.includes('data:read'));
console.log(`\n6. 权限检查结果:`);
console.log(`- 是否有data:read权限: ${hasDataRead}`);
console.log(`- 权限数组长度: ${Array.isArray(decoded.permissions) ? decoded.permissions.length : 'N/A'}`);
} catch (error) {
console.error('Token验证失败:', error.message);
}
} catch (error) {
console.error('检查时出错:', error);
} finally {
await connection.end();
}
}
checkUserPermissions();

View File

@@ -0,0 +1,89 @@
const { sequelize } = require('../models');
async function createTables() {
try {
console.log('开始创建设备相关表...');
// 创建设备表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS devices (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '设备ID',
device_number VARCHAR(50) NOT NULL UNIQUE COMMENT '设备编号',
device_name VARCHAR(100) NOT NULL COMMENT '设备名称',
device_type VARCHAR(50) NOT NULL COMMENT '设备类型',
device_model VARCHAR(100) COMMENT '设备型号',
manufacturer VARCHAR(100) COMMENT '制造商',
installation_location VARCHAR(200) COMMENT '安装位置',
installation_date DATE COMMENT '安装日期',
status ENUM('normal', 'warning', 'error', 'offline') DEFAULT 'normal' COMMENT '设备状态',
farm_id INT COMMENT '养殖场ID',
barn_id INT COMMENT '栏舍ID',
created_by INT COMMENT '创建人ID',
updated_by INT COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_device_number (device_number),
INDEX idx_device_type (device_type),
INDEX idx_status (status),
INDEX idx_farm_barn (farm_id, barn_id),
INDEX idx_created_by (created_by),
INDEX idx_updated_by (updated_by)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备表';
`);
console.log('✅ 设备表创建成功');
// 创建设备预警表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS device_alerts (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '预警ID',
device_id INT NOT NULL COMMENT '设备ID',
alert_type VARCHAR(50) NOT NULL COMMENT '预警类型',
alert_level ENUM('info', 'warning', 'critical') NOT NULL COMMENT '预警级别',
alert_title VARCHAR(200) NOT NULL COMMENT '预警标题',
alert_content TEXT COMMENT '预警内容',
alert_time TIMESTAMP NOT NULL COMMENT '预警时间',
status ENUM('pending', 'processing', 'resolved', 'ignored') DEFAULT 'pending' COMMENT '处理状态',
handler_id INT COMMENT '处理人ID',
handle_time TIMESTAMP NULL COMMENT '处理时间',
handle_note TEXT COMMENT '处理备注',
farm_id INT COMMENT '养殖场ID',
barn_id INT COMMENT '栏舍ID',
is_read BOOLEAN DEFAULT FALSE COMMENT '是否已读',
read_time TIMESTAMP NULL COMMENT '阅读时间',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_device_id (device_id),
INDEX idx_alert_type (alert_type),
INDEX idx_alert_level (alert_level),
INDEX idx_alert_time (alert_time),
INDEX idx_status (status),
INDEX idx_farm_barn (farm_id, barn_id),
INDEX idx_handler_id (handler_id),
INDEX idx_is_read (is_read),
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE,
FOREIGN KEY (handler_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备预警表';
`);
console.log('✅ 设备预警表创建成功');
console.log('🎉 所有表创建完成!');
} catch (error) {
console.error('❌ 创建表失败:', error);
throw error;
}
}
// 如果直接运行此脚本
if (require.main === module) {
createTables().then(() => {
console.log('数据库表创建完成');
process.exit(0);
}).catch(error => {
console.error('创建失败:', error);
process.exit(1);
});
}
module.exports = createTables;

View File

@@ -0,0 +1,161 @@
const { Device, DeviceAlert, User } = require('../models');
async function createTestData() {
try {
console.log('开始创建测试数据...');
// 获取第一个用户作为创建者
const user = await User.findOne();
if (!user) {
console.log('❌ 没有找到用户,请先创建用户');
return;
}
// 检查是否已有测试设备
const existingDevice = await Device.findOne({ where: { device_number: 'DEV001' } });
let devices;
if (existingDevice) {
console.log('📋 测试设备已存在,使用现有设备');
devices = await Device.findAll({
where: {
device_number: ['DEV001', 'DEV002', 'DEV003']
}
});
} else {
// 创建测试设备
devices = await Device.bulkCreate([
{
device_number: 'DEV001',
device_name: '温度传感器A',
device_type: '温度传感器',
device_model: 'TMP-100',
manufacturer: '智能科技',
installation_location: '1号栏舍',
installation_date: new Date('2024-01-15'),
status: 'normal',
farm_id: 1,
barn_id: 1,
created_by: user.id,
updated_by: user.id
},
{
device_number: 'DEV002',
device_name: '湿度传感器B',
device_type: '湿度传感器',
device_model: 'HUM-200',
manufacturer: '智能科技',
installation_location: '2号栏舍',
installation_date: new Date('2024-01-20'),
status: 'warning',
farm_id: 1,
barn_id: 2,
created_by: user.id,
updated_by: user.id
},
{
device_number: 'DEV003',
device_name: '监控摄像头C',
device_type: '监控设备',
device_model: 'CAM-300',
manufacturer: '安防科技',
installation_location: '3号栏舍',
installation_date: new Date('2024-02-01'),
status: 'error',
farm_id: 1,
barn_id: 3,
created_by: user.id,
updated_by: user.id
}
]);
console.log(`✅ 创建了 ${devices.length} 个测试设备`);
}
console.log(`📊 当前有 ${devices.length} 个测试设备`);
// 检查是否已有测试预警
const existingAlert = await DeviceAlert.findOne({ where: { device_id: devices[0].id } });
let alerts;
if (existingAlert) {
console.log('📋 测试预警已存在,清除旧数据并创建新数据');
await DeviceAlert.destroy({ where: { device_id: devices.map(d => d.id) } });
}
// 创建测试预警
alerts = await DeviceAlert.bulkCreate([
{
device_id: devices[0].id,
alert_type: 'temperature',
alert_level: 'warning',
alert_title: '温度异常',
alert_content: '1号栏舍温度传感器检测到温度过高当前温度35°C',
alert_time: new Date(),
status: 'pending',
farm_id: 1,
barn_id: 1,
is_read: false
},
{
device_id: devices[1].id,
alert_type: 'humidity',
alert_level: 'critical',
alert_title: '湿度严重异常',
alert_content: '2号栏舍湿度传感器检测到湿度过低当前湿度30%',
alert_time: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2小时前
status: 'pending',
farm_id: 1,
barn_id: 2,
is_read: false
},
{
device_id: devices[2].id,
alert_type: 'offline',
alert_level: 'critical',
alert_title: '设备离线',
alert_content: '3号栏舍监控摄像头已离线超过30分钟',
alert_time: new Date(Date.now() - 4 * 60 * 60 * 1000), // 4小时前
status: 'pending',
farm_id: 1,
barn_id: 3,
is_read: true,
read_time: new Date(Date.now() - 3 * 60 * 60 * 1000)
},
{
device_id: devices[0].id,
alert_type: 'maintenance',
alert_level: 'info',
alert_title: '设备维护提醒',
alert_content: '温度传感器A需要进行定期维护检查',
alert_time: new Date(Date.now() - 24 * 60 * 60 * 1000), // 1天前
status: 'resolved',
handler_id: user.id,
handle_time: new Date(Date.now() - 20 * 60 * 60 * 1000),
handle_note: '已完成维护检查,设备运行正常',
farm_id: 1,
barn_id: 1,
is_read: true,
read_time: new Date(Date.now() - 23 * 60 * 60 * 1000)
}
]);
console.log(`✅ 创建了 ${alerts.length} 个测试预警`);
console.log('🎉 测试数据创建完成!');
} catch (error) {
console.error('❌ 创建测试数据失败:', error);
}
}
// 如果直接运行此脚本
if (require.main === module) {
createTestData().then(() => {
process.exit(0);
}).catch(error => {
console.error(error);
process.exit(1);
});
}
module.exports = createTestData;

View File

@@ -0,0 +1,76 @@
const axios = require('axios');
const jwt = require('jsonwebtoken');
async function testFrontendAuth() {
const baseURL = 'http://localhost:3000';
try {
console.log('=== 测试前端认证流程 ===');
// 1. 模拟前端登录
console.log('\n1. 模拟前端登录...');
const loginResponse = await axios.post(`${baseURL}/api/auth/login`, {
username: 'admin',
password: '123456'
});
if (loginResponse.data.status === 'success' || loginResponse.data.success) {
console.log('✅ 登录成功');
const token = loginResponse.data.data.token;
console.log(`Token: ${token.substring(0, 50)}...`);
// 2. 解码token查看内容
console.log('\n2. 解码JWT Token:');
try {
const jwtSecret = 'insurance_super_secret_jwt_key_2024';
const decoded = jwt.verify(token, jwtSecret);
console.log('Token内容:', JSON.stringify(decoded, null, 2));
// 检查权限
const hasDataRead = decoded.permissions &&
(Array.isArray(decoded.permissions) ?
decoded.permissions.includes('data:read') :
decoded.permissions.includes && decoded.permissions.includes('data:read'));
console.log(`\n权限检查:`);
console.log(`- 是否有data:read权限: ${hasDataRead}`);
console.log(`- 权限数组长度: ${Array.isArray(decoded.permissions) ? decoded.permissions.length : 'N/A'}`);
} catch (error) {
console.error('❌ Token解码失败:', error.message);
}
// 3. 测试数据仓库接口
console.log('\n3. 测试数据仓库接口访问:');
try {
const overviewResponse = await axios.get(`${baseURL}/api/data-warehouse/overview`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
console.log('✅ 数据仓库接口访问成功');
console.log('响应状态:', overviewResponse.status);
console.log('响应数据:', JSON.stringify(overviewResponse.data, null, 2));
} catch (error) {
console.error('❌ 数据仓库接口访问失败:');
console.error('状态码:', error.response?.status);
console.error('错误信息:', error.response?.data);
console.error('完整错误:', error.message);
}
} else {
console.error('❌ 登录失败:', loginResponse.data);
}
} catch (error) {
console.error('❌ 测试过程中出错:', error.message);
if (error.response) {
console.error('响应状态:', error.response.status);
console.error('响应数据:', error.response.data);
}
}
}
testFrontendAuth();

View File

@@ -13,7 +13,7 @@ const PORT = process.env.PORT || 3000;
// 安全中间件
app.use(helmet());
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
origin: process.env.FRONTEND_URL || 'http://localhost:3001',
credentials: true
}));
@@ -56,6 +56,7 @@ app.use('/api/insurance-types', require('../routes/insuranceTypes'));
app.use('/api/policies', require('../routes/policies'));
app.use('/api/claims', require('../routes/claims'));
app.use('/api/system', require('../routes/system'));
app.use('/api/operation-logs', require('../routes/operationLogs'));
app.use('/api/menus', require('../routes/menus'));
app.use('/api/data-warehouse', require('../routes/dataWarehouse'));
app.use('/api/supervisory-tasks', require('../routes/supervisoryTasks'));
@@ -68,6 +69,10 @@ app.use('/api/livestock-types', require('../routes/livestockTypes'));
app.use('/api/livestock-policies', require('../routes/livestockPolicies'));
app.use('/api/livestock-claims', require('../routes/livestockClaims'));
// 设备管理相关路由
app.use('/api/devices', require('../routes/devices'));
app.use('/api/device-alerts', require('../routes/deviceAlerts'));
// API文档路由
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
explorer: true,

View File

@@ -0,0 +1,29 @@
const express = require('express');
// 测试路由加载
console.log('开始测试路由加载...');
try {
// 测试设备路由
const deviceRoutes = require('./routes/devices');
console.log('✅ 设备路由加载成功');
// 测试设备控制器
const deviceController = require('./controllers/deviceController');
console.log('✅ 设备控制器加载成功');
// 测试模型
const { Device, DeviceAlert } = require('./models');
console.log('✅ 设备模型加载成功');
// 创建简单的Express应用测试
const app = express();
app.use('/api/devices', deviceRoutes);
console.log('✅ 路由注册成功');
console.log('所有组件加载正常!');
} catch (error) {
console.error('❌ 路由加载失败:', error.message);
console.error('错误详情:', error);
}

View File

@@ -0,0 +1,99 @@
const axios = require('axios');
// 创建一个模拟浏览器的axios实例
const browserAPI = axios.create({
baseURL: 'http://localhost:3001',
timeout: 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
});
async function testBrowserBehavior() {
console.log('=== 模拟浏览器行为测试 ===\n');
try {
// 1. 模拟前端登录
console.log('1. 模拟浏览器登录...');
const loginResponse = await browserAPI.post('/api/auth/login', {
username: 'admin',
password: '123456'
});
console.log('登录响应状态:', loginResponse.status);
console.log('登录响应数据:', JSON.stringify(loginResponse.data, null, 2));
if (!loginResponse.data || loginResponse.data.code !== 200) {
console.log('❌ 登录失败');
return;
}
const token = loginResponse.data.data.token;
console.log('✅ 获取到Token:', token.substring(0, 50) + '...');
// 2. 设置Authorization header
browserAPI.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// 3. 模拟前端API调用
console.log('\n2. 模拟浏览器API调用...');
try {
const apiResponse = await browserAPI.get('/api/data-warehouse/overview');
console.log('✅ API调用成功!');
console.log('状态码:', apiResponse.status);
console.log('响应数据:', JSON.stringify(apiResponse.data, null, 2));
} catch (apiError) {
console.log('❌ API调用失败:', apiError.response?.status, apiError.response?.statusText);
console.log('错误详情:', apiError.response?.data);
console.log('请求头:', apiError.config?.headers);
// 检查是否是权限问题
if (apiError.response?.status === 403) {
console.log('\n🔍 403错误分析:');
console.log('- Token是否正确传递:', !!apiError.config?.headers?.Authorization);
console.log('- Authorization头:', apiError.config?.headers?.Authorization?.substring(0, 50) + '...');
// 尝试验证token
console.log('\n3. 验证Token有效性...');
try {
const profileResponse = await browserAPI.get('/api/auth/profile');
console.log('✅ Token验证成功用户信息:', profileResponse.data);
} catch (profileError) {
console.log('❌ Token验证失败:', profileError.response?.status, profileError.response?.data);
}
}
}
// 4. 测试其他需要权限的接口
console.log('\n4. 测试其他权限接口...');
const testAPIs = [
'/api/insurance/applications',
'/api/device-alerts/stats',
'/api/system/stats'
];
for (const apiPath of testAPIs) {
try {
const response = await browserAPI.get(apiPath);
console.log(`${apiPath}: 成功 (${response.status})`);
} catch (error) {
console.log(`${apiPath}: 失败 (${error.response?.status}) - ${error.response?.data?.message || error.message}`);
}
}
} catch (error) {
console.log('❌ 测试失败:', error.response?.data || error.message);
if (error.response) {
console.log('错误状态:', error.response.status);
console.log('错误数据:', error.response.data);
}
}
}
testBrowserBehavior();

View File

@@ -0,0 +1,32 @@
const axios = require('axios');
async function testMenuAPI() {
try {
// 1. 先登录获取token
console.log('1. 登录获取token...');
const loginResponse = await axios.post('http://localhost:3000/api/auth/login', {
username: 'admin',
password: '123456'
});
const token = loginResponse.data.data.accessToken;
console.log('登录成功token:', token.substring(0, 50) + '...');
// 2. 测试菜单接口
console.log('\n2. 测试菜单接口...');
const menuResponse = await axios.get('http://localhost:3000/api/menus', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log('菜单接口响应状态:', menuResponse.status);
console.log('菜单接口响应数据:', JSON.stringify(menuResponse.data, null, 2));
} catch (error) {
console.error('测试失败:', error.response?.data || error.message);
}
}
testMenuAPI();