# 一、给角色配置权限
# 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('修改角色成功');
}
优化策略详解:
- 数据结构优化(核心提升)
// 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));
- 将数组转换为Set,查找操作从O(n)降为O(1)
- 2000个元素时,差异计算速度可提升200倍
- 分批次操作
const BATCH_SIZE = 500; // 根据数据库调整 for (let i = 0; i < array.length; i += BATCH_SIZE) { // 处理分片 }
- 避免单次SQL语句过长(某些数据库限制SQL长度)
- 减少锁竞争,提升并发性能
- 推荐批次大小:500-1000,需根据实际数据库测试
- 并行化处理
const batches = []; // 生成批次任务 await Promise.all(batches);
- 使用Promise.all实现批次内并行
- 注意:需确保数据库连接池足够(调整egg-sequelize配置)
- 内存优化
// 使用流式处理代替完整数组 function* chunkArray(arr, size) { for (let i = 0; i < arr.length; i += size) { yield arr.slice(i, i + size); } }
- 对于超大数据(10万+),建议使用生成器逐块处理
- 当前2000量级可不使用,但保留扩展性
- 数据库层面优化
-- 确保表有复合索引 CREATE INDEX idx_role_rule ON role_rule (role_id, rule_id);
- 必须为(role_id, rule_id)建立复合索引
- 为rule_id单独建立索引(如果业务需要)
- 事务优化
// 调整Sequelize连接池配置 config.sequelize = { dialectOptions: { // 提升事务性能 options: { enableArithAbort: true, requestTimeout: 30000 } } }
- 调整事务超时时间(默认5秒可能不够)
- 确保连接池大小足够(建议pool.max >= 10)
- 防重机制
bulkCreate(..., { ignoreDuplicates: true })
- 防止重复插入导致的错误
- 需要数据库支持(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;
},
# 四、删除权限(演示模型关联删除子集)
首先我们要先分析,删除权限,应该删除哪些数据
- 删除权限之前,先删除rule表里面的子分类,他们的关系由pid进行关联(模型关联已关联)
- 删除权限之前,先删除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. 代码说明
- 事务处理
使用transaction保证数据一致性,所有数据库操作要么全部成功,要么全部回滚
- 递归删除子权限
通过getAllChildIds递归获取所有层级子权限ID
使用pid字段进行父子关系关联
- 批量删除优化
使用[Op.in]一次性删除所有相关权限
利用Sequelize的onDelete: 'cascade配置自动清理关联的role_rule数据
- 错误处理
使用try-catch捕获异常
提供明确的成功/失败提示
- 多层子权限处理:
递归算法会自动处理无限层级的权限树结构
示例:删除父权限时,会同时删除子权限、孙权限等所有后代
- 性能优化:
使用单事务减少数据库连接开销
批量删除减少SQL执行次数