How to set permissions
xly's permission model has two complementary tables. They answer different questions and live at different layers; reading the wiki correctly here matters because the names look superficially similar.
The two tables
| Table | Granularity | What it stores | Loaded when |
|---|---|---|---|
gdsjurisdiction |
per module | the catalog of actions/buttons that exist on each module (BtnAdd, BtnUpd, BtnDel, …) |
every getModelBysId call (skipped for ADMIN — see Slice 1) |
sysjurisdiction |
per role (and optionally per user) | the grants — which role (or user) can perform which action on which module | resolved on getModelBysId for the user's role; BusinessGdsconfigformsServiceImpl.getJurisdictionData()
|
The mental model: gdsjurisdiction is the menu of permission items
the framework knows about per module. sysjurisdiction is the seating
chart — who's allowed to use which item.
What gdsjurisdiction contains
A tree of permission items keyed by module:
| Column | Meaning |
|---|---|
sParentId |
the gdsmodule.sId this permission item belongs to |
sName |
the action key — e.g., BtnAdd, BtnUpd, BtnDel, BtnExport
|
sChinese / sEnglish / sBig5
|
display label (e.g., 新增) |
iOrder |
sort order in the permission UI |
sBrandsId / sSubsidiaryId
|
tenant scope |
In the current live DB there are 4,335 gdsjurisdiction rows covering
892 distinct modules and 186 distinct action names. gdsjurisdiction
does not contain a role column or a user column — it is purely a
catalog.
What sysjurisdiction contains
The actual grants:
| Column | Meaning |
|---|---|
sParentId |
the module sId the grant is for |
sParent2Id |
the parent module (group/category) — used by the permission UI for tree rendering |
sJurisdictionClassifyId |
role / permission-class id (FK to sisjurisdictionclassify) |
sUserId |
user id, for per-user overrides — empty means "role-level grant" |
sAction |
the granted action — e.g., BtnBsOperation.BtnDesignFunction
|
sKey |
composite path key, encoding the module hierarchy that owns the action |
In the current live DB there are 28,045 sysjurisdiction rows, all
of them role-level (every sUserId is empty) — 22 roles in
sisjurisdictionclassify × the modules and actions each role can use.
The runtime in BusinessGdsconfigformsServiceImpl.getJurisdictionData
(line 212) chooses between user-level and role-level grants:
Boolean hasCheckUser = checkUserJurisdiction(map);
if (hasCheckUser) {
return this.getGroupJurisdictionUserNew(map); // per-user overrides
} else {
return this.getGroupJurisdictionNew(map); // role-only fallback
}
If the calling user has any row in sysjurisdiction with their sUserId
set, the user path wins; otherwise the role-only path is used. In the
present dev DB, the user path is unreachable.
How permissions are loaded on a request
Pseudocode for the relevant slice of BusinessBaseServiceImpl.getModelBysId
(xlyBusinessService/.../BusinessBaseServiceImpl.java:196-198):
List<Map<String, Object>> jList = new ArrayList<>();
if (!UserType.ADMIN.getTypeName().equals(map.get("sUserType"))) {
jList = businessGdsconfigformsService.getJurisdictionData(qMap);
}
returnMap.put("gdsjurisdiction", jList);
ADMIN bypasses permissions entirely and gets an empty grant list, which the SPA interprets as "show every button". Non-admin users get the filtered list of actions resolvable for their role + module + tenant. The SPA enables/disables buttons and toggles columns based on what came back.
How to grant a permission
For a new module, the typical path is:
- Define the module + form (recipe).
- Insert
gdsjurisdictionrows for each button/action the module exposes (BtnAdd,BtnUpd,BtnExport, …). One row per action;sParentId = your module's sId. - Decide which roles get which actions, and insert
sysjurisdictionrows:sParentId = module sId,sJurisdictionClassifyId = role sId,sAction = action key. LeavesUserIdempty for role-level grants. - (Or override at the user level by inserting rows with
sUserIdset; per-user overrides shadow the role-level grants for that user.) - Save through BACK so cache invalidation propagates (the runtime caches
getJurisdictionDataper(sUserId, sModelsId, sBrandsId, sSubsidiaryId)— see the@Cacheableannotation at line 209).
ADMIN as a special case
The hardcoded ADMIN bypass is a real escape hatch. Every deployment has at least one ADMIN account by convention. From a security audit perspective, ADMIN access should be tightly controlled and audited externally — the framework offers no within-app constraint on what an ADMIN can do.
Cross-tenant boundary is not permissions
Worth saying explicitly: gdsjurisdiction + sysjurisdiction enforce
within-tenant permissions. The cross-tenant boundary (one company
can't read another company's rows) is enforced by the
multi-tenant scoping — automatic
sBrandsId/sSubsidiaryId injection — not by jurisdiction rules.
Note on plat_base_authority_*
Three tables under plat_base_authority (plat_base_authority,
plat_base_authority_button_type, plat_base_authority_data_type) live
in the schema and look like they ought to be lookups for button-type and
data-permission-type identifiers. All three are empty in the live DB;
they belong to the out-of-scope xlyPlat* B2B platform layer, not to
the framework's own permission flow. Don't write referencing them when
documenting framework permissions — the jurisdiction surface above is
self-contained.