更新政府端和银行端
This commit is contained in:
224
bank-backend/models/Account.js
Normal file
224
bank-backend/models/Account.js
Normal 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;
|
||||
108
bank-backend/models/BaseModel.js
Normal file
108
bank-backend/models/BaseModel.js
Normal 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
113
bank-backend/models/Role.js
Normal 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;
|
||||
210
bank-backend/models/Transaction.js
Normal file
210
bank-backend/models/Transaction.js
Normal 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
183
bank-backend/models/User.js
Normal 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;
|
||||
60
bank-backend/models/index.js
Normal file
60
bank-backend/models/index.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user