修改保险后端代码,政府前端代码
This commit is contained in:
962
bank-backend/API_INTERFACE_DOCUMENTATION.md
Normal file
962
bank-backend/API_INTERFACE_DOCUMENTATION.md
Normal file
@@ -0,0 +1,962 @@
|
||||
# 银行管理系统后端API接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细描述了银行管理系统后端的所有API接口,包括接口地址、请求方法、参数说明、响应格式等信息。后端基于Node.js + Express.js + Sequelize + MySQL技术栈开发。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **基础URL**: `http://localhost:5351`
|
||||
- **API版本**: v1.0.0
|
||||
- **认证方式**: JWT Token
|
||||
- **数据格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
|
||||
## 通用响应格式
|
||||
|
||||
### 成功响应
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {},
|
||||
"timestamp": "2024-01-18T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误信息",
|
||||
"error": "ERROR_CODE",
|
||||
"timestamp": "2024-01-18T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## 1. 认证模块 (Authentication)
|
||||
|
||||
### 1.1 用户登录
|
||||
- **接口地址**: `POST /api/auth/login`
|
||||
- **功能描述**: 用户登录获取访问令牌
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"name": "管理员",
|
||||
"role": "admin",
|
||||
"email": "admin@bank.com"
|
||||
},
|
||||
"expiresIn": 86400
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 用户登出
|
||||
- **接口地址**: `POST /api/auth/logout`
|
||||
- **功能描述**: 用户登出,使令牌失效
|
||||
- **请求头**: `Authorization: Bearer <token>`
|
||||
|
||||
### 1.3 刷新令牌
|
||||
- **接口地址**: `POST /api/auth/refresh`
|
||||
- **功能描述**: 刷新访问令牌
|
||||
- **请求头**: `Authorization: Bearer <token>`
|
||||
|
||||
### 1.4 获取当前用户信息
|
||||
- **接口地址**: `GET /api/auth/me`
|
||||
- **功能描述**: 获取当前登录用户信息
|
||||
- **请求头**: `Authorization: Bearer <token>`
|
||||
|
||||
## 2. 用户管理模块 (User Management)
|
||||
|
||||
### 2.1 获取用户列表
|
||||
- **接口地址**: `GET /api/users`
|
||||
- **功能描述**: 分页获取用户列表
|
||||
- **请求参数**:
|
||||
- `page`: 页码 (默认: 1)
|
||||
- `pageSize`: 每页数量 (默认: 10)
|
||||
- `search`: 搜索关键词
|
||||
- `role`: 角色筛选
|
||||
- `status`: 状态筛选
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"users": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"name": "管理员",
|
||||
"role": "admin",
|
||||
"status": "active",
|
||||
"email": "admin@bank.com",
|
||||
"createdAt": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 100,
|
||||
"totalPages": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 创建用户
|
||||
- **接口地址**: `POST /api/users`
|
||||
- **功能描述**: 创建新用户
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"username": "newuser",
|
||||
"password": "password123",
|
||||
"name": "新用户",
|
||||
"role": "teller",
|
||||
"email": "newuser@bank.com",
|
||||
"phone": "13800138000"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 获取用户详情
|
||||
- **接口地址**: `GET /api/users/:id`
|
||||
- **功能描述**: 获取指定用户详细信息
|
||||
|
||||
### 2.4 更新用户信息
|
||||
- **接口地址**: `PUT /api/users/:id`
|
||||
- **功能描述**: 更新用户信息
|
||||
- **请求参数**: 同创建用户,所有字段可选
|
||||
|
||||
### 2.5 删除用户
|
||||
- **接口地址**: `DELETE /api/users/:id`
|
||||
- **功能描述**: 删除用户(软删除)
|
||||
|
||||
### 2.6 重置用户密码
|
||||
- **接口地址**: `POST /api/users/:id/reset-password`
|
||||
- **功能描述**: 重置用户密码
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"newPassword": "newpassword123"
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 账户管理模块 (Account Management)
|
||||
|
||||
### 3.1 获取账户列表
|
||||
- **接口地址**: `GET /api/accounts`
|
||||
- **功能描述**: 分页获取账户列表
|
||||
- **请求参数**:
|
||||
- `page`: 页码
|
||||
- `pageSize`: 每页数量
|
||||
- `search`: 搜索关键词
|
||||
- `type`: 账户类型筛选
|
||||
- `status`: 状态筛选
|
||||
- `userId`: 用户ID筛选
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"accounts": [
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": "6225123456789001",
|
||||
"name": "张三储蓄账户",
|
||||
"type": "savings",
|
||||
"status": "active",
|
||||
"balance": 10000.50,
|
||||
"userId": 1,
|
||||
"userName": "张三",
|
||||
"createdAt": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 100,
|
||||
"totalPages": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 创建账户
|
||||
- **接口地址**: `POST /api/accounts`
|
||||
- **功能描述**: 创建新账户
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"name": "新账户",
|
||||
"type": "savings",
|
||||
"userId": 1,
|
||||
"initialBalance": 0,
|
||||
"notes": "账户备注"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 获取账户详情
|
||||
- **接口地址**: `GET /api/accounts/:id`
|
||||
- **功能描述**: 获取指定账户详细信息
|
||||
|
||||
### 3.4 更新账户信息
|
||||
- **接口地址**: `PUT /api/accounts/:id`
|
||||
- **功能描述**: 更新账户信息
|
||||
|
||||
### 3.5 冻结/解冻账户
|
||||
- **接口地址**: `POST /api/accounts/:id/toggle-status`
|
||||
- **功能描述**: 切换账户状态(活跃/冻结)
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"status": "frozen" // 或 "active"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 获取账户交易记录
|
||||
- **接口地址**: `GET /api/accounts/:id/transactions`
|
||||
- **功能描述**: 获取指定账户的交易记录
|
||||
- **请求参数**:
|
||||
- `page`: 页码
|
||||
- `pageSize`: 每页数量
|
||||
- `startDate`: 开始日期
|
||||
- `endDate`: 结束日期
|
||||
- `type`: 交易类型筛选
|
||||
|
||||
## 4. 交易管理模块 (Transaction Management)
|
||||
|
||||
### 4.1 获取交易列表
|
||||
- **接口地址**: `GET /api/transactions`
|
||||
- **功能描述**: 分页获取交易记录列表
|
||||
- **请求参数**:
|
||||
- `page`: 页码
|
||||
- `pageSize`: 每页数量
|
||||
- `type`: 交易类型筛选
|
||||
- `status`: 状态筛选
|
||||
- `accountNumber`: 账户号码筛选
|
||||
- `startDate`: 开始日期
|
||||
- `endDate`: 结束日期
|
||||
- `minAmount`: 最小金额
|
||||
- `maxAmount`: 最大金额
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"transactions": [
|
||||
{
|
||||
"id": "T202401180001",
|
||||
"type": "deposit",
|
||||
"accountNumber": "6225123456789001",
|
||||
"accountName": "张三储蓄账户",
|
||||
"amount": 5000.00,
|
||||
"status": "completed",
|
||||
"channel": "counter",
|
||||
"description": "现金存款",
|
||||
"timestamp": "2024-01-18T09:30:25.000Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 1000,
|
||||
"totalPages": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 创建交易
|
||||
- **接口地址**: `POST /api/transactions`
|
||||
- **功能描述**: 创建新交易记录
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"type": "deposit",
|
||||
"accountId": 1,
|
||||
"amount": 1000.00,
|
||||
"description": "存款描述",
|
||||
"channel": "counter"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 获取交易详情
|
||||
- **接口地址**: `GET /api/transactions/:id`
|
||||
- **功能描述**: 获取指定交易详细信息
|
||||
|
||||
### 4.4 更新交易状态
|
||||
- **接口地址**: `PUT /api/transactions/:id/status`
|
||||
- **功能描述**: 更新交易状态
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"status": "completed",
|
||||
"notes": "处理备注"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 获取交易统计
|
||||
- **接口地址**: `GET /api/transactions/statistics`
|
||||
- **功能描述**: 获取交易统计数据
|
||||
- **请求参数**:
|
||||
- `startDate`: 开始日期
|
||||
- `endDate`: 结束日期
|
||||
- `type`: 交易类型
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"todayCount": 15,
|
||||
"todayAmount": 25000.00,
|
||||
"monthCount": 342,
|
||||
"monthAmount": 1250000.00,
|
||||
"typeDistribution": {
|
||||
"deposit": 120,
|
||||
"withdrawal": 80,
|
||||
"transfer": 100,
|
||||
"payment": 42
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.6 导出交易数据
|
||||
- **接口地址**: `POST /api/transactions/export`
|
||||
- **功能描述**: 导出交易数据为Excel/PDF/CSV格式
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"format": "excel",
|
||||
"startDate": "2024-01-01",
|
||||
"endDate": "2024-01-31",
|
||||
"filters": {
|
||||
"type": ["deposit", "withdrawal"],
|
||||
"status": ["completed"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 贷款管理模块 (Loan Management)
|
||||
|
||||
### 5.1 贷款商品管理
|
||||
|
||||
#### 5.1.1 获取贷款商品列表
|
||||
- **接口地址**: `GET /api/loans/products`
|
||||
- **功能描述**: 获取贷款商品列表
|
||||
- **请求参数**:
|
||||
- `page`: 页码
|
||||
- `pageSize`: 每页数量
|
||||
- `search`: 搜索关键词
|
||||
- `type`: 产品类型筛选
|
||||
- `status`: 状态筛选
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"products": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "个人住房贷款",
|
||||
"code": "LOAN-001",
|
||||
"type": "mortgage",
|
||||
"status": "active",
|
||||
"minAmount": 100000,
|
||||
"maxAmount": 5000000,
|
||||
"minTerm": 12,
|
||||
"maxTerm": 360,
|
||||
"interestRate": 4.5,
|
||||
"maxInterestRate": 6.5,
|
||||
"requirements": "年满18周岁,有稳定收入来源",
|
||||
"description": "专为个人购房提供的住房抵押贷款产品"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 50,
|
||||
"totalPages": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.2 创建贷款商品
|
||||
- **接口地址**: `POST /api/loans/products`
|
||||
- **功能描述**: 创建新的贷款商品
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"name": "个人消费贷款",
|
||||
"code": "LOAN-002",
|
||||
"type": "personal",
|
||||
"minAmount": 10000,
|
||||
"maxAmount": 500000,
|
||||
"minTerm": 6,
|
||||
"maxTerm": 60,
|
||||
"interestRate": 6.8,
|
||||
"maxInterestRate": 12.5,
|
||||
"requirements": "年满18周岁,有稳定收入来源",
|
||||
"description": "用于个人消费支出的信用贷款产品"
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.3 更新贷款商品
|
||||
- **接口地址**: `PUT /api/loans/products/:id`
|
||||
- **功能描述**: 更新贷款商品信息
|
||||
|
||||
#### 5.1.4 删除贷款商品
|
||||
- **接口地址**: `DELETE /api/loans/products/:id`
|
||||
- **功能描述**: 删除贷款商品
|
||||
|
||||
#### 5.1.5 切换商品状态
|
||||
- **接口地址**: `POST /api/loans/products/:id/toggle-status`
|
||||
- **功能描述**: 启用/停用贷款商品
|
||||
|
||||
### 5.2 贷款申请管理
|
||||
|
||||
#### 5.2.1 获取贷款申请列表
|
||||
- **接口地址**: `GET /api/loans/applications`
|
||||
- **功能描述**: 获取贷款申请列表
|
||||
- **请求参数**:
|
||||
- `page`: 页码
|
||||
- `pageSize`: 每页数量
|
||||
- `search`: 搜索关键词
|
||||
- `type`: 申请类型筛选
|
||||
- `status`: 状态筛选
|
||||
- `startDate`: 开始日期
|
||||
- `endDate`: 结束日期
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"applications": [
|
||||
{
|
||||
"id": 1,
|
||||
"applicationNumber": "APP-202401180001",
|
||||
"applicantName": "张三",
|
||||
"type": "personal",
|
||||
"status": "pending",
|
||||
"amount": 200000,
|
||||
"term": 24,
|
||||
"interestRate": 6.5,
|
||||
"applicationTime": "2024-01-18T09:30:00.000Z",
|
||||
"phone": "13800138000",
|
||||
"idCard": "110101199001011234",
|
||||
"purpose": "个人消费"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 100,
|
||||
"totalPages": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2.2 创建贷款申请
|
||||
- **接口地址**: `POST /api/loans/applications`
|
||||
- **功能描述**: 创建新的贷款申请
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"productId": 1,
|
||||
"applicantName": "张三",
|
||||
"amount": 200000,
|
||||
"term": 24,
|
||||
"phone": "13800138000",
|
||||
"idCard": "110101199001011234",
|
||||
"purpose": "个人消费",
|
||||
"remark": "申请备注"
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2.3 获取申请详情
|
||||
- **接口地址**: `GET /api/loans/applications/:id`
|
||||
- **功能描述**: 获取贷款申请详细信息
|
||||
|
||||
#### 5.2.4 审核贷款申请
|
||||
- **接口地址**: `POST /api/loans/applications/:id/audit`
|
||||
- **功能描述**: 审核贷款申请
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"action": "approve", // 或 "reject"
|
||||
"comment": "审核意见"
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2.5 获取申请审核记录
|
||||
- **接口地址**: `GET /api/loans/applications/:id/audit-records`
|
||||
- **功能描述**: 获取申请审核历史记录
|
||||
|
||||
### 5.3 贷款合同管理
|
||||
|
||||
#### 5.3.1 获取贷款合同列表
|
||||
- **接口地址**: `GET /api/loans/contracts`
|
||||
- **功能描述**: 获取贷款合同列表
|
||||
|
||||
#### 5.3.2 创建贷款合同
|
||||
- **接口地址**: `POST /api/loans/contracts`
|
||||
- **功能描述**: 创建贷款合同
|
||||
|
||||
#### 5.3.3 获取合同详情
|
||||
- **接口地址**: `GET /api/loans/contracts/:id`
|
||||
- **功能描述**: 获取合同详细信息
|
||||
|
||||
#### 5.3.4 更新合同状态
|
||||
- **接口地址**: `PUT /api/loans/contracts/:id/status`
|
||||
- **功能描述**: 更新合同状态
|
||||
|
||||
### 5.4 贷款解押管理
|
||||
|
||||
#### 5.4.1 获取解押申请列表
|
||||
- **接口地址**: `GET /api/loans/releases`
|
||||
- **功能描述**: 获取贷款解押申请列表
|
||||
|
||||
#### 5.4.2 创建解押申请
|
||||
- **接口地址**: `POST /api/loans/releases`
|
||||
- **功能描述**: 创建解押申请
|
||||
|
||||
#### 5.4.3 处理解押申请
|
||||
- **接口地址**: `POST /api/loans/releases/:id/process`
|
||||
- **功能描述**: 处理解押申请
|
||||
|
||||
## 6. 报表统计模块 (Reports)
|
||||
|
||||
### 6.1 生成交易报表
|
||||
- **接口地址**: `POST /api/reports/transactions`
|
||||
- **功能描述**: 生成交易报表
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"startDate": "2024-01-01",
|
||||
"endDate": "2024-01-31",
|
||||
"transactionTypes": ["deposit", "withdrawal"],
|
||||
"format": "excel"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 生成账户报表
|
||||
- **接口地址**: `POST /api/reports/accounts`
|
||||
- **功能描述**: 生成账户报表
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"endDate": "2024-01-31",
|
||||
"accountTypes": ["savings", "checking"],
|
||||
"format": "pdf"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 生成用户报表
|
||||
- **接口地址**: `POST /api/reports/users`
|
||||
- **功能描述**: 生成用户报表
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"roles": ["admin", "manager"],
|
||||
"startDate": "2024-01-01",
|
||||
"endDate": "2024-01-31",
|
||||
"format": "csv"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 获取报表列表
|
||||
- **接口地址**: `GET /api/reports`
|
||||
- **功能描述**: 获取已生成的报表列表
|
||||
|
||||
### 6.5 下载报表
|
||||
- **接口地址**: `GET /api/reports/:id/download`
|
||||
- **功能描述**: 下载指定报表文件
|
||||
|
||||
### 6.6 删除报表
|
||||
- **接口地址**: `DELETE /api/reports/:id`
|
||||
- **功能描述**: 删除报表文件
|
||||
|
||||
## 7. 仪表盘模块 (Dashboard)
|
||||
|
||||
### 7.1 获取仪表盘数据
|
||||
- **接口地址**: `GET /api/dashboard`
|
||||
- **功能描述**: 获取仪表盘统计数据
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"stats": {
|
||||
"totalUsers": 1250,
|
||||
"totalAccounts": 3420,
|
||||
"todayTransactions": 156,
|
||||
"totalAssets": 12500000.50
|
||||
},
|
||||
"recentTransactions": [
|
||||
{
|
||||
"id": "T202401180001",
|
||||
"type": "deposit",
|
||||
"amount": 5000.00,
|
||||
"accountNumber": "6225123456789001",
|
||||
"timestamp": "2024-01-18T09:30:25.000Z"
|
||||
}
|
||||
],
|
||||
"systemInfo": {
|
||||
"uptime": "5天12小时",
|
||||
"lastUpdate": "2024-01-18T10:30:00.000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 获取图表数据
|
||||
- **接口地址**: `GET /api/dashboard/charts`
|
||||
- **功能描述**: 获取仪表盘图表数据
|
||||
- **请求参数**:
|
||||
- `type`: 图表类型 (transaction-trend, account-distribution)
|
||||
- `period`: 时间周期 (7d, 30d, 90d)
|
||||
|
||||
## 8. 项目管理模块 (Project Management)
|
||||
|
||||
### 8.1 获取项目列表
|
||||
- **接口地址**: `GET /api/projects`
|
||||
- **功能描述**: 获取项目列表
|
||||
- **请求参数**:
|
||||
- `page`: 页码
|
||||
- `pageSize`: 每页数量
|
||||
- `search`: 搜索关键词
|
||||
- `status`: 状态筛选
|
||||
- `priority`: 优先级筛选
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"projects": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "核心银行系统升级",
|
||||
"code": "PRJ-001",
|
||||
"status": "active",
|
||||
"priority": "high",
|
||||
"manager": "张三",
|
||||
"progress": 75,
|
||||
"startDate": "2024-01-01",
|
||||
"endDate": "2024-06-30",
|
||||
"description": "升级核心银行系统,提升性能和安全性"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current": 1,
|
||||
"pageSize": 10,
|
||||
"total": 50,
|
||||
"totalPages": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 创建项目
|
||||
- **接口地址**: `POST /api/projects`
|
||||
- **功能描述**: 创建新项目
|
||||
|
||||
### 8.3 更新项目
|
||||
- **接口地址**: `PUT /api/projects/:id`
|
||||
- **功能描述**: 更新项目信息
|
||||
|
||||
### 8.4 删除项目
|
||||
- **接口地址**: `DELETE /api/projects/:id`
|
||||
- **功能描述**: 删除项目
|
||||
|
||||
## 9. 系统检查模块 (System Check)
|
||||
|
||||
### 9.1 获取检查项目列表
|
||||
- **接口地址**: `GET /api/system/check-items`
|
||||
- **功能描述**: 获取系统检查项目列表
|
||||
|
||||
### 9.2 执行系统检查
|
||||
- **接口地址**: `POST /api/system/check`
|
||||
- **功能描述**: 执行系统健康检查
|
||||
|
||||
### 9.3 获取检查历史
|
||||
- **接口地址**: `GET /api/system/check-history`
|
||||
- **功能描述**: 获取系统检查历史记录
|
||||
|
||||
### 9.4 获取系统状态
|
||||
- **接口地址**: `GET /api/system/status`
|
||||
- **功能描述**: 获取系统运行状态
|
||||
|
||||
## 10. 市场行情模块 (Market Trends)
|
||||
|
||||
### 10.1 获取市场数据
|
||||
- **接口地址**: `GET /api/market/data`
|
||||
- **功能描述**: 获取实时市场数据
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"shanghaiIndex": 3245.67,
|
||||
"shanghaiIndexChange": 1.25,
|
||||
"shenzhenIndex": 12345.89,
|
||||
"shenzhenIndexChange": -0.85,
|
||||
"chinextIndex": 2567.34,
|
||||
"chinextIndexChange": 2.15,
|
||||
"exchangeRate": 7.2345,
|
||||
"exchangeRateChange": -0.12
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 获取银行股行情
|
||||
- **接口地址**: `GET /api/market/bank-stocks`
|
||||
- **功能描述**: 获取银行股票行情
|
||||
|
||||
### 10.3 获取市场新闻
|
||||
- **接口地址**: `GET /api/market/news`
|
||||
- **功能描述**: 获取市场新闻
|
||||
|
||||
## 11. 硬件管理模块 (Hardware Management)
|
||||
|
||||
### 11.1 获取设备列表
|
||||
- **接口地址**: `GET /api/hardware/devices`
|
||||
- **功能描述**: 获取硬件设备列表
|
||||
- **请求参数**:
|
||||
- `page`: 页码
|
||||
- `pageSize`: 每页数量
|
||||
- `search`: 搜索关键词
|
||||
- `type`: 设备类型筛选
|
||||
- `status`: 状态筛选
|
||||
- `location`: 位置筛选
|
||||
|
||||
### 11.2 创建设备
|
||||
- **接口地址**: `POST /api/hardware/devices`
|
||||
- **功能描述**: 添加新设备
|
||||
|
||||
### 11.3 更新设备
|
||||
- **接口地址**: `PUT /api/hardware/devices/:id`
|
||||
- **功能描述**: 更新设备信息
|
||||
|
||||
### 11.4 删除设备
|
||||
- **接口地址**: `DELETE /api/hardware/devices/:id`
|
||||
- **功能描述**: 删除设备
|
||||
|
||||
### 11.5 设备检查
|
||||
- **接口地址**: `POST /api/hardware/devices/:id/check`
|
||||
- **功能描述**: 执行设备检查
|
||||
|
||||
### 11.6 设备维护
|
||||
- **接口地址**: `POST /api/hardware/devices/:id/maintenance`
|
||||
- **功能描述**: 设置设备维护状态
|
||||
|
||||
## 12. 员工管理模块 (Employee Management)
|
||||
|
||||
### 12.1 获取员工列表
|
||||
- **接口地址**: `GET /api/employees`
|
||||
- **功能描述**: 获取员工列表
|
||||
- **请求参数**:
|
||||
- `page`: 页码
|
||||
- `pageSize`: 每页数量
|
||||
- `search`: 搜索关键词
|
||||
- `status`: 状态筛选
|
||||
- `department`: 部门筛选
|
||||
- `position`: 职位筛选
|
||||
|
||||
### 12.2 创建员工
|
||||
- **接口地址**: `POST /api/employees`
|
||||
- **功能描述**: 添加新员工
|
||||
|
||||
### 12.3 更新员工
|
||||
- **接口地址**: `PUT /api/employees/:id`
|
||||
- **功能描述**: 更新员工信息
|
||||
|
||||
### 12.4 删除员工
|
||||
- **接口地址**: `DELETE /api/employees/:id`
|
||||
- **功能描述**: 删除员工
|
||||
|
||||
### 12.5 切换员工状态
|
||||
- **接口地址**: `POST /api/employees/:id/toggle-status`
|
||||
- **功能描述**: 切换员工在职/离职状态
|
||||
|
||||
### 12.6 导出员工数据
|
||||
- **接口地址**: `POST /api/employees/export`
|
||||
- **功能描述**: 导出员工数据
|
||||
|
||||
## 13. 个人中心模块 (Profile)
|
||||
|
||||
### 13.1 获取个人信息
|
||||
- **接口地址**: `GET /api/profile`
|
||||
- **功能描述**: 获取当前用户个人信息
|
||||
|
||||
### 13.2 更新个人信息
|
||||
- **接口地址**: `PUT /api/profile`
|
||||
- **功能描述**: 更新个人信息
|
||||
|
||||
### 13.3 修改密码
|
||||
- **接口地址**: `POST /api/profile/change-password`
|
||||
- **功能描述**: 修改用户密码
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"oldPassword": "oldpassword123",
|
||||
"newPassword": "newpassword123"
|
||||
}
|
||||
```
|
||||
|
||||
### 13.4 更新头像
|
||||
- **接口地址**: `POST /api/profile/avatar`
|
||||
- **功能描述**: 更新用户头像
|
||||
- **请求类型**: `multipart/form-data`
|
||||
|
||||
### 13.5 获取通知列表
|
||||
- **接口地址**: `GET /api/profile/notifications`
|
||||
- **功能描述**: 获取用户通知列表
|
||||
|
||||
### 13.6 标记通知已读
|
||||
- **接口地址**: `PUT /api/profile/notifications/:id/read`
|
||||
- **功能描述**: 标记通知为已读
|
||||
|
||||
## 14. 系统设置模块 (Settings)
|
||||
|
||||
### 14.1 获取系统设置
|
||||
- **接口地址**: `GET /api/settings`
|
||||
- **功能描述**: 获取系统配置设置
|
||||
|
||||
### 14.2 更新基本设置
|
||||
- **接口地址**: `PUT /api/settings/basic`
|
||||
- **功能描述**: 更新系统基本设置
|
||||
|
||||
### 14.3 更新安全设置
|
||||
- **接口地址**: `PUT /api/settings/security`
|
||||
- **功能描述**: 更新安全相关设置
|
||||
|
||||
### 14.4 获取系统日志
|
||||
- **接口地址**: `GET /api/settings/logs`
|
||||
- **功能描述**: 获取系统操作日志
|
||||
|
||||
### 14.5 备份系统数据
|
||||
- **接口地址**: `POST /api/settings/backup`
|
||||
- **功能描述**: 备份系统数据
|
||||
|
||||
### 14.6 恢复系统数据
|
||||
- **接口地址**: `POST /api/settings/restore`
|
||||
- **功能描述**: 恢复系统数据
|
||||
|
||||
## 15. 文件上传模块 (File Upload)
|
||||
|
||||
### 15.1 上传文件
|
||||
- **接口地址**: `POST /api/upload`
|
||||
- **功能描述**: 上传文件
|
||||
- **请求类型**: `multipart/form-data`
|
||||
- **请求参数**:
|
||||
- `file`: 文件
|
||||
- `type`: 文件类型 (avatar, document, image)
|
||||
|
||||
### 15.2 删除文件
|
||||
- **接口地址**: `DELETE /api/upload/:id`
|
||||
- **功能描述**: 删除已上传的文件
|
||||
|
||||
## 16. 健康检查模块 (Health Check)
|
||||
|
||||
### 16.1 系统健康检查
|
||||
- **接口地址**: `GET /api/health`
|
||||
- **功能描述**: 检查系统健康状态
|
||||
- **响应数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status": "healthy",
|
||||
"timestamp": "2024-01-18T10:30:00.000Z",
|
||||
"services": {
|
||||
"database": "healthy",
|
||||
"redis": "healthy",
|
||||
"api": "healthy"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权访问 |
|
||||
| 403 | 禁止访问 |
|
||||
| 404 | 资源不存在 |
|
||||
| 409 | 资源冲突 |
|
||||
| 422 | 数据验证失败 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 认证说明
|
||||
|
||||
所有需要认证的接口都需要在请求头中携带JWT令牌:
|
||||
|
||||
```
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
## 分页说明
|
||||
|
||||
所有列表接口都支持分页,通用参数:
|
||||
|
||||
- `page`: 页码,从1开始
|
||||
- `pageSize`: 每页数量,默认10,最大100
|
||||
- `sortField`: 排序字段
|
||||
- `sortOrder`: 排序方向 (asc/desc)
|
||||
|
||||
## 搜索说明
|
||||
|
||||
支持搜索的接口通用参数:
|
||||
|
||||
- `search`: 搜索关键词,支持模糊匹配
|
||||
- `startDate`: 开始日期 (YYYY-MM-DD)
|
||||
- `endDate`: 结束日期 (YYYY-MM-DD)
|
||||
|
||||
## 数据验证
|
||||
|
||||
所有接口都包含数据验证,验证规则:
|
||||
|
||||
- 必填字段不能为空
|
||||
- 邮箱格式验证
|
||||
- 手机号格式验证
|
||||
- 身份证号格式验证
|
||||
- 金额格式验证
|
||||
- 日期格式验证
|
||||
|
||||
## 响应时间
|
||||
|
||||
- 简单查询: < 100ms
|
||||
- 复杂查询: < 500ms
|
||||
- 数据导出: < 5s
|
||||
- 文件上传: 根据文件大小
|
||||
|
||||
## 限流说明
|
||||
|
||||
- 普通接口: 100次/分钟
|
||||
- 登录接口: 10次/分钟
|
||||
- 文件上传: 20次/分钟
|
||||
- 数据导出: 5次/分钟
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0.0
|
||||
**最后更新**: 2024-01-18
|
||||
**维护人员**: 开发团队
|
||||
|
||||
186
bank-backend/FRONTEND_BACKEND_INTEGRATION_PLAN.md
Normal file
186
bank-backend/FRONTEND_BACKEND_INTEGRATION_PLAN.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 前后端集成实现计划
|
||||
|
||||
## 前端页面分析
|
||||
|
||||
根据前端页面分析,需要实现以下模块的后端API接口:
|
||||
|
||||
### 1. 用户管理模块 (Users.vue)
|
||||
**功能需求**:
|
||||
- 用户列表查询(分页、搜索、筛选)
|
||||
- 用户创建、编辑、删除
|
||||
- 用户状态管理(启用/禁用)
|
||||
- 用户角色管理
|
||||
- 密码重置
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/users` - 获取用户列表
|
||||
- `POST /api/users` - 创建用户
|
||||
- `GET /api/users/:id` - 获取用户详情
|
||||
- `PUT /api/users/:id` - 更新用户
|
||||
- `DELETE /api/users/:id` - 删除用户
|
||||
- `PUT /api/users/:id/status` - 更新用户状态
|
||||
- `POST /api/users/:id/reset-password` - 重置密码
|
||||
|
||||
### 2. 账户管理模块 (Accounts.vue)
|
||||
**功能需求**:
|
||||
- 账户列表查询(分页、搜索、筛选)
|
||||
- 账户创建、编辑、删除
|
||||
- 账户状态管理
|
||||
- 账户类型管理
|
||||
- 余额管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/accounts` - 获取账户列表
|
||||
- `POST /api/accounts` - 创建账户
|
||||
- `GET /api/accounts/:id` - 获取账户详情
|
||||
- `PUT /api/accounts/:id` - 更新账户
|
||||
- `DELETE /api/accounts/:id` - 删除账户
|
||||
- `PUT /api/accounts/:id/status` - 更新账户状态
|
||||
- `POST /api/accounts/:id/deposit` - 存款
|
||||
- `POST /api/accounts/:id/withdraw` - 取款
|
||||
|
||||
### 3. 交易管理模块 (Transactions.vue)
|
||||
**功能需求**:
|
||||
- 交易记录查询(分页、搜索、筛选)
|
||||
- 交易详情查看
|
||||
- 交易统计
|
||||
- 交易撤销
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/transactions` - 获取交易列表
|
||||
- `GET /api/transactions/:id` - 获取交易详情
|
||||
- `POST /api/transactions` - 创建交易
|
||||
- `PUT /api/transactions/:id/cancel` - 撤销交易
|
||||
- `GET /api/transactions/statistics` - 获取交易统计
|
||||
|
||||
### 4. 贷款管理模块
|
||||
#### 4.1 贷款商品 (LoanProducts.vue)
|
||||
**功能需求**:
|
||||
- 贷款产品列表查询
|
||||
- 产品创建、编辑、删除
|
||||
- 产品状态管理
|
||||
- 产品配置管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/loan-products` - 获取贷款产品列表
|
||||
- `POST /api/loan-products` - 创建贷款产品
|
||||
- `GET /api/loan-products/:id` - 获取产品详情
|
||||
- `PUT /api/loan-products/:id` - 更新产品
|
||||
- `DELETE /api/loan-products/:id` - 删除产品
|
||||
- `PUT /api/loan-products/:id/status` - 更新产品状态
|
||||
|
||||
#### 4.2 贷款申请 (LoanApplications.vue)
|
||||
**功能需求**:
|
||||
- 贷款申请列表查询
|
||||
- 申请详情查看
|
||||
- 申请审批流程
|
||||
- 申请状态管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/loan-applications` - 获取申请列表
|
||||
- `POST /api/loan-applications` - 提交申请
|
||||
- `GET /api/loan-applications/:id` - 获取申请详情
|
||||
- `PUT /api/loan-applications/:id` - 更新申请
|
||||
- `PUT /api/loan-applications/:id/approve` - 审批申请
|
||||
- `PUT /api/loan-applications/:id/reject` - 拒绝申请
|
||||
|
||||
#### 4.3 贷款合同 (LoanContracts.vue)
|
||||
**功能需求**:
|
||||
- 合同列表查询
|
||||
- 合同详情查看
|
||||
- 合同生成和管理
|
||||
- 合同状态管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/loan-contracts` - 获取合同列表
|
||||
- `POST /api/loan-contracts` - 创建合同
|
||||
- `GET /api/loan-contracts/:id` - 获取合同详情
|
||||
- `PUT /api/loan-contracts/:id` - 更新合同
|
||||
- `PUT /api/loan-contracts/:id/status` - 更新合同状态
|
||||
|
||||
#### 4.4 贷款解押 (LoanRelease.vue)
|
||||
**功能需求**:
|
||||
- 解押申请列表
|
||||
- 解押申请处理
|
||||
- 解押状态管理
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/loan-releases` - 获取解押申请列表
|
||||
- `POST /api/loan-releases` - 提交解押申请
|
||||
- `GET /api/loan-releases/:id` - 获取申请详情
|
||||
- `PUT /api/loan-releases/:id` - 更新申请
|
||||
- `PUT /api/loan-releases/:id/approve` - 审批解押
|
||||
|
||||
### 5. 报表统计模块 (Reports.vue)
|
||||
**功能需求**:
|
||||
- 交易报表生成
|
||||
- 用户报表生成
|
||||
- 账户报表生成
|
||||
- 报表导出功能
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/reports/transactions` - 获取交易报表
|
||||
- `GET /api/reports/users` - 获取用户报表
|
||||
- `GET /api/reports/accounts` - 获取账户报表
|
||||
- `GET /api/reports/export/:type` - 导出报表
|
||||
|
||||
### 6. 员工管理模块 (EmployeeManagement.vue)
|
||||
**功能需求**:
|
||||
- 员工列表管理
|
||||
- 员工信息维护
|
||||
- 员工统计
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/employees` - 获取员工列表
|
||||
- `POST /api/employees` - 创建员工
|
||||
- `GET /api/employees/:id` - 获取员工详情
|
||||
- `PUT /api/employees/:id` - 更新员工
|
||||
- `DELETE /api/employees/:id` - 删除员工
|
||||
- `GET /api/employees/statistics` - 获取员工统计
|
||||
|
||||
### 7. 系统管理模块
|
||||
#### 7.1 系统设置 (Settings.vue)
|
||||
**功能需求**:
|
||||
- 系统参数配置
|
||||
- 系统日志查看
|
||||
- 系统备份恢复
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/settings` - 获取系统设置
|
||||
- `PUT /api/settings` - 更新系统设置
|
||||
- `GET /api/logs` - 获取系统日志
|
||||
- `POST /api/backup` - 创建备份
|
||||
- `POST /api/restore` - 恢复备份
|
||||
|
||||
#### 7.2 个人中心 (Profile.vue)
|
||||
**功能需求**:
|
||||
- 个人信息查看和修改
|
||||
- 密码修改
|
||||
- 登录日志
|
||||
|
||||
**后端API接口**:
|
||||
- `GET /api/profile` - 获取个人信息
|
||||
- `PUT /api/profile` - 更新个人信息
|
||||
- `PUT /api/profile/password` - 修改密码
|
||||
- `GET /api/profile/login-logs` - 获取登录日志
|
||||
|
||||
## 实现步骤
|
||||
|
||||
1. **完善现有API接口** - 用户管理、账户管理、交易管理
|
||||
2. **实现贷款管理API** - 贷款产品、申请、合同、解押
|
||||
3. **实现报表统计API** - 各种报表生成和导出
|
||||
4. **实现员工管理API** - 员工信息管理
|
||||
5. **实现系统管理API** - 设置、日志、备份
|
||||
6. **更新前端API调用** - 确保前端正确调用所有接口
|
||||
7. **测试和验证** - 端到端功能测试
|
||||
|
||||
## 数据库模型扩展
|
||||
|
||||
需要创建以下新的数据模型:
|
||||
- LoanProduct (贷款产品)
|
||||
- LoanApplication (贷款申请)
|
||||
- LoanContract (贷款合同)
|
||||
- LoanRelease (贷款解押)
|
||||
- Employee (员工)
|
||||
- SystemLog (系统日志)
|
||||
- SystemSetting (系统设置)
|
||||
119
bank-backend/PORT_UPDATE_SUMMARY.md
Normal file
119
bank-backend/PORT_UPDATE_SUMMARY.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# 银行后端端口号修改总结
|
||||
|
||||
## 修改概述
|
||||
|
||||
将银行后端系统的端口号从5350修改为5351,确保所有相关配置文件都已同步更新。
|
||||
|
||||
## 修改的文件
|
||||
|
||||
### 1. 后端配置文件
|
||||
|
||||
#### ✅ `bank-backend/server.js`
|
||||
- **修改内容**: 端口号配置已经是5351
|
||||
- **代码位置**: 第30行 `const PORT = process.env.PORT || 5351;`
|
||||
- **状态**: 无需修改,已正确配置
|
||||
|
||||
#### ✅ `bank-backend/env.example`
|
||||
- **修改内容**: 环境变量示例文件中的端口号已经是5351
|
||||
- **代码位置**: 第2行 `PORT=5351`
|
||||
- **状态**: 无需修改,已正确配置
|
||||
|
||||
### 2. 前端配置文件
|
||||
|
||||
#### ✅ `bank-frontend/src/config/env.js`
|
||||
- **修改内容**: API基础URL配置已经是5351
|
||||
- **代码位置**: 第15行和第21行 `baseUrl: getEnvVar('VITE_API_BASE_URL', 'http://localhost:5351')`
|
||||
- **状态**: 无需修改,已正确配置
|
||||
|
||||
#### ✅ `bank-frontend/env.example`
|
||||
- **修改内容**: 更新环境变量示例文件中的API基础URL
|
||||
- **修改前**: `VITE_API_BASE_URL=http://localhost:5350`
|
||||
- **修改后**: `VITE_API_BASE_URL=http://localhost:5351`
|
||||
- **状态**: ✅ 已修改
|
||||
|
||||
#### ✅ `bank-frontend/vite.config.js`
|
||||
- **修改内容**: 更新Vite代理配置中的默认目标端口
|
||||
- **修改前**: `target: env.VITE_API_BASE_URL || 'http://localhost:5350'`
|
||||
- **修改后**: `target: env.VITE_API_BASE_URL || 'http://localhost:5351'`
|
||||
- **状态**: ✅ 已修改
|
||||
|
||||
#### ✅ `bank-frontend/README.md`
|
||||
- **修改内容**: 更新文档中的API基础URL示例
|
||||
- **修改前**: `VITE_API_BASE_URL=http://localhost:5350`
|
||||
- **修改后**: `VITE_API_BASE_URL=http://localhost:5351`
|
||||
- **状态**: ✅ 已修改
|
||||
|
||||
### 3. 小程序配置文件
|
||||
|
||||
#### ✅ `bank_mini_program/src/config/api.js`
|
||||
- **修改内容**: 更新银行小程序的API基础URL
|
||||
- **修改前**: `BASE_URL: 'http://localhost:5350'`
|
||||
- **修改后**: `BASE_URL: 'http://localhost:5351'`
|
||||
- **状态**: ✅ 已修改
|
||||
|
||||
## 端口号配置总结
|
||||
|
||||
### 后端服务
|
||||
- **服务器端口**: 5351
|
||||
- **配置文件**: `bank-backend/server.js`
|
||||
- **环境变量**: `PORT=5351`
|
||||
|
||||
### 前端服务
|
||||
- **前端端口**: 5300 (保持不变)
|
||||
- **API代理目标**: http://localhost:5351
|
||||
- **环境变量**: `VITE_API_BASE_URL=http://localhost:5351`
|
||||
|
||||
### 小程序服务
|
||||
- **API基础URL**: http://localhost:5351
|
||||
- **配置文件**: `bank_mini_program/src/config/api.js`
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 1. 启动后端服务
|
||||
```bash
|
||||
cd bank-backend
|
||||
npm start
|
||||
```
|
||||
预期输出:
|
||||
```
|
||||
🚀 银行管理后台服务器启动成功
|
||||
📡 服务地址: http://localhost:5351
|
||||
📚 API文档: http://localhost:5351/api-docs
|
||||
🏥 健康检查: http://localhost:5351/health
|
||||
```
|
||||
|
||||
### 2. 启动前端服务
|
||||
```bash
|
||||
cd bank-frontend
|
||||
npm run dev
|
||||
```
|
||||
预期输出:
|
||||
```
|
||||
Local: http://localhost:5300/
|
||||
Network: http://192.168.x.x:5300/
|
||||
```
|
||||
|
||||
### 3. 测试API连接
|
||||
访问以下URL验证服务是否正常运行:
|
||||
- 后端健康检查: http://localhost:5351/health
|
||||
- 后端API文档: http://localhost:5351/api-docs
|
||||
- 前端应用: http://localhost:5300
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **环境变量**: 如果使用了自定义的 `.env` 文件,请确保其中的 `VITE_API_BASE_URL` 设置为 `http://localhost:5351`
|
||||
|
||||
2. **代理配置**: 前端开发服务器会自动将 `/api` 请求代理到后端5351端口
|
||||
|
||||
3. **生产环境**: 生产环境部署时,需要相应更新生产环境的API基础URL配置
|
||||
|
||||
4. **数据库连接**: 端口号修改不影响数据库连接,数据库配置保持不变
|
||||
|
||||
## 修改完成状态
|
||||
|
||||
✅ **所有相关配置文件已成功更新为5351端口**
|
||||
✅ **前后端服务配置已同步**
|
||||
✅ **小程序API配置已更新**
|
||||
✅ **文档已更新**
|
||||
|
||||
现在银行后端系统将在5351端口运行,所有相关服务都已正确配置。
|
||||
@@ -140,9 +140,11 @@ exports.getAccounts = async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取账户列表错误:', error);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
message: '服务器内部错误',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
301
bank-backend/controllers/authController.js
Normal file
301
bank-backend/controllers/authController.js
Normal file
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* 认证控制器
|
||||
* @file authController.js
|
||||
* @description 处理用户认证相关的请求
|
||||
*/
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { User, Role } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const login = async (req, res) => {
|
||||
try {
|
||||
// 验证请求参数
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { username, password } = req.body;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findOne({
|
||||
where: { username },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '账户已被禁用,请联系管理员'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await user.validPassword(password);
|
||||
if (!isValidPassword) {
|
||||
// 增加登录失败次数
|
||||
user.login_attempts += 1;
|
||||
if (user.login_attempts >= 5) {
|
||||
user.status = 'locked';
|
||||
user.locked_until = new Date(Date.now() + 30 * 60 * 1000); // 锁定30分钟
|
||||
}
|
||||
await user.save();
|
||||
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 重置登录失败次数
|
||||
user.login_attempts = 0;
|
||||
user.locked_until = null;
|
||||
user.last_login = new Date();
|
||||
await user.save();
|
||||
|
||||
// 生成JWT令牌
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role?.name || 'user'
|
||||
},
|
||||
process.env.JWT_SECRET || 'your_jwt_secret_key_here',
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
// 返回成功响应
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.real_name,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
role: user.role?.name || 'user',
|
||||
avatar: user.avatar,
|
||||
lastLogin: user.last_login
|
||||
},
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const logout = async (req, res) => {
|
||||
try {
|
||||
// 在实际应用中,可以将token加入黑名单
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登出成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('登出错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const refreshToken = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.user;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user || user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户不存在或已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
// 生成新的JWT令牌
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role?.name || 'user'
|
||||
},
|
||||
process.env.JWT_SECRET || 'your_jwt_secret_key_here',
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '令牌刷新成功',
|
||||
data: {
|
||||
token,
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('刷新令牌错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const getCurrentUser = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.user;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取用户信息成功',
|
||||
data: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.real_name,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
role: user.role?.name || 'user',
|
||||
avatar: user.avatar,
|
||||
status: user.status,
|
||||
lastLogin: user.last_login,
|
||||
createdAt: user.created_at
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const changePassword = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请求参数错误',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { userId } = req.user;
|
||||
const { oldPassword, newPassword } = req.body;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
const isValidPassword = await user.validPassword(oldPassword);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '原密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '密码修改成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('修改密码错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
login,
|
||||
logout,
|
||||
refreshToken,
|
||||
getCurrentUser,
|
||||
changePassword
|
||||
};
|
||||
456
bank-backend/controllers/dashboardController.js
Normal file
456
bank-backend/controllers/dashboardController.js
Normal file
@@ -0,0 +1,456 @@
|
||||
/**
|
||||
* 仪表盘控制器
|
||||
* @file dashboardController.js
|
||||
* @description 处理仪表盘相关的请求
|
||||
*/
|
||||
const { User, Account, Transaction, Role } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取仪表盘统计数据
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const getDashboardStats = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.user;
|
||||
|
||||
// 首先尝试从数据库获取数据
|
||||
try {
|
||||
// 获取基础统计数据
|
||||
const [
|
||||
totalUsers,
|
||||
totalAccounts,
|
||||
totalTransactions,
|
||||
totalBalance,
|
||||
activeUsers,
|
||||
activeAccounts
|
||||
] = await Promise.all([
|
||||
// 总用户数
|
||||
User.count(),
|
||||
// 总账户数
|
||||
Account.count(),
|
||||
// 总交易数
|
||||
Transaction.count(),
|
||||
// 总余额
|
||||
Account.sum('balance'),
|
||||
// 活跃用户数
|
||||
User.count({ where: { status: 'active' } }),
|
||||
// 活跃账户数
|
||||
Account.count({ where: { status: 'active' } })
|
||||
]);
|
||||
|
||||
// 获取今日交易统计
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
const todayStats = await Transaction.findOne({
|
||||
where: {
|
||||
created_at: {
|
||||
[Op.gte]: today,
|
||||
[Op.lt]: tomorrow
|
||||
}
|
||||
},
|
||||
attributes: [
|
||||
[Transaction.sequelize.fn('COUNT', Transaction.sequelize.col('id')), 'count'],
|
||||
[Transaction.sequelize.fn('SUM', Transaction.sequelize.col('amount')), 'totalAmount']
|
||||
],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 获取账户类型分布
|
||||
const accountTypeStats = await Account.findAll({
|
||||
attributes: [
|
||||
'account_type',
|
||||
[Account.sequelize.fn('COUNT', Account.sequelize.col('id')), 'count'],
|
||||
[Account.sequelize.fn('SUM', Account.sequelize.col('balance')), 'totalBalance']
|
||||
],
|
||||
group: ['account_type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 获取最近7天的交易趋势
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||
sevenDaysAgo.setHours(0, 0, 0, 0);
|
||||
|
||||
const transactionTrends = await Transaction.findAll({
|
||||
where: {
|
||||
created_at: {
|
||||
[Op.gte]: sevenDaysAgo
|
||||
}
|
||||
},
|
||||
attributes: [
|
||||
[Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at')), 'date'],
|
||||
[Transaction.sequelize.fn('COUNT', Transaction.sequelize.col('id')), 'count'],
|
||||
[Transaction.sequelize.fn('SUM', Transaction.sequelize.col('amount')), 'totalAmount']
|
||||
],
|
||||
group: [Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at'))],
|
||||
order: [[Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at')), 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
const stats = {
|
||||
overview: {
|
||||
totalUsers: totalUsers || 0,
|
||||
totalAccounts: totalAccounts || 0,
|
||||
totalTransactions: totalTransactions || 0,
|
||||
totalBalance: totalBalance || 0,
|
||||
activeUsers: activeUsers || 0,
|
||||
activeAccounts: activeAccounts || 0
|
||||
},
|
||||
today: {
|
||||
transactionCount: parseInt(todayStats?.count) || 0,
|
||||
transactionAmount: parseInt(todayStats?.totalAmount) || 0
|
||||
},
|
||||
accountTypes: accountTypeStats.map(item => ({
|
||||
type: item.account_type,
|
||||
count: parseInt(item.count),
|
||||
totalBalance: parseInt(item.totalBalance) || 0
|
||||
})),
|
||||
trends: transactionTrends.map(item => ({
|
||||
date: item.date,
|
||||
count: parseInt(item.count),
|
||||
totalAmount: parseInt(item.totalAmount) || 0
|
||||
}))
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取统计数据成功',
|
||||
data: stats
|
||||
});
|
||||
|
||||
} catch (dbError) {
|
||||
console.warn('数据库查询失败,使用模拟数据:', dbError.message);
|
||||
|
||||
// 如果数据库查询失败,返回模拟数据
|
||||
const mockStats = {
|
||||
overview: {
|
||||
totalUsers: 1250,
|
||||
totalAccounts: 3420,
|
||||
totalTransactions: 15680,
|
||||
totalBalance: 12500000.50,
|
||||
activeUsers: 1180,
|
||||
activeAccounts: 3200
|
||||
},
|
||||
today: {
|
||||
transactionCount: 156,
|
||||
transactionAmount: 125000.00
|
||||
},
|
||||
accountTypes: [
|
||||
{ type: 'savings', count: 2100, totalBalance: 8500000.00 },
|
||||
{ type: 'checking', count: 800, totalBalance: 3200000.00 },
|
||||
{ type: 'credit', count: 400, totalBalance: 500000.00 },
|
||||
{ type: 'loan', count: 120, totalBalance: 300000.00 }
|
||||
],
|
||||
trends: [
|
||||
{ date: '2024-01-15', count: 45, totalAmount: 12500.00 },
|
||||
{ date: '2024-01-16', count: 52, totalAmount: 15200.00 },
|
||||
{ date: '2024-01-17', count: 38, totalAmount: 9800.00 },
|
||||
{ date: '2024-01-18', count: 61, totalAmount: 18500.00 },
|
||||
{ date: '2024-01-19', count: 48, totalAmount: 13200.00 },
|
||||
{ date: '2024-01-20', count: 55, totalAmount: 16800.00 },
|
||||
{ date: '2024-01-21', count: 42, totalAmount: 11200.00 }
|
||||
]
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取统计数据成功(模拟数据)',
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取仪表盘统计数据错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取图表数据
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const getChartData = async (req, res) => {
|
||||
try {
|
||||
const { type, period = '7d' } = req.query;
|
||||
|
||||
let startDate = new Date();
|
||||
switch (period) {
|
||||
case '1d':
|
||||
startDate.setDate(startDate.getDate() - 1);
|
||||
break;
|
||||
case '7d':
|
||||
startDate.setDate(startDate.getDate() - 7);
|
||||
break;
|
||||
case '30d':
|
||||
startDate.setDate(startDate.getDate() - 30);
|
||||
break;
|
||||
case '90d':
|
||||
startDate.setDate(startDate.getDate() - 90);
|
||||
break;
|
||||
default:
|
||||
startDate.setDate(startDate.getDate() - 7);
|
||||
}
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
|
||||
let chartData = {};
|
||||
|
||||
switch (type) {
|
||||
case 'transaction_trend':
|
||||
// 交易趋势图
|
||||
chartData = await getTransactionTrendData(startDate, period);
|
||||
break;
|
||||
case 'account_distribution':
|
||||
// 账户类型分布图
|
||||
chartData = await getAccountDistributionData();
|
||||
break;
|
||||
case 'user_growth':
|
||||
// 用户增长图
|
||||
chartData = await getUserGrowthData(startDate, period);
|
||||
break;
|
||||
case 'balance_trend':
|
||||
// 余额趋势图
|
||||
chartData = await getBalanceTrendData(startDate, period);
|
||||
break;
|
||||
default:
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '无效的图表类型'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取图表数据成功',
|
||||
data: chartData
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取图表数据错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取交易趋势数据
|
||||
* @param {Date} startDate 开始日期
|
||||
* @param {String} period 周期
|
||||
* @returns {Object} 交易趋势数据
|
||||
*/
|
||||
const getTransactionTrendData = async (startDate, period) => {
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
const trends = await Transaction.findAll({
|
||||
where: {
|
||||
created_at: {
|
||||
[Op.gte]: startDate
|
||||
}
|
||||
},
|
||||
attributes: [
|
||||
[Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at')), 'date'],
|
||||
[Transaction.sequelize.fn('COUNT', Transaction.sequelize.col('id')), 'count'],
|
||||
[Transaction.sequelize.fn('SUM', Transaction.sequelize.col('amount')), 'totalAmount']
|
||||
],
|
||||
group: [Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at'))],
|
||||
order: [[Transaction.sequelize.fn('DATE', Transaction.sequelize.col('created_at')), 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'line',
|
||||
data: trends.map(item => ({
|
||||
date: item.date,
|
||||
count: parseInt(item.count),
|
||||
amount: parseInt(item.totalAmount) || 0
|
||||
}))
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取账户分布数据
|
||||
* @returns {Object} 账户分布数据
|
||||
*/
|
||||
const getAccountDistributionData = async () => {
|
||||
const distribution = await Account.findAll({
|
||||
attributes: [
|
||||
'account_type',
|
||||
[Account.sequelize.fn('COUNT', Account.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['account_type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'pie',
|
||||
data: distribution.map(item => ({
|
||||
name: item.account_type,
|
||||
value: parseInt(item.count)
|
||||
}))
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户增长数据
|
||||
* @param {Date} startDate 开始日期
|
||||
* @param {String} period 周期
|
||||
* @returns {Object} 用户增长数据
|
||||
*/
|
||||
const getUserGrowthData = async (startDate, period) => {
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
const growth = await User.findAll({
|
||||
where: {
|
||||
created_at: {
|
||||
[Op.gte]: startDate
|
||||
}
|
||||
},
|
||||
attributes: [
|
||||
[User.sequelize.fn('DATE', User.sequelize.col('created_at')), 'date'],
|
||||
[User.sequelize.fn('COUNT', User.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: [User.sequelize.fn('DATE', User.sequelize.col('created_at'))],
|
||||
order: [[User.sequelize.fn('DATE', User.sequelize.col('created_at')), 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'bar',
|
||||
data: growth.map(item => ({
|
||||
date: item.date,
|
||||
count: parseInt(item.count)
|
||||
}))
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取余额趋势数据
|
||||
* @param {Date} startDate 开始日期
|
||||
* @param {String} period 周期
|
||||
* @returns {Object} 余额趋势数据
|
||||
*/
|
||||
const getBalanceTrendData = async (startDate, period) => {
|
||||
// 这里可以实现余额趋势逻辑
|
||||
// 由于余额是实时变化的,这里返回模拟数据
|
||||
return {
|
||||
type: 'line',
|
||||
data: []
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取最近交易记录
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
const getRecentTransactions = async (req, res) => {
|
||||
try {
|
||||
const { limit = 10 } = req.query;
|
||||
|
||||
// 首先尝试从数据库获取数据
|
||||
try {
|
||||
const transactions = await Transaction.findAll({
|
||||
include: [{
|
||||
model: Account,
|
||||
as: 'account',
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['username', 'real_name']
|
||||
}]
|
||||
}],
|
||||
order: [['created_at', 'DESC']],
|
||||
limit: parseInt(limit)
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '获取最近交易记录成功',
|
||||
data: transactions
|
||||
});
|
||||
} catch (dbError) {
|
||||
console.warn('数据库查询失败,使用模拟数据:', dbError.message);
|
||||
|
||||
// 如果数据库查询失败,返回模拟数据
|
||||
const mockTransactions = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'deposit',
|
||||
amount: 1000.00,
|
||||
description: '存款',
|
||||
status: 'completed',
|
||||
created_at: new Date(),
|
||||
account: {
|
||||
account_number: '1234567890',
|
||||
user: {
|
||||
username: 'user1',
|
||||
real_name: '张三'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'withdrawal',
|
||||
amount: 500.00,
|
||||
description: '取款',
|
||||
status: 'completed',
|
||||
created_at: new Date(Date.now() - 3600000),
|
||||
account: {
|
||||
account_number: '1234567891',
|
||||
user: {
|
||||
username: 'user2',
|
||||
real_name: '李四'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'transfer',
|
||||
amount: 200.00,
|
||||
description: '转账',
|
||||
status: 'completed',
|
||||
created_at: new Date(Date.now() - 7200000),
|
||||
account: {
|
||||
account_number: '1234567892',
|
||||
user: {
|
||||
username: 'user3',
|
||||
real_name: '王五'
|
||||
}
|
||||
}
|
||||
}
|
||||
].slice(0, parseInt(limit));
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '获取最近交易记录成功(模拟数据)',
|
||||
data: mockTransactions
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取最近交易记录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getDashboardStats,
|
||||
getChartData,
|
||||
getRecentTransactions
|
||||
};
|
||||
366
bank-backend/controllers/employeeController.js
Normal file
366
bank-backend/controllers/employeeController.js
Normal file
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* 员工控制器
|
||||
* @file employeeController.js
|
||||
* @description 处理员工相关的请求
|
||||
*/
|
||||
const { Employee, Department, Position } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取员工列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getEmployees = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
department = '',
|
||||
position = '',
|
||||
status = '',
|
||||
sortBy = 'created_at',
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const whereClause = {};
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ name: { [Op.like]: `%${search}%` } },
|
||||
{ employee_id: { [Op.like]: `%${search}%` } },
|
||||
{ phone: { [Op.like]: `%${search}%` } },
|
||||
{ email: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 部门筛选
|
||||
if (department) {
|
||||
whereClause.department_id = department;
|
||||
}
|
||||
|
||||
// 职位筛选
|
||||
if (position) {
|
||||
whereClause.position_id = position;
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const { count, rows: employees } = await Employee.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['id', 'name', 'level']
|
||||
}
|
||||
],
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取员工列表成功',
|
||||
data: {
|
||||
employees,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createEmployee = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
employee_id,
|
||||
department_id,
|
||||
position_id,
|
||||
phone,
|
||||
email,
|
||||
hire_date,
|
||||
salary,
|
||||
status = 'active'
|
||||
} = req.body;
|
||||
|
||||
// 检查员工编号是否已存在
|
||||
const existingEmployee = await Employee.findOne({
|
||||
where: { employee_id }
|
||||
});
|
||||
|
||||
if (existingEmployee) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '员工编号已存在'
|
||||
});
|
||||
}
|
||||
|
||||
const employee = await Employee.create({
|
||||
name,
|
||||
employee_id,
|
||||
department_id,
|
||||
position_id,
|
||||
phone,
|
||||
email,
|
||||
hire_date,
|
||||
salary: salary * 100, // 转换为分
|
||||
status
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建员工成功',
|
||||
data: employee
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建员工错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取员工详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getEmployeeById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const employee = await Employee.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['id', 'name', 'description']
|
||||
},
|
||||
{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['id', 'name', 'level', 'description']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '员工不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取员工详情成功',
|
||||
data: employee
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateEmployee = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
// 如果更新薪资,转换为分
|
||||
if (updateData.salary) {
|
||||
updateData.salary = updateData.salary * 100;
|
||||
}
|
||||
|
||||
const employee = await Employee.findByPk(id);
|
||||
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '员工不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await employee.update(updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新员工成功',
|
||||
data: employee
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新员工错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deleteEmployee = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const employee = await Employee.findByPk(id);
|
||||
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '员工不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await employee.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除员工成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除员工错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取员工统计
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getEmployeeStats = async (req, res) => {
|
||||
try {
|
||||
const totalEmployees = await Employee.count();
|
||||
const activeEmployees = await Employee.count({ where: { status: 'active' } });
|
||||
const inactiveEmployees = await Employee.count({ where: { status: 'inactive' } });
|
||||
|
||||
const departmentStats = await Employee.findAll({
|
||||
attributes: [
|
||||
'department_id',
|
||||
[Employee.sequelize.fn('COUNT', Employee.sequelize.col('id')), 'count']
|
||||
],
|
||||
include: [{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['name']
|
||||
}],
|
||||
group: ['department_id', 'department.id'],
|
||||
raw: false
|
||||
});
|
||||
|
||||
const positionStats = await Employee.findAll({
|
||||
attributes: [
|
||||
'position_id',
|
||||
[Employee.sequelize.fn('COUNT', Employee.sequelize.col('id')), 'count']
|
||||
],
|
||||
include: [{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['name', 'level']
|
||||
}],
|
||||
group: ['position_id', 'position.id'],
|
||||
raw: false
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取员工统计成功',
|
||||
data: {
|
||||
total: totalEmployees,
|
||||
active: activeEmployees,
|
||||
inactive: inactiveEmployees,
|
||||
departmentStats: departmentStats.map(item => ({
|
||||
department: item.department.name,
|
||||
count: parseInt(item.dataValues.count)
|
||||
})),
|
||||
positionStats: positionStats.map(item => ({
|
||||
position: item.position.name,
|
||||
level: item.position.level,
|
||||
count: parseInt(item.dataValues.count)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工统计错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
363
bank-backend/controllers/loanProductController.js
Normal file
363
bank-backend/controllers/loanProductController.js
Normal file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* 贷款产品控制器
|
||||
* @file loanProductController.js
|
||||
* @description 处理贷款产品相关的请求
|
||||
*/
|
||||
const { LoanProduct } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取贷款产品列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProducts = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
status = '',
|
||||
type = '',
|
||||
sortBy = 'created_at',
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const whereClause = {};
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ name: { [Op.like]: `%${search}%` } },
|
||||
{ code: { [Op.like]: `%${search}%` } },
|
||||
{ description: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
if (type) {
|
||||
whereClause.type = type;
|
||||
}
|
||||
|
||||
const { count, rows: products } = await LoanProduct.findAndCountAll({
|
||||
where: whereClause,
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品列表成功',
|
||||
data: {
|
||||
products,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
code,
|
||||
type,
|
||||
description,
|
||||
min_amount,
|
||||
max_amount,
|
||||
interest_rate,
|
||||
term_min,
|
||||
term_max,
|
||||
requirements,
|
||||
status = 'draft'
|
||||
} = req.body;
|
||||
|
||||
// 检查产品代码是否已存在
|
||||
const existingProduct = await LoanProduct.findOne({
|
||||
where: { code }
|
||||
});
|
||||
|
||||
if (existingProduct) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '产品代码已存在'
|
||||
});
|
||||
}
|
||||
|
||||
const product = await LoanProduct.create({
|
||||
name,
|
||||
code,
|
||||
type,
|
||||
description,
|
||||
min_amount: min_amount * 100, // 转换为分
|
||||
max_amount: max_amount * 100,
|
||||
interest_rate,
|
||||
term_min,
|
||||
term_max,
|
||||
requirements,
|
||||
status
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建贷款产品成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建贷款产品错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取贷款产品详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProductById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品详情成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
// 如果更新金额,转换为分
|
||||
if (updateData.min_amount) {
|
||||
updateData.min_amount = updateData.min_amount * 100;
|
||||
}
|
||||
if (updateData.max_amount) {
|
||||
updateData.max_amount = updateData.max_amount * 100;
|
||||
}
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.update(updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新贷款产品成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新贷款产品错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deleteLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除贷款产品成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除贷款产品错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新贷款产品状态
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateLoanProductStatus = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.update({ status });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新贷款产品状态成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新贷款产品状态错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取贷款产品统计
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProductStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await LoanProduct.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[LoanProduct.sequelize.fn('COUNT', LoanProduct.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const typeStats = await LoanProduct.findAll({
|
||||
attributes: [
|
||||
'type',
|
||||
[LoanProduct.sequelize.fn('COUNT', LoanProduct.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品统计成功',
|
||||
data: {
|
||||
statusStats: stats,
|
||||
typeStats: typeStats
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品统计错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
546
bank-backend/controllers/reportController.js
Normal file
546
bank-backend/controllers/reportController.js
Normal file
@@ -0,0 +1,546 @@
|
||||
const { Transaction, Account, User, Report } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const ExcelJS = require('exceljs');
|
||||
const PDFDocument = require('pdfkit');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class ReportController {
|
||||
/**
|
||||
* 生成报表
|
||||
*/
|
||||
static async generateReport(req, res) {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '参数验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { reportType, dateRange, transactionType, format } = req.body;
|
||||
const userId = req.user.id;
|
||||
const [startDate, endDate] = dateRange;
|
||||
|
||||
// 创建报表记录
|
||||
const report = await Report.create({
|
||||
name: `${this.getReportTypeName(reportType)}_${new Date().toISOString().split('T')[0]}`,
|
||||
type: reportType,
|
||||
format: format,
|
||||
status: 'processing',
|
||||
createdBy: userId,
|
||||
parameters: {
|
||||
dateRange,
|
||||
transactionType,
|
||||
format
|
||||
}
|
||||
});
|
||||
|
||||
// 异步生成报表
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
const reportData = await this.generateReportData(reportType, startDate, endDate, transactionType);
|
||||
const filePath = await this.exportReport(reportData, format, report.id);
|
||||
|
||||
await report.update({
|
||||
status: 'completed',
|
||||
filePath: filePath,
|
||||
data: reportData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('报表生成失败:', error);
|
||||
await report.update({
|
||||
status: 'failed',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '报表生成中,请稍后查看',
|
||||
data: {
|
||||
reportId: report.id,
|
||||
status: 'processing'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '生成报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报表历史
|
||||
*/
|
||||
static async getReportHistory(req, res) {
|
||||
try {
|
||||
const { page = 1, pageSize = 10, type, status } = req.query;
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
const where = {};
|
||||
if (type) where.type = type;
|
||||
if (status) where.status = status;
|
||||
|
||||
const { count, rows: reports } = await Report.findAndCountAll({
|
||||
where,
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: parseInt(pageSize),
|
||||
offset: parseInt(offset),
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['username', 'real_name']
|
||||
}]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
reports: reports.map(report => ({
|
||||
id: report.id,
|
||||
name: report.name,
|
||||
type: report.type,
|
||||
format: report.format,
|
||||
status: report.status,
|
||||
createdAt: report.createdAt,
|
||||
createdBy: report.creator ?
|
||||
`${report.creator.real_name || report.creator.username} (${report.creator.role})` :
|
||||
'未知用户',
|
||||
filePath: report.filePath
|
||||
})),
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
total: count
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取报表历史失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取报表历史失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载报表
|
||||
*/
|
||||
static async downloadReport(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const report = await Report.findByPk(id);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '报表不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (report.status !== 'completed') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '报表尚未生成完成'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(__dirname, '..', 'uploads', 'reports', report.filePath);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '报表文件不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.download(filePath, `${report.name}.${report.format}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('下载报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '下载报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览报表
|
||||
*/
|
||||
static async previewReport(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const report = await Report.findByPk(id);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '报表不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (report.status !== 'completed') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '报表尚未生成完成'
|
||||
});
|
||||
}
|
||||
|
||||
// 返回报表数据的前100条用于预览
|
||||
const previewData = report.data ? report.data.slice(0, 100) : [];
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
name: report.name,
|
||||
type: report.type,
|
||||
format: report.format,
|
||||
createdAt: report.createdAt,
|
||||
data: previewData
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('预览报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '预览报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除报表
|
||||
*/
|
||||
static async deleteReport(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const report = await Report.findByPk(id);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '报表不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
if (report.filePath) {
|
||||
const filePath = path.join(__dirname, '..', 'uploads', 'reports', report.filePath);
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
await report.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '报表删除成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除报表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除报表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报表统计
|
||||
*/
|
||||
static async getReportStats(req, res) {
|
||||
try {
|
||||
const stats = await Report.findAll({
|
||||
attributes: [
|
||||
'type',
|
||||
'status',
|
||||
[Report.sequelize.fn('COUNT', Report.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['type', 'status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const formattedStats = {
|
||||
total: 0,
|
||||
byType: {},
|
||||
byStatus: {}
|
||||
};
|
||||
|
||||
stats.forEach(stat => {
|
||||
const count = parseInt(stat.count);
|
||||
formattedStats.total += count;
|
||||
|
||||
if (!formattedStats.byType[stat.type]) {
|
||||
formattedStats.byType[stat.type] = 0;
|
||||
}
|
||||
formattedStats.byType[stat.type] += count;
|
||||
|
||||
if (!formattedStats.byStatus[stat.status]) {
|
||||
formattedStats.byStatus[stat.status] = 0;
|
||||
}
|
||||
formattedStats.byStatus[stat.status] += count;
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: formattedStats
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取报表统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取报表统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成报表数据
|
||||
*/
|
||||
static async generateReportData(reportType, startDate, endDate, transactionType) {
|
||||
const where = {
|
||||
createdAt: {
|
||||
[Op.between]: [startDate, endDate]
|
||||
}
|
||||
};
|
||||
|
||||
if (transactionType) {
|
||||
where.type = transactionType;
|
||||
}
|
||||
|
||||
switch (reportType) {
|
||||
case 'transaction':
|
||||
return await this.generateTransactionReport(where);
|
||||
case 'account':
|
||||
return await this.generateAccountReport(where);
|
||||
case 'user':
|
||||
return await this.generateUserReport(where);
|
||||
default:
|
||||
throw new Error('不支持的报表类型');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成交易报表数据
|
||||
*/
|
||||
static async generateTransactionReport(where) {
|
||||
const transactions = await Transaction.findAll({
|
||||
where,
|
||||
include: [{
|
||||
model: Account,
|
||||
as: 'account',
|
||||
attributes: ['account_number', 'account_type']
|
||||
}],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return transactions.map(t => ({
|
||||
id: t.id,
|
||||
date: t.createdAt.toISOString().split('T')[0],
|
||||
type: t.type,
|
||||
amount: t.amount,
|
||||
account: t.account ? t.account.account_number : 'N/A',
|
||||
description: t.description,
|
||||
status: t.status
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成账户报表数据
|
||||
*/
|
||||
static async generateAccountReport(where) {
|
||||
const accounts = await Account.findAll({
|
||||
where: {
|
||||
createdAt: {
|
||||
[Op.between]: [where.createdAt[Op.between][0], where.createdAt[Op.between][1]]
|
||||
}
|
||||
},
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['username', 'real_name']
|
||||
}],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return accounts.map(a => ({
|
||||
id: a.id,
|
||||
accountNumber: a.account_number,
|
||||
accountType: a.account_type,
|
||||
balance: a.balance,
|
||||
owner: a.user ? a.user.real_name || a.user.username : 'N/A',
|
||||
createdAt: a.createdAt.toISOString().split('T')[0],
|
||||
status: a.status
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户报表数据
|
||||
*/
|
||||
static async generateUserReport(where) {
|
||||
const users = await User.findAll({
|
||||
where: {
|
||||
createdAt: {
|
||||
[Op.between]: [where.createdAt[Op.between][0], where.createdAt[Op.between][1]]
|
||||
}
|
||||
},
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return users.map(u => ({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
realName: u.real_name,
|
||||
email: u.email,
|
||||
phone: u.phone,
|
||||
role: u.role,
|
||||
status: u.status,
|
||||
createdAt: u.createdAt.toISOString().split('T')[0]
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出报表文件
|
||||
*/
|
||||
static async exportReport(data, format, reportId) {
|
||||
const fileName = `report_${reportId}_${Date.now()}`;
|
||||
const uploadsDir = path.join(__dirname, '..', 'uploads', 'reports');
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(uploadsDir)) {
|
||||
fs.mkdirSync(uploadsDir, { recursive: true });
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case 'excel':
|
||||
return await this.exportToExcel(data, uploadsDir, fileName);
|
||||
case 'pdf':
|
||||
return await this.exportToPDF(data, uploadsDir, fileName);
|
||||
case 'csv':
|
||||
return await this.exportToCSV(data, uploadsDir, fileName);
|
||||
default:
|
||||
throw new Error('不支持的导出格式');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为Excel
|
||||
*/
|
||||
static async exportToExcel(data, uploadsDir, fileName) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('报表数据');
|
||||
|
||||
if (data.length > 0) {
|
||||
const headers = Object.keys(data[0]);
|
||||
worksheet.addRow(headers);
|
||||
|
||||
data.forEach(row => {
|
||||
worksheet.addRow(Object.values(row));
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadsDir, `${fileName}.xlsx`);
|
||||
await workbook.xlsx.writeFile(filePath);
|
||||
return `${fileName}.xlsx`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为PDF
|
||||
*/
|
||||
static async exportToPDF(data, uploadsDir, fileName) {
|
||||
const doc = new PDFDocument();
|
||||
const filePath = path.join(uploadsDir, `${fileName}.pdf`);
|
||||
const stream = fs.createWriteStream(filePath);
|
||||
doc.pipe(stream);
|
||||
|
||||
doc.fontSize(16).text('银行报表', { align: 'center' });
|
||||
doc.moveDown();
|
||||
|
||||
if (data.length > 0) {
|
||||
const headers = Object.keys(data[0]);
|
||||
let tableData = [headers];
|
||||
|
||||
data.forEach(row => {
|
||||
tableData.push(Object.values(row));
|
||||
});
|
||||
|
||||
// 简单的表格实现
|
||||
tableData.forEach((row, index) => {
|
||||
if (index === 0) {
|
||||
doc.fontSize(12).font('Helvetica-Bold');
|
||||
} else {
|
||||
doc.fontSize(10).font('Helvetica');
|
||||
}
|
||||
|
||||
doc.text(row.join(' | '));
|
||||
doc.moveDown(0.5);
|
||||
});
|
||||
}
|
||||
|
||||
doc.end();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('finish', () => resolve(`${fileName}.pdf`));
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为CSV
|
||||
*/
|
||||
static async exportToCSV(data, uploadsDir, fileName) {
|
||||
const filePath = path.join(uploadsDir, `${fileName}.csv`);
|
||||
|
||||
if (data.length === 0) {
|
||||
fs.writeFileSync(filePath, '');
|
||||
return `${fileName}.csv`;
|
||||
}
|
||||
|
||||
const headers = Object.keys(data[0]);
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...data.map(row =>
|
||||
Object.values(row).map(value =>
|
||||
typeof value === 'string' && value.includes(',') ? `"${value}"` : value
|
||||
).join(',')
|
||||
)
|
||||
].join('\n');
|
||||
|
||||
fs.writeFileSync(filePath, csvContent, 'utf8');
|
||||
return `${fileName}.csv`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报表类型名称
|
||||
*/
|
||||
static getReportTypeName(type) {
|
||||
const names = {
|
||||
transaction: '交易报表',
|
||||
account: '账户报表',
|
||||
user: '用户报表'
|
||||
};
|
||||
return names[type] || type;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReportController;
|
||||
@@ -7,6 +7,7 @@ const { User, Role, Account } = require('../models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
@@ -445,4 +446,278 @@ exports.getUserAccounts = async (req, res) => {
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建用户(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createUser = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { username, email, password, phone, real_name, id_card, role_id } = req.body;
|
||||
|
||||
// 检查用户名是否已存在
|
||||
const existingUser = await User.findOne({
|
||||
where: { username }
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名已存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
const existingEmail = await User.findOne({
|
||||
where: { email }
|
||||
});
|
||||
|
||||
if (existingEmail) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '邮箱已被注册'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
const user = await User.create({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
phone,
|
||||
real_name,
|
||||
id_card,
|
||||
role_id: role_id || 2 // 默认为普通用户
|
||||
});
|
||||
|
||||
// 获取用户信息(包含角色)
|
||||
const userWithRole = await User.findByPk(user.id, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '用户创建成功',
|
||||
data: userWithRole.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户信息(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateUser = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { userId } = req.params;
|
||||
const { username, email, phone, real_name, id_card, role_id, status } = req.body;
|
||||
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户名是否被其他用户使用
|
||||
if (username && username !== user.username) {
|
||||
const existingUser = await User.findOne({
|
||||
where: { username, id: { [Op.ne]: userId } }
|
||||
});
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名已被其他用户使用'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 检查邮箱是否被其他用户使用
|
||||
if (email && email !== user.email) {
|
||||
const existingEmail = await User.findOne({
|
||||
where: { email, id: { [Op.ne]: userId } }
|
||||
});
|
||||
if (existingEmail) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '邮箱已被其他用户使用'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
await user.update({
|
||||
username: username || user.username,
|
||||
email: email || user.email,
|
||||
phone: phone || user.phone,
|
||||
real_name: real_name || user.real_name,
|
||||
id_card: id_card || user.id_card,
|
||||
role_id: role_id || user.role_id,
|
||||
status: status || user.status
|
||||
});
|
||||
|
||||
// 获取更新后的用户信息(包含角色)
|
||||
const updatedUser = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户信息更新成功',
|
||||
data: updatedUser.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除用户(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deleteUser = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否是当前用户
|
||||
if (userId === req.user.userId.toString()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '不能删除自己的账户'
|
||||
});
|
||||
}
|
||||
|
||||
// 软删除用户(更新状态为inactive)
|
||||
await user.update({ status: 'inactive' });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置用户密码(管理员)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.resetPassword = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const { newPassword } = req.body;
|
||||
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '密码重置成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('重置密码错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getUserById = async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取用户详情成功',
|
||||
data: user.getSafeInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
70
bank-backend/debug-accounts.js
Normal file
70
bank-backend/debug-accounts.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const http = require('http');
|
||||
|
||||
async function testAccountsAPI() {
|
||||
try {
|
||||
// 先登录获取token
|
||||
const loginResult = await makeRequest({
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/auth/login',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
if (!loginResult.data.success) {
|
||||
console.log('登录失败:', loginResult.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const token = loginResult.data.data.token;
|
||||
console.log('登录成功,token:', token.substring(0, 20) + '...');
|
||||
|
||||
// 测试账户API
|
||||
const accountsResult = await makeRequest({
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/accounts?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
console.log('账户API响应状态:', accountsResult.status);
|
||||
console.log('账户API响应数据:', JSON.stringify(accountsResult.data, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function makeRequest(options, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const result = JSON.parse(body);
|
||||
resolve({ status: res.statusCode, data: result });
|
||||
} catch (error) {
|
||||
resolve({ status: res.statusCode, data: body });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (data) {
|
||||
req.write(JSON.stringify(data));
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
testAccountsAPI();
|
||||
@@ -1,43 +1,44 @@
|
||||
/**
|
||||
* 认证中间件
|
||||
* @file auth.js
|
||||
* @description 处理用户认证和授权
|
||||
* @description JWT认证中间件
|
||||
*/
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { User, Role } = require('../models');
|
||||
const { User } = require('../models');
|
||||
|
||||
/**
|
||||
* 验证JWT令牌
|
||||
* JWT认证中间件
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
* @param {Function} next 下一个中间件
|
||||
*/
|
||||
const verifyToken = async (req, res, next) => {
|
||||
const authMiddleware = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization')?.replace('Bearer ', '');
|
||||
// 从请求头获取token
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!token) {
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '访问被拒绝,未提供令牌'
|
||||
message: '未提供认证令牌'
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const user = await User.findByPk(decoded.id, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
const token = authHeader.substring(7); // 移除 'Bearer ' 前缀
|
||||
|
||||
// 验证token
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key_here');
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(decoded.userId);
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '令牌无效,用户不存在'
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
@@ -45,54 +46,57 @@ const verifyToken = async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
// 将用户信息添加到请求对象
|
||||
req.user = {
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: decoded.role
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '无效的认证令牌'
|
||||
});
|
||||
}
|
||||
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '令牌已过期'
|
||||
});
|
||||
} else if (error.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '令牌无效'
|
||||
});
|
||||
} else {
|
||||
console.error('认证中间件错误:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
message: '认证令牌已过期'
|
||||
});
|
||||
}
|
||||
|
||||
console.error('认证中间件错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查用户角色权限
|
||||
* @param {String|Array} roles 允许的角色
|
||||
* 角色权限中间件
|
||||
* @param {Array|String} roles 允许的角色
|
||||
* @returns {Function} 中间件函数
|
||||
*/
|
||||
const requireRole = (roles) => {
|
||||
return async (req, res, next) => {
|
||||
const roleMiddleware = (roles) => {
|
||||
return (req, res, next) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
const userRole = req.user?.role;
|
||||
|
||||
if (!userRole) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '请先登录'
|
||||
});
|
||||
}
|
||||
|
||||
const userRole = req.user.role;
|
||||
if (!userRole) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '用户角色未分配'
|
||||
message: '未认证用户'
|
||||
});
|
||||
}
|
||||
|
||||
const allowedRoles = Array.isArray(roles) ? roles : [roles];
|
||||
if (!allowedRoles.includes(userRole.name)) {
|
||||
|
||||
if (!allowedRoles.includes(userRole)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足'
|
||||
@@ -101,8 +105,8 @@ const requireRole = (roles) => {
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('角色权限检查错误:', error);
|
||||
return res.status(500).json({
|
||||
console.error('角色权限中间件错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
@@ -111,116 +115,24 @@ const requireRole = (roles) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查用户权限级别
|
||||
* @param {Number} minLevel 最小权限级别
|
||||
* @returns {Function} 中间件函数
|
||||
* 管理员权限中间件
|
||||
*/
|
||||
const requireLevel = (minLevel) => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '请先登录'
|
||||
});
|
||||
}
|
||||
|
||||
const userRole = req.user.role;
|
||||
if (!userRole || userRole.level < minLevel) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限级别不足'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('权限级别检查错误:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
const adminMiddleware = roleMiddleware(['admin']);
|
||||
|
||||
/**
|
||||
* 可选认证中间件(不强制要求登录)
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
* @param {Function} next 下一个中间件
|
||||
* 管理员或经理权限中间件
|
||||
*/
|
||||
const optionalAuth = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
if (token) {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const user = await User.findByPk(decoded.id, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (user && user.status === 'active') {
|
||||
req.user = user;
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
// 可选认证失败时不返回错误,继续执行
|
||||
next();
|
||||
}
|
||||
};
|
||||
const managerMiddleware = roleMiddleware(['admin', 'manager']);
|
||||
|
||||
/**
|
||||
* 检查账户所有权
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
* @param {Function} next 下一个中间件
|
||||
* 管理员、经理或柜员权限中间件
|
||||
*/
|
||||
const checkAccountOwnership = async (req, res, next) => {
|
||||
try {
|
||||
const { accountId } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 管理员可以访问所有账户
|
||||
if (req.user.role && req.user.role.name === 'admin') {
|
||||
return next();
|
||||
}
|
||||
|
||||
const { Account } = require('../models');
|
||||
const account = await Account.findOne({
|
||||
where: {
|
||||
id: accountId,
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '无权访问该账户'
|
||||
});
|
||||
}
|
||||
|
||||
req.account = account;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('账户所有权检查错误:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
const tellerMiddleware = roleMiddleware(['admin', 'manager', 'teller']);
|
||||
|
||||
module.exports = {
|
||||
verifyToken,
|
||||
requireRole,
|
||||
requireLevel,
|
||||
optionalAuth,
|
||||
checkAccountOwnership
|
||||
authMiddleware,
|
||||
roleMiddleware,
|
||||
adminMiddleware,
|
||||
managerMiddleware,
|
||||
tellerMiddleware
|
||||
};
|
||||
85
bank-backend/migrations/20241220000001-create-reports.js
Normal file
85
bank-backend/migrations/20241220000001-create-reports.js
Normal file
@@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('reports', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '报表名称'
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('transaction', 'account', 'user'),
|
||||
allowNull: false,
|
||||
comment: '报表类型'
|
||||
},
|
||||
format: {
|
||||
type: Sequelize.ENUM('excel', 'pdf', 'csv'),
|
||||
allowNull: false,
|
||||
comment: '报表格式'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('processing', 'completed', 'failed'),
|
||||
allowNull: false,
|
||||
defaultValue: 'processing',
|
||||
comment: '报表状态'
|
||||
},
|
||||
filePath: {
|
||||
type: Sequelize.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '文件路径'
|
||||
},
|
||||
parameters: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: true,
|
||||
comment: '生成参数'
|
||||
},
|
||||
data: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: true,
|
||||
comment: '报表数据'
|
||||
},
|
||||
error: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '错误信息'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
}
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('reports', ['type', 'status']);
|
||||
await queryInterface.addIndex('reports', ['createdBy']);
|
||||
await queryInterface.addIndex('reports', ['createdAt']);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('reports');
|
||||
}
|
||||
};
|
||||
@@ -13,9 +13,11 @@ class BaseModel extends Model {
|
||||
*/
|
||||
getSafeInfo(excludeFields = ['password', 'pin', 'secret']) {
|
||||
const data = this.get({ plain: true });
|
||||
excludeFields.forEach(field => {
|
||||
delete data[field];
|
||||
});
|
||||
if (Array.isArray(excludeFields)) {
|
||||
excludeFields.forEach(field => {
|
||||
delete data[field];
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
55
bank-backend/models/Department.js
Normal file
55
bank-backend/models/Department.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 部门模型
|
||||
* @file Department.js
|
||||
* @description 部门数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const Department = sequelize.define('Department', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '部门名称'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '部门描述'
|
||||
},
|
||||
manager_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '部门经理ID'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '部门状态'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'bank_departments',
|
||||
modelName: 'Department',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
module.exports = Department;
|
||||
82
bank-backend/models/Employee.js
Normal file
82
bank-backend/models/Employee.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 员工模型
|
||||
* @file Employee.js
|
||||
* @description 员工数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const Employee = sequelize.define('Employee', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '员工姓名'
|
||||
},
|
||||
employee_id: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '员工编号'
|
||||
},
|
||||
department_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '部门ID'
|
||||
},
|
||||
position_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '职位ID'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
comment: '联系电话'
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '邮箱地址'
|
||||
},
|
||||
hire_date: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '入职日期'
|
||||
},
|
||||
salary: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '薪资(分)'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive', 'resigned'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '员工状态:在职、离职、已辞职'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'bank_employees',
|
||||
modelName: 'Employee',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
module.exports = Employee;
|
||||
93
bank-backend/models/LoanProduct.js
Normal file
93
bank-backend/models/LoanProduct.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 贷款产品模型
|
||||
* @file LoanProduct.js
|
||||
* @description 贷款产品数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const LoanProduct = sequelize.define('LoanProduct', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称'
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '产品代码'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('personal', 'business', 'mortgage', 'credit'),
|
||||
allowNull: false,
|
||||
comment: '产品类型:个人贷款、企业贷款、抵押贷款、信用贷款'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '产品描述'
|
||||
},
|
||||
min_amount: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '最小贷款金额(分)'
|
||||
},
|
||||
max_amount: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '最大贷款金额(分)'
|
||||
},
|
||||
interest_rate: {
|
||||
type: DataTypes.DECIMAL(5, 4),
|
||||
allowNull: false,
|
||||
comment: '年化利率'
|
||||
},
|
||||
term_min: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '最短期限(月)'
|
||||
},
|
||||
term_max: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '最长期限(月)'
|
||||
},
|
||||
requirements: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: '申请要求(JSON格式)'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('draft', 'active', 'inactive'),
|
||||
allowNull: false,
|
||||
defaultValue: 'draft',
|
||||
comment: '产品状态:草稿、启用、停用'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'bank_loan_products',
|
||||
modelName: 'LoanProduct',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
module.exports = LoanProduct;
|
||||
67
bank-backend/models/Position.js
Normal file
67
bank-backend/models/Position.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 职位模型
|
||||
* @file Position.js
|
||||
* @description 职位数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const Position = sequelize.define('Position', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '职位名称'
|
||||
},
|
||||
level: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '职位级别'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '职位描述'
|
||||
},
|
||||
min_salary: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '最低薪资(分)'
|
||||
},
|
||||
max_salary: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '最高薪资(分)'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '职位状态'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'bank_positions',
|
||||
modelName: 'Position',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
module.exports = Position;
|
||||
86
bank-backend/models/Report.js
Normal file
86
bank-backend/models/Report.js
Normal file
@@ -0,0 +1,86 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
const BaseModel = require('./BaseModel');
|
||||
|
||||
class Report extends BaseModel {
|
||||
static associate(models) {
|
||||
// 报表属于用户
|
||||
Report.belongsTo(models.User, {
|
||||
as: 'creator',
|
||||
foreignKey: 'createdBy',
|
||||
targetKey: 'id'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Report.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '报表名称'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('transaction', 'account', 'user'),
|
||||
allowNull: false,
|
||||
comment: '报表类型'
|
||||
},
|
||||
format: {
|
||||
type: DataTypes.ENUM('excel', 'pdf', 'csv'),
|
||||
allowNull: false,
|
||||
comment: '报表格式'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('processing', 'completed', 'failed'),
|
||||
allowNull: false,
|
||||
defaultValue: 'processing',
|
||||
comment: '报表状态'
|
||||
},
|
||||
filePath: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '文件路径'
|
||||
},
|
||||
parameters: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: '生成参数'
|
||||
},
|
||||
data: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: '报表数据'
|
||||
},
|
||||
error: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '错误信息'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Report',
|
||||
tableName: 'reports',
|
||||
comment: '报表记录表',
|
||||
indexes: [
|
||||
{
|
||||
fields: ['type', 'status']
|
||||
},
|
||||
{
|
||||
fields: ['createdBy']
|
||||
},
|
||||
{
|
||||
fields: ['createdAt']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
module.exports = Report;
|
||||
@@ -10,6 +10,11 @@ const User = require('./User');
|
||||
const Role = require('./Role');
|
||||
const Account = require('./Account');
|
||||
const Transaction = require('./Transaction');
|
||||
const LoanProduct = require('./LoanProduct');
|
||||
const Employee = require('./Employee');
|
||||
const Department = require('./Department');
|
||||
const Position = require('./Position');
|
||||
const Report = require('./Report');
|
||||
|
||||
// 定义模型关联关系
|
||||
|
||||
@@ -50,11 +55,52 @@ Transaction.belongsTo(Account, {
|
||||
// 交易记录与用户关联(通过账户)
|
||||
// 移除不合理的Transaction->User through Account的belongsTo定义,避免错误外键映射
|
||||
|
||||
// 员工与部门关联
|
||||
Employee.belongsTo(Department, {
|
||||
foreignKey: 'department_id',
|
||||
as: 'department',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
Department.hasMany(Employee, {
|
||||
foreignKey: 'department_id',
|
||||
as: 'employees'
|
||||
});
|
||||
|
||||
// 员工与职位关联
|
||||
Employee.belongsTo(Position, {
|
||||
foreignKey: 'position_id',
|
||||
as: 'position',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
Position.hasMany(Employee, {
|
||||
foreignKey: 'position_id',
|
||||
as: 'employees'
|
||||
});
|
||||
|
||||
// 报表与用户关联
|
||||
Report.belongsTo(User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(Report, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'reports'
|
||||
});
|
||||
|
||||
// 导出所有模型和数据库实例
|
||||
module.exports = {
|
||||
sequelize,
|
||||
User,
|
||||
Role,
|
||||
Account,
|
||||
Transaction
|
||||
Transaction,
|
||||
LoanProduct,
|
||||
Employee,
|
||||
Department,
|
||||
Position,
|
||||
Report
|
||||
};
|
||||
872
bank-backend/package-lock.json
generated
872
bank-backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"ejs": "^3.1.9",
|
||||
"exceljs": "^4.4.0",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"express-validator": "^7.0.1",
|
||||
@@ -55,26 +56,28 @@
|
||||
"mysql2": "^3.6.5",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^6.9.8",
|
||||
"pdfkit": "^0.17.2",
|
||||
"redis": "^4.6.12",
|
||||
"sequelize": "^6.35.2",
|
||||
"sharp": "^0.33.2",
|
||||
"socket.io": "^4.7.4",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"tslib": "^2.8.1",
|
||||
"winston": "^3.11.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2",
|
||||
"@types/jest": "^29.5.8",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"supertest": "^6.3.3",
|
||||
"nodemon": "^3.0.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"@types/jest": "^29.5.8"
|
||||
"supertest": "^6.3.3"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
@@ -88,10 +91,16 @@
|
||||
"!**/seeds/**"
|
||||
],
|
||||
"coverageDirectory": "coverage",
|
||||
"coverageReporters": ["text", "lcov", "html"]
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"lcov",
|
||||
"html"
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": ["standard"],
|
||||
"extends": [
|
||||
"standard"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
"es2021": true,
|
||||
@@ -111,4 +120,4 @@
|
||||
"url": "https://github.com/bank-management/bank-backend/issues"
|
||||
},
|
||||
"homepage": "https://github.com/bank-management/bank-backend#readme"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const express = require('express');
|
||||
const { verifyToken, requireRole, checkAccountOwnership } = require('../middleware/auth');
|
||||
const { authMiddleware, roleMiddleware, adminMiddleware, managerMiddleware, tellerMiddleware } = require('../middleware/auth');
|
||||
const {
|
||||
validateAccountNumber,
|
||||
validateAmount,
|
||||
@@ -92,8 +92,8 @@ const accountController = require('../controllers/accountController');
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.post('/',
|
||||
verifyToken,
|
||||
requireRole(['admin', 'manager']),
|
||||
authMiddleware,
|
||||
roleMiddleware(['admin', 'manager']),
|
||||
accountController.createAccount
|
||||
);
|
||||
|
||||
@@ -142,7 +142,7 @@ router.post('/',
|
||||
* description: 未授权
|
||||
*/
|
||||
router.get('/',
|
||||
verifyToken,
|
||||
authMiddleware,
|
||||
accountController.getAccounts
|
||||
);
|
||||
|
||||
@@ -172,8 +172,7 @@ router.get('/',
|
||||
* description: 账户不存在
|
||||
*/
|
||||
router.get('/:accountId',
|
||||
verifyToken,
|
||||
checkAccountOwnership,
|
||||
authMiddleware,
|
||||
accountController.getAccountDetail
|
||||
);
|
||||
|
||||
@@ -216,8 +215,8 @@ router.get('/:accountId',
|
||||
* description: 账户不存在
|
||||
*/
|
||||
router.put('/:accountId/status',
|
||||
verifyToken,
|
||||
requireRole(['admin', 'manager']),
|
||||
authMiddleware,
|
||||
roleMiddleware(['admin', 'manager']),
|
||||
accountController.updateAccountStatus
|
||||
);
|
||||
|
||||
@@ -264,8 +263,8 @@ router.put('/:accountId/status',
|
||||
* description: 账户不存在
|
||||
*/
|
||||
router.post('/:accountId/deposit',
|
||||
verifyToken,
|
||||
requireRole(['admin', 'manager', 'teller']),
|
||||
authMiddleware,
|
||||
roleMiddleware(['admin', 'manager', 'teller']),
|
||||
validateAmount,
|
||||
accountController.deposit
|
||||
);
|
||||
@@ -313,8 +312,8 @@ router.post('/:accountId/deposit',
|
||||
* description: 账户不存在
|
||||
*/
|
||||
router.post('/:accountId/withdraw',
|
||||
verifyToken,
|
||||
requireRole(['admin', 'manager', 'teller']),
|
||||
authMiddleware,
|
||||
roleMiddleware(['admin', 'manager', 'teller']),
|
||||
validateAmount,
|
||||
accountController.withdraw
|
||||
);
|
||||
|
||||
165
bank-backend/routes/auth.js
Normal file
165
bank-backend/routes/auth.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 认证路由
|
||||
* @file auth.js
|
||||
* @description 认证相关的路由定义
|
||||
*/
|
||||
const express = require('express');
|
||||
const { body } = require('express-validator');
|
||||
const authController = require('../controllers/authController');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 登录验证规则
|
||||
const loginValidation = [
|
||||
body('username')
|
||||
.notEmpty()
|
||||
.withMessage('用户名不能为空')
|
||||
.isLength({ min: 3, max: 50 })
|
||||
.withMessage('用户名长度必须在3-50个字符之间'),
|
||||
body('password')
|
||||
.notEmpty()
|
||||
.withMessage('密码不能为空')
|
||||
.isLength({ min: 6 })
|
||||
.withMessage('密码长度不能少于6个字符')
|
||||
];
|
||||
|
||||
// 修改密码验证规则
|
||||
const changePasswordValidation = [
|
||||
body('oldPassword')
|
||||
.notEmpty()
|
||||
.withMessage('原密码不能为空'),
|
||||
body('newPassword')
|
||||
.notEmpty()
|
||||
.withMessage('新密码不能为空')
|
||||
.isLength({ min: 6 })
|
||||
.withMessage('新密码长度不能少于6个字符')
|
||||
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
|
||||
.withMessage('新密码必须包含大小写字母和数字')
|
||||
];
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/login:
|
||||
* post:
|
||||
* summary: 用户登录
|
||||
* tags: [认证]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - username
|
||||
* - password
|
||||
* properties:
|
||||
* username:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* password:
|
||||
* type: string
|
||||
* description: 密码
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 登录成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* token:
|
||||
* type: string
|
||||
* user:
|
||||
* type: object
|
||||
* 401:
|
||||
* description: 登录失败
|
||||
*/
|
||||
router.post('/login', loginValidation, authController.login);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/logout:
|
||||
* post:
|
||||
* summary: 用户登出
|
||||
* tags: [认证]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 登出成功
|
||||
*/
|
||||
router.post('/logout', authMiddleware, authController.logout);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/refresh:
|
||||
* post:
|
||||
* summary: 刷新令牌
|
||||
* tags: [认证]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 令牌刷新成功
|
||||
* 401:
|
||||
* description: 令牌无效
|
||||
*/
|
||||
router.post('/refresh', authMiddleware, authController.refreshToken);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/me:
|
||||
* get:
|
||||
* summary: 获取当前用户信息
|
||||
* tags: [认证]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
*/
|
||||
router.get('/me', authMiddleware, authController.getCurrentUser);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/change-password:
|
||||
* post:
|
||||
* summary: 修改密码
|
||||
* tags: [认证]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - oldPassword
|
||||
* - newPassword
|
||||
* properties:
|
||||
* oldPassword:
|
||||
* type: string
|
||||
* description: 原密码
|
||||
* newPassword:
|
||||
* type: string
|
||||
* description: 新密码
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 密码修改成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/change-password', authMiddleware, changePasswordValidation, authController.changePassword);
|
||||
|
||||
module.exports = router;
|
||||
177
bank-backend/routes/dashboard.js
Normal file
177
bank-backend/routes/dashboard.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* 仪表盘路由
|
||||
* @file dashboard.js
|
||||
* @description 仪表盘相关的路由定义
|
||||
*/
|
||||
const express = require('express');
|
||||
const dashboardController = require('../controllers/dashboardController');
|
||||
const { authMiddleware, tellerMiddleware } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(authMiddleware);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/dashboard:
|
||||
* get:
|
||||
* summary: 获取仪表盘统计数据
|
||||
* tags: [仪表盘]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* overview:
|
||||
* type: object
|
||||
* properties:
|
||||
* totalUsers:
|
||||
* type: integer
|
||||
* totalAccounts:
|
||||
* type: integer
|
||||
* totalTransactions:
|
||||
* type: integer
|
||||
* totalBalance:
|
||||
* type: integer
|
||||
* activeUsers:
|
||||
* type: integer
|
||||
* activeAccounts:
|
||||
* type: integer
|
||||
* today:
|
||||
* type: object
|
||||
* properties:
|
||||
* transactionCount:
|
||||
* type: integer
|
||||
* transactionAmount:
|
||||
* type: integer
|
||||
* accountTypes:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* type:
|
||||
* type: string
|
||||
* count:
|
||||
* type: integer
|
||||
* totalBalance:
|
||||
* type: integer
|
||||
* trends:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* date:
|
||||
* type: string
|
||||
* count:
|
||||
* type: integer
|
||||
* totalAmount:
|
||||
* type: integer
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/', tellerMiddleware, dashboardController.getDashboardStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/dashboard/charts:
|
||||
* get:
|
||||
* summary: 获取图表数据
|
||||
* tags: [仪表盘]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [transaction_trend, account_distribution, user_growth, balance_trend]
|
||||
* description: 图表类型
|
||||
* - in: query
|
||||
* name: period
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [1d, 7d, 30d, 90d]
|
||||
* default: 7d
|
||||
* description: 时间周期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* type:
|
||||
* type: string
|
||||
* data:
|
||||
* type: array
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/charts', tellerMiddleware, dashboardController.getChartData);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/dashboard/recent-transactions:
|
||||
* get:
|
||||
* summary: 获取最近交易记录
|
||||
* tags: [仪表盘]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 返回记录数量
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/recent-transactions', tellerMiddleware, dashboardController.getRecentTransactions);
|
||||
|
||||
module.exports = router;
|
||||
316
bank-backend/routes/employees.js
Normal file
316
bank-backend/routes/employees.js
Normal file
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* 员工路由
|
||||
* @file employees.js
|
||||
* @description 员工相关的路由定义
|
||||
*/
|
||||
const express = require('express');
|
||||
const { body } = require('express-validator');
|
||||
const { authMiddleware, roleMiddleware, adminMiddleware, managerMiddleware } = require('../middleware/auth');
|
||||
const employeeController = require('../controllers/employeeController');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(authMiddleware);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Employees
|
||||
* description: 员工管理
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees:
|
||||
* get:
|
||||
* summary: 获取员工列表
|
||||
* tags: [Employees]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: department
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 部门筛选
|
||||
* - in: query
|
||||
* name: position
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 职位筛选
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [active, inactive, resigned]
|
||||
* description: 状态筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* employees:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Employee'
|
||||
* pagination:
|
||||
* $ref: '#/components/schemas/Pagination'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/', roleMiddleware(['admin', 'manager', 'teller']), employeeController.getEmployees);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees:
|
||||
* post:
|
||||
* summary: 创建员工
|
||||
* tags: [Employees]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* - employee_id
|
||||
* - department_id
|
||||
* - position_id
|
||||
* - hire_date
|
||||
* - salary
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 员工姓名
|
||||
* employee_id:
|
||||
* type: string
|
||||
* description: 员工编号
|
||||
* department_id:
|
||||
* type: integer
|
||||
* description: 部门ID
|
||||
* position_id:
|
||||
* type: integer
|
||||
* description: 职位ID
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 联系电话
|
||||
* email:
|
||||
* type: string
|
||||
* description: 邮箱地址
|
||||
* hire_date:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 入职日期
|
||||
* salary:
|
||||
* type: number
|
||||
* description: 薪资
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, inactive, resigned]
|
||||
* description: 员工状态
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('name').notEmpty().withMessage('员工姓名不能为空'),
|
||||
body('employee_id').notEmpty().withMessage('员工编号不能为空'),
|
||||
body('department_id').isInt().withMessage('部门ID必须是整数'),
|
||||
body('position_id').isInt().withMessage('职位ID必须是整数'),
|
||||
body('phone').optional().isMobilePhone('zh-CN').withMessage('手机号格式不正确'),
|
||||
body('email').optional().isEmail().withMessage('邮箱格式不正确'),
|
||||
body('hire_date').isISO8601().withMessage('入职日期格式不正确'),
|
||||
body('salary').isNumeric().withMessage('薪资必须是数字'),
|
||||
body('status').optional().isIn(['active', 'inactive', 'resigned']).withMessage('状态值无效')
|
||||
],
|
||||
employeeController.createEmployee
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees/{id}:
|
||||
* get:
|
||||
* summary: 获取员工详情
|
||||
* tags: [Employees]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 员工ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 404:
|
||||
* description: 员工不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/:id', roleMiddleware(['admin', 'manager', 'teller']), employeeController.getEmployeeById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees/{id}:
|
||||
* put:
|
||||
* summary: 更新员工
|
||||
* tags: [Employees]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 员工ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* employee_id:
|
||||
* type: string
|
||||
* department_id:
|
||||
* type: integer
|
||||
* position_id:
|
||||
* type: integer
|
||||
* phone:
|
||||
* type: string
|
||||
* email:
|
||||
* type: string
|
||||
* hire_date:
|
||||
* type: string
|
||||
* format: date
|
||||
* salary:
|
||||
* type: number
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, inactive, resigned]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 员工不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.put('/:id',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('name').optional().notEmpty().withMessage('员工姓名不能为空'),
|
||||
body('employee_id').optional().notEmpty().withMessage('员工编号不能为空'),
|
||||
body('department_id').optional().isInt().withMessage('部门ID必须是整数'),
|
||||
body('position_id').optional().isInt().withMessage('职位ID必须是整数'),
|
||||
body('phone').optional().isMobilePhone('zh-CN').withMessage('手机号格式不正确'),
|
||||
body('email').optional().isEmail().withMessage('邮箱格式不正确'),
|
||||
body('hire_date').optional().isISO8601().withMessage('入职日期格式不正确'),
|
||||
body('salary').optional().isNumeric().withMessage('薪资必须是数字'),
|
||||
body('status').optional().isIn(['active', 'inactive', 'resigned']).withMessage('状态值无效')
|
||||
],
|
||||
employeeController.updateEmployee
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees/{id}:
|
||||
* delete:
|
||||
* summary: 删除员工
|
||||
* tags: [Employees]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 员工ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 404:
|
||||
* description: 员工不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.delete('/:id', adminMiddleware, employeeController.deleteEmployee);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees/stats/overview:
|
||||
* get:
|
||||
* summary: 获取员工统计
|
||||
* tags: [Employees]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/overview', roleMiddleware(['admin', 'manager', 'teller']), employeeController.getEmployeeStats);
|
||||
|
||||
module.exports = router;
|
||||
372
bank-backend/routes/loanProducts.js
Normal file
372
bank-backend/routes/loanProducts.js
Normal file
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* 贷款产品路由
|
||||
* @file loanProducts.js
|
||||
* @description 贷款产品相关的路由定义
|
||||
*/
|
||||
const express = require('express');
|
||||
const { body } = require('express-validator');
|
||||
const { authMiddleware, roleMiddleware, adminMiddleware, managerMiddleware } = require('../middleware/auth');
|
||||
const loanProductController = require('../controllers/loanProductController');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(authMiddleware);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: LoanProducts
|
||||
* description: 贷款产品管理
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products:
|
||||
* get:
|
||||
* summary: 获取贷款产品列表
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [draft, active, inactive]
|
||||
* description: 产品状态
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [personal, business, mortgage, credit]
|
||||
* description: 产品类型
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* products:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/LoanProduct'
|
||||
* pagination:
|
||||
* $ref: '#/components/schemas/Pagination'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/', roleMiddleware(['admin', 'manager', 'teller']), loanProductController.getLoanProducts);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products:
|
||||
* post:
|
||||
* summary: 创建贷款产品
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* - code
|
||||
* - type
|
||||
* - min_amount
|
||||
* - max_amount
|
||||
* - interest_rate
|
||||
* - term_min
|
||||
* - term_max
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 产品名称
|
||||
* code:
|
||||
* type: string
|
||||
* description: 产品代码
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [personal, business, mortgage, credit]
|
||||
* description: 产品类型
|
||||
* description:
|
||||
* type: string
|
||||
* description: 产品描述
|
||||
* min_amount:
|
||||
* type: number
|
||||
* description: 最小贷款金额
|
||||
* max_amount:
|
||||
* type: number
|
||||
* description: 最大贷款金额
|
||||
* interest_rate:
|
||||
* type: number
|
||||
* description: 年化利率
|
||||
* term_min:
|
||||
* type: integer
|
||||
* description: 最短期限(月)
|
||||
* term_max:
|
||||
* type: integer
|
||||
* description: 最长期限(月)
|
||||
* requirements:
|
||||
* type: object
|
||||
* description: 申请要求
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [draft, active, inactive]
|
||||
* description: 产品状态
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('name').notEmpty().withMessage('产品名称不能为空'),
|
||||
body('code').notEmpty().withMessage('产品代码不能为空'),
|
||||
body('type').isIn(['personal', 'business', 'mortgage', 'credit']).withMessage('产品类型无效'),
|
||||
body('min_amount').isNumeric().withMessage('最小金额必须是数字'),
|
||||
body('max_amount').isNumeric().withMessage('最大金额必须是数字'),
|
||||
body('interest_rate').isNumeric().withMessage('利率必须是数字'),
|
||||
body('term_min').isInt({ min: 1 }).withMessage('最短期限必须是正整数'),
|
||||
body('term_max').isInt({ min: 1 }).withMessage('最长期限必须是正整数')
|
||||
],
|
||||
loanProductController.createLoanProduct
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/{id}:
|
||||
* get:
|
||||
* summary: 获取贷款产品详情
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 产品ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 404:
|
||||
* description: 产品不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/:id', roleMiddleware(['admin', 'manager', 'teller']), loanProductController.getLoanProductById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/{id}:
|
||||
* put:
|
||||
* summary: 更新贷款产品
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 产品ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* code:
|
||||
* type: string
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [personal, business, mortgage, credit]
|
||||
* description:
|
||||
* type: string
|
||||
* min_amount:
|
||||
* type: number
|
||||
* max_amount:
|
||||
* type: number
|
||||
* interest_rate:
|
||||
* type: number
|
||||
* term_min:
|
||||
* type: integer
|
||||
* term_max:
|
||||
* type: integer
|
||||
* requirements:
|
||||
* type: object
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [draft, active, inactive]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 产品不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.put('/:id',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('name').optional().notEmpty().withMessage('产品名称不能为空'),
|
||||
body('code').optional().notEmpty().withMessage('产品代码不能为空'),
|
||||
body('type').optional().isIn(['personal', 'business', 'mortgage', 'credit']).withMessage('产品类型无效'),
|
||||
body('min_amount').optional().isNumeric().withMessage('最小金额必须是数字'),
|
||||
body('max_amount').optional().isNumeric().withMessage('最大金额必须是数字'),
|
||||
body('interest_rate').optional().isNumeric().withMessage('利率必须是数字'),
|
||||
body('term_min').optional().isInt({ min: 1 }).withMessage('最短期限必须是正整数'),
|
||||
body('term_max').optional().isInt({ min: 1 }).withMessage('最长期限必须是正整数')
|
||||
],
|
||||
loanProductController.updateLoanProduct
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/{id}:
|
||||
* delete:
|
||||
* summary: 删除贷款产品
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 产品ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 404:
|
||||
* description: 产品不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.delete('/:id', adminMiddleware, loanProductController.deleteLoanProduct);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/{id}/status:
|
||||
* put:
|
||||
* summary: 更新贷款产品状态
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 产品ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - status
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [draft, active, inactive]
|
||||
* description: 产品状态
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 产品不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.put('/:id/status',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('status').isIn(['draft', 'active', 'inactive']).withMessage('状态值无效')
|
||||
],
|
||||
loanProductController.updateLoanProductStatus
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/stats/overview:
|
||||
* get:
|
||||
* summary: 获取贷款产品统计
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/overview', roleMiddleware(['admin', 'manager', 'teller']), loanProductController.getLoanProductStats);
|
||||
|
||||
module.exports = router;
|
||||
58
bank-backend/routes/reports.js
Normal file
58
bank-backend/routes/reports.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const authMiddleware = require('../middleware/auth');
|
||||
const ReportController = require('../controllers/reportController');
|
||||
|
||||
// 生成报表
|
||||
router.post('/generate',
|
||||
authMiddleware,
|
||||
[
|
||||
body('reportType')
|
||||
.notEmpty()
|
||||
.withMessage('报表类型不能为空')
|
||||
.isIn(['transaction', 'account', 'user'])
|
||||
.withMessage('无效的报表类型'),
|
||||
body('dateRange')
|
||||
.isArray({ min: 2, max: 2 })
|
||||
.withMessage('日期范围必须包含开始和结束日期'),
|
||||
body('format')
|
||||
.notEmpty()
|
||||
.withMessage('报表格式不能为空')
|
||||
.isIn(['excel', 'pdf', 'csv'])
|
||||
.withMessage('无效的报表格式')
|
||||
],
|
||||
ReportController.generateReport
|
||||
);
|
||||
|
||||
// 获取报表历史
|
||||
router.get('/history',
|
||||
authMiddleware,
|
||||
ReportController.getReportHistory
|
||||
);
|
||||
|
||||
// 下载报表
|
||||
router.get('/download/:id',
|
||||
authMiddleware,
|
||||
ReportController.downloadReport
|
||||
);
|
||||
|
||||
// 预览报表
|
||||
router.get('/preview/:id',
|
||||
authMiddleware,
|
||||
ReportController.previewReport
|
||||
);
|
||||
|
||||
// 删除报表
|
||||
router.delete('/:id',
|
||||
authMiddleware,
|
||||
ReportController.deleteReport
|
||||
);
|
||||
|
||||
// 获取报表统计
|
||||
router.get('/stats',
|
||||
authMiddleware,
|
||||
ReportController.getReportStats
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,5 +1,5 @@
|
||||
const express = require('express');
|
||||
const { verifyToken, requireRole } = require('../middleware/auth');
|
||||
const { authMiddleware, roleMiddleware, adminMiddleware, managerMiddleware, tellerMiddleware } = require('../middleware/auth');
|
||||
const {
|
||||
validateAmount,
|
||||
validateAccountNumber,
|
||||
@@ -130,7 +130,7 @@ const transactionController = require('../controllers/transactionController');
|
||||
* description: 未授权
|
||||
*/
|
||||
router.get('/',
|
||||
verifyToken,
|
||||
authMiddleware,
|
||||
transactionController.getTransactions
|
||||
);
|
||||
|
||||
@@ -160,7 +160,7 @@ router.get('/',
|
||||
* description: 交易记录不存在
|
||||
*/
|
||||
router.get('/:transactionId',
|
||||
verifyToken,
|
||||
authMiddleware,
|
||||
transactionController.getTransactionDetail
|
||||
);
|
||||
|
||||
@@ -208,7 +208,7 @@ router.get('/:transactionId',
|
||||
* description: 账户不存在
|
||||
*/
|
||||
router.post('/transfer',
|
||||
verifyToken,
|
||||
authMiddleware,
|
||||
validateAmount,
|
||||
validateAccountNumber,
|
||||
transactionController.transfer
|
||||
@@ -242,8 +242,8 @@ router.post('/transfer',
|
||||
* description: 交易记录不存在
|
||||
*/
|
||||
router.post('/:transactionId/reverse',
|
||||
verifyToken,
|
||||
requireRole(['admin', 'manager']),
|
||||
authMiddleware,
|
||||
roleMiddleware(['admin', 'manager']),
|
||||
transactionController.reverseTransaction
|
||||
);
|
||||
|
||||
@@ -280,7 +280,7 @@ router.post('/:transactionId/reverse',
|
||||
* description: 未授权
|
||||
*/
|
||||
router.get('/stats',
|
||||
verifyToken,
|
||||
authMiddleware,
|
||||
transactionController.getTransactionStats
|
||||
);
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
const express = require('express');
|
||||
const { verifyToken, requireRole, requireLevel } = require('../middleware/auth');
|
||||
const {
|
||||
validatePhone,
|
||||
validatePassword,
|
||||
validateIdCard,
|
||||
handleValidationErrors
|
||||
} = require('../middleware/security');
|
||||
const { body } = require('express-validator');
|
||||
const { authMiddleware, adminMiddleware, managerMiddleware } = require('../middleware/auth');
|
||||
const router = express.Router();
|
||||
const userController = require('../controllers/userController');
|
||||
|
||||
@@ -101,9 +96,14 @@ const userController = require('../controllers/userController');
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/register',
|
||||
validatePassword,
|
||||
validateIdCard,
|
||||
validatePhone,
|
||||
[
|
||||
body('username').notEmpty().isLength({ min: 3, max: 50 }),
|
||||
body('email').isEmail(),
|
||||
body('password').isLength({ min: 6 }),
|
||||
body('real_name').notEmpty(),
|
||||
body('id_card').matches(/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/),
|
||||
body('phone').optional().matches(/^1[3-9]\d{9}$/)
|
||||
],
|
||||
userController.register
|
||||
);
|
||||
|
||||
@@ -155,7 +155,7 @@ router.post('/login', userController.login);
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
*/
|
||||
router.get('/profile', verifyToken, userController.getProfile);
|
||||
router.get('/profile', authMiddleware, userController.getProfile);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@@ -190,8 +190,11 @@ router.get('/profile', verifyToken, userController.getProfile);
|
||||
* description: 未授权
|
||||
*/
|
||||
router.put('/profile',
|
||||
verifyToken,
|
||||
validatePhone,
|
||||
authMiddleware,
|
||||
[
|
||||
body('phone').optional().matches(/^1[3-9]\d{9}$/),
|
||||
body('real_name').optional().notEmpty()
|
||||
],
|
||||
userController.updateProfile
|
||||
);
|
||||
|
||||
@@ -228,8 +231,11 @@ router.put('/profile',
|
||||
* description: 未授权
|
||||
*/
|
||||
router.put('/change-password',
|
||||
verifyToken,
|
||||
validatePassword,
|
||||
authMiddleware,
|
||||
[
|
||||
body('old_password').notEmpty(),
|
||||
body('new_password').isLength({ min: 6 })
|
||||
],
|
||||
userController.changePassword
|
||||
);
|
||||
|
||||
@@ -268,8 +274,8 @@ router.put('/change-password',
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/',
|
||||
verifyToken,
|
||||
requireRole('admin'),
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
userController.getUsers
|
||||
);
|
||||
|
||||
@@ -312,8 +318,11 @@ router.get('/',
|
||||
* description: 用户不存在
|
||||
*/
|
||||
router.put('/:userId/status',
|
||||
verifyToken,
|
||||
requireRole('admin'),
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
[
|
||||
body('status').isIn(['active', 'inactive', 'suspended', 'locked'])
|
||||
],
|
||||
userController.updateUserStatus
|
||||
);
|
||||
|
||||
@@ -341,8 +350,233 @@ router.put('/:userId/status',
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.get('/:userId/accounts',
|
||||
verifyToken,
|
||||
authMiddleware,
|
||||
userController.getUserAccounts
|
||||
);
|
||||
|
||||
// 新增的管理员路由
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users:
|
||||
* post:
|
||||
* summary: 创建用户(管理员)
|
||||
* tags: [Users]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - username
|
||||
* - email
|
||||
* - password
|
||||
* - real_name
|
||||
* - id_card
|
||||
* properties:
|
||||
* username:
|
||||
* type: string
|
||||
* email:
|
||||
* type: string
|
||||
* password:
|
||||
* type: string
|
||||
* real_name:
|
||||
* type: string
|
||||
* id_card:
|
||||
* type: string
|
||||
* phone:
|
||||
* type: string
|
||||
* role_id:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
*/
|
||||
router.post('/',
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
[
|
||||
body('username').notEmpty().isLength({ min: 3, max: 50 }),
|
||||
body('email').isEmail(),
|
||||
body('password').isLength({ min: 6 }),
|
||||
body('real_name').notEmpty(),
|
||||
body('id_card').matches(/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/),
|
||||
body('phone').optional().matches(/^1[3-9]\d{9}$/)
|
||||
],
|
||||
userController.createUser
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{userId}:
|
||||
* get:
|
||||
* summary: 获取用户详情
|
||||
* tags: [Users]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: userId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
*/
|
||||
router.get('/:userId',
|
||||
authMiddleware,
|
||||
userController.getUserById
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{userId}:
|
||||
* put:
|
||||
* summary: 更新用户信息(管理员)
|
||||
* tags: [Users]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: userId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* username:
|
||||
* type: string
|
||||
* email:
|
||||
* type: string
|
||||
* real_name:
|
||||
* type: string
|
||||
* id_card:
|
||||
* type: string
|
||||
* phone:
|
||||
* type: string
|
||||
* role_id:
|
||||
* type: integer
|
||||
* status:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
*/
|
||||
router.put('/:userId',
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
[
|
||||
body('username').optional().isLength({ min: 3, max: 50 }),
|
||||
body('email').optional().isEmail(),
|
||||
body('real_name').optional().notEmpty(),
|
||||
body('id_card').optional().matches(/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/),
|
||||
body('phone').optional().matches(/^1[3-9]\d{9}$/)
|
||||
],
|
||||
userController.updateUser
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{userId}:
|
||||
* delete:
|
||||
* summary: 删除用户(管理员)
|
||||
* tags: [Users]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: userId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 400:
|
||||
* description: 不能删除自己的账户
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
*/
|
||||
router.delete('/:userId',
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
userController.deleteUser
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{userId}/reset-password:
|
||||
* post:
|
||||
* summary: 重置用户密码(管理员)
|
||||
* tags: [Users]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: userId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - newPassword
|
||||
* properties:
|
||||
* newPassword:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 重置成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
*/
|
||||
router.post('/:userId/reset-password',
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
[
|
||||
body('newPassword').isLength({ min: 6 })
|
||||
],
|
||||
userController.resetPassword
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
120
bank-backend/scripts/migrate-reports.js
Normal file
120
bank-backend/scripts/migrate-reports.js
Normal file
@@ -0,0 +1,120 @@
|
||||
const { sequelize } = require('../config/database');
|
||||
const { QueryInterface, DataTypes } = require('sequelize');
|
||||
|
||||
async function createReportsTable() {
|
||||
try {
|
||||
console.log('开始创建报表表...');
|
||||
|
||||
const queryInterface = sequelize.getQueryInterface();
|
||||
|
||||
// 检查表是否已存在
|
||||
const tableExists = await queryInterface.tableExists('reports');
|
||||
if (tableExists) {
|
||||
console.log('报表表已存在,跳过创建');
|
||||
return;
|
||||
}
|
||||
|
||||
await queryInterface.createTable('reports', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '报表名称'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('transaction', 'account', 'user'),
|
||||
allowNull: false,
|
||||
comment: '报表类型'
|
||||
},
|
||||
format: {
|
||||
type: DataTypes.ENUM('excel', 'pdf', 'csv'),
|
||||
allowNull: false,
|
||||
comment: '报表格式'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('processing', 'completed', 'failed'),
|
||||
allowNull: false,
|
||||
defaultValue: 'processing',
|
||||
comment: '报表状态'
|
||||
},
|
||||
filePath: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '文件路径'
|
||||
},
|
||||
parameters: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: '生成参数'
|
||||
},
|
||||
data: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: '报表数据'
|
||||
},
|
||||
error: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '错误信息'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
});
|
||||
|
||||
// 暂时不添加外键约束,避免兼容性问题
|
||||
// await queryInterface.addConstraint('reports', {
|
||||
// fields: ['createdBy'],
|
||||
// type: 'foreign key',
|
||||
// name: 'fk_reports_createdBy',
|
||||
// references: {
|
||||
// table: 'users',
|
||||
// field: 'id'
|
||||
// },
|
||||
// onDelete: 'CASCADE',
|
||||
// onUpdate: 'CASCADE'
|
||||
// });
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('reports', ['type', 'status']);
|
||||
await queryInterface.addIndex('reports', ['createdBy']);
|
||||
await queryInterface.addIndex('reports', ['createdAt']);
|
||||
|
||||
console.log('✅ 报表表创建成功');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 创建报表表失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await createReportsTable();
|
||||
console.log('🎉 数据库迁移完成');
|
||||
} catch (error) {
|
||||
console.error('💥 数据库迁移失败:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
297
bank-backend/scripts/seed-basic-data.js
Normal file
297
bank-backend/scripts/seed-basic-data.js
Normal file
@@ -0,0 +1,297 @@
|
||||
const { sequelize, User, Role, Account, Transaction, LoanProduct, Employee, Department, Position } = require('../models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function seedBasicData() {
|
||||
try {
|
||||
console.log('开始补充基础测试数据...');
|
||||
|
||||
// 1. 创建角色
|
||||
console.log('创建角色...');
|
||||
const roles = await Role.bulkCreate([
|
||||
{ name: 'admin', display_name: '系统管理员', description: '系统管理员,拥有所有权限' },
|
||||
{ name: 'manager', display_name: '经理', description: '部门经理,拥有部门管理权限' },
|
||||
{ name: 'teller', display_name: '柜员', description: '银行柜员,处理日常业务' },
|
||||
{ name: 'user', display_name: '普通用户', description: '普通银行客户' }
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 2. 创建部门
|
||||
console.log('创建部门...');
|
||||
const departments = await Department.bulkCreate([
|
||||
{ name: '行政部', code: 'ADMIN', description: '行政管理部门' },
|
||||
{ name: '财务部', code: 'FINANCE', description: '财务管理部门' },
|
||||
{ name: '技术部', code: 'IT', description: '技术开发部门' },
|
||||
{ name: '人事部', code: 'HR', description: '人力资源部门' },
|
||||
{ name: '销售部', code: 'SALES', description: '销售部门' },
|
||||
{ name: '风控部', code: 'RISK', description: '风险控制部门' },
|
||||
{ name: '客服部', code: 'SERVICE', description: '客户服务部门' }
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 3. 创建职位
|
||||
console.log('创建职位...');
|
||||
const positions = await Position.bulkCreate([
|
||||
{ name: '总经理', code: 'GM', level: 1, description: '总经理职位' },
|
||||
{ name: '副总经理', code: 'DGM', level: 2, description: '副总经理职位' },
|
||||
{ name: '部门经理', code: 'MGR', level: 3, description: '部门经理职位' },
|
||||
{ name: '主管', code: 'SUP', level: 4, description: '主管职位' },
|
||||
{ name: '高级员工', code: 'SENIOR', level: 5, description: '高级员工职位' },
|
||||
{ name: '普通员工', code: 'STAFF', level: 6, description: '普通员工职位' },
|
||||
{ name: '实习生', code: 'INTERN', level: 7, description: '实习生职位' }
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 4. 创建用户
|
||||
console.log('创建用户...');
|
||||
const users = await User.bulkCreate([
|
||||
{
|
||||
username: 'admin',
|
||||
email: 'admin@bank.com',
|
||||
password: await bcrypt.hash('Admin123456', 10),
|
||||
phone: '13800138000',
|
||||
real_name: '系统管理员',
|
||||
id_card: '110101199003071234',
|
||||
role_id: roles.find(r => r.name === 'admin').id,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'manager1',
|
||||
email: 'manager1@bank.com',
|
||||
password: await bcrypt.hash('Manager123456', 10),
|
||||
phone: '13800138001',
|
||||
real_name: '张经理',
|
||||
id_card: '110101198503071234',
|
||||
role_id: roles.find(r => r.name === 'manager').id,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'teller1',
|
||||
email: 'teller1@bank.com',
|
||||
password: await bcrypt.hash('Teller123456', 10),
|
||||
phone: '13800138002',
|
||||
real_name: '李柜员',
|
||||
id_card: '110101199203071234',
|
||||
role_id: roles.find(r => r.name === 'teller').id,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'user1',
|
||||
email: 'user1@bank.com',
|
||||
password: await bcrypt.hash('User123456', 10),
|
||||
phone: '13800138003',
|
||||
real_name: '王客户',
|
||||
id_card: '110101199503071234',
|
||||
role_id: roles.find(r => r.name === 'user').id,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'user2',
|
||||
email: 'user2@bank.com',
|
||||
password: await bcrypt.hash('User123456', 10),
|
||||
phone: '13800138004',
|
||||
real_name: '赵客户',
|
||||
id_card: '110101199603071234',
|
||||
role_id: roles.find(r => r.name === 'user').id,
|
||||
status: 'active'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 5. 创建员工
|
||||
console.log('创建员工...');
|
||||
const employees = await Employee.bulkCreate([
|
||||
{
|
||||
name: '张经理',
|
||||
employee_id: 'EMP001',
|
||||
email: 'manager1@bank.com',
|
||||
phone: '13800138001',
|
||||
id_card: '110101198503071234',
|
||||
department_id: departments.find(d => d.name === '财务部').id,
|
||||
position_id: positions.find(p => p.name === '部门经理').id,
|
||||
hire_date: '2020-01-15',
|
||||
salary_level: 'L6',
|
||||
status: 'active',
|
||||
supervisor: '系统管理员'
|
||||
},
|
||||
{
|
||||
name: '李柜员',
|
||||
employee_id: 'EMP002',
|
||||
email: 'teller1@bank.com',
|
||||
phone: '13800138002',
|
||||
id_card: '110101199203071234',
|
||||
department_id: departments.find(d => d.name === '客服部').id,
|
||||
position_id: positions.find(p => p.name === '普通员工').id,
|
||||
hire_date: '2021-03-20',
|
||||
salary_level: 'L4',
|
||||
status: 'active',
|
||||
supervisor: '张经理'
|
||||
},
|
||||
{
|
||||
name: '王技术',
|
||||
employee_id: 'EMP003',
|
||||
email: 'wangtech@bank.com',
|
||||
phone: '13800138005',
|
||||
id_card: '110101199103071234',
|
||||
department_id: departments.find(d => d.name === '技术部').id,
|
||||
position_id: positions.find(p => p.name === '高级员工').id,
|
||||
hire_date: '2019-06-10',
|
||||
salary_level: 'L5',
|
||||
status: 'active',
|
||||
supervisor: '张经理'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 6. 创建账户
|
||||
console.log('创建账户...');
|
||||
const accounts = await Account.bulkCreate([
|
||||
{
|
||||
account_number: '6225123456789001',
|
||||
account_type: 'savings',
|
||||
balance: 500000, // 5000元
|
||||
user_id: users.find(u => u.username === 'user1').id,
|
||||
status: 'active',
|
||||
interest_rate: 0.0035
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789002',
|
||||
account_type: 'checking',
|
||||
balance: 100000, // 1000元
|
||||
user_id: users.find(u => u.username === 'user1').id,
|
||||
status: 'active',
|
||||
interest_rate: 0.001
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789003',
|
||||
account_type: 'savings',
|
||||
balance: 200000, // 2000元
|
||||
user_id: users.find(u => u.username === 'user2').id,
|
||||
status: 'active',
|
||||
interest_rate: 0.0035
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789004',
|
||||
account_type: 'credit',
|
||||
balance: -50000, // -500元(信用卡欠款)
|
||||
user_id: users.find(u => u.username === 'user2').id,
|
||||
status: 'active',
|
||||
credit_limit: 100000, // 1000元信用额度
|
||||
interest_rate: 0.18
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 7. 创建交易记录
|
||||
console.log('创建交易记录...');
|
||||
const transactions = await Transaction.bulkCreate([
|
||||
{
|
||||
account_id: accounts[0].id,
|
||||
type: 'deposit',
|
||||
amount: 100000, // 1000元
|
||||
balance_after: 600000, // 6000元
|
||||
description: '工资入账',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN001'
|
||||
},
|
||||
{
|
||||
account_id: accounts[0].id,
|
||||
type: 'withdrawal',
|
||||
amount: 50000, // 500元
|
||||
balance_after: 550000, // 5500元
|
||||
description: 'ATM取款',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN002'
|
||||
},
|
||||
{
|
||||
account_id: accounts[1].id,
|
||||
type: 'transfer',
|
||||
amount: 20000, // 200元
|
||||
balance_after: 120000, // 1200元
|
||||
description: '转账到储蓄账户',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN003'
|
||||
},
|
||||
{
|
||||
account_id: accounts[2].id,
|
||||
type: 'deposit',
|
||||
amount: 50000, // 500元
|
||||
balance_after: 250000, // 2500元
|
||||
description: '现金存款',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN004'
|
||||
},
|
||||
{
|
||||
account_id: accounts[3].id,
|
||||
type: 'payment',
|
||||
amount: 30000, // 300元
|
||||
balance_after: -80000, // -800元
|
||||
description: '信用卡消费',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN005'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 8. 创建贷款产品
|
||||
console.log('创建贷款产品...');
|
||||
const loanProducts = await LoanProduct.bulkCreate([
|
||||
{
|
||||
name: '个人住房贷款',
|
||||
type: 'mortgage',
|
||||
min_amount: 1000000, // 10万元
|
||||
max_amount: 50000000, // 500万元
|
||||
min_term: 12, // 1年
|
||||
max_term: 360, // 30年
|
||||
interest_rate: 0.045, // 4.5%
|
||||
max_interest_rate: 0.055, // 5.5%
|
||||
description: '个人住房按揭贷款,利率优惠',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '个人消费贷款',
|
||||
type: 'consumer',
|
||||
min_amount: 10000, // 1万元
|
||||
max_amount: 500000, // 50万元
|
||||
min_term: 6, // 6个月
|
||||
max_term: 60, // 5年
|
||||
interest_rate: 0.065, // 6.5%
|
||||
max_interest_rate: 0.085, // 8.5%
|
||||
description: '个人消费贷款,用途广泛',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '小微企业贷款',
|
||||
type: 'business',
|
||||
min_amount: 50000, // 5万元
|
||||
max_amount: 1000000, // 100万元
|
||||
min_term: 12, // 1年
|
||||
max_term: 60, // 5年
|
||||
interest_rate: 0.055, // 5.5%
|
||||
max_interest_rate: 0.075, // 7.5%
|
||||
description: '小微企业生产经营贷款',
|
||||
status: 'active'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
console.log('✅ 基础测试数据补充完成');
|
||||
console.log(`- 角色: ${roles.length} 个`);
|
||||
console.log(`- 部门: ${departments.length} 个`);
|
||||
console.log(`- 职位: ${positions.length} 个`);
|
||||
console.log(`- 用户: ${users.length} 个`);
|
||||
console.log(`- 员工: ${employees.length} 个`);
|
||||
console.log(`- 账户: ${accounts.length} 个`);
|
||||
console.log(`- 交易记录: ${transactions.length} 个`);
|
||||
console.log(`- 贷款产品: ${loanProducts.length} 个`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 补充测试数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await seedBasicData();
|
||||
console.log('🎉 基础测试数据补充完成');
|
||||
} catch (error) {
|
||||
console.error('💥 基础测试数据补充失败:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
339
bank-backend/scripts/seed-comprehensive-data.js
Normal file
339
bank-backend/scripts/seed-comprehensive-data.js
Normal file
@@ -0,0 +1,339 @@
|
||||
const { sequelize, User, Role, Account, Transaction, LoanProduct, Employee, Department, Position, Report } = require('../models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function seedComprehensiveData() {
|
||||
try {
|
||||
console.log('开始补充数据库测试数据...');
|
||||
|
||||
// 1. 创建角色
|
||||
console.log('创建角色...');
|
||||
const roles = await Role.bulkCreate([
|
||||
{ name: 'admin', display_name: '系统管理员', description: '系统管理员,拥有所有权限' },
|
||||
{ name: 'manager', display_name: '经理', description: '部门经理,拥有部门管理权限' },
|
||||
{ name: 'teller', display_name: '柜员', description: '银行柜员,处理日常业务' },
|
||||
{ name: 'user', display_name: '普通用户', description: '普通银行客户' }
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 2. 创建部门
|
||||
console.log('创建部门...');
|
||||
const departments = await Department.bulkCreate([
|
||||
{ name: '行政部', code: 'ADMIN', description: '行政管理部门' },
|
||||
{ name: '财务部', code: 'FINANCE', description: '财务管理部门' },
|
||||
{ name: '技术部', code: 'IT', description: '技术开发部门' },
|
||||
{ name: '人事部', code: 'HR', description: '人力资源部门' },
|
||||
{ name: '销售部', code: 'SALES', description: '销售部门' },
|
||||
{ name: '风控部', code: 'RISK', description: '风险控制部门' },
|
||||
{ name: '客服部', code: 'SERVICE', description: '客户服务部门' }
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 3. 创建职位
|
||||
console.log('创建职位...');
|
||||
const positions = await Position.bulkCreate([
|
||||
{ name: '总经理', code: 'GM', level: 1, description: '总经理职位' },
|
||||
{ name: '副总经理', code: 'DGM', level: 2, description: '副总经理职位' },
|
||||
{ name: '部门经理', code: 'MGR', level: 3, description: '部门经理职位' },
|
||||
{ name: '主管', code: 'SUP', level: 4, description: '主管职位' },
|
||||
{ name: '高级员工', code: 'SENIOR', level: 5, description: '高级员工职位' },
|
||||
{ name: '普通员工', code: 'STAFF', level: 6, description: '普通员工职位' },
|
||||
{ name: '实习生', code: 'INTERN', level: 7, description: '实习生职位' }
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 4. 创建用户
|
||||
console.log('创建用户...');
|
||||
const users = await User.bulkCreate([
|
||||
{
|
||||
username: 'admin',
|
||||
email: 'admin@bank.com',
|
||||
password: await bcrypt.hash('Admin123456', 10),
|
||||
phone: '13800138000',
|
||||
real_name: '系统管理员',
|
||||
id_card: '110101199003071234',
|
||||
role_id: roles.find(r => r.name === 'admin').id,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'manager1',
|
||||
email: 'manager1@bank.com',
|
||||
password: await bcrypt.hash('Manager123456', 10),
|
||||
phone: '13800138001',
|
||||
real_name: '张经理',
|
||||
id_card: '110101198503071234',
|
||||
role_id: roles.find(r => r.name === 'manager').id,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'teller1',
|
||||
email: 'teller1@bank.com',
|
||||
password: await bcrypt.hash('Teller123456', 10),
|
||||
phone: '13800138002',
|
||||
real_name: '李柜员',
|
||||
id_card: '110101199203071234',
|
||||
role_id: roles.find(r => r.name === 'teller').id,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'user1',
|
||||
email: 'user1@bank.com',
|
||||
password: await bcrypt.hash('User123456', 10),
|
||||
phone: '13800138003',
|
||||
real_name: '王客户',
|
||||
id_card: '110101199503071234',
|
||||
role_id: roles.find(r => r.name === 'user').id,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'user2',
|
||||
email: 'user2@bank.com',
|
||||
password: await bcrypt.hash('User123456', 10),
|
||||
phone: '13800138004',
|
||||
real_name: '赵客户',
|
||||
id_card: '110101199603071234',
|
||||
role_id: roles.find(r => r.name === 'user').id,
|
||||
status: 'active'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 5. 创建员工
|
||||
console.log('创建员工...');
|
||||
const employees = await Employee.bulkCreate([
|
||||
{
|
||||
name: '张经理',
|
||||
employee_id: 'EMP001',
|
||||
email: 'manager1@bank.com',
|
||||
phone: '13800138001',
|
||||
id_card: '110101198503071234',
|
||||
department_id: departments.find(d => d.name === '财务部').id,
|
||||
position_id: positions.find(p => p.name === '部门经理').id,
|
||||
hire_date: '2020-01-15',
|
||||
salary_level: 'L6',
|
||||
status: 'active',
|
||||
supervisor: '系统管理员'
|
||||
},
|
||||
{
|
||||
name: '李柜员',
|
||||
employee_id: 'EMP002',
|
||||
email: 'teller1@bank.com',
|
||||
phone: '13800138002',
|
||||
id_card: '110101199203071234',
|
||||
department_id: departments.find(d => d.name === '客服部').id,
|
||||
position_id: positions.find(p => p.name === '普通员工').id,
|
||||
hire_date: '2021-03-20',
|
||||
salary_level: 'L4',
|
||||
status: 'active',
|
||||
supervisor: '张经理'
|
||||
},
|
||||
{
|
||||
name: '王技术',
|
||||
employee_id: 'EMP003',
|
||||
email: 'wangtech@bank.com',
|
||||
phone: '13800138005',
|
||||
id_card: '110101199103071234',
|
||||
department_id: departments.find(d => d.name === '技术部').id,
|
||||
position_id: positions.find(p => p.name === '高级员工').id,
|
||||
hire_date: '2019-06-10',
|
||||
salary_level: 'L5',
|
||||
status: 'active',
|
||||
supervisor: '张经理'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 6. 创建账户
|
||||
console.log('创建账户...');
|
||||
const accounts = await Account.bulkCreate([
|
||||
{
|
||||
account_number: '6225123456789001',
|
||||
account_type: 'savings',
|
||||
balance: 500000, // 5000元
|
||||
user_id: users.find(u => u.username === 'user1').id,
|
||||
status: 'active',
|
||||
interest_rate: 0.0035
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789002',
|
||||
account_type: 'checking',
|
||||
balance: 100000, // 1000元
|
||||
user_id: users.find(u => u.username === 'user1').id,
|
||||
status: 'active',
|
||||
interest_rate: 0.001
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789003',
|
||||
account_type: 'savings',
|
||||
balance: 200000, // 2000元
|
||||
user_id: users.find(u => u.username === 'user2').id,
|
||||
status: 'active',
|
||||
interest_rate: 0.0035
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789004',
|
||||
account_type: 'credit',
|
||||
balance: -50000, // -500元(信用卡欠款)
|
||||
user_id: users.find(u => u.username === 'user2').id,
|
||||
status: 'active',
|
||||
credit_limit: 100000, // 1000元信用额度
|
||||
interest_rate: 0.18
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 7. 创建交易记录
|
||||
console.log('创建交易记录...');
|
||||
const transactions = await Transaction.bulkCreate([
|
||||
{
|
||||
account_id: accounts[0].id,
|
||||
type: 'deposit',
|
||||
amount: 100000, // 1000元
|
||||
balance_after: 600000, // 6000元
|
||||
description: '工资入账',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN001'
|
||||
},
|
||||
{
|
||||
account_id: accounts[0].id,
|
||||
type: 'withdrawal',
|
||||
amount: 50000, // 500元
|
||||
balance_after: 550000, // 5500元
|
||||
description: 'ATM取款',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN002'
|
||||
},
|
||||
{
|
||||
account_id: accounts[1].id,
|
||||
type: 'transfer',
|
||||
amount: 20000, // 200元
|
||||
balance_after: 120000, // 1200元
|
||||
description: '转账到储蓄账户',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN003'
|
||||
},
|
||||
{
|
||||
account_id: accounts[2].id,
|
||||
type: 'deposit',
|
||||
amount: 50000, // 500元
|
||||
balance_after: 250000, // 2500元
|
||||
description: '现金存款',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN004'
|
||||
},
|
||||
{
|
||||
account_id: accounts[3].id,
|
||||
type: 'payment',
|
||||
amount: 30000, // 300元
|
||||
balance_after: -80000, // -800元
|
||||
description: '信用卡消费',
|
||||
status: 'completed',
|
||||
reference_number: 'TXN005'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 8. 创建贷款产品
|
||||
console.log('创建贷款产品...');
|
||||
const loanProducts = await LoanProduct.bulkCreate([
|
||||
{
|
||||
name: '个人住房贷款',
|
||||
type: 'mortgage',
|
||||
min_amount: 1000000, // 10万元
|
||||
max_amount: 50000000, // 500万元
|
||||
min_term: 12, // 1年
|
||||
max_term: 360, // 30年
|
||||
interest_rate: 0.045, // 4.5%
|
||||
max_interest_rate: 0.055, // 5.5%
|
||||
description: '个人住房按揭贷款,利率优惠',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '个人消费贷款',
|
||||
type: 'consumer',
|
||||
min_amount: 10000, // 1万元
|
||||
max_amount: 500000, // 50万元
|
||||
min_term: 6, // 6个月
|
||||
max_term: 60, // 5年
|
||||
interest_rate: 0.065, // 6.5%
|
||||
max_interest_rate: 0.085, // 8.5%
|
||||
description: '个人消费贷款,用途广泛',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
name: '小微企业贷款',
|
||||
type: 'business',
|
||||
min_amount: 50000, // 5万元
|
||||
max_amount: 1000000, // 100万元
|
||||
min_term: 12, // 1年
|
||||
max_term: 60, // 5年
|
||||
interest_rate: 0.055, // 5.5%
|
||||
max_interest_rate: 0.075, // 7.5%
|
||||
description: '小微企业生产经营贷款',
|
||||
status: 'active'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
// 9. 创建报表记录
|
||||
console.log('创建报表记录...');
|
||||
const reports = await Report.bulkCreate([
|
||||
{
|
||||
name: '2024年12月交易报表',
|
||||
type: 'transaction',
|
||||
format: 'excel',
|
||||
status: 'completed',
|
||||
file_path: 'reports/transaction_202412.xlsx',
|
||||
created_by: users.find(u => u.username === 'admin').id,
|
||||
parameters: {
|
||||
dateRange: ['2024-12-01', '2024-12-31'],
|
||||
format: 'excel'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '储蓄账户余额报表',
|
||||
type: 'account',
|
||||
format: 'pdf',
|
||||
status: 'completed',
|
||||
file_path: 'reports/account_balance_202412.pdf',
|
||||
created_by: users.find(u => u.username === 'manager1').id,
|
||||
parameters: {
|
||||
dateRange: ['2024-12-01', '2024-12-31'],
|
||||
format: 'pdf'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '用户活跃度报表',
|
||||
type: 'user',
|
||||
format: 'csv',
|
||||
status: 'completed',
|
||||
file_path: 'reports/user_activity_202412.csv',
|
||||
created_by: users.find(u => u.username === 'admin').id,
|
||||
parameters: {
|
||||
dateRange: ['2024-12-01', '2024-12-31'],
|
||||
format: 'csv'
|
||||
}
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
|
||||
console.log('✅ 数据库测试数据补充完成');
|
||||
console.log(`- 角色: ${roles.length} 个`);
|
||||
console.log(`- 部门: ${departments.length} 个`);
|
||||
console.log(`- 职位: ${positions.length} 个`);
|
||||
console.log(`- 用户: ${users.length} 个`);
|
||||
console.log(`- 员工: ${employees.length} 个`);
|
||||
console.log(`- 账户: ${accounts.length} 个`);
|
||||
console.log(`- 交易记录: ${transactions.length} 个`);
|
||||
console.log(`- 贷款产品: ${loanProducts.length} 个`);
|
||||
console.log(`- 报表记录: ${reports.length} 个`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 补充测试数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await seedComprehensiveData();
|
||||
console.log('🎉 数据库测试数据补充完成');
|
||||
} catch (error) {
|
||||
console.error('💥 数据库测试数据补充失败:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
249
bank-backend/scripts/seed-test-data.js
Normal file
249
bank-backend/scripts/seed-test-data.js
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* 数据库种子文件 - 添加测试数据
|
||||
* @file seed-test-data.js
|
||||
* @description 为银行系统添加测试数据
|
||||
*/
|
||||
const { sequelize } = require('../config/database');
|
||||
const { User, Account, Transaction, Role } = require('../models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function seedTestData() {
|
||||
console.log('🌱 开始添加测试数据...');
|
||||
|
||||
try {
|
||||
// 同步数据库
|
||||
await sequelize.sync({ force: false });
|
||||
console.log('✅ 数据库同步完成');
|
||||
|
||||
// 1. 创建角色数据
|
||||
console.log('📝 创建角色数据...');
|
||||
const roles = await Role.bulkCreate([
|
||||
{
|
||||
name: 'admin',
|
||||
display_name: '系统管理员',
|
||||
description: '系统管理员,拥有所有权限',
|
||||
permissions: JSON.stringify(['*'])
|
||||
},
|
||||
{
|
||||
name: 'manager',
|
||||
display_name: '银行经理',
|
||||
description: '银行经理,管理银行日常运营',
|
||||
permissions: JSON.stringify(['users:read', 'users:write', 'accounts:read', 'accounts:write', 'transactions:read'])
|
||||
},
|
||||
{
|
||||
name: 'teller',
|
||||
display_name: '银行柜员',
|
||||
description: '银行柜员,处理客户业务',
|
||||
permissions: JSON.stringify(['accounts:read', 'transactions:read', 'transactions:write'])
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
display_name: '普通用户',
|
||||
description: '普通用户,查看自己的账户信息',
|
||||
permissions: JSON.stringify(['accounts:read:own', 'transactions:read:own'])
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
console.log(`✅ 创建了 ${roles.length} 个角色`);
|
||||
|
||||
// 2. 创建用户数据
|
||||
console.log('👥 创建用户数据...');
|
||||
const users = await User.bulkCreate([
|
||||
{
|
||||
username: 'admin',
|
||||
email: 'admin@bank.com',
|
||||
password: 'admin123',
|
||||
phone: '13800138000',
|
||||
real_name: '系统管理员',
|
||||
id_card: '110101199001010001',
|
||||
role_id: 1,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'manager1',
|
||||
email: 'manager1@bank.com',
|
||||
password: 'manager123',
|
||||
phone: '13800138001',
|
||||
real_name: '张经理',
|
||||
id_card: '110101199001010002',
|
||||
role_id: 2,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'teller1',
|
||||
email: 'teller1@bank.com',
|
||||
password: 'teller123',
|
||||
phone: '13800138002',
|
||||
real_name: '李柜员',
|
||||
id_card: '110101199001010003',
|
||||
role_id: 3,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'user1',
|
||||
email: 'user1@bank.com',
|
||||
password: 'user123',
|
||||
phone: '13800138004',
|
||||
real_name: '王用户',
|
||||
id_card: '110101199001010004',
|
||||
role_id: 4,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
username: 'user2',
|
||||
email: 'user2@bank.com',
|
||||
password: 'user123',
|
||||
phone: '13800138005',
|
||||
real_name: '赵客户',
|
||||
id_card: '110101199001010005',
|
||||
role_id: 4,
|
||||
status: 'active'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
console.log(`✅ 创建了 ${users.length} 个用户`);
|
||||
|
||||
// 3. 创建账户数据
|
||||
console.log('🏦 创建账户数据...');
|
||||
const accounts = await Account.bulkCreate([
|
||||
{
|
||||
account_number: '6225123456789001',
|
||||
account_name: '王用户储蓄账户',
|
||||
account_type: 'savings',
|
||||
user_id: 4,
|
||||
balance: 5000000, // 50,000元
|
||||
available_balance: 5000000,
|
||||
frozen_amount: 0,
|
||||
status: 'active',
|
||||
currency: 'CNY'
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789002',
|
||||
account_name: '王用户活期账户',
|
||||
account_type: 'checking',
|
||||
user_id: 4,
|
||||
balance: 2000000, // 20,000元
|
||||
available_balance: 2000000,
|
||||
frozen_amount: 0,
|
||||
status: 'active',
|
||||
currency: 'CNY'
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789003',
|
||||
account_name: '赵客户储蓄账户',
|
||||
account_type: 'savings',
|
||||
user_id: 5,
|
||||
balance: 10000000, // 100,000元
|
||||
available_balance: 10000000,
|
||||
frozen_amount: 0,
|
||||
status: 'active',
|
||||
currency: 'CNY'
|
||||
},
|
||||
{
|
||||
account_number: '6225123456789004',
|
||||
account_name: '赵客户信用卡',
|
||||
account_type: 'credit',
|
||||
user_id: 5,
|
||||
balance: -500000, // -5,000元(信用卡欠款)
|
||||
available_balance: 4500000, // 45,000元可用额度
|
||||
frozen_amount: 0,
|
||||
status: 'active',
|
||||
currency: 'CNY'
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
console.log(`✅ 创建了 ${accounts.length} 个账户`);
|
||||
|
||||
// 4. 创建交易记录数据
|
||||
console.log('💳 创建交易记录数据...');
|
||||
const transactions = await Transaction.bulkCreate([
|
||||
{
|
||||
transaction_id: 'T20240922001',
|
||||
type: 'deposit',
|
||||
account_id: 1,
|
||||
amount: 1000000, // 10,000元
|
||||
balance_after: 6000000,
|
||||
description: '现金存款',
|
||||
status: 'completed',
|
||||
channel: 'counter',
|
||||
created_at: new Date('2024-09-22T08:00:00Z')
|
||||
},
|
||||
{
|
||||
transaction_id: 'T20240922002',
|
||||
type: 'withdrawal',
|
||||
account_id: 2,
|
||||
amount: 500000, // 5,000元
|
||||
balance_after: 1500000,
|
||||
description: 'ATM取款',
|
||||
status: 'completed',
|
||||
channel: 'atm',
|
||||
created_at: new Date('2024-09-22T09:30:00Z')
|
||||
},
|
||||
{
|
||||
transaction_id: 'T20240922003',
|
||||
type: 'transfer',
|
||||
account_id: 1,
|
||||
target_account_id: 3,
|
||||
amount: 2000000, // 20,000元
|
||||
balance_after: 4000000,
|
||||
description: '转账给赵客户',
|
||||
status: 'completed',
|
||||
channel: 'online',
|
||||
created_at: new Date('2024-09-22T10:15:00Z')
|
||||
},
|
||||
{
|
||||
transaction_id: 'T20240922004',
|
||||
type: 'payment',
|
||||
account_id: 4,
|
||||
amount: 300000, // 3,000元
|
||||
balance_after: -800000,
|
||||
description: '信用卡消费',
|
||||
status: 'completed',
|
||||
channel: 'pos',
|
||||
created_at: new Date('2024-09-22T11:45:00Z')
|
||||
},
|
||||
{
|
||||
transaction_id: 'T20240922005',
|
||||
type: 'interest',
|
||||
account_id: 1,
|
||||
amount: 5000, // 50元
|
||||
balance_after: 4005000,
|
||||
description: '储蓄利息',
|
||||
status: 'completed',
|
||||
channel: 'system',
|
||||
created_at: new Date('2024-09-22T12:00:00Z')
|
||||
}
|
||||
], { ignoreDuplicates: true });
|
||||
console.log(`✅ 创建了 ${transactions.length} 条交易记录`);
|
||||
|
||||
console.log('');
|
||||
console.log('🎉 测试数据添加完成!');
|
||||
console.log('📊 数据统计:');
|
||||
console.log(` - 角色: ${roles.length} 个`);
|
||||
console.log(` - 用户: ${users.length} 个`);
|
||||
console.log(` - 账户: ${accounts.length} 个`);
|
||||
console.log(` - 交易记录: ${transactions.length} 条`);
|
||||
console.log('');
|
||||
console.log('🔑 测试账号:');
|
||||
console.log(' - 管理员: admin / admin123');
|
||||
console.log(' - 经理: manager1 / manager123');
|
||||
console.log(' - 柜员: teller1 / teller123');
|
||||
console.log(' - 用户: user1 / user123');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 添加测试数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
seedTestData()
|
||||
.then(() => {
|
||||
console.log('✅ 种子数据脚本执行完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 种子数据脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = seedTestData;
|
||||
@@ -27,7 +27,7 @@ dotenv.config();
|
||||
// 创建Express应用和HTTP服务器
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const PORT = process.env.PORT || 5351;
|
||||
const PORT = 5351; // 强制设置为5351端口
|
||||
|
||||
// 安全中间件
|
||||
app.use(securityHeaders);
|
||||
@@ -67,9 +67,14 @@ app.get('/health', (req, res) => {
|
||||
});
|
||||
|
||||
// API路由
|
||||
app.use('/api/auth', require('./routes/auth'));
|
||||
app.use('/api/users', require('./routes/users'));
|
||||
app.use('/api/accounts', require('./routes/accounts'));
|
||||
app.use('/api/transactions', require('./routes/transactions'));
|
||||
app.use('/api/dashboard', require('./routes/dashboard'));
|
||||
app.use('/api/loan-products', require('./routes/loanProducts'));
|
||||
app.use('/api/employees', require('./routes/employees'));
|
||||
// app.use('/api/reports', require('./routes/reports'));
|
||||
|
||||
// 根路径
|
||||
app.get('/', (req, res) => {
|
||||
|
||||
16
bank-backend/start-5351.bat
Normal file
16
bank-backend/start-5351.bat
Normal file
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
echo 启动银行后端服务 - 端口5351
|
||||
echo ================================
|
||||
|
||||
REM 设置端口环境变量
|
||||
set PORT=5351
|
||||
set NODE_ENV=development
|
||||
|
||||
echo 端口设置: %PORT%
|
||||
echo 环境: %NODE_ENV%
|
||||
echo.
|
||||
|
||||
REM 启动服务器
|
||||
node server.js
|
||||
|
||||
pause
|
||||
14
bank-backend/start-5351.ps1
Normal file
14
bank-backend/start-5351.ps1
Normal file
@@ -0,0 +1,14 @@
|
||||
# 启动银行后端服务 - 端口5351
|
||||
Write-Host "启动银行后端服务 - 端口5351" -ForegroundColor Green
|
||||
Write-Host "================================" -ForegroundColor Green
|
||||
|
||||
# 设置端口环境变量
|
||||
$env:PORT = "5351"
|
||||
$env:NODE_ENV = "development"
|
||||
|
||||
Write-Host "端口设置: $env:PORT" -ForegroundColor Yellow
|
||||
Write-Host "环境: $env:NODE_ENV" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# 启动服务器
|
||||
node server.js
|
||||
291
bank-backend/test-api-integration.js
Normal file
291
bank-backend/test-api-integration.js
Normal file
@@ -0,0 +1,291 @@
|
||||
const http = require('http');
|
||||
|
||||
// 测试配置
|
||||
const BASE_URL = 'http://localhost:5351';
|
||||
let authToken = '';
|
||||
|
||||
// 辅助函数:发送HTTP请求
|
||||
function makeRequest(options, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const result = JSON.parse(body);
|
||||
resolve({ status: res.statusCode, data: result });
|
||||
} catch (error) {
|
||||
resolve({ status: res.statusCode, data: body });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (data) {
|
||||
req.write(JSON.stringify(data));
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 测试登录
|
||||
async function testLogin() {
|
||||
console.log('🔐 测试登录...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/auth/login',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options, {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
if (result.status === 200 && result.data.success) {
|
||||
authToken = result.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 登录失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 登录请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试仪表盘统计
|
||||
async function testDashboardStats() {
|
||||
console.log('📊 测试仪表盘统计...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/dashboard',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 仪表盘统计获取成功');
|
||||
console.log(' - 总用户数:', result.data.data.overview?.totalUsers || 0);
|
||||
console.log(' - 总账户数:', result.data.data.overview?.totalAccounts || 0);
|
||||
console.log(' - 今日交易数:', result.data.data.today?.transactionCount || 0);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 仪表盘统计获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 仪表盘统计请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试用户列表
|
||||
async function testUsersList() {
|
||||
console.log('👥 测试用户列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/users?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 用户列表获取成功');
|
||||
console.log(' - 用户数量:', result.data.data.users?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 用户列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 用户列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试账户列表
|
||||
async function testAccountsList() {
|
||||
console.log('🏦 测试账户列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/accounts?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 账户列表获取成功');
|
||||
console.log(' - 账户数量:', result.data.data.accounts?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 账户列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 账户列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试交易记录列表
|
||||
async function testTransactionsList() {
|
||||
console.log('💳 测试交易记录列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/transactions?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 交易记录列表获取成功');
|
||||
console.log(' - 交易数量:', result.data.data.transactions?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 交易记录列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 交易记录列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试员工列表
|
||||
async function testEmployeesList() {
|
||||
console.log('👨💼 测试员工列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/employees?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 员工列表获取成功');
|
||||
console.log(' - 员工数量:', result.data.data.employees?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 员工列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 员工列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试贷款产品列表
|
||||
async function testLoanProductsList() {
|
||||
console.log('💰 测试贷款产品列表...');
|
||||
try {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 5351,
|
||||
path: '/api/loan-products?page=1&pageSize=10',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await makeRequest(options);
|
||||
if (result.status === 200 && result.data.success) {
|
||||
console.log('✅ 贷款产品列表获取成功');
|
||||
console.log(' - 产品数量:', result.data.data.products?.length || 0);
|
||||
console.log(' - 分页信息:', result.data.data.pagination);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 贷款产品列表获取失败:', result.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 贷款产品列表请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runTests() {
|
||||
console.log('🚀 开始API集成测试...\n');
|
||||
|
||||
const tests = [
|
||||
{ name: '登录', fn: testLogin },
|
||||
{ name: '仪表盘统计', fn: testDashboardStats },
|
||||
{ name: '用户列表', fn: testUsersList },
|
||||
{ name: '账户列表', fn: testAccountsList },
|
||||
{ name: '交易记录列表', fn: testTransactionsList },
|
||||
{ name: '员工列表', fn: testEmployeesList },
|
||||
{ name: '贷款产品列表', fn: testLoanProductsList }
|
||||
];
|
||||
|
||||
let passed = 0;
|
||||
let total = tests.length;
|
||||
|
||||
for (const test of tests) {
|
||||
try {
|
||||
const success = await test.fn();
|
||||
if (success) {
|
||||
passed++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`❌ ${test.name}测试异常:`, error.message);
|
||||
}
|
||||
console.log(''); // 空行分隔
|
||||
}
|
||||
|
||||
console.log('📋 测试结果汇总:');
|
||||
console.log(`✅ 通过: ${passed}/${total}`);
|
||||
console.log(`❌ 失败: ${total - passed}/${total}`);
|
||||
|
||||
if (passed === total) {
|
||||
console.log('🎉 所有测试通过!API集成正常');
|
||||
} else {
|
||||
console.log('⚠️ 部分测试失败,请检查相关API接口');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTests().catch(console.error);
|
||||
129
bank-backend/test-api.js
Normal file
129
bank-backend/test-api.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
|
||||
const app = express();
|
||||
|
||||
// 中间件
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// 模拟认证中间件
|
||||
const mockAuthMiddleware = (req, res, next) => {
|
||||
req.user = { id: 1, username: 'test' };
|
||||
next();
|
||||
};
|
||||
|
||||
// 模拟仪表盘控制器
|
||||
const mockDashboardController = {
|
||||
getStats: (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取统计数据成功(模拟数据)',
|
||||
data: {
|
||||
overview: {
|
||||
totalUsers: 1250,
|
||||
totalAccounts: 3420,
|
||||
totalTransactions: 15680,
|
||||
totalBalance: 12500000.50,
|
||||
activeUsers: 1180,
|
||||
activeAccounts: 3200
|
||||
},
|
||||
today: {
|
||||
transactionCount: 156,
|
||||
transactionAmount: 125000.00
|
||||
},
|
||||
accountTypes: [
|
||||
{ type: 'savings', count: 2100, totalBalance: 8500000.00 },
|
||||
{ type: 'checking', count: 800, totalBalance: 3200000.00 },
|
||||
{ type: 'credit', count: 400, totalBalance: 500000.00 },
|
||||
{ type: 'loan', count: 120, totalBalance: 300000.00 }
|
||||
],
|
||||
trends: [
|
||||
{ date: '2024-01-15', count: 45, totalAmount: 12500.00 },
|
||||
{ date: '2024-01-16', count: 52, totalAmount: 15200.00 },
|
||||
{ date: '2024-01-17', count: 38, totalAmount: 9800.00 },
|
||||
{ date: '2024-01-18', count: 61, totalAmount: 18500.00 },
|
||||
{ date: '2024-01-19', count: 48, totalAmount: 13200.00 },
|
||||
{ date: '2024-01-20', count: 55, totalAmount: 16800.00 },
|
||||
{ date: '2024-01-21', count: 42, totalAmount: 11200.00 }
|
||||
]
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getRecentTransactions: (req, res) => {
|
||||
const { limit = 10 } = req.query;
|
||||
|
||||
const mockTransactions = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'deposit',
|
||||
amount: 1000.00,
|
||||
description: '存款',
|
||||
status: 'completed',
|
||||
created_at: new Date(),
|
||||
account: {
|
||||
account_number: '1234567890',
|
||||
user: {
|
||||
username: 'user1',
|
||||
real_name: '张三'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'withdrawal',
|
||||
amount: 500.00,
|
||||
description: '取款',
|
||||
status: 'completed',
|
||||
created_at: new Date(Date.now() - 3600000),
|
||||
account: {
|
||||
account_number: '1234567891',
|
||||
user: {
|
||||
username: 'user2',
|
||||
real_name: '李四'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'transfer',
|
||||
amount: 200.00,
|
||||
description: '转账',
|
||||
status: 'completed',
|
||||
created_at: new Date(Date.now() - 7200000),
|
||||
account: {
|
||||
account_number: '1234567892',
|
||||
user: {
|
||||
username: 'user3',
|
||||
real_name: '王五'
|
||||
}
|
||||
}
|
||||
}
|
||||
].slice(0, parseInt(limit));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取最近交易记录成功(模拟数据)',
|
||||
data: mockTransactions
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 路由
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ message: '银行后端服务运行正常', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.get('/api/dashboard', mockAuthMiddleware, mockDashboardController.getStats);
|
||||
app.get('/api/dashboard/recent-transactions', mockAuthMiddleware, mockDashboardController.getRecentTransactions);
|
||||
|
||||
// 启动服务器
|
||||
const PORT = 5351;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 银行后端模拟服务已启动`);
|
||||
console.log(`📡 服务地址: http://localhost:${PORT}`);
|
||||
console.log(`🏥 健康检查: http://localhost:${PORT}/health`);
|
||||
console.log(`📊 仪表盘API: http://localhost:${PORT}/api/dashboard`);
|
||||
console.log(`💳 最近交易API: http://localhost:${PORT}/api/dashboard/recent-transactions`);
|
||||
});
|
||||
10
bank-backend/test-controller.js
Normal file
10
bank-backend/test-controller.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const authController = require('./controllers/authController');
|
||||
const authMiddleware = require('./middleware/auth');
|
||||
|
||||
console.log('authController type:', typeof authController);
|
||||
console.log('authController.logout type:', typeof authController.logout);
|
||||
console.log('authMiddleware type:', typeof authMiddleware);
|
||||
console.log('authMiddleware.authMiddleware type:', typeof authMiddleware.authMiddleware);
|
||||
|
||||
console.log('authController keys:', Object.keys(authController));
|
||||
console.log('authMiddleware keys:', Object.keys(authMiddleware));
|
||||
Reference in New Issue
Block a user