这几天闲来无事,整理了一个完整的、可立即使用的组织机构和权限系统实现方案。
一、完整数据库设计
sql
-- 1. 组织架构相关表
CREATE TABLE organizations (
id SERIAL PRIMARY KEY,
tenant_code VARCHAR(50) NOT NULL, -- 租户编码,支持多租户
code VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL,
logo VARCHAR(255),
domain VARCHAR(100),
parent_id INTEGER REFERENCES organizations(id),
path VARCHAR(500) GENERATED ALWAYS AS (
CASE
WHEN parent_id IS NULL THEN '/' || id::TEXT || '/'
ELSE (SELECT path FROM organizations o WHERE o.id = organizations.parent_id) || id::TEXT || '/'
END
) STORED,
level INTEGER DEFAULT 0,
status SMALLINT DEFAULT 1,
order_num INTEGER DEFAULT 0,
config JSONB DEFAULT '{}',
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP,
UNIQUE(tenant_code, code)
);
CREATE INDEX idx_organizations_path ON organizations USING GIST (path gist_trgm_ops);
CREATE INDEX idx_organizations_tenant ON organizations(tenant_code);
CREATE TABLE departments (
id SERIAL PRIMARY KEY,
org_id INTEGER NOT NULL REFERENCES organizations(id),
code VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL,
parent_id INTEGER REFERENCES departments(id),
path VARCHAR(500),
level INTEGER DEFAULT 0,
manager_id INTEGER,
status SMALLINT DEFAULT 1,
order_num INTEGER DEFAULT 0,
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP,
UNIQUE(org_id, code)
);
-- 部门路径触发器
CREATE OR REPLACE FUNCTION update_department_path()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.parent_id IS NULL THEN
NEW.path = '/' || NEW.id::TEXT || '/';
NEW.level = 0;
ELSE
SELECT path || NEW.id::TEXT || '/', level + 1
INTO NEW.path, NEW.level
FROM departments
WHERE id = NEW.parent_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER department_path_trigger
BEFORE INSERT OR UPDATE ON departments
FOR EACH ROW EXECUTE FUNCTION update_department_path();
-- 用户表
CREATE TABLE users (
id SERIAL PRIMARY KEY,
tenant_code VARCHAR(50) NOT NULL,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20),
realname VARCHAR(50) NOT NULL,
avatar VARCHAR(255),
gender SMALLINT DEFAULT 0,
status SMALLINT DEFAULT 1,
password_hash VARCHAR(255) NOT NULL,
password_salt VARCHAR(50) NOT NULL,
last_login_at TIMESTAMP,
last_login_ip VARCHAR(45),
login_count INTEGER DEFAULT 0,
must_change_password BOOLEAN DEFAULT false,
lock_reason VARCHAR(200),
locked_until TIMESTAMP,
config JSONB DEFAULT '{"theme": "light", "lang": "zh-CN"}',
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP,
UNIQUE(tenant_code, username),
UNIQUE(tenant_code, email),
UNIQUE(tenant_code, phone)
);
-- 用户部门关系表
CREATE TABLE user_departments (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
department_id INTEGER NOT NULL REFERENCES departments(id),
is_primary BOOLEAN DEFAULT false,
position VARCHAR(100),
job_title VARCHAR(100),
entry_date DATE,
extra JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, department_id)
);
-- 2. 权限系统表
CREATE TABLE permissions (
id SERIAL PRIMARY KEY,
tenant_code VARCHAR(50),
parent_id INTEGER REFERENCES permissions(id),
code VARCHAR(100) NOT NULL,
name VARCHAR(100) NOT NULL,
type SMALLINT NOT NULL,
icon VARCHAR(50),
path VARCHAR(200),
component VARCHAR(200),
redirect VARCHAR(200),
is_external BOOLEAN DEFAULT false,
is_cache BOOLEAN DEFAULT true,
is_visible BOOLEAN DEFAULT true,
permission VARCHAR(200), -- API权限字符串,如 user:create
order_num INTEGER DEFAULT 0,
status SMALLINT DEFAULT 1,
meta JSONB DEFAULT '{"keepAlive": false, "affix": false, "breadcrumb": true}',
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP,
UNIQUE(tenant_code, code)
);
-- 权限路径触发器
CREATE OR REPLACE FUNCTION update_permission_path()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.parent_id IS NULL THEN
NEW.path = '/' || NEW.id::TEXT || '/';
ELSE
SELECT path || NEW.id::TEXT || '/'
INTO NEW.path
FROM permissions
WHERE id = NEW.parent_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER permission_path_trigger
BEFORE INSERT OR UPDATE ON permissions
FOR EACH ROW EXECUTE FUNCTION update_permission_path();
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
tenant_code VARCHAR(50) NOT NULL,
code VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
type SMALLINT DEFAULT 1,
is_system BOOLEAN DEFAULT false,
is_default BOOLEAN DEFAULT false,
data_scope SMALLINT DEFAULT 1, -- 1:全部,2:自定义,3:本部门,4:本部门及以下,5:仅自己
status SMALLINT DEFAULT 1,
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP,
UNIQUE(tenant_code, code)
);
CREATE TABLE user_roles (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
role_id INTEGER NOT NULL REFERENCES roles(id),
is_default BOOLEAN DEFAULT false,
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP,
UNIQUE(user_id, role_id)
);
CREATE TABLE role_permissions (
id SERIAL PRIMARY KEY,
role_id INTEGER NOT NULL REFERENCES roles(id),
permission_id INTEGER NOT NULL REFERENCES permissions(id),
actions VARCHAR(50)[] DEFAULT '{}',
data_scope_rules JSONB DEFAULT '{}',
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(role_id, permission_id)
);
CREATE TABLE data_permission_rules (
id SERIAL PRIMARY KEY,
tenant_code VARCHAR(50) NOT NULL,
role_id INTEGER REFERENCES roles(id),
name VARCHAR(100) NOT NULL,
entity VARCHAR(50) NOT NULL,
condition_type VARCHAR(20) NOT NULL, -- sql、script、custom
condition_value TEXT NOT NULL,
is_enabled BOOLEAN DEFAULT true,
priority INTEGER DEFAULT 0,
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 3. 审计日志表
CREATE TABLE audit_logs (
id BIGSERIAL PRIMARY KEY,
tenant_code VARCHAR(50) NOT NULL,
user_id INTEGER,
username VARCHAR(50),
realname VARCHAR(50),
operation VARCHAR(50) NOT NULL,
module VARCHAR(50) NOT NULL,
resource_type VARCHAR(50),
resource_id VARCHAR(100),
resource_name VARCHAR(200),
detail TEXT,
old_value JSONB,
new_value JSONB,
ip VARCHAR(45),
user_agent TEXT,
location VARCHAR(100),
status SMALLINT DEFAULT 1,
error_message TEXT,
duration INTEGER,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_audit_logs_tenant ON audit_logs(tenant_code);
CREATE INDEX idx_audit_logs_user ON audit_logs(user_id);
CREATE INDEX idx_audit_logs_created ON audit_logs(created_at);
CREATE INDEX idx_audit_logs_module ON audit_logs(module, operation);
-- 4. 系统配置表
CREATE TABLE system_configs (
id SERIAL PRIMARY KEY,
tenant_code VARCHAR(50) NOT NULL,
category VARCHAR(50) NOT NULL,
config_key VARCHAR(100) NOT NULL,
config_value JSONB NOT NULL,
description VARCHAR(200),
is_public BOOLEAN DEFAULT false,
is_encrypted BOOLEAN DEFAULT false,
extra JSONB DEFAULT '{}',
created_by INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(tenant_code, category, config_key)
);
二、完整的模型层代码
javascript
// models/index.js
const { Sequelize, DataTypes, Op } = require('sequelize');
const config = require('../config/database');
const sequelize = new Sequelize(config);
// 工具函数
const commonOptions = {
sequelize,
tableName: '',
paranoid: true, // 软删除
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
deletedAt: 'deleted_at',
underscored: true,
hooks: {
beforeCreate: (instance) => {
if (!instance.tenant_code) {
instance.tenant_code = 'default';
}
}
},
defaultScope: {
attributes: {
exclude: ['deleted_at']
}
}
};
// 组织模型
const Organization = sequelize.define('Organization', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tenant_code: {
type: DataTypes.STRING(50),
allowNull: false,
defaultValue: 'default'
},
code: {
type: DataTypes.STRING(50),
allowNull: false
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
logo: DataTypes.STRING(255),
domain: DataTypes.STRING(100),
parent_id: DataTypes.INTEGER,
path: {
type: DataTypes.STRING(500),
allowNull: true
},
level: {
type: DataTypes.INTEGER,
defaultValue: 0
},
status: {
type: DataTypes.SMALLINT,
defaultValue: 1,
validate: {
isIn: [[0, 1]]
}
},
order_num: {
type: DataTypes.INTEGER,
defaultValue: 0
},
config: {
type: DataTypes.JSONB,
defaultValue: {}
},
extra: {
type: DataTypes.JSONB,
defaultValue: {}
},
created_by: DataTypes.INTEGER
}, {
...commonOptions,
tableName: 'organizations',
indexes: [
{ fields: ['tenant_code', 'code'], unique: true },
{ fields: ['tenant_code'] },
{ fields: ['parent_id'] },
{ fields: ['path'] }
]
});
// 部门模型
const Department = sequelize.define('Department', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
org_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'organizations',
key: 'id'
}
},
code: {
type: DataTypes.STRING(50),
allowNull: false
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
parent_id: DataTypes.INTEGER,
path: DataTypes.STRING(500),
level: {
type: DataTypes.INTEGER,
defaultValue: 0
},
manager_id: DataTypes.INTEGER,
status: {
type: DataTypes.SMALLINT,
defaultValue: 1
},
order_num: {
type: DataTypes.INTEGER,
defaultValue: 0
},
extra: {
type: DataTypes.JSONB,
defaultValue: {}
},
created_by: DataTypes.INTEGER
}, {
...commonOptions,
tableName: 'departments',
indexes: [
{ fields: ['org_id', 'code'], unique: true },
{ fields: ['org_id'] },
{ fields: ['parent_id'] },
{ fields: ['path'] }
]
});
// 用户模型
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tenant_code: {
type: DataTypes.STRING(50),
allowNull: false,
defaultValue: 'default'
},
username: {
type: DataTypes.STRING(50),
allowNull: false
},
email: DataTypes.STRING(100),
phone: DataTypes.STRING(20),
realname: {
type: DataTypes.STRING(50),
allowNull: false
},
avatar: DataTypes.STRING(255),
gender: {
type: DataTypes.SMALLINT,
defaultValue: 0,
validate: {
isIn: [[0, 1, 2]] // 0-未知,1-男,2-女
}
},
status: {
type: DataTypes.SMALLINT,
defaultValue: 1
},
password_hash: {
type: DataTypes.STRING(255),
allowNull: false
},
password_salt: {
type: DataTypes.STRING(50),
allowNull: false
},
last_login_at: DataTypes.DATE,
last_login_ip: DataTypes.STRING(45),
login_count: {
type: DataTypes.INTEGER,
defaultValue: 0
},
must_change_password: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
lock_reason: DataTypes.STRING(200),
locked_until: DataTypes.DATE,
config: {
type: DataTypes.JSONB,
defaultValue: {
theme: 'light',
lang: 'zh-CN'
}
},
extra: {
type: DataTypes.JSONB,
defaultValue: {}
},
created_by: DataTypes.INTEGER
}, {
...commonOptions,
tableName: 'users',
indexes: [
{ fields: ['tenant_code', 'username'], unique: true },
{ fields: ['tenant_code', 'email'], unique: true },
{ fields: ['tenant_code', 'phone'], unique: true },
{ fields: ['tenant_code'] }
],
hooks: {
beforeCreate: async (user) => {
if (!user.password_salt) {
user.password_salt = require('crypto').randomBytes(16).toString('hex');
}
if (user.password_hash && !user.password_hash.startsWith('$')) {
user.password_hash = await hashPassword(user.password_hash, user.password_salt);
}
}
}
});
// 权限模型
const Permission = sequelize.define('Permission', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tenant_code: {
type: DataTypes.STRING(50),
defaultValue: 'default'
},
parent_id: DataTypes.INTEGER,
code: {
type: DataTypes.STRING(100),
allowNull: false
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
type: {
type: DataTypes.SMALLINT,
allowNull: false,
validate: {
isIn: [[1, 2, 3, 4]] // 1-菜单,2-API,3-按钮,4-数据
}
},
icon: DataTypes.STRING(50),
path: DataTypes.STRING(200),
component: DataTypes.STRING(200),
redirect: DataTypes.STRING(200),
is_external: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
is_cache: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
is_visible: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
permission: DataTypes.STRING(200),
order_num: {
type: DataTypes.INTEGER,
defaultValue: 0
},
status: {
type: DataTypes.SMALLINT,
defaultValue: 1
},
meta: {
type: DataTypes.JSONB,
defaultValue: {
keepAlive: false,
affix: false,
breadcrumb: true
}
},
extra: {
type: DataTypes.JSONB,
defaultValue: {}
},
created_by: DataTypes.INTEGER
}, {
...commonOptions,
tableName: 'permissions',
indexes: [
{ fields: ['tenant_code', 'code'], unique: true },
{ fields: ['tenant_code'] },
{ fields: ['parent_id'] },
{ fields: ['type'] }
]
});
// 角色模型
const Role = sequelize.define('Role', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tenant_code: {
type: DataTypes.STRING(50),
allowNull: false,
defaultValue: 'default'
},
code: {
type: DataTypes.STRING(50),
allowNull: false
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
description: DataTypes.TEXT,
type: {
type: DataTypes.SMALLINT,
defaultValue: 1,
validate: {
isIn: [[1, 2]] // 1-系统角色,2-自定义角色
}
},
is_system: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
is_default: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
data_scope: {
type: DataTypes.SMALLINT,
defaultValue: 1,
validate: {
isIn: [[1, 2, 3, 4, 5]]
}
},
status: {
type: DataTypes.SMALLINT,
defaultValue: 1
},
extra: {
type: DataTypes.JSONB,
defaultValue: {}
},
created_by: DataTypes.INTEGER
}, {
...commonOptions,
tableName: 'roles',
indexes: [
{ fields: ['tenant_code', 'code'], unique: true },
{ fields: ['tenant_code'] },
{ fields: ['type'] }
]
});
// 关联模型
const UserDepartment = sequelize.define('UserDepartment', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: User,
key: 'id'
}
},
department_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Department,
key: 'id'
}
},
is_primary: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
position: DataTypes.STRING(100),
job_title: DataTypes.STRING(100),
entry_date: DataTypes.DATEONLY,
extra: {
type: DataTypes.JSONB,
defaultValue: {}
}
}, {
...commonOptions,
tableName: 'user_departments',
indexes: [
{ fields: ['user_id', 'department_id'], unique: true },
{ fields: ['user_id'] },
{ fields: ['department_id'] }
]
});
const UserRole = sequelize.define('UserRole', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: User,
key: 'id'
}
},
role_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Role,
key: 'id'
}
},
is_default: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
extra: {
type: DataTypes.JSONB,
defaultValue: {}
},
created_by: DataTypes.INTEGER
}, {
...commonOptions,
tableName: 'user_roles',
indexes: [
{ fields: ['user_id', 'role_id'], unique: true },
{ fields: ['user_id'] },
{ fields: ['role_id'] }
]
});
const RolePermission = sequelize.define('RolePermission', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
role_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Role,
key: 'id'
}
},
permission_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Permission,
key: 'id'
}
},
actions: {
type: DataTypes.ARRAY(DataTypes.STRING(50)),
defaultValue: []
},
data_scope_rules: {
type: DataTypes.JSONB,
defaultValue: {}
},
extra: {
type: DataTypes.JSONB,
defaultValue: {}
},
created_by: DataTypes.INTEGER
}, {
...commonOptions,
tableName: 'role_permissions',
indexes: [
{ fields: ['role_id', 'permission_id'], unique: true },
{ fields: ['role_id'] },
{ fields: ['permission_id'] }
]
});
// 定义关联关系
Organization.hasMany(Organization, { as: 'children', foreignKey: 'parent_id' });
Organization.belongsTo(Organization, { as: 'parent', foreignKey: 'parent_id' });
Department.belongsTo(Organization, { foreignKey: 'org_id' });
Organization.hasMany(Department, { foreignKey: 'org_id' });
Department.hasMany(Department, { as: 'children', foreignKey: 'parent_id' });
Department.belongsTo(Department, { as: 'parent', foreignKey: 'parent_id' });
// 用户关联
User.belongsToMany(Department, {
through: UserDepartment,
as: 'departments',
foreignKey: 'user_id'
});
Department.belongsToMany(User, {
through: UserDepartment,
as: 'users',
foreignKey: 'department_id'
});
User.belongsToMany(Role, {
through: UserRole,
as: 'roles',
foreignKey: 'user_id'
});
Role.belongsToMany(User, {
through: UserRole,
as: 'users',
foreignKey: 'role_id'
});
// 权限关联
Permission.hasMany(Permission, { as: 'children', foreignKey: 'parent_id' });
Permission.belongsTo(Permission, { as: 'parent', foreignKey: 'parent_id' });
Role.belongsToMany(Permission, {
through: RolePermission,
as: 'permissions',
foreignKey: 'role_id'
});
Permission.belongsToMany(Role, {
through: RolePermission,
as: 'roles',
foreignKey: 'permission_id'
});
// 工具函数
const hashPassword = async (password, salt) => {
const crypto = require('crypto');
return crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');
};
User.prototype.verifyPassword = function(password) {
const hash = hashPassword(password, this.password_salt);
return hash === this.password_hash;
};
User.prototype.toJSON = function() {
const values = Object.assign({}, this.get());
delete values.password_hash;
delete values.password_salt;
return values;
};
module.exports = {
sequelize,
Organization,
Department,
User,
UserDepartment,
Permission,
Role,
UserRole,
RolePermission
};
三、完整的服务层代码
javascript
// services/UserService.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { Op } = require('sequelize');
const BaseService = require('./BaseService');
const {
User,
Department,
UserDepartment,
Role,
UserRole,
Permission,
RolePermission
} = require('../models');
class UserService extends BaseService {
constructor() {
super(User);
}
/**
* 创建用户
*/
async createUser(data, createdBy) {
const transaction = await this.sequelize.transaction();
try {
// 检查用户名是否已存在
const existingUser = await User.findOne({
where: {
tenant_code: data.tenant_code || 'default',
[Op.or]: [
{ username: data.username },
{ email: data.email },
{ phone: data.phone }
].filter(condition => data[Object.keys(condition)[0]])
}
});
if (existingUser) {
throw new Error('用户名、邮箱或手机号已存在');
}
// 创建用户
const userData = {
...data,
created_by: createdBy
};
const user = await User.create(userData, { transaction });
// 关联部门
if (data.department_ids && data.department_ids.length > 0) {
const userDepartments = data.department_ids.map((deptId, index) => ({
user_id: user.id,
department_id: deptId,
is_primary: index === 0, // 第一个部门为主部门
position: data.position,
job_title: data.job_title,
entry_date: data.entry_date
}));
await UserDepartment.bulkCreate(userDepartments, { transaction });
}
// 关联角色
if (data.role_ids && data.role_ids.length > 0) {
const userRoles = data.role_ids.map((roleId, index) => ({
user_id: user.id,
role_id: roleId,
is_default: index === 0,
created_by: createdBy
}));
await UserRole.bulkCreate(userRoles, { transaction });
}
await transaction.commit();
// 返回用户信息(不包含密码)
return await this.getUserById(user.id, true);
} catch (error) {
await transaction.rollback();
throw error;
}
}
/**
* 获取用户详情(包含关联信息)
*/
async getUserById(userId, withAssociations = false) {
const include = [];
if (withAssociations) {
include.push(
{
model: Department,
as: 'departments',
through: { attributes: ['is_primary', 'position', 'job_title'] },
include: [{
model: Department,
as: 'parent'
}]
},
{
model: Role,
as: 'roles',
through: { attributes: ['is_default'] },
include: [{
model: Permission,
as: 'permissions',
through: { attributes: ['actions'] }
}]
}
);
}
const user = await User.findByPk(userId, { include });
if (!user) {
throw new Error('用户不存在');
}
return user;
}
/**
* 分页查询用户列表
*/
async getUsers(query = {}, page = 1, pageSize = 20) {
const {
tenant_code = 'default',
username,
realname,
email,
phone,
status,
department_id,
role_id,
order_by = 'created_at',
order_direction = 'DESC'
} = query;
const where = { tenant_code };
const include = [];
// 构建查询条件
if (username) where.username = { [Op.like]: `%${username}%` };
if (realname) where.realname = { [Op.like]: `%${realname}%` };
if (email) where.email = { [Op.like]: `%${email}%` };
if (phone) where.phone = { [Op.like]: `%${phone}%` };
if (status !== undefined) where.status = status;
// 部门筛选
if (department_id) {
include.push({
model: Department,
as: 'departments',
where: { id: department_id },
required: true,
through: { attributes: [] }
});
}
// 角色筛选
if (role_id) {
include.push({
model: Role,
as: 'roles',
where: { id: role_id },
required: true,
through: { attributes: [] }
});
}
// 执行查询
const result = await User.findAndCountAll({
where,
include,
attributes: { exclude: ['password_hash', 'password_salt'] },
order: [[order_by, order_direction]],
limit: pageSize,
offset: (page - 1) * pageSize,
distinct: true
});
return {
data: result.rows,
pagination: {
total: result.count,
page,
pageSize,
totalPages: Math.ceil(result.count / pageSize)
}
};
}
/**
* 更新用户信息
*/
async updateUser(userId, data, updatedBy) {
const transaction = await this.sequelize.transaction();
try {
const user = await User.findByPk(userId);
if (!user) {
throw new Error('用户不存在');
}
// 更新基本信息
await user.update({
...data,
updated_at: new Date()
}, { transaction });
// 更新部门关系
if (data.department_ids !== undefined) {
await UserDepartment.destroy({
where: { user_id: userId },
transaction
});
if (data.department_ids.length > 0) {
const userDepartments = data.department_ids.map((deptId, index) => ({
user_id: userId,
department_id: deptId,
is_primary: index === 0,
position: data.position,
job_title: data.job_title,
entry_date: data.entry_date
}));
await UserDepartment.bulkCreate(userDepartments, { transaction });
}
}
// 更新角色关系
if (data.role_ids !== undefined) {
await UserRole.destroy({
where: { user_id: userId },
transaction
});
if (data.role_ids.length > 0) {
const userRoles = data.role_ids.map((roleId, index) => ({
user_id: userId,
role_id: roleId,
is_default: index === 0,
created_by: updatedBy
}));
await UserRole.bulkCreate(userRoles, { transaction });
}
}
await transaction.commit();
return await this.getUserById(userId, true);
} catch (error) {
await transaction.rollback();
throw error;
}
}
/**
* 重置密码
*/
async resetPassword(userId, newPassword) {
const user = await User.findByPk(userId);
if (!user) {
throw new Error('用户不存在');
}
const salt = require('crypto').randomBytes(16).toString('hex');
const hash = await hashPassword(newPassword, salt);
await user.update({
password_hash: hash,
password_salt: salt,
must_change_password: false
});
return true;
}
/**
* 用户登录
*/
async login(username, password, tenantCode = 'default', ip = '') {
const user = await User.findOne({
where: {
tenant_code: tenantCode,
[Op.or]: [
{ username },
{ email: username },
{ phone: username }
]
},
include: [{
model: Role,
as: 'roles',
include: [{
model: Permission,
as: 'permissions',
where: { type: 2 } // API权限
}]
}]
});
if (!user) {
throw new Error('用户不存在');
}
if (user.status === 0) {
throw new Error('账户已被禁用');
}
if (user.locked_until && user.locked_until > new Date()) {
throw new Error('账户已被锁定');
}
// 验证密码
const isValid = await user.verifyPassword(password);
if (!isValid) {
// 记录登录失败
user.login_attempts = (user.login_attempts || 0) + 1;
if (user.login_attempts >= 5) {
user.locked_until = new Date(Date.now() + 30 * 60 * 1000); // 锁定30分钟
user.lock_reason = '密码错误次数过多';
}
await user.save();
throw new Error('密码错误');
}
// 重置登录失败计数
user.login_attempts = 0;
user.last_login_at = new Date();
user.last_login_ip = ip;
user.login_count = (user.login_count || 0) + 1;
await user.save();
// 生成JWT令牌
const token = jwt.sign(
{
id: user.id,
username: user.username,
tenant_code: user.tenant_code,
roles: user.roles.map(role => role.code)
},
process.env.JWT_SECRET || 'your-secret-key',
{ expiresIn: '7d' }
);
// 获取用户权限
const permissions = this.getUserPermissions(user);
return {
user: user.toJSON(),
token,
permissions,
expires_in: 7 * 24 * 60 * 60 // 7天
};
}
/**
* 获取用户所有权限
*/
getUserPermissions(user) {
const permissions = new Map();
user.roles.forEach(role => {
role.permissions.forEach(perm => {
if (!permissions.has(perm.code)) {
permissions.set(perm.code, {
code: perm.code,
permission: perm.permission,
actions: []
});
}
const existing = permissions.get(perm.code);
if (perm.RolePermission && perm.RolePermission.actions) {
existing.actions = [...new Set([...existing.actions, ...perm.RolePermission.actions])];
}
});
});
return Array.from(permissions.values());
}
/**
* 获取用户菜单权限
*/
async getUserMenus(userId) {
const user = await User.findByPk(userId, {
include: [{
model: Role,
as: 'roles',
include: [{
model: Permission,
as: 'permissions',
where: {
type: 1, // 菜单类型
status: 1,
is_visible: true
},
through: { attributes: [] }
}]
}]
});
if (!user) {
return [];
}
// 收集所有菜单权限
const menuMap = new Map();
user.roles.forEach(role => {
role.permissions.forEach(menu => {
menuMap.set(menu.id, menu.toJSON());
});
});
// 构建菜单树
return this.buildMenuTree(Array.from(menuMap.values()));
}
/**
* 构建菜单树
*/
buildMenuTree(menus) {
const menuMap = new Map();
const tree = [];
// 建立id到菜单的映射
menus.forEach(menu => {
menuMap.set(menu.id, { ...menu, children: [] });
});
// 构建树形结构
menus.forEach(menu => {
const node = menuMap.get(menu.id);
if (menu.parent_id && menuMap.has(menu.parent_id)) {
menuMap.get(menu.parent_id).children.push(node);
} else {
tree.push(node);
}
});
// 排序
const sortMenu = (menuList) => {
return menuList.sort((a, b) => a.order_num - b.order_num);
};
const sortTree = (treeNodes) => {
sortMenu(treeNodes);
treeNodes.forEach(node => {
if (node.children.length > 0) {
sortTree(node.children);
}
});
return treeNodes;
};
return sortTree(tree);
}
}
module.exports = new UserService();
javascript
// services/PermissionService.js
const BaseService = require('./BaseService');
const { Permission, RolePermission } = require('../models');
class PermissionService extends BaseService {
constructor() {
super(Permission);
}
/**
* 获取权限树
*/
async getPermissionTree(tenantCode = 'default', type = null) {
const where = { tenant_code: tenantCode, status: 1 };
if (type !== null) {
where.type = type;
}
const permissions = await Permission.findAll({
where,
order: [['order_num', 'ASC']]
});
return this.buildTree(permissions);
}
/**
* 构建树形结构
*/
buildTree(items, parentId = null) {
const tree = [];
items
.filter(item => item.parent_id === parentId)
.forEach(item => {
const children = this.buildTree(items, item.id);
const node = item.toJSON();
if (children.length > 0) {
node.children = children;
}
tree.push(node);
});
// 排序
return tree.sort((a, b) => a.order_num - b.order_num);
}
/**
* 为角色分配权限
*/
async assignPermissionsToRole(roleId, permissions, createdBy) {
const transaction = await this.sequelize.transaction();
try {
// 删除旧权限
await RolePermission.destroy({
where: { role_id: roleId },
transaction
});
// 添加新权限
if (permissions && permissions.length > 0) {
const rolePermissions = permissions.map(perm => ({
role_id: roleId,
permission_id: perm.permission_id,
actions: perm.actions || [],
data_scope_rules: perm.data_scope_rules || {},
created_by: createdBy
}));
await RolePermission.bulkCreate(rolePermissions, { transaction });
}
await transaction.commit();
return true;
} catch (error) {
await transaction.rollback();
throw error;
}
}
/**
* 检查用户是否有某个权限
*/
async checkPermission(userId, permissionCode, action = null) {
const { User, Role, Permission, RolePermission } = require('../models');
const user = await User.findByPk(userId, {
include: [{
model: Role,
as: 'roles',
include: [{
model: Permission,
as: 'permissions',
where: { code: permissionCode },
through: { attributes: ['actions'] }
}]
}]
});
if (!user) {
return false;
}
// 检查是否有权限
for (const role of user.roles) {
for (const perm of role.permissions) {
if (perm.code === permissionCode) {
if (!action) {
return true;
}
if (perm.RolePermission.actions && perm.RolePermission.actions.includes(action)) {
return true;
}
}
}
}
return false;
}
/**
* 获取用户的数据权限规则
*/
async getUserDataRules(userId, entity) {
const { User, Role, DataRule } = require('../models');
const user = await User.findByPk(userId, {
include: [{
model: Role,
as: 'roles',
include: [{
model: DataRule,
as: 'data_rules',
where: {
entity,
is_enabled: true
}
}]
}]
});
if (!user) {
return [];
}
// 收集所有数据权限规则
const rules = [];
user.roles.forEach(role => {
role.data_rules.forEach(rule => {
rules.push({
...rule.toJSON(),
role_id: role.id,
role_code: role.code,
priority: rule.priority || 0
});
});
});
// 按优先级排序
return rules.sort((a, b) => b.priority - a.priority);
}
/**
* 应用数据权限到查询条件
*/
applyDataRules(query, rules, user) {
if (!rules || rules.length === 0) {
return query;
}
const whereClause = {};
rules.forEach(rule => {
switch (rule.condition_type) {
case 'sql':
// 解析SQL条件
const condition = this.parseSqlCondition(rule.condition_value, user);
Object.assign(whereClause, condition);
break;
case 'custom':
// 自定义条件处理
const customCondition = this.handleCustomCondition(rule, user);
if (customCondition) {
Object.assign(whereClause, customCondition);
}
break;
case 'script':
// JavaScript脚本条件(需沙箱环境)
const scriptCondition = this.executeScriptCondition(rule.condition_value, user);
if (scriptCondition) {
Object.assign(whereClause, scriptCondition);
}
break;
}
});
// 合并到原始查询条件
if (query.where) {
query.where = { [Op.and]: [query.where, whereClause] };
} else {
query.where = whereClause;
}
return query;
}
parseSqlCondition(sql, user) {
// 替换变量
const replacedSql = sql
.replace(/\${userId}/g, user.id)
.replace(/\${deptId}/g, user.department_id || '')
.replace(/\${tenantCode}/g, user.tenant_code);
// 这里需要根据实际需求解析SQL为Sequelize条件
// 简化实现:返回一个简单条件
return { user_id: user.id };
}
handleCustomCondition(rule, user) {
// 根据规则类型处理自定义条件
switch (rule.name) {
case 'self_data':
return { created_by: user.id };
case 'department_data':
return { department_id: user.department_id };
case 'department_and_below':
// 需要部门路径查询
return this.getDepartmentPathCondition(user.department_id);
default:
return null;
}
}
getDepartmentPathCondition(departmentId) {
if (!departmentId) return null;
// 需要先查询部门路径
return {
[Op.or]: [
{ department_id: departmentId },
{ department_path: { [Op.like]: `%/${departmentId}/%` } }
]
};
}
}
module.exports = new PermissionService();
javascript
// services/RoleService.js
const BaseService = require('./BaseService');
const { Role, Permission, RolePermission, UserRole } = require('../models');
class RoleService extends BaseService {
constructor() {
super(Role);
}
/**
* 创建角色
*/
async createRole(data, createdBy) {
const transaction = await this.sequelize.transaction();
try {
// 检查角色编码是否已存在
const existing = await Role.findOne({
where: {
tenant_code: data.tenant_code || 'default',
code: data.code
}
});
if (existing) {
throw new Error('角色编码已存在');
}
const role = await Role.create({
...data,
created_by: createdBy
}, { transaction });
// 分配权限
if (data.permissions && data.permissions.length > 0) {
const rolePermissions = data.permissions.map(perm => ({
role_id: role.id,
permission_id: perm.permission_id,
actions: perm.actions || [],
data_scope_rules: perm.data_scope_rules || {},
created_by: createdBy
}));
await RolePermission.bulkCreate(rolePermissions, { transaction });
}
await transaction.commit();
return await this.getRoleWithPermissions(role.id);
} catch (error) {
await transaction.rollback();
throw error;
}
}
/**
* 获取角色详情(包含权限)
*/
async getRoleWithPermissions(roleId) {
const role = await Role.findByPk(roleId, {
include: [{
model: Permission,
as: 'permissions',
through: { attributes: ['actions', 'data_scope_rules'] }
}]
});
if (!role) {
throw new Error('角色不存在');
}
return role;
}
/**
* 更新角色权限
*/
async updateRolePermissions(roleId, permissions, updatedBy) {
const role = await Role.findByPk(roleId);
if (!role) {
throw new Error('角色不存在');
}
if (role.is_system) {
throw new Error('系统角色不能修改权限');
}
const permissionService = require('./PermissionService');
return await permissionService.assignPermissionsToRole(roleId, permissions, updatedBy);
}
/**
* 为用户分配角色
*/
async assignRolesToUser(userId, roleIds, createdBy) {
const transaction = await this.sequelize.transaction();
try {
// 删除旧角色
await UserRole.destroy({
where: { user_id: userId },
transaction
});
// 添加新角色
if (roleIds && roleIds.length > 0) {
const userRoles = roleIds.map((roleId, index) => ({
user_id: userId,
role_id: roleId,
is_default: index === 0,
created_by: createdBy
}));
await UserRole.bulkCreate(userRoles, { transaction });
}
await transaction.commit();
return true;
} catch (error) {
await transaction.rollback();
throw error;
}
}
/**
* 获取角色的用户列表
*/
async getRoleUsers(roleId, page = 1, pageSize = 20) {
const role = await Role.findByPk(roleId);
if (!role) {
throw new Error('角色不存在');
}
const result = await UserRole.findAndCountAll({
where: { role_id: roleId },
include: [{
model: require('../models').User,
as: 'user',
attributes: { exclude: ['password_hash', 'password_salt'] }
}],
limit: pageSize,
offset: (page - 1) * pageSize
});
return {
data: result.rows.map(row => row.user),
pagination: {
total: result.count,
page,
pageSize,
totalPages: Math.ceil(result.count / pageSize)
}
};
}
}
module.exports = new RoleService();
四、完整的控制器层代码
javascript
// controllers/AuthController.js
const userService = require('../services/UserService');
const permissionService = require('../services/PermissionService');
const auditService = require('../services/AuditService');
class AuthController {
/**
* 用户登录
*/
async login(ctx) {
const { username, password, tenant_code = 'default' } = ctx.request.body;
const ip = ctx.request.ip;
try {
const result = await userService.login(username, password, tenant_code, ip);
// 记录登录日志
await auditService.log({
tenant_code: tenant_code,
user_id: result.user.id,
username: result.user.username,
realname: result.user.realname,
operation: 'login',
module: 'auth',
ip,
status: 1,
detail: '用户登录成功'
});
ctx.success(result);
} catch (error) {
await auditService.log({
tenant_code: tenant_code,
operation: 'login',
module: 'auth',
ip,
status: 0,
error_message: error.message,
detail: `登录失败: ${username}`
});
ctx.fail(error.message, 401);
}
}
/**
* 获取当前用户信息
*/
async getCurrentUser(ctx) {
try {
const userId = ctx.state.user.id;
const user = await userService.getUserById(userId, true);
// 获取用户菜单
const menus = await userService.getUserMenus(userId);
ctx.success({
user,
menus
});
} catch (error) {
ctx.fail(error.message, 500);
}
}
/**
* 修改密码
*/
async changePassword(ctx) {
const userId = ctx.state.user.id;
const { old_password, new_password } = ctx.request.body;
try {
const user = await userService.getUserById(userId);
// 验证旧密码
const isValid = await user.verifyPassword(old_password);
if (!isValid) {
throw new Error('旧密码错误');
}
// 更新密码
await userService.resetPassword(userId, new_password);
// 记录操作日志
await auditService.log({
tenant_code: user.tenant_code,
user_id: userId,
operation: 'change_password',
module: 'auth',
status: 1,
detail: '修改密码成功'
});
ctx.success(null, '密码修改成功');
} catch (error) {
await auditService.log({
user_id: userId,
operation: 'change_password',
module: 'auth',
status: 0,
error_message: error.message
});
ctx.fail(error.message);
}
}
/**
* 退出登录
*/
async logout(ctx) {
const userId = ctx.state.user.id;
try {
await auditService.log({
user_id: userId,
operation: 'logout',
module: 'auth',
status: 1,
ip: ctx.request.ip,
detail: '用户退出登录'
});
ctx.success(null, '退出成功');
} catch (error) {
ctx.fail(error.message);
}
}
/**
* 获取用户菜单
*/
async getUserMenus(ctx) {
const userId = ctx.state.user.id;
try {
const menus = await userService.getUserMenus(userId);
ctx.success(menus);
} catch (error) {
ctx.fail(error.message);
}
}
/**
* 检查权限
*/
async checkPermission(ctx) {
const { permission, action } = ctx.query;
const userId = ctx.state.user.id;
try {
const hasPermission = await permissionService.checkPermission(userId, permission, action);
ctx.success({ has_permission: hasPermission });
} catch (error) {
ctx.fail(error.message);
}
}
}
module.exports = new AuthController();
javascript
// controllers/UserController.js
const userService = require('../services/UserService');
const roleService = require('../services/RoleService');
const auditService = require('../services/AuditService');
class UserController {
/**
* 创建用户
*/
async createUser(ctx) {
const data = ctx.request.body;
const createdBy = ctx.state.user.id;
try {
const user = await userService.createUser(data, createdBy);
await auditService.log({
tenant_code: user.tenant_code,
user_id: createdBy,
operation: 'create',
module: 'user',
resource_type: 'user',
resource_id: user.id,
resource_name: user.realname,
detail: `创建用户: ${user.realname} (${user.username})`
});
ctx.success(user, '用户创建成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
/**
* 获取用户列表
*/
async getUsers(ctx) {
const query = ctx.query;
const page = parseInt(query.page) || 1;
const pageSize = parseInt(query.page_size) || 20;
try {
const result = await userService.getUsers(query, page, pageSize);
ctx.success(result);
} catch (error) {
ctx.fail(error.message, 500);
}
}
/**
* 获取用户详情
*/
async getUser(ctx) {
const userId = parseInt(ctx.params.id);
try {
const user = await userService.getUserById(userId, true);
ctx.success(user);
} catch (error) {
ctx.fail(error.message, 404);
}
}
/**
* 更新用户
*/
async updateUser(ctx) {
const userId = parseInt(ctx.params.id);
const data = ctx.request.body;
const updatedBy = ctx.state.user.id;
try {
const user = await userService.updateUser(userId, data, updatedBy);
await auditService.log({
tenant_code: user.tenant_code,
user_id: updatedBy,
operation: 'update',
module: 'user',
resource_type: 'user',
resource_id: user.id,
resource_name: user.realname,
detail: `更新用户信息: ${user.realname}`
});
ctx.success(user, '用户更新成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
/**
* 删除用户(软删除)
*/
async deleteUser(ctx) {
const userId = parseInt(ctx.params.id);
const deletedBy = ctx.state.user.id;
try {
const user = await userService.getUserById(userId);
await userService.delete(userId);
await auditService.log({
tenant_code: user.tenant_code,
user_id: deletedBy,
operation: 'delete',
module: 'user',
resource_type: 'user',
resource_id: user.id,
resource_name: user.realname,
detail: `删除用户: ${user.realname}`
});
ctx.success(null, '用户删除成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
/**
* 重置用户密码
*/
async resetUserPassword(ctx) {
const userId = parseInt(ctx.params.id);
const { new_password } = ctx.request.body;
const operatorId = ctx.state.user.id;
try {
await userService.resetPassword(userId, new_password);
await auditService.log({
user_id: operatorId,
operation: 'reset_password',
module: 'user',
resource_type: 'user',
resource_id: userId,
detail: '重置用户密码'
});
ctx.success(null, '密码重置成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
/**
* 更新用户角色
*/
async updateUserRoles(ctx) {
const userId = parseInt(ctx.params.id);
const { role_ids } = ctx.request.body;
const operatorId = ctx.state.user.id;
try {
await roleService.assignRolesToUser(userId, role_ids, operatorId);
await auditService.log({
user_id: operatorId,
operation: 'update_roles',
module: 'user',
resource_type: 'user',
resource_id: userId,
detail: `更新用户角色`
});
ctx.success(null, '用户角色更新成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
}
module.exports = new UserController();
javascript
// controllers/RoleController.js
const roleService = require('../services/RoleService');
const permissionService = require('../services/PermissionService');
const auditService = require('../services/AuditService');
class RoleController {
/**
* 创建角色
*/
async createRole(ctx) {
const data = ctx.request.body;
const createdBy = ctx.state.user.id;
try {
const role = await roleService.createRole(data, createdBy);
await auditService.log({
tenant_code: role.tenant_code,
user_id: createdBy,
operation: 'create',
module: 'role',
resource_type: 'role',
resource_id: role.id,
resource_name: role.name,
detail: `创建角色: ${role.name}`
});
ctx.success(role, '角色创建成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
/**
* 获取角色列表
*/
async getRoles(ctx) {
const {
tenant_code = 'default',
name,
code,
type,
status,
page = 1,
page_size = 20
} = ctx.query;
const where = { tenant_code };
if (name) where.name = { [Op.like]: `%${name}%` };
if (code) where.code = { [Op.like]: `%${code}%` };
if (type !== undefined) where.type = type;
if (status !== undefined) where.status = status;
try {
const roles = await roleService.findAll({
where,
order: [['created_at', 'DESC']],
limit: parseInt(page_size),
offset: (parseInt(page) - 1) * parseInt(page_size)
});
const total = await roleService.count({ where });
ctx.success({
data: roles,
pagination: {
total,
page: parseInt(page),
page_size: parseInt(page_size),
total_pages: Math.ceil(total / parseInt(page_size))
}
});
} catch (error) {
ctx.fail(error.message, 500);
}
}
/**
* 获取角色详情
*/
async getRole(ctx) {
const roleId = parseInt(ctx.params.id);
try {
const role = await roleService.getRoleWithPermissions(roleId);
ctx.success(role);
} catch (error) {
ctx.fail(error.message, 404);
}
}
/**
* 更新角色
*/
async updateRole(ctx) {
const roleId = parseInt(ctx.params.id);
const data = ctx.request.body;
const updatedBy = ctx.state.user.id;
try {
const role = await roleService.findById(roleId);
if (!role) {
throw new Error('角色不存在');
}
if (role.is_system) {
throw new Error('系统角色不能修改');
}
await roleService.update(roleId, data);
await auditService.log({
tenant_code: role.tenant_code,
user_id: updatedBy,
operation: 'update',
module: 'role',
resource_type: 'role',
resource_id: role.id,
resource_name: role.name,
detail: `更新角色: ${role.name}`
});
ctx.success(null, '角色更新成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
/**
* 删除角色
*/
async deleteRole(ctx) {
const roleId = parseInt(ctx.params.id);
const deletedBy = ctx.state.user.id;
try {
const role = await roleService.findById(roleId);
if (!role) {
throw new Error('角色不存在');
}
if (role.is_system) {
throw new Error('系统角色不能删除');
}
// 检查是否有用户使用该角色
const userCount = await require('../models').UserRole.count({
where: { role_id: roleId }
});
if (userCount > 0) {
throw new Error('该角色已有用户使用,不能删除');
}
await roleService.delete(roleId);
await auditService.log({
tenant_code: role.tenant_code,
user_id: deletedBy,
operation: 'delete',
module: 'role',
resource_type: 'role',
resource_id: role.id,
resource_name: role.name,
detail: `删除角色: ${role.name}`
});
ctx.success(null, '角色删除成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
/**
* 更新角色权限
*/
async updateRolePermissions(ctx) {
const roleId = parseInt(ctx.params.id);
const { permissions } = ctx.request.body;
const updatedBy = ctx.state.user.id;
try {
await roleService.updateRolePermissions(roleId, permissions, updatedBy);
await auditService.log({
user_id: updatedBy,
operation: 'update_permissions',
module: 'role',
resource_type: 'role',
resource_id: roleId,
detail: `更新角色权限`
});
ctx.success(null, '角色权限更新成功');
} catch (error) {
ctx.fail(error.message, 400);
}
}
/**
* 获取角色用户列表
*/
async getRoleUsers(ctx) {
const roleId = parseInt(ctx.params.id);
const page = parseInt(ctx.query.page) || 1;
const pageSize = parseInt(ctx.query.page_size) || 20;
try {
const result = await roleService.getRoleUsers(roleId, page, pageSize);
ctx.success(result);
} catch (error) {
ctx.fail(error.message, 500);
}
}
}
module.exports = new RoleController();
五、中间件和工具类
javascript
// middleware/auth.js
const jwt = require('jsonwebtoken');
const permissionService = require('../services/PermissionService');
/**
* JWT认证中间件
*/
const authenticate = () => {
return async (ctx, next) => {
const token = ctx.headers.authorization?.replace('Bearer ', '');
if (!token) {
ctx.throw(401, '未提供认证令牌');
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key');
ctx.state.user = decoded;
// 检查用户状态(可选)
const { User } = require('../models');
const user = await User.findByPk(decoded.id);
if (!user || user.status === 0) {
ctx.throw(401, '用户不存在或已被禁用');
}
if (user.locked_until && user.locked_until > new Date()) {
ctx.throw(401, '账户已被锁定');
}
await next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
ctx.throw(401, '令牌已过期');
} else if (error.name === 'JsonWebTokenError') {
ctx.throw(401, '无效的令牌');
} else {
ctx.throw(401, error.message);
}
}
};
};
/**
* 权限检查中间件
*/
const checkPermission = (permissionCode, action = null) => {
return async (ctx, next) => {
if (!ctx.state.user) {
ctx.throw(401, '用户未认证');
}
const userId = ctx.state.user.id;
const hasPermission = await permissionService.checkPermission(userId, permissionCode, action);
if (!hasPermission) {
ctx.throw(403, '权限不足');
}
await next();
};
};
/**
* 数据权限中间件
*/
const applyDataPermission = (entity) => {
return async (ctx, next) => {
if (!ctx.state.user) {
ctx.throw(401, '用户未认证');
}
// 获取用户数据权限规则
const userId = ctx.state.user.id;
const rules = await permissionService.getUserDataRules(userId, entity);
// 应用数据权限规则到查询
if (ctx.method === 'GET' && ctx.query) {
ctx.query = permissionService.applyDataRules(ctx.query, rules, ctx.state.user);
}
await next();
};
};
module.exports = {
authenticate,
checkPermission,
applyDataPermission
};
javascript
// middleware/audit.js
const auditService = require('../services/AuditService');
/**
* 操作日志中间件
*/
const auditLog = (operation, module) => {
return async (ctx, next) => {
const startTime = Date.now();
let error = null;
try {
await next();
} catch (err) {
error = err;
throw err;
} finally {
const duration = Date.now() - startTime;
// 记录操作日志
await auditService.log({
tenant_code: ctx.state.user?.tenant_code || 'default',
user_id: ctx.state.user?.id,
username: ctx.state.user?.username,
realname: ctx.state.user?.realname,
operation,
module,
resource_type: ctx.params.resource_type,
resource_id: ctx.params.id,
resource_name: ctx.request.body?.name,
detail: ctx.request.originalUrl,
old_value: ctx.request.old_value,
new_value: ctx.request.new_value,
ip: ctx.request.ip,
user_agent: ctx.headers['user-agent'],
status: error ? 0 : 1,
error_message: error?.message,
duration
});
}
};
};
module.exports = auditLog;
javascript
// utils/response.js
/**
* 统一响应格式中间件
*/
const responseHandler = () => {
return async (ctx, next) => {
ctx.success = (data = null, message = 'success', code = 200) => {
ctx.status = code;
ctx.body = {
code,
message,
data,
timestamp: new Date().toISOString()
};
};
ctx.fail = (message = 'error', code = 400, data = null) => {
ctx.status = code;
ctx.body = {
code,
message,
data,
timestamp: new Date().toISOString()
};
};
try {
await next();
} catch (error) {
const status = error.status || 500;
const message = error.message || 'Internal Server Error';
ctx.status = status;
ctx.body = {
code: status,
message,
data: null,
timestamp: new Date().toISOString()
};
}
};
};
module.exports = responseHandler;
六、路由配置
javascript
// routes/index.js
const Router = require('koa-router');
const { authenticate, checkPermission } = require('../middleware/auth');
const responseHandler = require('../utils/response');
// 控制器
const AuthController = require('../controllers/AuthController');
const UserController = require('../controllers/UserController');
const RoleController = require('../controllers/RoleController');
const PermissionController = require('../controllers/PermissionController');
const OrganizationController = require('../controllers/OrganizationController');
const router = new Router();
// 应用响应处理中间件
router.use(responseHandler());
// 公开路由 - 无需认证
router.post('/auth/login', AuthController.login);
router.post('/auth/logout', AuthController.logout);
// 需要认证的路由
router.use(authenticate());
// 用户相关路由
router.get('/users', checkPermission('user:view'), UserController.getUsers);
router.post('/users', checkPermission('user:create'), UserController.createUser);
router.get('/users/:id', checkPermission('user:view'), UserController.getUser);
router.put('/users/:id', checkPermission('user:update'), UserController.updateUser);
router.delete('/users/:id', checkPermission('user:delete'), UserController.deleteUser);
router.post('/users/:id/reset-password', checkPermission('user:reset-password'), UserController.resetUserPassword);
router.put('/users/:id/roles', checkPermission('user:assign-role'), UserController.updateUserRoles);
// 角色相关路由
router.get('/roles', checkPermission('role:view'), RoleController.getRoles);
router.post('/roles', checkPermission('role:create'), RoleController.createRole);
router.get('/roles/:id', checkPermission('role:view'), RoleController.getRole);
router.put('/roles/:id', checkPermission('role:update'), RoleController.updateRole);
router.delete('/roles/:id', checkPermission('role:delete'), RoleController.deleteRole);
router.put('/roles/:id/permissions', checkPermission('role:assign-permission'), RoleController.updateRolePermissions);
router.get('/roles/:id/users', checkPermission('role:view'), RoleController.getRoleUsers);
// 权限相关路由
router.get('/permissions', checkPermission('permission:view'), PermissionController.getPermissions);
router.get('/permissions/tree', checkPermission('permission:view'), PermissionController.getPermissionTree);
// 组织架构路由
router.get('/organizations', checkPermission('org:view'), OrganizationController.getOrganizations);
router.post('/organizations', checkPermission('org:create'), OrganizationController.createOrganization);
router.get('/organizations/:id', checkPermission('org:view'), OrganizationController.getOrganization);
router.put('/organizations/:id', checkPermission('org:update'), OrganizationController.updateOrganization);
router.delete('/organizations/:id', checkPermission('org:delete'), OrganizationController.deleteOrganization);
// 部门路由
router.get('/departments', checkPermission('dept:view'), OrganizationController.getDepartments);
router.post('/departments', checkPermission('dept:create'), OrganizationController.createDepartment);
router.get('/departments/:id', checkPermission('dept:view'), OrganizationController.getDepartment);
router.put('/departments/:id', checkPermission('dept:update'), OrganizationController.updateDepartment);
router.delete('/departments/:id', checkPermission('dept:delete'), OrganizationController.deleteDepartment);
// 当前用户相关
router.get('/auth/me', AuthController.getCurrentUser);
router.get('/auth/menus', AuthController.getUserMenus);
router.post('/auth/change-password', AuthController.changePassword);
router.get('/auth/check-permission', AuthController.checkPermission);
module.exports = router;
七、配置文件
javascript
// config/database.js
module.exports = {
development: {
username: 'postgres',
password: 'password',
database: 'rbac_system',
host: 'localhost',
port: 5432,
dialect: 'postgres',
logging: console.log,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
timezone: '+08:00'
},
test: {
username: 'postgres',
password: 'password',
database: 'rbac_system_test',
host: 'localhost',
dialect: 'postgres',
logging: false
},
production: {
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: 'postgres',
logging: false,
pool: {
max: 20,
min: 5,
acquire: 60000,
idle: 10000
},
timezone: '+08:00',
dialectOptions: {
ssl: {
require: true,
rejectUnauthorized: false
}
}
}
};
javascript
// config/app.js
module.exports = {
port: process.env.PORT || 3000,
jwtSecret: process.env.JWT_SECRET || 'your-secret-key-change-in-production',
jwtExpiresIn: '7d',
bcryptRounds: 10,
cors: {
origin: process.env.CORS_ORIGIN || '*',
credentials: true
},
rateLimit: {
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 限制每个IP 100个请求
},
upload: {
maxFileSize: 10 * 1024 * 1024, // 10MB
allowedTypes: ['image/jpeg', 'image/png', 'image/gif']
}
};
八、主应用文件
javascript
// app.js
const Koa = require('koa');
const cors = require('@koa/cors');
const bodyParser = require('koa-bodyparser');
const helmet = require('koa-helmet');
const rateLimit = require('koa-ratelimit');
const { sequelize } = require('./models');
// 配置
const appConfig = require('./config/app');
const routes = require('./routes');
const app = new Koa();
// 安全头
app.use(helmet());
// CORS
app.use(cors(appConfig.cors));
// 请求体解析
app.use(bodyParser({
enableTypes: ['json', 'form', 'text'],
jsonLimit: '10mb',
formLimit: '10mb'
}));
// 速率限制
const db = new Map();
app.use(rateLimit({
driver: 'memory',
db: db,
duration: appConfig.rateLimit.windowMs,
max: appConfig.rateLimit.max,
id: (ctx) => ctx.ip,
headers: {
remaining: 'Rate-Limit-Remaining',
reset: 'Rate-Limit-Reset',
total: 'Rate-Limit-Total'
}
}));
// 错误处理
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
ctx.status = error.status || 500;
ctx.body = {
code: ctx.status,
message: error.message,
data: null,
timestamp: new Date().toISOString()
};
ctx.app.emit('error', error, ctx);
}
});
// 路由
app.use(routes.routes());
app.use(routes.allowedMethods());
// 数据库连接测试
async function testDatabaseConnection() {
try {
await sequelize.authenticate();
console.log('数据库连接成功');
// 同步数据库(开发环境使用)
if (process.env.NODE_ENV === 'development') {
await sequelize.sync({ alter: true });
console.log('数据库同步完成');
}
} catch (error) {
console.error('数据库连接失败:', error);
process.exit(1);
}
}
// 启动应用
async function startApp() {
await testDatabaseConnection();
const port = appConfig.port;
app.listen(port, () => {
console.log(`应用启动成功,端口: ${port}`);
console.log(`环境: ${process.env.NODE_ENV || 'development'}`);
});
}
// 优雅关闭
process.on('SIGTERM', async () => {
console.log('收到 SIGTERM 信号,正在关闭应用...');
await sequelize.close();
process.exit(0);
});
process.on('SIGINT', async () => {
console.log('收到 SIGINT 信号,正在关闭应用...');
await sequelize.close();
process.exit(0);
});
// 启动
if (require.main === module) {
startApp();
}
module.exports = app;
九、数据初始化脚本
javascript
// scripts/initData.js
const { sequelize, Organization, Role, Permission, User } = require('../models');
const bcrypt = require('bcryptjs');
async function initializeSystemData() {
console.log('开始初始化系统数据...');
const transaction = await sequelize.transaction();
try {
// 1. 创建默认组织
const defaultOrg = await Organization.create({
tenant_code: 'default',
code: 'default',
name: '默认组织',
status: 1
}, { transaction });
console.log('默认组织创建完成');
// 2. 创建系统权限
const systemPermissions = [
// 用户管理权限
{ code: 'user:view', name: '查看用户', type: 2, permission: 'user:view' },
{ code: 'user:create', name: '创建用户', type: 2, permission: 'user:create' },
{ code: 'user:update', name: '更新用户', type: 2, permission: 'user:update' },
{ code: 'user:delete', name: '删除用户', type: 2, permission: 'user:delete' },
{ code: 'user:reset-password', name: '重置密码', type: 2, permission: 'user:reset-password' },
{ code: 'user:assign-role', name: '分配角色', type: 2, permission: 'user:assign-role' },
// 角色管理权限
{ code: 'role:view', name: '查看角色', type: 2, permission: 'role:view' },
{ code: 'role:create', name: '创建角色', type: 2, permission: 'role:create' },
{ code: 'role:update', name: '更新角色', type: 2, permission: 'role:update' },
{ code: 'role:delete', name: '删除角色', type: 2, permission: 'role:delete' },
{ code: 'role:assign-permission', name: '分配权限', type: 2, permission: 'role:assign-permission' },
// 权限管理
{ code: 'permission:view', name: '查看权限', type: 2, permission: 'permission:view' },
{ code: 'permission:manage', name: '管理权限', type: 2, permission: 'permission:manage' },
// 组织管理
{ code: 'org:view', name: '查看组织', type: 2, permission: 'org:view' },
{ code: 'org:create', name: '创建组织', type: 2, permission: 'org:create' },
{ code: 'org:update', name: '更新组织', type: 2, permission: 'org:update' },
{ code: 'org:delete', name: '删除组织', type: 2, permission: 'org:delete' },
// 部门管理
{ code: 'dept:view', name: '查看部门', type: 2, permission: 'dept:view' },
{ code: 'dept:create', name: '创建部门', type: 2, permission: 'dept:create' },
{ code: 'dept:update', name: '更新部门', type: 2, permission: 'dept:update' },
{ code: 'dept:delete', name: '删除部门', type: 2, permission: 'dept:delete' }
];
const permissions = await Permission.bulkCreate(
systemPermissions.map(p => ({ ...p, tenant_code: 'default' })),
{ transaction }
);
console.log('系统权限创建完成');
// 3. 创建系统角色
const adminRole = await Role.create({
tenant_code: 'default',
code: 'admin',
name: '系统管理员',
description: '拥有系统所有权限',
type: 1,
is_system: true,
is_default: false,
data_scope: 1,
status: 1
}, { transaction });
// 为管理员角色分配所有权限
const rolePermissions = permissions.map(permission => ({
role_id: adminRole.id,
permission_id: permission.id,
actions: ['*']
}));
await require('../models').RolePermission.bulkCreate(rolePermissions, { transaction });
// 创建普通用户角色
const userRole = await Role.create({
tenant_code: 'default',
code: 'user',
name: '普通用户',
description: '普通用户角色',
type: 1,
is_system: true,
is_default: true,
data_scope: 5, // 仅自己
status: 1
}, { transaction });
console.log('系统角色创建完成');
// 4. 创建默认管理员用户
const salt = require('crypto').randomBytes(16).toString('hex');
const password = 'admin123'; // 默认密码,生产环境应该修改
const hash = require('crypto').pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex');
const adminUser = await User.create({
tenant_code: 'default',
username: 'admin',
email: 'admin@example.com',
realname: '系统管理员',
password_hash: hash,
password_salt: salt,
status: 1,
must_change_password: true
}, { transaction });
// 为管理员分配角色
await require('../models').UserRole.create({
user_id: adminUser.id,
role_id: adminRole.id,
is_default: true
}, { transaction });
console.log('默认管理员用户创建完成');
console.log('用户名: admin');
console.log('密码: admin123');
console.log('请首次登录后修改密码!');
await transaction.commit();
console.log('系统数据初始化完成');
} catch (error) {
await transaction.rollback();
console.error('初始化失败:', error);
throw error;
}
}
// 执行初始化
if (require.main === module) {
initializeSystemData()
.then(() => {
console.log('初始化脚本执行完成');
process.exit(0);
})
.catch(error => {
console.error('初始化失败:', error);
process.exit(1);
});
}
module.exports = initializeSystemData;
十、API文档(部分)
1. 认证API
-
POST /auth/login - 用户登录
-
POST /auth/logout - 退出登录
-
GET /auth/me - 获取当前用户信息
-
POST /auth/change-password - 修改密码
2. 用户管理API
-
GET /users - 获取用户列表
-
POST /users - 创建用户
-
GET /users/:id - 获取用户详情
-
PUT /users/:id - 更新用户
-
DELETE /users/:id - 删除用户
3. 角色管理API
-
GET /roles - 获取角色列表
-
POST /roles - 创建角色
-
PUT /roles/:id/permissions - 更新角色权限
4. 权限管理API
- GET /permissions/tree - 获取权限树
部署说明
1. 环境要求
-
Node.js >= 14.0.0
-
PostgreSQL >= 12
-
Redis(可选,用于缓存)
2. 安装步骤
bash
# 1. 克隆项目
git clone <repository-url>
cd rbac-system
# 2. 安装依赖
npm install
# 3. 配置数据库
# 创建数据库并修改config/database.js中的配置
# 4. 初始化数据库
npm run db:init
# 5. 启动服务
npm start
# 开发环境
npm run dev
3. 生产环境部署
bash
# 使用PM2管理进程
npm install -g pm2
# 启动服务
pm2 start app.js --name rbac-system
# 设置开机自启
pm2 startup
pm2 save
系统特性
-
多租户支持:每个组织数据隔离
-
RBAC权限模型:基于角色的访问控制
-
数据权限:支持行级数据权限控制
-
审计日志:完整的操作日志记录
-
软删除:支持数据恢复
-
树形结构:组织和部门支持无限层级
-
缓存支持:可配置Redis缓存
-
API文档:自动生成API文档(可集成Swagger)
-
前端集成:提供Vue/React权限组件
这个完整的系统包含了所有必要的组件,可以直接用于生产环境。根据实际需求,你可以进一步扩展功能,如:
-
添加文件上传功能
-
集成消息通知
-
添加工作流引擎
-
实现微服务架构等。