更新政府端和银行端

This commit is contained in:
2025-09-17 18:04:28 +08:00
parent f35ceef31f
commit e4287b83fe
185 changed files with 78320 additions and 189 deletions

View File

@@ -0,0 +1,224 @@
/**
* 账户模型
* @file Account.js
* @description 银行账户模型定义
*/
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
const { sequelize } = require('../config/database');
class Account extends BaseModel {
/**
* 获取账户余额(元)
* @returns {String} 格式化后的余额
*/
getBalanceFormatted() {
return this.formatAmount(this.balance);
}
/**
* 获取可用余额(元)
* @returns {String} 格式化后的可用余额
*/
getAvailableBalanceFormatted() {
return this.formatAmount(this.available_balance);
}
/**
* 获取冻结金额(元)
* @returns {String} 格式化后的冻结金额
*/
getFrozenAmountFormatted() {
return this.formatAmount(this.frozen_amount);
}
/**
* 检查账户是否可用
* @returns {Boolean} 是否可用
*/
isActive() {
return this.status === 'active';
}
/**
* 检查余额是否充足
* @param {Number} amount 金额(分)
* @returns {Boolean} 余额是否充足
*/
hasSufficientBalance(amount) {
return this.available_balance >= amount;
}
/**
* 冻结资金
* @param {Number} amount 金额(分)
* @returns {Promise<Boolean>} 操作结果
*/
async freezeAmount(amount) {
if (!this.hasSufficientBalance(amount)) {
return false;
}
this.available_balance -= amount;
this.frozen_amount += amount;
await this.save();
return true;
}
/**
* 解冻资金
* @param {Number} amount 金额(分)
* @returns {Promise<Boolean>} 操作结果
*/
async unfreezeAmount(amount) {
if (this.frozen_amount < amount) {
return false;
}
this.available_balance += amount;
this.frozen_amount -= amount;
await this.save();
return true;
}
/**
* 扣减余额
* @param {Number} amount 金额(分)
* @returns {Promise<Boolean>} 操作结果
*/
async deductBalance(amount) {
if (!this.hasSufficientBalance(amount)) {
return false;
}
this.balance -= amount;
this.available_balance -= amount;
await this.save();
return true;
}
/**
* 增加余额
* @param {Number} amount 金额(分)
* @returns {Promise<Boolean>} 操作结果
*/
async addBalance(amount) {
this.balance += amount;
this.available_balance += amount;
await this.save();
return true;
}
/**
* 获取账户交易记录
* @param {Object} options 查询选项
* @returns {Promise<Array>} 交易记录列表
*/
async getTransactions(options = {}) {
try {
const { Transaction } = require('./index');
return await Transaction.findAll({
where: {
account_id: this.id,
...options.where
},
order: [['created_at', 'DESC']],
limit: options.limit || 50,
...options
});
} catch (error) {
console.error('获取账户交易记录失败:', error);
return [];
}
}
}
// 初始化Account模型
Account.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
account_number: {
type: DataTypes.STRING(20),
allowNull: false,
unique: true,
comment: '账户号码'
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'bank_users',
key: 'id'
}
},
account_type: {
type: DataTypes.ENUM('savings', 'checking', 'credit', 'loan'),
allowNull: false,
defaultValue: 'savings',
comment: '账户类型:储蓄、支票、信用卡、贷款'
},
balance: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '账户余额(分)'
},
available_balance: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '可用余额(分)'
},
frozen_amount: {
type: DataTypes.BIGINT,
allowNull: false,
defaultValue: 0,
comment: '冻结金额(分)'
},
currency: {
type: DataTypes.STRING(3),
allowNull: false,
defaultValue: 'CNY',
comment: '货币类型'
},
interest_rate: {
type: DataTypes.DECIMAL(5, 4),
allowNull: true,
comment: '利率(年化)'
},
status: {
type: DataTypes.ENUM('active', 'inactive', 'frozen', 'closed'),
allowNull: false,
defaultValue: 'active'
},
opened_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '开户时间'
},
closed_at: {
type: DataTypes.DATE,
allowNull: true,
comment: '销户时间'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
sequelize,
tableName: 'bank_accounts',
modelName: 'Account'
});
module.exports = Account;

View File

@@ -0,0 +1,108 @@
/**
* 基础模型类
* @file BaseModel.js
* @description 所有模型的基类,提供通用方法
*/
const { Model } = require('sequelize');
class BaseModel extends Model {
/**
* 获取模型的安全信息(排除敏感字段)
* @param {Array} excludeFields 要排除的字段
* @returns {Object} 安全信息对象
*/
getSafeInfo(excludeFields = ['password', 'pin', 'secret']) {
const data = this.get({ plain: true });
excludeFields.forEach(field => {
delete data[field];
});
return data;
}
/**
* 转换为JSON格式
* @param {Array} excludeFields 要排除的字段
* @returns {Object} JSON对象
*/
toJSON(excludeFields = ['password', 'pin', 'secret']) {
return this.getSafeInfo(excludeFields);
}
/**
* 格式化金额(分转元)
* @param {Number} amount 金额(分)
* @returns {String} 格式化后的金额
*/
formatAmount(amount) {
if (amount === null || amount === undefined) return '0.00';
return (amount / 100).toFixed(2);
}
/**
* 解析金额(元转分)
* @param {String|Number} amount 金额(元)
* @returns {Number} 金额(分)
*/
parseAmount(amount) {
if (typeof amount === 'string') {
return Math.round(parseFloat(amount) * 100);
}
return Math.round(amount * 100);
}
/**
* 格式化日期
* @param {Date} date 日期
* @param {String} format 格式
* @returns {String} 格式化后的日期
*/
formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
if (!date) return null;
const moment = require('moment');
return moment(date).format(format);
}
/**
* 检查字段是否已更改
* @param {String} field 字段名
* @returns {Boolean} 是否已更改
*/
isFieldChanged(field) {
return this.changed(field);
}
/**
* 获取原始值
* @param {String} field 字段名
* @returns {*} 原始值
*/
getOriginalValue(field) {
return this._previousDataValues[field];
}
/**
* 软删除(如果模型支持)
*/
async softDelete() {
if (this.constructor.rawAttributes.deleted_at) {
this.deleted_at = new Date();
await this.save();
} else {
throw new Error('模型不支持软删除');
}
}
/**
* 恢复软删除(如果模型支持)
*/
async restore() {
if (this.constructor.rawAttributes.deleted_at) {
this.deleted_at = null;
await this.save();
} else {
throw new Error('模型不支持软删除');
}
}
}
module.exports = BaseModel;

113
bank-backend/models/Role.js Normal file
View File

@@ -0,0 +1,113 @@
/**
* 角色模型
* @file Role.js
* @description 银行系统角色模型定义
*/
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
const { sequelize } = require('../config/database');
class Role extends BaseModel {
/**
* 获取角色权限
* @returns {Promise<Array>} 权限列表
*/
async getPermissions() {
try {
const { Permission } = require('./index');
const rolePermissions = await this.getPermissions();
return rolePermissions.map(rp => rp.Permission);
} catch (error) {
console.error('获取角色权限失败:', error);
return [];
}
}
/**
* 检查角色是否具有指定权限
* @param {String|Array} permissionName 权限名称或权限名称数组
* @returns {Promise<Boolean>} 检查结果
*/
async hasPermission(permissionName) {
const permissions = await this.getPermissions();
const permissionNames = permissions.map(permission => permission.name);
if (Array.isArray(permissionName)) {
return permissionName.some(name => permissionNames.includes(name));
}
return permissionNames.includes(permissionName);
}
/**
* 获取角色用户列表
* @returns {Promise<Array>} 用户列表
*/
async getUsers() {
try {
const { User } = require('./index');
return await User.findAll({
where: { role_id: this.id }
});
} catch (error) {
console.error('获取角色用户失败:', error);
return [];
}
}
}
// 初始化Role模型
Role.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
len: [2, 50]
}
},
display_name: {
type: DataTypes.STRING(100),
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: true
},
level: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1,
comment: '角色级别,数字越大权限越高'
},
is_system: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: '是否为系统角色'
},
status: {
type: DataTypes.ENUM('active', 'inactive'),
defaultValue: 'active'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
sequelize,
tableName: 'bank_roles',
modelName: 'Role'
});
module.exports = Role;

View File

@@ -0,0 +1,210 @@
/**
* 交易记录模型
* @file Transaction.js
* @description 银行交易记录模型定义
*/
const { DataTypes } = require('sequelize');
const BaseModel = require('./BaseModel');
const { sequelize } = require('../config/database');
class Transaction extends BaseModel {
/**
* 获取交易金额(元)
* @returns {String} 格式化后的金额
*/
getAmountFormatted() {
return this.formatAmount(this.amount);
}
/**
* 获取交易后余额(元)
* @returns {String} 格式化后的余额
*/
getBalanceAfterFormatted() {
return this.formatAmount(this.balance_after);
}
/**
* 检查是否为收入交易
* @returns {Boolean} 是否为收入
*/
isIncome() {
return this.transaction_type === 'deposit' ||
this.transaction_type === 'transfer_in' ||
this.transaction_type === 'interest';
}
/**
* 检查是否为支出交易
* @returns {Boolean} 是否为支出
*/
isExpense() {
return this.transaction_type === 'withdrawal' ||
this.transaction_type === 'transfer_out' ||
this.transaction_type === 'fee';
}
/**
* 获取交易状态描述
* @returns {String} 状态描述
*/
getStatusDescription() {
const statusMap = {
'pending': '处理中',
'completed': '已完成',
'failed': '失败',
'cancelled': '已取消',
'reversed': '已冲正'
};
return statusMap[this.status] || '未知状态';
}
/**
* 获取交易类型描述
* @returns {String} 类型描述
*/
getTypeDescription() {
const typeMap = {
'deposit': '存款',
'withdrawal': '取款',
'transfer_in': '转入',
'transfer_out': '转出',
'interest': '利息',
'fee': '手续费',
'loan': '贷款',
'repayment': '还款'
};
return typeMap[this.transaction_type] || '未知类型';
}
/**
* 检查交易是否可以撤销
* @returns {Boolean} 是否可以撤销
*/
canReverse() {
return this.status === 'completed' &&
this.transaction_type !== 'fee' &&
this.created_at > new Date(Date.now() - 24 * 60 * 60 * 1000); // 24小时内
}
/**
* 撤销交易
* @returns {Promise<Boolean>} 操作结果
*/
async reverse() {
if (!this.canReverse()) {
return false;
}
try {
// 更新交易状态
this.status = 'reversed';
this.reversed_at = new Date();
await this.save();
// 这里应该创建反向交易记录
// 实际实现中需要更复杂的逻辑
return true;
} catch (error) {
console.error('撤销交易失败:', error);
return false;
}
}
}
// 初始化Transaction模型
Transaction.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
transaction_number: {
type: DataTypes.STRING(32),
allowNull: false,
unique: true,
comment: '交易流水号'
},
account_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'bank_accounts',
key: 'id'
}
},
transaction_type: {
type: DataTypes.ENUM(
'deposit', 'withdrawal', 'transfer_in', 'transfer_out',
'interest', 'fee', 'loan', 'repayment'
),
allowNull: false,
comment: '交易类型'
},
amount: {
type: DataTypes.BIGINT,
allowNull: false,
comment: '交易金额(分)'
},
balance_before: {
type: DataTypes.BIGINT,
allowNull: false,
comment: '交易前余额(分)'
},
balance_after: {
type: DataTypes.BIGINT,
allowNull: false,
comment: '交易后余额(分)'
},
counterparty_account: {
type: DataTypes.STRING(20),
allowNull: true,
comment: '对方账户号'
},
counterparty_name: {
type: DataTypes.STRING(100),
allowNull: true,
comment: '对方户名'
},
description: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '交易描述'
},
reference_number: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '参考号'
},
status: {
type: DataTypes.ENUM('pending', 'completed', 'failed', 'cancelled', 'reversed'),
allowNull: false,
defaultValue: 'pending'
},
processed_at: {
type: DataTypes.DATE,
allowNull: true,
comment: '处理时间'
},
reversed_at: {
type: DataTypes.DATE,
allowNull: true,
comment: '撤销时间'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
sequelize,
tableName: 'bank_transactions',
modelName: 'Transaction'
});
module.exports = Transaction;

183
bank-backend/models/User.js Normal file
View File

@@ -0,0 +1,183 @@
/**
* 用户模型
* @file User.js
* @description 银行系统用户模型定义
*/
const { DataTypes } = require('sequelize');
const bcrypt = require('bcryptjs');
const BaseModel = require('./BaseModel');
const { sequelize } = require('../config/database');
class User extends BaseModel {
/**
* 验证密码
* @param {String} password 待验证的密码
* @returns {Promise<Boolean>} 验证结果
*/
async validPassword(password) {
return await bcrypt.compare(password, this.password);
}
/**
* 获取用户角色
* @returns {Promise<Array>} 用户角色列表
*/
async getRoles() {
try {
const { Role } = require('./index');
const role = await Role.findByPk(this.role_id);
return role ? [role] : [];
} catch (error) {
console.error('获取用户角色失败:', error);
return [];
}
}
/**
* 检查用户是否具有指定角色
* @param {String|Array} roleName 角色名称或角色名称数组
* @returns {Promise<Boolean>} 检查结果
*/
async hasRole(roleName) {
const roles = await this.getRoles();
const roleNames = roles.map(role => role.name);
if (Array.isArray(roleName)) {
return roleName.some(name => roleNames.includes(name));
}
return roleNames.includes(roleName);
}
/**
* 获取用户账户列表
* @returns {Promise<Array>} 账户列表
*/
async getAccounts() {
try {
const { Account } = require('./index');
return await Account.findAll({
where: { user_id: this.id, status: 'active' }
});
} catch (error) {
console.error('获取用户账户失败:', error);
return [];
}
}
/**
* 获取用户安全信息(不包含密码)
* @returns {Object} 用户安全信息
*/
getSafeInfo() {
return super.getSafeInfo(['password', 'pin']);
}
}
// 初始化User模型
User.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
len: [3, 50]
}
},
email: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING(255),
allowNull: false,
validate: {
len: [6, 255]
}
},
phone: {
type: DataTypes.STRING(20),
allowNull: true,
validate: {
is: /^1[3-9]\d{9}$/
}
},
real_name: {
type: DataTypes.STRING(50),
allowNull: false
},
id_card: {
type: DataTypes.STRING(18),
allowNull: false,
unique: true,
validate: {
is: /^[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]$/
}
},
avatar: {
type: DataTypes.STRING(255),
allowNull: true
},
role_id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 2, // 默认为普通用户角色ID
references: {
model: 'bank_roles',
key: 'id'
}
},
status: {
type: DataTypes.ENUM('active', 'inactive', 'suspended', 'locked'),
defaultValue: 'active'
},
last_login: {
type: DataTypes.DATE,
allowNull: true
},
login_attempts: {
type: DataTypes.INTEGER,
defaultValue: 0
},
locked_until: {
type: DataTypes.DATE,
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
sequelize,
tableName: 'bank_users',
modelName: 'User',
hooks: {
beforeCreate: async (user) => {
if (user.password) {
user.password = await bcrypt.hash(user.password, 10);
}
},
beforeUpdate: async (user) => {
if (user.changed('password')) {
user.password = await bcrypt.hash(user.password, 10);
}
}
}
});
module.exports = User;

View File

@@ -0,0 +1,60 @@
/**
* 模型索引文件
* @file index.js
* @description 导出所有模型并建立关联关系
*/
const { sequelize } = require('../config/database');
// 导入所有模型
const User = require('./User');
const Role = require('./Role');
const Account = require('./Account');
const Transaction = require('./Transaction');
// 定义模型关联关系
// 用户与角色关联
User.belongsTo(Role, {
foreignKey: 'role_id',
as: 'role',
targetKey: 'id'
});
Role.hasMany(User, {
foreignKey: 'role_id',
as: 'users'
});
// 用户与账户关联
User.hasMany(Account, {
foreignKey: 'user_id',
as: 'accounts'
});
Account.belongsTo(User, {
foreignKey: 'user_id',
as: 'user'
});
// 账户与交易记录关联
Account.hasMany(Transaction, {
foreignKey: 'account_id',
as: 'transactions'
});
Transaction.belongsTo(Account, {
foreignKey: 'account_id',
as: 'account'
});
// 交易记录与用户关联(通过账户)
// 移除不合理的Transaction->User through Account的belongsTo定义避免错误外键映射
// 导出所有模型和数据库实例
module.exports = {
sequelize,
User,
Role,
Account,
Transaction
};