Adonis.js最佳实践:用户角色关联建模——User hasOne Role还是Role hasMany Users?
在Adonis.js中设计用户-角色关联的最佳实践
基础场景:单角色体系(Role hasMany Users + User belongsTo Role)
这是最常见的权限模型,适合用户只能拥有一个角色的场景(比如普通用户、管理员二选一)。
数据库结构
roles表:存储角色信息字段名 类型 说明 id int/unsigned 主键 name varchar 角色标识(如 admin、user)description text 角色描述(可选) created_at datetime 创建时间 updated_at datetime 更新时间 users表:添加外键关联角色
在原有用户字段基础上,新增role_id字段(外键关联roles.id),允许为null(比如未分配角色的新用户)。
Adonis.js 模型实现
Role 模型
import { BaseModel, column, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm' import User from 'App/Models/User' import { DateTime } from 'luxon' export default class Role extends BaseModel { @column({ isPrimary: true }) public id: number @column() public name: string @column() public description: string | null @column.dateTime({ autoCreate: true }) public createdAt: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) public updatedAt: DateTime // 一个角色对应多个用户 @hasMany(() => User) public users: HasMany<typeof User> }
User 模型
import { BaseModel, column, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm' import Role from 'App/Models/Role' import { DateTime } from 'luxon' export default class User extends BaseModel { @column({ isPrimary: true }) public id: number @column() public name: string @column() public email: string @column() public roleId: number | null // 驼峰命名自动映射到数据库的role_id @column.dateTime({ autoCreate: true }) public createdAt: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) public updatedAt: DateTime // 用户属于一个角色 @belongsTo(() => Role) public role: BelongsTo<typeof Role> }
优势
- 结构简单,查询效率高,维护成本低
- 符合大多数中小型系统的权限需求
进阶方案:多角色体系(User belongsToMany Role)
如果需要支持用户拥有多个角色(比如一个用户既是内容编辑又是审核员),一对多关联就无法满足,此时应该用多对多关联,通过中间表建立关系。
数据库结构
- 保留
roles和users表(去掉users表的role_id字段) - 新增中间表
user_roles,存储用户与角色的关联关系:
注意:将字段名 类型 说明 user_id int/unsigned 外键,关联 users.idrole_id int/unsigned 外键,关联 roles.idcreated_at datetime 创建时间(可选) user_id和role_id设置为联合主键,避免重复关联。
Adonis.js 模型实现
User 模型新增关联
import { belongsToMany, BelongsToMany } from '@ioc:Adonis/Lucid/Orm' import Role from 'App/Models/Role' // ... 原有字段 ... // 用户拥有多个角色 @belongsToMany(() => Role, { pivotTable: 'user_roles', // 指定中间表名称 }) public roles: BelongsToMany<typeof Role>
Role 模型新增关联
import { belongsToMany, BelongsToMany } from '@ioc:Adonis/Lucid/Orm' import User from 'App/Models/User' // ... 原有字段 ... // 角色被多个用户拥有 @belongsToMany(() => User, { pivotTable: 'user_roles', }) public users: BelongsToMany<typeof User>
优势
- 灵活性极强,支持复杂的权限组合
- 扩展性好,未来新增角色或调整用户权限无需修改核心表结构
配套权限校验实践
不管用哪种关联方案,都可以通过Adonis.js的中间件实现权限校验,确保只有对应角色的用户能访问特定路由。
示例:角色校验中间件
创建app/Middleware/Role.ts:
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' export default class RoleMiddleware { public async handle({ auth, response }: HttpContextContract, next: () => Promise<void>, allowedRoles: string[]) { const user = auth.user if (!user) { return response.unauthorized('请先登录') } // 单角色场景:加载用户关联的角色 await user.load('role') if (!allowedRoles.includes(user.role.name)) { return response.forbidden('无权限访问此资源') } // 多角色场景:替换为以下代码 // await user.load('roles') // const hasPermission = user.roles.some(role => allowedRoles.includes(role.name)) // if (!hasPermission) { // return response.forbidden('无权限访问此资源') // } await next() } }
注册与使用中间件
在start/kernel.ts中注册中间件:
export default class Kernel { protected namedMiddleware = { // ... 其他中间件 ... role: () => import('App/Middleware/Role'), } }
在路由中使用:
// 只有admin角色能访问 Route.get('/admin/dashboard', 'AdminController.dashboard').middleware(['auth', 'role:admin']) // 多角色场景:允许admin和editor访问 Route.get('/content/edit', 'ContentController.edit').middleware(['auth', 'role:admin,editor'])
选型建议
- 优先选一对多:如果你的系统是明确的单角色体系,或者短期内不需要多角色功能,用一对多关联足够,代码和数据库都更简洁。
- 提前布局多对多:如果不确定未来是否需要多角色,或者已经有相关需求,直接用多对多关联,避免后期重构数据库和模型的麻烦。
- 规范角色标识:把角色名定义为常量或枚举(比如
app/Constants/Role.ts),避免硬编码带来的拼写错误,提升可维护性:export enum RoleName { ADMIN = 'admin', USER = 'user', EDITOR = 'editor', }
内容的提问来源于stack exchange,提问作者Gilson Garcia
相关产品推荐
相关产品推荐

