CRM Four-Layer Data Permission Solution

Core Positioning

In a nutshell: On top of Yudao’s native @CrmPermission annotation, build a complete permission system with “User-Role-Post-Department” four-layer data permissions + customer level cascade filtering.

What Problems It Solves

LevelCurrent StatusAfter Enhancement
User-Level✅ Supported✅ Maintained
Role-Level❌ Not supported✅ Role-permission association
Post-Level❌ Not supported✅ Post-permission association
Dept-Level❌ Not supported✅ Dept-permission association
Customer Level❌ Not supported✅ Level stacking + cascade filtering

Suitable Users

  • CRM systems requiring fine-grained data permission control
  • Enterprises with customer level tiered management needs
  • Organizations needing to isolate customer data by department/post

1. Four-Layer Data Permission Model

graph TB subgraph "Permission Decision Engine" A["User Request"] --> B["Permission Resolution"] B --> C["Permission Merge
Union"] C --> D["Data Scope Filtering"] D --> E["Customer Level Filtering
AND Stacking"] end subgraph "Permission Sources" F["User-Level Permission
Priority 1 (Highest)"] G["Role-Level Permission
Priority 2"] H["Post-Level Permission
Priority 3"] I["Dept-Level Permission
Priority 4 (Lowest)"] end F --> B G --> B H --> B I --> B E --> J["SQL Filtering"] J --> K["Return Results"]

2. Permission Verification Flow

2.1 Operation Permission Verification

sequenceDiagram participant User as User participant Controller as Controller participant Aspect as @CrmPermission Aspect participant Engine as Permission Engine participant DB as Database User->>Controller: Request customer operation Controller->>Aspect: Trigger permission check Aspect->>Engine: hasBizOperationPermission() Engine->>Engine: Check user-level permission Engine->>Engine: Check role-level permission Engine->>Engine: Check post-level permission Engine->>Engine: Check dept-level permission Engine->>Engine: Merge results (union) alt Has Permission Engine->>Aspect: Return true Aspect->>Controller: Allow Controller->>DB: Execute business operation else No Permission Engine->>Aspect: Return false Aspect->>Controller: Throw permission exception end

2.2 Data Permission Filtering Flow

sequenceDiagram participant User as User participant Controller as Controller participant Interceptor as MyBatis Interceptor participant Engine as Permission Engine participant DB as Database User->>Controller: Query customer list Controller->>Interceptor: Trigger SQL interception Interceptor->>Engine: buildDataScopeSql() Engine->>Engine: Get data scope conditions Engine->>Engine: Get customer level conditions Engine->>Engine: Determine if cascade (related tables) Engine->>Interceptor: Return SQL filter conditions Interceptor->>Interceptor: Inject WHERE conditions Interceptor->>DB: Execute filtered SQL DB->>User: Return filtered results

3. Database Table Structure

3.1 Business Object Operation Permission Table

CREATE TABLE `crm_biz_object_permission` (
    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
    `biz_type` INT NOT NULL COMMENT 'Business Object Type (1=Lead/2=Customer/3=Contact/4=Opportunity/5=Contract)',
    `biz_type_name` VARCHAR(50) NOT NULL COMMENT 'Business Object Name',
    `operation_type` VARCHAR(20) NOT NULL COMMENT 'Operation Type (CREATE/READ/UPDATE/DELETE/TRANSFER)',
    `operation_name` VARCHAR(50) NOT NULL COMMENT 'Operation Name',
    `role_id` BIGINT DEFAULT NULL COMMENT 'Role ID',
    `post_id` BIGINT DEFAULT NULL COMMENT 'Post ID',
    `dept_id` BIGINT DEFAULT NULL COMMENT 'Department ID',
    `user_id` BIGINT DEFAULT NULL COMMENT 'User ID',
    `status` TINYINT DEFAULT 1 COMMENT 'Status (0=Disabled/1=Enabled)',
    `creator` VARCHAR(64) DEFAULT '' COMMENT 'Creator',
    `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create Time',
    `updater` VARCHAR(64) DEFAULT '' COMMENT 'Updater',
    `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `deleted` BIT DEFAULT 0 COMMENT 'Is Deleted',
    `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'Tenant ID',
    PRIMARY KEY (`id`),
    KEY `idx_biz_type` (`biz_type`),
    KEY `idx_role_id` (`role_id`),
    KEY `idx_post_id` (`post_id`),
    KEY `idx_dept_id` (`dept_id`),
    KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Business Object Operation Permission Table';

3.2 Data Scope Permission Table (Including Customer Level)

CREATE TABLE `crm_data_scope_permission` (
    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
    `biz_type` INT NOT NULL COMMENT 'Business Object Type',
    `scope_type` VARCHAR(20) NOT NULL COMMENT 'Data Scope Type (ALL/SELF/DEPT/DEPT_AND_CHILD/CUSTOM)',
    `scope_value` TEXT COMMENT 'Custom Scope Value (JSON format)',
    `max_customer_level` INT DEFAULT NULL COMMENT 'Max Customer Level (NULL=No Limit)',
    `role_id` BIGINT DEFAULT NULL COMMENT 'Role ID',
    `post_id` BIGINT DEFAULT NULL COMMENT 'Post ID',
    `dept_id` BIGINT DEFAULT NULL COMMENT 'Department ID',
    `user_id` BIGINT DEFAULT NULL COMMENT 'User ID',
    `status` TINYINT DEFAULT 1 COMMENT 'Status (0=Disabled/1=Enabled)',
    `creator` VARCHAR(64) DEFAULT '' COMMENT 'Creator',
    `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Create Time',
    `updater` VARCHAR(64) DEFAULT '' COMMENT 'Updater',
    `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `deleted` BIT DEFAULT 0 COMMENT 'Is Deleted',
    `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'Tenant ID',
    PRIMARY KEY (`id`),
    KEY `idx_biz_type` (`biz_type`),
    KEY `idx_role_id` (`role_id`),
    KEY `idx_post_id` (`post_id`),
    KEY `idx_dept_id` (`dept_id`),
    KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Data Scope Permission Table';

3.3 Data Scope Type Description

Scope TypeDescriptionSQL Filter Logic
ALLAll dataNo filter condition
SELFOwn dataowner_user_id = current_user
DEPTDept datadept_id IN (current_dept)
DEPT_AND_CHILDDept and sub-dept datadept_id IN (current_dept_and_children)
CUSTOMCustom scopeGenerate filter condition based on scope_value

3.4 Customer Level Filtering Description

Level ConfigDescriptionSQL Filter Logic
NULLNo customer level restrictionNo filter condition
0Level 0 onlylevel = 0
1<= Level 1level <= 1
2<= Level 2level <= 2

4. Permission Engine Core Implementation

@Service
public class CrmPermissionEngineServiceImpl implements CrmPermissionEngineService {

    @Override
    public boolean hasBizOperationPermission(Long userId, Integer bizType, String operationType) {
        // Check by priority high to low: User → Role → Post → Dept
        if (hasUserPermission(userId, bizType, operationType)) return true;
        if (hasRolePermission(userId, bizType, operationType)) return true;
        if (hasPostPermission(userId, bizType, operationType)) return true;
        if (hasDeptPermission(userId, bizType, operationType)) return true;
        return false;
    }

    @Override
    public String buildDataScopeSql(Long userId, Integer bizType) {
        DataScopePermissionDO scope = getDataScope(userId, bizType);
        if (scope == null) return "1=0";

        StringBuilder sqlBuilder = new StringBuilder();

        // Data scope filtering
        switch (scope.getScopeType()) {
            case "ALL": break;
            case "SELF": sqlBuilder.append("owner_user_id = ").append(userId); break;
            case "DEPT": sqlBuilder.append("dept_id = ").append(getUserDeptId(userId)); break;
            case "DEPT_AND_CHILD": sqlBuilder.append("dept_id IN (").append(getUserDeptAndChildIds(userId)).append(")"); break;
            case "CUSTOM": sqlBuilder.append(scope.getScopeValue()); break;
            default: return "1=0";
        }

        // Customer level filtering (AND stacking)
        Integer maxLevel = scope.getMaxCustomerLevel();
        if (maxLevel != null) {
            if (sqlBuilder.length() > 0) sqlBuilder.append(" AND ");
            sqlBuilder.append("level <= ").append(maxLevel);
        }

        return sqlBuilder.toString();
    }
}

5. MyBatis Data Permission Interceptor

@Component
@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class CrmDataPermissionInterceptor implements Interceptor {

    // Tables that need cascade customer level filtering via customer_id
    private static final Set<String> CASCADE_CUSTOMER_LEVEL_TABLES = Set.of(
        "crm_business", "crm_contract", "crm_contact",
        "crm_receivable", "crm_receivable_plan"
    );

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. Determine if data permission filtering is needed
        // 2. Get current user ID
        // 3. Parse business type
        // 4. Build data permission filter conditions
        // 5. Modify SQL, add WHERE filter conditions
        // 6. Execute query
    }
}

Cascade Association Relationships

graph TB subgraph "Customer Level Cascade" CUSTOMER["Customer Table
crm_customer
level field"] -->|customer_id| CONTACT["Contact
crm_contact"] CUSTOMER -->|customer_id| CONTRACT["Contract
crm_contract"] CUSTOMER -->|customer_id| RECEIVABLE["Receivable
crm_receivable"] CUSTOMER -->|customer_id| RECEIVABLE_PLAN["Receivable Plan
crm_receivable_plan"] CUSTOMER -->|customer_id| BUSINESS["Opportunity
crm_business"] end CLUE["Lead
crm_clue"] -.->|No Cascade| CUSTOMER
Business ObjectAssociation FieldCascade?Description
Customerlevel❌ SourceDirectly apply level condition
Contactcustomer_id✅ YesInherit level via customer ID association
Contractcustomer_id✅ YesInherit level via customer ID association
Receivablecustomer_id✅ YesInherit level via customer ID association
Opportunitycustomer_id✅ YesInherit level via customer ID association
Lead-❌ NoLeads are not associated with customers

6. Frontend Permission Configuration Interface

graph TB subgraph "CRM Permission Management" subgraph "Permission Level Switch" A1["Dept Permission"] A2["Post Permission"] A3["Role Permission"] A4["User Permission"] end subgraph "Business Object Selection" B1["Customer"] B2["Opportunity"] B3["Contract"] B4["Contact"] B5["Lead"] B6["Receivable"] end subgraph "Operation Permission Config" C1["Create"] C2["View"] C3["Edit"] C4["Delete"] C5["Transfer"] C6["Export"] end subgraph "Data Scope Config" D1["All Data"] D2["Own Data"] D3["Dept Data"] D4["Dept & Sub-Dept"] D5["Custom Scope"] end subgraph "Customer Level Filter" E1["All Levels"] E2["Level 0"] E3["<= Level 1"] E4["<= Level 2"] end end

7. Implementation Progress

PhaseTaskStatus
Phase 1Database table structure design & creation✅ Completed
Phase 2Backend entity classes & Mapper✅ Completed
Phase 3Permission engine service implementation✅ Completed
Phase 4MyBatis data permission interceptor✅ Completed
Phase 5API interface implementation✅ Completed
Phase 6Frontend page development✅ Completed
Phase 7Customer level cascade filtering✅ Completed
Phase 8Testing & verification✅ Completed

Summary

This solution implements a four-layer data permission filtering mechanism based on Department-Post-Role-User:

  1. Operation Permission: Managed via crm_biz_object_permission table, four-level priority checking
  2. Data Scope: Supports five scope types: ALL/SELF/DEPT/DEPT_AND_CHILD/CUSTOM
  3. Customer Level Filtering: max_customer_level field, supports level range stacking filter
  4. Cascade Inheritance: Contracts, contacts, etc. automatically inherit customer level restrictions via customer_id
  5. SQL Interceptor: Auto-injects WHERE conditions without modifying existing business code
  6. Frontend Configuration: Unified permission configuration interface with level switching and level configuration
docs