Node.js组织机构与权限设计方案

h5打开以查看

这几天闲来无事,整理了一个完整的、可立即使用的组织机构和权限系统实现方案。

一、完整数据库设计

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

系统特性

  1. 多租户支持:每个组织数据隔离

  2. RBAC权限模型:基于角色的访问控制

  3. 数据权限:支持行级数据权限控制

  4. 审计日志:完整的操作日志记录

  5. 软删除:支持数据恢复

  6. 树形结构:组织和部门支持无限层级

  7. 缓存支持:可配置Redis缓存

  8. API文档:自动生成API文档(可集成Swagger)

  9. 前端集成:提供Vue/React权限组件

这个完整的系统包含了所有必要的组件,可以直接用于生产环境。根据实际需求,你可以进一步扩展功能,如:

  • 添加文件上传功能

  • 集成消息通知

  • 添加工作流引擎

  • 实现微服务架构等。

h5打开以查看

相关推荐
小北方城市网3 小时前
第 5 课:后端工程化进阶 ——Python 分层架构 + 中间件 + 日志 / 异常统一处理(打造企业级高可用后端)
数据库·人工智能·python·mysql·数据库架构
小北方城市网4 小时前
第 4 课:前端工程化进阶 ——Vue 核心语法 + 组件化开发(前端能力质的飞跃)
大数据·开发语言·数据库·python·状态模式·数据库架构
全栈前端老曹15 小时前
【前端路由】Vue Router 嵌套路由 - 配置父子级路由、命名视图、动态路径匹配
前端·javascript·vue.js·node.js·ecmascript·vue-router·前端路由
EndingCoder20 小时前
TypeScript 入门:理解其本质与价值
前端·javascript·ubuntu·typescript·node.js
程序员爱钓鱼1 天前
Node.js 编程实战:RESTful API 设计
前端·后端·node.js
程序员爱钓鱼1 天前
Node.js 编程实战:GraphQL 简介与实战
前端·后端·node.js
小雪_Snow1 天前
安装 nvm 和 Node.js 教程
npm·node.js·nvm·nrm
大布布将军1 天前
☁️ 自动化交付:CI/CD 流程与云端部署
运维·前端·程序人生·ci/cd·职场和发展·node.js·自动化
冥界摄政王1 天前
Cesium学习第二章 camera 相机
node.js·html·vue3·js·cesium