# 一、创建超级管理员
# 1. 新建角色表role的模型
app/model/role.js
'use strict';
module.exports = app => {
const { INTEGER, STRING, DATE, ENUM, TEXT, BIGINT } = app.Sequelize;
const Role = app.model.define('role', {
id: {
type: INTEGER(20).UNSIGNED,
primaryKey: true,
autoIncrement: true,
comment: '角色表主键id'
},
name: {
type: STRING(30),
allowNull: false,
defaultValue: '',
comment: '角色名称'
},
desc: {
type: STRING(255),
allowNull: true,
defaultValue: '',
comment: '角色描述,对角色的简单介绍'
},
status: {
type: INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '状态:1:启用,0:禁用'
},
// 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') }
});
return Role;
}
# 2. 新建商城管理员shop_manager的模型
app/model/shop_manager.js
'use strict';
//哈希函数
const crypto = require('node:crypto');
//实现与 PHP password_hash 函数(使用 PASSWORD_DEFAULT 选项)相同的加密结果
//采用 bcryptjs 库生成 BCrypt 哈希
//安装库:npm install bcryptjs
const bcrypt = require('bcryptjs');
module.exports = app => {
const { INTEGER, STRING, DATE, ENUM, TEXT, BIGINT } = app.Sequelize;
const ShopManager = app.model.define('shop_manager', {
id: {
type: INTEGER(20).UNSIGNED,
primaryKey: true,
autoIncrement: true,
comment: '管理员表主键id'
},
username: {
type: STRING(30),
allowNull: false,
defaultValue: '',
comment: '管理员账号'
},
password: {
type: STRING(255),
allowNull: false,
defaultValue: '',
comment: '管理员密码',
set(val) {
// 盐的轮数设为10,与PHP的PASSWORD_DEFAULT一致
const saltRounds = 10;
// 同步生成哈希
const result = bcrypt.hashSync(val, saltRounds);
// console.log(result);
this.setDataValue('password', result);
}
},
avatar: {
type: STRING(1000),
allowNull: true,
defaultValue: 'https://thinkphp-all.oss-cn-hangzhou.aliyuncs.com/public/67b3001b2aedd.png',
comment: '管理员头像(本地、网络图片地址)'
},
status: {
type: INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '状态:1:启用,0:禁用'
},
super: {
type: INTEGER(1),
allowNull: true,
defaultValue: 0,
comment: '是否超级管理员 1是,0:否'
},
role_id: {
type: INTEGER(20).UNSIGNED,
allowNull: true,
defaultValue: 0,
comment: '角色id',
references: { //关联关系
model: 'role', //关联的表
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') }
});
return ShopManager;
}
# 3. 创建商城超级管理员
app/controller/admin/home.js
// 创建超级管理员账号
async createSuperManager() {
const { ctx, app } = this;
let manager = await app.model.Manager.findAndCountAll();
if(manager.count != 0){
return ctx.apiFail('非法操作,系统拒绝您的请求');
}
//1.参数验证
ctx.validate({
username: {
type: 'string', //参数类型
required: true, //是否必须
// defValue: '',
desc: '管理员账号' //字段含义
},
password: {
type: 'string',
required: true,
// defValue: '',
desc: '管理员密码'
},
});
// 拿参数
const { username, password } = ctx.request.body;
await app.model.Manager.create({
username,
password,
status:1,
super:1, //超级管理员
});
// 创建商城的超级管理员
await this.createSuperShopManager({
username,
password,
status:1,
super:1, //超级管理员
});
ctx.apiSuccess('ok');
}
// 创建商城超级管理员
async createSuperShopManager(obj) {
const { ctx, app } = this;
//创建一个角色
await app.model.Role.create({
name: '超级管理员',
desc: '拥有商城管理的所有权限',
});
//创建超级管理员
await app.model.ShopManager.create({
...obj,
role_id: 1,
});
return true;
}
# 二、后台管理员登录生成商城超级管理员token
# 1. eggjs参数验证
# 2. 生成唯一token用到jwt加密鉴权
npm i egg-jwt --save
# 3. 配置jwt加密鉴权
config/plugin.js
module.exports = {
...
// 配置jwt加密鉴权
jwt: {
enable: true,
package: 'egg-jwt',
},
};
# 4. 配置密钥
config/config.default.js
...
//加密处理秘钥---随便定义:字母符号下划线数字等任意组合
config.crypto = {
secret: 'qhdgw@45ncashdaksh2!#@3nxjdas*_672',
};
//jwt鉴权密钥,可以和上面crypto一样,也可以不一样,但一定要复杂
config.jwt = {
secret: 'qhdgw@45ncashdaksh2!#@3nxjdas*_259',
};
...
# 5. 扩展context.js封装生成token和验证token的方法
app/extend/context.js
...
// 生成token
getToken(value){
return this.app.jwt.sign(value,this.app.config.jwt.secret);
},
//验证token
checkToken(token){
return this.app.jwt.verify(token,this.app.config.jwt.secret);
},
# 6. 控制器代码
app/controller/admin/home.js
...
//管理员登录提交数据逻辑
async loginevent() {
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
username: {
type: 'string', //参数类型
required: true, //是否必须
// defValue: '',
desc: '管理员账号' //字段含义
},
password: {
type: 'string',
required: true,
// defValue: '',
desc: '管理员密码'
},
});
// 拿参数
const { username, password } = ctx.request.body;
// 判断管理员是否存在
const manager = await app.model.Manager.findOne({
where: {
username: username,
status:1,
},
});
if (!manager) {
ctx.throw(400, '管理员不存在或者被禁用');
}
// 存在则验证密码
await this.checkPassword(password, manager.password);
//存储在session中,定义session中的一个属性auth存储登录信息
ctx.session.auth = manager;
// 同时登录商城系统,生成token(网站后台只支持超级管理员登录商城系统)
// 普通管理员跟thinkphp框架一样,提供api接口
if(manager.super == 1){
await this.loginShopManager({
username,
password,
});
}
return ctx.apiSuccess('ok');
}
// 登录商城系统,生成token(网站后台只支持超级管理员登录商城系统)
async loginShopManager(obj){
const { ctx, app } = this;
//一般流程
// 1.参数验证 ---上面已经验证过了
// 2.验证用户是否存在 -- 和网站超级管理员一起生成的,存在
// 3.验证用户状态是否启用 -- 超级管理员默认启用
// 4.验证用户密码
// 5. 生成唯一token
// 6. 加入到缓存中(保证用户提交验证的token来自我们服务器生成的)
// 7. 返回用户信息和token
// 拿参数
// const { username, password } = ctx.request.body;
// 判断管理员是否存在
let shop_manager = await app.model.ShopManager.findOne({
where: {
username: obj.username,
status:1,
},
});
if (!shop_manager) {
ctx.throw(400, '管理员不存在或者被禁用');
}
// 验证用户密码
const isMatch = bcrypt.compareSync(obj.password, shop_manager.password);
if (!isMatch) {
ctx.throw(400, '商城系统管理员密码错误');
}
// 生成唯一token
// 用到jwt加密鉴权: npm i egg-jwt --save 放到扩展中写一个公共方法
shop_manager = JSON.parse(JSON.stringify(shop_manager));
// console.log(shop_manager);
//this.app.jwt.sign(shop_manager, this.app.config.jwt.secret);
let token = ctx.getToken(shop_manager);
shop_manager.token = token; //给用户信息加上token属性
//删除密码
delete shop_manager.password;
console.log('商城登录的超级管理员', shop_manager);
return true;
}
# 三、商城系统管理员登录信息加入缓存redis中
# 1. 安装redis插件
npm i egg-redis --save
# 2. 配置redis插件
config/plugin.js
//配置redis插件
redis: {
enable: true,
package: 'egg-redis',
},
# 3. 配置redis存储服务器
config/config.default.js
...
//配置redis存储服务器
config.redis = {
client: {
port: 6379, // Redis 默认端口
host: '127.0.0.1', // Redis host
password: '',
db: 0, // 默认为 0
},
};
# 4. 新建封装一个redis的缓存服务类
app/service/cache.js
'use strict';
const Service = require('egg').Service;
class CacheService extends Service {
/**
* 获取列表
* @param {string} key 键
* @param {boolean} isChildObject 元素是否为对象
* @return { array } 返回数组
*/
async getList(key, isChildObject = false) {
const { redis } = this.app
let data = await redis.lrange(key, 0, -1)
if (isChildObject) {
data = data.map(item => {
return JSON.parse(item);
});
}
return data;
}
/**
* 设置列表
* @param {string} key 键
* @param {object|string} value 值
* @param {string} type 类型:push和unshift
* @param {Number} expir 过期时间 单位秒
* @return { Number } 返回索引
*/
async setList(key, value, type = 'push', expir = 0) {
const { redis } = this.app
if (expir > 0) {
await redis.expire(key, expir);
}
if (typeof value === 'object') {
value = JSON.stringify(value);
}
if (type === 'push') {
return await redis.rpush(key, value);
}
return await redis.lpush(key, value);
}
/**
* 设置 redis 缓存
* @param { String } key 键
* @param {String | Object | array} value 值
* @param { Number } expir 过期时间 单位秒
* @return { String } 返回成功字符串OK
*/
async set(key, value, expir = 0) {
const { redis } = this.app
if (expir === 0) {
return await redis.set(key, JSON.stringify(value));
} else {
return await redis.set(key, JSON.stringify(value), 'EX', expir);
}
}
/**
* 获取 redis 缓存
* @param { String } key 键
* @return { String | array | Object } 返回获取的数据
*/
async get(key) {
const { redis } = this.app
const result = await redis.get(key)
return JSON.parse(result)
}
/**
* redis 自增
* @param { String } key 键
* @param { Number } value 自增的值
* @return { Number } 返回递增值
*/
async incr(key, number = 1) {
const { redis } = this.app
if (number === 1) {
return await redis.incr(key)
} else {
return await redis.incrby(key, number)
}
}
/**
* 查询长度
* @param { String } key
* @return { Number } 返回数据长度
*/
async strlen(key) {
const { redis } = this.app
return await redis.strlen(key)
}
/**
* 删除指定key
* @param {String} key
*/
async remove(key) {
const { redis } = this.app
return await redis.del(key)
}
/**
* 清空缓存
*/
async clear() {
return await this.app.redis.flushall()
}
}
module.exports = CacheService;
# 5. 控制器代码
app/controller/admin/home.js
//登录商城系统,生成token(网站后台只支持超级管理员登录商城系统)
async loginShopManager(obj){
const { ctx, app } = this;
//一般流程
// 1.参数验证 ---上面已经验证过了
// 2.验证用户是否存在 -- 和网站超级管理员一起生成的,存在
// 3.验证用户状态是否启用 -- 超级管理员默认启用
// 4.验证用户密码
// 5. 生成唯一token
// 6. 加入到缓存中(保证用户提交验证的token来自我们服务器生成的)
// 7. 返回用户信息和token
// 拿参数
// const { username, password } = ctx.request.body;
// 判断商城管理员是否存在
let shop_manager = await app.model.ShopManager.findOne({
where: {
username: obj.username,
status:1,
},
});
if (!shop_manager) {
ctx.throw(400, '商城管理员不存在或者被禁用');
}
// 4.验证用户密码
const isMatch = bcrypt.compareSync(obj.password,shop_manager.password);
if(!isMatch){
ctx.throw(400, '商城管理员密码错误');
}
// 5. 生成唯一token
shop_manager = JSON.parse(JSON.stringify(shop_manager));
// this.app.jwt.sign(shop_manager, this.app.config.jwt.secret);
let token = ctx.getToken(shop_manager);
shop_manager.token = token;
console.log('商城超级管理员', shop_manager);
// 6. 加入到缓存中(保证用户提交验证的token来自我们服务器生成的)
let shop_manager_token = await this.service.cache.set('shop_manager_' + shop_manager.id,
token, 60 * 60 * 24 * 365);
if(!shop_manager_token){
ctx.throw(400, '登录失败');
}
return true;
}
# 四、清除缓存redis
以后台超级管理员退出登录为例,因为后台超级管理员登录的时候,一起登录了商城的超级管理员,所以,后台超级管理员退出登录,商城超级管理员也一样退出登录(清除缓存redis信息)。
app/controller/admin/home.js
...
// 管理员退出登录
async logout() {
const { ctx } = this;
// 后台退出登录,商城管理员一样会退出登录(超级管理员有效)
if(ctx.session.auth && ctx.session.auth.super == 1){
// 商城系统超级管理员退出登录,并清除缓存
await this.logoutShopManager();
}
// 清除session
ctx.session.auth = null;
ctx.toast('退出登录成功', 'success');
ctx.redirect('/admin/login');
}
...
// 商城系统超级管理员退出登录清除缓存redis
async logoutShopManager(){
const { ctx, app } = this;
//拿到商城系统超级管理员
if(ctx.session.auth && ctx.session.auth.super == 1){
let shop_manager = await app.model.ShopManager.findOne({
where:{
username: ctx.session.auth.username, //因为两个超级管理员账号一样
}
});
console.log('商城超级管理员', shop_manager);
let shop_manager_token = await this.service.cache.get('shop_manager_' + shop_manager.id);
console.log('商城超级管理员缓存信息', shop_manager_token);
// 清除缓存redis
if(shop_manager_token){
await this.service.cache.remove('shop_manager_' + shop_manager.id);
}
}
}
# 五、token权限验证中间件开发
# 1. 新建中间件 app/middleware/shop_manager_auth.js
module.exports = (option, app) => {
return async function shopManagerAuth(ctx, next) {
// 1. 获取header头的token
const { token } = ctx.header;
// console.log('看一下header头',token);
if (!token) {
ctx.throw(400, '您没有权限访问商城接口');
}
//2. 根据token解密,换取用户信息,失败则要么Token已过期或者不合法,抛出错误,终止程序
let shopmanager = {};
try {
shopmanager = ctx.checkToken(token);
} catch (error) {
let fail = error.name == 'TokenExpiredError'?
'Token已过期!请重新获取令牌':'Token 令牌不合法!';
ctx.throw(400,fail);
}
//3. 说明token解密正确,此时判断用户是否登录过
// 根据当前解密的用户信息的id,去缓存拿一下该id的信息
let t = await ctx.service.cache.get('shop_manager_' + shopmanager.id);
if(!t || t != token){
ctx.throw(400,'Token 令牌不合法!');
}
// 4. 说明当前用户之前登录过了,缓存里有他的数据,比如说已经登录过3天了
// 但有一种情况,这三天内,他发了违规信息,已经被超级管理员禁用了或者被超级管理员把他从数据库删除了
// 那么即使现在他传的token有效,也没有用,依旧不能让他操作
shopmanager = await app.model.ShopManager.findByPk(shopmanager.id);
if(!shopmanager || shopmanager.status == 0){
ctx.throw(400,'用户不存在或已被禁用!');
}
// 5. 没什么问题了,把用户信息挂载到ctx上,方便调用
ctx.auth_shop_manager = shopmanager;
await next();
}
}
# 2. 配置中间件 config/config.default.js
...
config.middleware = ['errorHandler', 'adminAuth', 'adminMenu','shopManagerAuth'];
...
// 对中间件shopManagerAuth进一步配置
config.shopManagerAuth = {
match:[
'/shop/admin',//只要包含/shop/admin路由的任何页面都生效
],
};
...
# 六、角色管理
字体图标,查看:https://fontawesome.dashgame.com/ (opens new window)
# 1. 新建管理商城的路由
app/router/admin/shop.js
module.exports = app => {
const { router, controller } = app;
// 给角色配置权限(授权)
router.post('/shop/admin/role/set_rules',controller.admin.role.setRules);
//删除角色功能
router.get('/shop/admin/role/:id/delete', controller.admin.role.delete);
//修改角色状态
router.post('/shop/admin/role/:id/update_status',controller.admin.role.updateStatus);
//修改角色界面
router.get('/shop/admin/role/edit/:id', controller.admin.role.edit);
//修改角色数据功能
router.post('/shop/admin/role/:id', controller.admin.role.update);
//创建角色界面
router.get('/shop/admin/role/create', controller.admin.role.create);
//创建角色提交数据
router.post('/shop/admin/role', controller.admin.role.save);
//角色列表页面
router.get('/shop/admin/role', controller.admin.role.index);
};
# 2. 完成创建角色、角色列表功能
新建角色控制器 app/controller/admin/role.js
'use strict';
const Controller = require('egg').Controller;
class RoleController extends Controller {
//创建角色界面
async create(){
const { ctx } = this;
//渲染公共模版
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: '角色的简单介绍,选填',
}
],
},
//新增成功之后跳转到哪个页面
successUrl: '/shop/admin/role',
});
}
//创建角色提交数据
async save(){
//一般处理流程
//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
}
}
});
//先判断一下直播功能中的礼物账号是否存在,不存在在写入数据库
//2.写入数据库
//3.成功之后给页面反馈
let { name, desc } = 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
});
this.ctx.apiSuccess('创建角色成功');
}
//角色列表页面
async index(){
const { ctx, app } = this;
//分页:可以提炼成一个公共方法page(模型名称,where条件,其他参数options)
let data = await ctx.page('Role');
//渲染公共模版
await ctx.renderTemplate({
title: '角色列表',//现在网页title,面包屑导航title,页面标题
data,
tempType: 'table', //模板类型:table表格模板 ,form表单模板
table: {
//表格上方按钮,没有不要填buttons
buttons: [
{
url: '/shop/admin/role/create',//新增路径
desc: '创建商城系统管理角色',//新增 //按钮名称
// icon: 'fa fa-plus fa-lg',//按钮图标
}
],
//表头
columns: [
{
title: '角色名称',
key: 'name',
class: 'text-center',//可选
},
{
title: '角色描述',
key: 'desc',
class: 'text-center',//可选
},
{
title: '创建时间',
key: 'create_time',
width: 200,//可选
class: 'text-center',//可选
},
{
title: '操作',
class: 'text-right',//可选
action: {
//修改
edit: function (id) {
return `/shop/admin/role/edit/${id}`;
},
//删除
delete: function (id) {
return `/shop/admin/role/${id}/delete`;
}
}
},
],
},
});
}
//修改角色界面
async edit(){
}
//修改角色数据功能
async update(){
}
//修改角色状态
async updateStatus(){
}
//删除角色功能
async delete(){
}
// 给角色配置权限(授权)
async setRules(){
}
}
module.exports = RoleController;
# 3. 网站后台处理商城管理,超级管理员不走中间件
app/middleware/shop_manager_auth.js
module.exports = (option, app)=>{
return async function shopManagerAuth(ctx,next){
// 针对在网站后台操作的商城超级管理员,不需要验证token
if(ctx.session.auth && ctx.session.auth.super == 1){
// 查询一下商城超级管理员是否存在,他们的用户名是一样的
let shop_manager = await app.model.ShopManager.findOne({
where:{
username:ctx.session.auth.username,//因为两个超级管理员账号一样
}
});
if(shop_manager){
//ctx 是 Egg.js 的请求上下文对象,所有 Service 都通过 ctx.service.xxx 暴露
let shop_manager_token = await ctx.service.cache.get('shop_manager_' + shop_manager.id);
if(shop_manager_token){
await next();
return;
}
}
}
...
}
}
# 4. 路由分组
app/router.js
module.exports = app => {
const { router, controller } = app;
//分组
...
require('./router/admin/shop')(app);
...
};
# 5. 后台左侧菜单新增商城管理-角色管理
data/root.json
[
...
{"id":17,"pid":0, "name": "商城管理", "icon": "fa fa-shopping-cart", "url": "" },
{"id":18,"pid":17, "name": "角色管理", "icon": "fa fa-users", "url": "/shop/admin/role" }
]
# 七、修改角色、删除角色、修改角色状态
控制器app/controller/admin/role.js
...
//角色列表页面
async index(){
const { ctx, app } = this;
//分页:可以提炼成一个公共方法page(模型名称,where条件,其他参数options)
let data = await ctx.page('Role');
//渲染公共模版
await ctx.renderTemplate({
title: '角色列表',//现在网页title,面包屑导航title,页面标题
data,
tempType: 'table', //模板类型:table表格模板 ,form表单模板
table: {
//表格上方按钮,没有不要填buttons
buttons: [
{
url: '/shop/admin/role/create',//新增路径
desc: '新增角色',//新增 //按钮名称
// icon: 'fa fa-plus fa-lg',//按钮图标
}
],
//表头
columns: [
{
title: '角色名称',
key: 'name',
class: 'text-center',//可选
},
{
title: '角色描述',
key: 'desc',
class: 'text-center',//可选
},
{
title: '可用状态',
key: 'status',
width: 200,//可选
class: 'text-center',//可选
hidekeyData: true,//是否隐藏key对应的数据
render(item) {
// console.log('可用状态里面每个item', item);
let arr = [
{ value: 1, name: '可用' },
{ value: 0, name: '不可用' },
];
let str = `<div class="btn-group btn-group-${item.id}">`;
for (let i = 0; i < arr.length; i++) {
str += `<button type="button" class="btn btn-light" data="${item.status}"
value="${arr[i].value}"
@click="changeBtnStatus('status','btn-group-${item.id}',${arr[i].value},${i},${item.id},'/shop/admin/role','Role')">${arr[i].name}</button>`;
}
str += `</div>`;
return str;
}
},
{
title: '创建时间',
key: 'create_time',
width: 200,//可选
class: 'text-center',//可选
},
{
title: '操作',
class: 'text-right',//可选
action: {
//修改
edit: function (id) {
return `/shop/admin/role/edit/${id}`;
},
//删除
delete: function (id) {
return `/shop/admin/role/${id}/delete`;
}
}
},
],
},
});
}
//修改角色界面
async edit(){
const { ctx, app } = this;
const id = ctx.params.id;
let data = await app.model.Role.findOne({
where: {
id
}
});
if (!data) {
return this.ctx.apiFail('该角色不存在');
}
// data = JSON.parse(JSON.stringify(data));
// console.log(data);
//渲染公共模版
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: '角色的简单介绍,选填',
}
],
//修改内容默认值
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
},
}
});
// 参数
const id = ctx.params.id;
const { name, desc} = ctx.request.body;
// 是否存在
const role = await app.model.Role.findOne({ where: { id } });
if (!role) {
return ctx.apiFail('角色不存在');
}
//存在,由于唯一性,你不能修改的时候,修改成存在的名称
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();
// 给一个反馈
ctx.apiSuccess('修改角色成功');
}
//修改角色状态功能
async updateStatus(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
id: {
type: 'int',
required: true,
desc: '角色id'
},
status: {
type: 'int', //参数类型
required: true, //是否必须
// defValue: '',
desc: '角色状态', //字段含义
range:{
in: [0,1]
},
},
});
// 参数
const id = ctx.params.id;
const { status} = ctx.request.body;
// 是否存在
const role = await app.model.Role.findOne({ where: { id } });
if (!role) {
return ctx.apiFail('角色不存在');
}
// 修改数据
role.status = status;
await role.save();
// 给一个反馈
ctx.apiSuccess('修改角色状态成功');
}
//删除角色功能
async delete(){
const { ctx, app } = this;
const id = ctx.params.id;
const role = await app.model.Role.findOne({ where: { id } });
if(role && role.name == '超级管理员'){
return ctx.apiFail('超级管理员角色不能删除');
}
await app.model.Role.destroy({
where: {
id
}
});
//提示
ctx.toast('角色删除成功', 'success');
//跳转
ctx.redirect('/shop/admin/role');
}
...
# 八、删除角色最终代码
控制器app/controller/admin/role.js
//删除角色功能
async delete(){
const { ctx, app } = this;
const id = ctx.params.id;
const role = await app.model.Role.findOne({ where: { id } });
if(role && role.name == '超级管理员'){
return ctx.apiFail('超级管理员角色不能删除');
}
//由于管理员配置了角色,删除角色,关联查询管理员缺乏角色会报错,要做一下提示
let shopmanager = await app.model.ShopManager.findAndCountAll({
where:{
role_id:id,
}
});
// console.log('管理员使用该角色',JSON.parse(JSON.stringify(shopmanager)));
if(shopmanager.count>0){
return ctx.apiFail('您删除的这个角色,有管理员在使用,请先删除这些管理员或修改这些管理员角色,在删除当前角色');
}
await app.model.Role.destroy({
where: {
id
}
});
//提示
ctx.toast('角色删除成功', 'success');
//跳转
ctx.redirect('/shop/admin/role');
}
# 九、角色列表(包含角色对应的权限API接口)
# 1. 路由
app/router/admin/shop.js
module.exports = app => {
const { router, controller } = app;
...
//角色列表包含角色对应的权限api接口
router.get('/shop/admin/role/:page', controller.admin.role.indexlist);
//角色列表页面
router.get('/shop/admin/role', controller.admin.role.index);
}
# 2. 模型关联
# 1. 角色role表模型Role
app/model/role.js
...
module.exports = app => {
...
const Role = app.model.define('role', {
...
});
Role.associate = function (models) {
// 多对多关联到权限表
Role.belongsToMany(app.model.Rule, {
through: app.model.RoleRule, // 指定中间模型
foreignKey: 'role_id', // 中间表中关联Role的外键
otherKey: 'rule_id' // 中间表中关联Rule的外键
});
};
return Role;
}
# 2. 权限rule表模型Rule
app/model/rule.js
...
module.exports = app => {
...
const Rule = app.model.define('rule', {
id: {
type: INTEGER(20).UNSIGNED,
primaryKey: true,
autoIncrement: true,
comment: '权限主键id'
},
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' // 别名(可选)
});
//在 Rule 模型中,同样建立反向的多对多关联:
// 多对多关联到角色表
Rule.belongsToMany(app.model.Role, {
through: app.model.RoleRule,
foreignKey: 'rule_id', // 中间表中关联Rule的外键
otherKey: 'role_id' // 中间表中关联Role的外键
});
}
return Rule;
}
# 3. 中间表role_rule
app/model/role_rule.js
中间表模型关联可不填,如果要写关联,则如下:
...
module.exports = app => {
...
const RoleRule = app.model.define('role_rule', {
...
});
// 模型关联关系
RoleRule.associate = function (models) {
// 关联角色
RoleRule.belongsTo(app.model.Role);
// RoleRule.belongsTo(app.model.Role, { foreignKey: 'role_id' });
// 关联权限
RoleRule.belongsTo(app.model.Rule);
// RoleRule.belongsTo(app.model.Rule, { foreignKey: 'rule_id' });
}
return RoleRule;
}
# 3. 控制器API接口代码
app/controller/admin/role.js
//角色列表包含角色对应的权限api接口
async indexlist(){
const { ctx, app } = this;
let limit = parseInt(ctx.query.limit) || 10;
let role = await app.model.Role.findAll({
where:{
status:1
},
order:[
['id','desc']
],
limit,
include: [
{
model: app.model.Rule, // 直接关联权限
through: {
attributes: [] // 不需要返回中间表字段
},
/*
// 如果需要权限的子权限(如树形结构),可添加子关联
include: [
{
model: app.model.Rule,
as: 'ChildRule' // 自关联别名
}
]
*/
}
],
});
let rolecount = await app.model.Role.count({
where:{
status:1
}
});
// ctx.body = role;
ctx.apiSuccess({
list: role,
totalCount: rolecount
});
}
# 4. 返回结果
{
"msg": "ok",
"data": {
"list": [
{
"create_time": "2025-04-09 16:47:28",
"id": 9,
"name": "普通管理人员",
"desc": "对商城系统拥有一般的关联权限",
"status": 1,
"update_time": "2025-04-09T08:47:28.000Z",
"rules": [
{
"create_time": "2025-04-10 17:15:10",
"id": 34,
"pid": 33,
"status": 1,
"name": "创建角色",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 0,
"order": 50,
"icon": "",
"method": "GET",
"update_time": "2025-04-10T09:15:10.000Z",
"ChildRule": []
},
{
"create_time": "2025-04-10 17:13:54",
"id": 30,
"pid": 0,
"status": 1,
"name": "管理员管理",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 1,
"order": 50,
"icon": "",
"method": "GET",
"update_time": "2025-04-10T09:13:54.000Z",
"ChildRule": [
{
"create_time": "2025-04-10 17:14:07",
"id": 31,
"pid": 30,
"status": 1,
"name": "创建管理员",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 1,
"order": 50,
"icon": "",
"method": "POST",
"update_time": "2025-04-10T09:14:44.000Z"
},
{
"create_time": "2025-04-10 17:14:21",
"id": 32,
"pid": 30,
"status": 1,
"name": "修改管理员",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 1,
"order": 50,
"icon": "",
"method": "POST",
"update_time": "2025-04-10T09:14:49.000Z"
}
]
},
{
"create_time": "2025-04-10 17:14:07",
"id": 31,
"pid": 30,
"status": 1,
"name": "创建管理员",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 1,
"order": 50,
"icon": "",
"method": "POST",
"update_time": "2025-04-10T09:14:44.000Z",
"ChildRule": []
},
{
"create_time": "2025-04-10 17:14:21",
"id": 32,
"pid": 30,
"status": 1,
"name": "修改管理员",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 1,
"order": 50,
"icon": "",
"method": "POST",
"update_time": "2025-04-10T09:14:49.000Z",
"ChildRule": []
},
{
"create_time": "2025-04-10 17:14:37",
"id": 33,
"pid": 0,
"status": 1,
"name": "角色管理",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 1,
"order": 50,
"icon": "",
"method": "GET",
"update_time": "2025-04-10T09:14:37.000Z",
"ChildRule": [
{
"create_time": "2025-04-10 17:15:10",
"id": 34,
"pid": 33,
"status": 1,
"name": "创建角色",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 0,
"order": 50,
"icon": "",
"method": "GET",
"update_time": "2025-04-10T09:15:10.000Z"
}
]
}
]
},
{
"create_time": "2025-04-07 17:48:53",
"id": 8,
"name": "销售人员",
"desc": "对商城产品销售做一些统计等等",
"status": 1,
"update_time": "2025-04-07T09:48:53.000Z",
"rules": [
{
"create_time": "2025-04-10 17:14:37",
"id": 33,
"pid": 0,
"status": 1,
"name": "角色管理",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 1,
"order": 50,
"icon": "",
"method": "GET",
"update_time": "2025-04-10T09:14:37.000Z",
"ChildRule": [
{
"create_time": "2025-04-10 17:15:10",
"id": 34,
"pid": 33,
"status": 1,
"name": "创建角色",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 0,
"order": 50,
"icon": "",
"method": "GET",
"update_time": "2025-04-10T09:15:10.000Z"
}
]
},
{
"create_time": "2025-04-10 17:15:10",
"id": 34,
"pid": 33,
"status": 1,
"name": "创建角色",
"frontname": "",
"frontpath": "",
"condition": "",
"menu": 0,
"order": 50,
"icon": "",
"method": "GET",
"update_time": "2025-04-10T09:15:10.000Z",
"ChildRule": []
}
]
},
{
"create_time": "2025-04-02 17:52:13",
"id": 7,
"name": "库管",
"desc": "管理商城日常库存,上架、下架商品",
"status": 1,
"update_time": "2025-04-08T11:09:00.000Z",
"rules": []
},
{
"create_time": "2025-04-01 12:17:54",
"id": 1,
"name": "超级管理员",
"desc": "这是商城的超级管理员角色,它拥有最高权限,可以管理商城后台的所有功能,并且只有一个商城超级管理员",
"status": 1,
"update_time": "2025-04-08T11:08:53.000Z",
"rules": []
}
],
"totalCount": 4
}
}