Skip to content

Role & Permission ​

Roles carry three things: the menus (and buttons) their members can use, the permission strings the backend checks, and a data scope that limits which rows they see.

Features ​

  • Role CRUD with status management
  • Menu permission tree per role (menus, pages and buttons)
  • Button-level permissions — the same strings the backend checks with @SaCheckPermission
  • Role-level data scope

Data Model ​

SysRole (sys_role) ​

FieldTypeDescription
roleIdLongPrimary key
roleNameStringDisplay name (e.g. "Administrator")
roleKeyStringRole key (e.g. admin)
roleSortIntegerSort order
statusShort1 enabled, 0 disabled
dataScopeShort1 all, 2 custom, 3 own department, 4 department tree, 5 self only
deptIdSetStringDepartments for the custom scope
remarkStringNotes

SysRoleMenu (sys_role_menu) ​

FieldTypeDescription
roleIdLongRole ID
menuIdLongMenu ID

The many-to-many join between roles and the menus / buttons they may use.

API Endpoints ​

RoleController and PermissionMatrixController (server-admin). The authoritative list is ArchForge/spec/openapi.yaml; the old /system/role/* endpoints no longer exist.

MethodEndpointPermissionDescription
POST/admin/rolesystem:role:listPaginated role list
GET/admin/role/allsystem:role:queryAll roles (for pickers)
POST/admin/role/createsystem:role:addCreate a role
PUT/admin/role/updatesystem:role:editUpdate a role
POST/admin/role/deletesystem:role:removeDelete a role
POST/admin/role/statussystem:role:editEnable / disable
POST/admin/role/data-scopesystem:role:editSet the data scope (and departments for custom)
POST/admin/role/menusystem:role:queryMenu tree for the permission dialog
POST/admin/role/menu-idssystem:role:queryMenu ids granted to a role
POST/admin/role/save-menusystem:role:editSave the role's menus
GET/admin/permission-matrix/menus/treesystem:role:queryMenu permission tree
GET/admin/permission-matrix/roles/{roleId}/permissionssystem:role:queryA role's granted menus
PUT/admin/permission-matrix/roles/{roleId}/permissionssystem:role:editSave a role's grants

Permission Model ​

Each role is granted a set of menus. At login the backend loads the role's menus; the sidebar is built from them (GET /admin/auth/get-async-routes), and menus outside the set are not returned.

Button-Level Permissions ​

A menu entry with isButton = true carries a permission string such as system:user:add. Controllers check the same string — @SaCheckPermission(value = "system:user:add", type = StpAdminUtil.TYPE) — and the strings also reach the frontend in each route's meta.auths. Every admin handler declares a permission, and every checked permission must be grantable through a seeded menu or button (both enforced by tests).

On the frontend, hasPerms() / v-perms hide what the user cannot do:

vue
<template>
  <el-button v-if="hasPerms(['system:user:add'])">Create user</el-button>
</template>

Permission Format ​

module:entity:action, with the actions used throughout the seed data:

PermissionDescription
system:user:list / queryList / read users
system:user:addCreate user
system:user:editUpdate user
system:user:removeDelete user
system:role:addCreate role
system:menu:addCreate menu

Data Permission ​

In addition to menu and button permissions, each role can define a data scope that controls which records the role's members can see. The scope is stored on sys_role.data_scope and applied via the @DataPermission annotation combined with JPA Specifications.

Data Scopes ​

ScopeValueDescription
All1Access all data
Custom2Access data only from selected departments
Single Department3Access data from the user's own department
Department Tree4Access data from the user's department and all descendants
Self Only5Access only records created by the user

Configuration ​

Admins set the data scope in the role management UI. When Custom is selected, a department tree lets the admin pick the allowed departments. The backend exposes POST /admin/role/data-scope to persist the configuration.

Usage in Code ​

java
// AdminUserServiceImpl — behind POST /admin/user (system:user:list)
@DataPermission(deptAlias = "deptId", userAlias = "id")
public AdminPageResponse<AdminUserDTO> getUserList(AdminUserListRequest request) {
    Specification<SysUser> spec = (root, q, cb) -> QueryHelp.getPredicate(root, criteria, cb);
    spec = dataScopeSpecification.apply(spec, DataScopeContextHolder.get());
    Page<SysUser> users = sysUserService.findAll(spec, pageable);
    // ...map to DTOs
}

The scope only exists inside a @DataPermission call, and it fails closed: when the current user cannot be resolved the call sees no rows at all. Every meta-table row-data endpoint carries the annotation (a contract test fails on one that does not), and generated module controllers check their <tableCode>:* permissions and run export/import under the caller's data scope.

Role Assignment Flow ​

  1. An admin creates a role and grants menus and buttons in the permission tree
  2. Optionally sets the role's data scope
  3. Assigns the role to users on the user page (one role per user)
  4. At login the backend loads the user's role, permissions and data scope
  5. The frontend builds the sidebar and button visibility from them

Released under the MIT License.