一种用工具类简易实现字段级别权限控制的方案

704 阅读3分钟

需求:在更新数据时,如果用户没有权限修改某个字段,则保留原数据的字段值;如果有权限,则更新为新数据。以下是实现方案:


整体流程

  1. 查询原始数据:从数据库中获取原始实体对象。
  2. 检查字段权限
    • 遍历需要更新的字段。
    • 如果用户没有权限修改某个字段,则保留原始数据的字段值。
    • 如果用户有权限,则更新为新数据的字段值。
  3. 执行业务逻辑:将处理后的数据传递给Service层,执行后续的业务逻辑。
  4. 更新数据:将最终的数据更新到数据库中。

代码实现

1. 定义字段权限注解

用于标记哪些字段需要权限控制。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
    String[] roles() default {}; // 允许的角色
    String[] permissions() default {}; // 允许的权限(如 WRITE)
}

2. 在实体类中标记字段权限

在实体类中使用注解标记需要权限控制的字段。

public class User {
    @DataPermission(roles = {"ADMIN"}, permissions = {"WRITE"})
    private String username;

    @DataPermission(roles = {"ADMIN", "MANAGER"}, permissions = {"WRITE"})
    private String email;

    // 其他字段
}

3. 权限检查与字段更新逻辑

编写一个工具类,用于检查字段权限并更新数据。

public class DataPermissionUtil {

    /**
     * 检查字段权限并更新数据
     *
     * @param original 原始数据
     * @param updated  更新数据
     * @param userRole 用户角色
     * @param userPermissions 用户权限集合
     * @param <T> 实体类型
     * @return 处理后的实体
     */
    public static <T> T checkAndUpdateFields(T original, T updated, String userRole, Set<String> userPermissions) {
        Class<?> clazz = original.getClass();
        try {
            for (Field field : clazz.getDeclaredFields()) {
                DataPermission annotation = field.getAnnotation(DataPermission.class);
                if (annotation != null) {
                    boolean hasRole = Arrays.asList(annotation.roles()).contains(userRole);
                    boolean hasPermission = Arrays.stream(annotation.permissions())
                            .anyMatch(userPermissions::contains);

                    field.setAccessible(true);
                    Object updatedValue = field.get(updated);

                    // 如果用户没有权限修改该字段,则保留原始值
                    if (!hasRole || !hasPermission) {
                        field.set(updated, field.get(original));
                    } else if (updatedValue != null) {
                        // 如果有权限且新值不为空,则更新为新值
                        field.set(updated, updatedValue);
                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Failed to update fields due to permission check", e);
        }
        return updated;
    }
}

4. 在Service层调用权限检查逻辑

在Service层中,调用权限检查工具类,确保数据更新符合权限要求。

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserContext userContext; // 获取当前用户信息

    public void updateUser(User updatedUser) {
        // 1. 查询原始数据
        User originalUser = userRepository.findById(updatedUser.getId());

        // 2. 检查字段权限并更新数据
        User finalUser = DataPermissionUtil.checkAndUpdateFields(
                originalUser,
                updatedUser,
                userContext.getCurrentUserRole(),
                userContext.getCurrentUserPermissions()
        );

        // 3. 执行业务逻辑(如果有)
        // 例如:验证数据、触发事件等

        // 4. 更新数据到数据库
        userRepository.update(finalUser);
    }
}

5. 集成到Controller层

在Controller层接收前端传入的更新数据,并调用Service层的方法。

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PutMapping("/{id}")
    public ResponseEntity<String> updateUser(@PathVariable Long id, @RequestBody User updatedUser) {
        updatedUser.setId(id); // 确保ID一致
        userService.updateUser(updatedUser);
        return ResponseEntity.ok("User updated successfully");
    }
}

流程总结

  1. 前端传入更新数据:通过HTTP请求传入需要更新的字段。
  2. 查询原始数据:从数据库中获取原始实体对象。
  3. 权限检查与字段更新
    • 遍历字段,检查用户是否有权限修改。
    • 如果没有权限,则保留原始值;如果有权限,则更新为新值。
  4. 执行业务逻辑:在Service层中执行额外的业务逻辑(如数据验证、触发事件等)。
  5. 更新数据库:将处理后的数据保存到数据库中。

示例场景

假设:

  • 原始数据:{ "username": "admin", "email": "admin@example.com" }
  • 更新数据:{ "username": "new_admin", "email": "new_admin@example.com" }
  • 用户角色:MANAGER
  • 权限配置:
    • username:仅ADMIN可写。
    • emailADMINMANAGER可写。

处理结果

  • username:用户没有权限修改,保留原始值 "admin"
  • email:用户有权限修改,更新为 "new_admin@example.com"

最终更新到数据库的数据为:{ "username": "admin", "email": "new_admin@example.com" }


优化建议

  1. 缓存原始数据:如果查询原始数据的开销较大,可以考虑缓存原始数据。
  2. 批量更新支持:如果需要支持批量更新,可以扩展工具类以处理多个实体。
  3. 日志记录:记录权限检查的日志,便于审计和排查问题。
  4. 单元测试:编写单元测试,覆盖各种权限场景。

希望这个方案能够满足你的需求!如果有进一步的问题,欢迎随时讨论。