数据联动列表:数据权限隔离

This commit is contained in:
zhugongping 2026-09-30 07:02:16 +08:00
parent f8a8474f0f
commit 5d8ae0e2f0
9 changed files with 161 additions and 13 deletions

View File

@ -73,11 +73,11 @@ public class NotifyTemplateServiceImpl implements INotifyTemplateService {
// notifyTemplate.setTenantId(user.getUserId());
// }
// 查询所属机构
if (null != user.getDeptId()) {
notifyTemplate.setTenantId(user.getDept().getDeptUserId());
} else {
notifyTemplate.setTenantId(user.getUserId());
}
// if (null != user.getDeptId()) {
// notifyTemplate.setTenantId(user.getDept().getDeptUserId());
// } else {
// notifyTemplate.setTenantId(user.getUserId());
// }
List<NotifyTemplate> notifyTemplates = notifyTemplateMapper.selectNotifyTemplateList(notifyTemplate);
if (org.apache.commons.collections4.CollectionUtils.isEmpty(notifyTemplates)) {
return notifyTemplates;

View File

@ -42,12 +42,16 @@ public class ThingsModelPushController extends BaseController {
return getDataTable(list);
}
/** 获取详情 */
/** 获取详情(超管看全部,机构/终端用户只能看自己机构的) */
@ApiOperation("获取推送配置详情")
@PreAuthorize("@ss.hasPermi('iot:push:query')")
@GetMapping("/{id}")
public AjaxResult getById(@PathVariable Long id) {
return AjaxResult.success(pushConfigService.selectById(id));
ThingsModelPushConfig config = pushConfigService.selectById(id);
if (config == null) {
return AjaxResult.error("推送配置不存在或无访问权限");
}
return AjaxResult.success(config);
}
/** 新增 */
@ -66,6 +70,7 @@ public class ThingsModelPushController extends BaseController {
@Log(title = "推送配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody ThingsModelPushConfig config) {
pushConfigService.assertOwned(config.getId());
return toAjax(pushConfigService.updateConfig(config));
}
@ -75,6 +80,7 @@ public class ThingsModelPushController extends BaseController {
@Log(title = "推送配置状态", businessType = BusinessType.UPDATE)
@PutMapping("/status")
public AjaxResult changeStatus(@RequestBody ThingsModelPushConfig config) {
pushConfigService.assertOwned(config.getId());
return toAjax(pushConfigService.updateStatus(config.getId(), config.getStatus()));
}
@ -84,6 +90,12 @@ public class ThingsModelPushController extends BaseController {
@Log(title = "推送配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
if (ids == null || ids.length == 0) {
return AjaxResult.error("请选择要删除的推送配置");
}
for (Long id : ids) {
pushConfigService.assertOwned(id);
}
return toAjax(pushConfigService.deleteByIds(ids));
}
@ -97,7 +109,7 @@ public class ThingsModelPushController extends BaseController {
public AjaxResult manualPush(@PathVariable Long id) {
ThingsModelPushConfig config = pushConfigService.selectById(id);
if (config == null) {
return AjaxResult.error("推送配置不存在");
return AjaxResult.error("推送配置不存在或无访问权限");
}
if (config.getStatus() == null || config.getStatus() == 0) {
return AjaxResult.error("推送配置已停用,请先启用");

View File

@ -71,7 +71,7 @@ public class ThingsModelPushConfig extends BaseEntity {
@ApiModelProperty("租户名称")
private String tenantName;
@ApiModelProperty("配置归属用户ID(列表按此过滤;与接收用户 userIds 不同)")
@ApiModelProperty("创建人用户ID(数据隔离按 tenantId 过滤,与此字段及接收用户 userIds 均不同)")
private Long userId;
/** 物模型项列表(非持久化,前端用) */

View File

@ -29,6 +29,14 @@ public interface DeviceMapper extends BaseMapperX<Device> {
*/
public Device selectDeviceByDeviceId(Long deviceId);
/**
* 只查设备的归属(tenant_id / tenant_name),用于按设备推导数据归属
*
* @param deviceId 设备主键
* @return 设备归属,设备不存在时返回 null
*/
Device selectTenantByDeviceId(Long deviceId);
/**
* 查询设备和产品总数
*

View File

@ -19,6 +19,9 @@ public interface ThingsModelPushConfigMapper {
ThingsModelPushConfig selectById(Long id);
/** 只查归属租户ID,用于越权校验(避免加载物模型项和接收用户) */
Long selectTenantIdById(Long id);
List<ThingsModelPushConfig> selectList(ThingsModelPushConfig query);
/** 查询所有启用的推送配置(含物模型项和接收用户) */

View File

@ -21,6 +21,11 @@ public interface IThingsModelPushConfigService {
/** 按产品/设备查询启用配置 */
List<ThingsModelPushConfig> selectEnabledByProductAndDevice(Long productId, Long deviceId);
/**
* 校验配置在当前登录账号的数据范围内,越权抛异常
*/
void assertOwned(Long configId);
int insertConfig(ThingsModelPushConfig config);
int updateConfig(ThingsModelPushConfig config);

View File

@ -1,11 +1,17 @@
package com.fastbee.iot.service.impl;
import com.fastbee.common.core.domain.entity.SysDept;
import com.fastbee.common.core.domain.entity.SysUser;
import com.fastbee.common.core.domain.model.LoginUser;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.SecurityUtils;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.iot.domain.Device;
import com.fastbee.iot.domain.PushModelItem;
import com.fastbee.iot.domain.PushUser;
import com.fastbee.iot.domain.ThingsModelPushConfig;
import com.fastbee.iot.mapper.DeviceMapper;
import com.fastbee.iot.mapper.ThingsModelPushConfigMapper;
import com.fastbee.iot.service.IThingsModelPushConfigService;
import lombok.extern.slf4j.Slf4j;
@ -16,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@ -29,18 +36,31 @@ public class ThingsModelPushConfigServiceImpl implements IThingsModelPushConfigS
@Resource
private ThingsModelPushConfigMapper mapper;
@Resource
private DeviceMapper deviceMapper;
@Override
public ThingsModelPushConfig selectById(Long id) {
return mapper.selectById(id);
ThingsModelPushConfig config = mapper.selectById(id);
if (config == null) {
return null;
}
assertInScope(config.getTenantId());
return config;
}
@Override
public List<ThingsModelPushConfig> selectList(ThingsModelPushConfig query) {
query.setUserId(SecurityUtils.getUserId());
// 归属条件只由服务端计算,忽略前端传入值:超管看全部,机构/终端用户只看自己
query.setTenantId(resolveScopeTenantId());
return mapper.selectList(query);
}
@Override
public void assertOwned(Long configId) {
assertInScope(mapper.selectTenantIdById(configId));
}
@Override
public List<ThingsModelPushConfig> selectAllEnabled() {
return mapper.selectAllEnabled();
@ -55,6 +75,11 @@ public class ThingsModelPushConfigServiceImpl implements IThingsModelPushConfigS
@Transactional(rollbackFor = Exception.class)
public int insertConfig(ThingsModelPushConfig config) {
LoginUser loginUser = SecurityUtils.getLoginUser();
Device deviceTenant = loadDeviceTenant(config.getDeviceId());
Long ownerTenantId = resolveOwnerTenantId(loginUser, deviceTenant);
config.setTenantId(ownerTenantId);
// 名称必须与最终归属ID同源,否则会写出 tenant_id=3 / tenant_name=admin 这类错配数据
config.setTenantName(resolveOwnerTenantName(loginUser, deviceTenant, ownerTenantId));
config.setUserId(loginUser.getUserId());
config.setCreateBy(loginUser.getUsername());
config.setCreateTime(DateUtils.getNowDate());
@ -110,6 +135,77 @@ public class ThingsModelPushConfigServiceImpl implements IThingsModelPushConfigS
mapper.updateLastPushTime(id, new Date());
}
/**
* 当前登录账号的查询范围:超级管理员返回 null(不限),其余账号返回其归属租户ID
*/
private Long resolveScopeTenantId() {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (SecurityUtils.isAdmin(loginUser.getUserId())) {
return null;
}
return resolveAccountTenantId(loginUser);
}
/**
* 数据归属租户:<b>优先取所挂设备的归属</b>,设备无归属时回落登录账号的机构。
* 推送配置的主体是设备而非创建人——超管替下级机构建配置时必须落到该机构,否则机构看不到自己的配置。
*/
private Long resolveOwnerTenantId(LoginUser loginUser, Device deviceTenant) {
if (deviceTenant != null && deviceTenant.getTenantId() != null) {
return deviceTenant.getTenantId();
}
return resolveAccountTenantId(loginUser);
}
/**
* 归属名称,必须与 {@link #resolveOwnerTenantId} 的结果同源:
* 归属ID取自设备时名称就用设备的(哪怕为空,如实反映),否则回落登录账号。
*/
private String resolveOwnerTenantName(LoginUser loginUser, Device deviceTenant, Long ownerTenantId) {
if (deviceTenant != null && Objects.equals(deviceTenant.getTenantId(), ownerTenantId)) {
return deviceTenant.getTenantName();
}
return resolveAccountTenantName(loginUser);
}
/**
* 登录账号自身归属:机构用户取机构绑定的系统账号ID,终端用户(无机构)取自身账号ID
*/
private Long resolveAccountTenantId(LoginUser loginUser) {
SysUser user = loginUser.getUser();
SysDept dept = user.getDept();
if (user.getDeptId() != null && dept != null && dept.getDeptUserId() != null) {
return dept.getDeptUserId();
}
return loginUser.getUserId();
}
private String resolveAccountTenantName(LoginUser loginUser) {
SysUser user = loginUser.getUser();
SysDept dept = user.getDept();
if (user.getDeptId() != null && dept != null && dept.getDeptUserId() != null
&& StringUtils.isNotEmpty(dept.getDeptUserName())) {
return dept.getDeptUserName();
}
return user.getUserName();
}
/** 设备归属,deviceId 为空或 0(未指定/全设备)时返回 null */
private Device loadDeviceTenant(Long deviceId) {
if (deviceId == null || deviceId == 0L) {
return null;
}
return deviceMapper.selectTenantByDeviceId(deviceId);
}
private void assertInScope(Long tenantId) {
Long scopeTenantId = resolveScopeTenantId();
if (scopeTenantId == null || scopeTenantId.equals(tenantId)) {
return;
}
throw new ServiceException("推送配置不存在或无访问权限");
}
private void saveItemsAndUsers(ThingsModelPushConfig config) {
if (CollectionUtils.isNotEmpty(config.getItems())) {
List<PushModelItem> items = config.getItems();

View File

@ -550,6 +550,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where device_id = #{deviceId}
</select>
<resultMap type="com.fastbee.iot.domain.Device" id="DeviceTenantResult">
<result property="tenantId" column="tenant_id" />
<result property="tenantName" column="tenant_name" />
</resultMap>
<select id="selectTenantByDeviceId" parameterType="Long" resultMap="DeviceTenantResult">
select d.tenant_id, d.tenant_name from iot_device d
where d.device_id = #{deviceId}
</select>
<select id="selectDeviceInfoBySerialNumber" parameterType="String" resultMap="DeviceImeiIccidTypeResult">
select
p.device_type as device_type,

View File

@ -19,6 +19,7 @@
<result property="status" column="status"/>
<result property="tenantId" column="tenant_id"/>
<result property="tenantName" column="tenant_name"/>
<result property="userId" column="user_id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
@ -53,9 +54,10 @@
<sql id="baseColumns">
c.id, c.config_name, c.product_id, c.device_id, c.serial_number,
c.template_id, c.push_interval, c.last_push_time, c.status,
c.tenant_id, c.tenant_name, c.create_by, c.create_time,
c.tenant_id, c.tenant_name, c.user_id, c.create_by, c.create_time,
c.update_by, c.update_time,p.product_name, p.device_type,
d.device_name,d.network_address AS device_address, nt.name AS template_name
d.device_name,
d.network_address AS device_address, nt.name AS template_name
</sql>
<sql id="baseJoins">
@ -74,6 +76,16 @@
AND c.del_flag = '0'
</select>
<select id="selectTenantIdById" resultType="java.lang.Long">
SELECT
c.tenant_id
FROM
iot_things_model_push_config c
WHERE
c.id = #{id}
AND c.del_flag = '0'
</select>
<select id="selectList" resultMap="ConfigResult">
SELECT
<include refid="baseColumns"/>
@ -131,6 +143,7 @@
status,
tenant_id,
tenant_name,
user_id,
create_by,
create_time,
del_flag
@ -146,6 +159,7 @@
#{status},
#{tenantId},
#{tenantName},
#{userId},
#{createBy},
#{createTime},
'0'