ard-admin/src/main/java/com/ard/web/controller/sample/DeviceController.java
@@ -95,9 +95,11 @@ * @return */ @ApiOperation("设备列表") @PreAuthorize("@ss.hasPermi('device:dock:list')") @GetMapping("/list") public ResponseResult<List<DeviceDTO>> getDevices() { List<DeviceDTO> devicesList = deviceService.getDevicesTopoForWeb(); // List<DeviceDTO> devicesList = deviceService.getDevicesTopoForWeb(); List<DeviceDTO> devicesList = deviceService.getDockList(); return ResponseResult.success(devicesList); } @@ -140,6 +142,7 @@ */ @ApiOperation("绑定设备列表") @GetMapping("/list/bound") @PreAuthorize("@ss.hasPermi('device:dock:list')") public ResponseResult<PaginationData<DeviceDTO>> getBoundDevicesWithDomain(Integer domain, @RequestParam(defaultValue = "1") Long page, @RequestParam(value = "page_size", @@ -214,19 +217,19 @@ return ResponseResult.success(); } /** * 查询机场设备列表 */ @PreAuthorize("@ss.hasPermi('device:camera:list')") @GetMapping("/test/list") @ApiOperation("查询机场设备列表") public TableDataInfo list(DeviceDTO device) { startPage(); DeviceEntity deviceEntity = deviceDTO2Entity(device); QueryWrapper<DeviceEntity> queryWrapper=new QueryWrapper<>(deviceEntity); List<DeviceEntity> list = deviceService.selectList(queryWrapper); return getDataTable(list); } // /** // * 查询机场设备列表 // */ // @PreAuthorize("@ss.hasPermi('device:camera:list')") // @GetMapping("/test/list") // @ApiOperation("查询机场设备列表") // public TableDataInfo list(DeviceDTO device) { // startPage(); // DeviceEntity deviceEntity = deviceDTO2Entity(device); // QueryWrapper<DeviceEntity> queryWrapper=new QueryWrapper<>(deviceEntity); // List<DeviceEntity> list = deviceService.selectList(queryWrapper); // return getDataTable(list); // } private DeviceEntity deviceDTO2Entity(DeviceDTO dto) { DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder(); ard-common/src/main/java/com/ard/common/annotation/DataScopePlus.java
@@ -1,29 +1,34 @@ package com.ard.common.annotation; import java.lang.annotation.*; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 数据权限过滤注解 * MyBatis-Plus 专用数据权限过滤注解 * 用于标注需要进行数据权限过滤的方法,配合 QueryWrapper 使用 * * @author ard */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataScopePlus { public @interface DataScopePlus { /** * 部门表的别名 * 部门表的别名(需与 Wrapper 中设置的表别名一致) */ public String deptAlias() default ""; String deptAlias() default ""; /** * 用户表的别名 * 用户表的别名(需与 Wrapper 中设置的表别名一致) */ public String userAlias() default ""; String userAlias() default ""; /** * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 * 权限字符(用于匹配角色对应的权限) * 多个权限用逗号分隔,默认从 PermissionContextHolder 获取 */ public String permission() default ""; String permission() default ""; } ard-framework/src/main/java/com/ard/framework/aspectj/DataScopeAspectPlus.java
New file @@ -0,0 +1,189 @@ package com.ard.framework.aspectj; import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import com.ard.common.annotation.DataScopePlus; import com.ard.common.constant.UserConstants; import com.ard.common.core.domain.entity.SysRole; import com.ard.common.core.domain.entity.SysUser; import com.ard.common.core.domain.model.LoginUser; import com.ard.common.core.text.Convert; import com.ard.common.utils.SecurityUtils; import com.ard.common.utils.StringUtils; import com.ard.framework.security.context.PermissionContextHolder; import com.baomidou.mybatisplus.core.conditions.AbstractWrapper; /** * MyBatis-Plus 专用数据权限切面 * 仅处理带有 @MyBatisPlusDataScope 注解的方法,且参数包含 QueryWrapper/LambdaQueryWrapper */ @Aspect @Component public class DataScopeAspectPlus { /** * 全部数据权限 */ public static final String DATA_SCOPE_ALL = "1"; /** * 自定数据权限(部门维度) */ public static final String DATA_SCOPE_CUSTOM = "2"; /** * 部门数据权限(仅本部门) */ public static final String DATA_SCOPE_DEPT = "3"; /** * 部门及以下数据权限 */ public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; /** * 仅本人数据权限 */ public static final String DATA_SCOPE_SELF = "5"; /** * 拦截带有 @MyBatisPlusDataScope 注解的方法,处理部门权限 */ @Before("@annotation(dataScope)") public void doBefore(JoinPoint point, DataScopePlus dataScope) throws Throwable { // 遍历方法参数,找到 Wrapper 类型的参数 AbstractWrapper<?, ?, ?> wrapper = findWrapperFromArgs(point.getArgs()); if (wrapper != null) { handleDataScope(wrapper, dataScope); // 找到后执行权限逻辑 } } /** * 从方法参数中找到 AbstractWrapper 类型的参数(兼容任意位置) */ private AbstractWrapper<?, ?, ?> findWrapperFromArgs(Object[] args) { if (args == null || args.length == 0) { return null; } for (Object arg : args) { // 判断是否为 MyBatis-Plus 的 Wrapper 类型 if (arg instanceof AbstractWrapper) { return (AbstractWrapper<?, ?, ?>) arg; } } return null; // 没找到 Wrapper,不执行权限过滤 } /** * 处理部门数据权限 */ protected void handleDataScope(AbstractWrapper<?, ?, ?> wrapper, DataScopePlus dataScope) { LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNotNull(loginUser)) { SysUser currentUser = loginUser.getUser(); // 超级管理员跳过权限过滤 if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) { String permission = StringUtils.defaultIfEmpty( dataScope.permission(), PermissionContextHolder.getContext() ); // 生成部门权限条件并注入 Wrapper generateDeptScopeSql(wrapper, currentUser, dataScope.deptAlias(), dataScope.userAlias(), permission); } } } /** * 仅生成部门维度的权限 SQL(移除用户相关逻辑) */ private void generateDeptScopeSql(AbstractWrapper<?, ?, ?> wrapper, SysUser user, String deptAlias, String userAlias, String permission) { StringBuilder sqlBuilder = new StringBuilder(); List<String> conditions = new ArrayList<>(); List<String> customRoleIds = new ArrayList<>(); // 收集拥有自定义部门权限的角色ID user.getRoles().forEach(role -> { if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { customRoleIds.add(Convert.toStr(role.getRoleId())); } }); // 遍历角色生,生成对应的数据权限条件 for (SysRole role : user.getRoles()) { String dataScope = role.getDataScope(); // 跳过已处理的权限类型或禁用角色 if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE)) { continue; } // 跳过不包含目标权限的角色 if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { continue; } // 仅保留部门相关的权限逻辑(移除用户权限) if (DATA_SCOPE_ALL.equals(dataScope)) { // 全部权限:清空条件 sqlBuilder.setLength(0); conditions.add(dataScope); break; } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) { // 自定义部门权限:关联角色-部门表 if (customRoleIds.size() > 1) { sqlBuilder.append(StringUtils.format( " OR {}.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id IN ({}) )", deptAlias, String.join(",", customRoleIds) )); } else { sqlBuilder.append(StringUtils.format( " OR {}.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = {} )", deptAlias, role.getRoleId() )); } } else if (DATA_SCOPE_DEPT.equals(dataScope)) { // 本部门权限:仅当前用户所属部门 sqlBuilder.append(StringUtils.format( " OR {}.dept_id = {}", deptAlias, user.getDeptId() )); } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { // 本部门及下属部门权限:通过部门表 ancestors 字段递归查询 sqlBuilder.append(StringUtils.format( " OR {}.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = {} OR find_in_set({}, ancestors) )", deptAlias, user.getDeptId(), user.getDeptId() )); } else if (DATA_SCOPE_SELF.equals(dataScope)) { // 仅本人权限:关联用户表 if (StringUtils.isNotBlank(userAlias)) { sqlBuilder.append(StringUtils.format( " OR {}.user_id = {}", userAlias, user.getUserId() )); } else { // 无用户别名时强制过滤(不查询任何数据) sqlBuilder.append(StringUtils.format( " OR {}.dept_id = 0", deptAlias )); } } conditions.add(dataScope); } // 无匹配角色时,强制过滤(不查询任何数据) if (conditions.isEmpty()) { sqlBuilder.append(StringUtils.format(" OR {}.dept_id = 0", deptAlias)); } // 注入部门权限条件 if (sqlBuilder.length() > 0) { String dataScopeSql = sqlBuilder.substring(4); // 去除开头的 "OR " wrapper.apply(dataScopeSql); } } } ard-sample/src/main/java/com/ard/sample/manage/mapper/IDeviceMapper.java
@@ -1,7 +1,14 @@ package com.ard.sample.manage.mapper; import com.ard.common.annotation.DataScopePlus; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ard.sample.manage.model.entity.DeviceEntity; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; /** * @@ -11,4 +18,17 @@ */ public interface IDeviceMapper extends BaseMapper<DeviceEntity> { @Select("SELECT m.* " + "FROM manage_device m " + "left join sys_dept d on d.dept_id = m.dept_id " + "${ew.customSqlSegment}") @DataScopePlus(deptAlias = "d", permission = "device:dock:list") List<DeviceEntity> selectListPer(@Param("ew") Wrapper<DeviceEntity> queryWrapper); @Select("SELECT m.* " + "FROM manage_device m " + "left join sys_dept d on d.dept_id = m.dept_id " + "${ew.customSqlSegment}") @DataScopePlus(deptAlias = "d", permission = "device:dock:list") <P extends IPage<DeviceEntity>> P selectPage(P page, @Param("ew") Wrapper<DeviceEntity> queryWrapper); } ard-sample/src/main/java/com/ard/sample/manage/service/IDeviceService.java
@@ -104,6 +104,16 @@ void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn, String gatewaySn); /** * 根据部门权限获取机场列表 * */ List<DeviceDTO> getDockList(); void spliceDeviceTopoPer(DeviceDTO gateway); // @DataScope(deptAlias = "d") List<DeviceDTO> getDockListByParams(DeviceQueryParam param); /** * Query the information of the device according to the sn of the device. * @param sn device sn * @return @@ -232,5 +242,5 @@ Boolean checkDeviceOnline(String sn); public List<DeviceEntity> selectList(Wrapper<DeviceEntity> queryWrapper); // public List<DeviceEntity> selectList(Wrapper<DeviceEntity> queryWrapper); } ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceServiceImpl.java
@@ -1,10 +1,12 @@ package com.ard.sample.manage.service.impl; import com.alibaba.fastjson2.JSONObject; import com.ard.common.annotation.DataScope; import com.ard.common.annotation.DataScopePlus; import com.ard.system.authorize.service.AuthorizeService; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ard.common.core.domain.entity.SysDept; @@ -347,6 +349,68 @@ // payloads // subDevice.setPayloadsList(payloadService.getDevicePayloadEntitiesByDeviceSn(gateway.getChildDeviceSn())); } @Override public List<DeviceDTO> getDockList() { List<DeviceDTO> devicesList = this.getDockListByParams( DeviceQueryParam.builder() .domains(List.of(DeviceDomainEnum.GATEWAY.getVal(), DeviceDomainEnum.DOCK.getVal())) .build()); devicesList.stream() .filter(gateway -> DeviceDomainEnum.DOCK.getVal() == gateway.getDomain() || RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn())) .forEach(this::spliceDeviceTopoPer); return devicesList; } @Override public void spliceDeviceTopoPer(DeviceDTO gateway) { gateway.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn())); // sub device if (!StringUtils.hasText(gateway.getChildDeviceSn())) { return; } DeviceDTO subDevice = getDevicesByParams(DeviceQueryParam.builder().deviceSn(gateway.getChildDeviceSn()).build()).get(0); subDevice.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + subDevice.getDeviceSn())); gateway.setChildren(subDevice); // payloads // subDevice.setPayloadsList(payloadService.getDevicePayloadEntitiesByDeviceSn(gateway.getChildDeviceSn())); } //@DataScope(deptAlias = "d") @Override public List<DeviceDTO> getDockListByParams(DeviceQueryParam param){ return mapper.selectListPer( new LambdaQueryWrapper<DeviceEntity>() .eq(StringUtils.hasText(param.getDeviceSn()), DeviceEntity::getDeviceSn, param.getDeviceSn()) .eq(param.getDeviceType() != null, DeviceEntity::getDeviceType, param.getDeviceType()) .eq(param.getSubType() != null, DeviceEntity::getSubType, param.getSubType()) .eq(StringUtils.hasText(param.getChildSn()), DeviceEntity::getChildSn, param.getChildSn()) .and(!CollectionUtils.isEmpty(param.getDomains()), wrapper -> { for (Integer domain : param.getDomains()) { wrapper.eq(DeviceEntity::getDomain, domain).or(); } }) .eq(param.getBoundStatus() != null, DeviceEntity::getBoundStatus, param.getBoundStatus()) .orderBy(param.isOrderBy(), param.isAsc(), DeviceEntity::getId)) .stream() .map(this::deviceEntityConvertToDTO) .collect(Collectors.toList()); } @Override @@ -874,11 +938,17 @@ Long pageSize, Integer domain, Long deptId) { Page<DeviceEntity> pagination = mapper.selectPage(new Page<>(page, pageSize), new LambdaQueryWrapper<DeviceEntity>() .eq(DeviceEntity::getDomain, domain) .eq(deptId != null, DeviceEntity::getDeptId, deptId) .isNotNull(DeviceEntity::getDeptId)); // .eq(DeviceEntity::getBoundStatus, true)); new QueryWrapper<DeviceEntity>() .eq("m.domain", domain) .eq(deptId != null, "m.dept_id", deptId) .isNotNull("m.dept_id")); //以前的写法 // new LambdaQueryWrapper<DeviceEntity>() // .eq(DeviceEntity::getDomain, domain) // .eq(deptId != null, DeviceEntity::getDeptId, deptId) // .isNotNull(DeviceEntity::getDeptId); List<DeviceDTO> devicesList = pagination.getRecords().stream().map(this::deviceEntityConvertToDTO) .peek(device -> { device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn())); @@ -1221,9 +1291,9 @@ messageSender.publish(topic, builder.build()); } @DataScopePlus(deptAlias="d") @Override public List<DeviceEntity> selectList(Wrapper<DeviceEntity> queryWrapper) { return mapper.selectList(queryWrapper); } // @DataScopePlus(deptAlias="d") // @Override // public List<DeviceEntity> selectList(Wrapper<DeviceEntity> queryWrapper) { // return mapper.selectList(queryWrapper); // } }