部署保险端项目和大屏

This commit is contained in:
xuqiuyun
2025-09-30 17:41:21 +08:00
parent 4e8d4dc92d
commit e9be0f9d98
767 changed files with 50062 additions and 24144 deletions

Binary file not shown.

View File

@@ -102,7 +102,7 @@ export const useUserStore = defineStore('user', () => {
}
try {
const response = await fetch('http://localhost:3000/api/auth/refresh', {
const response = await fetch('/insurance/api/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@@ -4,7 +4,7 @@ import router from '@/router'
// API基础配置
const API_CONFIG = {
baseURL: 'http://localhost:3000/api',
baseURL: '/insurance/api',
timeout: 10000
}

View File

@@ -45,8 +45,8 @@
allow-clear
style="width: 120px"
>
<a-select-option value="individual">个人参保</a-select-option>
<a-select-option value="enterprise">企业参保</a-select-option>
<a-select-option value="养殖">养殖</a-select-option>
<a-select-option value="畜牧养殖">畜牧养殖</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="申请状态">
@@ -100,7 +100,7 @@
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'insurance_category'">
{{ record.insurance_category === 'individual' ? '个人' : '企业' }}
{{ record.insurance_category }}
</template>
<template v-else-if="column.key === 'insurance_amount'">
¥{{ Number(record.insurance_amount).toLocaleString() }}
@@ -173,7 +173,7 @@
{{ selectedApplication.application_no }}
</a-descriptions-item>
<a-descriptions-item label="参保类型">
{{ selectedApplication.insurance_category === 'individual' ? '个人参保' : '企业参保' }}
{{ selectedApplication.insurance_category }}
</a-descriptions-item>
<a-descriptions-item label="参保险种">
{{ selectedApplication.insurance_type?.name || selectedApplication.insurance_type_id }}
@@ -273,8 +273,8 @@
>
<a-form-item label="参保类型" name="insurance_category" :rules="[{ required: true, message: '请选择参保类型' }]">
<a-radio-group v-model:value="createForm.insurance_category">
<a-radio value="individual">个人参保</a-radio>
<a-radio value="enterprise">企业参保</a-radio>
<a-radio value="养殖">养殖</a-radio>
<a-radio value="畜牧养殖">畜牧养殖</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="参保险种" name="insurance_type_id" :rules="[{ required: true, message: '请选择参保险种' }]">
@@ -466,11 +466,26 @@ const columns = [
const loadApplications = async () => {
loading.value = true
try {
// 构建搜索参数,映射前端字段名到后端期望的字段名
const params = {
page: pagination.current,
limit: pagination.pageSize,
...searchForm
// 字段名映射
applicationNumber: searchForm.application_no || undefined,
applicantName: searchForm.customer_name || undefined,
insuranceType: searchForm.insuranceType || undefined,
insuranceCategory: searchForm.insuranceCategory || undefined,
status: searchForm.status || undefined
}
// 过滤掉undefined值
Object.keys(params).forEach(key => {
if (params[key] === undefined) {
delete params[key]
}
})
console.log('搜索参数:', params)
const response = await applicationAPI.getList(params)
console.log('申请列表API响应:', response)

View File

@@ -59,7 +59,7 @@
<!-- 图表区域 -->
<a-row :gutter="16" style="margin-top: 24px">
<a-col :span="12">
<a-card title="保险申请趋势" :bordered="false">
<a-card title="申请状态分布" :bordered="false">
<div style="height: 300px">
<v-chart
:option="applicationTrendOption"
@@ -71,7 +71,7 @@
</a-col>
<a-col :span="12">
<a-card title="保单状态分布" :bordered="false">
<a-card title="险种分布" :bordered="false">
<div style="height: 300px">
<v-chart
:option="policyDistributionOption"
@@ -122,7 +122,7 @@ import {
BarChartOutlined,
PieChartOutlined
} from '@ant-design/icons-vue'
import { dashboardAPI } from '@/utils/api'
import { dashboardAPI, policyAPI, insuranceTypeAPI, applicationAPI } from '@/utils/api'
import { message } from 'ant-design-vue'
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
@@ -279,169 +279,66 @@ const loadDashboardData = async () => {
created_at: new Date(Date.now() - 10800000).toISOString()
}
]
// 加载图表数据
await loadChartData()
} finally {
loading.value = false
}
// 无论统计数据和活动数据是否成功,都要加载图表数据
await loadChartData()
}
// 加载图表数据
const loadChartData = async () => {
console.log('开始加载图表数据...')
chartLoading.value = true
// 独立加载申请状态分布数据
try {
// 获取申请趋势数据
console.log('正在获取申请趋势数据...')
const applicationTrendResponse = await dashboardAPI.getChartData({
type: 'applications',
period: '7d'
console.log('正在获取申请状态分布数据...')
const applicationsResponse = await applicationAPI.getList({
page: 1,
limit: 100 // 获取所有申请用于状态统计
})
console.log('申请趋势响应:', applicationTrendResponse)
if (applicationTrendResponse.data && applicationTrendResponse.data.status === 'success') {
console.log('设置申请趋势图表,数据:', applicationTrendResponse.data.data)
setupApplicationTrendChart(applicationTrendResponse.data.data)
console.log('申请状态分布响应:', applicationsResponse)
if (applicationsResponse.data && applicationsResponse.data.status === 'success') {
console.log('设置申请状态分布图表,数据:', applicationsResponse.data.data)
setupApplicationStatusChart(applicationsResponse.data.data)
} else {
console.log('申请趋势响应格式错误:', applicationTrendResponse)
}
// 获取保单状态分布数据
console.log('正在获取保单状态分布数据...')
const policyDistributionResponse = await dashboardAPI.getChartData({
type: 'policy_status'
})
console.log('保单状态分布响应:', policyDistributionResponse)
if (policyDistributionResponse.data && policyDistributionResponse.data.status === 'success') {
console.log('设置保单状态分布图表,数据:', policyDistributionResponse.data.data)
setupPolicyDistributionChart(policyDistributionResponse.data.data)
} else {
console.log('保单状态分布响应格式错误:', policyDistributionResponse)
console.log('申请状态分布响应格式错误:', applicationsResponse)
}
} catch (error) {
console.error('加载图表数据失败:', error)
console.error('错误详情:', error.response?.data || error.message)
message.error('加载图表数据失败')
} finally {
chartLoading.value = false
console.log('图表数据加载完成')
console.error('加载申请状态分布数据失败:', error)
}
// 独立加载险种分布数据
try {
console.log('正在获取险种分布数据...')
const insuranceTypesResponse = await insuranceTypeAPI.getList({
page: 1,
pageSize: 100, // 获取所有险种用于分布统计
name: ''
})
console.log('险种分布响应:', insuranceTypesResponse)
if (insuranceTypesResponse.data && insuranceTypesResponse.data.status === 'success') {
console.log('设置险种分布图表,数据:', insuranceTypesResponse.data.data)
setupInsuranceTypeDistributionChart(insuranceTypesResponse.data.data)
} else {
console.log('险种分布响应格式错误:', insuranceTypesResponse)
}
} catch (error) {
console.error('加载险种分布数据失败:', error)
message.error('加载险种分布数据失败')
}
chartLoading.value = false
console.log('图表数据加载完成')
}
// 设置申请趋势图表
const setupApplicationTrendChart = (data) => {
console.log('设置申请趋势图表,接收数据:', data)
if (!data || !Array.isArray(data) || data.length === 0) {
console.warn('申请趋势数据为空或格式错误')
return
}
const dates = data.map(item => item.date)
const counts = data.map(item => item.value || item.count)
console.log('处理后的数据 - 日期:', dates, '数量:', counts)
applicationTrendOption.value = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#ccc',
borderWidth: 1,
textStyle: {
color: '#333'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: dates,
axisLine: {
lineStyle: {
color: '#d9d9d9'
}
},
axisTick: {
alignWithLabel: true
},
axisLabel: {
color: '#666'
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: '#d9d9d9'
}
},
axisLabel: {
color: '#666'
},
splitLine: {
lineStyle: {
color: '#f0f0f0'
}
}
},
series: [
{
name: '申请数量',
type: 'bar',
data: counts,
barWidth: '60%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: '#1890ff'
}, {
offset: 1, color: '#40a9ff'
}]
},
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: '#0050b3'
}
}
}
]
}
}
// 设置保单状态分布图表
const setupPolicyDistributionChart = (data) => {
console.log('设置保单状态分布图表,接收数据:', data)
if (!data || !Array.isArray(data) || data.length === 0) {
console.warn('保单状态分布数据为空或格式错误')
return
}
const chartData = data.map(item => ({
name: getPolicyStatusLabel(item.status),
value: item.count
}))
console.log('处理后的图表数据:', chartData)
policyDistributionOption.value = {
// 创建饼图配置的通用函数
const createPieChartOption = (seriesName, data) => {
return {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)',
@@ -462,9 +359,10 @@ const setupPolicyDistributionChart = (data) => {
},
itemGap: 15
},
color: ['#1890ff', '#52c41a', '#faad14', '#f5222d', '#722ed1', '#13c2c2', '#eb2f96', '#fa8c16'],
series: [
{
name: '保单数量',
name: seriesName,
type: 'pie',
radius: ['45%', '75%'],
center: ['65%', '50%'],
@@ -489,7 +387,7 @@ const setupPolicyDistributionChart = (data) => {
labelLine: {
show: false
},
data: chartData,
data: data,
itemStyle: {
borderRadius: 8,
borderColor: '#fff',
@@ -500,6 +398,78 @@ const setupPolicyDistributionChart = (data) => {
}
}
// 设置申请状态分布图表
const setupApplicationStatusChart = (data) => {
console.log('设置申请状态分布图表,接收数据:', data)
if (!data || !Array.isArray(data) || data.length === 0) {
console.warn('申请状态分布数据为空或格式错误,使用默认数据')
// 使用默认数据
const defaultData = [
{ name: '待审核', value: 0 },
{ name: '已通过', value: 0 },
{ name: '已拒绝', value: 0 }
]
applicationTrendOption.value = createPieChartOption('申请数量', defaultData)
return
}
// 统计各状态的申请数量
const statusCounts = {}
data.forEach(app => {
const status = app.status || 'unknown'
statusCounts[status] = (statusCounts[status] || 0) + 1
})
// 转换为图表数据格式
const chartData = Object.entries(statusCounts).map(([status, count]) => ({
name: getApplicationStatusLabel(status),
value: count
}))
console.log('处理后的申请状态分布图表数据:', chartData)
applicationTrendOption.value = createPieChartOption('申请数量', chartData)
}
// 设置险种分布图表
const setupInsuranceTypeDistributionChart = (data) => {
console.log('设置险种分布图表,接收数据:', data)
if (!data || !Array.isArray(data) || data.length === 0) {
console.warn('险种分布数据为空或格式错误,使用默认数据')
// 使用默认数据
const defaultData = [
{ name: '人寿保险', value: 0 },
{ name: '健康保险', value: 0 },
{ name: '财产保险', value: 0 }
]
policyDistributionOption.value = createPieChartOption('险种数量', defaultData)
return
}
// 统计每个险种的数量(这里假设每个险种都有相同的权重,实际项目中可能需要根据保单数量统计)
const chartData = data.map((item, index) => ({
name: item.name || `险种${index + 1}`,
value: 1 // 每个险种计数为1实际项目中可以根据保单数量统计
}))
console.log('处理后的险种分布图表数据:', chartData)
policyDistributionOption.value = createPieChartOption('险种数量', chartData)
}
// 获取申请状态标签
const getApplicationStatusLabel = (status) => {
const statusMap = {
'pending': '待审核',
'initial_approved': '初审通过',
'under_review': '复审中',
'approved': '已通过',
'rejected': '已拒绝',
'unknown': '未知状态'
}
return statusMap[status] || status
}
// 获取保单状态标签
const getPolicyStatusLabel = (status) => {
const statusMap = {
@@ -518,29 +488,8 @@ onMounted(() => {
policyDistributionOption: policyDistributionOption.value
})
// 设置测试图表数据
console.log('设置测试图表数据...')
applicationTrendOption.value = {
title: { text: '测试图表' },
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150], type: 'bar' }]
}
policyDistributionOption.value = {
title: { text: '测试饼图' },
series: [{
type: 'pie',
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' }
]
}]
}
console.log('测试图表设置完成')
// 直接加载动态数据,不再设置测试数据
console.log('开始加载动态图表数据...')
loadDashboardData()
})
</script>

View File

@@ -259,12 +259,22 @@ const getStatusText = (status) => {
const loadUsers = async () => {
loading.value = true
try {
// 构建搜索参数,映射前端字段名到后端期望的字段名
const params = {
page: pagination.current,
pageSize: pagination.pageSize,
...searchForm
limit: pagination.pageSize,
// 字段名映射
search: searchForm.username || undefined,
status: searchForm.status || undefined
}
// 过滤掉undefined值
Object.keys(params).forEach(key => {
if (params[key] === undefined) {
delete params[key]
}
})
console.log('用户管理API请求参数:', params)
const response = await userAPI.getList(params)
console.log('用户管理API响应:', response)
@@ -280,6 +290,7 @@ const loadUsers = async () => {
userList.value = []
}
} catch (error) {
console.error('加载用户列表失败:', error)
message.error('加载用户列表失败')
} finally {
loading.value = false
@@ -336,17 +347,27 @@ const handleModalOk = async () => {
await formRef.value.validate()
if (editingId.value) {
// await userAPI.update(editingId.value, formState)
// 编辑用户 - 不包含密码字段
const updateData = { ...formState }
delete updateData.password // 编辑时不更新密码
await userAPI.update(editingId.value, updateData)
message.success('用户更新成功')
} else {
// await userAPI.create(formState)
// 新增用户
await userAPI.create(formState)
message.success('用户创建成功')
}
modalVisible.value = false
loadUsers()
} catch (error) {
console.log('表单验证失败', error)
console.error('操作失败:', error)
if (error.response?.data?.message) {
message.error(error.response.data.message)
} else {
message.error(editingId.value ? '用户更新失败' : '用户创建失败')
}
}
}
@@ -357,21 +378,31 @@ const handleModalCancel = () => {
const handleToggleStatus = async (record) => {
try {
const newStatus = record.status === 'active' ? 'inactive' : 'active'
// await userAPI.update(record.id, { status: newStatus })
message.success('状态更新成功')
await userAPI.update(record.id, { status: newStatus })
message.success(`用户已${newStatus === 'active' ? '启用' : '禁用'}`)
loadUsers()
} catch (error) {
message.error('状态更新失败')
console.error('状态更新失败:', error)
if (error.response?.data?.message) {
message.error(error.response.data.message)
} else {
message.error('状态更新失败')
}
}
}
const handleDelete = async (id) => {
try {
// await userAPI.delete(id)
await userAPI.delete(id)
message.success('用户删除成功')
loadUsers()
} catch (error) {
message.error('用户删除失败')
console.error('删除用户失败:', error)
if (error.response?.data?.message) {
message.error(error.response.data.message)
} else {
message.error('用户删除失败')
}
}
}

View File

@@ -4,7 +4,7 @@ const testFrontendAPI = async () => {
console.log('开始测试前端API调用...');
// 模拟前端API调用
const response = await fetch('http://localhost:3000/api/policies?page=1&pageSize=10', {
const response = await fetch('http://49.51.70.206:3000/api/policies?page=1&pageSize=10', {
method: 'GET',
headers: {
'Content-Type': 'application/json',

View File

@@ -6,7 +6,7 @@ async function testApiConnection() {
console.log('开始测试前后端通信...');
// 测试后端健康检查接口
const backendResponse = await axios.get('http://localhost:3000/health', {
const backendResponse = await axios.get('http://49.51.70.206:3000/health', {
timeout: 5000
});
console.log('✅ 后端健康检查接口正常:', backendResponse.data);

View File

@@ -5,7 +5,7 @@ async function testCurrentLogs() {
console.log('🔐 先登录获取token...');
// 登录获取token
const loginResponse = await axios.post('http://localhost:3000/api/auth/login', {
const loginResponse = await axios.post('http://49.51.70.206:3000/api/auth/login', {
username: 'admin',
password: '123456'
});
@@ -16,7 +16,7 @@ async function testCurrentLogs() {
console.log('🔍 测试当前日志API...');
// 测试系统日志API
const logsResponse = await axios.get('http://localhost:3000/api/system/logs', {
const logsResponse = await axios.get('http://49.51.70.206:3000/api/system/logs', {
headers: {
'Authorization': `Bearer ${token}`
},
@@ -32,7 +32,7 @@ async function testCurrentLogs() {
console.log('📄 当前页日志数量:', logsResponse.data.data?.logs?.length || 0);
// 测试仪表板统计API
const statsResponse = await axios.get('http://localhost:3000/api/dashboard/stats', {
const statsResponse = await axios.get('http://49.51.70.206:3000/api/dashboard/stats', {
headers: {
'Authorization': `Bearer ${token}`
}
@@ -41,7 +41,7 @@ async function testCurrentLogs() {
console.log('📋 统计数据:', JSON.stringify(statsResponse.data, null, 2));
// 测试最近活动API
const activitiesResponse = await axios.get('http://localhost:3000/api/dashboard/recent-activities', {
const activitiesResponse = await axios.get('http://49.51.70.206:3000/api/dashboard/recent-activities', {
headers: {
'Authorization': `Bearer ${token}`
}

View File

@@ -19,7 +19,7 @@ export default defineConfig(({ mode }) => {
historyApiFallback: true,
proxy: {
'/api': {
target: env.VITE_API_BASE_URL || 'http://localhost:3000',
target: env.VITE_API_BASE_URL || 'https://ad.ningmuyun.com/insurace/',
changeOrigin: true,
rewrite: (path) => path
}

View File

@@ -0,0 +1,163 @@
# 保险系统部署指南
## 部署架构
```
用户访问: https://ad.ningmuyun.com/insurance/
nginx (443端口)
前端静态文件: /var/www/insurance-admin-system/dist/
API请求: /insurance/api/* → 代理到后端 3000端口
```
## 部署步骤
### 1. 服务器准备
```bash
# 安装必要软件
sudo apt update
sudo apt install nginx nodejs npm pm2 -y
# 创建项目目录
sudo mkdir -p /var/www/insurance-admin-system
sudo mkdir -p /var/www/insurance-backend
```
### 2. 部署前端
```bash
# 上传前端构建文件到服务器
scp -r insurance_admin-system/dist/* user@49.51.70.206:/var/www/insurance-admin-system/
# 设置权限
sudo chown -R www-data:www-data /var/www/insurance-admin-system
sudo chmod -R 755 /var/www/insurance-admin-system
```
### 3. 部署后端
```bash
# 上传后端代码到服务器
scp -r insurance_backend/* user@49.51.70.206:/var/www/insurance-backend/
# 安装依赖
cd /var/www/insurance-backend
npm install --production
# 创建环境变量文件
cp .env.example .env
# 编辑 .env 文件,设置正确的数据库连接信息
```
### 4. 配置nginx
```bash
# 复制nginx配置
sudo cp /var/www/insurance-backend/nginx-ad.ningmuyun.com.conf /etc/nginx/sites-available/ad.ningmuyun.com
# 启用站点
sudo ln -sf /etc/nginx/sites-available/ad.ningmuyun.com /etc/nginx/sites-enabled/
# 测试配置
sudo nginx -t
# 重启nginx
sudo systemctl restart nginx
```
### 5. 启动后端服务
```bash
cd /var/www/insurance-backend
# 给脚本执行权限
chmod +x start.sh deploy.sh
# 启动服务
./start.sh
```
## 配置说明
### 前端配置
- **baseURL**: `/insurance/api`
- **访问地址**: `https://ad.ningmuyun.com/insurance/`
### 后端配置
- **端口**: 3000
- **进程管理**: PM2
- **环境**: production
### nginx配置
- **SSL端口**: 443
- **前端路径**: `/insurance/` → 静态文件
- **API路径**: `/insurance/api/` → 代理到后端3000端口
## 服务管理
### PM2命令
```bash
# 查看服务状态
pm2 status
# 查看日志
pm2 logs insurance-backend
# 重启服务
pm2 restart insurance-backend
# 停止服务
pm2 stop insurance-backend
# 监控面板
pm2 monit
```
### nginx命令
```bash
# 测试配置
sudo nginx -t
# 重新加载配置
sudo systemctl reload nginx
# 重启服务
sudo systemctl restart nginx
# 查看状态
sudo systemctl status nginx
```
## 故障排除
### 1. 前端无法访问
- 检查nginx配置是否正确
- 检查SSL证书是否有效
- 检查静态文件路径是否正确
### 2. API请求失败
- 检查后端服务是否运行: `pm2 status`
- 检查nginx代理配置
- 检查CORS设置
- 查看后端日志: `pm2 logs insurance-backend`
### 3. 数据库连接问题
- 检查数据库服务是否运行
- 检查.env文件中的数据库配置
- 检查防火墙设置
## 监控和维护
### 日志位置
- nginx日志: `/var/log/nginx/`
- 后端日志: `/var/www/insurance-backend/logs/`
- PM2日志: `pm2 logs`
### 定期维护
- 定期清理日志文件
- 监控服务器资源使用情况
- 更新SSL证书
- 备份数据库

View File

@@ -1,27 +0,0 @@
const { User } = require('./models');
async function checkAdminToken() {
try {
const admin = await User.findOne({
where: { username: 'admin' },
attributes: ['id', 'username', 'fixed_token', 'status']
});
if (admin) {
console.log('Admin用户信息:');
console.log('ID:', admin.id);
console.log('用户名:', admin.username);
console.log('Token:', admin.fixed_token);
console.log('状态:', admin.status);
} else {
console.log('未找到admin用户');
}
process.exit(0);
} catch (error) {
console.error('错误:', error.message);
process.exit(1);
}
}
checkAdminToken();

View File

@@ -1,55 +0,0 @@
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

@@ -1,93 +0,0 @@
const { sequelize } = require('./config/database');
async function checkDatabaseStructure() {
try {
console.log('🔍 检查数据库表结构...\n');
// 检查数据库连接
await sequelize.authenticate();
console.log('✅ 数据库连接成功\n');
// 检查所有表
const [tables] = await sequelize.query('SHOW TABLES');
console.log('📋 数据库中的表:');
tables.forEach(table => {
const tableName = Object.values(table)[0];
console.log(` - ${tableName}`);
});
console.log('\n🏗 检查关键表结构:\n');
// 检查users表
try {
const [usersStructure] = await sequelize.query('DESCRIBE users');
console.log('👥 users表结构:');
usersStructure.forEach(column => {
console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? '(必填)' : '(可选)'} ${column.Key ? `[${column.Key}]` : ''}`);
});
} catch (error) {
console.log('❌ users表不存在');
}
console.log('');
// 检查permissions表
try {
const [permissionsStructure] = await sequelize.query('DESCRIBE permissions');
console.log('🔐 permissions表结构:');
permissionsStructure.forEach(column => {
console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? '(必填)' : '(可选)'} ${column.Key ? `[${column.Key}]` : ''}`);
});
} catch (error) {
console.log('❌ permissions表不存在');
}
console.log('');
// 检查roles表
try {
const [rolesStructure] = await sequelize.query('DESCRIBE roles');
console.log('👑 roles表结构:');
rolesStructure.forEach(column => {
console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? '(必填)' : '(可选)'} ${column.Key ? `[${column.Key}]` : ''}`);
});
} catch (error) {
console.log('❌ roles表不存在');
}
console.log('');
// 检查role_permissions表
try {
const [rolePermissionsStructure] = await sequelize.query('DESCRIBE role_permissions');
console.log('🔗 role_permissions表结构:');
rolePermissionsStructure.forEach(column => {
console.log(` - ${column.Field}: ${column.Type} ${column.Null === 'NO' ? '(必填)' : '(可选)'} ${column.Key ? `[${column.Key}]` : ''}`);
});
} catch (error) {
console.log('❌ role_permissions表不存在');
}
console.log('\n📊 检查数据量:');
// 检查各表数据量
const tables_to_check = ['users', 'permissions', 'roles', 'role_permissions'];
for (const tableName of tables_to_check) {
try {
const [result] = await sequelize.query(`SELECT COUNT(*) as count FROM ${tableName}`);
console.log(` - ${tableName}: ${result[0].count} 条记录`);
} catch (error) {
console.log(` - ${tableName}: 表不存在或查询失败`);
}
}
} catch (error) {
console.error('❌ 检查失败:', error.message);
} finally {
await sequelize.close();
console.log('\n🔚 检查完成');
}
}
checkDatabaseStructure();

View File

@@ -1,110 +0,0 @@
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

@@ -1,142 +0,0 @@
const puppeteer = require('puppeteer');
async function checkInsuranceNavigation() {
console.log('🔍 检查险种管理页面导航...');
const browser = await puppeteer.launch({
headless: false,
slowMo: 1000
});
const page = await browser.newPage();
try {
// 访问前端页面并登录
await page.goto('http://localhost:3002', { waitUntil: 'networkidle2' });
// 登录
await page.type('input[type="text"], input[placeholder*="用户名"], input[placeholder*="账号"]', 'admin');
await page.type('input[type="password"], input[placeholder*="密码"]', '123456');
const loginSelectors = ['button[type="submit"]', '.login-btn', 'button'];
for (const selector of loginSelectors) {
try {
const button = await page.$(selector);
if (button) {
await button.click();
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
// 等待登录完成
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('✅ 登录完成当前URL:', page.url());
// 方法1直接访问险种管理页面URL
console.log('🔍 方法1直接访问险种管理页面URL...');
await page.goto('http://localhost:3002/#/insurance-types', { waitUntil: 'networkidle2' });
await new Promise(resolve => setTimeout(resolve, 3000));
let currentUrl = page.url();
console.log('直接访问后的URL:', currentUrl);
if (currentUrl.includes('insurance-types')) {
console.log('✅ 直接访问险种管理页面成功');
// 检查页面内容
const pageContent = await page.content();
const hasInsuranceContent = pageContent.includes('险种管理') ||
pageContent.includes('保险类型') ||
pageContent.includes('新增险种');
console.log('页面包含险种管理内容:', hasInsuranceContent);
// 查找表格元素
const tableElements = await page.evaluate(() => {
const selectors = [
'table',
'.ant-table',
'.ant-table-tbody',
'.el-table',
'.data-table',
'[class*="table"]',
'tbody tr',
'.ant-table-row'
];
const found = [];
selectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
found.push({
selector,
count: elements.length,
visible: elements[0] ? !elements[0].hidden && elements[0].offsetParent !== null : false
});
}
});
return found;
});
console.log('找到的表格元素:', JSON.stringify(tableElements, null, 2));
// 检查是否有数据加载
const hasData = await page.evaluate(() => {
const rows = document.querySelectorAll('.ant-table-tbody tr, tbody tr, .table-row');
return rows.length > 0;
});
console.log('表格是否有数据:', hasData);
// 检查加载状态
const isLoading = await page.evaluate(() => {
const loadingElements = document.querySelectorAll('.ant-spin, .loading, [class*="loading"]');
return loadingElements.length > 0;
});
console.log('页面是否在加载中:', isLoading);
// 等待一段时间看数据是否会加载
console.log('等待数据加载...');
await new Promise(resolve => setTimeout(resolve, 5000));
const hasDataAfterWait = await page.evaluate(() => {
const rows = document.querySelectorAll('.ant-table-tbody tr, tbody tr, .table-row');
return rows.length > 0;
});
console.log('等待后表格是否有数据:', hasDataAfterWait);
// 检查网络请求
const networkLogs = [];
page.on('response', response => {
if (response.url().includes('insurance-types') || response.url().includes('api')) {
networkLogs.push({
url: response.url(),
status: response.status(),
statusText: response.statusText()
});
}
});
// 刷新页面看网络请求
await page.reload({ waitUntil: 'networkidle2' });
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('网络请求日志:', JSON.stringify(networkLogs, null, 2));
} else {
console.log('❌ 直接访问险种管理页面失败,仍在:', currentUrl);
}
} catch (error) {
console.error('检查导航失败:', error.message);
} finally {
await browser.close();
}
}
checkInsuranceNavigation().catch(console.error);

View File

@@ -1,116 +0,0 @@
const puppeteer = require('puppeteer');
async function checkLoginDOM() {
console.log('🔍 检查登录后的DOM结构...');
const browser = await puppeteer.launch({
headless: false,
slowMo: 500
});
const page = await browser.newPage();
try {
// 访问前端页面
await page.goto('http://localhost:3002', { waitUntil: 'networkidle2' });
// 填写登录信息
await page.type('input[type="text"], input[placeholder*="用户名"], input[placeholder*="账号"]', 'admin');
await page.type('input[type="password"], input[placeholder*="密码"]', '123456');
// 点击登录按钮
const loginSelectors = ['button[type="submit"]', '.login-btn', 'button'];
let loginClicked = false;
for (const selector of loginSelectors) {
try {
const button = await page.$(selector);
if (button) {
await button.click();
loginClicked = true;
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
if (!loginClicked) {
await page.keyboard.press('Enter');
}
// 等待页面加载
await new Promise(resolve => setTimeout(resolve, 5000));
// 获取当前URL
const currentUrl = page.url();
console.log('当前URL:', currentUrl);
// 检查页面标题
const title = await page.title();
console.log('页面标题:', title);
// 查找可能的用户信息元素
const userElements = await page.evaluate(() => {
const selectors = [
'.user-info',
'.header-user',
'.logout-btn',
'.user-name',
'.username',
'.user-avatar',
'.header-right',
'.navbar-user',
'.el-dropdown',
'[class*="user"]',
'[class*="logout"]',
'[class*="header"]'
];
const found = [];
selectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
found.push({
selector,
count: elements.length,
text: Array.from(elements).map(el => el.textContent?.trim()).filter(t => t)
});
}
});
return found;
});
console.log('找到的用户相关元素:', JSON.stringify(userElements, null, 2));
// 获取页面的主要结构
const pageStructure = await page.evaluate(() => {
const body = document.body;
const mainElements = [];
// 查找主要的容器元素
const containers = body.querySelectorAll('div[class], nav[class], header[class], main[class]');
containers.forEach(el => {
if (el.className) {
mainElements.push({
tag: el.tagName.toLowerCase(),
className: el.className,
text: el.textContent?.substring(0, 100) + '...'
});
}
});
return mainElements.slice(0, 20); // 只返回前20个
});
console.log('页面主要结构:', JSON.stringify(pageStructure, null, 2));
} catch (error) {
console.error('检查DOM失败:', error.message);
} finally {
await browser.close();
}
}
checkLoginDOM().catch(console.error);

View File

@@ -1,29 +0,0 @@
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

@@ -1,37 +0,0 @@
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

@@ -1,136 +0,0 @@
const puppeteer = require('puppeteer');
async function checkTokenStatus() {
console.log('🔍 检查Token状态...');
const browser = await puppeteer.launch({
headless: false,
slowMo: 500
});
const page = await browser.newPage();
try {
// 访问前端页面并登录
await page.goto('http://localhost:3002', { waitUntil: 'networkidle2' });
// 登录
await page.type('input[type="text"], input[placeholder*="用户名"], input[placeholder*="账号"]', 'admin');
await page.type('input[type="password"], input[placeholder*="密码"]', '123456');
const loginSelectors = ['button[type="submit"]', '.login-btn', 'button'];
for (const selector of loginSelectors) {
try {
const button = await page.$(selector);
if (button) {
await button.click();
break;
}
} catch (e) {
// 继续尝试下一个选择器
}
}
// 等待登录完成
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('✅ 登录完成当前URL:', page.url());
// 检查localStorage中的Token信息
const tokenInfo = await page.evaluate(() => {
return {
accessToken: localStorage.getItem('accessToken'),
refreshToken: localStorage.getItem('refreshToken'),
tokenExpiresAt: localStorage.getItem('tokenExpiresAt'),
userInfo: localStorage.getItem('userInfo'),
currentTime: Date.now()
};
});
console.log('Token信息:');
console.log('- accessToken:', tokenInfo.accessToken ? `${tokenInfo.accessToken.substring(0, 20)}...` : 'null');
console.log('- refreshToken:', tokenInfo.refreshToken ? `${tokenInfo.refreshToken.substring(0, 20)}...` : 'null');
console.log('- tokenExpiresAt:', tokenInfo.tokenExpiresAt);
console.log('- userInfo:', tokenInfo.userInfo);
console.log('- currentTime:', tokenInfo.currentTime);
if (tokenInfo.tokenExpiresAt) {
const expiresAt = parseInt(tokenInfo.tokenExpiresAt);
const isExpired = tokenInfo.currentTime >= expiresAt;
const timeUntilExpiry = expiresAt - tokenInfo.currentTime;
console.log('- Token是否过期:', isExpired);
console.log('- 距离过期时间:', Math.round(timeUntilExpiry / 1000), '秒');
}
// 测试API调用
console.log('🔍 测试API调用...');
const apiResponse = await page.evaluate(async () => {
try {
const token = localStorage.getItem('accessToken');
const response = await fetch('http://localhost:3000/api/insurance-types', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
return {
status: response.status,
statusText: response.statusText,
ok: response.ok,
data: response.ok ? await response.json() : await response.text()
};
} catch (error) {
return {
error: error.message
};
}
});
console.log('API响应:', JSON.stringify(apiResponse, null, 2));
// 现在尝试访问险种管理页面
console.log('🔍 尝试访问险种管理页面...');
await page.goto('http://localhost:3002/#/insurance-types', { waitUntil: 'networkidle2' });
await new Promise(resolve => setTimeout(resolve, 3000));
const finalUrl = page.url();
console.log('最终URL:', finalUrl);
if (finalUrl.includes('insurance-types')) {
console.log('✅ 成功访问险种管理页面');
// 检查页面是否有表格
const hasTable = await page.evaluate(() => {
const tables = document.querySelectorAll('table, .ant-table, .ant-table-tbody');
return tables.length > 0;
});
console.log('页面是否有表格:', hasTable);
} else if (finalUrl.includes('login')) {
console.log('❌ 被重定向到登录页面');
// 检查控制台错误
const consoleLogs = [];
page.on('console', msg => {
if (msg.type() === 'error') {
consoleLogs.push(msg.text());
}
});
console.log('控制台错误:', consoleLogs);
} else {
console.log('❌ 访问了其他页面:', finalUrl);
}
} catch (error) {
console.error('检查Token状态失败:', error.message);
} finally {
await browser.close();
}
}
checkTokenStatus().catch(console.error);

View File

@@ -1,61 +0,0 @@
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

@@ -0,0 +1,33 @@
{
"development": {
"username": "root",
"password": "aiotAiot123!",
"database": "insurance_data",
"host": "129.211.213.226",
"port": 9527,
"dialect": "mysql",
"timezone": "+08:00",
"dialectOptions": {
"dateStrings": true,
"typeCast": true
}
},
"test": {
"username": "root",
"password": "aiotAiot123!",
"database": "insurance_data_test",
"host": "129.211.213.226",
"port": 9527,
"dialect": "mysql",
"timezone": "+08:00"
},
"production": {
"username": "root",
"password": "aiotAiot123!",
"database": "insurance_data_prod",
"host": "129.211.213.226",
"port": 9527,
"dialect": "mysql",
"timezone": "+08:00"
}
}

View File

@@ -0,0 +1,49 @@
const { Sequelize } = require('sequelize');
require('dotenv').config();
// 创建Sequelize实例
const sequelize = new Sequelize({
dialect: process.env.DB_DIALECT || 'mysql',
host: process.env.DB_HOST || '129.211.213.226',
port: process.env.DB_PORT || 9527,
database: process.env.DB_DATABASE || 'insurance_data',
username: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'aiotAiot123!',
logging: process.env.NODE_ENV === 'development' ? console.log : false,
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
},
define: {
timestamps: true,
underscored: true,
charset: 'utf8mb4',
collate: 'utf8mb4_unicode_ci'
},
dialectOptions: {
// 解决MySQL严格模式问题
dateStrings: true,
typeCast: true,
// 允许0000-00-00日期值
connectAttributes: {
sql_mode: 'TRADITIONAL'
}
},
timezone: '+08:00' // 设置时区为东八区
});
// 测试数据库连接
const testConnection = async () => {
try {
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
return true;
} catch (error) {
console.error('❌ 数据库连接失败:', error.message);
return false;
}
};
module.exports = { sequelize, testConnection };

View File

@@ -0,0 +1,42 @@
const redis = require('redis');
require('dotenv').config();
// 创建Redis客户端
const createRedisClient = () => {
const client = redis.createClient({
socket: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
},
password: process.env.REDIS_PASSWORD || '',
legacyMode: false
});
// 错误处理
client.on('error', (err) => {
console.error('❌ Redis连接错误:', err);
});
// 连接成功
client.on('connect', () => {
console.log('✅ Redis连接成功');
});
return client;
};
// 创建并连接Redis客户端
const redisClient = createRedisClient();
// 连接Redis
const connectRedis = async () => {
try {
await redisClient.connect();
return true;
} catch (error) {
console.error('❌ Redis连接失败:', error.message);
return false;
}
};
module.exports = { redisClient, connectRedis };

View File

@@ -0,0 +1,244 @@
const { Claim, Policy, User, InsuranceApplication, InsuranceType } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取理赔列表
const getClaims = async (req, res) => {
try {
const {
claim_no,
customer_name,
claim_status,
dateRange,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
// 理赔编号筛选
if (claim_no) {
whereClause.claim_no = { [Op.like]: `%${claim_no}%` };
}
// 理赔状态筛选
if (claim_status) {
whereClause.claim_status = claim_status;
}
// 日期范围筛选
if (dateRange && dateRange.start && dateRange.end) {
whereClause.claim_date = {
[Op.between]: [new Date(dateRange.start), new Date(dateRange.end)]
};
}
const offset = (page - 1) * limit;
const { count, rows } = await Claim.findAndCountAll({
where: whereClause,
include: [
{
model: Policy,
as: 'policy',
attributes: ['id', 'policy_no', 'coverage_amount'],
include: [
{
model: InsuranceApplication,
as: 'application',
attributes: ['id', 'customer_name'],
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name']
}
]
}
]
},
{
model: User,
as: 'customer',
attributes: ['id', 'real_name', 'username']
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
],
order: [['created_at', 'DESC']],
offset,
limit: parseInt(limit)
});
res.json(responseFormat.pagination(rows, {
page: parseInt(page),
limit: parseInt(limit),
total: count
}, '获取理赔列表成功'));
} catch (error) {
console.error('获取理赔列表错误:', error);
res.status(500).json(responseFormat.error('获取理赔列表失败'));
}
};
// 创建理赔申请
const createClaim = async (req, res) => {
try {
const claimData = req.body;
// 生成理赔编号
const claimNo = `CLM${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
const claim = await Claim.create({
...claimData,
claim_no: claimNo
});
res.status(201).json(responseFormat.created(claim, '理赔申请创建成功'));
} catch (error) {
console.error('创建理赔申请错误:', error);
res.status(500).json(responseFormat.error('创建理赔申请失败'));
}
};
// 获取单个理赔详情
const getClaimById = async (req, res) => {
try {
const { id } = req.params;
const claim = await Claim.findByPk(id, {
include: [
{
model: Policy,
as: 'policy',
include: [
{
model: InsuranceApplication,
as: 'application',
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description']
}
]
}
]
},
{
model: User,
as: 'customer',
attributes: ['id', 'real_name', 'username', 'phone', 'email']
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
]
});
if (!claim) {
return res.status(404).json(responseFormat.error('理赔申请不存在'));
}
res.json(responseFormat.success(claim, '获取理赔详情成功'));
} catch (error) {
console.error('获取理赔详情错误:', error);
res.status(500).json(responseFormat.error('获取理赔详情失败'));
}
};
// 审核理赔申请
const reviewClaim = async (req, res) => {
try {
const { id } = req.params;
const { claim_status, review_notes } = req.body;
const reviewerId = req.user.id;
const claim = await Claim.findByPk(id);
if (!claim) {
return res.status(404).json(responseFormat.error('理赔申请不存在'));
}
if (!['approved', 'rejected', 'processing', 'paid'].includes(claim_status)) {
return res.status(400).json(responseFormat.error('无效的理赔状态'));
}
await claim.update({
claim_status,
review_notes,
reviewer_id: reviewerId,
review_date: new Date()
});
res.json(responseFormat.success(claim, '理赔申请审核成功'));
} catch (error) {
console.error('审核理赔申请错误:', error);
res.status(500).json(responseFormat.error('审核理赔申请失败'));
}
};
// 更新理赔支付状态
const updateClaimPayment = async (req, res) => {
try {
const { id } = req.params;
const claim = await Claim.findByPk(id);
if (!claim) {
return res.status(404).json(responseFormat.error('理赔申请不存在'));
}
if (claim.claim_status !== 'approved') {
return res.status(400).json(responseFormat.error('只有已批准的理赔才能进行支付'));
}
await claim.update({
claim_status: 'paid',
payment_date: new Date()
});
res.json(responseFormat.success(claim, '理赔支付状态更新成功'));
} catch (error) {
console.error('更新理赔支付状态错误:', error);
res.status(500).json(responseFormat.error('更新理赔支付状态失败'));
}
};
// 获取理赔统计
const getClaimStats = async (req, res) => {
try {
const stats = await Claim.findAll({
attributes: [
'claim_status',
[Claim.sequelize.fn('COUNT', Claim.sequelize.col('id')), 'count'],
[Claim.sequelize.fn('SUM', Claim.sequelize.col('claim_amount')), 'total_amount']
],
group: ['claim_status']
});
const total = await Claim.count();
const totalAmount = await Claim.sum('claim_amount');
res.json(responseFormat.success({
stats,
total,
total_amount: totalAmount || 0
}, '获取理赔统计成功'));
} catch (error) {
console.error('获取理赔统计错误:', error);
res.status(500).json(responseFormat.error('获取理赔统计失败'));
}
};
module.exports = {
getClaims,
createClaim,
getClaimById,
reviewClaim,
updateClaimPayment,
getClaimStats
};

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

@@ -0,0 +1,477 @@
const InstallationTask = require('../models/InstallationTask');
const User = require('../models/User');
const { Op, sequelize } = require('sequelize');
const logger = require('../utils/logger');
class InstallationTaskController {
// 获取待安装任务列表
async getInstallationTasks(req, res) {
try {
const {
page = 1,
pageSize = 10,
policyNumber,
customerName,
installationStatus,
priority,
keyword,
startDate,
endDate
} = req.query;
const offset = (page - 1) * pageSize;
const limit = parseInt(pageSize);
// 构建查询条件
const whereConditions = {};
if (policyNumber) {
whereConditions.policyNumber = { [Op.like]: `%${policyNumber}%` };
}
if (customerName) {
whereConditions.customerName = { [Op.like]: `%${customerName}%` };
}
if (installationStatus) {
whereConditions.installationStatus = installationStatus;
}
if (priority) {
whereConditions.priority = priority;
}
// 关键字搜索(搜索申请单号、保单编号、客户姓名等)
if (keyword) {
whereConditions[Op.or] = [
{ applicationNumber: { [Op.like]: `%${keyword}%` } },
{ policyNumber: { [Op.like]: `%${keyword}%` } },
{ customerName: { [Op.like]: `%${customerName}%` } },
{ productName: { [Op.like]: `%${keyword}%` } }
];
}
if (startDate && endDate) {
whereConditions.taskGeneratedTime = {
[Op.between]: [new Date(startDate), new Date(endDate)]
};
}
const { count, rows } = await InstallationTask.findAndCountAll({
where: whereConditions,
order: [['taskGeneratedTime', 'DESC']],
offset,
limit
});
res.json({
code: 200,
status: 'success',
message: '获取待安装任务列表成功',
data: {
list: rows,
total: count,
page: parseInt(page),
pageSize: limit,
totalPages: Math.ceil(count / limit)
}
});
} catch (error) {
logger.error('获取待安装任务列表失败:', error);
res.status(500).json({
code: 500,
status: 'error',
message: '获取待安装任务列表失败',
error: error.message
});
}
}
// 创建待安装任务
async createInstallationTask(req, res) {
try {
const {
applicationNumber,
policyNumber,
productName,
customerName,
idType,
idNumber,
livestockSupplyType,
pendingDevices,
installationStatus = '待安装',
priority = '中',
assignedTo,
taskGeneratedTime = new Date()
} = req.body;
// 验证必填字段
if (!applicationNumber || !policyNumber || !productName || !customerName) {
return res.status(400).json({
code: 400,
status: 'error',
message: '申请单号、保单编号、产品名称、客户姓名为必填项'
});
}
// 检查申请单号是否已存在
const existingTask = await InstallationTask.findOne({
where: { applicationNumber }
});
if (existingTask) {
return res.status(409).json({
code: 409,
status: 'error',
message: '该申请单号已存在待安装任务'
});
}
const installationTask = await InstallationTask.create({
applicationNumber,
policyNumber,
productName,
customerName,
idType: idType || '身份证',
idNumber: idNumber || '',
livestockSupplyType,
pendingDevices: pendingDevices ? JSON.stringify(pendingDevices) : null,
installationStatus,
priority,
assignedTo,
taskGeneratedTime,
createdBy: req.user?.id
});
res.status(201).json({
code: 201,
status: 'success',
message: '创建待安装任务成功',
data: installationTask
});
} catch (error) {
logger.error('创建待安装任务失败:', error);
res.status(500).json({
code: 500,
status: 'error',
message: '创建待安装任务失败',
error: error.message
});
}
}
// 获取待安装任务详情
async getInstallationTaskById(req, res) {
try {
const { id } = req.params;
const installationTask = await InstallationTask.findByPk(id, {
include: [
{
model: User,
as: 'technician',
attributes: ['id', 'username', 'real_name']
},
{
model: User,
as: 'creator',
attributes: ['id', 'username', 'real_name']
},
{
model: User,
as: 'updater',
attributes: ['id', 'username', 'real_name']
}
]
});
if (!installationTask) {
return res.status(404).json({
code: 404,
status: 'error',
message: '待安装任务不存在'
});
}
res.json({
code: 200,
status: 'success',
message: '获取待安装任务详情成功',
data: installationTask
});
} catch (error) {
logger.error('获取待安装任务详情失败:', error);
res.status(500).json({
code: 500,
status: 'error',
message: '获取待安装任务详情失败',
error: error.message
});
}
}
// 更新待安装任务
async updateInstallationTask(req, res) {
try {
const { id } = req.params;
const updateData = { ...req.body };
// 添加更新人信息
updateData.updatedBy = req.user?.id;
// 处理设备数据
if (updateData.pendingDevices) {
updateData.pendingDevices = JSON.stringify(updateData.pendingDevices);
}
// 处理安装完成时间
if (updateData.installationStatus === '已安装' && !updateData.installationCompletedTime) {
updateData.installationCompletedTime = new Date();
}
const [updatedCount] = await InstallationTask.update(updateData, {
where: { id }
});
if (updatedCount === 0) {
return res.status(404).json({
code: 404,
status: 'error',
message: '待安装任务不存在或未做任何修改'
});
}
// 获取更新后的任务
const updatedTask = await InstallationTask.findByPk(id);
res.json({
code: 200,
status: 'success',
message: '更新待安装任务成功',
data: updatedTask
});
} catch (error) {
logger.error('更新待安装任务失败:', error);
res.status(500).json({
code: 500,
status: 'error',
message: '更新待安装任务失败',
error: error.message
});
}
}
// 删除待安装任务
async deleteInstallationTask(req, res) {
try {
const { id } = req.params;
const deletedCount = await InstallationTask.destroy({
where: { id }
});
if (deletedCount === 0) {
return res.status(404).json({
code: 404,
status: 'error',
message: '待安装任务不存在'
});
}
res.json({
code: 200,
status: 'success',
message: '删除待安装任务成功'
});
} catch (error) {
logger.error('删除待安装任务失败:', error);
res.status(500).json({
code: 500,
status: 'error',
message: '删除待安装任务失败',
error: error.message
});
}
}
// 批量操作待安装任务
async batchOperateInstallationTasks(req, res) {
try {
const { ids, operation, data } = req.body;
if (!ids || !Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({
code: 400,
status: 'error',
message: '任务ID列表不能为空'
});
}
let result;
switch (operation) {
case 'assign':
if (!data.assignedTo) {
return res.status(400).json({
code: 400,
status: 'error',
message: '分配操作需要指定分配给的用户'
});
}
result = await InstallationTask.update(
{ assignedTo: data.assignedTo, updatedBy: req.user?.id },
{ where: { id: ids } }
);
break;
case 'updateStatus':
if (!data.installationStatus) {
return res.status(400).json({
code: 400,
status: 'error',
message: '状态更新操作需要指定新状态'
});
}
result = await InstallationTask.update(
{ installationStatus: data.installationStatus, updatedBy: req.user?.id },
{ where: { id: ids } }
);
break;
case 'delete':
result = await InstallationTask.destroy({ where: { id: ids } });
break;
default:
return res.status(400).json({
code: 400,
status: 'error',
message: '不支持的操作类型'
});
}
res.json({
code: 200,
status: 'success',
message: `批量${operation}操作完成`,
data: { affectedRows: result[0] || result }
});
} catch (error) {
logger.error('批量操作待安装任务失败:', error);
res.status(500).json({
code: 500,
status: 'error',
message: '批量操作失败',
error: error.message
});
}
}
// 导出待安装任务数据
async exportInstallationTasks(req, res) {
try {
const { ids } = req.query;
let where = {};
if (ids) {
where.id = { [Op.in]: ids.split(',') };
}
const tasks = await InstallationTask.findAll({
where,
include: [
{
model: User,
as: 'technician',
attributes: ['username', 'real_name']
},
{
model: User,
as: 'creator',
attributes: ['username', 'real_name']
}
],
order: [['taskGeneratedTime', 'DESC']]
});
res.json({
code: 200,
status: 'success',
message: '导出待安装任务数据成功',
data: tasks
});
} catch (error) {
logger.error('导出待安装任务数据失败:', error);
res.status(500).json({
code: 500,
status: 'error',
message: '导出数据失败',
error: error.message
});
}
}
// 获取任务统计信息
async getInstallationTaskStats(req, res) {
try {
// 按状态统计
const statusStats = await InstallationTask.findAll({
attributes: [
'installationStatus',
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
],
group: ['installationStatus'],
raw: true
});
// 按优先级统计
const priorityStats = await InstallationTask.findAll({
attributes: [
'priority',
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
],
group: ['priority'],
raw: true
});
// 总数统计
const total = await InstallationTask.count();
// 本月新增任务
const thisMonth = await InstallationTask.count({
where: {
taskGeneratedTime: {
[Op.gte]: new Date(new Date().getFullYear(), new Date().getMonth(), 1)
}
}
});
res.json({
code: 200,
status: 'success',
message: '获取任务统计信息成功',
data: {
total,
thisMonth,
statusStats,
priorityStats
}
});
} catch (error) {
logger.error('获取任务统计信息失败:', error);
res.status(500).json({
code: 500,
status: 'error',
message: '获取统计信息失败',
error: error.message
});
}
}
}
module.exports = new InstallationTaskController();

View File

@@ -0,0 +1,409 @@
const { InsuranceApplication, InsuranceType, User } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取保险申请列表
const getApplications = async (req, res) => {
try {
console.log('获取保险申请列表 - 请求参数:', req.query);
const {
applicantName,
status,
insuranceType,
insuranceCategory,
applicationNumber,
dateRange,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
const includeClause = [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description'],
where: {}
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
];
// 申请单号筛选
if (applicationNumber) {
whereClause.application_no = { [Op.like]: `%${applicationNumber}%` };
}
// 投保人姓名筛选
if (applicantName) {
whereClause.customer_name = { [Op.like]: `%${applicantName}%` };
}
// 状态筛选
if (status) {
whereClause.status = status;
}
// 参保险种筛选
if (insuranceType) {
includeClause[0].where.name = { [Op.like]: `%${insuranceType}%` };
}
// 参保类型筛选
if (insuranceCategory) {
whereClause.insurance_category = { [Op.like]: `%${insuranceCategory}%` };
}
// 日期范围筛选
if (dateRange && dateRange.start && dateRange.end) {
whereClause.application_date = {
[Op.between]: [new Date(dateRange.start), new Date(dateRange.end)]
};
}
const offset = (page - 1) * limit;
// 如果没有保险类型筛选条件清空where条件
if (!insuranceType) {
delete includeClause[0].where;
}
console.log('查询条件:', { whereClause, includeClause, offset, limit: parseInt(limit) });
const { count, rows } = await InsuranceApplication.findAndCountAll({
where: whereClause,
include: includeClause,
order: [['created_at', 'DESC']],
offset,
limit: parseInt(limit)
});
console.log('查询结果:', { count, rowsLength: rows.length });
res.json(responseFormat.pagination(rows, {
page: parseInt(page),
limit: parseInt(limit),
total: count
}, '获取保险申请列表成功'));
} catch (error) {
console.error('获取保险申请列表错误:', error);
res.status(500).json(responseFormat.error('获取保险申请列表失败'));
}
};
// 创建保险申请
const createApplication = async (req, res) => {
try {
const {
customer_name,
customer_id_card,
customer_phone,
customer_address,
insurance_type_id,
insurance_category,
application_quantity,
application_amount,
remarks
} = req.body;
// 验证必填字段
if (!customer_name || !customer_id_card || !customer_phone || !customer_address || !insurance_type_id) {
return res.status(400).json(responseFormat.error('请填写所有必填字段'));
}
// 生成申请编号
const applicationNo = `${new Date().getFullYear()}${(new Date().getMonth() + 1).toString().padStart(2, '0')}${new Date().getDate().toString().padStart(2, '0')}${Date.now().toString().slice(-6)}`;
const application = await InsuranceApplication.create({
application_no: applicationNo,
customer_name,
customer_id_card,
customer_phone,
customer_address,
insurance_type_id,
insurance_category,
application_quantity: application_quantity || 1,
application_amount: application_amount || 0,
remarks,
status: 'pending'
});
// 返回创建的申请信息,包含关联数据
const createdApplication = await InsuranceApplication.findByPk(application.id, {
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description']
}
]
});
res.status(201).json(responseFormat.created(createdApplication, '保险申请创建成功'));
} catch (error) {
console.error('创建保险申请错误:', error);
if (error.name === 'SequelizeValidationError') {
const errorMessages = error.errors.map(err => err.message).join(', ');
return res.status(400).json(responseFormat.error(`数据验证失败: ${errorMessages}`));
}
res.status(500).json(responseFormat.error('创建保险申请失败'));
}
};
// 获取单个保险申请详情
const getApplicationById = async (req, res) => {
try {
const { id } = req.params;
const application = await InsuranceApplication.findByPk(id, {
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description']
},
{
model: User,
as: 'reviewer',
attributes: ['id', 'real_name', 'username']
}
]
});
if (!application) {
return res.status(404).json(responseFormat.error('保险申请不存在'));
}
res.json(responseFormat.success(application, '获取保险申请详情成功'));
} catch (error) {
console.error('获取保险申请详情错误:', error);
res.status(500).json(responseFormat.error('获取保险申请详情失败'));
}
};
// 更新保险申请
const updateApplication = async (req, res) => {
try {
const { id } = req.params;
const updateData = req.body;
const application = await InsuranceApplication.findByPk(id);
if (!application) {
return res.status(404).json(responseFormat.error('保险申请不存在'));
}
await application.update(updateData);
res.json(responseFormat.success(application, '保险申请更新成功'));
} catch (error) {
console.error('更新保险申请错误:', error);
res.status(500).json(responseFormat.error('更新保险申请失败'));
}
};
// 审核保险申请
const reviewApplication = async (req, res) => {
try {
const { id } = req.params;
const { status, result, review_notes } = req.body;
const reviewerId = req.user.id;
const application = await InsuranceApplication.findByPk(id);
if (!application) {
return res.status(404).json(responseFormat.error('保险申请不存在'));
}
// 前端发送的是result字段后端期望status字段
const reviewStatus = status || result;
// 状态映射将前端的pending_review映射为under_review
let mappedStatus = reviewStatus;
if (reviewStatus === 'pending_review') {
mappedStatus = 'under_review';
}
if (!['approved', 'rejected', 'under_review'].includes(mappedStatus)) {
return res.status(400).json(responseFormat.error('无效的审核状态'));
}
await application.update({
status: mappedStatus,
review_notes,
reviewer_id: reviewerId,
review_date: new Date()
});
res.json(responseFormat.success(application, '保险申请审核成功'));
} catch (error) {
console.error('审核保险申请错误:', error);
res.status(500).json(responseFormat.error('审核保险申请失败'));
}
};
// 删除保险申请
const deleteApplication = async (req, res) => {
try {
const { id } = req.params;
const application = await InsuranceApplication.findByPk(id);
if (!application) {
return res.status(404).json(responseFormat.error('保险申请不存在'));
}
await application.destroy();
res.json(responseFormat.success(null, '保险申请删除成功'));
} catch (error) {
console.error('删除保险申请错误:', error);
res.status(500).json(responseFormat.error('删除保险申请失败'));
}
};
// 获取保险申请统计
const getApplicationStats = async (req, res) => {
try {
const stats = await InsuranceApplication.findAll({
attributes: [
'status',
[InsuranceApplication.sequelize.fn('COUNT', InsuranceApplication.sequelize.col('id')), 'count']
],
group: ['status']
});
const total = await InsuranceApplication.count();
res.json(responseFormat.success({
stats,
total
}, '获取保险申请统计成功'));
} catch (error) {
console.error('获取保险申请统计错误:', error);
res.status(500).json(responseFormat.error('获取保险申请统计失败'));
}
};
// 获取参保类型选项
const getInsuranceCategories = async (req, res) => {
try {
const categories = await InsuranceApplication.findAll({
attributes: ['insurance_category'],
where: {
insurance_category: {
[Op.ne]: null
}
},
group: ['insurance_category'],
raw: true
});
const categoryList = categories.map(item => item.insurance_category).filter(Boolean);
// 添加一些常用的参保类型
const defaultCategories = ['牛', '羊', '猪', '鸡', '鸭', '鹅'];
const allCategories = [...new Set([...defaultCategories, ...categoryList])];
res.json(responseFormat.success(allCategories, '获取参保类型选项成功'));
} catch (error) {
console.error('获取参保类型选项错误:', error);
res.status(500).json(responseFormat.error('获取参保类型选项失败'));
}
};
// 导出保险申请数据
const exportApplications = async (req, res) => {
try {
const {
page = 1,
limit = 1000,
applicantName,
insuranceType,
insuranceCategory,
status
} = req.query;
const where = {};
if (applicantName) {
where.applicant_name = { [Op.like]: `%${applicantName}%` };
}
if (insuranceType) {
where.insurance_type = insuranceType;
}
if (insuranceCategory) {
where.insurance_category = insuranceCategory;
}
if (status) {
where.status = status;
}
const applications = await InsuranceApplication.findAll({
where,
include: [
{
model: InsuranceType,
as: 'insuranceTypeInfo',
attributes: ['name', 'description']
},
{
model: User,
as: 'createdByUser',
attributes: ['username', 'real_name']
}
],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
// 简单的CSV格式导出
const csvHeader = '申请编号,申请人姓名,身份证号,联系电话,参保类型,保险类型,保险金额,保险期限,地址,状态,申请时间,备注\n';
const csvData = applications.map(app => {
const statusMap = {
'pending': '待审核',
'initial_approved': '初审通过',
'under_review': '复审中',
'approved': '已通过',
'rejected': '已拒绝'
};
return [
app.application_number || '',
app.applicant_name || '',
app.id_card || '',
app.phone || '',
app.insurance_category || '',
app.insurance_type || '',
app.insurance_amount || '',
app.insurance_period || '',
app.address || '',
statusMap[app.status] || app.status,
app.created_at ? new Date(app.created_at).toLocaleString('zh-CN') : '',
app.remarks || ''
].map(field => `"${String(field).replace(/"/g, '""')}"`).join(',');
}).join('\n');
const csvContent = csvHeader + csvData;
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', `attachment; filename="insurance_applications_${new Date().toISOString().slice(0, 10)}.csv"`);
res.send('\uFEFF' + csvContent); // 添加BOM以支持中文
} catch (error) {
console.error('导出保险申请数据错误:', error);
res.status(500).json(responseFormat.error('导出保险申请数据失败'));
}
};
module.exports = {
getApplications,
createApplication,
getApplicationById,
updateApplication,
reviewApplication,
deleteApplication,
getApplicationStats,
getInsuranceCategories,
exportApplications
};

View File

@@ -0,0 +1,221 @@
const LivestockType = require('../models/LivestockType');
const User = require('../models/User');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取牲畜类型列表
const getLivestockTypes = async (req, res) => {
try {
const {
name,
is_active,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
// 名称筛选
if (name) {
whereClause.name = { [Op.like]: `%${name}%` };
}
// 状态筛选
if (is_active !== undefined) {
whereClause.is_active = is_active === 'true';
}
const offset = (page - 1) * limit;
const { count, rows } = await LivestockType.findAndCountAll({
where: whereClause,
order: [['created_at', 'DESC']],
offset,
limit: parseInt(limit)
});
res.json(responseFormat.pagination(rows, {
page: parseInt(page),
limit: parseInt(limit),
total: count
}, '获取牲畜类型列表成功'));
} catch (error) {
console.error('获取牲畜类型列表错误:', error);
res.status(500).json(responseFormat.error('获取牲畜类型列表失败'));
}
};
// 获取所有启用的牲畜类型(用于下拉选择)
const getActiveLivestockTypes = async (req, res) => {
try {
const types = await LivestockType.findAll({
where: { is_active: true },
attributes: ['id', 'name', 'description', 'base_value', 'premium_rate'],
order: [['name', 'ASC']]
});
res.json(responseFormat.success(types, '获取启用牲畜类型成功'));
} catch (error) {
console.error('获取启用牲畜类型错误:', error);
res.status(500).json(responseFormat.error('获取启用牲畜类型失败'));
}
};
// 创建牲畜类型
const createLivestockType = async (req, res) => {
try {
const typeData = req.body;
// 检查名称是否已存在
const existingType = await LivestockType.findOne({
where: { name: typeData.name }
});
if (existingType) {
return res.status(400).json(responseFormat.error('牲畜类型名称已存在'));
}
const type = await LivestockType.create({
...typeData,
created_by: req.user?.id
});
res.status(201).json(responseFormat.created(type, '牲畜类型创建成功'));
} catch (error) {
console.error('创建牲畜类型错误:', error);
if (error.name === 'SequelizeValidationError') {
const messages = error.errors.map(err => err.message);
return res.status(400).json(responseFormat.error(messages.join(', ')));
}
res.status(500).json(responseFormat.error('创建牲畜类型失败'));
}
};
// 获取单个牲畜类型详情
const getLivestockTypeById = async (req, res) => {
try {
const { id } = req.params;
const type = await LivestockType.findByPk(id);
if (!type) {
return res.status(404).json(responseFormat.error('牲畜类型不存在'));
}
res.json(responseFormat.success(type, '获取牲畜类型详情成功'));
} catch (error) {
console.error('获取牲畜类型详情错误:', error);
res.status(500).json(responseFormat.error('获取牲畜类型详情失败'));
}
};
// 更新牲畜类型
const updateLivestockType = async (req, res) => {
try {
const { id } = req.params;
const updateData = req.body;
const type = await LivestockType.findByPk(id);
if (!type) {
return res.status(404).json(responseFormat.error('牲畜类型不存在'));
}
// 如果更新名称,检查是否与其他记录重复
if (updateData.name && updateData.name !== type.name) {
const existingType = await LivestockType.findOne({
where: {
name: updateData.name,
id: { [Op.ne]: id }
}
});
if (existingType) {
return res.status(400).json(responseFormat.error('牲畜类型名称已存在'));
}
}
updateData.updated_by = req.user?.id;
await type.update(updateData);
res.json(responseFormat.success(type, '牲畜类型更新成功'));
} catch (error) {
console.error('更新牲畜类型错误:', error);
if (error.name === 'SequelizeValidationError') {
const messages = error.errors.map(err => err.message);
return res.status(400).json(responseFormat.error(messages.join(', ')));
}
res.status(500).json(responseFormat.error('更新牲畜类型失败'));
}
};
// 删除牲畜类型(软删除 - 设置为不启用)
const deleteLivestockType = async (req, res) => {
try {
const { id } = req.params;
const type = await LivestockType.findByPk(id);
if (!type) {
return res.status(404).json(responseFormat.error('牲畜类型不存在'));
}
// 检查是否有关联的保单
const LivestockPolicy = require('../models/LivestockPolicy');
const relatedPolicies = await LivestockPolicy.count({
where: { livestock_type_id: id }
});
if (relatedPolicies > 0) {
// 如果有关联保单,只能设置为不启用
await type.update({
is_active: false,
updated_by: req.user?.id
});
return res.json(responseFormat.success(null, '牲畜类型已设置为不启用(存在关联保单)'));
}
// 如果没有关联保单,可以物理删除
await type.destroy();
res.json(responseFormat.success(null, '牲畜类型删除成功'));
} catch (error) {
console.error('删除牲畜类型错误:', error);
res.status(500).json(responseFormat.error('删除牲畜类型失败'));
}
};
// 批量更新牲畜类型状态
const batchUpdateLivestockTypeStatus = async (req, res) => {
try {
const { ids, is_active } = req.body;
if (!Array.isArray(ids) || ids.length === 0) {
return res.status(400).json(responseFormat.error('请提供有效的ID列表'));
}
await LivestockType.update(
{
is_active,
updated_by: req.user?.id
},
{
where: { id: { [Op.in]: ids } }
}
);
res.json(responseFormat.success(null, '批量更新牲畜类型状态成功'));
} catch (error) {
console.error('批量更新牲畜类型状态错误:', error);
res.status(500).json(responseFormat.error('批量更新牲畜类型状态失败'));
}
};
module.exports = {
getLivestockTypes,
getActiveLivestockTypes,
createLivestockType,
getLivestockTypeById,
updateLivestockType,
deleteLivestockType,
batchUpdateLivestockTypeStatus
};

View File

@@ -0,0 +1,178 @@
const { User, Role, Menu } = require('../models');
/**
* 获取菜单列表
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
*/
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,
show: true,
status: 'active'
},
include: [
{
model: Menu,
as: 'children',
where: {
show: true,
status: 'active'
},
required: false,
order: [['order', 'ASC']]
}
],
order: [['order', 'ASC']]
});
console.log('基础菜单查询结果:', menus.length);
return res.json({
code: 200,
status: 'success',
data: menus,
message: '获取菜单成功'
});
}
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,
status: 'error',
message: '用户不存在'
});
}
// 获取角色的权限列表
const userPermissions = user.role?.permissions || [];
console.log('用户权限:', userPermissions);
console.log('查询菜单数据...');
// 查询菜单,这里简化处理,实际应用中可能需要根据权限过滤
const menus = await Menu.findAll({
where: {
parent_id: null,
show: true,
status: 'active'
},
include: [
{
model: Menu,
as: 'children',
where: {
show: true,
status: 'active'
},
required: false,
order: [['order', 'ASC']]
}
],
order: [['order', 'ASC']]
});
console.log('菜单查询结果:', menus.length);
// 这里可以添加根据权限过滤菜单的逻辑
// 简化示例,假设所有用户都能看到所有激活的菜单
return res.json({
code: 200,
status: 'success',
data: menus,
message: '获取菜单成功'
});
} catch (error) {
console.error('获取菜单失败:', error);
console.error('错误堆栈:', error.stack);
return res.status(500).json({
code: 500,
status: 'error',
message: '服务器内部错误',
error: error.message
});
}
};
/**
* 获取所有菜单(包括非激活状态,仅管理员可用)
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
*/
exports.getAllMenus = async (req, res) => {
try {
// 检查用户是否为管理员(简化示例)
const user = await User.findByPk(req.user?.id, {
include: [
{
model: Role,
attributes: ['id', 'name', 'permissions']
}
]
});
if (!user || !user.Role || !user.Role.permissions.includes('*:*')) {
return res.status(403).json({
code: 403,
status: 'error',
message: '没有权限查看所有菜单'
});
}
// 查询所有菜单
const menus = await Menu.findAll({
where: {
parent_id: null
},
include: [
{
model: Menu,
as: 'children',
required: false,
order: [['order', 'ASC']]
}
],
order: [['order', 'ASC']]
});
return res.json({
code: 200,
status: 'success',
data: menus,
message: '获取所有菜单成功'
});
} catch (error) {
console.error('获取所有菜单失败:', error);
return res.status(500).json({
code: 500,
status: '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

@@ -0,0 +1,415 @@
const { Permission, Role, Menu, RolePermission, MenuPermission } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
/**
* 构建权限树形结构
*/
function buildPermissionTree(permissions, parentId = null) {
const tree = [];
for (const permission of permissions) {
if (permission.parent_id === parentId) {
const children = buildPermissionTree(permissions, permission.id);
const node = {
...permission.toJSON(),
children: children.length > 0 ? children : undefined
};
tree.push(node);
}
}
return tree;
}
/**
* 权限管理控制器
*/
class PermissionController {
/**
* 获取权限列表
*/
async getPermissions(req, res) {
try {
const {
page = 1,
limit = 10,
module,
type,
status = 'active',
keyword
} = req.query;
const offset = (page - 1) * limit;
const where = { status };
// 模块筛选
if (module) {
where.module = module;
}
// 类型筛选
if (type) {
where.type = type;
}
// 关键词搜索
if (keyword) {
where[Op.or] = [
{ name: { [Op.like]: `%${keyword}%` } },
{ code: { [Op.like]: `%${keyword}%` } },
{ description: { [Op.like]: `%${keyword}%` } }
];
}
const { count, rows } = await Permission.findAndCountAll({
where,
include: [
{
model: Permission,
as: 'parent',
attributes: ['id', 'name', 'code']
},
{
model: Permission,
as: 'children',
attributes: ['id', 'name', 'code', 'type']
}
],
order: [['sort_order', 'ASC'], ['id', 'ASC']],
limit: parseInt(limit),
offset: parseInt(offset)
});
res.json(responseFormat.success({
permissions: rows,
pagination: {
total: count,
page: parseInt(page),
limit: parseInt(limit),
pages: Math.ceil(count / limit)
}
}, '获取权限列表成功'));
} catch (error) {
console.error('获取权限列表失败:', error);
res.status(500).json(responseFormat.error('获取权限列表失败'));
}
}
/**
* 获取权限树形结构
*/
async getPermissionTree(req, res) {
try {
const { module, type } = req.query;
const where = { status: 'active' };
if (module) {
where.module = module;
}
if (type) {
where.type = type;
}
const permissions = await Permission.findAll({
where,
order: [['sort_order', 'ASC'], ['id', 'ASC']]
});
// 构建树形结构
const tree = buildPermissionTree(permissions);
res.json(responseFormat.success(tree, '获取权限树成功'));
} catch (error) {
console.error('获取权限树失败:', error);
res.status(500).json(responseFormat.error('获取权限树失败'));
}
}
/**
* 获取单个权限详情
*/
async getPermissionById(req, res) {
try {
const { id } = req.params;
const permission = await Permission.findByPk(id, {
include: [
{
model: Permission,
as: 'parent',
attributes: ['id', 'name', 'code']
},
{
model: Permission,
as: 'children',
attributes: ['id', 'name', 'code', 'type']
}
]
});
if (!permission) {
return res.status(404).json(responseFormat.error('权限不存在'));
}
res.json(responseFormat.success(permission, '获取权限详情成功'));
} catch (error) {
console.error('获取权限详情失败:', error);
res.status(500).json(responseFormat.error('获取权限详情失败'));
}
}
/**
* 创建权限
*/
async createPermission(req, res) {
try {
const {
name,
code,
description,
module,
type = 'operation',
parent_id,
sort_order = 0
} = req.body;
// 验证必填字段
if (!name || !code || !module) {
return res.status(400).json(responseFormat.error('权限名称、权限代码和所属模块为必填项'));
}
// 检查权限代码是否已存在
const existingPermission = await Permission.findOne({ where: { code } });
if (existingPermission) {
return res.status(400).json(responseFormat.error('权限代码已存在'));
}
// 如果有父权限,验证父权限是否存在
if (parent_id) {
const parentPermission = await Permission.findByPk(parent_id);
if (!parentPermission) {
return res.status(400).json(responseFormat.error('父权限不存在'));
}
}
const permission = await Permission.create({
name,
code,
description,
module,
type,
parent_id,
sort_order,
status: 'active'
});
res.status(201).json(responseFormat.created(permission, '创建权限成功'));
} catch (error) {
console.error('创建权限失败:', error);
res.status(500).json(responseFormat.error('创建权限失败'));
}
}
/**
* 更新权限
*/
async updatePermission(req, res) {
try {
const { id } = req.params;
const {
name,
code,
description,
module,
type,
parent_id,
sort_order,
status
} = req.body;
const permission = await Permission.findByPk(id);
if (!permission) {
return res.status(404).json(responseFormat.error('权限不存在'));
}
// 如果修改了权限代码,检查是否与其他权限冲突
if (code && code !== permission.code) {
const existingPermission = await Permission.findOne({
where: {
code,
id: { [Op.ne]: id }
}
});
if (existingPermission) {
return res.status(400).json(responseFormat.error('权限代码已存在'));
}
}
// 如果有父权限,验证父权限是否存在且不是自己
if (parent_id) {
if (parent_id == id) {
return res.status(400).json(responseFormat.error('不能将自己设为父权限'));
}
const parentPermission = await Permission.findByPk(parent_id);
if (!parentPermission) {
return res.status(400).json(responseFormat.error('父权限不存在'));
}
}
await permission.update({
name: name || permission.name,
code: code || permission.code,
description: description !== undefined ? description : permission.description,
module: module || permission.module,
type: type || permission.type,
parent_id: parent_id !== undefined ? parent_id : permission.parent_id,
sort_order: sort_order !== undefined ? sort_order : permission.sort_order,
status: status || permission.status
});
res.json(responseFormat.success(permission, '更新权限成功'));
} catch (error) {
console.error('更新权限失败:', error);
res.status(500).json(responseFormat.error('更新权限失败'));
}
}
/**
* 删除权限
*/
async deletePermission(req, res) {
try {
const { id } = req.params;
const permission = await Permission.findByPk(id);
if (!permission) {
return res.status(404).json(responseFormat.error('权限不存在'));
}
// 检查是否有子权限
const childrenCount = await Permission.count({ where: { parent_id: id } });
if (childrenCount > 0) {
return res.status(400).json(responseFormat.error('该权限下还有子权限,无法删除'));
}
// 检查是否有角色在使用该权限
const rolePermissionCount = await RolePermission.count({ where: { permission_id: id } });
if (rolePermissionCount > 0) {
return res.status(400).json(responseFormat.error('该权限正在被角色使用,无法删除'));
}
// 检查是否有菜单在使用该权限
const menuPermissionCount = await MenuPermission.count({ where: { permission_id: id } });
if (menuPermissionCount > 0) {
return res.status(400).json(responseFormat.error('该权限正在被菜单使用,无法删除'));
}
await permission.destroy();
res.json(responseFormat.success(null, '删除权限成功'));
} catch (error) {
console.error('删除权限失败:', error);
res.status(500).json(responseFormat.error('删除权限失败'));
}
}
/**
* 获取角色权限
*/
async getRolePermissions(req, res) {
try {
const { roleId } = req.params;
const role = await Role.findByPk(roleId, {
include: [
{
model: Permission,
as: 'rolePermissions',
through: {
attributes: ['granted']
}
}
]
});
if (!role) {
return res.status(404).json(responseFormat.error('角色不存在'));
}
res.json(responseFormat.success({
role: {
id: role.id,
name: role.name,
description: role.description
},
permissions: role.rolePermissions
}, '获取角色权限成功'));
} catch (error) {
console.error('获取角色权限失败:', error);
res.status(500).json(responseFormat.error('获取角色权限失败'));
}
}
/**
* 分配角色权限
*/
async assignRolePermissions(req, res) {
try {
const { roleId } = req.params;
const { permissionIds } = req.body;
if (!Array.isArray(permissionIds)) {
return res.status(400).json(responseFormat.error('权限ID列表格式错误'));
}
const role = await Role.findByPk(roleId);
if (!role) {
return res.status(404).json(responseFormat.error('角色不存在'));
}
// 删除现有的角色权限关联
await RolePermission.destroy({ where: { role_id: roleId } });
// 创建新的角色权限关联
if (permissionIds.length > 0) {
const rolePermissions = permissionIds.map(permissionId => ({
role_id: roleId,
permission_id: permissionId,
granted: true
}));
await RolePermission.bulkCreate(rolePermissions);
}
res.json(responseFormat.success(null, '分配角色权限成功'));
} catch (error) {
console.error('分配角色权限失败:', error);
res.status(500).json(responseFormat.error('分配角色权限失败'));
}
}
/**
* 获取模块列表
*/
async getModules(req, res) {
try {
const modules = await Permission.findAll({
attributes: ['module'],
group: ['module'],
order: [['module', 'ASC']]
});
const moduleList = modules.map(item => item.module);
res.json(responseFormat.success(moduleList, '获取模块列表成功'));
} catch (error) {
console.error('获取模块列表失败:', error);
res.status(500).json(responseFormat.error('获取模块列表失败'));
}
}
}
module.exports = new PermissionController();

View File

@@ -0,0 +1,374 @@
const { Policy, InsuranceApplication, InsuranceType, User } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取保单列表
const getPolicies = async (req, res) => {
try {
const {
policy_number, // 前端发送的参数名
policyholder_name, // 前端发送的参数名
insurance_type_id, // 前端发送的参数名
status, // 前端发送的参数名
page = 1,
pageSize = 10 // 前端发送的参数名
} = req.query;
const whereClause = {};
// 保单编号筛选
if (policy_number) {
whereClause.policy_no = { [Op.like]: `%${policy_number}%` };
}
// 保单状态筛选
if (status) {
whereClause.policy_status = status;
}
// 保险类型筛选
if (insurance_type_id) {
whereClause.insurance_type_id = insurance_type_id;
}
const offset = (page - 1) * pageSize;
const { count, rows } = await Policy.findAndCountAll({
where: whereClause,
include: [
{
model: InsuranceApplication,
as: 'application',
attributes: ['id', 'customer_name', 'customer_phone'],
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name']
}
]
},
{
model: User,
as: 'customer',
attributes: ['id', 'real_name', 'username']
},
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name']
}
],
order: [['created_at', 'DESC']],
offset,
limit: parseInt(pageSize)
});
// 处理返回数据,确保前端能够正确解析
const processedRows = rows.map(row => {
const policy = row.toJSON();
return {
id: policy.id,
policy_number: policy.policy_no, // 映射字段名
// 优先使用保单表中的姓名字段,如果为空则使用关联表中的数据
policyholder_name: policy.policyholder_name || policy.application?.customer_name || policy.customer?.real_name || '',
insured_name: policy.insured_name || policy.application?.customer_name || policy.customer?.real_name || '',
insurance_type_id: policy.insurance_type_id,
insurance_type_name: policy.insurance_type?.name || '',
premium_amount: parseFloat(policy.premium_amount) || 0,
coverage_amount: parseFloat(policy.coverage_amount) || 0,
start_date: policy.start_date,
end_date: policy.end_date,
status: policy.policy_status, // 映射字段名
// 优先使用保单表中的联系信息字段
phone: policy.phone || policy.application?.customer_phone || '',
email: policy.email || policy.customer?.email || '',
address: policy.address || policy.application?.address || '',
remarks: policy.terms_and_conditions || '',
created_at: policy.created_at,
updated_at: policy.updated_at
};
});
res.json(responseFormat.pagination(processedRows, {
page: parseInt(page),
pageSize: parseInt(pageSize),
total: count
}, '获取保单列表成功'));
} catch (error) {
console.error('获取保单列表错误:', error);
res.status(500).json(responseFormat.error('获取保单列表失败'));
}
};
// 创建保单
const createPolicy = async (req, res) => {
try {
const policyData = req.body;
// 生成保单编号优先使用前端传递的policy_number否则自动生成
const policyNo = policyData.policy_number || `POL${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
// 构建保单数据,优先使用前端传递的数据
const createData = {
// 基本信息
policy_no: policyNo,
insurance_type_id: policyData.insurance_type_id || 1,
// 客户信息 - 只有在前端没有传递时才使用默认值
application_id: policyData.application_id || 4, // 默认关联存在的申请id=4
customer_id: policyData.customer_id || 1, // 默认关联第一个客户admin用户
// 保单持有人信息直接保存到Policy表中
policyholder_name: policyData.policyholder_name || '',
insured_name: policyData.insured_name || '',
// 金额信息
premium_amount: policyData.premium_amount,
coverage_amount: policyData.coverage_amount,
// 日期信息
start_date: policyData.start_date,
end_date: policyData.end_date,
// 状态信息
policy_status: policyData.status || 'active',
payment_status: 'unpaid',
// 联系信息直接保存到Policy表中
phone: policyData.phone || '',
email: policyData.email || '',
address: policyData.address || '',
// 备注
terms_and_conditions: policyData.remarks,
// 系统字段
created_by: req.user?.id || 1,
updated_by: req.user?.id || 1
};
const policy = await Policy.create(createData);
res.status(201).json(responseFormat.created(policy, '保单创建成功'));
} catch (error) {
console.error('创建保单错误:', error);
res.status(500).json(responseFormat.error('创建保单失败'));
}
};
// 获取单个保单详情
const getPolicyById = async (req, res) => {
try {
const { id } = req.params;
const policy = await Policy.findByPk(id, {
include: [
{
model: InsuranceApplication,
as: 'application',
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description', 'premium_rate']
}
]
},
{
model: User,
as: 'customer',
attributes: ['id', 'real_name', 'username', 'phone', 'email']
}
]
});
if (!policy) {
return res.status(404).json(responseFormat.error('保单不存在'));
}
res.json(responseFormat.success(policy, '获取保单详情成功'));
} catch (error) {
console.error('获取保单详情错误:', error);
res.status(500).json(responseFormat.error('获取保单详情失败'));
}
};
// 更新保单
const updatePolicy = async (req, res) => {
try {
const { id } = req.params;
const updateData = req.body;
console.log('更新保单请求 - ID:', id);
console.log('更新保单数据:', JSON.stringify(updateData, null, 2));
const policy = await Policy.findByPk(id);
if (!policy) {
return res.status(404).json(responseFormat.error('保单不存在'));
}
// 过滤掉null值的必填关联字段避免覆盖现有数据
const filteredUpdateData = { ...updateData };
// 处理日期字段格式
if (filteredUpdateData.start_date) {
// 确保日期格式正确
const startDate = new Date(filteredUpdateData.start_date);
if (isNaN(startDate.getTime())) {
console.error('无效的开始日期格式:', filteredUpdateData.start_date);
return res.status(400).json(responseFormat.error('开始日期格式无效'));
}
filteredUpdateData.start_date = startDate;
}
if (filteredUpdateData.end_date) {
// 确保日期格式正确
const endDate = new Date(filteredUpdateData.end_date);
if (isNaN(endDate.getTime())) {
console.error('无效的结束日期格式:', filteredUpdateData.end_date);
return res.status(400).json(responseFormat.error('结束日期格式无效'));
}
filteredUpdateData.end_date = endDate;
}
// 如果这些关联字段为null或undefined则不更新它们
if (filteredUpdateData.customer_id === null || filteredUpdateData.customer_id === undefined) {
delete filteredUpdateData.customer_id;
}
if (filteredUpdateData.application_id === null || filteredUpdateData.application_id === undefined) {
delete filteredUpdateData.application_id;
}
if (filteredUpdateData.insurance_type_id === null || filteredUpdateData.insurance_type_id === undefined) {
delete filteredUpdateData.insurance_type_id;
}
// 验证必填字段
if (filteredUpdateData.hasOwnProperty('policy_no') && (!filteredUpdateData.policy_no || filteredUpdateData.policy_no.trim() === '')) {
return res.status(400).json(responseFormat.error('保单编号不能为空'));
}
if (filteredUpdateData.hasOwnProperty('coverage_amount') && (filteredUpdateData.coverage_amount < 0)) {
return res.status(400).json(responseFormat.error('保额不能小于0'));
}
if (filteredUpdateData.hasOwnProperty('premium_amount') && (filteredUpdateData.premium_amount < 0)) {
return res.status(400).json(responseFormat.error('保费金额不能小于0'));
}
console.log('处理后的更新数据:', JSON.stringify(filteredUpdateData, null, 2));
await policy.update(filteredUpdateData);
console.log('保单更新成功:', policy.id);
res.json(responseFormat.success(policy, '保单更新成功'));
} catch (error) {
console.error('更新保单错误详情:', {
message: error.message,
stack: error.stack,
name: error.name,
sql: error.sql || 'N/A',
parameters: error.parameters || 'N/A'
});
// 根据错误类型返回更具体的错误信息
if (error.name === 'SequelizeValidationError') {
const validationErrors = error.errors.map(err => err.message).join(', ');
return res.status(400).json(responseFormat.error(`数据验证失败: ${validationErrors}`));
}
if (error.name === 'SequelizeUniqueConstraintError') {
return res.status(400).json(responseFormat.error('保单编号已存在'));
}
if (error.name === 'SequelizeForeignKeyConstraintError') {
return res.status(400).json(responseFormat.error('关联数据不存在'));
}
res.status(500).json(responseFormat.error('更新保单失败'));
}
};
// 更新保单状态
const updatePolicyStatus = async (req, res) => {
try {
const { id } = req.params;
const { policy_status } = req.body;
const policy = await Policy.findByPk(id);
if (!policy) {
return res.status(404).json(responseFormat.error('保单不存在'));
}
if (!['active', 'expired', 'cancelled', 'suspended'].includes(policy_status)) {
return res.status(400).json(responseFormat.error('无效的保单状态'));
}
await policy.update({ policy_status });
res.json(responseFormat.success(policy, '保单状态更新成功'));
} catch (error) {
console.error('更新保单状态错误:', error);
res.status(500).json(responseFormat.error('更新保单状态失败'));
}
};
// 获取保单统计
const getPolicyStats = async (req, res) => {
try {
const stats = await Policy.findAll({
attributes: [
'policy_status',
[Policy.sequelize.fn('COUNT', Policy.sequelize.col('id')), 'count'],
[Policy.sequelize.fn('SUM', Policy.sequelize.col('coverage_amount')), 'total_coverage'],
[Policy.sequelize.fn('SUM', Policy.sequelize.col('premium_amount')), 'total_premium']
],
group: ['policy_status']
});
const total = await Policy.count();
const totalCoverage = await Policy.sum('coverage_amount');
const totalPremium = await Policy.sum('premium_amount');
res.json(responseFormat.success({
stats,
total,
total_coverage: totalCoverage || 0,
total_premium: totalPremium || 0
}, '获取保单统计成功'));
} catch (error) {
console.error('获取保单统计错误:', error);
res.status(500).json(responseFormat.error('获取保单统计失败'));
}
};
// 删除保单
const deletePolicy = async (req, res) => {
try {
const { id } = req.params;
const policy = await Policy.findByPk(id);
if (!policy) {
return res.status(404).json(responseFormat.error('保单不存在'));
}
await policy.destroy();
res.json(responseFormat.success(null, '保单删除成功'));
} catch (error) {
console.error('删除保单错误:', error);
res.status(500).json(responseFormat.error('删除保单失败'));
}
};
module.exports = {
getPolicies,
createPolicy,
getPolicyById,
updatePolicy,
updatePolicyStatus,
deletePolicy,
getPolicyStats
};

View File

@@ -0,0 +1,603 @@
const { sequelize } = require('../config/database');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
/**
* 监管任务结项控制器
* 处理监管任务结项相关的业务逻辑
*/
// 获取监管任务结项列表
const getTaskCompletions = async (req, res) => {
try {
const {
application_no, // 申请编号
policy_no, // 保单号
product_name, // 产品名称
customer_name, // 客户名称
completion_date, // 结项日期
status, // 状态
reviewer_name, // 审核人员
page = 1,
limit = 10
} = req.query;
// 构建查询条件
let whereClause = '';
const params = [];
if (application_no) {
whereClause += ' AND rtc.application_no LIKE ?';
params.push(`%${application_no}%`);
}
if (policy_no) {
whereClause += ' AND rtc.policy_no LIKE ?';
params.push(`%${policy_no}%`);
}
if (product_name) {
whereClause += ' AND rtc.product_name LIKE ?';
params.push(`%${product_name}%`);
}
if (customer_name) {
whereClause += ' AND rtc.customer_name LIKE ?';
params.push(`%${customer_name}%`);
}
if (completion_date) {
whereClause += ' AND DATE(rtc.completion_date) = ?';
params.push(completion_date);
}
if (status) {
whereClause += ' AND rtc.status = ?';
params.push(status);
}
if (reviewer_name) {
whereClause += ' AND rtc.reviewer_name LIKE ?';
params.push(`%${reviewer_name}%`);
}
// 计算偏移量
const offset = (page - 1) * limit;
// 查询总数
const countQuery = `
SELECT COUNT(*) as total
FROM regulatory_task_completions rtc
WHERE 1=1 ${whereClause}
`;
const [countResult] = await sequelize.query(countQuery, {
replacements: params,
type: sequelize.QueryTypes.SELECT
});
// 查询数据
const dataQuery = `
SELECT
rtc.id,
rtc.application_no,
rtc.policy_no,
rtc.product_name,
rtc.customer_name,
rtc.customer_phone,
rtc.insurance_amount,
rtc.premium_amount,
rtc.start_date,
rtc.end_date,
rtc.completion_date,
rtc.status,
rtc.reviewer_name,
rtc.review_comments,
rtc.created_at,
rtc.updated_at
FROM regulatory_task_completions rtc
WHERE 1=1 ${whereClause}
ORDER BY rtc.created_at DESC
LIMIT ? OFFSET ?
`;
const results = await sequelize.query(dataQuery, {
replacements: [...params, parseInt(limit), offset],
type: sequelize.QueryTypes.SELECT
});
res.json(responseFormat.success({
list: results,
pagination: {
current: parseInt(page),
pageSize: parseInt(limit),
total: countResult.total,
totalPages: Math.ceil(countResult.total / limit)
}
}));
} catch (error) {
console.error('获取监管任务结项列表失败:', error);
res.status(500).json(responseFormat.error('获取监管任务结项列表失败'));
}
};
// 获取单个监管任务结项详情
const getTaskCompletionById = async (req, res) => {
try {
const { id } = req.params;
const query = `
SELECT
rtc.*,
(
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id', rtca.id,
'file_name', rtca.file_name,
'file_path', rtca.file_path,
'file_size', rtca.file_size,
'file_type', rtca.file_type,
'upload_time', rtca.upload_time
)
)
FROM regulatory_task_completion_attachments rtca
WHERE rtca.completion_id = rtc.id
) as attachments,
(
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id', rtcl.id,
'operation_type', rtcl.operation_type,
'operation_description', rtcl.operation_description,
'operator_name', rtcl.operator_name,
'operation_time', rtcl.operation_time,
'ip_address', rtcl.ip_address
)
)
FROM regulatory_task_completion_logs rtcl
WHERE rtcl.completion_id = rtc.id
ORDER BY rtcl.operation_time DESC
) as operation_logs
FROM regulatory_task_completions rtc
WHERE rtc.id = ?
`;
const [result] = await sequelize.query(query, {
replacements: [id],
type: sequelize.QueryTypes.SELECT
});
if (!result) {
return res.status(404).json(responseFormat.error('监管任务结项记录不存在'));
}
// 解析JSON字段
if (result.attachments) {
result.attachments = JSON.parse(result.attachments) || [];
} else {
result.attachments = [];
}
if (result.operation_logs) {
result.operation_logs = JSON.parse(result.operation_logs) || [];
} else {
result.operation_logs = [];
}
res.json(responseFormat.success(result));
} catch (error) {
console.error('获取监管任务结项详情失败:', error);
res.status(500).json(responseFormat.error('获取监管任务结项详情失败'));
}
};
// 创建监管任务结项记录
const createTaskCompletion = async (req, res) => {
const transaction = await sequelize.transaction();
try {
const {
application_no,
policy_no,
product_name,
customer_name,
customer_phone,
insurance_amount,
premium_amount,
start_date,
end_date,
completion_date,
status = 'pending',
review_comments,
attachments = []
} = req.body;
// 验证必填字段
if (!application_no || !policy_no || !product_name || !customer_name) {
return res.status(400).json(responseFormat.error('申请编号、保单号、产品名称、客户名称为必填项'));
}
// 检查申请编号是否已存在
const existingQuery = `
SELECT id FROM regulatory_task_completions
WHERE application_no = ? OR policy_no = ?
`;
const [existing] = await sequelize.query(existingQuery, {
replacements: [application_no, policy_no],
type: sequelize.QueryTypes.SELECT,
transaction
});
if (existing) {
await transaction.rollback();
return res.status(400).json(responseFormat.error('申请编号或保单号已存在'));
}
// 创建监管任务结项记录
const insertQuery = `
INSERT INTO regulatory_task_completions (
application_no, policy_no, product_name, customer_name, customer_phone,
insurance_amount, premium_amount, start_date, end_date, completion_date,
status, review_comments, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
`;
const [insertResult] = await sequelize.query(insertQuery, {
replacements: [
application_no, policy_no, product_name, customer_name, customer_phone,
insurance_amount, premium_amount, start_date, end_date, completion_date,
status, review_comments
],
type: sequelize.QueryTypes.INSERT,
transaction
});
const completionId = insertResult;
// 创建附件记录
if (attachments && attachments.length > 0) {
for (const attachment of attachments) {
const attachmentQuery = `
INSERT INTO regulatory_task_completion_attachments (
completion_id, file_name, file_path, file_size, file_type, upload_time
) VALUES (?, ?, ?, ?, ?, NOW())
`;
await sequelize.query(attachmentQuery, {
replacements: [
completionId,
attachment.file_name,
attachment.file_path,
attachment.file_size,
attachment.file_type
],
type: sequelize.QueryTypes.INSERT,
transaction
});
}
}
// 记录操作日志
const logQuery = `
INSERT INTO regulatory_task_completion_logs (
completion_id, operation_type, operation_description,
operator_name, operation_time, ip_address
) VALUES (?, ?, ?, ?, NOW(), ?)
`;
await sequelize.query(logQuery, {
replacements: [
completionId,
'create',
'创建监管任务结项记录',
req.user?.real_name || '系统',
req.ip
],
type: sequelize.QueryTypes.INSERT,
transaction
});
await transaction.commit();
res.status(201).json(responseFormat.success({
id: completionId,
message: '监管任务结项记录创建成功'
}));
} catch (error) {
await transaction.rollback();
console.error('创建监管任务结项记录失败:', error);
res.status(500).json(responseFormat.error('创建监管任务结项记录失败'));
}
};
// 更新监管任务结项记录
const updateTaskCompletion = async (req, res) => {
const transaction = await sequelize.transaction();
try {
const { id } = req.params;
const {
application_no,
policy_no,
product_name,
customer_name,
customer_phone,
insurance_amount,
premium_amount,
start_date,
end_date,
completion_date,
status,
review_comments
} = req.body;
// 检查记录是否存在
const checkQuery = `SELECT id FROM regulatory_task_completions WHERE id = ?`;
const [existing] = await sequelize.query(checkQuery, {
replacements: [id],
type: sequelize.QueryTypes.SELECT,
transaction
});
if (!existing) {
await transaction.rollback();
return res.status(404).json(responseFormat.error('监管任务结项记录不存在'));
}
// 更新记录
const updateQuery = `
UPDATE regulatory_task_completions SET
application_no = ?, policy_no = ?, product_name = ?, customer_name = ?,
customer_phone = ?, insurance_amount = ?, premium_amount = ?,
start_date = ?, end_date = ?, completion_date = ?, status = ?,
review_comments = ?, updated_at = NOW()
WHERE id = ?
`;
await sequelize.query(updateQuery, {
replacements: [
application_no, policy_no, product_name, customer_name, customer_phone,
insurance_amount, premium_amount, start_date, end_date, completion_date,
status, review_comments, id
],
type: sequelize.QueryTypes.UPDATE,
transaction
});
// 记录操作日志
const logQuery = `
INSERT INTO regulatory_task_completion_logs (
completion_id, operation_type, operation_description,
operator_name, operation_time, ip_address
) VALUES (?, ?, ?, ?, NOW(), ?)
`;
await sequelize.query(logQuery, {
replacements: [
id,
'update',
'更新监管任务结项记录',
req.user?.real_name || '系统',
req.ip
],
type: sequelize.QueryTypes.INSERT,
transaction
});
await transaction.commit();
res.json(responseFormat.success({ message: '监管任务结项记录更新成功' }));
} catch (error) {
await transaction.rollback();
console.error('更新监管任务结项记录失败:', error);
res.status(500).json(responseFormat.error('更新监管任务结项记录失败'));
}
};
// 删除监管任务结项记录
const deleteTaskCompletion = async (req, res) => {
const transaction = await sequelize.transaction();
try {
const { id } = req.params;
// 检查记录是否存在
const checkQuery = `SELECT id FROM regulatory_task_completions WHERE id = ?`;
const [existing] = await sequelize.query(checkQuery, {
replacements: [id],
type: sequelize.QueryTypes.SELECT,
transaction
});
if (!existing) {
await transaction.rollback();
return res.status(404).json(responseFormat.error('监管任务结项记录不存在'));
}
// 删除相关附件记录
await sequelize.query(
'DELETE FROM regulatory_task_completion_attachments WHERE completion_id = ?',
{
replacements: [id],
type: sequelize.QueryTypes.DELETE,
transaction
}
);
// 删除相关日志记录
await sequelize.query(
'DELETE FROM regulatory_task_completion_logs WHERE completion_id = ?',
{
replacements: [id],
type: sequelize.QueryTypes.DELETE,
transaction
}
);
// 删除主记录
await sequelize.query(
'DELETE FROM regulatory_task_completions WHERE id = ?',
{
replacements: [id],
type: sequelize.QueryTypes.DELETE,
transaction
}
);
await transaction.commit();
res.json(responseFormat.success({ message: '监管任务结项记录删除成功' }));
} catch (error) {
await transaction.rollback();
console.error('删除监管任务结项记录失败:', error);
res.status(500).json(responseFormat.error('删除监管任务结项记录失败'));
}
};
// 审核监管任务结项记录
const reviewTaskCompletion = async (req, res) => {
const transaction = await sequelize.transaction();
try {
const { id } = req.params;
const { status, review_comments, reviewer_name } = req.body;
// 验证状态值
const validStatuses = ['pending', 'approved', 'rejected', 'completed'];
if (!validStatuses.includes(status)) {
return res.status(400).json(responseFormat.error('无效的状态值'));
}
// 检查记录是否存在
const checkQuery = `SELECT id, status FROM regulatory_task_completions WHERE id = ?`;
const [existing] = await sequelize.query(checkQuery, {
replacements: [id],
type: sequelize.QueryTypes.SELECT,
transaction
});
if (!existing) {
await transaction.rollback();
return res.status(404).json(responseFormat.error('监管任务结项记录不存在'));
}
// 更新审核状态
const updateQuery = `
UPDATE regulatory_task_completions SET
status = ?, review_comments = ?, reviewer_name = ?, updated_at = NOW()
WHERE id = ?
`;
await sequelize.query(updateQuery, {
replacements: [status, review_comments, reviewer_name, id],
type: sequelize.QueryTypes.UPDATE,
transaction
});
// 记录操作日志
const logQuery = `
INSERT INTO regulatory_task_completion_logs (
completion_id, operation_type, operation_description,
operator_name, operation_time, ip_address
) VALUES (?, ?, ?, ?, NOW(), ?)
`;
const operationDesc = `审核监管任务结项记录,状态变更为:${status}`;
await sequelize.query(logQuery, {
replacements: [
id,
'review',
operationDesc,
reviewer_name || req.user?.real_name || '系统',
req.ip
],
type: sequelize.QueryTypes.INSERT,
transaction
});
await transaction.commit();
res.json(responseFormat.success({ message: '监管任务结项记录审核成功' }));
} catch (error) {
await transaction.rollback();
console.error('审核监管任务结项记录失败:', error);
res.status(500).json(responseFormat.error('审核监管任务结项记录失败'));
}
};
// 获取监管任务结项统计数据
const getTaskCompletionStats = async (req, res) => {
try {
// 状态统计
const statusStatsQuery = `
SELECT
status,
COUNT(*) as count
FROM regulatory_task_completions
GROUP BY status
`;
const statusStats = await sequelize.query(statusStatsQuery, {
type: sequelize.QueryTypes.SELECT
});
// 月度统计
const monthlyStatsQuery = `
SELECT
DATE_FORMAT(completion_date, '%Y-%m') as month,
COUNT(*) as count,
SUM(insurance_amount) as total_amount
FROM regulatory_task_completions
WHERE completion_date >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY DATE_FORMAT(completion_date, '%Y-%m')
ORDER BY month
`;
const monthlyStats = await sequelize.query(monthlyStatsQuery, {
type: sequelize.QueryTypes.SELECT
});
// 产品统计
const productStatsQuery = `
SELECT
product_name,
COUNT(*) as count,
SUM(insurance_amount) as total_amount
FROM regulatory_task_completions
GROUP BY product_name
ORDER BY count DESC
LIMIT 10
`;
const productStats = await sequelize.query(productStatsQuery, {
type: sequelize.QueryTypes.SELECT
});
res.json(responseFormat.success({
statusStats,
monthlyStats,
productStats
}));
} catch (error) {
console.error('获取监管任务结项统计数据失败:', error);
res.status(500).json(responseFormat.error('获取监管任务结项统计数据失败'));
}
};
module.exports = {
getTaskCompletions,
getTaskCompletionById,
createTaskCompletion,
updateTaskCompletion,
deleteTaskCompletion,
reviewTaskCompletion,
getTaskCompletionStats
};

View File

@@ -0,0 +1,459 @@
const { Role, Permission, RolePermission, User } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
/**
* 角色权限管理控制器
* 专门处理角色权限的分配、管理和动态调用
*/
class RolePermissionController {
/**
* 获取所有角色及其权限
*/
async getAllRolesWithPermissions(req, res) {
try {
const roles = await Role.findAll({
order: [['id', 'ASC']]
});
const rolesData = await Promise.all(roles.map(async (role) => {
// 从RolePermission表获取权限
const rolePermissions = await RolePermission.findAll({
where: {
role_id: role.id,
granted: true
},
include: [{
model: Permission,
as: 'permission',
attributes: ['id', 'name', 'code', 'description', 'module', 'type']
}]
});
const permissions = rolePermissions.map(rp => rp.permission.code);
return {
id: role.id,
name: role.name,
description: role.description,
status: role.status,
permissions: permissions,
permissionCount: permissions.length
};
}));
res.json(responseFormat.success({
roles: rolesData,
total: rolesData.length
}, '获取角色权限列表成功'));
} catch (error) {
console.error('获取角色权限列表失败:', error);
res.status(500).json(responseFormat.error('获取角色权限列表失败'));
}
}
/**
* 获取所有权限
*/
async getAllPermissions(req, res) {
try {
const permissions = await Permission.findAll({
order: [['id', 'ASC']]
});
res.json(responseFormat.success(permissions, '获取权限列表成功'));
} catch (error) {
console.error('获取权限列表失败:', error);
res.status(500).json(responseFormat.error('获取权限列表失败'));
}
}
/**
* 获取指定角色的权限详情
*/
async getRolePermissionDetail(req, res) {
try {
const { roleId } = req.params;
console.log('获取角色权限详情角色ID:', roleId);
const role = await Role.findByPk(roleId);
console.log('角色查询结果:', role ? role.name : '未找到');
if (!role) {
return res.status(404).json(responseFormat.error('角色不存在'));
}
// 获取所有权限用于对比
const allPermissions = await Permission.findAll({
attributes: ['id', 'name', 'code', 'description', 'module', 'type', 'parent_id'],
order: [['module', 'ASC'], ['id', 'ASC']]
});
console.log('权限查询结果:', allPermissions.length, '个权限');
// 从RolePermission表获取角色已分配的权限
const rolePermissions = await RolePermission.findAll({
where: {
role_id: roleId,
granted: true
},
include: [{
model: Permission,
as: 'permission',
attributes: ['id', 'name', 'code', 'description', 'module', 'type']
}]
});
const assignedPermissionCodes = rolePermissions.map(rp => rp.permission.code);
console.log('已分配权限代码:', assignedPermissionCodes.length, '个');
// 暂时返回简化数据,不构建权限树
res.json(responseFormat.success({
role: {
id: role.id,
name: role.name,
description: role.description,
status: role.status
},
assignedPermissions: assignedPermissionCodes,
allPermissions: allPermissions.map(p => ({
id: p.id,
name: p.name,
code: p.code,
description: p.description,
module: p.module,
type: p.type,
parent_id: p.parent_id,
assigned: assignedPermissionCodes.includes(p.code)
})),
assignedCount: assignedPermissionCodes.length,
totalCount: allPermissions.length
}, '获取角色权限详情成功'));
} catch (error) {
console.error('获取角色权限详情失败:', error);
console.error('错误堆栈:', error.stack);
res.status(500).json(responseFormat.error('获取角色权限详情失败'));
}
}
/**
* 批量分配角色权限
*/
async batchAssignPermissions(req, res) {
try {
const { roleId } = req.params;
const { permissionIds, operation = 'replace' } = req.body;
console.log('=== 批量分配权限开始 ===');
console.log('角色ID:', roleId);
console.log('权限ID列表:', permissionIds);
console.log('操作类型:', operation);
console.log('请求体:', JSON.stringify(req.body, null, 2));
if (!Array.isArray(permissionIds)) {
console.log('❌ 权限ID列表格式错误');
return res.status(400).json(responseFormat.error('权限ID列表格式错误'));
}
const role = await Role.findByPk(roleId);
if (!role) {
console.log('❌ 角色不存在');
return res.status(404).json(responseFormat.error('角色不存在'));
}
console.log('找到角色:', role.name);
// 验证权限ID是否存在
const validPermissions = await Permission.findAll({
where: { id: { [Op.in]: permissionIds } },
attributes: ['id']
});
const validPermissionIds = validPermissions.map(p => p.id);
const invalidIds = permissionIds.filter(id => !validPermissionIds.includes(id));
console.log('有效权限ID:', validPermissionIds);
console.log('无效权限ID:', invalidIds);
if (invalidIds.length > 0) {
console.log('❌ 存在无效的权限ID');
return res.status(400).json(responseFormat.error(`无效的权限ID: ${invalidIds.join(', ')}`));
}
// 根据操作类型处理权限分配
if (operation === 'replace') {
console.log('执行替换模式权限分配');
// 替换模式:删除现有权限,添加新权限
const deletedCount = await RolePermission.destroy({ where: { role_id: roleId } });
console.log('删除现有权限数量:', deletedCount);
if (permissionIds.length > 0) {
const rolePermissions = permissionIds.map(permissionId => ({
role_id: roleId,
permission_id: permissionId,
granted: true
}));
console.log('准备创建的权限记录:', rolePermissions);
const createdPermissions = await RolePermission.bulkCreate(rolePermissions);
console.log('成功创建的权限记录数量:', createdPermissions.length);
}
} else if (operation === 'add') {
// 添加模式:只添加新权限
const existingPermissions = await RolePermission.findAll({
where: { role_id: roleId },
attributes: ['permission_id']
});
const existingIds = existingPermissions.map(p => p.permission_id);
const newPermissionIds = permissionIds.filter(id => !existingIds.includes(id));
if (newPermissionIds.length > 0) {
const rolePermissions = newPermissionIds.map(permissionId => ({
role_id: roleId,
permission_id: permissionId,
granted: true
}));
await RolePermission.bulkCreate(rolePermissions);
}
} else if (operation === 'remove') {
// 移除模式:删除指定权限
await RolePermission.destroy({
where: {
role_id: roleId,
permission_id: { [Op.in]: permissionIds }
}
});
}
console.log('✅ 权限分配完成');
res.json(responseFormat.success(null, `${operation === 'replace' ? '替换' : operation === 'add' ? '添加' : '移除'}角色权限成功`));
} catch (error) {
console.error('❌ 批量分配权限失败:', error);
console.error('错误堆栈:', error.stack);
res.status(500).json(responseFormat.error('批量分配角色权限失败'));
}
console.log('=== 批量分配权限结束 ===');
}
/**
* 复制角色权限
*/
async copyRolePermissions(req, res) {
try {
const { sourceRoleId, targetRoleId } = req.body;
if (!sourceRoleId || !targetRoleId) {
return res.status(400).json(responseFormat.error('源角色ID和目标角色ID不能为空'));
}
if (sourceRoleId === targetRoleId) {
return res.status(400).json(responseFormat.error('源角色和目标角色不能相同'));
}
// 验证角色存在
const [sourceRole, targetRole] = await Promise.all([
Role.findByPk(sourceRoleId),
Role.findByPk(targetRoleId)
]);
if (!sourceRole) {
return res.status(404).json(responseFormat.error('源角色不存在'));
}
if (!targetRole) {
return res.status(404).json(responseFormat.error('目标角色不存在'));
}
// 获取源角色的权限
const sourcePermissions = await RolePermission.findAll({
where: { role_id: sourceRoleId },
attributes: ['permission_id']
});
// 删除目标角色现有权限
await RolePermission.destroy({ where: { role_id: targetRoleId } });
// 复制权限到目标角色
if (sourcePermissions.length > 0) {
const targetPermissions = sourcePermissions.map(p => ({
role_id: targetRoleId,
permission_id: p.permission_id,
granted: true
}));
await RolePermission.bulkCreate(targetPermissions);
}
res.json(responseFormat.success(null, `成功将 ${sourceRole.name} 的权限复制到 ${targetRole.name}`));
} catch (error) {
console.error('复制角色权限失败:', error);
res.status(500).json(responseFormat.error('复制角色权限失败'));
}
}
/**
* 检查用户权限
*/
async checkUserPermission(req, res) {
try {
const { userId, permissionCode } = req.params;
const user = await User.findByPk(userId, {
include: [
{
model: Role,
as: 'role',
include: [
{
model: Permission,
as: 'rolePermissions',
where: { code: permissionCode },
required: false,
through: {
attributes: ['granted']
}
}
]
}
]
});
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
const hasPermission = user.role &&
user.role.rolePermissions &&
user.role.rolePermissions.length > 0 &&
user.role.rolePermissions[0].RolePermission.granted;
res.json(responseFormat.success({
userId: user.id,
username: user.username,
roleName: user.role ? user.role.name : null,
permissionCode,
hasPermission,
checkTime: new Date()
}, '权限检查完成'));
} catch (error) {
console.error('检查用户权限失败:', error);
res.status(500).json(responseFormat.error('检查用户权限失败'));
}
}
/**
* 获取权限统计信息
*/
async getPermissionStats(req, res) {
try {
// 统计各种数据
const [
totalRoles,
totalPermissions,
moduleStats,
roles
] = await Promise.all([
Role.count(),
Permission.count(),
Permission.findAll({
attributes: [
'module',
[Permission.sequelize.fn('COUNT', Permission.sequelize.col('id')), 'count']
],
group: ['module'],
order: [['module', 'ASC']]
}),
Role.findAll({
attributes: ['id', 'name', 'permissions'],
order: [['name', 'ASC']]
})
]);
// 计算总分配数和角色权限分布
let totalAssignments = 0;
const roleDistribution = roles.map(role => {
let permissions = [];
if (Array.isArray(role.permissions)) {
permissions = role.permissions;
} else if (typeof role.permissions === 'string') {
try {
permissions = JSON.parse(role.permissions);
} catch (e) {
permissions = [];
}
}
const permissionCount = permissions.length;
totalAssignments += permissionCount;
return {
roleId: role.id,
roleName: role.name,
permissionCount
};
});
res.json(responseFormat.success({
overview: {
totalRoles,
totalPermissions,
totalAssignments,
averagePermissionsPerRole: totalRoles > 0 ? Math.round(totalAssignments / totalRoles) : 0
},
moduleDistribution: moduleStats.map(stat => ({
module: stat.module,
count: parseInt(stat.dataValues.count)
})),
roleDistribution
}, '获取权限统计成功'));
} catch (error) {
console.error('获取权限统计失败:', error);
res.status(500).json(responseFormat.error('获取权限统计失败'));
}
}
/**
* 构建权限树
*/
buildPermissionTree(permissions, parentId = null) {
const tree = [];
for (const permission of permissions) {
if (permission.parent_id === parentId) {
const children = this.buildPermissionTree(permissions, permission.id);
const node = {
...(permission.toJSON ? permission.toJSON() : permission),
children: children.length > 0 ? children : undefined
};
tree.push(node);
}
}
return tree;
}
/**
* 标记已分配的权限
*/
markAssignedPermissions(permissions, assignedIds) {
return permissions.map(permission => ({
...permission,
assigned: assignedIds.includes(permission.id),
children: permission.children ?
this.markAssignedPermissions(permission.children, assignedIds) :
undefined
}));
}
/**
* 根据权限代码标记已分配的权限
*/
markAssignedPermissionsByCode(permissions, assignedCodes) {
return permissions.map(permission => ({
...permission,
assigned: assignedCodes.includes(permission.code),
children: permission.children ?
this.markAssignedPermissionsByCode(permission.children, assignedCodes) :
undefined
}));
}
}
module.exports = new RolePermissionController();

View File

@@ -0,0 +1,316 @@
const { User, Role, InsuranceApplication, Policy, Claim } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取系统统计信息
const getSystemStats = async (req, res) => {
try {
const [
totalUsers,
totalRoles,
totalApplications,
totalPolicies,
totalClaims,
activeUsers,
pendingApplications,
approvedApplications,
activePolicies,
pendingClaims,
approvedClaims
] = await Promise.all([
User.count(),
Role.count(),
InsuranceApplication.count(),
Policy.count(),
Claim.count(),
User.count({ where: { status: 'active' } }),
InsuranceApplication.count({ where: { status: 'pending' } }),
InsuranceApplication.count({ where: { status: 'approved' } }),
Policy.count({ where: { policy_status: 'active' } }),
Claim.count({ where: { claim_status: 'pending' } }),
Claim.count({ where: { claim_status: 'approved' } })
]);
// 获取最近7天的数据趋势
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const recentStats = await Promise.all([
User.count({ where: { created_at: { [Op.gte]: sevenDaysAgo } } }),
InsuranceApplication.count({ where: { created_at: { [Op.gte]: sevenDaysAgo } } }),
Policy.count({ where: { created_at: { [Op.gte]: sevenDaysAgo } } }),
Claim.count({ where: { created_at: { [Op.gte]: sevenDaysAgo } } })
]);
res.json(responseFormat.success({
overview: {
users: totalUsers,
roles: totalRoles,
applications: totalApplications,
policies: totalPolicies,
claims: totalClaims
},
status: {
active_users: activeUsers,
pending_applications: pendingApplications,
approved_applications: approvedApplications,
active_policies: activePolicies,
pending_claims: pendingClaims,
approved_claims: approvedClaims
},
recent: {
new_users: recentStats[0],
new_applications: recentStats[1],
new_policies: recentStats[2],
new_claims: recentStats[3]
}
}, '获取系统统计信息成功'));
} catch (error) {
console.error('获取系统统计信息错误:', error);
res.status(500).json(responseFormat.error('获取系统统计信息失败'));
}
};
// 获取系统日志(模拟)
const getSystemLogs = async (req, res) => {
try {
const { page = 1, limit = 50, level, start_date, end_date } = req.query;
const offset = (page - 1) * limit;
// 模拟日志数据 - 扩展更多有意义的日志记录
const mockLogs = [
{
id: 1,
level: 'info',
message: '系统启动成功',
timestamp: new Date().toISOString(),
user: 'system'
},
{
id: 2,
level: 'info',
message: '数据库连接成功',
timestamp: new Date(Date.now() - 1000 * 60).toISOString(),
user: 'system'
},
{
id: 3,
level: 'warning',
message: 'Redis连接失败使用内存缓存',
timestamp: new Date(Date.now() - 1000 * 120).toISOString(),
user: 'system'
},
{
id: 4,
level: 'info',
message: '用户 admin 登录成功',
timestamp: new Date(Date.now() - 1000 * 180).toISOString(),
user: 'admin'
},
{
id: 5,
level: 'info',
message: '新增保险申请:车险申请 - 申请人:张三',
timestamp: new Date(Date.now() - 1000 * 240).toISOString(),
user: 'zhangsan'
},
{
id: 6,
level: 'info',
message: '保单生效:保单号 POL-2024-001 - 投保人:李四',
timestamp: new Date(Date.now() - 1000 * 300).toISOString(),
user: 'lisi'
},
{
id: 7,
level: 'warning',
message: '理赔申请待审核:理赔号 CLM-2024-001 - 申请人:王五',
timestamp: new Date(Date.now() - 1000 * 360).toISOString(),
user: 'wangwu'
},
{
id: 8,
level: 'info',
message: '新用户注册:用户名 zhaoliu',
timestamp: new Date(Date.now() - 1000 * 420).toISOString(),
user: 'system'
},
{
id: 9,
level: 'error',
message: '支付接口调用失败:订单号 ORD-2024-001',
timestamp: new Date(Date.now() - 1000 * 480).toISOString(),
user: 'system'
},
{
id: 10,
level: 'info',
message: '保险类型更新:新增意外险产品',
timestamp: new Date(Date.now() - 1000 * 540).toISOString(),
user: 'admin'
},
{
id: 11,
level: 'info',
message: '系统备份完成:数据库备份成功',
timestamp: new Date(Date.now() - 1000 * 600).toISOString(),
user: 'system'
},
{
id: 12,
level: 'warning',
message: '磁盘空间不足警告:剩余空间 15%',
timestamp: new Date(Date.now() - 1000 * 660).toISOString(),
user: 'system'
},
{
id: 13,
level: 'info',
message: '理赔审核通过:理赔号 CLM-2024-002 - 赔付金额 ¥5000',
timestamp: new Date(Date.now() - 1000 * 720).toISOString(),
user: 'admin'
},
{
id: 14,
level: 'info',
message: '保单续费成功:保单号 POL-2024-002 - 续费期限 1年',
timestamp: new Date(Date.now() - 1000 * 780).toISOString(),
user: 'system'
},
{
id: 15,
level: 'error',
message: '短信发送失败:手机号 138****8888',
timestamp: new Date(Date.now() - 1000 * 840).toISOString(),
user: 'system'
}
];
// 简单的过滤逻辑
let filteredLogs = mockLogs;
if (level) {
filteredLogs = filteredLogs.filter(log => log.level === level);
}
if (start_date) {
const startDate = new Date(start_date);
filteredLogs = filteredLogs.filter(log => new Date(log.timestamp) >= startDate);
}
if (end_date) {
const endDate = new Date(end_date);
filteredLogs = filteredLogs.filter(log => new Date(log.timestamp) <= endDate);
}
// 分页
const paginatedLogs = filteredLogs.slice(offset, offset + parseInt(limit));
res.json(responseFormat.success({
logs: paginatedLogs,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: filteredLogs.length,
pages: Math.ceil(filteredLogs.length / parseInt(limit))
}
}, '获取系统日志成功'));
} catch (error) {
console.error('获取系统日志错误:', error);
res.status(500).json(responseFormat.error('获取系统日志失败'));
}
};
// 获取系统配置(模拟)
const getSystemConfig = async (req, res) => {
try {
const config = {
site_name: '保险端口管理系统',
version: '1.0.0',
environment: process.env.NODE_ENV || 'development',
max_file_size: '10MB',
allowed_file_types: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'],
session_timeout: 3600,
backup_enabled: true,
backup_schedule: '0 2 * * *', // 每天凌晨2点
email_notifications: true,
sms_notifications: false,
maintenance_mode: false
};
res.json(responseFormat.success(config, '获取系统配置成功'));
} catch (error) {
console.error('获取系统配置错误:', error);
res.status(500).json(responseFormat.error('获取系统配置失败'));
}
};
// 更新系统配置(模拟)
const updateSystemConfig = async (req, res) => {
try {
const { maintenance_mode, email_notifications, sms_notifications } = req.body;
// 模拟更新配置
const updatedConfig = {
maintenance_mode: maintenance_mode !== undefined ? maintenance_mode : false,
email_notifications: email_notifications !== undefined ? email_notifications : true,
sms_notifications: sms_notifications !== undefined ? sms_notifications : false,
updated_at: new Date().toISOString()
};
res.json(responseFormat.success(updatedConfig, '系统配置更新成功'));
} catch (error) {
console.error('更新系统配置错误:', error);
res.status(500).json(responseFormat.error('更新系统配置失败'));
}
};
// 备份数据库(模拟)
const backupDatabase = async (req, res) => {
try {
// 模拟备份过程
const backupInfo = {
id: `backup_${Date.now()}`,
filename: `backup_${new Date().toISOString().replace(/:/g, '-')}.sql`,
size: '2.5MB',
status: 'completed',
created_at: new Date().toISOString(),
download_url: '/api/system/backup/download/backup.sql'
};
res.json(responseFormat.success(backupInfo, '数据库备份成功'));
} catch (error) {
console.error('数据库备份错误:', error);
res.status(500).json(responseFormat.error('数据库备份失败'));
}
};
// 恢复数据库(模拟)
const restoreDatabase = async (req, res) => {
try {
const { backup_id } = req.body;
if (!backup_id) {
return res.status(400).json(responseFormat.error('备份ID不能为空'));
}
// 模拟恢复过程
const restoreInfo = {
backup_id,
status: 'completed',
restored_at: new Date().toISOString(),
message: '数据库恢复成功'
};
res.json(responseFormat.success(restoreInfo, '数据库恢复成功'));
} catch (error) {
console.error('数据库恢复错误:', error);
res.status(500).json(responseFormat.error('数据库恢复失败'));
}
};
module.exports = {
getSystemStats,
getSystemLogs,
getSystemConfig,
updateSystemConfig,
backupDatabase,
restoreDatabase
};

View File

@@ -1,183 +0,0 @@
const { sequelize } = require('./config/database');
const bcrypt = require('bcrypt');
async function createAdminUser() {
try {
// 连接数据库
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 检查是否有users表
const [tables] = await sequelize.query(
"SHOW TABLES LIKE 'users'"
);
if (tables.length === 0) {
console.log('⚠️ users表不存在开始创建必要的表结构');
// 创建roles表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS roles (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '角色ID',
name VARCHAR(50) NOT NULL UNIQUE COMMENT '角色名称',
description VARCHAR(255) NULL COMMENT '角色描述',
permissions JSON NOT NULL 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 '更新时间',
INDEX idx_roles_name (name),
INDEX idx_roles_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表';
`);
console.log('✅ roles表创建完成');
// 创建users表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '密码',
real_name VARCHAR(50) NOT NULL COMMENT '真实姓名',
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
phone VARCHAR(20) NOT NULL COMMENT '手机号',
role_id INT NOT NULL COMMENT '角色ID',
status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active' COMMENT '状态',
last_login TIMESTAMP NULL COMMENT '最后登录时间',
avatar VARCHAR(255) NULL COMMENT '头像URL',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_users_username (username),
INDEX idx_users_email (email),
INDEX idx_users_phone (phone),
INDEX idx_users_role_id (role_id),
INDEX idx_users_status (status),
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
`);
console.log('✅ users表创建完成');
// 创建system_configs表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS system_configs (
id INT AUTO_INCREMENT PRIMARY KEY,
config_key VARCHAR(50) NOT NULL UNIQUE,
config_value TEXT NOT NULL,
description VARCHAR(255) NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_config_key (config_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
console.log('✅ system_configs表创建完成');
} else {
console.log('✅ users表已存在');
}
// 检查admin角色是否存在
const [adminRole] = await sequelize.query(
'SELECT id FROM roles WHERE name = ?',
{ replacements: ['admin'] }
);
let roleId;
if (adminRole.length === 0) {
// 创建admin角色
await sequelize.query(
'INSERT INTO roles (name, description, permissions, status) VALUES (?, ?, ?, ?)',
{
replacements: [
'admin',
'系统管理员',
JSON.stringify(['*']),
'active'
]
}
);
console.log('✅ admin角色创建完成');
// 获取刚创建的角色ID
const [newRole] = await sequelize.query(
'SELECT id FROM roles WHERE name = ?',
{ replacements: ['admin'] }
);
roleId = newRole[0].id;
} else {
roleId = adminRole[0].id;
console.log('✅ admin角色已存在');
}
// 检查admin用户是否存在
const [adminUser] = await sequelize.query(
'SELECT id FROM users WHERE username = ?',
{ replacements: ['admin'] }
);
if (adminUser.length === 0) {
// 密码为123456使用bcrypt进行哈希处理
const plainPassword = '123456';
const hashedPassword = await bcrypt.hash(plainPassword, 12);
// 创建admin用户
await sequelize.query(
`INSERT INTO users (username, password, real_name, email, phone, role_id, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
{
replacements: [
'admin',
hashedPassword,
'系统管理员',
'admin@insurance.com',
'13800138000',
roleId,
'active'
]
}
);
console.log('✅ admin用户创建完成用户名: admin密码: 123456');
// 插入默认系统配置
await sequelize.query(
`INSERT IGNORE INTO system_configs (config_key, config_value, description) VALUES
('system_name', ?, '系统名称'),
('company_name', ?, '公司名称'),
('contact_email', ?, '联系邮箱'),
('contact_phone', ?, '联系电话'),
('max_file_size', ?, '最大文件上传大小(字节)'),
('allowed_file_types', ?, '允许上传的文件类型')`,
{
replacements: [
JSON.stringify('保险端口系统'),
JSON.stringify('XX保险公司'),
JSON.stringify('support@insurance.com'),
JSON.stringify('400-123-4567'),
'10485760',
JSON.stringify(['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'])
]
}
);
console.log('✅ 默认系统配置已插入');
} else {
console.log('⚠️ admin用户已存在更新密码为: 123456');
// 更新admin用户的密码
const plainPassword = '123456';
const hashedPassword = await bcrypt.hash(plainPassword, 12);
await sequelize.query(
'UPDATE users SET password = ? WHERE username = ?',
{ replacements: [hashedPassword, 'admin'] }
);
console.log('✅ admin用户密码已更新为: 123456');
}
} catch (error) {
console.error('❌ 操作失败:', error.message);
console.error(error.stack);
} finally {
// 关闭数据库连接
await sequelize.close();
console.log('🔒 数据库连接已关闭');
}
}
// 执行脚本
createAdminUser();

View File

@@ -1,112 +0,0 @@
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

@@ -1,54 +0,0 @@
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

@@ -1,73 +0,0 @@
const axios = require('axios');
const BASE_URL = 'http://localhost:3000/api';
const ADMIN_TOKEN = '5659725423f665a8bf5053b37e624ea86387f9113ae77ac75fc102012a349180';
// 创建axios实例
const api = axios.create({
baseURL: BASE_URL,
headers: {
'Authorization': `Bearer ${ADMIN_TOKEN}`,
'Content-Type': 'application/json'
}
});
async function debugAPIResponse() {
console.log('调试API响应格式...\n');
try {
console.log('测试获取所有角色及其权限...');
const response = await api.get('/role-permissions/roles');
console.log('完整响应:', JSON.stringify(response.data, null, 2));
console.log('数据路径检查:');
console.log('- response.data:', typeof response.data);
console.log('- response.data.data:', typeof response.data.data);
// 尝试解析data字段
let parsedData = response.data.data;
if (typeof parsedData === 'string') {
try {
parsedData = JSON.parse(parsedData);
console.log('- 解析后的data:', typeof parsedData);
console.log('- 解析后的data.roles:', typeof parsedData?.roles);
console.log('- 解析后的data.roles 是数组吗:', Array.isArray(parsedData?.roles));
if (parsedData?.roles) {
console.log('- 角色数量:', parsedData.roles.length);
}
} catch (e) {
console.log('- 无法解析data字段:', e.message);
}
} else {
console.log('- response.data.data.roles:', typeof response.data.data?.roles);
console.log('- response.data.data.roles 是数组吗:', Array.isArray(response.data.data?.roles));
if (response.data.data?.roles) {
console.log('- 角色数量:', response.data.data.roles.length);
}
}
} catch (error) {
console.log('错误:', {
code: error.response?.status,
status: error.response?.data?.status,
data: error.response?.data?.data,
message: error.response?.data?.message,
});
}
try {
console.log('\n测试获取特定角色的详细权限...');
const response = await api.get('/role-permissions/roles/1/permissions');
console.log('完整响应:', JSON.stringify(response.data, null, 2));
} catch (error) {
console.log('错误:', error.response?.data || error.message);
}
try {
console.log('\n测试权限统计...');
const response = await api.get('/role-permissions/stats');
console.log('完整响应:', JSON.stringify(response.data, null, 2));
} catch (error) {
console.log('错误:', error.response?.data || error.message);
}
}
debugAPIResponse().catch(console.error);

View File

@@ -1,77 +0,0 @@
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();

132
insurance_backend/deploy.sh Normal file
View File

@@ -0,0 +1,132 @@
#!/bin/bash
# 保险系统部署脚本
# 用于部署前端和后端到服务器
echo "🚀 开始部署保险系统..."
# 设置变量
PROJECT_ROOT="/var/www/insurance-system"
FRONTEND_DIR="/var/www/insurance-admin-system"
BACKEND_DIR="/var/www/insurance-backend"
NGINX_CONFIG="/etc/nginx/sites-available/ad.ningmuyun.com"
# 1. 创建项目目录
echo "📁 创建项目目录..."
sudo mkdir -p $PROJECT_ROOT
sudo mkdir -p $FRONTEND_DIR
sudo mkdir -p $BACKEND_DIR
# 2. 部署前端
echo "🎨 部署前端..."
cd $FRONTEND_DIR
# 构建前端(如果还没有构建)
if [ ! -d "dist" ]; then
echo "📦 构建前端项目..."
npm install
npm run build
fi
# 设置权限
sudo chown -R www-data:www-data $FRONTEND_DIR/dist
sudo chmod -R 755 $FRONTEND_DIR/dist
# 3. 部署后端
echo "⚙️ 部署后端..."
cd $BACKEND_DIR
# 安装依赖
npm install --production
# 创建环境变量文件
if [ ! -f ".env" ]; then
echo "📝 创建环境变量文件..."
cat > .env << EOF
NODE_ENV=production
PORT=3000
FRONTEND_URL=https://ad.ningmuyun.com
# 数据库配置
DB_DIALECT=mysql
DB_HOST=129.211.213.226
DB_PORT=9527
DB_DATABASE=insurance_data
DB_USER=root
DB_PASSWORD=aiotAiot123!
# JWT配置
JWT_SECRET=insurance_super_secret_jwt_key_2024_very_long_and_secure_production
JWT_EXPIRES_IN=24h
# 文件上传配置
UPLOAD_PATH=uploads
MAX_FILE_SIZE=10485760
# 日志配置
LOG_LEVEL=info
EOF
fi
# 4. 配置nginx
echo "🌐 配置nginx..."
sudo cp $BACKEND_DIR/nginx-ad.ningmuyun.com.conf $NGINX_CONFIG
# 启用站点
sudo ln -sf $NGINX_CONFIG /etc/nginx/sites-enabled/
# 测试nginx配置
sudo nginx -t
if [ $? -eq 0 ]; then
echo "✅ nginx配置测试通过"
sudo systemctl reload nginx
else
echo "❌ nginx配置测试失败"
exit 1
fi
# 5. 配置PM2进程管理
echo "🔄 配置PM2..."
npm install -g pm2
# 创建PM2配置文件
cat > ecosystem.config.js << EOF
module.exports = {
apps: [{
name: 'insurance-backend',
script: 'src/app.js',
cwd: '$BACKEND_DIR',
instances: 1,
exec_mode: 'fork',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true,
max_memory_restart: '1G',
restart_delay: 4000,
max_restarts: 10,
min_uptime: '10s'
}]
};
EOF
# 启动应用
pm2 start ecosystem.config.js
pm2 save
pm2 startup
echo "✅ 部署完成!"
echo "📋 部署信息:"
echo " 前端地址: https://ad.ningmuyun.com/insurance/"
echo " 后端API: https://ad.ningmuyun.com/insurance/api/"
echo " 后端端口: 3000"
echo " 进程管理: PM2"
echo "🔍 检查服务状态:"
pm2 status
sudo systemctl status nginx

View File

@@ -0,0 +1,28 @@
module.exports = {
apps: [{
name: 'insurance-backend',
script: 'src/app.js',
instances: 1,
exec_mode: 'fork',
env: {
NODE_ENV: 'production',
PORT: 3000,
FRONTEND_URL: 'https://ad.ningmuyun.com'
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
FRONTEND_URL: 'https://ad.ningmuyun.com'
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true,
max_memory_restart: '1G',
restart_delay: 4000,
max_restarts: 10,
min_uptime: '10s',
watch: false,
ignore_watch: ['node_modules', 'logs']
}]
};

View File

@@ -0,0 +1,153 @@
#!/bin/bash
# nginx配置检查和修复脚本
echo "🌐 检查和修复nginx配置..."
# 检查nginx是否安装
if ! command -v nginx &> /dev/null; then
echo "❌ nginx未安装正在安装..."
sudo apt update
sudo apt install nginx -y
fi
# 检查nginx配置文件
NGINX_CONFIG="/etc/nginx/sites-available/ad.ningmuyun.com"
NGINX_ENABLED="/etc/nginx/sites-enabled/ad.ningmuyun.com"
echo "📁 检查nginx配置文件..."
if [ ! -f "$NGINX_CONFIG" ]; then
echo "❌ nginx配置文件不存在正在创建..."
# 创建nginx配置
sudo tee "$NGINX_CONFIG" > /dev/null << 'EOF'
server {
listen 443 ssl http2;
server_name ad.ningmuyun.com;
# SSL证书配置需要替换为实际的证书路径
ssl_certificate /etc/ssl/certs/ad.ningmuyun.com.crt;
ssl_certificate_key /etc/ssl/private/ad.ningmuyun.com.key;
# 如果SSL证书不存在使用自签名证书仅用于测试
# ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
# ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
# SSL配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# 前端静态文件
location /insurance/ {
alias /var/www/insurance-admin-system/dist/;
try_files $uri $uri/ /insurance/index.html;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# 后端API代理
location /insurance/api/ {
proxy_pass http://127.0.0.1:3000/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# CORS headers
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
add_header Access-Control-Allow-Credentials true;
# 处理预检请求
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
add_header Access-Control-Allow-Credentials true;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
# 健康检查
location /health {
proxy_pass http://127.0.0.1:3000/health;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP重定向到HTTPS
server {
listen 80;
server_name ad.ningmuyun.com;
return 301 https://$server_name$request_uri;
}
EOF
echo "✅ nginx配置文件已创建"
else
echo "✅ nginx配置文件已存在"
fi
# 启用站点
if [ ! -L "$NGINX_ENABLED" ]; then
echo "🔗 启用nginx站点..."
sudo ln -sf "$NGINX_CONFIG" "$NGINX_ENABLED"
echo "✅ nginx站点已启用"
else
echo "✅ nginx站点已启用"
fi
# 检查前端目录
if [ ! -d "/var/www/insurance-admin-system/dist" ]; then
echo "⚠️ 前端目录不存在,创建目录..."
sudo mkdir -p /var/www/insurance-admin-system/dist
sudo chown -R www-data:www-data /var/www/insurance-admin-system
echo "✅ 前端目录已创建"
fi
# 测试nginx配置
echo "🧪 测试nginx配置..."
if sudo nginx -t; then
echo "✅ nginx配置测试通过"
# 重新加载nginx
echo "🔄 重新加载nginx..."
sudo systemctl reload nginx
# 检查nginx状态
echo "📊 nginx状态:"
sudo systemctl status nginx --no-pager
else
echo "❌ nginx配置测试失败"
exit 1
fi
echo ""
echo "✅ nginx配置检查和修复完成"
echo "📋 配置信息:"
echo " 配置文件: $NGINX_CONFIG"
echo " 启用链接: $NGINX_ENABLED"
echo " 前端目录: /var/www/insurance-admin-system/dist"
echo " API代理: /insurance/api/ → http://127.0.0.1:3000/api/"

View File

@@ -1,4 +1,4 @@
const bcrypt = require('bcrypt');
const bcrypt = require('bcryptjs');
async function generateHash() {
const password = 'admin123';

View File

@@ -0,0 +1,212 @@
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const Role = require('../models/Role');
const responseFormat = require('../utils/response');
// JWT认证中间件
const jwtAuth = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
console.log('Authorization header:', authHeader);
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json(responseFormat.error('未提供有效的认证token'));
}
const token = authHeader.substring(7);
console.log('提取的token:', token);
console.log('token类型:', typeof token);
console.log('token长度:', token.length);
// 首先尝试固定token验证
const user = await User.findOne({
where: {
fixed_token: token,
status: 'active'
},
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name', 'permissions']
}]
});
if (user) {
// 固定token验证成功
req.user = {
id: user.id,
userId: user.id,
username: user.username,
role_id: user.role_id,
role: user.role,
permissions: user.role ? user.role.permissions : [],
type: 'fixed_token'
};
return next();
}
// 如果固定token验证失败尝试JWT验证
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (decoded.type !== 'access') {
return res.status(401).json(responseFormat.error('Token类型错误'));
}
// 验证用户是否存在且状态正常
const jwtUser = await User.findOne({
where: {
id: decoded.id,
status: 'active'
},
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name', 'permissions']
}]
});
if (!jwtUser) {
return res.status(401).json(responseFormat.error('用户不存在或已被禁用'));
}
req.user = {
id: decoded.id,
userId: decoded.id,
username: decoded.username,
role_id: decoded.role_id,
role: jwtUser.role,
permissions: decoded.permissions || (jwtUser.role ? jwtUser.role.permissions : []),
type: 'jwt'
};
return next();
} catch (jwtError) {
if (jwtError.name === 'TokenExpiredError') {
return res.status(401).json(responseFormat.error('Token已过期', 'TOKEN_EXPIRED'));
} else if (jwtError.name === 'JsonWebTokenError') {
return res.status(401).json(responseFormat.error('Token无效'));
}
throw jwtError;
}
} catch (error) {
console.error('Token验证错误:', error);
return res.status(500).json(responseFormat.error('服务器内部错误'));
}
};
// 权限检查中间件
const checkPermission = (resource, action) => {
return async (req, res, next) => {
try {
console.log(`权限检查 - 资源: ${resource}, 操作: ${action}, 用户:`, req.user);
const user = req.user;
if (!user || !user.role_id) {
console.log('权限检查失败 - 用户角色信息缺失');
return res.status(403).json(responseFormat.error('用户角色信息缺失'));
}
let permissions = [];
// 优先使用JWT中的权限信息
if (user.permissions) {
if (typeof user.permissions === 'string') {
try {
permissions = JSON.parse(user.permissions);
} catch (e) {
console.log('JWT权限解析失败:', e.message);
permissions = [];
}
} else if (Array.isArray(user.permissions)) {
permissions = user.permissions;
}
}
const requiredPermission = `${resource}:${action}`;
// 首先检查JWT中的权限
let hasPermission = permissions.includes(requiredPermission) ||
permissions.includes('*:*') ||
permissions.includes('*');
// 如果JWT中没有权限信息或者JWT权限不足从数据库查询最新权限
if (permissions.length === 0 || !hasPermission) {
console.log('JWT权限不足或为空从数据库获取最新权限...');
const { Role, RolePermission, Permission } = require('../models');
const userRole = await Role.findByPk(user.role_id);
if (!userRole) {
console.log('权限检查失败 - 用户角色不存在, role_id:', user.role_id);
return res.status(403).json(responseFormat.error('用户角色不存在'));
}
// 从RolePermission表获取权限
const rolePermissions = await RolePermission.findAll({
where: {
role_id: user.role_id,
granted: true
},
include: [{
model: Permission,
as: 'permission',
attributes: ['code']
}]
});
permissions = rolePermissions.map(rp => rp.permission.code);
console.log('从RolePermission表获取的最新权限:', permissions);
// 重新检查权限
hasPermission = permissions.includes(requiredPermission) ||
permissions.includes('*:*') ||
permissions.includes('*');
}
console.log('权限检查 - 用户权限:', permissions, '需要权限:', requiredPermission, '是否有权限:', hasPermission);
// 检查权限或超级管理员权限
if (!hasPermission) {
console.log('权限检查失败 - 权限不足');
return res.status(403).json(responseFormat.error('权限不足'));
}
console.log('权限检查通过');
next();
} catch (error) {
return res.status(500).json(responseFormat.error('权限验证失败'));
}
};
};
// 可选认证中间件(不强制要求认证)
const optionalAuth = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
} catch (error) {
// 令牌无效,但不阻止请求
console.warn('可选认证令牌无效:', error.message);
}
}
next();
};
// 别名导出以匹配路由中的使用
const authenticateToken = jwtAuth;
const requirePermission = (permission) => {
const [resource, action] = permission.split(':');
return checkPermission(resource, action);
};
module.exports = {
jwtAuth,
checkPermission,
optionalAuth,
authenticateToken,
requirePermission
};

View File

@@ -0,0 +1,119 @@
const User = require('../models/User');
const Role = require('../models/Role');
/**
* 固定Token认证中间件
* 支持JWT token和固定token两种认证方式
*/
const fixedTokenAuth = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
status: 'error',
message: '未提供认证token'
});
}
// 检查是否为Bearer token格式
if (authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7);
// 首先尝试固定token验证
const user = await User.findOne({
where: {
fixed_token: token,
status: 'active'
},
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name', 'permissions']
}]
});
if (user) {
// 固定token验证成功
req.user = {
id: user.id,
username: user.username,
role_id: user.role_id,
role: user.role,
permissions: user.role ? user.role.permissions : []
};
return next();
}
// 如果固定token验证失败尝试JWT验证
const jwt = require('jsonwebtoken');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (decoded.type !== 'access') {
return res.status(401).json({
status: 'error',
message: 'Token类型错误'
});
}
// 验证用户是否存在且状态正常
const jwtUser = await User.findOne({
where: {
id: decoded.userId,
status: 'active'
},
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name', 'permissions']
}]
});
if (!jwtUser) {
return res.status(401).json({
status: 'error',
message: '用户不存在或已被禁用'
});
}
req.user = {
id: jwtUser.id,
username: jwtUser.username,
role_id: jwtUser.role_id,
role: jwtUser.role,
permissions: decoded.permissions || (jwtUser.role ? jwtUser.role.permissions : [])
};
return next();
} catch (jwtError) {
if (jwtError.name === 'TokenExpiredError') {
return res.status(401).json({
status: 'error',
code: 'TOKEN_EXPIRED',
message: 'Token已过期'
});
} else if (jwtError.name === 'JsonWebTokenError') {
return res.status(401).json({
status: 'error',
message: 'Token无效'
});
}
throw jwtError;
}
} else {
return res.status(401).json({
status: 'error',
message: 'Token格式错误请使用Bearer格式'
});
}
} catch (error) {
console.error('Token验证错误:', error);
return res.status(500).json({
status: 'error',
message: '服务器内部错误'
});
}
};
module.exports = fixedTokenAuth;

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,177 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('supervision_tasks', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '监管任务ID'
},
applicationId: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '申请单号',
field: 'application_id'
},
policyId: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '保单编号',
field: 'policy_id'
},
productName: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '产品名称',
field: 'product_name'
},
insurancePeriod: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '保险期间',
field: 'insurance_period'
},
customerName: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '客户姓名',
field: 'customer_name'
},
documentType: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '证件类型',
field: 'document_type'
},
documentNumber: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '证件号码',
field: 'document_number'
},
applicableAmount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '适用金额',
field: 'applicable_amount'
},
supervisionDataCount: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: '监管生成数量',
field: 'supervision_data_count'
},
status: {
type: DataTypes.ENUM('pending', 'processing', 'completed', 'rejected'),
defaultValue: 'pending',
comment: '状态: pending-待处理, processing-处理中, completed-已完成, rejected-已拒绝'
},
taskType: {
type: DataTypes.ENUM('new_application', 'task_guidance', 'batch_operation'),
allowNull: false,
comment: '任务类型: new_application-新增监管任务, task_guidance-任务导入, batch_operation-批量新增',
field: 'task_type'
},
assignedTo: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '分配给用户ID',
field: 'assigned_to',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
priority: {
type: DataTypes.ENUM('low', 'medium', 'high', 'urgent'),
defaultValue: 'medium',
comment: '优先级'
},
dueDate: {
type: DataTypes.DATE,
allowNull: true,
comment: '截止日期',
field: 'due_date'
},
completedAt: {
type: DataTypes.DATE,
allowNull: true,
comment: '完成时间',
field: 'completed_at'
},
remarks: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注'
},
createdBy: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '创建人ID',
field: 'created_by',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'RESTRICT'
},
updatedBy: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID',
field: 'updated_by',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
field: 'created_at'
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
field: 'updated_at'
}
}, {
comment: '监管任务表',
charset: 'utf8mb4',
collate: 'utf8mb4_unicode_ci'
});
// 添加索引
await queryInterface.addIndex('supervision_tasks', ['application_id']);
await queryInterface.addIndex('supervision_tasks', ['policy_id']);
await queryInterface.addIndex('supervision_tasks', ['customer_name']);
await queryInterface.addIndex('supervision_tasks', ['status']);
await queryInterface.addIndex('supervision_tasks', ['task_type']);
await queryInterface.addIndex('supervision_tasks', ['assigned_to']);
await queryInterface.addIndex('supervision_tasks', ['created_by']);
await queryInterface.addIndex('supervision_tasks', ['created_at']);
// 添加唯一索引
await queryInterface.addIndex('supervision_tasks', ['application_id'], {
unique: true,
name: 'unique_application_id'
});
await queryInterface.addIndex('supervision_tasks', ['policy_id'], {
unique: true,
name: 'unique_policy_id'
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('supervision_tasks');
}
};

View File

@@ -0,0 +1,16 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'fixed_token', {
type: Sequelize.STRING(255),
allowNull: true,
unique: true,
comment: '用户固定token用于API访问验证'
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('users', 'fixed_token');
}
};

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,181 @@
/**
* 创建监管任务表的迁移文件
*/
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('supervisory_tasks', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
comment: '主键ID'
},
applicationNumber: {
type: Sequelize.STRING(50),
allowNull: false,
unique: true,
field: 'application_number',
comment: '申请单号'
},
policyNumber: {
type: Sequelize.STRING(50),
allowNull: false,
field: 'policy_number',
comment: '保单编号'
},
productName: {
type: Sequelize.STRING(100),
allowNull: false,
field: 'product_name',
comment: '产品名称'
},
insurancePeriod: {
type: Sequelize.STRING(50),
allowNull: false,
field: 'insurance_period',
comment: '保险周期'
},
customerName: {
type: Sequelize.STRING(50),
allowNull: false,
field: 'customer_name',
comment: '客户姓名'
},
idType: {
type: Sequelize.ENUM('身份证', '护照', '军官证', '士兵证', '港澳台居民居住证', '其他'),
allowNull: false,
defaultValue: '身份证',
field: 'id_type',
comment: '证件类型'
},
idNumber: {
type: Sequelize.STRING(30),
allowNull: false,
field: 'id_number',
comment: '证件号码'
},
applicableSupplies: {
type: Sequelize.TEXT,
allowNull: true,
field: 'applicable_supplies',
comment: '适用生资JSON格式存储'
},
supervisorySuppliesQuantity: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 0,
field: 'supervisory_supplies_quantity',
comment: '监管生资数量'
},
taskStatus: {
type: Sequelize.ENUM('待处理', '处理中', '已完成', '已取消'),
allowNull: false,
defaultValue: '待处理',
field: 'task_status',
comment: '任务状态'
},
priority: {
type: Sequelize.ENUM('低', '中', '高', '紧急'),
allowNull: false,
defaultValue: '中',
comment: '任务优先级'
},
assignedTo: {
type: Sequelize.INTEGER,
allowNull: true,
field: 'assigned_to',
comment: '分配给用户ID',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
dueDate: {
type: Sequelize.DATE,
allowNull: true,
field: 'due_date',
comment: '截止日期'
},
completedAt: {
type: Sequelize.DATE,
allowNull: true,
field: 'completed_at',
comment: '完成时间'
},
notes: {
type: Sequelize.TEXT,
allowNull: true,
comment: '备注信息'
},
createdBy: {
type: Sequelize.INTEGER,
allowNull: true,
field: 'created_by',
comment: '创建人ID',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
updatedBy: {
type: Sequelize.INTEGER,
allowNull: true,
field: 'updated_by',
comment: '更新人ID',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
field: 'created_at',
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
field: 'updated_at',
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
}
}, {
comment: '监管任务表'
});
// 创建索引
await queryInterface.addIndex('supervisory_tasks', ['application_number'], {
name: 'idx_application_number'
});
await queryInterface.addIndex('supervisory_tasks', ['policy_number'], {
name: 'idx_policy_number'
});
await queryInterface.addIndex('supervisory_tasks', ['customer_name'], {
name: 'idx_customer_name'
});
await queryInterface.addIndex('supervisory_tasks', ['task_status'], {
name: 'idx_task_status'
});
await queryInterface.addIndex('supervisory_tasks', ['created_at'], {
name: 'idx_created_at'
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('supervisory_tasks');
}
};

View File

@@ -0,0 +1,43 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('policies', 'policyholder_name', {
type: Sequelize.STRING,
allowNull: true,
comment: '投保人姓名'
});
await queryInterface.addColumn('policies', 'insured_name', {
type: Sequelize.STRING,
allowNull: true,
comment: '被保险人姓名'
});
await queryInterface.addColumn('policies', 'phone', {
type: Sequelize.STRING,
allowNull: true,
comment: '联系电话'
});
await queryInterface.addColumn('policies', 'email', {
type: Sequelize.STRING,
allowNull: true,
comment: '邮箱地址'
});
await queryInterface.addColumn('policies', 'address', {
type: Sequelize.STRING,
allowNull: true,
comment: '联系地址'
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('policies', 'policyholder_name');
await queryInterface.removeColumn('policies', 'insured_name');
await queryInterface.removeColumn('policies', 'phone');
await queryInterface.removeColumn('policies', 'email');
await queryInterface.removeColumn('policies', 'address');
}
};

View File

@@ -0,0 +1,190 @@
/**
* 创建待安装任务表的迁移文件
*/
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('installation_tasks', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
comment: '主键ID'
},
applicationNumber: {
type: Sequelize.STRING(50),
allowNull: false,
field: 'application_number',
comment: '申请单号'
},
policyNumber: {
type: Sequelize.STRING(50),
allowNull: false,
field: 'policy_number',
comment: '保单编号'
},
productName: {
type: Sequelize.STRING(100),
allowNull: false,
field: 'product_name',
comment: '产品名称'
},
customerName: {
type: Sequelize.STRING(50),
allowNull: false,
field: 'customer_name',
comment: '客户姓名'
},
idType: {
type: Sequelize.ENUM('身份证', '护照', '军官证', '士兵证', '港澳台居民居住证', '其他'),
allowNull: false,
defaultValue: '身份证',
field: 'id_type',
comment: '证件类型'
},
idNumber: {
type: Sequelize.STRING(30),
allowNull: false,
field: 'id_number',
comment: '证件号码'
},
livestockSupplyType: {
type: Sequelize.STRING(100),
allowNull: false,
field: 'livestock_supply_type',
comment: '养殖生资种类'
},
pendingDevices: {
type: Sequelize.TEXT,
allowNull: true,
field: 'pending_devices',
comment: '待安装设备JSON格式存储'
},
installationStatus: {
type: Sequelize.ENUM('待安装', '安装中', '已安装', '安装失败', '已取消'),
allowNull: false,
defaultValue: '待安装',
field: 'installation_status',
comment: '安装状态'
},
taskGeneratedTime: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
field: 'task_generated_time',
comment: '生成安装任务时间'
},
installationCompletedTime: {
type: Sequelize.DATE,
allowNull: true,
field: 'installation_completed_time',
comment: '安装完成生效时间'
},
priority: {
type: Sequelize.ENUM('低', '中', '高', '紧急'),
allowNull: false,
defaultValue: '中',
comment: '安装优先级'
},
assignedTechnician: {
type: Sequelize.INTEGER,
allowNull: true,
field: 'assigned_technician',
comment: '分配的技术员ID',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
installationAddress: {
type: Sequelize.TEXT,
allowNull: true,
field: 'installation_address',
comment: '安装地址'
},
contactPhone: {
type: Sequelize.STRING(20),
allowNull: true,
field: 'contact_phone',
comment: '联系电话'
},
remarks: {
type: Sequelize.TEXT,
allowNull: true,
comment: '备注信息'
},
createdBy: {
type: Sequelize.INTEGER,
allowNull: true,
field: 'created_by',
comment: '创建人ID',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
updatedBy: {
type: Sequelize.INTEGER,
allowNull: true,
field: 'updated_by',
comment: '更新人ID',
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
field: 'created_at',
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
field: 'updated_at',
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
}
}, {
comment: '待安装任务表'
});
// 创建索引
await queryInterface.addIndex('installation_tasks', ['application_number'], {
name: 'idx_application_number'
});
await queryInterface.addIndex('installation_tasks', ['policy_number'], {
name: 'idx_policy_number'
});
await queryInterface.addIndex('installation_tasks', ['customer_name'], {
name: 'idx_customer_name'
});
await queryInterface.addIndex('installation_tasks', ['installation_status'], {
name: 'idx_installation_status'
});
await queryInterface.addIndex('installation_tasks', ['task_generated_time'], {
name: 'idx_task_generated_time'
});
await queryInterface.addIndex('installation_tasks', ['installation_completed_time'], {
name: 'idx_installation_completed_time'
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('installation_tasks');
}
};

View File

@@ -0,0 +1,143 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Claim = sequelize.define('Claim', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '理赔ID'
},
claim_no: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '理赔编号',
validate: {
notEmpty: {
msg: '理赔编号不能为空'
}
}
},
policy_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '关联的保单ID',
references: {
model: 'policies',
key: 'id'
}
},
customer_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '客户ID',
references: {
model: 'users',
key: 'id'
}
},
claim_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '理赔金额',
validate: {
min: {
args: [0],
msg: '理赔金额不能小于0'
}
}
},
claim_date: {
type: DataTypes.DATE,
allowNull: false,
comment: '理赔发生日期'
},
incident_description: {
type: DataTypes.TEXT,
allowNull: false,
comment: '事故描述',
validate: {
notEmpty: {
msg: '事故描述不能为空'
}
}
},
claim_status: {
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'processing', 'paid'),
allowNull: false,
defaultValue: 'pending',
comment: '理赔状态pending-待审核approved-已批准rejected-已拒绝processing-处理中paid-已支付'
},
review_notes: {
type: DataTypes.TEXT,
allowNull: true,
comment: '审核备注'
},
reviewer_id: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '审核人ID',
references: {
model: 'users',
key: 'id'
}
},
review_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '审核日期'
},
payment_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '支付日期'
},
supporting_documents: {
type: DataTypes.JSON,
allowNull: true,
comment: '支持文件JSON数组包含文件URL和描述'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID'
}
}, {
tableName: 'claims',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
name: 'idx_claim_no',
fields: ['claim_no'],
unique: true
},
{
name: 'idx_claim_policy',
fields: ['policy_id']
},
{
name: 'idx_claim_customer',
fields: ['customer_id']
},
{
name: 'idx_claim_status',
fields: ['claim_status']
},
{
name: 'idx_claim_date',
fields: ['claim_date']
}
],
comment: '理赔表'
});
module.exports = Claim;

View File

@@ -0,0 +1,87 @@
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,218 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const LivestockPolicy = sequelize.define('LivestockPolicy', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '生资保单ID'
},
policy_no: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '保单编号',
validate: {
notEmpty: {
msg: '保单编号不能为空'
}
}
},
livestock_type_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '牲畜类型ID',
references: {
model: 'livestock_types',
key: 'id'
}
},
policyholder_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '投保人姓名',
validate: {
notEmpty: {
msg: '投保人姓名不能为空'
}
}
},
policyholder_id_card: {
type: DataTypes.STRING(18),
allowNull: false,
comment: '投保人身份证号',
validate: {
notEmpty: {
msg: '投保人身份证号不能为空'
}
}
},
policyholder_phone: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '投保人手机号',
validate: {
notEmpty: {
msg: '投保人手机号不能为空'
}
}
},
policyholder_address: {
type: DataTypes.STRING(500),
allowNull: false,
comment: '投保人地址',
validate: {
notEmpty: {
msg: '投保人地址不能为空'
}
}
},
farm_name: {
type: DataTypes.STRING(200),
allowNull: true,
comment: '养殖场名称'
},
farm_address: {
type: DataTypes.STRING(500),
allowNull: true,
comment: '养殖场地址'
},
farm_license: {
type: DataTypes.STRING(100),
allowNull: true,
comment: '养殖场许可证号'
},
livestock_count: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '投保牲畜数量',
validate: {
min: {
args: [1],
msg: '投保牲畜数量不能小于1'
}
}
},
unit_value: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
comment: '单头价值',
validate: {
min: {
args: [0],
msg: '单头价值不能小于0'
}
}
},
total_value: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '总保额',
validate: {
min: {
args: [0],
msg: '总保额不能小于0'
}
}
},
premium_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
comment: '保费金额',
validate: {
min: {
args: [0],
msg: '保费金额不能小于0'
}
}
},
start_date: {
type: DataTypes.DATE,
allowNull: false,
comment: '保险开始日期'
},
end_date: {
type: DataTypes.DATE,
allowNull: false,
comment: '保险结束日期'
},
policy_status: {
type: DataTypes.ENUM('active', 'expired', 'cancelled', 'suspended'),
allowNull: false,
defaultValue: 'active',
comment: '保单状态active-有效expired-已过期cancelled-已取消suspended-已暂停'
},
payment_status: {
type: DataTypes.ENUM('paid', 'unpaid', 'partial'),
allowNull: false,
defaultValue: 'unpaid',
comment: '支付状态paid-已支付unpaid-未支付partial-部分支付'
},
payment_date: {
type: DataTypes.DATE,
allowNull: true,
comment: '支付日期'
},
policy_document_url: {
type: DataTypes.STRING(500),
allowNull: true,
comment: '保单文档URL'
},
notes: {
type: DataTypes.TEXT,
allowNull: true,
comment: '备注信息'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID',
references: {
model: 'users',
key: 'id'
}
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID',
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'livestock_policies',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
name: 'idx_livestock_policy_no',
fields: ['policy_no'],
unique: true
},
{
name: 'idx_livestock_policy_policyholder',
fields: ['policyholder_name']
},
{
name: 'idx_livestock_policy_status',
fields: ['policy_status']
},
{
name: 'idx_livestock_policy_payment_status',
fields: ['payment_status']
},
{
name: 'idx_livestock_policy_dates',
fields: ['start_date', 'end_date']
}
],
comment: '生资保单表'
});
module.exports = LivestockPolicy;

View File

@@ -0,0 +1,102 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const LivestockType = sequelize.define('LivestockType', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '牲畜类型ID'
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '牲畜类型名称',
validate: {
notEmpty: {
msg: '牲畜类型名称不能为空'
}
}
},
code: {
type: DataTypes.STRING(20),
allowNull: false,
unique: true,
comment: '牲畜类型代码'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '牲畜类型描述'
},
unit_price_min: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 0.00,
comment: '单头最低价值',
validate: {
min: {
args: [0],
msg: '单头最低价值不能小于0'
}
}
},
unit_price_max: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 50000.00,
comment: '单头最高价值',
validate: {
min: {
args: [0],
msg: '单头最高价值不能小于0'
}
}
},
premium_rate: {
type: DataTypes.DECIMAL(5, 4),
allowNull: false,
defaultValue: 0.0050,
comment: '保险费率',
validate: {
min: {
args: [0],
msg: '保费费率不能小于0'
},
max: {
args: [1],
msg: '保费费率不能大于1'
}
}
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
allowNull: false,
defaultValue: 'active',
comment: '状态'
}
}, {
tableName: 'livestock_types',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
name: 'idx_livestock_type_name',
fields: ['name']
},
{
name: 'idx_livestock_type_code',
fields: ['code']
},
{
name: 'idx_livestock_type_status',
fields: ['status']
}
],
comment: '牲畜类型表'
});
module.exports = LivestockType;

View File

@@ -0,0 +1,85 @@
const { sequelize } = require('../config/database');
const { DataTypes } = require('sequelize');
const Menu = sequelize.define('Menu', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
validate: {
len: [2, 50]
}
},
key: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
len: [2, 50]
}
},
path: {
type: DataTypes.STRING(100),
allowNull: false,
validate: {
len: [1, 100]
}
},
icon: {
type: DataTypes.STRING(50),
allowNull: true
},
parent_id: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'menus',
key: 'id'
},
defaultValue: null
},
component: {
type: DataTypes.STRING(100),
allowNull: true
},
order: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
defaultValue: 'active'
},
show: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'menus',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['key'] },
{ fields: ['parent_id'] },
{ fields: ['status'] },
{ fields: ['order'] }
]
});
// 设置自关联
Menu.hasMany(Menu, {
as: 'children',
foreignKey: 'parent_id'
});
Menu.belongsTo(Menu, {
as: 'parent',
foreignKey: 'parent_id'
});
module.exports = Menu;

View File

@@ -0,0 +1,43 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const MenuPermission = sequelize.define('MenuPermission', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '关联ID'
},
menu_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '菜单ID'
},
permission_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '权限ID'
},
required: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
comment: '是否必需权限'
}
}, {
tableName: 'menu_permissions',
timestamps: true,
underscored: true,
indexes: [
{
fields: ['menu_id', 'permission_id'],
unique: true,
name: 'uk_menu_permission'
},
{ fields: ['menu_id'] },
{ fields: ['permission_id'] }
]
});
module.exports = MenuPermission;

View File

@@ -0,0 +1,271 @@
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

@@ -0,0 +1,94 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Permission = sequelize.define('Permission', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '权限ID'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '权限名称',
validate: {
len: [2, 100]
}
},
code: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true,
comment: '权限代码',
validate: {
len: [2, 100],
is: /^[a-zA-Z0-9_:]+$/ // 只允许字母、数字、下划线和冒号
}
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '权限描述'
},
module: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '所属模块',
validate: {
len: [2, 50]
}
},
type: {
type: DataTypes.ENUM('menu', 'operation'),
allowNull: false,
defaultValue: 'operation',
comment: '权限类型menu-菜单权限operation-操作权限'
},
parent_id: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '父权限ID'
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
allowNull: false,
defaultValue: 'active',
comment: '状态'
},
sort_order: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '排序'
}
}, {
tableName: 'permissions',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['code'], unique: true },
{ fields: ['module'] },
{ fields: ['type'] },
{ fields: ['parent_id'] },
{ fields: ['status'] }
]
});
// 定义自关联关系
Permission.hasMany(Permission, {
as: 'children',
foreignKey: 'parent_id',
onDelete: 'SET NULL',
onUpdate: 'CASCADE'
});
Permission.belongsTo(Permission, {
as: 'parent',
foreignKey: 'parent_id',
onDelete: 'SET NULL',
onUpdate: 'CASCADE'
});
module.exports = Permission;

View File

@@ -1,6 +1,6 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const bcrypt = require('bcrypt');
const bcrypt = require('bcryptjs');
const User = sequelize.define('User', {
id: {

View File

@@ -0,0 +1,71 @@
server {
listen 443 ssl http2;
server_name ad.ningmuyun.com;
# SSL证书配置需要替换为实际的证书路径
ssl_certificate /etc/ssl/certs/ad.ningmuyun.com.crt;
ssl_certificate_key /etc/ssl/private/ad.ningmuyun.com.key;
# SSL配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# 前端静态文件
location /insurance/ {
alias /var/www/insurance-admin-system/dist/;
try_files $uri $uri/ /insurance/index.html;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# 后端API代理
location /insurance/api/ {
proxy_pass http://127.0.0.1:3000/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# CORS headers
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
add_header Access-Control-Allow-Credentials true;
# 处理预检请求
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin https://ad.ningmuyun.com;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
add_header Access-Control-Allow-Credentials true;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
# 健康检查
location /health {
proxy_pass http://127.0.0.1:3000/health;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP重定向到HTTPS
server {
listen 80;
server_name ad.ningmuyun.com;
return 301 https://$server_name$request_uri;
}

View File

@@ -0,0 +1,33 @@
server {
listen 443 ssl;
server_name 49.51.70.206;
# SSL证书配置需要申请SSL证书
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
# 代理到后端API
location /api/ {
proxy_pass http://127.0.0.1:3000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 代理到前端
location /insurance/ {
proxy_pass http://127.0.0.1:3001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP重定向到HTTPS
server {
listen 80;
server_name 49.51.70.206;
return 301 https://$server_name$request_uri;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
{
"name": "insurance_backend",
"version": "1.0.0",
"description": "保险端口后端服务",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js",
"dev": "nodemon src/app.js",
"test": "jest",
"migrate": "npx sequelize-cli db:migrate",
"seed": "npx sequelize-cli db:seed:all",
"migrate:undo": "npx sequelize-cli db:migrate:undo",
"seed:undo": "npx sequelize-cli db:seed:undo:all"
},
"keywords": [
"insurance",
"backend",
"nodejs",
"express"
],
"author": "Insurance Team",
"license": "MIT",
"dependencies": {
"axios": "^1.12.2",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"exceljs": "^4.4.0",
"express": "^4.18.2",
"express-rate-limit": "^8.1.0",
"helmet": "^7.1.0",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"multer": "^1.4.5-lts.1",
"mysql2": "^2.3.3",
"redis": "^4.5.0",
"sanitize-html": "^2.8.1",
"sequelize": "^6.29.0",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"validator": "^13.9.0",
"winston": "^3.8.2"
},
"devDependencies": {
"eslint": "^8.32.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"jest": "^29.5.0",
"nodemon": "^2.0.20",
"puppeteer": "^19.11.1",
"sequelize-cli": "^6.6.0",
"supertest": "^6.3.3"
},
"engines": {
"node": "16.20.2",
"npm": ">=8.0.0"
}
}

View File

@@ -0,0 +1,60 @@
#!/bin/bash
# 重启保险后端服务脚本
echo "🔄 重启保险后端服务..."
# 设置工作目录
cd /var/www/insurance-backend
# 停止现有服务
echo "🛑 停止现有服务..."
pm2 stop insurance-backend 2>/dev/null || true
pkill -f "node.*src/app.js" 2>/dev/null || true
# 等待进程完全停止
sleep 3
# 检查端口是否被释放
if lsof -Pi :3000 -sTCP:LISTEN -t >/dev/null ; then
echo "⚠️ 端口3000仍被占用强制停止..."
sudo fuser -k 3000/tcp 2>/dev/null || true
sleep 2
fi
# 启动服务
echo "🚀 启动服务..."
pm2 start ecosystem.config.js --env production
# 等待服务启动
echo "⏳ 等待服务启动..."
sleep 5
# 检查服务状态
echo "🔍 检查服务状态..."
pm2 status
# 测试API
echo "🧪 测试API..."
for i in {1..3}; do
if curl -s http://127.0.0.1:3000/health > /dev/null; then
echo "✅ API测试成功"
break
else
echo "⏳ 等待API启动... ($i/3)"
sleep 2
fi
done
# 保存PM2配置
pm2 save
echo ""
echo "✅ 服务重启完成!"
echo "📋 服务信息:"
echo " 服务名称: insurance-backend"
echo " 端口: 3000"
echo " 环境: production"
echo ""
echo "🔍 查看日志: pm2 logs insurance-backend"
echo "📊 监控面板: pm2 monit"

View File

@@ -0,0 +1,75 @@
const { sequelize } = require('./config/database.js');
const fs = require('fs');
const path = require('path');
async function runMigration() {
try {
console.log('开始运行数据库迁移...');
// 测试数据库连接
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 获取所有迁移文件
const migrationsPath = path.join(__dirname, 'migrations');
const migrationFiles = fs.readdirSync(migrationsPath)
.filter(file => file.endsWith('.js'))
.sort();
console.log(`找到 ${migrationFiles.length} 个迁移文件`);
// 确保 SequelizeMeta 表存在
await sequelize.query(`
CREATE TABLE IF NOT EXISTS \`SequelizeMeta\` (
\`name\` varchar(255) COLLATE utf8mb3_unicode_ci NOT NULL,
PRIMARY KEY (\`name\`),
UNIQUE KEY \`name\` (\`name\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
`);
// 检查哪些迁移已经运行过
const [executedMigrations] = await sequelize.query(
'SELECT name FROM SequelizeMeta ORDER BY name'
);
const executedNames = executedMigrations.map(row => row.name);
// 运行未执行的迁移
for (const file of migrationFiles) {
if (!executedNames.includes(file)) {
console.log(`正在运行迁移: ${file}`);
try {
const migration = require(path.join(migrationsPath, file));
await migration.up(sequelize.getQueryInterface(), sequelize.constructor);
// 记录迁移已执行
await sequelize.query(
'INSERT INTO SequelizeMeta (name) VALUES (?)',
{ replacements: [file] }
);
console.log(`✅ 迁移 ${file} 执行成功`);
} catch (error) {
console.error(`❌ 迁移 ${file} 执行失败:`, error);
throw error;
}
} else {
console.log(`⏭️ 迁移 ${file} 已执行,跳过`);
}
}
console.log('🎉 所有迁移执行完成!');
} catch (error) {
console.error('❌ 迁移执行失败:', error);
throw error;
} finally {
await sequelize.close();
}
}
// 运行迁移
runMigration().catch(error => {
console.error('迁移过程中发生错误:', error);
process.exit(1);
});

View File

@@ -19,11 +19,42 @@ const PORT = process.env.PORT || 3000;
// 安全中间件
app.use(helmet());
app.use(cors({
origin: [
process.env.FRONTEND_URL || 'http://localhost:3001',
'http://localhost:3002'
origin: function (origin, callback) {
// 允许的域名列表
const allowedOrigins = [
process.env.FRONTEND_URL || 'http://localhost:3001',
'http://127.0.0.1:3001',
'http://localhost:3002',
'http://127.0.0.1:3002',
'https://ad.ningmuyun.com',
'https://www.ningmuyun.com',
'https://ningmuyun.com'
];
// 允许没有origin的请求如移动应用、Postman等
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
console.log('CORS blocked origin:', origin);
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Requested-With',
'Accept',
'Cache-Control',
'Pragma',
'Origin',
'Access-Control-Request-Method',
'Access-Control-Request-Headers'
],
credentials: true
optionsSuccessStatus: 200
}));
// 速率限制 - 开发环境下放宽限制

View File

@@ -1,52 +0,0 @@
# 测试保险申请API
Write-Host "=== 保险申请API测试 ===" -ForegroundColor Green
# 1. 登录获取token
Write-Host "1. 正在登录..." -ForegroundColor Yellow
$loginResponse = Invoke-WebRequest -Uri "http://localhost:3000/api/auth/login" -Method POST -Body '{"username":"admin","password":"123456"}' -ContentType "application/json" -UseBasicParsing
$loginData = $loginResponse.Content | ConvertFrom-Json
if ($loginData.code -eq 200) {
$token = $loginData.data.token
Write-Host "✅ 登录成功,获取到令牌" -ForegroundColor Green
# 2. 测试保险申请列表API
Write-Host "2. 正在测试保险申请列表API..." -ForegroundColor Yellow
try {
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
$apiResponse = Invoke-WebRequest -Uri "http://localhost:3000/api/insurance/applications?page=1`&limit=10" -Headers $headers -UseBasicParsing
$apiData = $apiResponse.Content | ConvertFrom-Json
Write-Host "✅ API调用成功" -ForegroundColor Green
Write-Host "状态码: $($apiResponse.StatusCode)" -ForegroundColor Cyan
Write-Host "响应数据: $($apiResponse.Content)" -ForegroundColor Cyan
} catch {
Write-Host "❌ API调用失败: $($_.Exception.Message)" -ForegroundColor Red
if ($_.Exception.Response) {
$errorResponse = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorResponse)
$errorContent = $reader.ReadToEnd()
Write-Host "错误详情: $errorContent" -ForegroundColor Red
}
}
# 3. 测试前端代理
Write-Host "3. 正在测试前端代理..." -ForegroundColor Yellow
try {
$proxyResponse = Invoke-WebRequest -Uri "http://localhost:3002/api/insurance/applications?page=1`&limit=10" -Headers $headers -UseBasicParsing
Write-Host "✅ 前端代理调用成功!" -ForegroundColor Green
Write-Host "状态码: $($proxyResponse.StatusCode)" -ForegroundColor Cyan
} catch {
Write-Host "❌ 前端代理调用失败: $($_.Exception.Message)" -ForegroundColor Red
}
} else {
Write-Host "❌ 登录失败: $($loginData.message)" -ForegroundColor Red
}
Write-Host "=== 测试完成 ===" -ForegroundColor Green

View File

@@ -1 +0,0 @@
UPDATE users SET password = '$2b$12$8MenY1QAy0piFa64iM6k4.85TQAPjGEJRrNWV6g4C0gMP5/LmpEHe' WHERE username = 'admin';

View File

@@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100

1
insurance_datav/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

30
insurance_datav/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

View File

@@ -0,0 +1,55 @@
# Dashboard颜色修改报告
## 修改概述
本次修改将数据大屏Dashboard组件的颜色方案统一调整为基于Map3D组件的基础颜色移除了所有渐变色使用单一颜色方案。
## 颜色方案
基于Map3D组件的以下颜色
1. **主色调**: `#84acf0` - 地图顶部材质颜色
2. **辅助色**: `#7af4ff` - 地图光源颜色
3. **强调色**: `#00F6FF` - 养殖场标签颜色
4. **深色**: `#123024` - 地图侧面材质颜色
5. **背景色**: `#0c1426` - 深色背景
## 主要修改内容
### 1. 背景和容器
- 移除了复杂的渐变背景,使用纯色 `#0c1426`
- 简化背景装饰效果,只保留基础的径向渐变
### 2. 顶部标题栏
- 标题颜色改为 `#84acf0`
- 移除彩虹渐变文字效果
- 边框和装饰线使用单色
### 3. 面板和卡片
- 所有面板背景使用 `rgba(132, 172, 240, 0.05)`
- 边框颜色统一为 `rgba(132, 172, 240, 0.3)`
- 移除顶部装饰渐变线,使用单色
### 4. 数据展示
- 统计数值颜色改为 `#84acf0`
- 进度条背景使用 `rgba(18, 48, 36, 0.3)`
- 进度条填充使用单色 `#84acf0`
### 5. 图表配置
- 饼图使用四种基础颜色:`#84acf0`, `#7af4ff`, `#00F6FF`, `#123024`
- 柱状图使用单色 `#84acf0`
- 折线图使用 `#7af4ff` 或其他单色
- 移除所有LinearGradient渐变效果
## 修改文件
- `d:\1212\nxzhihui\website\src\components\Dashboard.vue`
## 验证方法
1. 启动website项目: `cd website && npm run dev`
2. 访问数据大屏页面
3. 检查颜色是否与Map3D组件协调统一
4. 确认没有渐变色残留
## 注意事项
- 保持了原有的视觉层次和可读性
- 所有交互效果和动画保持不变
- 响应式布局保持不变
- 只修改了颜色方案,未改变布局结构

238
insurance_datav/README.md Normal file
View File

@@ -0,0 +1,238 @@
# 宁夏智慧畜牧大数据平台 - 官网展示系统
## 项目概述
宁夏智慧畜牧大数据平台官网是一个现代化的数据可视化展示系统基于Vue 3和Three.js技术栈构建。该系统提供3D地图展示、实时数据监控、养殖场管理和数据大屏可视化等功能为宁夏地区的智慧畜牧业发展提供数字化支撑。
## 核心功能
### 🗺️ 3D地图可视化
- 基于Three.js的3D地球和地图展示
- 宁夏回族自治区地理边界可视化
- 养殖场地理位置标注和信息展示
- 交互式地图操作(缩放、旋转、点击)
### 📊 数据大屏展示
- 实时数据监控大屏
- 多维度数据图表展示
- 养殖业态势感知
- 预警信息集中展示
### 🏭 养殖场管理
- 养殖场基本信息展示
- 养殖规模和类型统计
- 联系方式和建立时间管理
- 点击弹窗详情查看
### 🎨 现代化UI设计
- 科技感十足的深色主题
- 流畅的动画效果
- 响应式布局设计
- 多页面导航系统
## 技术栈
### 前端框架
- **Vue 3** - 渐进式JavaScript框架
- **Vite** - 现代化构建工具
- **Vue Router** - 官方路由管理器
- **Pinia** - 状态管理库
### 3D可视化
- **Three.js** - 3D图形库
- **CSS2DRenderer** - 2D标签渲染器
- **WebGL** - 硬件加速图形渲染
### 数据可视化
- **ECharts** - 数据图表库
- **Vue-ECharts** - Vue集成组件
- **@jiaminghi/data-view** - 数据大屏组件
### 地图服务
- **Mapbox GL** - 地图渲染引擎
- **Cesium** - 3D地球可视化
- **GeoJSON** - 地理数据格式
### 开发工具
- **ESLint** - 代码质量检查
- **Oxlint** - 高性能代码检查
- **TypeScript** - 类型定义支持
## 项目结构
```
website/
├── public/ # 静态资源
│ ├── data/ # 地图数据文件
│ └── texture/ # 纹理贴图
├── src/
│ ├── components/ # Vue组件
│ │ ├── Map3D.vue # 3D地图主组件
│ │ ├── Home.vue # 首页组件
│ │ ├── Alert.vue # 预警监测组件
│ │ └── FarmPopup.vue # 养殖场弹窗组件
│ ├── hooks/ # 组合式API钩子
│ │ ├── useCoord.js # 坐标转换
│ │ ├── useFileLoader.js # 文件加载
│ │ └── useCSS2DRender.js # 2D渲染
│ ├── utils/ # 工具函数
│ ├── assets/ # 静态资源
│ ├── App.vue # 根组件
│ └── main.js # 应用入口
├── index.html # HTML模板
├── package.json # 项目配置
├── vite.config.js # Vite配置
└── README.md # 项目文档
```
## 快速开始
### 环境要求
- Node.js >= 20.19.0 或 >= 22.12.0
- npm 或 yarn 包管理器
### 安装依赖
```bash
npm install
```
### 开发环境运行
```bash
npm run dev
```
访问 http://localhost:5173 查看应用
### 生产环境构建
```bash
npm run build
```
### 预览构建结果
```bash
npm run preview
```
### 代码质量检查
```bash
# 运行所有代码检查
npm run lint
# 仅运行ESLint检查
npm run lint:eslint
# 仅运行Oxlint检查
npm run lint:oxlint
```
## 主要特性
### 3D地图交互
- 鼠标拖拽旋转地球
- 滚轮缩放地图视角
- 点击养殖场标记查看详情
- 平滑的相机动画过渡
### 数据展示
- 实时养殖场数据更新
- 多种图表类型支持
- 响应式数据大屏
- 自定义主题配置
### 用户体验
- 流畅的页面切换动画
- 现代化的UI设计
- 移动端适配支持
- 无障碍访问优化
## 开发指南
### 添加新的养殖场数据
`src/components/Map3D.vue` 文件中的 `farmData` 数组中添加新的养殖场信息:
```javascript
const farmData = [
{
id: 6,
name: '新养殖场',
position: [106.2081, 38.4681], // [经度, 纬度]
livestock: 15000,
area: '800亩',
type: '肉羊养殖',
established: '2023年',
contact: '张经理 138****1234'
}
];
```
### 自定义地图样式
修改 `src/components/Map3D.vue` 中的材质和颜色配置:
```javascript
// 修改地图边界线颜色
const lineMaterial = new THREE.LineBasicMaterial({
color: 0x00ffff, // 青色
linewidth: 2
});
// 修改养殖场标记颜色
const markerMaterial = new THREE.MeshBasicMaterial({
color: 0xff6b35 // 橙色
});
```
### 添加新的页面
1.`src/components/` 目录下创建新的Vue组件
2.`src/App.vue` 中注册组件和导航
3. 更新页面组件映射和导航菜单
## 部署说明
### 静态部署
构建完成后,将 `dist` 目录部署到任何静态文件服务器即可。
### Nginx配置示例
```nginx
server {
listen 80;
server_name your-domain.com;
root /path/to/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
## 贡献指南
1. Fork 项目仓库
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 创建 Pull Request
## 许可证
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
## 联系方式
- 项目维护者:开发团队
- 邮箱dev@example.com
- 项目地址https://github.com/your-org/nx-smart-livestock
## 更新日志
### v1.0.0 (2025-01-18)
- ✨ 初始版本发布
- 🗺️ 3D地图可视化功能
- 📊 数据大屏展示
- 🏭 养殖场管理系统
- 🎨 现代化UI设计
- 📱 响应式布局支持

View File

@@ -0,0 +1,26 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import globals from 'globals'
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
import pluginOxlint from 'eslint-plugin-oxlint'
export default defineConfig([
{
name: 'app/files-to-lint',
files: ['**/*.{js,mjs,jsx,vue}'],
},
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
{
languageOptions: {
globals: {
...globals.browser,
},
},
},
js.configs.recommended,
...pluginVue.configs['flat/essential'],
...pluginOxlint.configs['flat/recommended'],
])

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宁夏智慧畜牧大数据平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

5417
insurance_datav/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
{
"name": "nx",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
"lint:eslint": "eslint . --fix",
"lint": "run-s lint:*"
},
"dependencies": {
"@jiaminghi/data-view": "^2.10.0",
"@types/three": "^0.179.0",
"axios": "^1.11.0",
"cesium": "^1.133.0",
"echarts": "^5.6.0",
"mapbox-gl": "^3.14.0",
"pinia": "^3.0.3",
"three": "^0.179.1",
"vue": "^3.5.18",
"vue-echarts": "^7.0.3",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@eslint/js": "^9.31.0",
"@vitejs/plugin-vue": "^6.0.1",
"eslint": "^9.31.0",
"eslint-plugin-oxlint": "~1.8.0",
"eslint-plugin-vue": "~10.3.0",
"globals": "^16.3.0",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.8.0",
"vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

View File

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<!-- 背景圆形 -->
<circle cx="16" cy="16" r="15" fill="#0c1426" stroke="#00d4ff" stroke-width="2"/>
<!-- 牛头轮廓 -->
<path d="M8 18 Q8 12 12 10 Q16 8 20 10 Q24 12 24 18 L22 20 Q20 22 16 22 Q12 22 10 20 Z" fill="#00d4ff" opacity="0.8"/>
<!-- 牛角 -->
<path d="M10 12 Q8 10 7 8" stroke="#00ffff" stroke-width="2" fill="none" stroke-linecap="round"/>
<path d="M22 12 Q24 10 25 8" stroke="#00ffff" stroke-width="2" fill="none" stroke-linecap="round"/>
<!-- 眼睛 -->
<circle cx="13" cy="15" r="1.5" fill="#ffffff"/>
<circle cx="19" cy="15" r="1.5" fill="#ffffff"/>
<!-- 鼻子 -->
<ellipse cx="16" cy="18" rx="2" ry="1" fill="#ffffff" opacity="0.6"/>
<!-- 数据点装饰 -->
<circle cx="6" cy="8" r="1" fill="#00ffff" opacity="0.6"/>
<circle cx="26" cy="8" r="1" fill="#00ffff" opacity="0.6"/>
<circle cx="6" cy="24" r="1" fill="#00ffff" opacity="0.6"/>
<circle cx="26" cy="24" r="1" fill="#00ffff" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,32 @@
<svg width="512" height="256" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="earthGradient" cx="50%" cy="30%" r="70%">
<stop offset="0%" style="stop-color:#4a90e2;stop-opacity:1" />
<stop offset="30%" style="stop-color:#2c5aa0;stop-opacity:1" />
<stop offset="70%" style="stop-color:#1a365d;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0f2027;stop-opacity:1" />
</radialGradient>
<pattern id="continents" patternUnits="userSpaceOnUse" width="512" height="256">
<rect width="512" height="256" fill="url(#earthGradient)"/>
<!-- 简化的大陆轮廓 -->
<path d="M50 80 Q100 60 150 80 Q200 100 250 80 Q300 60 350 80 Q400 100 450 80"
stroke="#2d5016" stroke-width="3" fill="none" opacity="0.6"/>
<path d="M30 120 Q80 100 130 120 Q180 140 230 120 Q280 100 330 120 Q380 140 430 120"
stroke="#2d5016" stroke-width="2" fill="none" opacity="0.4"/>
<path d="M70 160 Q120 140 170 160 Q220 180 270 160 Q320 140 370 160 Q420 180 470 160"
stroke="#2d5016" stroke-width="2" fill="none" opacity="0.4"/>
<!-- 添加一些点状细节 -->
<circle cx="100" cy="90" r="2" fill="#2d5016" opacity="0.3"/>
<circle cx="200" cy="110" r="1.5" fill="#2d5016" opacity="0.3"/>
<circle cx="300" cy="130" r="2" fill="#2d5016" opacity="0.3"/>
<circle cx="400" cy="100" r="1.5" fill="#2d5016" opacity="0.3"/>
</pattern>
</defs>
<rect width="512" height="256" fill="url(#continents)"/>
<!-- 添加一些云层效果 -->
<ellipse cx="150" cy="60" rx="40" ry="15" fill="white" opacity="0.1"/>
<ellipse cx="350" cy="80" rx="50" ry="20" fill="white" opacity="0.1"/>
<ellipse cx="250" cy="180" rx="45" ry="18" fill="white" opacity="0.1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Some files were not shown because too many files have changed in this diff Show More