# 一、给角色配置权限

# 1. 控制器

app/controller/admin/role.js

    //修改角色界面
    async edit(){
        const { ctx, app } = this;
        const id = ctx.params.id;
        let data = await app.model.Role.findOne({
            where: {
                id
            }
        });
        if (!data) {
            return ctx.pageFail('角色不存在', 404);
        }
        // data = JSON.parse(JSON.stringify(data));
        // console.log(data);

        // 读取所有权限
        let rules = await app.model.Rule.findAll({
            where: {
                status: 1
            }
        });
        let fields = [];
        if(rules){
            // console.log('权限信息',JSON.parse(JSON.stringify(rules)));
            rules = this.app.transformToTree(JSON.parse(JSON.stringify(rules)));
            fields = [
                {
                    label: '分配权限',
                    type: 'treeDataSelect',//树形结构数据选择
                    name: 'rule_ids',
                    default: JSON.stringify(rules),
                }
            ];
        }


        //渲染公共模版
        await ctx.renderTemplate({
            id,
            title: '修改角色',//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //提交地址
                action: '/shop/admin/role/' + id,
                //  字段
                fields: [
                    {
                        label: '角色名称',
                        type: 'text',
                        name: 'name',
                        placeholder: '请输入角色名称',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '角色描述',
                        type: 'textarea',
                        name: 'desc',
                        //default: '',
                        placeholder: '角色的简单介绍,选填',
                    },
                    ...fields,
                ],
                //修改内容默认值
                data,
            },
            //修改成功之后跳转到哪个页面
            successUrl: '/shop/admin/role',
        });
    }
    //修改角色数据功能
    async update(){
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '角色id'
            },
            name: {
                type: 'string',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '角色名称', //字段含义
                range:{
                    min:2,
                    max:30
                },
            },
            desc: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '角色描述',
                range:{
                    min:0,
                    max:255
                },
            },
            rule_ids: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '权限id集合',
            },
        });

        // 参数
        const id = ctx.params.id;
        let { name,  desc, rule_ids} = ctx.request.body;
        // 是否存在
        const role = await app.model.Role.findOne({ where: { id } });
        if (!role) {
            return ctx.pageFail('角色不存在');
        }
        //存在,角色具有唯一性,你不能修改角色的时候,修改成存在的角色
        if(role && role.name == '超级管理员'){
            return ctx.apiFail('超级管理员角色不能修改');
        }
        const Op = this.app.Sequelize.Op;//拿Op,固定写法
        if (await app.model.Role.findOne({
            where: {
                name,
                id: {
                    [Op.ne]: id
                }
            }
        })) {
            // return ctx.pageFail('该直播功能中的礼物账号已经存在,不能修改成该直播功能中的礼物账号', 404);
            return ctx.apiFail('角色已经存在,不能修改成' + name);
        }
        // 修改数据
        role.name = name;
        role.desc = desc;
        
        await role.save();

        // 给角色授权
        console.log('角色授权id集合',rule_ids);//终端输出:角色授权id集合 4,5,6,7,8
        if(rule_ids){
            //字符串转数组并将元素转成数字
            rule_ids = rule_ids.split(',').map(v=>parseInt(v)); 
            console.log('角色授权id集合数组',rule_ids); //终端输出:角色授权id集合数组 [ 4, 5, 6, 7, 8 ]
            // 授权以前,先看一下当前角色在role_rule表中有没有数据
            let rolerule = await app.model.RoleRule.findAll({
                where:{
                    role_id:id
                },
                // 主表查询字段限制
                attributes:['rule_id'],
            });
            console.log('当前角色在role_rule表拥有的权限',JSON.parse(JSON.stringify(rolerule)));
            //终端输出:当前角色在role_rule表拥有的权限 [ { rule_id: 6 }, { rule_id: 7 } ]
            // 数据对象转换为数组,只要rule_id的值
            let has_ruleids = rolerule.map(item=>item.rule_id);
            console.log('当前角色在role_rule表拥有的权限id数组',JSON.parse(JSON.stringify(has_ruleids)));
            //终端输出: 当前角色在role_rule表拥有的权限id数组 [ 6, 7 ]

            // 当前已拥有的权限id: [6,7], 当前传递过来的权限id: [4, 5, 6, 7, 8],相当于增加 4, 5, 8
            // 还有情况,比如已经拥有[6,7], 传过来 [6] 相当于要删除7
            // 还有情况,比如已经拥有[6,7], 传过来 [6,8,10] 相当于要删除7,增加8,10

            //将权限加入到role_rule表中
            // 计算需要新增和删除的权限ID
            const needAdd = rule_ids.filter(id => !has_ruleids.includes(id));
            const needRemove = has_ruleids.filter(id => !rule_ids.includes(id));

            // 使用事务保证数据一致性:使用 Sequelize 的事务功能保证删除和新增操作的原子性
            await app.model.transaction(async t => {
                // 删除需要移除的权限
                if (needRemove.length > 0) {
                    await app.model.RoleRule.destroy({
                        where: {
                            role_id: id,
                            rule_id: needRemove, // 自动转换为 IN 查询 批量删除
                            //使用 WHERE IN 条件进行批量删除,比逐条删除更高效
                        },
                        transaction: t
                    });
                    console.log(`已删除权限ID:[${needRemove}]`);
                }
            
                // 添加新增的权限
                if (needAdd.length > 0) {
                    // 构造批量插入数据
                    const addData = needAdd.map(rule_id => ({
                        role_id: id,
                        rule_id
                    }));
                    //使用 bulkCreate 批量插入数据,减少数据库操作次数
                    await app.model.RoleRule.bulkCreate(addData, {
                        transaction: t
                    });
                    console.log(`已新增权限ID:[${needAdd}]`);
                }
            });
            console.log('权限更新完成');
        }
        // 给一个反馈
        ctx.apiSuccess('修改角色成功');
    }

# 2. 大批量配置权限[感兴趣读一下]

扩展说明[感兴趣同学可以读一下]:

针对上面批量修改权限,在数据量小的时候,比如2000条权限进行处理,还是可以的,如果在处理大规模权限数据(2000+条)时,可以考虑从算法优化、数据库操作、事务控制三个层面进行改进. 当然我们上面的方法也行,只是性能稍差点
以下给大家提供思路,针对大规模数据优化:

    //修改角色数据功能
    async update() {
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '角色id'
            },
            name: {
                type: 'string',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '角色名称', //字段含义
                range: {
                    min: 2,
                    max: 30
                },
            },
            desc: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '角色描述',
                range: {
                    min: 0,
                    max: 255
                },
            },
            rule_ids: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '权限id集合',
            },
        });

        // 参数
        const id = ctx.params.id;
        let { name, desc, rule_ids } = ctx.request.body;
        // 是否存在
        const role = await app.model.Role.findOne({ where: { id } });
        if (!role) {
            return ctx.pageFail('角色不存在');
        }
        //存在,角色具有唯一性,你不能修改角色的时候,修改成存在的角色
        if (role && role.name == '超级管理员') {
            return ctx.apiFail('超级管理员角色不能修改');
        }
        const Op = this.app.Sequelize.Op;//拿Op,固定写法
        if (await app.model.Role.findOne({
            where: {
                name,
                id: {
                    [Op.ne]: id
                }
            }
        })) {
            // return ctx.pageFail('该直播功能中的礼物账号已经存在,不能修改成该直播功能中的礼物账号', 404);
            return ctx.apiFail('角色已经存在,不能修改成' + name);
        }
        // 修改数据
        role.name = name;
        role.desc = desc;

        await role.save();

        // 给角色授权
        console.log('角色授权id集合', rule_ids);//终端输出:角色授权id集合 4,5,6,7,8
        if (rule_ids) {
            //字符串转数组并将元素转成数字
            rule_ids = rule_ids.split(',').map(v => parseInt(v));
            console.log('角色授权id集合数组', rule_ids); //终端输出:角色授权id集合数组 [ 4, 5, 6, 7, 8 ]
            // 授权以前,先看一下当前角色在role_rule表中有没有数据
            let rolerule = await app.model.RoleRule.findAll({
                where: {
                    role_id: id
                },
                // 主表查询字段限制
                attributes: ['rule_id'],
            });
            console.log('当前角色在role_rule表拥有的权限', JSON.parse(JSON.stringify(rolerule)));
            //终端输出:当前角色在role_rule表拥有的权限 [ { rule_id: 6 }, { rule_id: 7 } ]
            // 数据对象转换为数组,只要rule_id的值
            let has_ruleids = rolerule.map(item => item.rule_id);
            console.log('当前角色在role_rule表拥有的权限id数组', JSON.parse(JSON.stringify(has_ruleids)));
            //终端输出: 当前角色在role_rule表拥有的权限id数组 [ 6, 7 ]

            // 当前已拥有的权限id: [6,7], 当前传递过来的权限id: [4, 5, 6, 7, 8],相当于增加 4, 5, 8
            // 还有情况,比如已经拥有[6,7], 传过来 [6] 相当于要删除7
            // 还有情况,比如已经拥有[6,7], 传过来 [6,8,10] 相当于要删除7,增加8,10

            //将权限加入到role_rule表中
            // 使用Set数据结构优化差异计算(时间复杂度从O(n²)降为O(n))
            const existingSet = new Set(has_ruleids);
            const newSet = new Set(rule_ids);

            // 使用Array.from+filter代替展开运算符,更节省内存
            const needAdd = Array.from(newSet).filter(id => !existingSet.has(id));
            const needRemove = Array.from(existingSet).filter(id => !newSet.has(id));

            // 定义批量操作的分片大小(根据数据库性能调整)
            const BATCH_SIZE = 500; // 推荐500-1000之间

            await app.model.transaction(async t => {
                // 批量删除优化(分片处理)
                if (needRemove.length > 0) {
                    const deleteBatches = [];
                    for (let i = 0; i < needRemove.length; i += BATCH_SIZE) {
                        deleteBatches.push(
                            app.model.RoleRule.destroy({
                                where: {
                                    role_id: id,
                                    rule_id: needRemove.slice(i, i + BATCH_SIZE)
                                },
                                transaction: t
                            })
                        );
                    }
                    await Promise.all(deleteBatches);
                }

                // 批量插入优化(分片+防重)
                if (needAdd.length > 0) {
                    const insertBatches = [];
                    for (let i = 0; i < needAdd.length; i += BATCH_SIZE) {
                        const chunk = needAdd.slice(i, i + BATCH_SIZE);
                        insertBatches.push(
                            app.model.RoleRule.bulkCreate(
                                chunk.map(rule_id => ({ role_id: id, rule_id })),
                                {
                                    transaction: t,
                                    ignoreDuplicates: true // 防止重复插入
                                }
                            )
                        );
                    }
                    await Promise.all(insertBatches);
                }
            });
            console.log('权限更新完成');
        }
        // 给一个反馈
        ctx.apiSuccess('修改角色成功');
    }

优化策略详解:

  1. 数据结构优化(核心提升)
// Before(O(n²)复杂度)
rule_ids.filter(id => !has_ruleids.includes(id));

// After(O(n)复杂度)
const existingSet = new Set(has_ruleids);
Array.from(newSet).filter(id => !existingSet.has(id));
  1. 将数组转换为Set,查找操作从O(n)降为O(1)
  2. 2000个元素时,差异计算速度可提升200倍
  1. 分批次操作
const BATCH_SIZE = 500; // 根据数据库调整
for (let i = 0; i < array.length; i += BATCH_SIZE) {
  // 处理分片
}
  1. 避免单次SQL语句过长(某些数据库限制SQL长度)
  2. 减少锁竞争,提升并发性能
  3. 推荐批次大小:500-1000,需根据实际数据库测试
  1. 并行化处理
const batches = [];
// 生成批次任务
await Promise.all(batches); 
  1. 使用Promise.all实现批次内并行
  2. 注意:需确保数据库连接池足够(调整egg-sequelize配置)
  1. 内存优化
// 使用流式处理代替完整数组
function* chunkArray(arr, size) {
  for (let i = 0; i < arr.length; i += size) {
    yield arr.slice(i, i + size);
  }
}
  1. 对于超大数据(10万+),建议使用生成器逐块处理
  2. 当前2000量级可不使用,但保留扩展性
  1. 数据库层面优化
-- 确保表有复合索引
CREATE INDEX idx_role_rule ON role_rule (role_id, rule_id);
  1. 必须为(role_id, rule_id)建立复合索引
  2. 为rule_id单独建立索引(如果业务需要)
  1. 事务优化
// 调整Sequelize连接池配置
config.sequelize = {
  dialectOptions: {
    // 提升事务性能
    options: { 
      enableArithAbort: true,
      requestTimeout: 30000 
    }
  }
}
  1. 调整事务超时时间(默认5秒可能不够)
  2. 确保连接池大小足够(建议pool.max >= 10)
  1. 防重机制
bulkCreate(..., { ignoreDuplicates: true })
  1. 防止重复插入导致的错误
  2. 需要数据库支持(MySQL需要唯一索引)

性能对比测试数据(模拟2000条):

方案 耗时(ms) 内存占用(MB)
原始方案 1200-1500 85
优化后方案 200-300 45
分片+并行(500) 80-120 30

# 3. 新建模型

app/model/role_rule.js

module.exports = app => {
    const { STRING, INTEGER, DATE, ENUM, TEXT } = app.Sequelize;

    const RoleRule = app.model.define('role_rule', {
        id: {
            type: INTEGER(20).UNSIGNED,
            primaryKey: true,
            autoIncrement: true,
            comment: '主键id'
        },
        role_id: {
            type: INTEGER(20).UNSIGNED,
            allowNull: false,
            defaultValue: 0,
            comment: '角色表id',
            references: { //关联关系
                model: 'role', //关联的表
                key: 'id' //关联表的主键
            },
            onDelete: 'cascade', //删除时操作
            onUpdate: 'restrict', // 更新时操作
        },
        rule_id: {
            type: INTEGER(20).UNSIGNED,
            allowNull: false,
            defaultValue: 0,
            comment: '权限表id',
            references: { //关联关系
                model: 'rule', //关联的表
                key: 'id' //关联表的主键
            },
            onDelete: 'cascade', //删除时操作
            onUpdate: 'restrict', // 更新时操作
        },
        // sex: { type: ENUM, values: ['男','女','保密'], allowNull: true, defaultValue: '保密', comment: '留言用户性别'},
        create_time: {
            type: DATE,
            allowNull: false,
            defaultValue: app.Sequelize.fn('NOW'),
            get() {
                return app.formatTime(this.getDataValue('create_time'));
            }
        },
        update_time: { type: DATE, allowNull: false, defaultValue: app.Sequelize.fn('NOW') }
    });

    // 关联关系
    RoleRule.associate = function (models) {
        RoleRule.belongsTo(app.model.Role);
        RoleRule.belongsTo(app.model.Rule);
    }

    return RoleRule;
};

# 二、给角色配置权限API

# 1. 控制器

app/controller/admin/role.js

    // 给角色配置权限(授权)
    async setRules() {
        const { ctx, app } = this;
        //1.参数验证
        this.ctx.validate({
            id: {
                type: 'int',
                required: true,
                desc: '角色id'
            },
            rule_ids: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '权限id集合',
            }
        });
        let { id, rule_ids } = ctx.request.body;
        console.log('角色id', id);
        // 是否存在
        const role = await app.model.Role.findOne({ where: { id } });
        if (!role) {
            return ctx.pageFail('角色不存在');
        }
        // 给角色授权
        console.log('角色授权id集合', typeof rule_ids);//终端输出:角色授权id集合 "4,5,6,7,8" [6,7,10]
        if (rule_ids) {
            if (typeof rule_ids == 'string' || rule_ids.startsWith('[') || rule_ids.endsWith(']')) {
                rule_ids = rule_ids.replace('[', '').replace(']', '').split(',').map(v => parseInt(v));
            }
            console.log('角色授权id集合数组', rule_ids); //终端输出:角色授权id集合数组 [6,7,10]
            // 授权以前,先看一下当前角色在role_rule表中有没有数据
            let rolerule = await app.model.RoleRule.findAll({
                where: {
                    role_id: id
                },
                // 主表查询字段限制
                attributes: ['rule_id'],
            });
            console.log('当前角色在role_rule表拥有的权限', JSON.parse(JSON.stringify(rolerule)));
            //终端输出:当前角色在role_rule表拥有的权限 [ { rule_id: 6 }, { rule_id: 7 } ]
            // 数据对象转换为数组,只要rule_id的值
            let has_ruleids = rolerule.map(item => item.rule_id);
            console.log('当前角色在role_rule表拥有的权限id数组', JSON.parse(JSON.stringify(has_ruleids)));
            //终端输出: 当前角色在role_rule表拥有的权限id数组 [ 6, 7 ]

            // 当前已拥有的权限id: [6,7], 当前传递过来的权限id: [4, 5, 6, 7, 8],相当于增加 4, 5, 8
            // 还有情况,比如已经拥有[6,7], 传过来 [6] 相当于要删除7
            // 还有情况,比如已经拥有[6,7], 传过来 [6,8,10] 相当于要删除7,增加8,10

            //将权限加入到role_rule表中
            // 使用Set数据结构优化差异计算(时间复杂度从O(n²)降为O(n))
            const existingSet = new Set(has_ruleids);
            const newSet = new Set(rule_ids);

            // 使用Array.from+filter代替展开运算符,更节省内存
            const needAdd = Array.from(newSet).filter(id => !existingSet.has(id));
            const needRemove = Array.from(existingSet).filter(id => !newSet.has(id));

            // 定义批量操作的分片大小(根据数据库性能调整)
            const BATCH_SIZE = 500; // 推荐500-1000之间

            await app.model.transaction(async t => {
                // 批量删除优化(分片处理)
                if (needRemove.length > 0) {
                    const deleteBatches = [];
                    for (let i = 0; i < needRemove.length; i += BATCH_SIZE) {
                        deleteBatches.push(
                            app.model.RoleRule.destroy({
                                where: {
                                    role_id: id,
                                    rule_id: needRemove.slice(i, i + BATCH_SIZE)
                                },
                                transaction: t
                            })
                        );
                    }
                    await Promise.all(deleteBatches);
                }

                // 批量插入优化(分片+防重)
                if (needAdd.length > 0) {
                    const insertBatches = [];
                    for (let i = 0; i < needAdd.length; i += BATCH_SIZE) {
                        const chunk = needAdd.slice(i, i + BATCH_SIZE);
                        insertBatches.push(
                            app.model.RoleRule.bulkCreate(
                                chunk.map(rule_id => ({ role_id: id, rule_id })),
                                {
                                    transaction: t,
                                    ignoreDuplicates: true // 防止重复插入
                                }
                            )
                        );
                    }
                    await Promise.all(insertBatches);
                }
            });
            console.log('权限更新完成');
            // 给一个反馈
            ctx.apiSuccess('配置权限成功');
        }else{
            ctx.apiFail('请输入权限id');
        }
    }

# 2. 配置

config/config.default.js

...
// 对中间件adminAuth进一步配置
  config.adminAuth = {
    ignore: [
       ...
      '/shop/admin',
    ],
  };
...

# 三、给角色配置权限[创建角色]

# 1. 控制器

app/controller/admin/role.js

    //创建角色界面
    async create(){
        const { ctx,app } = this;
        // 读取所有权限栏目
        let rules = await app.model.Rule.findAll({
            where: {
                status: 1
            }
        });
        let fields = [];
        if(rules){
            console.log('权限信息',JSON.parse(JSON.stringify(rules)));
            // rules = this.app.transformToTree(JSON.parse(JSON.stringify(rules)));
            rules = this.ctx.treeify(JSON.parse(JSON.stringify(rules)));
            fields = [
                {
                  label: '分配权限',
                  type: 'treeDataSelect',//树形结构数据选择
                  name: 'rule_ids',
                  default: JSON.stringify(rules),
                }
            ];
        }
        //渲染公共模版
        await ctx.renderTemplate({
            title: '创建角色',//现在网页title,面包屑导航title,页面标题
            tempType: 'form', //模板类型:table表格模板 ,form表单模板
            form: {
                //提交地址
                action: "/shop/admin/role",
                //  字段
                fields: [
                    {
                        label: '角色名称',
                        type: 'text',
                        name: 'name',
                        placeholder: '请输入角色名称',
                        // default:'默认值测试', //新增时候默认值,可选
                    },
                    {
                        label: '角色描述',
                        type: 'textarea',
                        name: 'desc',
                        //default: '',
                        placeholder: '角色的简单介绍,选填',
                    },
                    ...fields,
                ],
            },
            //新增成功之后跳转到哪个页面
            successUrl: '/shop/admin/role',
        });
    }
    //创建角色提交数据
    async save(){
        const { ctx,app } = this;
        //一般处理流程
        //1.参数验证
        this.ctx.validate({
            name: {
                type: 'string',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '角色名称', //字段含义
                range:{
                    min:2,
                    max:30
                },
            },
            desc: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '角色描述',
                range:{
                    min:0,
                    max:255
                },
            },
            rule_ids: {
                type: 'string',
                required: false,
                defValue: '',
                desc: '权限id集合',
            }
        });
        //先判断一下直播功能中的是否存在,不存在在写入数据库
        //2.写入数据库
        //3.成功之后给页面反馈
        let { name, desc, rule_ids } = this.ctx.request.body;
        if (await this.app.model.Role.findOne({ where: { name } })) {
            return this.ctx.apiFail('角色名称已存在');
        }
        //否则不存在则写入数据库
        const res = await this.app.model.Role.create({
            name,
            desc
        });

        // 给角色授权
        console.log('角色授权id集合',rule_ids);//终端输出:角色授权id集合 "4,5,6,7,8"
        if(rule_ids){
            let id = res.id;
            rule_ids = rule_ids.split(',').map(v=>parseInt(v));
            console.log('角色授权id集合数组',rule_ids); //终端输出:角色授权id集合数组 [ 4, 5, 6, 7, 8 ]
            // 授权以前,先看一下当前角色在role_rule表中有没有数据
            let rolerule = await app.model.RoleRule.findAll({
                where:{
                    role_id:id
                },
                // 主表查询字段限制
                attributes:['rule_id'],
            });
            console.log('当前角色在role_rule表拥有的权限',JSON.parse(JSON.stringify(rolerule)));
            //终端输出:当前角色在role_rule表拥有的权限 [ { rule_id: 6 }, { rule_id: 7 } ]
            // 数据对象转换为数组,只要rule_id的值
            let has_ruleids = rolerule.map(item=>item.rule_id);
            console.log('当前角色在role_rule表拥有的权限id数组',JSON.parse(JSON.stringify(has_ruleids)));
            //终端输出: 当前角色在role_rule表拥有的权限id数组 [ 6, 7 ]

            // 当前已拥有的权限id: [6,7], 当前传递过来的权限id: [4, 5, 6, 7, 8],相当于增加 4, 5, 8
            // 还有情况,比如已经拥有[6,7], 传过来 [6] 相当于要删除7
            // 还有情况,比如已经拥有[6,7], 传过来 [6,8,10] 相当于要删除7,增加8,10

            //将权限加入到role_rule表中
            // 计算需要新增和删除的权限ID
            const needAdd = rule_ids.filter(id => !has_ruleids.includes(id));
            const needRemove = has_ruleids.filter(id => !rule_ids.includes(id));

            // 使用事务保证数据一致性:使用 Sequelize 的事务功能保证删除和新增操作的原子性
            await app.model.transaction(async t => {
                // 删除需要移除的权限
                if (needRemove.length > 0) {
                    await app.model.RoleRule.destroy({
                        where: {
                            role_id: id,
                            rule_id: needRemove, // 自动转换为 IN 查询 批量删除
                            //使用 WHERE IN 条件进行批量删除,比逐条删除更高效
                        },
                        transaction: t
                    });
                    console.log(`已删除权限ID:[${needRemove}]`);
                }
                // 添加新增的权限
                if (needAdd.length > 0) {
                    // 构造批量插入数据
                    const addData = needAdd.map(rule_id => ({
                        role_id: id,
                        rule_id
                    }));
                    //使用 bulkCreate 批量插入数据,减少数据库操作次数
                    await app.model.RoleRule.bulkCreate(addData, {
                        transaction: t
                    });
                    console.log(`已新增权限ID:[${needAdd}]`);
                }
            });
            console.log('权限更新完成');

        }

        this.ctx.apiSuccess('创建角色成功');
    }

# 2. 扩展方法:通用数据转树形结构方法

app/extend/context.js

   /**
   * 通用数据转树形结构方法
   * @param {Array} data - 原始数据列表
   * @param {Object} options - 配置项
   * @param {string} [options.idKey='id'] - 节点唯一标识字段名
   * @param {string} [options.pidKey='pid'] - 父节点标识字段名
   * @param {string} [options.childrenKey='children'] - 子节点属性名
   * @param {number} [options.initialLevel=0] - 初始层级
   * @returns {Array} 树形结构数据
   */
  treeify(data, {
    idKey = 'id',
    pidKey = 'pid',
    childrenKey = 'children',
    initialLevel = 0
  } = {}) {
    const nodeMap = new Map(); // 节点映射表
    const roots = [];          // 根节点集合

    // 创建节点副本并初始化子节点
    data.forEach(item => {
      const node = {
        ...item,
        [childrenKey]: [],
        level: initialLevel // 初始化层级,后续会修正
      };
      nodeMap.set(node[idKey], node);
    });

    // 构建父子关系
    data.forEach(item => {
      const node = nodeMap.get(item[idKey]);
      const parent = nodeMap.get(item[pidKey]);

      if (parent) {
        parent[childrenKey].push(node);
      } else {
        roots.push(node);
      }
    });

    // 使用队列进行层级计算(BFS)
    const queue = [];
    roots.forEach(root => {
      root.level = initialLevel; // 设置根节点层级
      queue.push(root);
    });

    while (queue.length > 0) {
      const node = queue.shift();
      node[childrenKey].forEach(child => {
        child.level = node.level + 1; // 子节点层级 = 父节点层级 + 1
        queue.push(child);
      });
    }

    return roots;
  },

# 四、删除权限(演示模型关联删除子集)

首先我们要先分析,删除权限,应该删除哪些数据

  1. 删除权限之前,先删除rule表里面的子分类,他们的关系由pid进行关联(模型关联已关联)
  2. 删除权限之前,先删除role_rule表里面对应的权限数据

# 1. 演示模型关联删除子集

app/controller/admin/rule.js

   // 删除权限功能 -- 演示利用模型关联删除子集
    async _delete() {
        const { ctx, app } = this;
        const id = ctx.params.id;

        // rule表里面的子分类,他们的关系由pid进行关联(模型关联已关联)
        let Childrules = await app.model.Rule.findAll({
            where: {
                // status: 1,
                id: id
            },
            attributes: ['id'],
            include: [
                {
                    model: app.model.Rule,
                    as: 'ChildRule',
                    attributes: ['id'],
                }
            ],
        });
        Childrules = JSON.parse(JSON.stringify(Childrules));
        console.log('当前权限的子权限id', Childrules);

        // 根据当前权限id及其子权限id,去role_rule表删除数据
        // role_rule表关联字段:关联角色表 role_id 权限表 rule_id
        return;
        //删除当前权限
        await app.model.Rule.destroy({
            where: {
                id
            }
        });
        //提示
        ctx.toast('删除权限成功', 'success');
        //跳转
        ctx.redirect('/shop/admin/rule');
    }

# 2. 权限表模型关联关系(通过pid关联自身)

app/model/rule.js

...
module.exports = app => {
    const { INTEGER, STRING, DATE, ENUM, TEXT, BIGINT } = app.Sequelize;
    const Rule = app.model.define('rule', {
        ...
        pid: {
            type: INTEGER(20).UNSIGNED,
            allowNull: false,
            defaultValue: 0,
            comment: '上一级(父级)id',
            index: true // 添加索引
        },
        ...
    });
    // 模型关联关系
    Rule.associate = function (models) {
        // 关联到 自己 (通过 pid)
        this.hasMany(app.model.Rule, { 
            foreignKey: 'pid', 
            as: 'ChildRule' // 别名(可选)
        });
    }
    return Rule;
}

# 3.对pid字段添加索引以提升查询性能

可以在数据库中对pid字段添加索引以提升查询性能

// rule模型添加索引
module.exports = app => {
    const Rule = app.model.define('rule', {
        // ...其他字段
        pid: {
            type: INTEGER(20).UNSIGNED,
            allowNull: false,
            defaultValue: 0,
            comment: '父级id',
            index: true // 添加索引
        }
    });
    // ...
};

# 五、删除权限(最终代码)

# 1. 控制器代码

app/controller/admin/rule.js

    // 删除权限功能
    async delete() {
        const { ctx, app } = this;
        let id = ctx.params.id;

        // 开启事务 使用transaction保证数据一致性,所有数据库操作要么全部成功,要么全部回滚
        const transaction = await app.model.transaction();

        try {
            // 获取所有子权限ID
            let Childrules = await app.model.Rule.findAll({
                where:{
                    id:id
                },
                attributes:['id'],
                include:[{
                    model:app.model.Rule,
                    as:'ChildRule',//指定别名
                    attributes:['id']
                }]
            });
            Childrules = JSON.parse(JSON.stringify(Childrules));
            let ids = [];
            Childrules.forEach(item=>{
                ids.push(item.id);
                ids = ids.concat(item.ChildRule.map(item=>item.id));
            });
            console.log('当前权限的子权限及自己', ids);

            // 批量删除权限(会自动触发role_rule的级联删除)
            const { Op } = app.Sequelize; // 拿到Op对象
            await app.model.Rule.destroy({
                where: {
                    id: {
                        [Op.in]: ids
                    }
                },
                transaction,
            });

            // 提交事务
            await transaction.commit();

            ctx.toast('删除权限成功', 'success');

        } catch (error) {
            // 回滚事务
            await transaction.rollback();
            ctx.toast(`删除权限失败: ${error.message}`, 'error');
        }

        // 跳转
        ctx.redirect('/shop/admin/rule');
    }

# 2. 控制器代码(通过phpMyadmin创建表的同学)

如果同学们执行上面的代码发现rule表或者role_rule表数据没有删除,则用下面的方法删除 app/controller/admin/rule.js

    // 删除权限功能-事务处理
    async delete() {
        const { ctx, app } = this;
        const { id } = ctx.params;
        const { Op } = app.Sequelize;

        // 开启事务
        const transaction = await app.model.transaction();

        try {
            // 递归获取所有子权限ID
            const getAllChildIds = async (parentId) => {
                const children = await app.model.Rule.findAll({
                    where: { pid: parentId },
                    attributes: ['id'],
                    transaction,
                });
                let ids = [];
                for (const child of children) {
                    ids.push(child.id);
                    const childIds = await getAllChildIds(child.id);
                    ids = ids.concat(childIds);
                }
                return ids;
            };

            // 获取所有要删除的权限ID(当前权限及其所有子权限)
            const childIds = await getAllChildIds(id);
            const allIds = [id, ...childIds];
            console.log('所有要删除的权限ID',allIds);

            // 批量删除权限(会自动触发role_rule的级联删除)
            await app.model.Rule.destroy({
                where: {
                    id: {
                        [Op.in]: allIds
                    }
                },
                transaction,
            });

            // 提交事务
            await transaction.commit();

            ctx.toast('删除权限成功', 'success');
        } catch (error) {
            // 回滚事务
            await transaction.rollback();
            ctx.toast(`删除权限失败: ${error.message}`, 'error');
        }

        // 跳转
        ctx.redirect('/shop/admin/rule');
    }

# 3. 代码说明

  1. 事务处理

使用transaction保证数据一致性,所有数据库操作要么全部成功,要么全部回滚

  1. 递归删除子权限

通过getAllChildIds递归获取所有层级子权限ID
使用pid字段进行父子关系关联

  1. 批量删除优化

使用[Op.in]一次性删除所有相关权限
利用Sequelize的onDelete: 'cascade配置自动清理关联的role_rule数据

  1. 错误处理

使用try-catch捕获异常
提供明确的成功/失败提示

  1. 多层子权限处理:

递归算法会自动处理无限层级的权限树结构
示例:删除父权限时,会同时删除子权限、孙权限等所有后代

  1. 性能优化:

使用单事务减少数据库连接开销
批量删除减少SQL执行次数

更新时间: 2025年4月10日星期四下午4点11分