You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shenyu.apache.org by xi...@apache.org on 2022/06/15 08:30:26 UTC

[incubator-shenyu] branch master updated: [ISSUE #3221] record role management logging (#3560)

This is an automated email from the ASF dual-hosted git repository.

xiaoyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new ac0a1342f [ISSUE #3221] record role management logging (#3560)
ac0a1342f is described below

commit ac0a1342fd4625de8f986a9d3d2f03242bfcee8e
Author: likeguo <33...@users.noreply.github.com>
AuthorDate: Wed Jun 15 16:30:21 2022 +0800

    [ISSUE #3221] record role management logging (#3560)
    
    * fixbug/pg script error
    
    * feature/record log in role
    
    * feature/record log in role
    
    * feature/record log in role
    
    * feature/record log in role
    
    * feature/record log in role
    
    * feature/record log in role
---
 .../org/apache/shenyu/admin/mapper/RoleMapper.java |   8 +
 .../shenyu/admin/model/enums/EventTypeEnum.java    |  18 +-
 .../model/event/role/BatchRoleDeletedEvent.java    |  80 +++++++++
 .../admin/model/event/role/RoleChangedEvent.java   |  76 +++++++++
 .../admin/model/event/role/RoleCreatedEvent.java   |  48 ++++++
 .../admin/model/event/role/RoleUpdatedEvent.java   |  62 +++++++
 .../apache/shenyu/admin/service/RoleService.java   |  33 +++-
 .../admin/service/impl/PermissionServiceImpl.java  |  95 +++++++++++
 .../shenyu/admin/service/impl/RoleServiceImpl.java | 183 ++++++++-------------
 .../admin/service/publish/RoleEventPublisher.java  |  88 ++++++++++
 .../src/main/resources/mappers/role-sqlmap.xml     |   9 +
 .../shenyu/admin/service/RoleServiceTest.java      |  36 ++--
 12 files changed, 594 insertions(+), 142 deletions(-)

diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/RoleMapper.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/RoleMapper.java
index 8d4b14829..2a2da629d 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/RoleMapper.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/RoleMapper.java
@@ -119,4 +119,12 @@ public interface RoleMapper extends ExistProvider {
      * @return {@linkplain List}
      */
     List<RoleDO> selectAll();
+    
+    /**
+     * select by ids.
+     *
+     * @param ids ids.
+     * @return list
+     */
+    List<RoleDO> selectByIds(@Param("ids") List<String> ids);
 }
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/enums/EventTypeEnum.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/enums/EventTypeEnum.java
index ce756ecad..e1026e2d4 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/enums/EventTypeEnum.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/enums/EventTypeEnum.java
@@ -70,6 +70,12 @@ public enum EventTypeEnum {
      */
     DICT_CREATE("CREATE:DICT", DataEventTypeEnum.CREATE, Color.CREATE_COLOR),
     
+    
+    /**
+     * role created event.
+     */
+    ROLE_CREATE("CREATE:ROLE", DataEventTypeEnum.CREATE, Color.CREATE_COLOR),
+    
     // ============== delete ===================
     /**
      * deleted event.
@@ -117,6 +123,11 @@ public enum EventTypeEnum {
      */
     DICT_DELETE("DELETE:DICT", DataEventTypeEnum.DELETE, Color.DELETE_COLOR),
     
+    /**
+     * role deleted event.
+     */
+    ROLE_DELETE("DELETE:ROLE", DataEventTypeEnum.DELETE, Color.DELETE_COLOR),
+    
     // ============== update ===================
     
     /**
@@ -157,7 +168,12 @@ public enum EventTypeEnum {
     /**
      * dict update.
      */
-    DICT_UPDATE("UPDATE:DICT", DataEventTypeEnum.UPDATE, Color.UPDATE_COLOR);
+    DICT_UPDATE("UPDATE:DICT", DataEventTypeEnum.UPDATE, Color.UPDATE_COLOR),
+    
+    /**
+     * role update.
+     */
+    ROLE_UPDATE("UPDATE:ROLE", DataEventTypeEnum.UPDATE, Color.UPDATE_COLOR);
     
     /**
      * type name.
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/BatchRoleDeletedEvent.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/BatchRoleDeletedEvent.java
new file mode 100644
index 000000000..41aa51ece
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/BatchRoleDeletedEvent.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.model.event.role;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.model.entity.BaseDO;
+import org.apache.shenyu.admin.model.entity.RoleDO;
+import org.apache.shenyu.admin.model.enums.EventTypeEnum;
+import org.apache.shenyu.admin.model.event.BatchChangedEvent;
+import org.apache.shenyu.admin.utils.ListUtil;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * BatchRuleDeletedEvent.
+ */
+public class BatchRoleDeletedEvent extends BatchChangedEvent {
+    
+    private final List<String> deletedIds;
+    
+    /**
+     * Create a new {@code BatchChangedEvent}.operator is unknown.
+     *
+     * @param source   Current plugin state
+     * @param operator operator
+     */
+    public BatchRoleDeletedEvent(final Collection<RoleDO> source, final String operator) {
+        super(source, null, EventTypeEnum.ROLE_DELETE, operator);
+        this.deletedIds = ListUtil.map(source, BaseDO::getId);
+    }
+    
+    @Override
+    public String buildContext() {
+        final String selector = ((Collection<?>) getSource())
+                .stream()
+                .map(s -> ((RoleDO) s).getRoleName())
+                .collect(Collectors.joining(","));
+        return String.format("the role[%s] is %s", selector, StringUtils.lowerCase(getType().getType().toString()));
+    }
+    
+    /**
+     * get roles.
+     *
+     * @return list
+     */
+    public List<RoleDO> getRoles() {
+        return ((Collection<?>) getSource())
+                .stream()
+                .map(RoleDO.class::cast)
+                .collect(Collectors.toList());
+    }
+    
+
+    
+    /**
+     * get deleted ids.
+     *
+     * @return ids.
+     */
+    public List<String> getDeletedIds() {
+        return deletedIds;
+    }
+}
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleChangedEvent.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleChangedEvent.java
new file mode 100644
index 000000000..965b1ad84
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleChangedEvent.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.model.event.role;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.model.entity.RoleDO;
+import org.apache.shenyu.admin.model.enums.EventTypeEnum;
+import org.apache.shenyu.admin.model.event.AdminDataModelChangedEvent;
+
+import java.util.Objects;
+
+/**
+ * RoleChangedEvent.
+ */
+public class RoleChangedEvent extends AdminDataModelChangedEvent {
+    
+    
+    /**
+     * Create a new {@code RoleChangedEvent}.operator is unknown.
+     *
+     * @param source Current role state
+     * @param before Before the change role state
+     * @param type   event type
+     */
+    public RoleChangedEvent(final RoleDO source, final RoleDO before, final EventTypeEnum type, final String operator) {
+        super(source, before, type, operator);
+    }
+    
+    @Override
+    public String buildContext() {
+        final RoleDO after = (RoleDO) getAfter();
+        if (Objects.isNull(getBefore())) {
+            return String.format("the role [%s] is %s", after.getRoleName(), StringUtils.lowerCase(getType().getType().toString()));
+        }
+        return String.format("the role [%s] is %s : %s", after.getRoleName(), StringUtils.lowerCase(getType().getType().toString()), contrast());
+        
+    }
+    
+    private String contrast() {
+        final RoleDO before = (RoleDO) getBefore();
+        Objects.requireNonNull(before);
+        final RoleDO after = (RoleDO) getAfter();
+        Objects.requireNonNull(after);
+        if (Objects.equals(before, after)) {
+            return "it no change";
+        }
+        final StringBuilder builder = new StringBuilder();
+        if (!Objects.equals(before.getRoleName(), after.getRoleName())) {
+            builder.append(String.format("name[%s => %s] ", before.getRoleName(), after.getRoleName()));
+        }
+        if (!Objects.equals(before.getDescription(), after.getDescription())) {
+            builder.append(String.format("disc[%s => %s] ", before.getDescription(), after.getDescription()));
+        }
+        return builder.toString();
+    }
+    
+    @Override
+    public String eventName() {
+        return "role";
+    }
+}
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleCreatedEvent.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleCreatedEvent.java
new file mode 100644
index 000000000..a5ed2727b
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleCreatedEvent.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.model.event.role;
+
+import org.apache.shenyu.admin.model.entity.RoleDO;
+import org.apache.shenyu.admin.model.enums.EventTypeEnum;
+
+/**
+ * RuleCreatedEvent.
+ */
+public class RoleCreatedEvent extends RoleChangedEvent {
+    
+    
+    /**
+     * Create a new {@code RoleCreatedEvent}.operator is unknown.
+     *
+     * @param source   Current plugin state
+     * @param operator operator
+     */
+    public RoleCreatedEvent(final RoleDO source, final String operator) {
+        super(source, null, EventTypeEnum.ROLE_CREATE, operator);
+    }
+    
+    /**
+     * the created role.
+     *
+     * @return role
+     */
+    public RoleDO getRole() {
+        return (RoleDO) getSource();
+    }
+    
+}
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleUpdatedEvent.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleUpdatedEvent.java
new file mode 100644
index 000000000..76ab10d05
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/event/role/RoleUpdatedEvent.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.model.event.role;
+
+import org.apache.shenyu.admin.model.entity.RoleDO;
+import org.apache.shenyu.admin.model.enums.EventTypeEnum;
+
+import java.util.List;
+
+/**
+ * RoleUpdatedEvent.
+ */
+public class RoleUpdatedEvent extends RoleChangedEvent {
+    
+    private final List<String> newPermission;
+    
+    /**
+     * Create a new {@code RoleUpdatedEvent}.operator is unknown.
+     *
+     * @param source   Current role state
+     * @param before   before role state
+     * @param operator operator
+     */
+    public RoleUpdatedEvent(final RoleDO source, final RoleDO before, final String operator, final List<String> newPermission) {
+        super(source, before, EventTypeEnum.ROLE_UPDATE, operator);
+        this.newPermission = newPermission;
+    }
+    
+    /**
+     * the created role.
+     *
+     * @return role
+     */
+    public RoleDO getRole() {
+        return (RoleDO) getSource();
+    }
+    
+    /**
+     * get new permission.
+     *
+     * @return permission.
+     */
+    public List<String> getNewPermission() {
+        return newPermission;
+    }
+    
+}
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/RoleService.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/RoleService.java
index b4b434f4a..cdc33640e 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/RoleService.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/RoleService.java
@@ -17,6 +17,7 @@
 
 package org.apache.shenyu.admin.service;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.shenyu.admin.model.dto.RoleDTO;
 import org.apache.shenyu.admin.model.page.CommonPager;
 import org.apache.shenyu.admin.model.query.RoleQuery;
@@ -29,15 +30,33 @@ import java.util.List;
  * this is role service.
  */
 public interface RoleService {
-
+    
     /**
      * create or update rule.
      *
      * @param roleDTO {@linkplain RoleDTO}
      * @return rows int
      */
-    int createOrUpdate(RoleDTO roleDTO);
-
+    default int createOrUpdate(RoleDTO roleDTO) {
+        return StringUtils.isBlank(roleDTO.getId()) ? create(roleDTO) : update(roleDTO);
+    }
+    
+    /**
+     * create or update rule.
+     *
+     * @param roleDTO {@linkplain RoleDTO}
+     * @return rows int
+     */
+    int create(RoleDTO roleDTO);
+    
+    /**
+     * create or update rule.
+     *
+     * @param roleDTO {@linkplain RoleDTO}
+     * @return rows int
+     */
+    int update(RoleDTO roleDTO);
+    
     /**
      * delete roles.
      *
@@ -45,7 +64,7 @@ public interface RoleService {
      * @return rows int
      */
     int delete(List<String> ids);
-
+    
     /**
      * find role by id.
      *
@@ -53,7 +72,7 @@ public interface RoleService {
      * @return {@linkplain RoleEditVO}
      */
     RoleEditVO findById(String id);
-
+    
     /**
      * find role by roleName.
      *
@@ -61,7 +80,7 @@ public interface RoleService {
      * @return {@linkplain RoleVO}
      */
     RoleVO findByQuery(String roleName);
-
+    
     /**
      * find page of role by query.
      *
@@ -69,7 +88,7 @@ public interface RoleService {
      * @return {@linkplain CommonPager}
      */
     CommonPager<RoleVO> listByPage(RoleQuery roleQuery);
-
+    
     /**
      * select all roles not super.
      *
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PermissionServiceImpl.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PermissionServiceImpl.java
index 04fb530f7..c6e4fad6d 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PermissionServiceImpl.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/PermissionServiceImpl.java
@@ -30,6 +30,9 @@ import org.apache.shenyu.admin.model.entity.UserRoleDO;
 import org.apache.shenyu.admin.model.event.resource.BatchResourceCreatedEvent;
 import org.apache.shenyu.admin.model.event.resource.BatchResourceDeletedEvent;
 import org.apache.shenyu.admin.model.event.resource.ResourceCreatedEvent;
+import org.apache.shenyu.admin.model.event.role.BatchRoleDeletedEvent;
+import org.apache.shenyu.admin.model.event.role.RoleUpdatedEvent;
+import org.apache.shenyu.admin.model.query.PermissionQuery;
 import org.apache.shenyu.admin.model.vo.PermissionMenuVO;
 import org.apache.shenyu.admin.model.vo.PermissionMenuVO.AuthPerm;
 import org.apache.shenyu.admin.model.vo.ResourceVO;
@@ -44,6 +47,7 @@ import org.springframework.stereotype.Service;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -139,6 +143,26 @@ public class PermissionServiceImpl implements PermissionService {
         permissionMapper.deleteByResourceId(event.getDeletedIds());
     }
     
+    /**
+     * listen {@link BatchRoleDeletedEvent} delete  permission.
+     *
+     * @param event event
+     */
+    @EventListener(BatchRoleDeletedEvent.class)
+    public void onRoleDeleted(final BatchRoleDeletedEvent event) {
+        permissionMapper.deleteByObjectIds(event.getDeletedIds());
+    }
+    
+    /**
+     * listen {@link RoleUpdatedEvent} delete  permission.
+     *
+     * @param event event
+     */
+    @EventListener(RoleUpdatedEvent.class)
+    public void onRoleUpdated(final RoleUpdatedEvent event) {
+        manageRolePermission(event.getRole().getId(), event.getNewPermission());
+    }
+    
     /**
      * get resource by username.
      *
@@ -191,4 +215,75 @@ public class PermissionServiceImpl implements PermissionService {
                 .map(item -> AuthPerm.buildAuthPerm(ResourceVO.buildResourceVO(item)))
                 .collect(Collectors.toList());
     }
+    
+    
+    /**
+     * manger role permission.
+     *
+     * @param roleId                role id.
+     * @param currentPermissionList {@linkplain List} current role permission ids
+     */
+    private void manageRolePermission(final String roleId, final List<String> currentPermissionList) {
+        List<String> lastPermissionList = permissionMapper.findByObjectId(roleId)
+                .stream()
+                .map(PermissionDO::getResourceId)
+                .collect(Collectors.toList());
+        List<String> addPermission = getListDiff(lastPermissionList, currentPermissionList);
+        if (CollectionUtils.isNotEmpty(addPermission)) {
+            batchSavePermission(addPermission.stream()
+                    .map(node -> PermissionDO.buildPermissionDO(PermissionDTO
+                            .builder()
+                            .objectId(roleId)
+                            .resourceId(node)
+                            .build()))
+                    .collect(Collectors.toList()));
+        }
+        
+        List<String> deletePermission = getListDiff(currentPermissionList, lastPermissionList);
+        if (CollectionUtils.isNotEmpty(deletePermission)) {
+            deletePermission.forEach(node -> deleteByObjectIdAndResourceId(new PermissionQuery(roleId, node)));
+        }
+    }
+    
+    /**
+     * get two list different.
+     *
+     * @param preList  {@linkplain List}
+     * @param lastList {@linkplain List}
+     * @return {@linkplain List}
+     */
+    private List<String> getListDiff(final List<String> preList, final List<String> lastList) {
+        if (CollectionUtils.isEmpty(lastList)) {
+            return null;
+        }
+        
+        if (CollectionUtils.isEmpty(preList)) {
+            return lastList;
+        }
+        
+        Map<String, Integer> map = preList.stream()
+                .distinct()
+                .collect(Collectors.toMap(source -> source, source -> 1));
+        return lastList.stream()
+                .filter(item -> !map.containsKey(item))
+                .collect(Collectors.toList());
+    }
+    
+    /**
+     * batch save permission.
+     *
+     * @param permissionDOList {@linkplain List}
+     */
+    private void batchSavePermission(final List<PermissionDO> permissionDOList) {
+        permissionDOList.forEach(permissionMapper::insertSelective);
+    }
+    
+    /**
+     * delete by object and resource id.
+     *
+     * @param permissionQuery permission query
+     */
+    private void deleteByObjectIdAndResourceId(final PermissionQuery permissionQuery) {
+        permissionMapper.deleteByObjectIdAndResourceId(permissionQuery);
+    }
 }
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RoleServiceImpl.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RoleServiceImpl.java
index 8b2cf5350..768bf2774 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RoleServiceImpl.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/RoleServiceImpl.java
@@ -23,14 +23,12 @@ import org.apache.shenyu.admin.aspect.annotation.Pageable;
 import org.apache.shenyu.admin.mapper.PermissionMapper;
 import org.apache.shenyu.admin.mapper.ResourceMapper;
 import org.apache.shenyu.admin.mapper.RoleMapper;
-import org.apache.shenyu.admin.model.dto.PermissionDTO;
 import org.apache.shenyu.admin.model.dto.ResourceDTO;
 import org.apache.shenyu.admin.model.dto.RoleDTO;
 import org.apache.shenyu.admin.model.entity.PermissionDO;
 import org.apache.shenyu.admin.model.entity.RoleDO;
 import org.apache.shenyu.admin.model.page.CommonPager;
 import org.apache.shenyu.admin.model.page.PageResultUtils;
-import org.apache.shenyu.admin.model.query.PermissionQuery;
 import org.apache.shenyu.admin.model.query.RoleQuery;
 import org.apache.shenyu.admin.model.vo.ResourceVO;
 import org.apache.shenyu.admin.model.vo.RoleEditVO;
@@ -38,10 +36,13 @@ import org.apache.shenyu.admin.model.vo.RoleEditVO.PermissionInfo;
 import org.apache.shenyu.admin.model.vo.RoleEditVO.ResourceInfo;
 import org.apache.shenyu.admin.model.vo.RoleVO;
 import org.apache.shenyu.admin.service.RoleService;
+import org.apache.shenyu.admin.service.publish.RoleEventPublisher;
+import org.apache.shenyu.admin.utils.ListUtil;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -57,21 +58,25 @@ import java.util.stream.Collectors;
  */
 @Service
 public class RoleServiceImpl implements RoleService {
-
+    
     private final RoleMapper roleMapper;
-
+    
     private final PermissionMapper permissionMapper;
-
+    
     private final ResourceMapper resourceMapper;
-
+    
+    private final RoleEventPublisher roleEventPublisher;
+    
     public RoleServiceImpl(final RoleMapper roleMapper,
                            final PermissionMapper permissionMapper,
-                           final ResourceMapper resourceMapper) {
+                           final ResourceMapper resourceMapper,
+                           final RoleEventPublisher roleEventPublisher) {
         this.roleMapper = roleMapper;
         this.permissionMapper = permissionMapper;
         this.resourceMapper = resourceMapper;
+        this.roleEventPublisher = roleEventPublisher;
     }
-
+    
     /**
      * create or update role info.
      *
@@ -81,15 +86,31 @@ public class RoleServiceImpl implements RoleService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int createOrUpdate(final RoleDTO roleDTO) {
-        RoleDO roleDO = RoleDO.buildRoleDO(roleDTO);
-        if (StringUtils.isEmpty(roleDTO.getId())) {
-            return roleMapper.insertSelective(roleDO);
-        } else {
-            manageRolePermission(roleDTO.getId(), roleDTO.getCurrentPermissionIds());
-            return roleMapper.updateSelective(roleDO);
+        return RoleService.super.createOrUpdate(roleDTO);
+    }
+    
+    @Override
+    public int create(final RoleDTO roleDTO) {
+        final RoleDO role = RoleDO.buildRoleDO(roleDTO);
+        final int insertCount = roleMapper.insertSelective(role);
+        if (insertCount > 0) {
+            roleEventPublisher.onCreated(role);
         }
+        return insertCount;
     }
-
+    
+    @Override
+    public int update(final RoleDTO roleDTO) {
+        final RoleDO before = roleMapper.selectById(roleDTO.getId());
+        final RoleDO role = RoleDO.buildRoleDO(roleDTO);
+        final int updateCount = roleMapper.updateSelective(role);
+        if (updateCount > 0) {
+            // add new permission
+            roleEventPublisher.onUpdated(role, before, roleDTO.getCurrentPermissionIds());
+        }
+        return updateCount;
+    }
+    
     /**
      * delete role info.
      *
@@ -98,10 +119,14 @@ public class RoleServiceImpl implements RoleService {
      */
     @Override
     public int delete(final List<String> ids) {
-        permissionMapper.deleteByObjectIds(ids);
-        return roleMapper.delete(ids);
+        final List<RoleDO> roles = roleMapper.selectByIds(ids);
+        final int deleteCount = roleMapper.delete(ids);
+        if (deleteCount > 0) {
+            roleEventPublisher.onDeleted(roles);
+        }
+        return deleteCount;
     }
-
+    
     /**
      * find role info by id.
      *
@@ -115,7 +140,7 @@ public class RoleServiceImpl implements RoleService {
                 .map(item -> new RoleEditVO(getPermissionIdsByRoleId(item.getId()), item, getAllPermissions()))
                 .orElse(null);
     }
-
+    
     /**
      * find role by query.
      *
@@ -126,7 +151,7 @@ public class RoleServiceImpl implements RoleService {
     public RoleVO findByQuery(final String roleName) {
         return RoleVO.buildRoleVO(roleMapper.findByRoleName(roleName));
     }
-
+    
     /**
      * find page of role by query.
      *
@@ -141,7 +166,7 @@ public class RoleServiceImpl implements RoleService {
                 .map(RoleVO::buildRoleVO)
                 .collect(Collectors.toList()));
     }
-
+    
     /**
      * select all roles.
      *
@@ -149,28 +174,22 @@ public class RoleServiceImpl implements RoleService {
      */
     @Override
     public List<RoleVO> selectAll() {
-        return roleMapper.selectAll()
-                .stream()
-                .map(RoleVO::buildRoleVO)
-                .collect(Collectors.toList());
+        return ListUtil.map(roleMapper.selectAll(), RoleVO::buildRoleVO);
     }
-
+    
     /**
      * get all permissions.
      *
      * @return {@linkplain PermissionInfo}
      */
     private PermissionInfo getAllPermissions() {
-        List<ResourceVO> resourceVOList = resourceMapper.selectAll()
-                .stream()
-                .map(ResourceVO::buildResourceVO)
-                .collect(Collectors.toList());
-        List<String> permissionIds = resourceVOList.stream().map(ResourceVO::getId).collect(Collectors.toList());
-
-        List<ResourceInfo> treeList = getTreeModelList(resourceVOList);
-        return PermissionInfo.builder().treeList(treeList).permissionIds(permissionIds).build();
+        final List<ResourceVO> resourceVOList = ListUtil.map(resourceMapper.selectAll(), ResourceVO::buildResourceVO);
+        return PermissionInfo.builder()
+                .treeList(getTreeModelList(resourceVOList))
+                .permissionIds(ListUtil.map(resourceVOList, ResourceVO::getId))
+                .build();
     }
-
+    
     /**
      * get permission ids by role id.
      *
@@ -178,12 +197,9 @@ public class RoleServiceImpl implements RoleService {
      * @return {@linkplain List}
      */
     private List<String> getPermissionIdsByRoleId(final String roleId) {
-        return permissionMapper.findByObjectId(roleId)
-                .stream()
-                .map(PermissionDO::getResourceId)
-                .collect(Collectors.toList());
+        return ListUtil.map(permissionMapper.findByObjectId(roleId), PermissionDO::getResourceId);
     }
-
+    
     /**
      * get menu list.
      *
@@ -191,33 +207,21 @@ public class RoleServiceImpl implements RoleService {
      * @return list of {@linkplain ResourceInfo}
      */
     private List<ResourceInfo> getTreeModelList(final List<ResourceVO> metaList) {
-
-        List<ResourceInfo> retList = new ArrayList<>();
+        final List<ResourceInfo> retList = new ArrayList<>();
         if (CollectionUtils.isEmpty(metaList)) {
             return retList;
         }
-        Map<String, ResourceInfo> resourceInfoMap = metaList.stream().map(ResourceInfo::buildResourceInfo)
+        final Map<String, ResourceInfo> resourceInfoMap = metaList.stream()
+                .map(ResourceInfo::buildResourceInfo)
                 .filter(resourceInfo -> Objects.nonNull(resourceInfo) && StringUtils.isNotEmpty(resourceInfo.getId()))
                 .collect(Collectors.toMap(ResourceInfo::getId, Function.identity(), (value1, value2) -> value1));
-        Map<String, Set<String>> metaChildrenMap = metaList.stream().filter(meta -> Objects.nonNull(meta) && StringUtils.isNotEmpty(meta.getId()))
-                .collect(Collectors.toMap(ResourceVO::getParentId,
-                    resourceVO -> {
-                        Set<String> idSet = new LinkedHashSet<>();
-                        idSet.add(resourceVO.getId());
-                        return idSet;
-                    }, (set1, set2) -> {
-                        set1.addAll(set2);
-                        return set1;
-                    }, LinkedHashMap::new));
+        final Map<String, Set<String>> metaChildrenMap = metaList.stream()
+                .filter(meta -> Objects.nonNull(meta) && StringUtils.isNotEmpty(meta.getId()))
+                .collect(Collectors.toMap(ResourceVO::getParentId, resourceVO -> new LinkedHashSet<>(Collections.singletonList(resourceVO.getId())), ListUtil::mergeSet, LinkedHashMap::new));
         metaChildrenMap.forEach((parent, children) -> {
-            ResourceInfo resourceInfo = resourceInfoMap.get(parent);
             if (CollectionUtils.isNotEmpty(children)) {
-                List<ResourceInfo> targetList;
-                if (Objects.isNull(resourceInfo)) {
-                    targetList = retList;
-                } else {
-                    targetList = resourceInfo.getChildren();
-                }
+                ResourceInfo resourceInfo = resourceInfoMap.get(parent);
+                List<ResourceInfo> targetList = Objects.isNull(resourceInfo) ? retList : resourceInfo.getChildren();
                 children.forEach(child -> {
                     ResourceInfo data = resourceInfoMap.get(child);
                     if (Objects.nonNull(data)) {
@@ -228,62 +232,5 @@ public class RoleServiceImpl implements RoleService {
         });
         return retList;
     }
-
-    /**
-     * get two list different.
-     *
-     * @param preList  {@linkplain List}
-     * @param lastList {@linkplain List}
-     * @return {@linkplain List}
-     */
-    private List<String> getListDiff(final List<String> preList, final List<String> lastList) {
-        if (CollectionUtils.isEmpty(lastList)) {
-            return null;
-        }
-
-        if (CollectionUtils.isEmpty(preList)) {
-            return lastList;
-        }
-
-        Map<String, Integer> map = preList.stream().distinct()
-                .collect(Collectors.toMap(source -> source, source -> 1));
-        return lastList.stream().filter(item -> !map.containsKey(item)).collect(Collectors.toList());
-    }
-
-    /**
-     * batch save permission.
-     *
-     * @param permissionDOList {@linkplain List}
-     */
-    private void batchSavePermission(final List<PermissionDO> permissionDOList) {
-        permissionDOList.forEach(permissionMapper::insertSelective);
-    }
-
-    /**
-     * delete by object and resource id.
-     *
-     * @param permissionQuery permission query
-     */
-    private void deleteByObjectIdAndResourceId(final PermissionQuery permissionQuery) {
-        permissionMapper.deleteByObjectIdAndResourceId(permissionQuery);
-    }
-
-    /**
-     * manger role permission.
-     *
-     * @param roleId                role id.
-     * @param currentPermissionList {@linkplain List} current role permission ids
-     */
-    private void manageRolePermission(final String roleId, final List<String> currentPermissionList) {
-        List<String> lastPermissionList = permissionMapper.findByObjectId(roleId).stream().map(PermissionDO::getResourceId).collect(Collectors.toList());
-        List<String> addPermission = getListDiff(lastPermissionList, currentPermissionList);
-        if (CollectionUtils.isNotEmpty(addPermission)) {
-            batchSavePermission(addPermission.stream().map(node -> PermissionDO.buildPermissionDO(PermissionDTO.builder().objectId(roleId).resourceId(node).build())).collect(Collectors.toList()));
-        }
-
-        List<String> deletePermission = getListDiff(currentPermissionList, lastPermissionList);
-        if (CollectionUtils.isNotEmpty(deletePermission)) {
-            deletePermission.forEach(node -> deleteByObjectIdAndResourceId(new PermissionQuery(roleId, node)));
-        }
-    }
+    
 }
diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/publish/RoleEventPublisher.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/publish/RoleEventPublisher.java
new file mode 100644
index 000000000..62b4c4bba
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/publish/RoleEventPublisher.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.service.publish;
+
+import org.apache.shenyu.admin.model.entity.RoleDO;
+import org.apache.shenyu.admin.model.event.AdminDataModelChangedEvent;
+import org.apache.shenyu.admin.model.event.role.BatchRoleDeletedEvent;
+import org.apache.shenyu.admin.model.event.role.RoleCreatedEvent;
+import org.apache.shenyu.admin.model.event.role.RoleUpdatedEvent;
+import org.apache.shenyu.admin.utils.SessionUtil;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * RoleEventPublisher.
+ */
+@Component
+public class RoleEventPublisher implements AdminDataModelChangedEventPublisher<RoleDO> {
+    
+    private final ApplicationEventPublisher publisher;
+    
+    public RoleEventPublisher(final ApplicationEventPublisher publisher) {
+        this.publisher = publisher;
+    }
+    
+    /**
+     * on rule created.
+     *
+     * @param rule rule
+     */
+    @Override
+    public void onCreated(final RoleDO rule) {
+        publish(new RoleCreatedEvent(rule, SessionUtil.visitorName()));
+    }
+    
+    
+    /**
+     * on rule updated.
+     *
+     * @param rule       rule
+     * @param before     before rule
+     * @param permission new permission
+     */
+    public void onUpdated(final RoleDO rule, final RoleDO before, final List<String> permission) {
+        publish(new RoleUpdatedEvent(rule, before, SessionUtil.visitorName(), permission));
+    }
+    
+    
+    /**
+     * role delete.
+     *
+     * @param roles data
+     */
+    @Override
+    public void onDeleted(final Collection<RoleDO> roles) {
+        publish(new BatchRoleDeletedEvent(roles, SessionUtil.visitorName()));
+    }
+    
+    
+    /**
+     * event.
+     *
+     * @param event event.
+     */
+    @Override
+    public void publish(final AdminDataModelChangedEvent event) {
+        publisher.publishEvent(event);
+    }
+    
+}
diff --git a/shenyu-admin/src/main/resources/mappers/role-sqlmap.xml b/shenyu-admin/src/main/resources/mappers/role-sqlmap.xml
index c80bc2d49..15187f547 100644
--- a/shenyu-admin/src/main/resources/mappers/role-sqlmap.xml
+++ b/shenyu-admin/src/main/resources/mappers/role-sqlmap.xml
@@ -82,6 +82,15 @@
          WHERE id = #{id}
          LIMIT 1
     </select>
+    <select id="selectByIds" resultType="org.apache.shenyu.admin.model.entity.RoleDO">
+        select
+            <include refid="Base_Column_List"/>
+        FROM role
+        WHERE id IN
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id, jdbcType=VARCHAR}
+        </foreach>
+    </select>
 
     <insert id="insert" parameterType="org.apache.shenyu.admin.model.entity.RoleDO">
         INSERT INTO role
diff --git a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/RoleServiceTest.java b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/RoleServiceTest.java
index bf2fe7c98..169a5239d 100644
--- a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/RoleServiceTest.java
+++ b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/RoleServiceTest.java
@@ -29,6 +29,7 @@ import org.apache.shenyu.admin.model.query.RoleQuery;
 import org.apache.shenyu.admin.model.vo.RoleEditVO;
 import org.apache.shenyu.admin.model.vo.RoleVO;
 import org.apache.shenyu.admin.service.impl.RoleServiceImpl;
+import org.apache.shenyu.admin.service.publish.RoleEventPublisher;
 import org.apache.shenyu.common.utils.UUIDUtils;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -51,7 +52,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -71,6 +71,9 @@ public class RoleServiceTest {
 
     @Mock
     private PermissionMapper permissionMapper;
+    
+    @Mock
+    private RoleEventPublisher publisher;
 
     @Mock
     private ResourceMapper resourceMapper;
@@ -79,6 +82,7 @@ public class RoleServiceTest {
     public void testCreateOrUpdate() {
         //test save
         given(this.roleMapper.insertSelective(any())).willReturn(1);
+        given(this.roleMapper.insertSelective(any())).willReturn(1);
         RoleDTO roleDTO = buildRoleDTOWithoutId();
         int count = roleService.createOrUpdate(roleDTO);
         assertThat(count, equalTo(1));
@@ -90,18 +94,19 @@ public class RoleServiceTest {
         roleDTO = buildRoleDTO();
         count = roleService.createOrUpdate(roleDTO);
         assertThat(count, equalTo(1));
-        verify(permissionMapper, times(1)).findByObjectId(anyString());
-        verify(permissionMapper, times(0)).deleteByObjectIdAndResourceId(any());
-        verify(permissionMapper, times(0)).insertSelective(any());
+//        verify(permissionMapper, times(1)).findByObjectId(anyString());
+//        verify(permissionMapper, times(0)).deleteByObjectIdAndResourceId(any());
+//        verify(permissionMapper, times(0)).insertSelective(any());
 
         //test update with delete permissions
         reset(permissionMapper, roleMapper);
         given(this.roleMapper.updateSelective(any())).willReturn(1);
-        given(this.permissionMapper.findByObjectId(anyString())).willReturn(Arrays.asList(PermissionDO.builder().resourceId("1").build()));
+        given(this.permissionMapper.findByObjectId(anyString())).willReturn(Collections.singletonList(PermissionDO.builder().resourceId("1").build()));
         count = roleService.createOrUpdate(roleDTO);
-        verify(permissionMapper, times(1)).findByObjectId(anyString());
-        verify(permissionMapper, atLeastOnce()).deleteByObjectIdAndResourceId(any());
-        verify(permissionMapper, times(0)).insertSelective(any());
+        assertThat(count, equalTo(1));
+//        verify(permissionMapper, times(1)).findByObjectId(anyString());
+//        verify(permissionMapper, atLeastOnce()).deleteByObjectIdAndResourceId(any());
+//        verify(permissionMapper, times(0)).insertSelective(any());
 
         //test update with insert permissions
         reset(permissionMapper, roleMapper);
@@ -109,26 +114,25 @@ public class RoleServiceTest {
         roleDTO.setCurrentPermissionIds(Arrays.asList("1", "2"));
         count = roleService.createOrUpdate(roleDTO);
         assertThat(count, equalTo(1));
-        verify(permissionMapper, times(1)).findByObjectId(anyString());
-        verify(permissionMapper, times(0)).deleteByObjectIdAndResourceId(any());
-        verify(permissionMapper, atLeastOnce()).insertSelective(any());
+//        verify(permissionMapper, times(1)).findByObjectId(anyString());
+//        verify(permissionMapper, times(0)).deleteByObjectIdAndResourceId(any());
+//        verify(permissionMapper, atLeastOnce()).insertSelective(any());
 
         //test update with exist difference between existing and current permissions
         reset(permissionMapper, roleMapper);
         given(this.roleMapper.updateSelective(any())).willReturn(1);
-        given(this.permissionMapper.findByObjectId(anyString())).willReturn(Arrays.asList(PermissionDO.builder().resourceId("3").build()));
+        given(this.permissionMapper.findByObjectId(anyString())).willReturn(Collections.singletonList(PermissionDO.builder().resourceId("3").build()));
         count = roleService.createOrUpdate(roleDTO);
         assertThat(count, equalTo(1));
-        verify(permissionMapper, times(1)).findByObjectId(anyString());
-        verify(permissionMapper, atLeastOnce()).deleteByObjectIdAndResourceId(any());
-        verify(permissionMapper, atLeastOnce()).insertSelective(any());
+//        verify(permissionMapper, times(1)).findByObjectId(anyString());
+//        verify(permissionMapper, atLeastOnce()).deleteByObjectIdAndResourceId(any());
+//        verify(permissionMapper, atLeastOnce()).insertSelective(any());
     }
 
     @Test
     public void testDelete() {
         List<String> ids = Arrays.asList("1", "2");
         roleService.delete(ids);
-        verify(permissionMapper, times(1)).deleteByObjectIds(ids);
         verify(roleMapper, times(1)).delete(ids);
     }