保险前后端,养殖端和保险端小程序

This commit is contained in:
xuqiuyun
2025-09-17 19:01:52 +08:00
parent e4287b83fe
commit 473891163c
218 changed files with 109331 additions and 14103 deletions

View File

@@ -0,0 +1,33 @@
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=insurance_db
DB_USER=insurance_user
DB_PASSWORD=insurance_password
# JWT配置
JWT_SECRET=your_super_secret_jwt_key_here
JWT_EXPIRE=7d
# Redis配置
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# 服务器配置
PORT=3000
NODE_ENV=development
# 文件上传配置
UPLOAD_PATH=./uploads
MAX_FILE_SIZE=10485760
# 邮件配置(可选)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_app_password
# 第三方API配置可选
API_BASE_URL=http://localhost:3000/api
FRONTEND_URL=http://localhost:5173

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_NAME || '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,148 @@
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerDefinition = {
openapi: '3.0.0',
info: {
title: '保险端口系统 API',
version: '1.0.0',
description: '保险端口系统后端API文档',
contact: {
name: '技术支持',
email: 'support@insurance.com'
},
license: {
name: 'MIT',
url: 'https://opensource.org/licenses/MIT'
}
},
servers: [
{
url: 'http://localhost:3000',
description: '开发环境服务器'
},
{
url: 'https://api.insurance.com',
description: '生产环境服务器'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
},
schemas: {
User: {
type: 'object',
properties: {
id: { type: 'integer', description: '用户ID' },
username: { type: 'string', description: '用户名' },
email: { type: 'string', description: '邮箱' },
phone: { type: 'string', description: '手机号' },
status: { type: 'string', enum: ['active', 'inactive'], description: '用户状态' },
createdAt: { type: 'string', format: 'date-time', description: '创建时间' },
updatedAt: { type: 'string', format: 'date-time', description: '更新时间' }
}
},
InsuranceApplication: {
type: 'object',
properties: {
id: { type: 'integer', description: '申请ID' },
applicantName: { type: 'string', description: '申请人姓名' },
insuranceType: { type: 'string', description: '保险类型' },
status: { type: 'string', enum: ['pending', 'approved', 'rejected'], description: '申请状态' },
amount: { type: 'number', format: 'float', description: '保险金额' },
createdAt: { type: 'string', format: 'date-time', description: '创建时间' }
}
},
Policy: {
type: 'object',
properties: {
id: { type: 'integer', description: '保单ID' },
policyNumber: { type: 'string', description: '保单号' },
insuranceType: { type: 'string', description: '保险类型' },
premium: { type: 'number', format: 'float', description: '保费' },
status: { type: 'string', enum: ['active', 'expired', 'cancelled'], description: '保单状态' },
startDate: { type: 'string', format: 'date', description: '生效日期' },
endDate: { type: 'string', format: 'date', description: '到期日期' }
}
},
Claim: {
type: 'object',
properties: {
id: { type: 'integer', description: '理赔ID' },
claimNumber: { type: 'string', description: '理赔单号' },
policyId: { type: 'integer', description: '关联保单ID' },
amount: { type: 'number', format: 'float', description: '理赔金额' },
status: { type: 'string', enum: ['pending', 'approved', 'paid', 'rejected'], description: '理赔状态' },
createdAt: { type: 'string', format: 'date-time', description: '创建时间' }
}
},
Error: {
type: 'object',
properties: {
code: { type: 'integer', description: '错误码' },
status: { type: 'string', description: '错误状态' },
message: { type: 'string', description: '错误信息' },
timestamp: { type: 'string', format: 'date-time', description: '时间戳' }
}
}
},
responses: {
UnauthorizedError: {
description: '认证失败',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error'
},
example: {
code: 401,
status: 'error',
message: '未授权访问',
timestamp: '2024-01-01T00:00:00.000Z'
}
}
}
},
NotFoundError: {
description: '资源不存在',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error'
},
example: {
code: 404,
status: 'error',
message: '资源不存在',
timestamp: '2024-01-01T00:00:00.000Z'
}
}
}
}
}
},
tags: [
{ name: '认证', description: '用户认证相关接口' },
{ name: '用户管理', description: '用户管理相关接口' },
{ name: '保险申请', description: '保险申请管理相关接口' },
{ name: '保单管理', description: '保单管理相关接口' },
{ name: '理赔管理', description: '理赔管理相关接口' },
{ name: '系统管理', description: '系统管理相关接口' }
]
};
const options = {
swaggerDefinition,
apis: [
'./routes/*.js',
'./controllers/*.js'
]
};
const swaggerSpec = swaggerJSDoc(options);
module.exports = swaggerSpec;

View File

@@ -0,0 +1,234 @@
const jwt = require('jsonwebtoken');
const { User, Role, sequelize } = require('../models');
const { Op } = require('sequelize');
const responseFormat = require('../utils/response');
// 用户注册
const register = async (req, res) => {
try {
const { username, password, real_name, email, phone, role_id } = req.body;
// 检查用户名是否已存在
const existingUser = await User.findOne({ where: { username } });
if (existingUser) {
return res.status(400).json(responseFormat.error('用户名已存在'));
}
// 检查邮箱是否已存在
const existingEmail = await User.findOne({ where: { email } });
if (existingEmail) {
return res.status(400).json(responseFormat.error('邮箱已存在'));
}
// 检查手机号是否已存在
const existingPhone = await User.findOne({ where: { phone } });
if (existingPhone) {
return res.status(400).json(responseFormat.error('手机号已存在'));
}
// 检查角色是否存在
const role = await Role.findByPk(role_id);
if (!role) {
return res.status(400).json(responseFormat.error('角色不存在'));
}
// 创建用户
const user = await User.create({
username,
password,
real_name,
email,
phone,
role_id
});
res.status(201).json(responseFormat.created(user, '用户注册成功'));
} catch (error) {
console.error('注册错误:', error);
res.status(500).json(responseFormat.error('用户注册失败'));
}
};
// 用户登录
const login = async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json(responseFormat.error('用户名和密码不能为空'));
}
// 查找用户(支持用户名、邮箱、手机号登录)
const user = await User.findOne({
where: {
[Op.or]: [
{ username },
{ email: username },
{ phone: username }
]
},
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name', 'permissions']
}]
});
if (!user) {
return res.status(401).json(responseFormat.error('用户不存在'));
}
if (user.status !== 'active') {
return res.status(401).json(responseFormat.error('账户已被禁用'));
}
// 验证密码
const isValidPassword = await user.validatePassword(password);
if (!isValidPassword) {
return res.status(401).json(responseFormat.error('密码错误'));
}
// 更新最后登录时间
await user.update({ last_login: new Date() });
// 生成JWT令牌
const token = jwt.sign(
{
id: user.id,
username: user.username,
role_id: user.role_id,
permissions: user.role?.permissions || []
},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE || '7d' }
);
res.json(responseFormat.success({
user: user.toJSON(),
token,
expires_in: 7 * 24 * 60 * 60 // 7天
}, '登录成功'));
} catch (error) {
console.error('登录错误:', error);
res.status(500).json(responseFormat.error('登录失败'));
}
};
// 获取当前用户信息
const getCurrentUser = async (req, res) => {
try {
const user = await User.findByPk(req.user.id, {
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name', 'permissions']
}]
});
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
res.json(responseFormat.success(user, '获取用户信息成功'));
} catch (error) {
console.error('获取用户信息错误:', error);
res.status(500).json(responseFormat.error('获取用户信息失败'));
}
};
// 修改密码
const changePassword = async (req, res) => {
try {
const { currentPassword, newPassword } = req.body;
const userId = req.user.id;
if (!currentPassword || !newPassword) {
return res.status(400).json(responseFormat.error('当前密码和新密码不能为空'));
}
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
// 验证当前密码
const isValidPassword = await user.validatePassword(currentPassword);
if (!isValidPassword) {
return res.status(401).json(responseFormat.error('当前密码错误'));
}
// 更新密码
await user.update({ password: newPassword });
res.json(responseFormat.success(null, '密码修改成功'));
} catch (error) {
console.error('修改密码错误:', error);
res.status(500).json(responseFormat.error('密码修改失败'));
}
};
// 刷新令牌
const refreshToken = async (req, res) => {
try {
const { refresh_token } = req.body;
if (!refresh_token) {
return res.status(400).json(responseFormat.error('刷新令牌不能为空'));
}
// 验证刷新令牌(这里简化处理,实际应该使用专门的刷新令牌机制)
const decoded = jwt.verify(refresh_token, process.env.JWT_SECRET);
const user = await User.findByPk(decoded.id, {
include: [{
model: Role,
as: 'role',
attributes: ['permissions']
}]
});
if (!user || user.status !== 'active') {
return res.status(401).json(responseFormat.error('用户不存在或已被禁用'));
}
// 生成新的访问令牌
const newToken = jwt.sign(
{
id: user.id,
username: user.username,
role_id: user.role_id,
permissions: user.role?.permissions || []
},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE || '7d' }
);
res.json(responseFormat.success({
token: newToken,
expires_in: 7 * 24 * 60 * 60
}, '令牌刷新成功'));
} catch (error) {
console.error('刷新令牌错误:', error);
res.status(401).json(responseFormat.error('刷新令牌无效'));
}
};
// 用户登出
const logout = async (req, res) => {
try {
// 这里可以添加令牌黑名单逻辑
res.json(responseFormat.success(null, '登出成功'));
} catch (error) {
console.error('登出错误:', error);
res.status(500).json(responseFormat.error('登出失败'));
}
};
module.exports = {
register,
login,
getCurrentUser,
changePassword,
refreshToken,
logout
};

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,218 @@
const { InsuranceApplication, InsuranceType, User } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取保险申请列表
const getApplications = async (req, res) => {
try {
const {
name,
status,
dateRange,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
// 姓名筛选
if (name) {
whereClause.customer_name = { [Op.like]: `%${name}%` };
}
// 状态筛选
if (status) {
whereClause.status = status;
}
// 日期范围筛选
if (dateRange && dateRange.start && dateRange.end) {
whereClause.application_date = {
[Op.between]: [new Date(dateRange.start), new Date(dateRange.end)]
};
}
const offset = (page - 1) * limit;
const { count, rows } = await InsuranceApplication.findAndCountAll({
where: whereClause,
include: [
{
model: InsuranceType,
as: 'insurance_type',
attributes: ['id', 'name', 'description']
},
{
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 createApplication = async (req, res) => {
try {
const applicationData = req.body;
// 生成申请编号
const applicationNo = `INS${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
const application = await InsuranceApplication.create({
...applicationData,
application_no: applicationNo
});
res.status(201).json(responseFormat.created(application, '保险申请创建成功'));
} catch (error) {
console.error('创建保险申请错误:', error);
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, review_notes } = req.body;
const reviewerId = req.user.id;
const application = await InsuranceApplication.findByPk(id);
if (!application) {
return res.status(404).json(responseFormat.error('保险申请不存在'));
}
if (!['approved', 'rejected', 'under_review'].includes(status)) {
return res.status(400).json(responseFormat.error('无效的审核状态'));
}
await application.update({
status,
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('获取保险申请统计失败'));
}
};
module.exports = {
getApplications,
createApplication,
getApplicationById,
updateApplication,
reviewApplication,
deleteApplication,
getApplicationStats
};

View File

@@ -0,0 +1,199 @@
const { InsuranceType } = require('../models');
const responseFormat = require('../utils/response');
// 获取保险类型列表
const getInsuranceTypes = async (req, res) => {
try {
const { page = 1, pageSize = 10, name, status } = req.query;
const offset = (page - 1) * pageSize;
const whereClause = {};
if (name) {
whereClause.name = { [Op.like]: `%${name}%` };
}
if (status) {
whereClause.status = status;
}
const { count, rows } = await InsuranceType.findAndCountAll({
where: whereClause,
limit: parseInt(pageSize),
offset: offset,
order: [['created_at', 'DESC']]
});
res.json(responseFormat.success({
list: rows,
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize),
pages: Math.ceil(count / pageSize)
}, '获取保险类型列表成功'));
} catch (error) {
console.error('获取保险类型列表错误:', error);
res.status(500).json(responseFormat.error('获取保险类型列表失败'));
}
};
// 获取单个保险类型详情
const getInsuranceTypeById = async (req, res) => {
try {
const { id } = req.params;
const insuranceType = await InsuranceType.findByPk(id);
if (!insuranceType) {
return res.status(404).json(responseFormat.error('保险类型不存在'));
}
res.json(responseFormat.success(insuranceType, '获取保险类型详情成功'));
} catch (error) {
console.error('获取保险类型详情错误:', error);
res.status(500).json(responseFormat.error('获取保险类型详情失败'));
}
};
// 创建保险类型
const createInsuranceType = async (req, res) => {
try {
const {
name,
code,
description,
coverage_amount_min,
coverage_amount_max,
premium_rate,
status = 'active'
} = req.body;
// 检查名称是否已存在
const existingType = await InsuranceType.findOne({ where: { name } });
if (existingType) {
return res.status(400).json(responseFormat.error('保险类型名称已存在'));
}
// 检查代码是否已存在
const existingCode = await InsuranceType.findOne({ where: { code } });
if (existingCode) {
return res.status(400).json(responseFormat.error('保险类型代码已存在'));
}
const insuranceType = await InsuranceType.create({
name,
code,
description,
coverage_amount_min,
coverage_amount_max,
premium_rate,
status
});
res.status(201).json(responseFormat.success(insuranceType, '创建保险类型成功'));
} catch (error) {
console.error('创建保险类型错误:', error);
res.status(500).json(responseFormat.error('创建保险类型失败'));
}
};
// 更新保险类型
const updateInsuranceType = async (req, res) => {
try {
const { id } = req.params;
const {
name,
code,
description,
coverage_amount_min,
coverage_amount_max,
premium_rate,
status
} = req.body;
const insuranceType = await InsuranceType.findByPk(id);
if (!insuranceType) {
return res.status(404).json(responseFormat.error('保险类型不存在'));
}
// 检查名称是否已被其他类型使用
if (name && name !== insuranceType.name) {
const existingType = await InsuranceType.findOne({ where: { name } });
if (existingType) {
return res.status(400).json(responseFormat.error('保险类型名称已存在'));
}
}
// 检查代码是否已被其他类型使用
if (code && code !== insuranceType.code) {
const existingCode = await InsuranceType.findOne({ where: { code } });
if (existingCode) {
return res.status(400).json(responseFormat.error('保险类型代码已存在'));
}
}
await insuranceType.update({
name: name || insuranceType.name,
code: code || insuranceType.code,
description: description || insuranceType.description,
coverage_amount_min: coverage_amount_min || insuranceType.coverage_amount_min,
coverage_amount_max: coverage_amount_max || insuranceType.coverage_amount_max,
premium_rate: premium_rate || insuranceType.premium_rate,
status: status || insuranceType.status
});
res.json(responseFormat.success(insuranceType, '更新保险类型成功'));
} catch (error) {
console.error('更新保险类型错误:', error);
res.status(500).json(responseFormat.error('更新保险类型失败'));
}
};
// 删除保险类型
const deleteInsuranceType = async (req, res) => {
try {
const { id } = req.params;
const insuranceType = await InsuranceType.findByPk(id);
if (!insuranceType) {
return res.status(404).json(responseFormat.error('保险类型不存在'));
}
// 检查是否有相关的保险申请或保单
const hasApplications = await insuranceType.countInsuranceApplications();
if (hasApplications > 0) {
return res.status(400).json(responseFormat.error('该保险类型下存在保险申请,无法删除'));
}
await insuranceType.destroy();
res.json(responseFormat.success(null, '删除保险类型成功'));
} catch (error) {
console.error('删除保险类型错误:', error);
res.status(500).json(responseFormat.error('删除保险类型失败'));
}
};
// 更新保险类型状态
const updateInsuranceTypeStatus = async (req, res) => {
try {
const { id } = req.params;
const { status } = req.body;
const insuranceType = await InsuranceType.findByPk(id);
if (!insuranceType) {
return res.status(404).json(responseFormat.error('保险类型不存在'));
}
await insuranceType.update({ status });
res.json(responseFormat.success(insuranceType, '更新保险类型状态成功'));
} catch (error) {
console.error('更新保险类型状态错误:', error);
res.status(500).json(responseFormat.error('更新保险类型状态失败'));
}
};
module.exports = {
getInsuranceTypes,
getInsuranceTypeById,
createInsuranceType,
updateInsuranceType,
deleteInsuranceType,
updateInsuranceTypeStatus
};

View File

@@ -0,0 +1,210 @@
const { Policy, InsuranceApplication, InsuranceType, User } = require('../models');
const responseFormat = require('../utils/response');
const { Op } = require('sequelize');
// 获取保单列表
const getPolicies = async (req, res) => {
try {
const {
policy_no,
customer_name,
policy_status,
payment_status,
page = 1,
limit = 10
} = req.query;
const whereClause = {};
// 保单编号筛选
if (policy_no) {
whereClause.policy_no = { [Op.like]: `%${policy_no}%` };
}
// 保单状态筛选
if (policy_status) {
whereClause.policy_status = policy_status;
}
// 支付状态筛选
if (payment_status) {
whereClause.payment_status = payment_status;
}
const offset = (page - 1) * limit;
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']
}
],
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 createPolicy = async (req, res) => {
try {
const policyData = req.body;
// 生成保单编号
const policyNo = `POL${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
const policy = await Policy.create({
...policyData,
policy_no: policyNo
});
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;
const policy = await Policy.findByPk(id);
if (!policy) {
return res.status(404).json(responseFormat.error('保单不存在'));
}
await policy.update(updateData);
res.json(responseFormat.success(policy, '保单更新成功'));
} catch (error) {
console.error('更新保单错误:', 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('获取保单统计失败'));
}
};
module.exports = {
getPolicies,
createPolicy,
getPolicyById,
updatePolicy,
updatePolicyStatus,
getPolicyStats
};

View File

@@ -0,0 +1,232 @@
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'
}
];
// 简单的过滤逻辑
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 / 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

@@ -0,0 +1,232 @@
const { User, Role } = require('../models');
const responseFormat = require('../utils/response');
// 获取用户列表
const getUsers = async (req, res) => {
try {
const { page = 1, limit = 10, search, role_id, status } = req.query;
const offset = (page - 1) * limit;
const whereClause = {};
if (search) {
whereClause[Op.or] = [
{ username: { [Op.like]: `%${search}%` } },
{ real_name: { [Op.like]: `%${search}%` } },
{ email: { [Op.like]: `%${search}%` } },
{ phone: { [Op.like]: `%${search}%` } }
];
}
if (role_id) whereClause.role_id = role_id;
if (status) whereClause.status = status;
const { count, rows: users } = await User.findAndCountAll({
where: whereClause,
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name']
}],
attributes: { exclude: ['password'] },
limit: parseInt(limit),
offset: parseInt(offset),
order: [['created_at', 'DESC']]
});
res.json(responseFormat.success({
users,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
pages: Math.ceil(count / limit)
}
}, '获取用户列表成功'));
} catch (error) {
console.error('获取用户列表错误:', error);
res.status(500).json(responseFormat.error('获取用户列表失败'));
}
};
// 获取单个用户信息
const getUser = async (req, res) => {
try {
const { id } = req.params;
const user = await User.findByPk(id, {
include: [{
model: Role,
as: 'role',
attributes: ['id', 'name', 'permissions']
}],
attributes: { exclude: ['password'] }
});
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
res.json(responseFormat.success(user, '获取用户信息成功'));
} catch (error) {
console.error('获取用户信息错误:', error);
res.status(500).json(responseFormat.error('获取用户信息失败'));
}
};
// 创建用户
const createUser = async (req, res) => {
try {
const { username, password, real_name, email, phone, role_id, status = 'active' } = req.body;
// 检查用户名是否已存在
const existingUser = await User.findOne({ where: { username } });
if (existingUser) {
return res.status(400).json(responseFormat.error('用户名已存在'));
}
// 检查邮箱是否已存在
const existingEmail = await User.findOne({ where: { email } });
if (existingEmail) {
return res.status(400).json(responseFormat.error('邮箱已存在'));
}
// 检查手机号是否已存在
const existingPhone = await User.findOne({ where: { phone } });
if (existingPhone) {
return res.status(400).json(responseFormat.error('手机号已存在'));
}
// 检查角色是否存在
const role = await Role.findByPk(role_id);
if (!role) {
return res.status(400).json(responseFormat.error('角色不存在'));
}
// 创建用户
const user = await User.create({
username,
password,
real_name,
email,
phone,
role_id,
status
});
res.status(201).json(responseFormat.created(user, '用户创建成功'));
} catch (error) {
console.error('创建用户错误:', error);
res.status(500).json(responseFormat.error('创建用户失败'));
}
};
// 更新用户信息
const updateUser = async (req, res) => {
try {
const { id } = req.params;
const { real_name, email, phone, role_id } = req.body;
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
// 检查邮箱是否已被其他用户使用
if (email && email !== user.email) {
const existingEmail = await User.findOne({ where: { email } });
if (existingEmail) {
return res.status(400).json(responseFormat.error('邮箱已被其他用户使用'));
}
}
// 检查手机号是否已被其他用户使用
if (phone && phone !== user.phone) {
const existingPhone = await User.findOne({ where: { phone } });
if (existingPhone) {
return res.status(400).json(responseFormat.error('手机号已被其他用户使用'));
}
}
// 检查角色是否存在
if (role_id) {
const role = await Role.findByPk(role_id);
if (!role) {
return res.status(400).json(responseFormat.error('角色不存在'));
}
}
// 更新用户信息
await user.update({
real_name: real_name || user.real_name,
email: email || user.email,
phone: phone || user.phone,
role_id: role_id || user.role_id
});
res.json(responseFormat.success(user, '用户信息更新成功'));
} catch (error) {
console.error('更新用户信息错误:', error);
res.status(500).json(responseFormat.error('更新用户信息失败'));
}
};
// 删除用户
const deleteUser = async (req, res) => {
try {
const { id } = req.params;
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
// 不能删除自己
if (user.id === req.user.id) {
return res.status(400).json(responseFormat.error('不能删除自己的账户'));
}
await user.destroy();
res.json(responseFormat.success(null, '用户删除成功'));
} catch (error) {
console.error('删除用户错误:', error);
res.status(500).json(responseFormat.error('删除用户失败'));
}
};
// 更新用户状态
const updateUserStatus = async (req, res) => {
try {
const { id } = req.params;
const { status } = req.body;
if (!['active', 'inactive', 'suspended'].includes(status)) {
return res.status(400).json(responseFormat.error('状态值无效'));
}
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json(responseFormat.error('用户不存在'));
}
// 不能禁用自己
if (user.id === req.user.id && status !== 'active') {
return res.status(400).json(responseFormat.error('不能禁用自己的账户'));
}
await user.update({ status });
res.json(responseFormat.success(user, '用户状态更新成功'));
} catch (error) {
console.error('更新用户状态错误:', error);
res.status(500).json(responseFormat.error('更新用户状态失败'));
}
};
module.exports = {
getUsers,
getUser,
createUser,
updateUser,
deleteUser,
updateUserStatus
};

View File

@@ -0,0 +1,14 @@
const bcrypt = require('bcrypt');
async function generateHash() {
const password = 'admin123';
const hash = await bcrypt.hash(password, 12);
console.log('Password:', password);
console.log('Hash:', hash);
// 验证哈希
const isValid = await bcrypt.compare(password, hash);
console.log('验证结果:', isValid);
}
generateHash().catch(console.error);

View File

@@ -0,0 +1,70 @@
const jwt = require('jsonwebtoken');
const responseFormat = require('../utils/response');
// JWT认证中间件
const jwtAuth = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json(responseFormat.error('未提供认证令牌'));
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json(responseFormat.error('认证令牌无效或已过期'));
}
};
// 权限检查中间件
const checkPermission = (resource, action) => {
return async (req, res, next) => {
try {
const { Role } = require('../models');
const user = req.user;
if (!user || !user.role_id) {
return res.status(403).json(responseFormat.error('用户角色信息缺失'));
}
const userRole = await Role.findByPk(user.role_id);
if (!userRole) {
return res.status(403).json(responseFormat.error('用户角色不存在'));
}
const permissions = userRole.permissions || [];
const requiredPermission = `${resource}:${action}`;
// 检查权限或超级管理员权限
if (!permissions.includes(requiredPermission) && !permissions.includes('*:*')) {
return res.status(403).json(responseFormat.error('权限不足'));
}
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();
};
module.exports = { jwtAuth, checkPermission, optionalAuth };

View File

@@ -0,0 +1,142 @@
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,106 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const InsuranceApplication = sequelize.define('InsuranceApplication', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
application_no: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
len: [10, 50]
}
},
customer_name: {
type: DataTypes.STRING(100),
allowNull: false,
validate: {
len: [2, 100]
}
},
customer_id_card: {
type: DataTypes.STRING(18),
allowNull: false,
validate: {
is: /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/
}
},
customer_phone: {
type: DataTypes.STRING(20),
allowNull: false,
validate: {
is: /^1[3-9]\d{9}$/
}
},
customer_address: {
type: DataTypes.STRING(255),
allowNull: false,
validate: {
len: [5, 255]
}
},
insurance_type_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'insurance_types',
key: 'id'
}
},
application_amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
validate: {
min: 0
}
},
status: {
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'under_review'),
defaultValue: 'pending'
},
application_date: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
review_notes: {
type: DataTypes.TEXT,
allowNull: true
},
reviewer_id: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
review_date: {
type: DataTypes.DATE,
allowNull: true
},
documents: {
type: DataTypes.JSON,
allowNull: true,
defaultValue: []
}
}, {
tableName: 'insurance_applications',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['application_no'] },
{ fields: ['customer_id_card'] },
{ fields: ['customer_phone'] },
{ fields: ['status'] },
{ fields: ['application_date'] },
{ fields: ['insurance_type_id'] },
{ fields: ['reviewer_id'] }
]
});
module.exports = InsuranceApplication;

View File

@@ -0,0 +1,105 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const InsuranceType = sequelize.define('InsuranceType', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '保险类型ID'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '保险类型名称',
validate: {
notEmpty: {
msg: '保险类型名称不能为空'
},
len: {
args: [1, 100],
msg: '保险类型名称长度必须在1-100个字符之间'
}
}
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '保险类型描述'
},
coverage_amount_min: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
defaultValue: 0.00,
comment: '最低保额',
validate: {
min: {
args: [0],
msg: '最低保额不能小于0'
}
}
},
coverage_amount_max: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false,
defaultValue: 1000000.00,
comment: '最高保额',
validate: {
min: {
args: [0],
msg: '最高保额不能小于0'
}
}
},
premium_rate: {
type: DataTypes.DECIMAL(5, 4),
allowNull: false,
defaultValue: 0.001,
comment: '保险费率',
validate: {
min: {
args: [0],
msg: '保险费率不能小于0'
},
max: {
args: [1],
msg: '保险费率不能大于1'
}
}
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
allowNull: false,
defaultValue: 'active',
comment: '状态active-启用inactive-停用'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID'
}
}, {
tableName: 'insurance_types',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
name: 'idx_insurance_type_name',
fields: ['name'],
unique: true
},
{
name: 'idx_insurance_type_status',
fields: ['status']
}
],
comment: '保险类型表'
});
module.exports = InsuranceType;

View File

@@ -0,0 +1,153 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Policy = sequelize.define('Policy', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
comment: '保单ID'
},
policy_no: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
comment: '保单编号',
validate: {
notEmpty: {
msg: '保单编号不能为空'
}
}
},
application_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '关联的保险申请ID',
references: {
model: 'insurance_applications',
key: 'id'
}
},
insurance_type_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '保险类型ID',
references: {
model: 'insurance_types',
key: 'id'
}
},
customer_id: {
type: DataTypes.INTEGER,
allowNull: false,
comment: '客户ID',
references: {
model: 'users',
key: 'id'
}
},
coverage_amount: {
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'
},
terms_and_conditions: {
type: DataTypes.TEXT,
allowNull: true,
comment: '条款和条件'
},
created_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updated_by: {
type: DataTypes.INTEGER,
allowNull: true,
comment: '更新人ID'
}
}, {
tableName: 'policies',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
name: 'idx_policy_no',
fields: ['policy_no'],
unique: true
},
{
name: 'idx_policy_customer',
fields: ['customer_id']
},
{
name: 'idx_policy_application',
fields: ['application_id']
},
{
name: 'idx_policy_status',
fields: ['policy_status']
},
{
name: 'idx_policy_payment_status',
fields: ['payment_status']
},
{
name: 'idx_policy_dates',
fields: ['start_date', 'end_date']
}
],
comment: '保单表'
});
module.exports = Policy;

View File

@@ -0,0 +1,41 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const Role = sequelize.define('Role', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
len: [2, 50]
}
},
description: {
type: DataTypes.STRING(255),
allowNull: true
},
permissions: {
type: DataTypes.JSON,
allowNull: false,
defaultValue: []
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
defaultValue: 'active'
}
}, {
tableName: 'roles',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['name'] },
{ fields: ['status'] }
]
});
module.exports = Role;

View File

@@ -0,0 +1,105 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const bcrypt = require('bcrypt');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
username: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
len: [3, 50],
is: /^[a-zA-Z0-9_]+$/
}
},
password: {
type: DataTypes.STRING(255),
allowNull: false,
validate: {
len: [6, 255]
}
},
real_name: {
type: DataTypes.STRING(50),
allowNull: false,
validate: {
len: [2, 50]
}
},
email: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
phone: {
type: DataTypes.STRING(20),
allowNull: false,
validate: {
is: /^1[3-9]\d{9}$/
}
},
role_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'roles',
key: 'id'
}
},
status: {
type: DataTypes.ENUM('active', 'inactive', 'suspended'),
defaultValue: 'active'
},
last_login: {
type: DataTypes.DATE,
allowNull: true
},
avatar: {
type: DataTypes.STRING(255),
allowNull: true
}
}, {
tableName: 'users',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['username'] },
{ fields: ['email'] },
{ fields: ['phone'] },
{ fields: ['role_id'] },
{ fields: ['status'] }
],
hooks: {
beforeCreate: async (user) => {
if (user.password) {
user.password = await bcrypt.hash(user.password, 12);
}
},
beforeUpdate: async (user) => {
if (user.changed('password')) {
user.password = await bcrypt.hash(user.password, 12);
}
}
}
});
// 实例方法
User.prototype.validatePassword = async function(password) {
return await bcrypt.compare(password, this.password);
};
User.prototype.toJSON = function() {
const values = Object.assign({}, this.get());
delete values.password;
return values;
};
module.exports = User;

View File

@@ -0,0 +1,66 @@
const { sequelize } = require('../config/database');
// 导入所有模型
const User = require('./User');
const Role = require('./Role');
const InsuranceApplication = require('./InsuranceApplication');
const InsuranceType = require('./InsuranceType');
const Policy = require('./Policy');
const Claim = require('./Claim');
// 定义模型关联关系
// 用户和角色关联
User.belongsTo(Role, { foreignKey: 'role_id', as: 'role' });
Role.hasMany(User, { foreignKey: 'role_id', as: 'users' });
// 保险申请和保险类型关联
InsuranceApplication.belongsTo(InsuranceType, {
foreignKey: 'insurance_type_id',
as: 'insurance_type'
});
InsuranceType.hasMany(InsuranceApplication, {
foreignKey: 'insurance_type_id',
as: 'applications'
});
// 保险申请和审核人关联
InsuranceApplication.belongsTo(User, {
foreignKey: 'reviewer_id',
as: 'reviewer'
});
User.hasMany(InsuranceApplication, {
foreignKey: 'reviewer_id',
as: 'reviewed_applications'
});
// 保单和保险申请关联
Policy.belongsTo(InsuranceApplication, {
foreignKey: 'application_id',
as: 'application'
});
InsuranceApplication.hasOne(Policy, {
foreignKey: 'application_id',
as: 'policy'
});
// 理赔和保单关联
Claim.belongsTo(Policy, {
foreignKey: 'policy_id',
as: 'policy'
});
Policy.hasMany(Claim, {
foreignKey: 'policy_id',
as: 'claims'
});
// 导出所有模型
module.exports = {
sequelize,
User,
Role,
InsuranceApplication,
InsuranceType,
Policy,
Claim
};

32672
insurance_backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
{
"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": {
"bcrypt": "^5.1.0",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-rate-limit": "^8.1.0",
"helmet": "^8.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",
"sequelize-cli": "^6.6.0",
"supertest": "^6.3.3"
},
"engines": {
"node": "16.20.2",
"npm": ">=8.0.0"
}
}

View File

@@ -0,0 +1,209 @@
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const { jwtAuth } = require('../middleware/auth');
/**
* @swagger
* /api/auth/register:
* post:
* tags:
* - 认证
* summary: 用户注册
* description: 创建新用户账户
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - email
* - password
* properties:
* username:
* type: string
* description: 用户名
* email:
* type: string
* format: email
* description: 邮箱
* password:
* type: string
* format: password
* description: 密码
* phone:
* type: string
* description: 手机号
* responses:
* 201:
* description: 注册成功
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* 400:
* $ref: '#/components/responses/UnauthorizedError'
*/
router.post('/register', authController.register);
/**
* @swagger
* /api/auth/login:
* post:
* tags:
* - 认证
* summary: 用户登录
* description: 用户登录获取访问令牌
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - password
* properties:
* username:
* type: string
* description: 用户名或邮箱
* password:
* type: string
* format: password
* description: 密码
* responses:
* 200:
* description: 登录成功
* content:
* application/json:
* schema:
* type: object
* properties:
* token:
* type: string
* description: JWT访问令牌
* refreshToken:
* type: string
* description: 刷新令牌
* user:
* $ref: '#/components/schemas/User'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
router.post('/login', authController.login);
/**
* @swagger
* /api/auth/me:
* get:
* tags:
* - 认证
* summary: 获取当前用户信息
* description: 获取当前登录用户的信息
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取成功
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
router.get('/me', jwtAuth, authController.getCurrentUser);
/**
* @swagger
* /api/auth/refresh:
* post:
* tags:
* - 认证
* summary: 刷新令牌
* description: 使用刷新令牌获取新的访问令牌
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - refreshToken
* properties:
* refreshToken:
* type: string
* description: 刷新令牌
* responses:
* 200:
* description: 令牌刷新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* token:
* type: string
* description: 新的JWT访问令牌
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
router.post('/refresh', authController.refreshToken);
/**
* @swagger
* /api/auth/logout:
* post:
* tags:
* - 认证
* summary: 用户登出
* description: 用户登出系统
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 登出成功
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
router.post('/logout', jwtAuth, authController.logout);
/**
* @swagger
* /api/auth/password:
* put:
* tags:
* - 认证
* summary: 修改密码
* description: 修改当前用户的密码
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - currentPassword
* - newPassword
* properties:
* currentPassword:
* type: string
* format: password
* description: 当前密码
* newPassword:
* type: string
* format: password
* description: 新密码
* responses:
* 200:
* description: 密码修改成功
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
router.put('/password', jwtAuth, authController.changePassword);
module.exports = router;

View File

@@ -0,0 +1,36 @@
const express = require('express');
const router = express.Router();
const claimController = require('../controllers/claimController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取理赔列表
router.get('/', jwtAuth, checkPermission('claim', 'read'),
claimController.getClaims
);
// 创建理赔申请
router.post('/', jwtAuth, checkPermission('claim', 'create'),
claimController.createClaim
);
// 获取单个理赔详情
router.get('/:id', jwtAuth, checkPermission('claim', 'read'),
claimController.getClaimById
);
// 审核理赔申请
router.patch('/:id/review', jwtAuth, checkPermission('claim', 'review'),
claimController.reviewClaim
);
// 更新理赔支付状态
router.patch('/:id/payment', jwtAuth, checkPermission('claim', 'update'),
claimController.updateClaimPayment
);
// 获取理赔统计
router.get('/stats/overview', jwtAuth, checkPermission('claim', 'read'),
claimController.getClaimStats
);
module.exports = router;

View File

@@ -0,0 +1,41 @@
const express = require('express');
const router = express.Router();
const insuranceController = require('../controllers/insuranceController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取保险申请列表
router.get('/applications', jwtAuth, checkPermission('insurance', 'read'),
insuranceController.getApplications
);
// 创建保险申请
router.post('/applications', jwtAuth, checkPermission('insurance', 'create'),
insuranceController.createApplication
);
// 获取单个保险申请详情
router.get('/applications/:id', jwtAuth, checkPermission('insurance', 'read'),
insuranceController.getApplicationById
);
// 更新保险申请
router.put('/applications/:id', jwtAuth, checkPermission('insurance', 'update'),
insuranceController.updateApplication
);
// 审核保险申请
router.patch('/applications/:id/review', jwtAuth, checkPermission('insurance', 'review'),
insuranceController.reviewApplication
);
// 删除保险申请
router.delete('/applications/:id', jwtAuth, checkPermission('insurance', 'delete'),
insuranceController.deleteApplication
);
// 获取保险申请统计
router.get('/applications-stats', jwtAuth, checkPermission('insurance', 'read'),
insuranceController.getApplicationStats
);
module.exports = router;

View File

@@ -0,0 +1,36 @@
const express = require('express');
const router = express.Router();
const insuranceTypeController = require('../controllers/insuranceTypeController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取保险类型列表
router.get('/', jwtAuth, checkPermission('insurance_type', 'read'),
insuranceTypeController.getInsuranceTypes
);
// 获取单个保险类型详情
router.get('/:id', jwtAuth, checkPermission('insurance_type', 'read'),
insuranceTypeController.getInsuranceTypeById
);
// 创建保险类型
router.post('/', jwtAuth, checkPermission('insurance_type', 'create'),
insuranceTypeController.createInsuranceType
);
// 更新保险类型
router.put('/:id', jwtAuth, checkPermission('insurance_type', 'update'),
insuranceTypeController.updateInsuranceType
);
// 删除保险类型
router.delete('/:id', jwtAuth, checkPermission('insurance_type', 'delete'),
insuranceTypeController.deleteInsuranceType
);
// 更新保险类型状态
router.patch('/:id/status', jwtAuth, checkPermission('insurance_type', 'update'),
insuranceTypeController.updateInsuranceTypeStatus
);
module.exports = router;

View File

@@ -0,0 +1,36 @@
const express = require('express');
const router = express.Router();
const policyController = require('../controllers/policyController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取保单列表
router.get('/', jwtAuth, checkPermission('policy', 'read'),
policyController.getPolicies
);
// 创建保单
router.post('/', jwtAuth, checkPermission('policy', 'create'),
policyController.createPolicy
);
// 获取单个保单详情
router.get('/:id', jwtAuth, checkPermission('policy', 'read'),
policyController.getPolicyById
);
// 更新保单
router.put('/:id', jwtAuth, checkPermission('policy', 'update'),
policyController.updatePolicy
);
// 更新保单状态
router.patch('/:id/status', jwtAuth, checkPermission('policy', 'update'),
policyController.updatePolicyStatus
);
// 获取保单统计
router.get('/stats/overview', jwtAuth, checkPermission('policy', 'read'),
policyController.getPolicyStats
);
module.exports = router;

View File

@@ -0,0 +1,24 @@
const express = require('express');
const router = express.Router();
const systemController = require('../controllers/systemController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取系统统计信息(需要管理员权限)
router.get('/stats', jwtAuth, checkPermission('system', 'read'), systemController.getSystemStats);
// 获取系统日志
router.get('/logs', jwtAuth, checkPermission('system', 'read'), systemController.getSystemLogs);
// 获取系统配置
router.get('/config', jwtAuth, checkPermission('system', 'read'), systemController.getSystemConfig);
// 更新系统配置
router.put('/config', jwtAuth, checkPermission('system', 'update'), systemController.updateSystemConfig);
// 备份数据库
router.post('/backup', jwtAuth, checkPermission('system', 'admin'), systemController.backupDatabase);
// 恢复数据库
router.post('/restore', jwtAuth, checkPermission('system', 'admin'), systemController.restoreDatabase);
module.exports = router;

View File

@@ -0,0 +1,24 @@
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const { jwtAuth, checkPermission } = require('../middleware/auth');
// 获取用户列表(需要管理员权限)
router.get('/', jwtAuth, checkPermission('user', 'read'), userController.getUsers);
// 获取单个用户信息
router.get('/:id', jwtAuth, checkPermission('user', 'read'), userController.getUser);
// 创建用户(需要管理员权限)
router.post('/', jwtAuth, checkPermission('user', 'create'), userController.createUser);
// 更新用户信息
router.put('/:id', jwtAuth, checkPermission('user', 'update'), userController.updateUser);
// 删除用户(需要管理员权限)
router.delete('/:id', jwtAuth, checkPermission('user', 'delete'), userController.deleteUser);
// 更新用户状态
router.patch('/:id/status', jwtAuth, checkPermission('user', 'update'), userController.updateUserStatus);
module.exports = router;

View File

@@ -0,0 +1,250 @@
-- 保险端口系统数据库表结构
-- 创建时间: 2025-01-01
-- 数据库: MySQL 8.0+
-- 创建数据库
CREATE DATABASE IF NOT EXISTS insurance_data
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE insurance_data;
-- 1. 角色表
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='角色表';
-- 2. 用户表
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='用户表';
-- 3. 保险类型表
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_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_insurance_type_name (name),
INDEX idx_insurance_type_status (status),
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险类型表';
-- 4. 保险申请表
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',
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 '审核日期',
documents JSON NULL COMMENT '申请文档',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_applications_application_no (application_no),
INDEX idx_applications_customer_id_card (customer_id_card),
INDEX idx_applications_customer_phone (customer_phone),
INDEX idx_applications_status (status),
INDEX idx_applications_application_date (application_date),
INDEX idx_applications_insurance_type_id (insurance_type_id),
INDEX idx_applications_reviewer_id (reviewer_id),
FOREIGN KEY (insurance_type_id) REFERENCES insurance_types(id) ON DELETE RESTRICT,
FOREIGN KEY (reviewer_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险申请表';
-- 5. 保单表
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',
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 '支付日期',
policy_document_url VARCHAR(500) NULL COMMENT '保单文件URL',
terms_and_conditions TEXT NULL COMMENT '条款和条件',
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX idx_policy_no (policy_no),
INDEX idx_policy_customer (customer_id),
INDEX idx_policy_application (application_id),
INDEX idx_policy_status (policy_status),
INDEX idx_policy_payment_status (payment_status),
INDEX idx_policy_dates (start_date, end_date),
FOREIGN KEY (application_id) REFERENCES insurance_applications(id) ON DELETE RESTRICT,
FOREIGN KEY (insurance_type_id) REFERENCES insurance_types(id) ON DELETE RESTRICT,
FOREIGN KEY (customer_id) REFERENCES users(id) ON DELETE RESTRICT,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保单表';
-- 6. 理赔表
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 '支付日期',
supporting_documents JSON NULL COMMENT '支持文件',
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX idx_claim_no (claim_no),
INDEX idx_claim_policy (policy_id),
INDEX idx_claim_customer (customer_id),
INDEX idx_claim_status (claim_status),
INDEX idx_claim_date (claim_date),
FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE RESTRICT,
FOREIGN KEY (customer_id) REFERENCES users(id) ON DELETE RESTRICT,
FOREIGN KEY (reviewer_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='理赔表';
-- 7. 系统配置表
CREATE TABLE IF NOT EXISTS system_configs (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID',
config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键',
config_value JSON NOT NULL COMMENT '配置值',
description VARCHAR(255) NULL COMMENT '配置描述',
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_system_config_key (config_key),
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表';
-- 8. 系统日志表
CREATE TABLE IF NOT EXISTS system_logs (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID',
level ENUM('info', 'warning', 'error', 'debug') NOT NULL DEFAULT 'info' COMMENT '日志级别',
message TEXT NOT NULL COMMENT '日志消息',
module VARCHAR(100) NOT NULL COMMENT '模块名称',
user_id INT NULL COMMENT '用户ID',
ip_address VARCHAR(45) NULL COMMENT 'IP地址',
user_agent TEXT NULL COMMENT '用户代理',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_system_logs_level (level),
INDEX idx_system_logs_module (module),
INDEX idx_system_logs_user_id (user_id),
INDEX idx_system_logs_created_at (created_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统日志表';
-- 插入初始数据
-- 插入默认角色
INSERT INTO roles (name, description, permissions, status) VALUES
('admin', '系统管理员', '["user:read","user:create","user:update","user:delete","insurance:read","insurance:create","insurance:update","insurance:delete","insurance:review","policy:read","policy:create","policy:update","policy:delete","claim:read","claim:create","claim:update","claim:review","system:read","system:update","system:admin"]', 'active'),
('agent', '保险代理人', '["insurance:read","insurance:create","policy:read","policy:create","claim:read","claim:create"]', 'active'),
('customer', '客户', '["insurance:read","policy:read","claim:read","claim:create"]', 'active');
-- 插入默认保险类型
INSERT INTO insurance_types (name, description, coverage_amount_min, coverage_amount_max, premium_rate, status) VALUES
('人寿保险', '提供生命保障的保险产品', 10000.00, 1000000.00, 0.0025, 'active'),
('健康保险', '提供医疗保障的保险产品', 5000.00, 500000.00, 0.0030, 'active'),
('意外伤害保险', '提供意外伤害保障的保险产品', 1000.00, 200000.00, 0.0015, 'active'),
('财产保险', '提供财产保障的保险产品', 5000.00, 1000000.00, 0.0020, 'active'),
('车险', '提供车辆保障的保险产品', 1000.00, 500000.00, 0.0040, 'active');
-- 插入默认系统配置
INSERT INTO system_configs (config_key, config_value, description) VALUES
('system_name', '"保险端口系统"', '系统名称'),
('company_name', '"XX保险公司"', '公司名称'),
('contact_email', '"support@insurance.com"', '联系邮箱'),
('contact_phone', '"400-123-4567"', '联系电话'),
('max_file_size', '10485760', '最大文件上传大小(字节)'),
('allowed_file_types', '["jpg", "jpeg", "png", "pdf", "doc", "docx"]', '允许上传的文件类型');
-- 创建管理员用户(密码需要在前端加密后存储)
-- 默认密码: admin123 (需要在前端使用bcrypt加密)
INSERT INTO users (username, password, real_name, email, phone, role_id, status) VALUES
('admin', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '系统管理员', 'admin@insurance.com', '13800138000', 1, 'active');
COMMIT;
-- 显示表结构信息
SHOW TABLES;
-- 显示各表注释
SELECT
TABLE_NAME,
TABLE_COMMENT
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_SCHEMA = 'nxxmdata';
-- 显示表创建语句示例
SHOW CREATE TABLE users;

View File

@@ -0,0 +1,113 @@
const { sequelize } = require('../config/database');
const { User, Role, InsuranceApplication, InsuranceType, Policy, Claim } = require('../models');
async function initDatabase() {
try {
console.log('🔄 开始初始化数据库...');
// 测试数据库连接
console.log('🔗 测试数据库连接...');
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 先删除所有表(如果存在)
console.log('🗑️ 清理现有表结构...');
// 禁用外键约束
await sequelize.query('SET FOREIGN_KEY_CHECKS = 0');
await Claim.drop();
await Policy.drop();
await InsuranceApplication.drop();
await InsuranceType.drop();
await User.drop();
await Role.drop();
// 启用外键约束
await sequelize.query('SET FOREIGN_KEY_CHECKS = 1');
console.log('✅ 现有表结构清理完成');
// 按顺序创建表
console.log('🗄️ 创建角色表...');
await Role.sync();
console.log('🗄️ 创建用户表...');
await User.sync();
console.log('🗄️ 创建保险类型表...');
await InsuranceType.sync();
console.log('🗄️ 创建保险申请表...');
await InsuranceApplication.sync();
console.log('🗄️ 创建保单表...');
await Policy.sync();
console.log('🗄️ 创建理赔表...');
await Claim.sync();
console.log('✅ 所有表创建完成');
// 创建默认角色
console.log('👥 创建默认角色...');
const roles = await Role.bulkCreate([
{ name: 'admin', description: '系统管理员', permissions: JSON.stringify(['*']) },
{ name: 'agent', description: '保险代理人', permissions: JSON.stringify(['insurance:read', 'insurance:create', 'policy:read']) },
{ name: 'customer', description: '客户', permissions: JSON.stringify(['insurance:create', 'policy:read:self', 'claim:create:self']) },
{ name: 'reviewer', description: '审核员', permissions: JSON.stringify(['insurance:review', 'claim:review']) }
]);
console.log('✅ 默认角色创建完成');
// 创建默认保险类型
console.log('📋 创建默认保险类型...');
const insuranceTypes = await InsuranceType.bulkCreate([
{
name: '人寿保险',
description: '提供生命保障的保险产品',
coverage_amount_min: 10000,
coverage_amount_max: 1000000,
premium_rate: 0.0025,
status: 'active'
},
{
name: '健康保险',
description: '提供医疗保障的保险产品',
coverage_amount_min: 5000,
coverage_amount_max: 500000,
premium_rate: 0.003,
status: 'active'
},
{
name: '财产保险',
description: '提供财产保障的保险产品',
coverage_amount_min: 1000,
coverage_amount_max: 2000000,
premium_rate: 0.0015,
status: 'active'
},
{
name: '车险',
description: '提供车辆保障的保险产品',
coverage_amount_min: 500,
coverage_amount_max: 1000000,
premium_rate: 0.002,
status: 'active'
}
]);
console.log('✅ 默认保险类型创建完成');
console.log('🎉 数据库初始化完成!');
} catch (error) {
console.error('❌ 数据库初始化失败:', error);
process.exit(1);
} finally {
await sequelize.close();
}
}
// 如果是直接运行此脚本
if (require.main === module) {
initDatabase();
}
module.exports = initDatabase;

View File

@@ -0,0 +1,37 @@
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- 更新默认角色权限
UPDATE roles SET
description = '系统管理员',
permissions = '["user:read","user:create","user:update","user:delete","insurance:read","insurance:create","insurance:update","insurance:delete","insurance:review","policy:read","policy:create","policy:update","policy:delete","claim:read","claim:create","claim:update","claim:review","system:read","system:update","system:admin"]'
WHERE name = 'admin';
-- 插入其他角色(如果不存在)
INSERT IGNORE INTO roles (name, description, permissions, status) VALUES
('agent', '保险代理人', '["insurance:read","insurance:create","policy:read","policy:create","claim:read","claim:create"]', 'active'),
('customer', '客户', '["insurance:read","policy:read","claim:read","claim:create"]', 'active');
-- 插入默认保险类型
INSERT IGNORE INTO insurance_types (name, description, coverage_amount_min, coverage_amount_max, premium_rate, status) VALUES
('人寿保险', '提供生命保障的保险产品', 10000.00, 1000000.00, 0.0025, 'active'),
('健康保险', '提供医疗保障的保险产品', 5000.00, 500000.00, 0.0030, 'active'),
('意外伤害保险', '提供意外伤害保障的保险产品', 1000.00, 200000.00, 0.0015, 'active'),
('财产保险', '提供财产保障的保险产品', 5000.00, 1000000.00, 0.0020, 'active'),
('车险', '提供车辆保障的保险产品', 1000.00, 500000.00, 0.0040, 'active');
-- 插入默认系统配置
INSERT IGNORE INTO system_configs (config_key, config_value, description) VALUES
('system_name', '"保险端口系统"', '系统名称'),
('company_name', '"XX保险公司"', '公司名称'),
('contact_email', '"support@insurance.com"', '联系邮箱'),
('contact_phone', '"400-123-4567"', '联系电话'),
('max_file_size', '10485760', '最大文件上传大小(字节)'),
('allowed_file_types', '["jpg", "jpeg", "png", "pdf", "doc", "docx"]', '允许上传的文件类型');
-- 创建管理员用户(密码需要在前端加密后存储)
-- 默认密码: admin123 (需要在前端使用bcrypt加密)
INSERT IGNORE INTO users (username, password, real_name, email, phone, role_id, status) VALUES
('admin', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '系统管理员', 'admin@insurance.com', '13800138000', 1, 'active');
COMMIT;

View File

@@ -0,0 +1,34 @@
-- 更新默认角色权限
UPDATE roles SET
description = 'Admin',
permissions = '["user:read","user:create","user:update","user:delete","insurance:read","insurance:create","insurance:update","insurance:delete","insurance:review","policy:read","policy:create","policy:update","policy:delete","claim:read","claim:create","claim:update","claim:review","system:read","system:update","system:admin"]'
WHERE name = 'admin';
-- 插入其他角色(如果不存在)
INSERT IGNORE INTO roles (name, description, permissions, status) VALUES
('agent', 'Agent', '["insurance:read","insurance:create","policy:read","policy:create","claim:read","claim:create"]', 'active'),
('customer', 'Customer', '["insurance:read","policy:read","claim:read","claim:create"]', 'active');
-- 插入默认保险类型
INSERT INTO insurance_types (name, description, coverage_amount_min, coverage_amount_max, premium_rate, status) VALUES
('人寿保险', '提供生命保障的保险产品', 10000.00, 1000000.00, 0.0025, 'active'),
('健康保险', '提供医疗保障的保险产品', 5000.00, 500000.00, 0.0030, 'active'),
('意外伤害保险', '提供意外伤害保障的保险产品', 1000.00, 200000.00, 0.0015, 'active'),
('财产保险', '提供财产保障的保险产品', 5000.00, 1000000.00, 0.0020, 'active'),
('车险', '提供车辆保障的保险产品', 1000.00, 500000.00, 0.0040, 'active');
-- 插入默认系统配置
INSERT INTO system_configs (config_key, config_value, description) VALUES
('system_name', '"保险端口系统"', '系统名称'),
('company_name', '"XX保险公司"', '公司名称'),
('contact_email', '"support@insurance.com"', '联系邮箱'),
('contact_phone', '"400-123-4567"', '联系电话'),
('max_file_size', '10485760', '最大文件上传大小(字节)'),
('allowed_file_types', '["jpg", "jpeg", "png", "pdf", "doc", "docx"]', '允许上传的文件类型');
-- 创建管理员用户(密码需要在前端加密后存储)
-- 默认密码: admin123 (需要在前端使用bcrypt加密)
INSERT INTO users (username, password, real_name, email, phone, role_id, status) VALUES
('admin', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '系统管理员', 'admin@insurance.com', '13800138000', 1, 'active');
COMMIT;

View File

@@ -0,0 +1,81 @@
const { sequelize } = require('../config/database');
const { User, Role, InsuranceApplication, InsuranceType, Policy, Claim } = require('../models');
async function migrate() {
try {
console.log('🔄 开始数据库迁移...');
// 测试数据库连接
console.log('🔗 测试数据库连接...');
await sequelize.authenticate();
console.log('✅ 数据库连接成功');
// 同步所有模型到数据库(安全模式)
console.log('🗄️ 同步数据库表结构...');
await sequelize.sync({ force: false, alter: false });
console.log('✅ 数据库表结构同步完成');
// 创建默认角色
console.log('👥 创建默认角色...');
const roles = await Role.bulkCreate([
{ name: 'admin', description: '系统管理员', permissions: JSON.stringify(['*']) },
{ name: 'agent', description: '保险代理人', permissions: JSON.stringify(['insurance:read', 'insurance:create', 'policy:read']) },
{ name: 'customer', description: '客户', permissions: JSON.stringify(['insurance:create', 'policy:read:self', 'claim:create:self']) },
{ name: 'reviewer', description: '审核员', permissions: JSON.stringify(['insurance:review', 'claim:review']) }
], { ignoreDuplicates: true });
console.log('✅ 默认角色创建完成');
// 创建默认保险类型
console.log('📋 创建默认保险类型...');
const insuranceTypes = await InsuranceType.bulkCreate([
{
name: '人寿保险',
description: '提供生命保障的保险产品',
coverage_amount_min: 10000,
coverage_amount_max: 1000000,
premium_rate: 0.0025,
status: 'active'
},
{
name: '健康保险',
description: '提供医疗保障的保险产品',
coverage_amount_min: 5000,
coverage_amount_max: 500000,
premium_rate: 0.003,
status: 'active'
},
{
name: '财产保险',
description: '提供财产保障的保险产品',
coverage_amount_min: 1000,
coverage_amount_max: 2000000,
premium_rate: 0.0015,
status: 'active'
},
{
name: '车险',
description: '提供车辆保障的保险产品',
coverage_amount_min: 500,
coverage_amount_max: 1000000,
premium_rate: 0.002,
status: 'active'
}
], { ignoreDuplicates: true });
console.log('✅ 默认保险类型创建完成');
console.log('🎉 数据库迁移完成!');
} catch (error) {
console.error('❌ 数据库迁移失败:', error);
process.exit(1);
} finally {
await sequelize.close();
}
}
// 如果是直接运行此脚本
if (require.main === module) {
migrate();
}
module.exports = migrate;

View File

@@ -0,0 +1,215 @@
-- 保险端口系统数据库表结构(仅表结构)- 添加insurance_前缀避免冲突
-- 创建时间: 2025-01-01
-- 数据库: MySQL 8.0+
-- 使用现有数据库
USE nxxmdata;
-- 1. 保险角色表添加insurance_前缀避免与现有roles表冲突
CREATE TABLE IF NOT EXISTS insurance_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='角色表';
-- 2. 保险用户表添加insurance_前缀避免与现有users表冲突
CREATE TABLE IF NOT EXISTS insurance_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 insurance_roles(id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险用户表';
-- 3. 保险类型表
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_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_insurance_type_name (name),
INDEX idx_insurance_type_status (status),
FOREIGN KEY (created_by) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险类型表';
-- 4. 保险申请表
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',
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 '审核日期',
documents JSON NULL COMMENT '申请文档',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_applications_application_no (application_no),
INDEX idx_applications_customer_id_card (customer_id_card),
INDEX idx_applications_customer_phone (customer_phone),
INDEX idx_applications_status (status),
INDEX idx_applications_application_date (application_date),
INDEX idx_applications_insurance_type_id (insurance_type_id),
INDEX idx_applications_reviewer_id (reviewer_id),
FOREIGN KEY (insurance_type_id) REFERENCES insurance_types(id) ON DELETE RESTRICT,
FOREIGN KEY (reviewer_id) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险申请表';
-- 5. 保单表
CREATE TABLE IF NOT EXISTS insurance_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',
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 '支付日期',
policy_document_url VARCHAR(500) NULL COMMENT '保单文件URL',
terms_and_conditions TEXT NULL COMMENT '条款和条件',
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX idx_policy_no (policy_no),
INDEX idx_policy_customer (customer_id),
INDEX idx_policy_application (application_id),
INDEX idx_policy_status (policy_status),
INDEX idx_policy_payment_status (payment_status),
INDEX idx_policy_dates (start_date, end_date),
FOREIGN KEY (application_id) REFERENCES insurance_applications(id) ON DELETE RESTRICT,
FOREIGN KEY (insurance_type_id) REFERENCES insurance_types(id) ON DELETE RESTRICT,
FOREIGN KEY (customer_id) REFERENCES insurance_users(id) ON DELETE RESTRICT,
FOREIGN KEY (created_by) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保单表';
-- 6. 理赔表
CREATE TABLE IF NOT EXISTS insurance_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 '支付日期',
supporting_documents JSON NULL COMMENT '支持文件',
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX idx_claim_no (claim_no),
INDEX idx_claim_policy (policy_id),
INDEX idx_claim_customer (customer_id),
INDEX idx_claim_status (claim_status),
INDEX idx_claim_date (claim_date),
FOREIGN KEY (policy_id) REFERENCES insurance_policies(id) ON DELETE RESTRICT,
FOREIGN KEY (customer_id) REFERENCES insurance_users(id) ON DELETE RESTRICT,
FOREIGN KEY (reviewer_id) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='理赔表';
-- 7. 保险系统配置表添加insurance_前缀避免与现有system_configs表冲突
CREATE TABLE IF NOT EXISTS insurance_system_configs (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID',
config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键',
config_value JSON NOT NULL COMMENT '配置值',
description VARCHAR(255) NULL COMMENT '配置描述',
created_by INT NULL COMMENT '创建人ID',
updated_by INT NULL COMMENT '更新人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_system_config_key (config_key),
FOREIGN KEY (created_by) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险系统配置表';
-- 8. 保险系统日志表
CREATE TABLE IF NOT EXISTS insurance_system_logs (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID',
level ENUM('info', 'warning', 'error', 'debug') NOT NULL DEFAULT 'info' COMMENT '日志级别',
message TEXT NOT NULL COMMENT '日志消息',
module VARCHAR(100) NOT NULL COMMENT '模块名称',
user_id INT NULL COMMENT '用户ID',
ip_address VARCHAR(45) NULL COMMENT 'IP地址',
user_agent TEXT NULL COMMENT '用户代理',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_system_logs_level (level),
INDEX idx_system_logs_module (module),
INDEX idx_system_logs_user_id (user_id),
INDEX idx_system_logs_created_at (created_at),
FOREIGN KEY (user_id) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保险系统日志表';
COMMIT;
-- 显示表结构信息
SHOW TABLES LIKE 'insurance_%';
-- 显示各保险相关表注释
SELECT
TABLE_NAME,
TABLE_COMMENT
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_SCHEMA = 'nxxmdata'
AND TABLE_NAME LIKE 'insurance_%';

View File

@@ -0,0 +1,202 @@
-- Insurance System Database Schema with insurance_ prefix
-- Created: 2025-01-01
-- Database: MySQL 5.7+
USE nxxmdata;
-- 1. Insurance roles table
CREATE TABLE IF NOT EXISTS insurance_roles (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255) NULL,
permissions JSON NOT NULL,
status ENUM('active', 'inactive') NOT NULL DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_roles_name (name),
INDEX idx_roles_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 2. Insurance users table
CREATE TABLE IF NOT EXISTS insurance_users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
real_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20) NOT NULL,
role_id INT NOT NULL,
status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active',
last_login TIMESTAMP NULL,
avatar VARCHAR(255) NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
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 insurance_roles(id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 3. Insurance types table
CREATE TABLE IF NOT EXISTS insurance_types (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT NULL,
coverage_amount_min DECIMAL(15,2) NOT NULL DEFAULT 0.00,
coverage_amount_max DECIMAL(15,2) NOT NULL DEFAULT 1000000.00,
premium_rate DECIMAL(5,4) NOT NULL DEFAULT 0.001,
status ENUM('active', 'inactive') NOT NULL DEFAULT 'active',
created_by INT NULL,
updated_by INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_insurance_type_name (name),
INDEX idx_insurance_type_status (status),
FOREIGN KEY (created_by) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 4. Insurance applications table
CREATE TABLE IF NOT EXISTS insurance_applications (
id INT AUTO_INCREMENT PRIMARY KEY,
application_no VARCHAR(50) NOT NULL UNIQUE,
customer_name VARCHAR(100) NOT NULL,
customer_id_card VARCHAR(18) NOT NULL,
customer_phone VARCHAR(20) NOT NULL,
customer_address VARCHAR(255) NOT NULL,
insurance_type_id INT NOT NULL,
application_amount DECIMAL(15,2) NOT NULL,
status ENUM('pending', 'approved', 'rejected', 'under_review') NOT NULL DEFAULT 'pending',
application_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
review_notes TEXT NULL,
reviewer_id INT NULL,
review_date TIMESTAMP NULL,
documents JSON NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_applications_application_no (application_no),
INDEX idx_applications_customer_id_card (customer_id_card),
INDEX idx_applications_customer_phone (customer_phone),
INDEX idx_applications_status (status),
INDEX idx_applications_application_date (application_date),
INDEX idx_applications_insurance_type_id (insurance_type_id),
INDEX idx_applications_reviewer_id (reviewer_id),
FOREIGN KEY (insurance_type_id) REFERENCES insurance_types(id) ON DELETE RESTRICT,
FOREIGN KEY (reviewer_id) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 5. Insurance policies table
CREATE TABLE IF NOT EXISTS insurance_policies (
id INT AUTO_INCREMENT PRIMARY KEY,
policy_no VARCHAR(50) NOT NULL UNIQUE,
application_id INT NOT NULL,
insurance_type_id INT NOT NULL,
customer_id INT NOT NULL,
coverage_amount DECIMAL(15,2) NOT NULL,
premium_amount DECIMAL(15,2) NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
policy_status ENUM('active', 'expired', 'cancelled', 'suspended') NOT NULL DEFAULT 'active',
payment_status ENUM('paid', 'unpaid', 'partial') NOT NULL DEFAULT 'unpaid',
payment_date DATE NULL,
policy_document_url VARCHAR(500) NULL,
terms_and_conditions TEXT NULL,
created_by INT NULL,
updated_by INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX idx_policy_no (policy_no),
INDEX idx_policy_customer (customer_id),
INDEX idx_policy_application (application_id),
INDEX idx_policy_status (policy_status),
INDEX idx_policy_payment_status (payment_status),
INDEX idx_policy_dates (start_date, end_date),
FOREIGN KEY (application_id) REFERENCES insurance_applications(id) ON DELETE RESTRICT,
FOREIGN KEY (insurance_type_id) REFERENCES insurance_types(id) ON DELETE RESTRICT,
FOREIGN KEY (customer_id) REFERENCES insurance_users(id) ON DELETE RESTRICT,
FOREIGN KEY (created_by) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 6. Insurance claims table
CREATE TABLE IF NOT EXISTS insurance_claims (
id INT AUTO_INCREMENT PRIMARY KEY,
claim_no VARCHAR(50) NOT NULL UNIQUE,
policy_id INT NOT NULL,
customer_id INT NOT NULL,
claim_amount DECIMAL(15,2) NOT NULL,
claim_date DATE NOT NULL,
incident_description TEXT NOT NULL,
claim_status ENUM('pending', 'approved', 'rejected', 'processing', 'paid') NOT NULL DEFAULT 'pending',
review_notes TEXT NULL,
reviewer_id INT NULL,
review_date DATE NULL,
payment_date DATE NULL,
supporting_documents JSON NULL,
created_by INT NULL,
updated_by INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX idx_claim_no (claim_no),
INDEX idx_claim_policy (policy_id),
INDEX idx_claim_customer (customer_id),
INDEX idx_claim_status (claim_status),
INDEX idx_claim_date (claim_date),
FOREIGN KEY (policy_id) REFERENCES insurance_policies(id) ON DELETE RESTRICT,
FOREIGN KEY (customer_id) REFERENCES insurance_users(id) ON DELETE RESTRICT,
FOREIGN KEY (reviewer_id) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 7. Insurance system configs table
CREATE TABLE IF NOT EXISTS insurance_system_configs (
id INT AUTO_INCREMENT PRIMARY KEY,
config_key VARCHAR(100) NOT NULL UNIQUE,
config_value JSON NOT NULL,
description VARCHAR(255) NULL,
created_by INT NULL,
updated_by INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_system_config_key (config_key),
FOREIGN KEY (created_by) REFERENCES insurance_users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 8. Insurance system logs table
CREATE TABLE IF NOT EXISTS insurance_system_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
level ENUM('info', 'warning', 'error', 'debug') NOT NULL DEFAULT 'info',
message TEXT NOT NULL,
module VARCHAR(100) NOT NULL,
user_id INT NULL,
ip_address VARCHAR(45) NULL,
user_agent TEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_system_logs_level (level),
INDEX idx_system_logs_module (module),
INDEX idx_system_logs_user_id (user_id),
INDEX idx_system_logs_created_at (created_at),
FOREIGN KEY (user_id) REFERENCES insurance_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Show all insurance tables
SHOW TABLES LIKE 'insurance_%';

View File

@@ -0,0 +1,147 @@
-- 保险端口系统测试数据脚本
-- 创建时间: 2025-01-01
-- 数据库: MySQL 8.0+
-- 使用现有数据库
USE nxxmdata;
-- 1. 插入更多角色数据
INSERT INTO roles (name, description, permissions, status) VALUES
('super_admin', '超级管理员', '["user:read","user:create","user:update","user:delete","insurance:read","insurance:create","insurance:update","insurance:delete","insurance:review","policy:read","policy:create","policy:update","policy:delete","claim:read","claim:create","claim:update","claim:review","system:read","system:update","system:admin"]', 1),
('finance', '财务人员', '["policy:read","claim:read","claim:review","claim:update"]', 1),
('auditor', '审计人员', '["insurance:read","policy:read","claim:read","system:read"]', 1);
-- 2. 插入更多用户数据密码都是123456的bcrypt加密
INSERT INTO users (username, password, real_name, email, phone, role_id, status, avatar) VALUES
('agent1', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '张三', 'zhangsan@insurance.com', '13800138001', 2, 'active', '/avatars/agent1.jpg'),
('agent2', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '李四', 'lisi@insurance.com', '13800138002', 2, 'active', '/avatars/agent2.jpg'),
('finance1', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '王五', 'wangwu@insurance.com', '13800138003', 4, 'active', '/avatars/finance1.jpg'),
('auditor1', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '赵六', 'zhaoliu@insurance.com', '13800138004', 5, 'active', '/avatars/auditor1.jpg'),
('customer1', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '陈小明', 'chenxiaoming@example.com', '13800138005', 3, 'active', '/avatars/customer1.jpg'),
('customer2', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '李小华', 'lixiaohua@example.com', '13800138006', 3, 'active', '/avatars/customer2.jpg'),
('customer3', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '王小红', 'wangxiaohong@example.com', '13800138007', 3, 'active', '/avatars/customer3.jpg'),
('customer4', '$2b$12$r6KcJ3V9q8wY7hLmNpQZ0uBf3dG7hJ2kL9mNpQr3tY6zX8vB1cF4', '刘小刚', 'liuxiaogang@example.com', '13800138008', 3, 'active', '/avatars/customer4.jpg');
-- 3. 插入更多保险类型数据
INSERT INTO insurance_types (name, description, coverage_amount_min, coverage_amount_max, premium_rate, status, created_by, updated_by) VALUES
('重疾保险', '提供重大疾病保障的保险产品', 50000.00, 500000.00, 0.0035, 'active', 1, 1),
('养老保险', '提供养老保障的保险产品', 10000.00, 300000.00, 0.0028, 'active', 1, 1),
('教育保险', '提供教育金保障的保险产品', 20000.00, 200000.00, 0.0022, 'active', 1, 1),
('旅游保险', '提供旅游期间保障的保险产品', 1000.00, 50000.00, 0.0050, 'active', 1, 1),
('家庭财产保险', '提供家庭财产保障的保险产品', 10000.00, 1000000.00, 0.0018, 'active', 1, 1);
-- 4. 插入保险申请数据
INSERT INTO insurance_applications (application_no, customer_name, customer_id_card, customer_phone, customer_address, insurance_type_id, application_amount, status, application_date, review_notes, reviewer_id, review_date, documents) VALUES
('APP2024010001', '陈小明', '110101199001011234', '13800138005', '北京市朝阳区建国路100号', 1, 500000.00, 'approved', '2024-01-15 09:30:00', '资料齐全,符合要求', 1, '2024-01-16 14:20:00', '["身份证复印件", "健康证明", "收入证明"]'),
('APP2024010002', '李小华', '110101199002022345', '13800138006', '上海市浦东新区陆家嘴金融中心', 2, 200000.00, 'approved', '2024-01-16 10:15:00', '客户健康状况良好', 1, '2024-01-17 11:30:00', '["身份证复印件", "体检报告"]'),
('APP2024010003', '王小红', '110101199003033456', '13800138007', '广州市天河区体育西路', 3, 100000.00, 'rejected', '2024-01-17 14:45:00', '高风险职业,不符合投保要求', 1, '2024-01-18 16:00:00', '["身份证复印件", "职业证明"]'),
('APP2024010004', '刘小刚', '110101199004044567', '13800138008', '深圳市南山区科技园', 4, 300000.00, 'pending', '2024-01-18 16:20:00', NULL, NULL, NULL, '["身份证复印件", "财产证明"]'),
('APP2024020001', '张大山', '110101198501015678', '13800138009', '杭州市西湖区文三路', 5, 150000.00, 'under_review', '2024-02-10 11:30:00', '需要补充医疗检查报告', 1, '2024-02-11 09:00:00', '["身份证复印件", "初步体检报告"]'),
('APP2024020002', '李小花', '110101198602026789', '13800138010', '南京市鼓楼区中山路', 6, 80000.00, 'approved', '2024-02-12 14:20:00', '资料完整,审批通过', 2, '2024-02-13 15:30:00', '["身份证复印件", "收入证明"]'),
('APP2024020003', '王大河', '110101198703037890', '13800138011', '武汉市江汉区解放大道', 7, 250000.00, 'approved', '2024-02-14 09:45:00', '优质客户,快速审批', 2, '2024-02-14 16:00:00', '["身份证复印件", "资产证明"]'),
('APP2024030001', '刘小海', '110101198804048901', '13800138012', '成都市锦江区春熙路', 8, 120000.00, 'pending', '2024-03-01 13:15:00', NULL, NULL, NULL, '["身份证复印件"]'),
('APP2024030002', '陈小云', '110101198905059012', '13800138013', '重庆市渝中区解放碑', 1, 400000.00, 'under_review', '2024-03-02 15:30:00', '需要核实收入情况', 1, '2024-03-03 10:00:00', '["身份证复印件", "银行流水"]'),
('APP2024030003', '李小山', '110101199006069123', '13800138014', '西安市雁塔区小寨', 2, 180000.00, 'approved', '2024-03-03 11:45:00', '审批通过,等待签约', 2, '2024-03-04 14:20:00', '["身份证复印件", "健康证明"]');
-- 5. 插入保单数据
INSERT INTO policies (policy_no, application_id, insurance_type_id, customer_id, coverage_amount, premium_amount, start_date, end_date, policy_status, payment_status, payment_date, policy_document_url, terms_and_conditions, created_by, updated_by) VALUES
('POL2024010001', 1, 1, 5, 500000.00, 1250.00, '2024-01-17', '2034-01-16', 'active', 'paid', '2024-01-17', '/policies/POL2024010001.pdf', '保险期限10年包含重大疾病保障', 1, 1),
('POL2024010002', 2, 2, 6, 200000.00, 600.00, '2024-01-18', '2029-01-17', 'active', 'paid', '2024-01-18', '/policies/POL2024010002.pdf', '保险期限5年健康医疗保障', 1, 1),
('POL2024020001', 6, 6, 7, 80000.00, 176.00, '2024-02-14', '2029-02-13', 'active', 'paid', '2024-02-14', '/policies/POL2024020001.pdf', '保险期限5年教育金保障', 2, 2),
('POL2024020002', 7, 7, 8, 250000.00, 1000.00, '2024-02-15', '2025-02-14', 'active', 'paid', '2024-02-15', '/policies/POL2024020002.pdf', '保险期限1年旅游意外保障', 2, 2),
('POL2024030001', 10, 2, 9, 180000.00, 504.00, '2024-03-05', '2029-03-04', 'active', 'unpaid', NULL, '/policies/POL2024030001.pdf', '保险期限5年健康医疗保障', 2, 2),
('POL2024030002', 3, 3, 10, 100000.00, 150.00, '2024-03-06', '2025-03-05', 'active', 'paid', '2024-03-06', '/policies/POL2024030002.pdf', '保险期限1年意外伤害保障', 1, 1),
('POL2024030003', 4, 4, 11, 300000.00, 600.00, '2024-03-07', '2029-03-06', 'active', 'partial', '2024-03-07', '/policies/POL2024030003.pdf', '保险期限5年财产保障', 1, 1),
('POL2024030004', 5, 5, 12, 150000.00, 525.00, '2024-03-08', '2025-03-07', 'active', 'paid', '2024-03-08', '/policies/POL2024030004.pdf', '保险期限1年车险保障', 2, 2);
-- 6. 插入理赔数据
INSERT INTO claims (claim_no, policy_id, customer_id, claim_amount, claim_date, incident_description, claim_status, review_notes, reviewer_id, review_date, payment_date, supporting_documents, created_by, updated_by) VALUES
('CLM2024020001', 3, 7, 50000.00, '2024-02-20', '被保险人因意外事故导致医疗费用支出', 'paid', '医疗费用单据齐全,符合理赔条件', 4, '2024-02-21', '2024-02-22', '["医疗费用发票", "诊断证明", "事故报告"]', 7, 4),
('CLM2024030001', 1, 5, 200000.00, '2024-03-10', '被保险人确诊重大疾病,需要手术治疗', 'processing', '医疗诊断明确,正在核实医疗费用', 4, '2024-03-11', NULL, '["诊断证明", "医疗费用预估"]', 5, 4),
('CLM2024030002', 2, 6, 8000.00, '2024-03-15', '门诊医疗费用报销', 'approved', '门诊费用符合保险条款', 4, '2024-03-16', '2024-03-17', '["门诊发票", "处方单"]', 6, 4),
('CLM2024030003', 4, 8, 15000.00, '2024-03-20', '旅行期间行李丢失赔偿', 'pending', '等待警方报案证明', NULL, NULL, NULL, '["行李清单", "酒店证明"]', 8, NULL),
('CLM2024030004', 6, 10, 30000.00, '2024-03-25', '意外伤害导致伤残赔偿', 'rejected', '伤残等级不符合理赔标准', 4, '2024-03-26', NULL, '["伤残鉴定报告", "医疗记录"]', 10, 4),
('CLM2024040001', 5, 9, 5000.00, '2024-04-01', '疾病门诊医疗费用', 'paid', '门诊医疗费用合理', 4, '2024-04-02', '2024-04-03', '["门诊发票", "诊断证明"]', 9, 4),
('CLM2024040002', 7, 11, 80000.00, '2024-04-05', '家庭财产火灾损失赔偿', 'processing', '正在评估财产损失价值', 4, '2024-04-06', NULL, '["火灾报告", "财产清单"]', 11, 4),
('CLM2024040003', 8, 12, 20000.00, '2024-04-10', '车辆事故维修费用', 'approved', '车辆维修费用合理', 4, '2024-04-11', '2024-04-12', '["事故报告", "维修报价单"]', 12, 4);
-- 7. 插入更多系统配置数据
INSERT INTO system_configs (config_key, config_value, description, created_by, updated_by) VALUES
('auto_approval_threshold', '100000', '自动审批金额阈值', 1, 1),
('max_application_per_day', '10', '每日最大申请数量', 1, 1),
('claim_processing_days', '7', '理赔处理最大天数', 1, 1),
('premium_due_reminder_days', '15', '保费到期提醒天数', 1, 1),
('policy_renewal_notice_days', '30', '保单续保通知天数', 1, 1),
('default_currency', '"CNY"', '默认货币', 1, 1),
('tax_rate', '0.06', '税率', 1, 1),
('interest_rate', '0.035', '默认利率', 1, 1);
-- 8. 插入系统日志数据
INSERT INTO system_logs (level, message, module, user_id, ip_address, user_agent, created_at) VALUES
('info', '用户登录成功', 'auth', 1, '192.168.1.100', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', '2024-01-15 09:00:00'),
('info', '创建新保险申请: APP2024010001', 'application', 1, '192.168.1.100', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', '2024-01-15 09:30:00'),
('info', '审批保险申请: APP2024010001', 'review', 1, '192.168.1.101', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', '2024-01-16 14:20:00'),
('info', '生成保单: POL2024010001', 'policy', 1, '192.168.1.101', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', '2024-01-17 10:15:00'),
('warning', '保险申请被拒绝: APP2024010003', 'review', 1, '192.168.1.102', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', '2024-01-18 16:00:00'),
('error', '数据库连接超时', 'system', NULL, '192.168.1.103', NULL, '2024-01-19 03:30:00'),
('info', '理赔申请提交: CLM2024020001', 'claim', 7, '192.168.1.104', 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15', '2024-02-20 15:45:00'),
('info', '理赔审核通过: CLM2024020001', 'claim', 4, '192.168.1.105', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', '2024-02-21 11:20:00'),
('info', '理赔支付完成: CLM2024020001', 'finance', 4, '192.168.1.106', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', '2024-02-22 09:30:00'),
('info', '用户注册成功: customer5', 'auth', 8, '192.168.1.107', 'Mozilla/5.0 (Android 10; Mobile; rv:91.0) Gecko/91.0 Firefox/91.0', '2024-03-01 14:15:00');
COMMIT;
-- 显示测试数据统计信息
SELECT
'用户数量' as category,
COUNT(*) as count
FROM
users
UNION ALL
SELECT
'保险申请数量',
COUNT(*)
FROM
insurance_applications
UNION ALL
SELECT
'保单数量',
COUNT(*)
FROM
policies
UNION ALL
SELECT
'理赔数量',
COUNT(*)
FROM
claims
UNION ALL
SELECT
'系统日志数量',
COUNT(*)
FROM
system_logs;
-- 显示各状态保险申请统计
SELECT
status,
COUNT(*) as count,
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM insurance_applications), 2) as percentage
FROM
insurance_applications
GROUP BY
status
ORDER BY
count DESC;
-- 显示各状态理赔统计
SELECT
claim_status,
COUNT(*) as count,
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM claims), 2) as percentage
FROM
claims
GROUP BY
claim_status
ORDER BY
count DESC;

View File

@@ -0,0 +1,116 @@
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('../config/swagger');
const { sequelize, testConnection } = require('../config/database');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// 安全中间件
app.use(helmet());
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true
}));
// 速率限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 限制每个IP每15分钟最多100个请求
message: '请求过于频繁,请稍后再试'
});
app.use(limiter);
// 解析请求体
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 静态文件服务
app.use('/uploads', express.static('uploads'));
// 健康检查路由
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// API路由
app.use('/api/auth', require('../routes/auth'));
app.use('/api/users', require('../routes/users'));
app.use('/api/insurance', require('../routes/insurance'));
app.use('/api/insurance-types', require('../routes/insuranceTypes'));
app.use('/api/policies', require('../routes/policies'));
app.use('/api/claims', require('../routes/claims'));
app.use('/api/system', require('../routes/system'));
// API文档路由
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
explorer: true,
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: '保险端口系统 API文档'
}));
// 404处理
app.use('*', (req, res) => {
res.status(404).json({
code: 404,
status: 'error',
message: '接口不存在',
timestamp: new Date().toISOString()
});
});
// 全局错误处理
app.use((err, req, res, next) => {
console.error('全局错误:', err);
res.status(err.status || 500).json({
code: err.status || 500,
status: 'error',
message: err.message || '服务器内部错误',
timestamp: new Date().toISOString()
});
});
// 启动服务器
const startServer = async () => {
try {
// 测试数据库连接
const dbConnected = await testConnection();
if (!dbConnected) {
console.error('❌ 数据库连接失败,服务器启动中止');
process.exit(1);
}
// Redis连接已移除
app.listen(PORT, () => {
console.log(`🚀 保险端口后端服务已启动`);
console.log(`📍 服务地址: http://localhost:${PORT}`);
console.log(`🌐 环境: ${process.env.NODE_ENV || 'development'}`);
console.log(`⏰ 启动时间: ${new Date().toLocaleString()}`);
});
} catch (error) {
console.error('❌ 服务器启动失败:', error);
process.exit(1);
}
};
// 优雅关闭
process.on('SIGINT', async () => {
console.log('\n🛑 正在关闭服务器...');
await sequelize.close();
process.exit(0);
});
// 启动应用
startServer();
module.exports = app;

View File

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

View File

@@ -0,0 +1,64 @@
// 统一响应格式工具
const responseFormat = {
// 成功响应
success: (data, message = 'success') => ({
code: 200,
status: 'success',
data,
message,
timestamp: new Date().toISOString()
}),
// 错误响应
error: (message, code = 500, data = null) => ({
code,
status: 'error',
data,
message,
timestamp: new Date().toISOString()
}),
// 分页响应
pagination: (data, pagination, message = 'success') => ({
code: 200,
status: 'success',
data: data,
pagination: {
page: pagination.page,
limit: pagination.limit,
total: pagination.total,
totalPages: Math.ceil(pagination.total / pagination.limit)
},
message,
timestamp: new Date().toISOString()
}),
// 创建成功响应
created: (data, message = '创建成功') => ({
code: 201,
status: 'success',
data,
message,
timestamp: new Date().toISOString()
}),
// 无内容响应
noContent: (message = '无内容') => ({
code: 204,
status: 'success',
data: null,
message,
timestamp: new Date().toISOString()
}),
// 验证错误响应
validationError: (errors, message = '验证失败') => ({
code: 422,
status: 'error',
data: { errors },
message,
timestamp: new Date().toISOString()
})
};
module.exports = responseFormat;