保险前后端,养殖端和保险端小程序
This commit is contained in:
33
insurance_backend/.env.example
Normal file
33
insurance_backend/.env.example
Normal 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
|
||||
49
insurance_backend/config/database.js
Normal file
49
insurance_backend/config/database.js
Normal 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 };
|
||||
42
insurance_backend/config/redis.js
Normal file
42
insurance_backend/config/redis.js
Normal 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 };
|
||||
148
insurance_backend/config/swagger.js
Normal file
148
insurance_backend/config/swagger.js
Normal 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;
|
||||
234
insurance_backend/controllers/authController.js
Normal file
234
insurance_backend/controllers/authController.js
Normal 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
|
||||
};
|
||||
244
insurance_backend/controllers/claimController.js
Normal file
244
insurance_backend/controllers/claimController.js
Normal 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
|
||||
};
|
||||
218
insurance_backend/controllers/insuranceController.js
Normal file
218
insurance_backend/controllers/insuranceController.js
Normal 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
|
||||
};
|
||||
199
insurance_backend/controllers/insuranceTypeController.js
Normal file
199
insurance_backend/controllers/insuranceTypeController.js
Normal 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
|
||||
};
|
||||
210
insurance_backend/controllers/policyController.js
Normal file
210
insurance_backend/controllers/policyController.js
Normal 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
|
||||
};
|
||||
232
insurance_backend/controllers/systemController.js
Normal file
232
insurance_backend/controllers/systemController.js
Normal 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
|
||||
};
|
||||
232
insurance_backend/controllers/userController.js
Normal file
232
insurance_backend/controllers/userController.js
Normal 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
|
||||
};
|
||||
14
insurance_backend/generate_password.js
Normal file
14
insurance_backend/generate_password.js
Normal 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);
|
||||
70
insurance_backend/middleware/auth.js
Normal file
70
insurance_backend/middleware/auth.js
Normal 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 };
|
||||
142
insurance_backend/models/Claim.js
Normal file
142
insurance_backend/models/Claim.js
Normal 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;
|
||||
106
insurance_backend/models/InsuranceApplication.js
Normal file
106
insurance_backend/models/InsuranceApplication.js
Normal 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;
|
||||
105
insurance_backend/models/InsuranceType.js
Normal file
105
insurance_backend/models/InsuranceType.js
Normal 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;
|
||||
153
insurance_backend/models/Policy.js
Normal file
153
insurance_backend/models/Policy.js
Normal 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;
|
||||
41
insurance_backend/models/Role.js
Normal file
41
insurance_backend/models/Role.js
Normal 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;
|
||||
105
insurance_backend/models/User.js
Normal file
105
insurance_backend/models/User.js
Normal 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;
|
||||
66
insurance_backend/models/index.js
Normal file
66
insurance_backend/models/index.js
Normal 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
32672
insurance_backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
58
insurance_backend/package.json
Normal file
58
insurance_backend/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
209
insurance_backend/routes/auth.js
Normal file
209
insurance_backend/routes/auth.js
Normal 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;
|
||||
36
insurance_backend/routes/claims.js
Normal file
36
insurance_backend/routes/claims.js
Normal 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;
|
||||
41
insurance_backend/routes/insurance.js
Normal file
41
insurance_backend/routes/insurance.js
Normal 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;
|
||||
36
insurance_backend/routes/insuranceTypes.js
Normal file
36
insurance_backend/routes/insuranceTypes.js
Normal 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;
|
||||
36
insurance_backend/routes/policies.js
Normal file
36
insurance_backend/routes/policies.js
Normal 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;
|
||||
24
insurance_backend/routes/system.js
Normal file
24
insurance_backend/routes/system.js
Normal 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;
|
||||
24
insurance_backend/routes/users.js
Normal file
24
insurance_backend/routes/users.js
Normal 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;
|
||||
250
insurance_backend/scripts/create_tables.sql
Normal file
250
insurance_backend/scripts/create_tables.sql
Normal 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;
|
||||
113
insurance_backend/scripts/init-db.js
Normal file
113
insurance_backend/scripts/init-db.js
Normal 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;
|
||||
37
insurance_backend/scripts/insert_data_fixed.sql
Normal file
37
insurance_backend/scripts/insert_data_fixed.sql
Normal 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;
|
||||
34
insurance_backend/scripts/insert_test_data.sql
Normal file
34
insurance_backend/scripts/insert_test_data.sql
Normal 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;
|
||||
81
insurance_backend/scripts/migrate.js
Normal file
81
insurance_backend/scripts/migrate.js
Normal 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;
|
||||
215
insurance_backend/scripts/schema_only.sql
Normal file
215
insurance_backend/scripts/schema_only.sql
Normal 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_%';
|
||||
202
insurance_backend/scripts/schema_simple.sql
Normal file
202
insurance_backend/scripts/schema_simple.sql
Normal 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_%';
|
||||
147
insurance_backend/scripts/test_data.sql
Normal file
147
insurance_backend/scripts/test_data.sql
Normal 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;
|
||||
116
insurance_backend/src/app.js
Normal file
116
insurance_backend/src/app.js
Normal 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;
|
||||
1
insurance_backend/update_password.sql
Normal file
1
insurance_backend/update_password.sql
Normal file
@@ -0,0 +1 @@
|
||||
UPDATE users SET password = '$2b$12$8MenY1QAy0piFa64iM6k4.85TQAPjGEJRrNWV6g4C0gMP5/LmpEHe' WHERE username = 'admin';
|
||||
64
insurance_backend/utils/response.js
Normal file
64
insurance_backend/utils/response.js
Normal 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;
|
||||
Reference in New Issue
Block a user