refactor: convert Device↔Profile from M:N to 1:1 via device.profile_id

Replace dc3_profile_bind junction table with direct profile_id column on
dc3_device, simplifying the domain model. Device now owns a single
Profile instead of maintaining a many-to-many binding.

No data migration — seed DDL is updated separately.
This commit is contained in:
Vickey
2026-05-23 14:42:34 +08:00
parent cf2ffb0194
commit dd4f575bae
46 changed files with 182 additions and 1353 deletions
@@ -221,7 +221,7 @@ public class PointCommandServiceImpl implements PointCommandService {
if (EnableFlagEnum.DISABLE.equals(point.getEnableFlag())) {
throw new ServiceException("Point is disabled");
}
if (Objects.isNull(device.getProfileIds()) || !device.getProfileIds().contains(point.getProfileId())) {
if (Objects.isNull(device.getProfileId()) || !Objects.equals(device.getProfileId(), point.getProfileId())) {
throw new UnAuthorizedException(ExceptionConstant.NO_AVAILABLE_AUTH);
}
}
@@ -252,7 +252,7 @@ public class PointValueServiceImpl implements PointValueService {
}
if (Objects.nonNull(device) && Objects.nonNull(point)
&& (Objects.isNull(device.getProfileIds()) || !device.getProfileIds().contains(point.getProfileId()))) {
&& (Objects.isNull(device.getProfileId()) || !Objects.equals(device.getProfileId(), point.getProfileId()))) {
throw new NotFoundException("Point does not exist");
}
}
@@ -53,31 +53,31 @@ public class PointCommandResultReceiver {
public void onResult(Channel channel, Message message, PointCommandResultDTO resultDTO) {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
if (Objects.isNull(resultDTO) || Objects.isNull(resultDTO.getCommandId())) {
if (Objects.isNull(resultDTO) || Objects.isNull(resultDTO.commandId())) {
RabbitAckUtil.reject(channel, deliveryTag);
return;
}
log.info("Receive point command result: commandId={}, status={}", resultDTO.getCommandId(), resultDTO.getStatus());
log.info("Receive point command result: commandId={}, status={}", resultDTO.commandId(), resultDTO.status());
PointCommandDO commandDO = pointCommandManager.lambdaQuery()
.eq(PointCommandDO::getCommandId, resultDTO.getCommandId())
.eq(PointCommandDO::getCommandId, resultDTO.commandId())
.one();
if (Objects.nonNull(commandDO)) {
commandDO.setStatus(resultDTO.getStatus());
commandDO.setErrorCode(resultDTO.getErrorCode());
commandDO.setErrorMessage(resultDTO.getErrorMessage());
commandDO.setResponseValue(resultDTO.getResponseValue());
if (Objects.nonNull(resultDTO.getFinishedAt())) {
commandDO.setFinishedAt(LocalDateTime.ofInstant(resultDTO.getFinishedAt(), ZoneId.systemDefault()));
commandDO.setStatus(resultDTO.status());
commandDO.setErrorCode(resultDTO.errorCode());
commandDO.setErrorMessage(resultDTO.errorMessage());
commandDO.setResponseValue(resultDTO.responseValue());
if (Objects.nonNull(resultDTO.finishedAt())) {
commandDO.setFinishedAt(LocalDateTime.ofInstant(resultDTO.finishedAt(), ZoneId.systemDefault()));
} else {
commandDO.setFinishedAt(LocalDateTime.now());
}
pointCommandManager.updateById(commandDO);
log.info("Updated command status: commandId={}, status={}", resultDTO.getCommandId(), resultDTO.getStatus());
log.info("Updated command status: commandId={}, status={}", resultDTO.commandId(), resultDTO.status());
} else {
log.warn("Command not found for result: commandId={}", resultDTO.getCommandId());
log.warn("Command not found for result: commandId={}", resultDTO.commandId());
}
RabbitAckUtil.ack(channel, deliveryTag);
@@ -292,19 +292,19 @@
</select>
<select id="peerAlarmCounts" resultType="io.github.pnoker.common.data.entity.bo.dashboard.PeerAlarmRow">
SELECT pb.profile_id AS profile_id,
ea.device_id AS device_id,
COUNT(*) AS alarm_count
SELECT d.profile_id AS profile_id,
ea.device_id AS device_id,
COUNT(*) AS alarm_count
FROM dc3_entity_alarm ea
JOIN dc3_manager.dc3_profile_bind pb
ON pb.device_id = ea.device_id
AND pb.tenant_id = ea.tenant_id
AND pb.deleted = 0
JOIN dc3_manager.dc3_device d
ON d.id = ea.device_id
AND d.tenant_id = ea.tenant_id
AND d.deleted = 0
WHERE ea.deleted = 0
AND ea.tenant_id = #{tenantId}
AND ea.alarm_target_type_flag IN (0, 1)
AND ea.create_time &gt;= #{from}
GROUP BY pb.profile_id, ea.device_id
GROUP BY d.profile_id, ea.device_id
</select>
<select id="agingBuckets" resultType="io.github.pnoker.common.data.entity.bo.dashboard.AgingBucketRow">
@@ -40,8 +40,6 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
@@ -79,7 +77,7 @@ class PointCommandServiceImplTest {
@BeforeEach
void setUp() {
device = new FacadeDeviceBO();
device.setProfileIds(List.of(5L));
device.setProfileId(5L);
device.setEnableFlag(EnableFlagEnum.ENABLE);
point = new FacadePointBO();
point.setProfileId(5L);
@@ -149,7 +147,7 @@ class PointCommandServiceImplTest {
@Test
void readRejectsCrossProfileBindingAsUnauthorized() {
FacadeDeviceBO mismatchedDevice = new FacadeDeviceBO();
mismatchedDevice.setProfileIds(List.of(99L));
mismatchedDevice.setProfileId(99L);
mismatchedDevice.setEnableFlag(EnableFlagEnum.ENABLE);
when(deviceFacade.getById(1L, 10L)).thenReturn(mismatchedDevice);
when(pointFacade.getById(1L, 20L)).thenReturn(point);
@@ -162,7 +160,7 @@ class PointCommandServiceImplTest {
@Test
void readRejectsDeviceWithoutAnyProfileBinding() {
FacadeDeviceBO bareDevice = new FacadeDeviceBO();
bareDevice.setProfileIds(null);
bareDevice.setProfileId(null);
bareDevice.setEnableFlag(EnableFlagEnum.ENABLE);
when(deviceFacade.getById(1L, 10L)).thenReturn(bareDevice);
when(pointFacade.getById(1L, 20L)).thenReturn(point);
@@ -220,7 +218,7 @@ class PointCommandServiceImplTest {
@Test
void readRejectsDisabledDevice() {
FacadeDeviceBO disabledDevice = new FacadeDeviceBO();
disabledDevice.setProfileIds(List.of(5L));
disabledDevice.setProfileId(5L);
disabledDevice.setEnableFlag(EnableFlagEnum.DISABLE);
when(deviceFacade.getById(1L, 10L)).thenReturn(disabledDevice);
@@ -87,9 +87,9 @@ public class DeviceBO extends BaseBO {
private Integer version;
/**
* Assigned profile identifiers.
* Assigned profile identifier.
*/
private Set<Long> profileIds;
private Long profileId;
/**
* Identifiers of points owned by the device.
@@ -31,8 +31,6 @@ import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import java.util.HashSet;
/**
* MapStruct mapper for converting device gRPC DTOs into internal business objects.
*
@@ -53,7 +51,7 @@ public interface DeviceBuilder {
@Mapping(target = "operateTime", ignore = true)
@Mapping(target = "deviceExt", ignore = true)
@Mapping(target = "enableFlag", ignore = true)
@Mapping(target = "profileIds", ignore = true)
@Mapping(target = "profileId", ignore = true)
@Mapping(target = "pointIds", ignore = true)
@Mapping(target = "driverAttributeConfigIdMap", ignore = true)
@Mapping(target = "pointAttributeConfigIdMap", ignore = true)
@@ -64,7 +62,7 @@ public interface DeviceBuilder {
GrpcBuilderUtil.buildBaseBOByGrpcBase(entityGrpc.getBase(), entityBO);
CollectionOptional.ofNullable(entityGrpc.getProfileIdsList())
.ifPresent(value -> entityBO.setProfileIds(new HashSet<>(value)));
.ifPresent(value -> entityBO.setProfileId(value.stream().findFirst().orElse(null)));
JsonOptional.ofNullable(entityGrpc.getDeviceExt())
.ifPresent(value -> entityBO.setDeviceExt(JsonUtil.parseObject(value, DeviceExt.class)));
EnableOptional.ofNullable(entityGrpc.getEnableFlag()).ifPresent(entityBO::setEnableFlag);
@@ -70,7 +70,7 @@ public class DriverReadScheduleJob extends QuartzJobBean {
for (Long deviceId : deviceIds) {
DeviceBO entityBO = deviceMetadata.getCache(deviceId);
if (Objects.nonNull(entityBO) && EnableFlagEnum.ENABLE.equals(entityBO.getEnableFlag())
&& CollectionUtils.isNotEmpty(entityBO.getProfileIds())
&& Objects.nonNull(entityBO.getProfileId())
&& CollectionUtils.isNotEmpty(entityBO.getPointIds())
&& MapUtils.isNotEmpty(entityBO.getDriverAttributeConfigIdMap())
&& MapUtils.isNotEmpty(entityBO.getPointAttributeConfigIdMap())) {
@@ -26,8 +26,6 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
/**
* Facade-level device BO.
* <p>
@@ -62,6 +60,6 @@ public class FacadeDeviceBO extends BaseBO {
private Integer version;
private List<Long> profileIds;
private Long profileId;
}
@@ -95,7 +95,7 @@ public class FacadeGrpcDeviceBuilder {
.ifPresent(value -> bo.setDeviceExt(JsonUtil.parseObject(value, DeviceExt.class)));
if (dto.getProfileIdsCount() > 0) {
bo.setProfileIds(new ArrayList<>(dto.getProfileIdsList()));
bo.setProfileId(dto.getProfileIdsList().stream().findFirst().orElse(null));
}
return bo;
@@ -27,12 +27,10 @@ import io.github.pnoker.common.manager.entity.bo.DriverAttributeConfigBO;
import io.github.pnoker.common.manager.entity.bo.PointAttributeBO;
import io.github.pnoker.common.manager.entity.bo.PointAttributeConfigBO;
import io.github.pnoker.common.manager.entity.bo.PointBO;
import io.github.pnoker.common.manager.entity.bo.ProfileBindBO;
import io.github.pnoker.common.manager.entity.builder.DeviceBuilder;
import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.service.DriverAttributeConfigService;
import io.github.pnoker.common.manager.service.PointAttributeConfigService;
import io.github.pnoker.common.manager.service.ProfileBindService;
import io.github.pnoker.common.utils.PoiUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -60,8 +58,6 @@ public class ImportDeviceServiceImpl implements ImportDeviceService {
private final DeviceManager deviceManager;
private final ProfileBindService profileBindService;
private final DriverAttributeConfigService driverAttributeConfigService;
private final PointAttributeConfigService pointAttributeConfigService;
@@ -85,12 +81,10 @@ public class ImportDeviceServiceImpl implements ImportDeviceService {
entityDO.setTenantId(deviceBO.getTenantId());
// Import device
entityDO.setProfileId(deviceBO.getProfileId());
entityDO = deviceManager.innerSave(entityDO);
DeviceBO entityBO = deviceBuilder.buildBOByDO(entityDO);
// Import device profile binding configuration
importProfileBind(entityBO, deviceBO.getProfileIds());
// Import driver attribute configuration
importDriverAttributeConfig(entityBO, driverAttributeBOList, sheet, row);
@@ -100,32 +94,6 @@ public class ImportDeviceServiceImpl implements ImportDeviceService {
return entityBO;
}
/**
* Import device profile binding configuration
*
* @param deviceBO Device
* @param profileIds Profile ID list
*/
private void importProfileBind(DeviceBO deviceBO, List<Long> profileIds) {
if (CollectionUtils.isEmpty(profileIds)) {
return;
}
profileIds.forEach(profileId -> {
try {
ProfileBindBO entityBO = new ProfileBindBO();
entityBO.setProfileId(profileId);
entityBO.setDeviceId(deviceBO.getId());
entityBO.setTenantId(deviceBO.getTenantId());
profileBindService.add(entityBO);
} catch (Exception e) {
log.warn("Skip profile bind during device import, deviceId={}, profileId={}, error={}",
deviceBO.getId(), profileId, e.getMessage(), e);
}
});
}
/**
* Import driver attribute configuration
*
@@ -221,7 +221,7 @@ public class PointAttributeConfigController implements BaseController {
private void requirePointConfigRelations(Long tenantId, Long deviceId, Long pointId, Long attributeId) {
DeviceBO deviceBO = requireTenant(tenantId, deviceService.getById(deviceId));
PointBO pointBO = requireTenant(tenantId, pointService.getById(pointId));
if (Objects.isNull(deviceBO.getProfileIds()) || !deviceBO.getProfileIds().contains(pointBO.getProfileId())) {
if (Objects.isNull(deviceBO.getProfileId()) || !Objects.equals(deviceBO.getProfileId(), pointBO.getProfileId())) {
throw new NotFoundException("Resource does not exist");
}
@@ -1,34 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.dal;
import com.baomidou.mybatisplus.extension.service.IService;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
/**
* <p>
* Profile bind manager.
* </p>
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
public interface ProfileBindManager extends IService<ProfileBindDO> {
}
@@ -1,39 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.dal.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
import io.github.pnoker.common.manager.mapper.ProfileBindMapper;
import org.springframework.stereotype.Service;
/**
* <p>
* Point Bind Manager Service implementation class
* </p>
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
@Service
public class ProfileBindManagerImpl extends ServiceImpl<ProfileBindMapper, ProfileBindDO>
implements ProfileBindManager {
}
@@ -27,8 +27,6 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
/**
* Business object for device operations.
*
@@ -86,8 +84,8 @@ public class DeviceBO extends BaseBO implements TenantOwned {
//
/**
* ID
* Profile ID
*/
private List<Long> profileIds;
private Long profileId;
}
@@ -1,56 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* Business object for profile binding operations.
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
public class ProfileBindBO extends BaseBO {
/**
* ID
*/
private Long profileId;
/**
* Device ID
*/
private Long deviceId;
/**
* Tenant ID
*/
private Long tenantId;
}
@@ -19,10 +19,9 @@ package io.github.pnoker.common.manager.entity.bo.dashboard;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* One (profile, device) link from dc3_profile_bind — M:N edge in the topology.
* One (device, profile) link from dc3_device — 1:1 edge in the topology.
*
* @author pnoker
* @version 2025.9.0
@@ -30,11 +29,10 @@ import lombok.ToString;
*/
@Getter
@Setter
@ToString
public class ProfileBindingRow {
private long profileId;
private Long profileId;
private long deviceId;
private Long deviceId;
}
@@ -116,7 +116,7 @@ public interface DeviceBuilder {
* @return EntityBO
*/
@Mapping(target = "deviceExt", ignore = true)
@Mapping(target = "profileIds", ignore = true)
@Mapping(target = "profileId", ignore = true)
@Mapping(target = "enableFlag", ignore = true)
DeviceBO buildBOByDO(DeviceDO entityDO);
@@ -1,127 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.entity.builder;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.pnoker.common.manager.entity.bo.ProfileBindBO;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
import io.github.pnoker.common.manager.entity.vo.ProfileBindVO;
import io.github.pnoker.common.utils.MapStructUtil;
import io.github.pnoker.common.utils.PageUtil;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.List;
/**
* MapStruct builder converting between profile binding BO, VO, and DO.
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
@Mapper(componentModel = "spring", uses = {MapStructUtil.class})
public interface ProfileBindBuilder {
/**
* VO to BO
*
* @param entityVO EntityVO
* @return EntityBO
*/
@Mapping(target = "tenantId", ignore = true)
ProfileBindBO buildBOByVO(ProfileBindVO entityVO);
/**
* VOList to BOList
*
* @param entityVOList EntityVO Array
* @return EntityBO Array
*/
List<ProfileBindBO> buildBOListByVOList(List<ProfileBindVO> entityVOList);
/**
* BO to DO
*
* @param entityBO EntityBO
* @return EntityDO
*/
@Mapping(target = "deleted", ignore = true)
ProfileBindDO buildDOByBO(ProfileBindBO entityBO);
/**
* BOList to DOList
*
* @param entityBOList EntityBO Array
* @return EntityDO Array
*/
List<ProfileBindDO> buildDOListByBOList(List<ProfileBindBO> entityBOList);
/**
* DO to BO
*
* @param entityDO EntityDO
* @return EntityBO
*/
ProfileBindBO buildBOByDO(ProfileBindDO entityDO);
/**
* DOList to BOList
*
* @param entityDOList EntityDO Array
* @return EntityBO Array
*/
List<ProfileBindBO> buildBOListByDOList(List<ProfileBindDO> entityDOList);
/**
* BO to VO
*
* @param entityBO EntityBO
* @return EntityVO
*/
ProfileBindVO buildVOByBO(ProfileBindBO entityBO);
/**
* BOList to VOList
*
* @param entityBOList EntityBO Array
* @return EntityVO Array
*/
List<ProfileBindVO> buildVOListByBOList(List<ProfileBindBO> entityBOList);
/**
* DOPage to BOPage
*
* @param entityPageDO EntityDO Page
* @return EntityBO Page
*/
default Page<ProfileBindBO> buildBOPageByDOPage(Page<ProfileBindDO> entityPageDO) {
return PageUtil.copyPage(entityPageDO, this::buildBOByDO);
}
/**
* BOPage to VOPage
*
* @param entityPageBO EntityBO Page
* @return EntityVO Page
*/
default Page<ProfileBindVO> buildVOPageByBOPage(Page<ProfileBindBO> entityPageBO) {
return PageUtil.copyPage(entityPageBO, this::buildVOByBO);
}
}
@@ -74,6 +74,12 @@ public class DeviceDO implements Serializable {
@TableField("driver_id")
private Long driverId;
/**
* Profile ID
*/
@TableField("profile_id")
private Long profileId;
/**
*
*/
@@ -1,124 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.entity.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* Profile bind persistence object.
* </p>
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
@Getter
@Setter
@ToString
@TableName("dc3_profile_bind")
public class ProfileBindDO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* Primary key ID
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* ID
*/
@TableField("profile_id")
private Long profileId;
/**
* Device ID
*/
@TableField("device_id")
private Long deviceId;
/**
* Tenant ID
*/
@TableField("tenant_id")
private Long tenantId;
/**
* Description
*/
@TableField("remark")
private String remark;
/**
* Creator ID
*/
@TableField("creator_id")
private Long creatorId;
/**
* Creator Name
*/
@TableField("creator_name")
private String creatorName;
/**
* Create Time
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* Operator ID
*/
@TableField("operator_id")
private Long operatorId;
/**
* Operator Name
*/
@TableField("operator_name")
private String operatorName;
/**
* Operate Time
*/
@TableField("operate_time")
private LocalDateTime operateTime;
/**
* Logical delete flag, 0:not deleted, 1:deleted
*/
@TableLogic
@TableField("deleted")
private Byte deleted;
}
@@ -1,68 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.entity.query;
import io.github.pnoker.common.entity.common.Pages;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
/**
* Query parameters for profile binding listing and filtering.
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class ProfileBindQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Pages page;
/**
* Tenant ID
*/
private Long tenantId;
//
/**
* ID
*/
private Long profileId;
/**
* Device ID
*/
private Long deviceId;
}
@@ -33,8 +33,6 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.Set;
/**
* View object for device API responses.
*
@@ -89,8 +87,9 @@ public class DeviceVO extends BaseVO {
*/
private Integer version;
//
@NotNull(message = "ID", groups = {Upload.class})
private Set<Long> profileIds;
/**
* Profile ID
*/
private Long profileId;
}
@@ -1,58 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.entity.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.github.pnoker.common.entity.base.BaseVO;
import io.github.pnoker.common.valid.Add;
import io.github.pnoker.common.valid.Update;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* View object for profile binding API responses.
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class ProfileBindVO extends BaseVO {
/**
* ID
*/
@NotNull(message = "Profile ID can't be empty", groups = {Add.class, Update.class})
private Long profileId;
/**
* Device ID
*/
@NotNull(message = "Device ID can't be empty", groups = {Add.class, Update.class})
private Long deviceId;
}
@@ -52,7 +52,7 @@ public class MybatisGenerator {
.strategyConfig(MybatisUtil::defaultStrategyConfig)
.strategyConfig(builder -> builder.addInclude("dc3_device", "dc3_driver", "dc3_driver_attribute",
"dc3_driver_attribute_config", "dc3_point", "dc3_point_attribute", "dc3_point_attribute_config",
"dc3_profile", "dc3_profile_bind"))
"dc3_profile"))
.execute();
}
@@ -34,6 +34,8 @@ import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
@@ -117,7 +119,9 @@ public interface GrpcDeviceBuilder {
GrpcBase grpcBase = GrpcBuilderUtil.buildGrpcBaseByBO(entityBO);
entityGrpc.setBase(grpcBase);
CollectionOptional.ofNullable(entityBO.getProfileIds()).ifPresent(entityGrpc::addAllProfileIds);
if (Objects.nonNull(entityBO.getProfileId())) {
entityGrpc.addAllProfileIds(List.of(entityBO.getProfileId()));
}
Optional.ofNullable(entityBO.getDeviceExt())
.ifPresent(value -> entityGrpc.setDeviceExt(JsonUtil.toJsonString(value)));
Optional.ofNullable(entityBO.getEnableFlag())
@@ -170,24 +170,26 @@ public class DriverPointServer extends PointApiGrpc.PointApiImplBase {
|| !Objects.equals(deviceBO.getTenantId(), driverBO.getTenantId())) {
return Collections.emptySet();
}
return filterProfileId(request, deviceBO.getProfileIds());
return filterProfileId(request, deviceBO.getProfileId());
}
Set<Long> profileIds = deviceService.listByDriverId(driverBO.getId())
.stream()
.filter(device -> Objects.equals(driverBO.getTenantId(), device.getTenantId()))
.filter(device -> Objects.nonNull(device.getProfileIds()))
.flatMap(device -> device.getProfileIds().stream())
.map(DeviceBO::getProfileId)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
return filterProfileId(request, profileIds);
}
private Set<Long> filterProfileId(GrpcPagePointQuery request, List<Long> profileIds) {
if (Objects.isNull(profileIds)) {
private Set<Long> filterProfileId(GrpcPagePointQuery request, Long profileId) {
if (Objects.isNull(profileId)) {
return Collections.emptySet();
}
return filterProfileId(request, new LinkedHashSet<>(profileIds));
if (request.getProfileId() <= 0) {
return Set.of(profileId);
}
return Objects.equals(profileId, request.getProfileId()) ? Set.of(profileId) : Collections.emptySet();
}
private Set<Long> filterProfileId(GrpcPagePointQuery request, Set<Long> profileIds) {
@@ -201,9 +203,8 @@ public class DriverPointServer extends PointApiGrpc.PointApiImplBase {
return deviceService.listByDriverId(driverBO.getId())
.stream()
.filter(device -> Objects.equals(driverBO.getTenantId(), device.getTenantId()))
.map(DeviceBO::getProfileIds)
.map(DeviceBO::getProfileId)
.filter(Objects::nonNull)
.flatMap(List::stream)
.anyMatch(profileId -> Objects.equals(profileId, pointBO.getProfileId()));
}
@@ -1,34 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
/**
* <p>
* MyBatis-Plus mapper for the dc3_profile_bind table
* </p>
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
public interface ProfileBindMapper extends BaseMapper<ProfileBindDO> {
}
@@ -1,77 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.service;
import io.github.pnoker.common.base.service.BaseService;
import io.github.pnoker.common.manager.entity.bo.ProfileBindBO;
import io.github.pnoker.common.manager.entity.query.ProfileBindQuery;
import java.util.List;
/**
* Business service for profile binding operations.
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
public interface ProfileBindService extends BaseService<ProfileBindBO, ProfileBindQuery> {
/**
* Remove all profile bindings for the given device.
*
* @param deviceId Device ID
* @throws io.github.pnoker.common.exception.DeleteException when removal fails
*/
void removeByDeviceId(Long deviceId);
/**
* Remove the profile binding for the given device and profile.
*
* @param deviceId Device ID
* @param profileId Profile ID
* @throws io.github.pnoker.common.exception.DeleteException when removal fails
*/
void removeByDeviceIdAndProfileId(Long deviceId, Long profileId);
/**
* Device ID ID
*
* @param deviceId Device ID
* @param profileId Point ID
* @return ProfileBind
*/
ProfileBindBO getByDeviceIdAndProfileId(Long deviceId, Long profileId);
/**
* ID Device ID
*
* @param profileId Point ID
* @return Device ID
*/
List<Long> listDeviceIdsByProfileId(Long profileId);
/**
* Device ID ID
*
* @param deviceId Device ID
* @return ID
*/
List<Long> listProfileIdsByDeviceId(Long deviceId);
}
@@ -43,7 +43,6 @@ import io.github.pnoker.common.manager.entity.bo.DriverBO;
import io.github.pnoker.common.manager.entity.bo.PointAttributeBO;
import io.github.pnoker.common.manager.entity.bo.PointBO;
import io.github.pnoker.common.manager.entity.bo.ProfileBO;
import io.github.pnoker.common.manager.entity.bo.ProfileBindBO;
import io.github.pnoker.common.manager.entity.builder.DeviceBuilder;
import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.entity.query.DeviceQuery;
@@ -54,7 +53,6 @@ import io.github.pnoker.common.manager.service.DriverAttributeService;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.PointAttributeService;
import io.github.pnoker.common.manager.service.PointService;
import io.github.pnoker.common.manager.service.ProfileBindService;
import io.github.pnoker.common.manager.service.ProfileService;
import io.github.pnoker.common.utils.FieldUtil;
import io.github.pnoker.common.utils.JsonUtil;
@@ -80,7 +78,6 @@ import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -107,8 +104,6 @@ public class DeviceServiceImpl implements DeviceService {
private final PointService pointService;
private final ProfileBindService profileBindService;
private final DriverService driverService;
private final ProfileService profileService;
@@ -136,8 +131,6 @@ public class DeviceServiceImpl implements DeviceService {
throw new AddException("Failed to create device");
}
addProfileBind(entityDO, entityBO.getProfileIds());
//
MetadataEvent metadataEvent = new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.DEVICE,
MetadataOperateTypeEnum.ADD, driverServiceNames(entityDO.getDriverId()));
@@ -151,8 +144,6 @@ public class DeviceServiceImpl implements DeviceService {
Set<String> targetServices = driverServiceNames(entityDO.getDriverId());
//
profileBindService.removeByDeviceId(id);
if (!deviceManager.removeById(id)) {
throw new DeleteException("Failed to remove device");
}
@@ -178,19 +169,6 @@ public class DeviceServiceImpl implements DeviceService {
throw new DuplicateException("Failed to update device: device has been duplicated");
}
List<Long> newProfileIds = entityBO.getProfileIds();
List<Long> oldProfileIds = profileBindService.listProfileIdsByDeviceId(entityBO.getId());
//
ArrayList<Long> addIds = new ArrayList<>(newProfileIds);
addIds.removeAll(oldProfileIds);
addProfileBind(entityDO, addIds);
//
ArrayList<Long> deleteIds = new ArrayList<>(oldProfileIds);
deleteIds.removeAll(newProfileIds);
deleteIds.forEach(profileId -> profileBindService.removeByDeviceIdAndProfileId(entityBO.getId(), profileId));
entityDO = deviceBuilder.buildDOByBO(entityBO);
entityBO.setOperateTime(null);
if (!deviceManager.updateById(entityDO)) {
@@ -198,7 +176,6 @@ public class DeviceServiceImpl implements DeviceService {
}
DeviceBO deviceBO = getById(entityBO.getId());
deviceBO.setProfileIds(CollectionUtils.isEmpty(newProfileIds) ? oldProfileIds : newProfileIds);
entityBO.setDeviceName(deviceBO.getDeviceName());
//
@@ -217,9 +194,7 @@ public class DeviceServiceImpl implements DeviceService {
@Override
public DeviceBO getById(Long id) {
DeviceDO entityDO = getDOById(id, true);
DeviceBO entityBO = deviceBuilder.buildBOByDO(entityDO);
entityBO.setProfileIds(profileBindService.listProfileIdsByDeviceId(id));
return entityBO;
return deviceBuilder.buildBOByDO(entityDO);
}
@Override
@@ -229,9 +204,7 @@ public class DeviceServiceImpl implements DeviceService {
wrapper.eq(DeviceDO::getTenantId, tenantId);
wrapper.last(QueryWrapperConstant.LIMIT_ONE);
DeviceDO entityDO = deviceManager.getOne(wrapper);
DeviceBO entityBO = deviceBuilder.buildBOByDO(entityDO);
entityBO.setProfileIds(profileBindService.listProfileIdsByDeviceId(entityDO.getId()));
return entityBO;
return deviceBuilder.buildBOByDO(entityDO);
}
@Override
@@ -241,9 +214,7 @@ public class DeviceServiceImpl implements DeviceService {
.eq(DeviceDO::getTenantId, tenantId)
.last(QueryWrapperConstant.LIMIT_ONE);
DeviceDO entityDO = wrapper.one();
DeviceBO entityBO = deviceBuilder.buildBOByDO(entityDO);
entityBO.setProfileIds(profileBindService.listProfileIdsByDeviceId(entityDO.getId()));
return entityBO;
return deviceBuilder.buildBOByDO(entityDO);
}
@Override
@@ -251,10 +222,7 @@ public class DeviceServiceImpl implements DeviceService {
LambdaQueryWrapper<DeviceDO> wrapper = Wrappers.<DeviceDO>query().lambda();
wrapper.eq(DeviceDO::getDriverId, driverId);
List<DeviceDO> entityDOList = deviceManager.list(wrapper);
List<DeviceBO> deviceBOList = deviceBuilder.buildBOListByDOList(entityDOList);
deviceBOList
.forEach(device -> device.setProfileIds(profileBindService.listProfileIdsByDeviceId(device.getId())));
return deviceBOList;
return deviceBuilder.buildBOListByDOList(entityDOList);
}
@Override
@@ -266,7 +234,10 @@ public class DeviceServiceImpl implements DeviceService {
@Override
public List<DeviceBO> listByProfileId(Long profileId) {
return listByIds(profileBindService.listDeviceIdsByProfileId(profileId));
LambdaQueryWrapper<DeviceDO> wrapper = Wrappers.<DeviceDO>query().lambda();
wrapper.eq(DeviceDO::getProfileId, profileId);
List<DeviceDO> entityDOList = deviceManager.list(wrapper);
return deviceBuilder.buildBOListByDOList(entityDOList);
}
@Override
@@ -275,10 +246,7 @@ public class DeviceServiceImpl implements DeviceService {
return Collections.emptyList();
}
List<DeviceDO> entityDOList = deviceManager.listByIds(ids);
List<DeviceBO> deviceBOList = deviceBuilder.buildBOListByDOList(entityDOList);
deviceBOList
.forEach(device -> device.setProfileIds(profileBindService.listProfileIdsByDeviceId(device.getId())));
return deviceBOList;
return deviceBuilder.buildBOListByDOList(entityDOList);
}
@Override
@@ -288,17 +256,14 @@ public class DeviceServiceImpl implements DeviceService {
}
Page<DeviceDO> entityPageDO = deviceMapper.selectPageWithProfile(PageUtil.page(entityQuery.getPage()),
fuzzyQuery(entityQuery), entityQuery.getProfileId());
Page<DeviceBO> entityPageBO = deviceBuilder.buildBOPageByDOPage(entityPageDO);
entityPageBO.getRecords()
.forEach(device -> device.setProfileIds(profileBindService.listProfileIdsByDeviceId(device.getId())));
return entityPageBO;
return deviceBuilder.buildBOPageByDOPage(entityPageDO);
}
@Override
public void importDevice(DeviceBO entityBO, File file) {
validateTenantRelations(entityBO);
List<PointBO> pointBOList = pointService.selectByProfileIds(entityBO.getProfileIds()).stream()
List<PointBO> pointBOList = pointService.listByProfileId(entityBO.getProfileId()).stream()
.filter(pointBO -> Objects.equals(entityBO.getTenantId(), pointBO.getTenantId()))
.toList();
List<DriverAttributeBO> driverAttributeBOList = driverAttributeService.listByDriverId(entityBO.getDriverId())
@@ -359,7 +324,7 @@ public class DeviceServiceImpl implements DeviceService {
.stream()
.filter(attributeBO -> Objects.equals(entityBO.getTenantId(), attributeBO.getTenantId()))
.toList();
List<PointBO> pointBOList = pointService.selectByProfileIds(entityBO.getProfileIds()).stream()
List<PointBO> pointBOList = pointService.listByProfileId(entityBO.getProfileId()).stream()
.filter(pointBO -> Objects.equals(entityBO.getTenantId(), pointBO.getTenantId()))
.toList();
@@ -516,26 +481,6 @@ public class DeviceServiceImpl implements DeviceService {
return path;
}
private void addProfileBind(DeviceDO entityDO, List<Long> profileIds) {
if (CollectionUtils.isEmpty(profileIds)) {
return;
}
profileIds.forEach(profileId -> {
try {
ProfileBindBO entityBO = new ProfileBindBO();
entityBO.setProfileId(profileId);
entityBO.setDeviceId(entityDO.getId());
entityBO.setTenantId(entityDO.getTenantId());
profileBindService.add(entityBO);
} catch (Exception e) {
log.warn("Skip profile bind during device save, deviceId={}, profileId={}, error={}",
entityDO.getId(), profileId, e.getMessage(), e);
}
});
}
private void validateTenantRelations(DeviceBO entityBO) {
Long tenantId = entityBO.getTenantId();
DriverBO driverBO = driverService.getById(entityBO.getDriverId());
@@ -543,18 +488,12 @@ public class DeviceServiceImpl implements DeviceService {
throw new NotFoundException("Resource does not exist");
}
if (CollectionUtils.isEmpty(entityBO.getProfileIds())) {
if (Objects.isNull(entityBO.getProfileId())) {
return;
}
Set<Long> profileIds = new HashSet<>(entityBO.getProfileIds());
if (profileIds.remove(null)) {
throw new NotFoundException("Resource does not exist");
}
List<ProfileBO> profileBOList = profileService.listByIds(profileIds);
if (profileBOList.size() != profileIds.size() || profileBOList.stream()
.anyMatch(profileBO -> !Objects.equals(tenantId, profileBO.getTenantId()))) {
ProfileBO profileBO = profileService.getById(entityBO.getProfileId());
if (Objects.isNull(profileBO) || !Objects.equals(tenantId, profileBO.getTenantId())) {
throw new NotFoundException("Resource does not exist");
}
}
@@ -39,7 +39,6 @@ import io.github.pnoker.common.manager.entity.model.DriverDO;
import io.github.pnoker.common.manager.entity.model.PointDO;
import io.github.pnoker.common.manager.entity.query.DriverQuery;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.ProfileBindService;
import io.github.pnoker.common.utils.FieldUtil;
import io.github.pnoker.common.utils.PageUtil;
import lombok.RequiredArgsConstructor;
@@ -74,8 +73,6 @@ public class DriverServiceImpl implements DriverService {
private final PointManager pointManager;
private final ProfileBindService profileBindService;
@Override
public void add(DriverBO entityBO) {
checkDuplicate(entityBO, false, true);
@@ -147,19 +144,20 @@ public class DriverServiceImpl implements DriverService {
@Override
public List<DriverBO> listByProfileId(Long profileId) {
List<Long> ids = profileBindService.listDeviceIdsByProfileId(profileId);
if (CollectionUtils.isEmpty(ids)) {
LambdaQueryWrapper<DeviceDO> wrapper = Wrappers.<DeviceDO>query().lambda()
.eq(DeviceDO::getProfileId, profileId);
List<DeviceDO> deviceDOList = deviceManager.list(wrapper);
if (CollectionUtils.isEmpty(deviceDOList)) {
return Collections.emptyList();
}
List<DeviceDO> deviceDOList = deviceManager.listByIds(ids);
Set<Long> driverIds = deviceDOList.stream().map(DeviceDO::getDriverId).collect(Collectors.toSet());
List<DriverBO> entityDOList = listByIds(driverIds);
if (CollectionUtils.isEmpty(entityDOList)) {
List<DriverBO> driverBOList = listByIds(driverIds);
if (CollectionUtils.isEmpty(driverBOList)) {
return Collections.emptyList();
}
return entityDOList;
return driverBOList;
}
@Override
@@ -35,7 +35,6 @@ import io.github.pnoker.common.manager.dal.DeviceManager;
import io.github.pnoker.common.manager.dal.PointAttributeConfigManager;
import io.github.pnoker.common.manager.dal.PointAttributeManager;
import io.github.pnoker.common.manager.dal.PointManager;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.entity.bo.PointAttributeConfigBO;
import io.github.pnoker.common.manager.entity.bo.PointBO;
import io.github.pnoker.common.manager.entity.builder.PointAttributeConfigBuilder;
@@ -43,7 +42,6 @@ import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.entity.model.PointAttributeConfigDO;
import io.github.pnoker.common.manager.entity.model.PointAttributeDO;
import io.github.pnoker.common.manager.entity.model.PointDO;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
import io.github.pnoker.common.manager.entity.query.PointAttributeConfigQuery;
import io.github.pnoker.common.manager.event.metadata.MetadataEventPublisher;
import io.github.pnoker.common.manager.service.PointAttributeConfigService;
@@ -87,8 +85,6 @@ public class PointAttributeConfigServiceImpl implements PointAttributeConfigServ
private final PointAttributeManager pointAttributeManager;
private final ProfileBindManager profileBindManager;
@Override
public void add(PointAttributeConfigBO entityBO) {
validateTenantRelations(entityBO);
@@ -274,19 +270,11 @@ public class PointAttributeConfigServiceImpl implements PointAttributeConfigServ
|| !Objects.equals(entityBO.getTenantId(), pointDO.getTenantId())
|| !Objects.equals(entityBO.getTenantId(), attributeDO.getTenantId())
|| !Objects.equals(deviceDO.getDriverId(), attributeDO.getDriverId())
|| !profileBindExists(entityBO.getTenantId(), deviceDO.getId(), pointDO.getProfileId())) {
|| !Objects.equals(deviceDO.getProfileId(), pointDO.getProfileId())) {
throw new NotFoundException("Resource does not exist");
}
}
private boolean profileBindExists(Long tenantId, Long deviceId, Long profileId) {
return profileBindManager.lambdaQuery()
.eq(ProfileBindDO::getTenantId, tenantId)
.eq(ProfileBindDO::getDeviceId, deviceId)
.eq(ProfileBindDO::getProfileId, profileId)
.count() > 0;
}
/**
* Primary key ID
*
@@ -35,7 +35,6 @@ import io.github.pnoker.common.exception.NotFoundException;
import io.github.pnoker.common.exception.UpdateException;
import io.github.pnoker.common.manager.dal.PointAttributeConfigManager;
import io.github.pnoker.common.manager.dal.PointManager;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.entity.bo.DeviceByPointBO;
import io.github.pnoker.common.manager.entity.bo.DriverBO;
import io.github.pnoker.common.manager.entity.bo.PointBO;
@@ -45,14 +44,12 @@ import io.github.pnoker.common.manager.entity.builder.PointBuilder;
import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.entity.model.PointAttributeConfigDO;
import io.github.pnoker.common.manager.entity.model.PointDO;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
import io.github.pnoker.common.manager.entity.query.PointQuery;
import io.github.pnoker.common.manager.event.metadata.MetadataEventPublisher;
import io.github.pnoker.common.manager.mapper.DeviceMapper;
import io.github.pnoker.common.manager.mapper.PointMapper;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.PointService;
import io.github.pnoker.common.manager.service.ProfileBindService;
import io.github.pnoker.common.manager.service.ProfileService;
import io.github.pnoker.common.utils.FieldUtil;
import io.github.pnoker.common.utils.PageUtil;
@@ -88,14 +85,10 @@ public class PointServiceImpl implements PointService {
private final PointManager pointManager;
private final ProfileBindManager profileBindManager;
private final PointMapper pointMapper;
private final MetadataEventPublisher metadataEventPublisher;
private final ProfileBindService profileBindService;
private final ProfileService profileService;
private final PointAttributeConfigManager pointAttributeConfigManager;
@@ -116,7 +109,7 @@ public class PointServiceImpl implements PointService {
}
//
List<Long> deviceIds = profileBindService.listDeviceIdsByProfileId(entityDO.getProfileId());
List<Long> deviceIds = listDeviceIdsByProfileId(entityDO.getProfileId());
metadataEventPublisher.publishEvent(
new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT, MetadataOperateTypeEnum.ADD,
driverServiceNamesByDeviceIds(deviceIds)));
@@ -127,7 +120,7 @@ public class PointServiceImpl implements PointService {
@Transactional
public void delete(Long id) {
PointDO entityDO = getDOById(id, true);
List<Long> deviceIds = profileBindService.listDeviceIdsByProfileId(entityDO.getProfileId());
List<Long> deviceIds = listDeviceIdsByProfileId(entityDO.getProfileId());
Set<String> targetServices = driverServiceNamesByDeviceIds(deviceIds);
if (!pointManager.removeById(id)) {
@@ -145,7 +138,7 @@ public class PointServiceImpl implements PointService {
@Transactional
public void update(PointBO entityBO) {
PointDO current = getDOById(entityBO.getId(), true);
List<Long> oldDeviceIds = profileBindService.listDeviceIdsByProfileId(current.getProfileId());
List<Long> oldDeviceIds = listDeviceIdsByProfileId(current.getProfileId());
if (!Objects.equals(entityBO.getTenantId(), current.getTenantId())) {
throw new NotFoundException("Resource does not exist");
}
@@ -165,7 +158,7 @@ public class PointServiceImpl implements PointService {
MetadataOperateTypeEnum.UPDATE, driverServiceNamesByDeviceIds(oldDeviceIds));
metadataEventPublisher.publishEvent(metadataEvent);
} else {
List<Long> newDeviceIds = profileBindService.listDeviceIdsByProfileId(entityDO.getProfileId());
List<Long> newDeviceIds = listDeviceIdsByProfileId(entityDO.getProfileId());
metadataEventPublisher.publishEvent(new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT,
MetadataOperateTypeEnum.DELETE, driverServiceNamesByDeviceIds(oldDeviceIds)));
metadataEventPublisher.publishEvent(new MetadataEvent(this, entityDO.getId(), MetadataTypeEnum.POINT,
@@ -193,15 +186,10 @@ public class PointServiceImpl implements PointService {
@Override
public List<PointBO> listByDeviceId(Long deviceId) {
DeviceDO deviceDO = deviceMapper.selectById(deviceId);
if (Objects.isNull(deviceDO)) {
if (Objects.isNull(deviceDO) || Objects.isNull(deviceDO.getProfileId())) {
return Collections.emptyList();
}
LambdaQueryChainWrapper<ProfileBindDO> wrapper = profileBindManager.lambdaQuery()
.eq(ProfileBindDO::getTenantId, deviceDO.getTenantId())
.eq(ProfileBindDO::getDeviceId, deviceId);
List<ProfileBindDO> entityDOList = wrapper.list();
List<Long> profileIds = entityDOList.stream().map(ProfileBindDO::getProfileId).toList();
return selectByProfileIds(profileIds)
return listByProfileId(deviceDO.getProfileId())
.stream()
.filter(point -> Objects.equals(deviceDO.getTenantId(), point.getTenantId()))
.toList();
@@ -248,7 +236,7 @@ public class PointServiceImpl implements PointService {
PointBO pointBO = getById(pointId);
Set<Long> deviceIds = new HashSet<>();
profileBindService.listDeviceIdsByProfileId(pointBO.getProfileId()).forEach(deviceId -> {
listDeviceIdsByProfileId(pointBO.getProfileId()).forEach(deviceId -> {
List<PointAttributeConfigDO> dos = listByDeviceIdAndPointId(deviceId, pointId);
if (!dos.isEmpty()) {
deviceIds.add(deviceId);
@@ -269,32 +257,25 @@ public class PointServiceImpl implements PointService {
@Override
public Long getPointByDeviceId(Long deviceId) {
List<ProfileBindDO> bindDOList = profileBindManager
.list(new LambdaQueryWrapper<ProfileBindDO>().eq(ProfileBindDO::getDeviceId, deviceId));
if (CollectionUtils.isEmpty(bindDOList)) {
DeviceDO deviceDO = deviceMapper.selectById(deviceId);
if (Objects.isNull(deviceDO) || Objects.isNull(deviceDO.getProfileId())) {
return 0L;
}
long count = 0L;
for (ProfileBindDO bindDO : bindDOList) {
count += pointManager
.count(new LambdaQueryWrapper<PointDO>().eq(PointDO::getProfileId, bindDO.getProfileId()));
}
return count;
return pointManager.count(new LambdaQueryWrapper<PointDO>()
.eq(PointDO::getProfileId, deviceDO.getProfileId()));
}
@Override
public PointConfigByDeviceBO getPointConfigByDeviceId(Long deviceId) {
PointConfigByDeviceBO pointConfigByDeviceBO = new PointConfigByDeviceBO();
List<ProfileBindDO> bindDOList = profileBindManager
.list(new LambdaQueryWrapper<ProfileBindDO>().eq(ProfileBindDO::getDeviceId, deviceId));
pointConfigByDeviceBO.setConfigCount(0L);
if (CollectionUtils.isEmpty(bindDOList)) {
DeviceDO deviceDO = deviceMapper.selectById(deviceId);
if (Objects.isNull(deviceDO) || Objects.isNull(deviceDO.getProfileId())) {
pointConfigByDeviceBO.setUnConfigCount(0L);
return pointConfigByDeviceBO;
}
List<Long> profileIds = bindDOList.stream().map(ProfileBindDO::getProfileId).distinct().toList();
List<PointDO> allPoints = pointManager
.list(new LambdaQueryWrapper<PointDO>().in(PointDO::getProfileId, profileIds));
.list(new LambdaQueryWrapper<PointDO>().eq(PointDO::getProfileId, deviceDO.getProfileId()));
if (CollectionUtils.isEmpty(allPoints)) {
pointConfigByDeviceBO.setUnConfigCount(0L);
return pointConfigByDeviceBO;
@@ -421,6 +402,15 @@ public class PointServiceImpl implements PointService {
return Set.of(driverBO.getServiceName());
}
private List<Long> listDeviceIdsByProfileId(Long profileId) {
if (Objects.isNull(profileId)) {
return Collections.emptyList();
}
LambdaQueryWrapper<DeviceDO> wrapper = Wrappers.<DeviceDO>lambdaQuery()
.eq(DeviceDO::getProfileId, profileId);
return deviceMapper.selectList(wrapper).stream().map(DeviceDO::getId).toList();
}
/**
* Primary key ID
*
@@ -1,212 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.pnoker.common.constant.common.QueryWrapperConstant;
import io.github.pnoker.common.entity.common.Pages;
import io.github.pnoker.common.exception.AddException;
import io.github.pnoker.common.exception.DeleteException;
import io.github.pnoker.common.exception.DuplicateException;
import io.github.pnoker.common.exception.NotFoundException;
import io.github.pnoker.common.exception.UpdateException;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.entity.bo.ProfileBindBO;
import io.github.pnoker.common.manager.entity.builder.ProfileBindBuilder;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
import io.github.pnoker.common.manager.entity.query.ProfileBindQuery;
import io.github.pnoker.common.manager.service.ProfileBindService;
import io.github.pnoker.common.utils.FieldUtil;
import io.github.pnoker.common.utils.PageUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
/**
* Business service implementation for profile binding operations.
*
* @author pnoker
* @version 2025.9.0
* @since 2016.10.1
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ProfileBindServiceImpl implements ProfileBindService {
private final ProfileBindBuilder profileBindBuilder;
private final ProfileBindManager profileBindManager;
@Override
public void add(ProfileBindBO entityBO) {
if (checkDuplicate(entityBO, false)) {
throw new DuplicateException("Failed to create profile bind: profile bind has been duplicated");
}
ProfileBindDO entityDO = profileBindBuilder.buildDOByBO(entityBO);
if (!profileBindManager.save(entityDO)) {
throw new AddException("Failed to create profile bind");
}
}
@Override
public void delete(Long id) {
getDOById(id, true);
if (!profileBindManager.removeById(id)) {
throw new DeleteException("Failed to remove profile bind");
}
}
@Override
public void removeByDeviceId(Long deviceId) {
LambdaQueryWrapper<ProfileBindDO> wrapper = Wrappers.<ProfileBindDO>query().lambda();
wrapper.eq(ProfileBindDO::getDeviceId, deviceId);
if (profileBindManager.count(wrapper) == 0) {
return;
}
if (!profileBindManager.remove(wrapper)) {
throw new DeleteException("Failed to remove profile bind by deviceId");
}
}
@Override
public void removeByDeviceIdAndProfileId(Long deviceId, Long profileId) {
LambdaQueryWrapper<ProfileBindDO> wrapper = Wrappers.<ProfileBindDO>query().lambda();
wrapper.eq(ProfileBindDO::getDeviceId, deviceId);
wrapper.eq(ProfileBindDO::getProfileId, profileId);
if (profileBindManager.count(wrapper) == 0) {
return;
}
if (!profileBindManager.remove(wrapper)) {
throw new DeleteException("Failed to remove profile bind by deviceId and profileId");
}
}
@Override
public void update(ProfileBindBO entityBO) {
getDOById(entityBO.getId(), true);
if (checkDuplicate(entityBO, true)) {
throw new DuplicateException("Failed to update profile bind: profile bind has been duplicated");
}
ProfileBindDO entityDO = profileBindBuilder.buildDOByBO(entityBO);
entityBO.setOperateTime(null);
if (!profileBindManager.updateById(entityDO)) {
throw new UpdateException("Failed to update profile bind");
}
}
@Override
public ProfileBindBO getById(Long id) {
ProfileBindDO entityDO = getDOById(id, true);
return profileBindBuilder.buildBOByDO(entityDO);
}
@Override
public ProfileBindBO getByDeviceIdAndProfileId(Long deviceId, Long profileId) {
LambdaQueryChainWrapper<ProfileBindDO> wrapper = profileBindManager.lambdaQuery()
.eq(ProfileBindDO::getDeviceId, deviceId)
.eq(ProfileBindDO::getProfileId, profileId)
.last(QueryWrapperConstant.LIMIT_ONE);
ProfileBindDO entityDO = wrapper.one();
return profileBindBuilder.buildBOByDO(entityDO);
}
@Override
public List<Long> listDeviceIdsByProfileId(Long profileId) {
LambdaQueryChainWrapper<ProfileBindDO> wrapper = profileBindManager.lambdaQuery()
.eq(ProfileBindDO::getProfileId, profileId)
.select(ProfileBindDO::getDeviceId);
return wrapper.list().stream().map(ProfileBindDO::getDeviceId).toList();
}
@Override
public List<Long> listProfileIdsByDeviceId(Long deviceId) {
LambdaQueryChainWrapper<ProfileBindDO> wrapper = profileBindManager.lambdaQuery()
.eq(ProfileBindDO::getDeviceId, deviceId)
.select(ProfileBindDO::getProfileId);
return wrapper.list().stream().map(ProfileBindDO::getProfileId).toList();
}
@Override
public Page<ProfileBindBO> list(ProfileBindQuery entityQuery) {
if (Objects.isNull(entityQuery.getPage())) {
entityQuery.setPage(new Pages());
}
Page<ProfileBindDO> entityPageDO = profileBindManager.page(PageUtil.page(entityQuery.getPage()),
fuzzyQuery(entityQuery));
return profileBindBuilder.buildBOPageByDOPage(entityPageDO);
}
/**
* @param entityQuery {@link ProfileBindQuery}
* @return {@link LambdaQueryWrapper}
*/
private LambdaQueryWrapper<ProfileBindDO> fuzzyQuery(ProfileBindQuery entityQuery) {
LambdaQueryWrapper<ProfileBindDO> wrapper = Wrappers.<ProfileBindDO>query().lambda();
wrapper.eq(FieldUtil.isValidIdField(entityQuery.getProfileId()), ProfileBindDO::getProfileId,
entityQuery.getProfileId());
wrapper.eq(FieldUtil.isValidIdField(entityQuery.getDeviceId()), ProfileBindDO::getDeviceId,
entityQuery.getDeviceId());
wrapper.eq(Objects.nonNull(entityQuery.getTenantId()), ProfileBindDO::getTenantId, entityQuery.getTenantId());
return wrapper;
}
/**
* @param entityBO {@link ProfileBindBO}
* @param isUpdate
* @return
*/
private boolean checkDuplicate(ProfileBindBO entityBO, boolean isUpdate) {
LambdaQueryWrapper<ProfileBindDO> wrapper = Wrappers.<ProfileBindDO>query().lambda();
wrapper.eq(ProfileBindDO::getDeviceId, entityBO.getDeviceId());
wrapper.eq(ProfileBindDO::getProfileId, entityBO.getProfileId());
wrapper.eq(ProfileBindDO::getTenantId, entityBO.getTenantId());
wrapper.last(QueryWrapperConstant.LIMIT_ONE);
ProfileBindDO one = profileBindManager.getOne(wrapper);
if (Objects.isNull(one)) {
return false;
}
return !isUpdate || !one.getId().equals(entityBO.getId());
}
/**
* Primary key ID
*
* @param id ID
* @param throwException
* @return {@link ProfileBindDO}
*/
private ProfileBindDO getDOById(Long id, boolean throwException) {
ProfileBindDO entityDO = profileBindManager.getById(id);
if (throwException && Objects.isNull(entityDO)) {
throw new NotFoundException("Profile bind does not exist");
}
return entityDO;
}
}
@@ -33,13 +33,11 @@ import io.github.pnoker.common.exception.DuplicateException;
import io.github.pnoker.common.exception.NotFoundException;
import io.github.pnoker.common.exception.UpdateException;
import io.github.pnoker.common.manager.dal.PointManager;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.dal.ProfileManager;
import io.github.pnoker.common.manager.entity.bo.ProfileBO;
import io.github.pnoker.common.manager.entity.builder.ProfileBuilder;
import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.entity.model.PointDO;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
import io.github.pnoker.common.manager.entity.model.ProfileDO;
import io.github.pnoker.common.manager.entity.query.ProfileQuery;
import io.github.pnoker.common.manager.mapper.DeviceMapper;
@@ -57,7 +55,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Business service implementation for profile operations.
@@ -75,8 +72,6 @@ public class ProfileServiceImpl implements ProfileService {
private final ProfileManager profileManager;
private final ProfileBindManager profileBindManager;
private final PointManager pointManager;
private final ProfileMapper profileMapper;
@@ -157,19 +152,17 @@ public class ProfileServiceImpl implements ProfileService {
@Override
public List<ProfileBO> listByDeviceId(Long deviceId) {
DeviceDO deviceDO = deviceMapper.selectById(deviceId);
if (Objects.isNull(deviceDO)) {
LambdaQueryWrapper<DeviceDO> wrapper = Wrappers.<DeviceDO>lambdaQuery()
.eq(DeviceDO::getId, deviceId);
DeviceDO deviceDO = deviceMapper.selectOne(wrapper);
if (Objects.isNull(deviceDO) || Objects.isNull(deviceDO.getProfileId())) {
return Collections.emptyList();
}
LambdaQueryChainWrapper<ProfileBindDO> wrapper = profileBindManager.lambdaQuery()
.eq(ProfileBindDO::getTenantId, deviceDO.getTenantId())
.eq(ProfileBindDO::getDeviceId, deviceId);
List<ProfileBindDO> entityDOList = wrapper.list();
Set<Long> profileIds = entityDOList.stream().map(ProfileBindDO::getProfileId).collect(Collectors.toSet());
return listByIds(profileIds)
.stream()
.filter(profile -> Objects.equals(deviceDO.getTenantId(), profile.getTenantId()))
.toList();
ProfileBO profile = getById(deviceDO.getProfileId());
if (Objects.isNull(profile) || !Objects.equals(deviceDO.getTenantId(), profile.getTenantId())) {
return Collections.emptyList();
}
return List.of(profile);
}
@Override
@@ -23,7 +23,6 @@ import com.baomidou.mybatisplus.extension.toolkit.Db;
import io.github.pnoker.common.entity.common.Pages;
import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.entity.model.PointDO;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
import io.github.pnoker.common.manager.entity.query.TopicQuery;
import io.github.pnoker.common.manager.entity.vo.TopicVO;
import io.github.pnoker.common.manager.mapper.DeviceMapper;
@@ -70,25 +69,22 @@ public class TopicServiceImpl extends ServiceImpl<DeviceMapper, DeviceDO> implem
for (DeviceDO device : deviceList) {
String deviceName = device.getDeviceName();
Long deviceId = device.getId();
List<ProfileBindDO> profileBinds = Db.lambdaQuery(ProfileBindDO.class)
.eq(ProfileBindDO::getDeviceId, deviceId)
.eq(Objects.nonNull(topicQuery.getTenantId()), ProfileBindDO::getTenantId, topicQuery.getTenantId())
Long profileId = device.getProfileId();
if (Objects.isNull(profileId)) {
continue;
}
List<PointDO> points = Db.lambdaQuery(PointDO.class)
.eq(PointDO::getProfileId, profileId)
.eq(Objects.nonNull(topicQuery.getTenantId()), PointDO::getTenantId, topicQuery.getTenantId())
// .eq(PointDO::getEnableFlag, 1)
.eq(PointDO::getDeleted, 0)
.list();
for (ProfileBindDO profileBind : profileBinds) {
Long profileBindId = profileBind.getProfileId();
List<PointDO> points = Db.lambdaQuery(PointDO.class)
.eq(PointDO::getProfileId, profileBindId)
.eq(Objects.nonNull(topicQuery.getTenantId()), PointDO::getTenantId, topicQuery.getTenantId())
// .eq(PointDO::getEnableFlag, 1)
.eq(PointDO::getDeleted, 0)
.list();
for (PointDO point : points) {
TopicVO topicVO = new TopicVO();
topicVO.setTopic("dc3/dc3-center-data/device/" + deviceId);
topicVO.setDeviceName(deviceName);
topicVO.setPointName(point.getPointName());
topicVOList.add(topicVO);
}
for (PointDO point : points) {
TopicVO topicVO = new TopicVO();
topicVO.setTopic("dc3/dc3-center-data/device/" + deviceId);
topicVO.setDeviceName(deviceName);
topicVO.setPointName(point.getPointName());
topicVOList.add(topicVO);
}
}
int totalItems = topicVOList.size();
@@ -80,16 +80,17 @@
</select>
<!--
One device can bind to multiple profiles, so the total here can
exceed the device count. Only active bindings participate.
Device count grouped by profile_id. Only devices with a non-null
profile_id are counted.
-->
<select id="countDeviceByProfile"
resultType="io.github.pnoker.common.manager.entity.bo.dashboard.BucketRow">
SELECT b.profile_id AS key, COUNT(*) AS count
FROM dc3_profile_bind b
WHERE b.deleted = 0
AND b.tenant_id = #{tenantId}
GROUP BY b.profile_id
SELECT d.profile_id AS key, COUNT(*) AS count
FROM dc3_device d
WHERE d.deleted = 0
AND d.tenant_id = #{tenantId}
AND d.profile_id IS NOT NULL
GROUP BY d.profile_id
ORDER BY count
DESC
LIMIT #{limit}
@@ -140,11 +141,7 @@
SELECT dev.id AS id,
dev.device_name AS device_name,
dev.driver_id AS driver_id,
(SELECT COUNT(*)
FROM dc3_profile_bind pb
WHERE pb.device_id = dev.id
AND pb.tenant_id = dev.tenant_id
AND pb.deleted = 0) AS profile_count
(CASE WHEN dev.profile_id IS NOT NULL THEN 1 ELSE 0 END) AS profile_count
FROM dc3_device dev
WHERE dev.deleted = 0
AND dev.tenant_id = #{tenantId}
@@ -156,12 +153,13 @@
<select id="topologyProfileBindings"
resultType="io.github.pnoker.common.manager.entity.bo.dashboard.ProfileBindingRow">
SELECT pb.profile_id AS profile_id,
pb.device_id AS device_id
FROM dc3_profile_bind pb
WHERE pb.deleted = 0
AND pb.tenant_id = #{tenantId}
AND pb.device_id IN
SELECT d.profile_id AS profile_id,
d.id AS device_id
FROM dc3_device d
WHERE d.deleted = 0
AND d.tenant_id = #{tenantId}
AND d.profile_id IS NOT NULL
AND d.id IN
<foreach collection="deviceIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
@@ -39,16 +39,8 @@
dd.operate_time,
dd.deleted
from dc3_device dd
<if test="profileId != null and profileId != '' and profileId > 0">
inner join dc3_profile_bind dpb
on dd.id = dpb.device_id
and dpb.tenant_id = dd.tenant_id
and dpb.deleted = 0
inner join dc3_profile dp
on dpb.profile_id = dp.id
and dp.id = #{profileId}
and dp.tenant_id = dd.tenant_id
and dp.deleted = 0
<if test="profileId != null and profileId > 0">
and dd.profile_id = #{profileId}
</if>
${ew.customSqlSegment}
</select>
@@ -45,14 +45,10 @@
dp.operate_time,
dp.deleted
from dc3_point dp
<if test="deviceId != null and deviceId != '' and deviceId > 0">
inner join dc3_profile_bind dpb
on dp.profile_id = dpb.profile_id
and dpb.device_id = #{deviceId}
and dpb.tenant_id = dp.tenant_id
and dpb.deleted = 0
<if test="deviceId != null and deviceId > 0">
inner join dc3_device dd
on dpb.device_id = dd.id
on dd.profile_id = dp.profile_id
and dd.id = #{deviceId}
and dd.tenant_id = dp.tenant_id
and dd.deleted = 0
</if>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present the IoT DC3 original author or authors.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License, or (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.github.pnoker.common.manager.mapper.ProfileBindMapper">
</mapper>
@@ -39,13 +39,9 @@
dp.operate_time,
dp.deleted
from dc3_profile dp
<if test="deviceId != null and deviceId != '' and deviceId > 0">
inner join dc3_profile_bind dpb
on dp.id = dpb.profile_id
and dpb.tenant_id = dp.tenant_id
and dpb.deleted = 0
<if test="deviceId != null and deviceId > 0">
inner join dc3_device dd
on dpb.device_id = dd.id
on dd.profile_id = dp.id
and dd.id = #{deviceId}
and dd.tenant_id = dp.tenant_id
and dd.deleted = 0
@@ -27,6 +27,7 @@ import io.github.pnoker.common.manager.biz.ImportDeviceService;
import io.github.pnoker.common.manager.dal.DeviceManager;
import io.github.pnoker.common.manager.entity.bo.DeviceBO;
import io.github.pnoker.common.manager.entity.bo.DriverBO;
import io.github.pnoker.common.manager.entity.bo.ProfileBO;
import io.github.pnoker.common.manager.entity.builder.DeviceBuilder;
import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.event.metadata.MetadataEventPublisher;
@@ -35,7 +36,6 @@ import io.github.pnoker.common.manager.service.DriverAttributeService;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.PointAttributeService;
import io.github.pnoker.common.manager.service.PointService;
import io.github.pnoker.common.manager.service.ProfileBindService;
import io.github.pnoker.common.manager.service.ProfileService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -44,13 +44,10 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -70,9 +67,6 @@ class DeviceServiceImplTest {
@Mock
private PointService pointService;
@Mock
private ProfileBindService profileBindService;
@Mock
private DriverService driverService;
@@ -106,7 +100,7 @@ class DeviceServiceImplTest {
bo.setDeviceCode("boiler-a");
bo.setDriverId(7L);
bo.setTenantId(100L);
bo.setProfileIds(new ArrayList<>());
bo.setProfileId(5L);
doRow = new DeviceDO();
doRow.setId(1L);
@@ -121,7 +115,10 @@ class DeviceServiceImplTest {
@Test
void saveSucceedsForUniqueDeviceWithMatchingTenantDriver() {
ProfileBO profile = new ProfileBO();
profile.setTenantId(100L);
when(driverService.getById(7L)).thenReturn(driver);
when(profileService.getById(5L)).thenReturn(profile);
when(deviceManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
when(deviceBuilder.buildDOByBO(bo)).thenReturn(doRow);
when(deviceManager.save(doRow)).thenReturn(true);
@@ -146,7 +143,10 @@ class DeviceServiceImplTest {
@Test
void saveRejectsDuplicateDeviceName() {
ProfileBO profile = new ProfileBO();
profile.setTenantId(100L);
when(driverService.getById(7L)).thenReturn(driver);
when(profileService.getById(5L)).thenReturn(profile);
when(deviceManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
assertThatThrownBy(() -> service.add(bo)).isInstanceOf(DuplicateException.class);
verify(deviceManager, never()).save(any(DeviceDO.class));
@@ -154,7 +154,10 @@ class DeviceServiceImplTest {
@Test
void saveThrowsAddExceptionWhenManagerReturnsFalse() {
ProfileBO profile = new ProfileBO();
profile.setTenantId(100L);
when(driverService.getById(7L)).thenReturn(driver);
when(profileService.getById(5L)).thenReturn(profile);
when(deviceManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
when(deviceBuilder.buildDOByBO(bo)).thenReturn(doRow);
when(deviceManager.save(doRow)).thenReturn(false);
@@ -167,15 +170,6 @@ class DeviceServiceImplTest {
assertThatThrownBy(() -> service.delete(1L)).isInstanceOf(NotFoundException.class);
}
@Test
void removeRollsBackWhenProfileBindRemovalFails() {
when(deviceManager.getById(1L)).thenReturn(doRow);
doThrow(new DeleteException("Failed to remove profile bind"))
.when(profileBindService).removeByDeviceId(1L);
assertThatThrownBy(() -> service.delete(1L)).isInstanceOf(DeleteException.class);
verify(deviceManager, never()).removeById(any(Long.class));
}
@Test
void removeRollsBackWhenManagerRemoveReturnsFalse() {
when(deviceManager.getById(1L)).thenReturn(doRow);
@@ -30,7 +30,6 @@ import io.github.pnoker.common.manager.entity.bo.DriverBO;
import io.github.pnoker.common.manager.entity.builder.DriverBuilder;
import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.entity.model.DriverDO;
import io.github.pnoker.common.manager.service.ProfileBindService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -64,9 +63,6 @@ class DriverServiceImplTest {
@Mock
private PointManager pointManager;
@Mock
private ProfileBindService profileBindService;
@InjectMocks
private DriverServiceImpl service;
@@ -194,7 +190,7 @@ class DriverServiceImplTest {
@Test
void listByProfileIdReturnsEmptyWhenNoDeviceBound() {
when(profileBindService.listDeviceIdsByProfileId(5L)).thenReturn(List.of());
when(deviceManager.list(any(LambdaQueryWrapper.class))).thenReturn(List.of());
assertThat(service.listByProfileId(5L)).isEmpty();
verify(deviceManager, never()).listByIds(any());
}
@@ -207,8 +203,7 @@ class DriverServiceImplTest {
DeviceDO d2 = new DeviceDO();
d2.setId(11L);
d2.setDriverId(1L);
when(profileBindService.listDeviceIdsByProfileId(5L)).thenReturn(List.of(10L, 11L));
when(deviceManager.listByIds(List.of(10L, 11L))).thenReturn(List.of(d1, d2));
when(deviceManager.list(any(LambdaQueryWrapper.class))).thenReturn(List.of(d1, d2));
when(driverManager.listByIds(Set.of(1L))).thenReturn(List.of(doRow));
when(driverBuilder.buildBOListByDOList(List.of(doRow))).thenReturn(List.of(bo));
@@ -26,16 +26,15 @@ import io.github.pnoker.common.exception.NotFoundException;
import io.github.pnoker.common.exception.UpdateException;
import io.github.pnoker.common.manager.dal.PointAttributeConfigManager;
import io.github.pnoker.common.manager.dal.PointManager;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.entity.bo.PointBO;
import io.github.pnoker.common.manager.entity.bo.ProfileBO;
import io.github.pnoker.common.manager.entity.builder.PointBuilder;
import io.github.pnoker.common.manager.entity.model.DeviceDO;
import io.github.pnoker.common.manager.entity.model.PointDO;
import io.github.pnoker.common.manager.event.metadata.MetadataEventPublisher;
import io.github.pnoker.common.manager.mapper.DeviceMapper;
import io.github.pnoker.common.manager.mapper.PointMapper;
import io.github.pnoker.common.manager.service.DriverService;
import io.github.pnoker.common.manager.service.ProfileBindService;
import io.github.pnoker.common.manager.service.ProfileService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -66,18 +65,12 @@ class PointServiceImplTest {
@Mock
private PointManager pointManager;
@Mock
private ProfileBindManager profileBindManager;
@Mock
private PointMapper pointMapper;
@Mock
private MetadataEventPublisher metadataEventPublisher;
@Mock
private ProfileBindService profileBindService;
@Mock
private ProfileService profileService;
@@ -124,7 +117,7 @@ class PointServiceImplTest {
when(pointManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
when(pointBuilder.buildDOByBO(bo)).thenReturn(doRow);
when(pointManager.save(doRow)).thenReturn(true);
when(profileBindService.listDeviceIdsByProfileId(5L)).thenReturn(List.of());
when(deviceMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of());
assertThatNoException().isThrownBy(() -> service.add(bo));
verify(metadataEventPublisher, atLeastOnce()).publishEvent(any(MetadataEvent.class));
@@ -170,6 +163,7 @@ class PointServiceImplTest {
@Test
void removeThrowsDeleteExceptionWhenManagerReturnsFalse() {
when(pointManager.getById(1L)).thenReturn(doRow);
when(deviceMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of());
when(pointManager.removeById(1L)).thenReturn(false);
assertThatThrownBy(() -> service.delete(1L)).isInstanceOf(DeleteException.class);
}
@@ -178,7 +172,11 @@ class PointServiceImplTest {
void removeSucceedsAndPublishesEvents() {
when(pointManager.getById(1L)).thenReturn(doRow);
when(pointManager.removeById(1L)).thenReturn(true);
when(profileBindService.listDeviceIdsByProfileId(5L)).thenReturn(List.of(10L, 11L));
DeviceDO device1 = new DeviceDO();
device1.setId(10L);
DeviceDO device2 = new DeviceDO();
device2.setId(11L);
when(deviceMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(device1, device2));
assertThatNoException().isThrownBy(() -> service.delete(1L));
// 1 DELETE for the point itself + 2 UPDATE for affected devices
verify(metadataEventPublisher, atLeastOnce()).publishEvent(any(MetadataEvent.class));
@@ -208,6 +206,7 @@ class PointServiceImplTest {
when(profileService.getById(5L)).thenReturn(profile);
PointDO other = new PointDO();
other.setId(2L);
when(deviceMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of());
when(pointManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(other);
assertThatThrownBy(() -> service.update(bo)).isInstanceOf(DuplicateException.class);
}
@@ -216,6 +215,7 @@ class PointServiceImplTest {
void updateThrowsUpdateExceptionWhenManagerReturnsFalse() {
when(pointManager.getById(1L)).thenReturn(doRow);
when(profileService.getById(5L)).thenReturn(profile);
when(deviceMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of());
when(pointManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
when(pointBuilder.buildDOByBO(bo)).thenReturn(doRow);
when(pointManager.updateById(doRow)).thenReturn(false);
@@ -1,159 +0,0 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.github.pnoker.common.exception.AddException;
import io.github.pnoker.common.exception.DeleteException;
import io.github.pnoker.common.exception.DuplicateException;
import io.github.pnoker.common.exception.NotFoundException;
import io.github.pnoker.common.exception.UpdateException;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.entity.bo.ProfileBindBO;
import io.github.pnoker.common.manager.entity.builder.ProfileBindBuilder;
import io.github.pnoker.common.manager.entity.model.ProfileBindDO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ProfileBindServiceImplTest {
@Mock
private ProfileBindBuilder profileBindBuilder;
@Mock
private ProfileBindManager profileBindManager;
@InjectMocks
private ProfileBindServiceImpl service;
private ProfileBindBO bo;
private ProfileBindDO doRow;
@BeforeEach
void setUp() {
bo = new ProfileBindBO();
bo.setId(1L);
bo.setProfileId(5L);
bo.setDeviceId(10L);
bo.setTenantId(100L);
doRow = new ProfileBindDO();
doRow.setId(1L);
doRow.setProfileId(5L);
doRow.setDeviceId(10L);
doRow.setTenantId(100L);
}
@Test
void saveSucceedsForUniqueBinding() {
when(profileBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
when(profileBindBuilder.buildDOByBO(bo)).thenReturn(doRow);
when(profileBindManager.save(doRow)).thenReturn(true);
assertThatNoException().isThrownBy(() -> service.add(bo));
}
@Test
void saveRejectsDuplicate() {
when(profileBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
assertThatThrownBy(() -> service.add(bo)).isInstanceOf(DuplicateException.class);
verify(profileBindManager, never()).save(any(ProfileBindDO.class));
}
@Test
void saveThrowsAddExceptionWhenManagerReturnsFalse() {
when(profileBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
when(profileBindBuilder.buildDOByBO(bo)).thenReturn(doRow);
when(profileBindManager.save(doRow)).thenReturn(false);
assertThatThrownBy(() -> service.add(bo)).isInstanceOf(AddException.class);
}
@Test
void removeRejectsUnknownId() {
when(profileBindManager.getById(1L)).thenReturn(null);
assertThatThrownBy(() -> service.delete(1L)).isInstanceOf(NotFoundException.class);
}
@Test
void removeThrowsDeleteExceptionWhenManagerReturnsFalse() {
when(profileBindManager.getById(1L)).thenReturn(doRow);
when(profileBindManager.removeById(1L)).thenReturn(false);
assertThatThrownBy(() -> service.delete(1L)).isInstanceOf(DeleteException.class);
}
@Test
void removeByDeviceIdNoOpsWhenNoBinding() {
when(profileBindManager.count(any(LambdaQueryWrapper.class))).thenReturn(0L);
service.removeByDeviceId(99L);
verify(profileBindManager, never()).remove(any(LambdaQueryWrapper.class));
}
@Test
void removeByDeviceIdDelegatesToManagerWhenBindingsExist() {
when(profileBindManager.count(any(LambdaQueryWrapper.class))).thenReturn(2L);
when(profileBindManager.remove(any(LambdaQueryWrapper.class))).thenReturn(true);
service.removeByDeviceId(10L);
verify(profileBindManager).remove(any(LambdaQueryWrapper.class));
}
@Test
void removeByDeviceIdThrowsWhenManagerRemoveReturnsFalse() {
when(profileBindManager.count(any(LambdaQueryWrapper.class))).thenReturn(2L);
when(profileBindManager.remove(any(LambdaQueryWrapper.class))).thenReturn(false);
assertThatThrownBy(() -> service.removeByDeviceId(10L)).isInstanceOf(DeleteException.class);
}
@Test
void removeByDeviceIdAndProfileIdNoOpsWhenNoBinding() {
when(profileBindManager.count(any(LambdaQueryWrapper.class))).thenReturn(0L);
service.removeByDeviceIdAndProfileId(10L, 5L);
verify(profileBindManager, never()).remove(any(LambdaQueryWrapper.class));
}
@Test
void updateRejectsUnknownId() {
when(profileBindManager.getById(1L)).thenReturn(null);
assertThatThrownBy(() -> service.update(bo)).isInstanceOf(NotFoundException.class);
}
@Test
void updateThrowsUpdateExceptionWhenManagerReturnsFalse() {
when(profileBindManager.getById(1L)).thenReturn(doRow);
when(profileBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
when(profileBindBuilder.buildDOByBO(bo)).thenReturn(doRow);
when(profileBindManager.updateById(doRow)).thenReturn(false);
assertThatThrownBy(() -> service.update(bo)).isInstanceOf(UpdateException.class);
}
@Test
void getByIdRejectsUnknownId() {
when(profileBindManager.getById(1L)).thenReturn(null);
assertThatThrownBy(() -> service.getById(1L)).isInstanceOf(NotFoundException.class);
}
}
@@ -23,7 +23,6 @@ import io.github.pnoker.common.exception.DuplicateException;
import io.github.pnoker.common.exception.NotFoundException;
import io.github.pnoker.common.exception.UpdateException;
import io.github.pnoker.common.manager.dal.PointManager;
import io.github.pnoker.common.manager.dal.ProfileBindManager;
import io.github.pnoker.common.manager.dal.ProfileManager;
import io.github.pnoker.common.manager.entity.bo.ProfileBO;
import io.github.pnoker.common.manager.entity.builder.ProfileBuilder;
@@ -57,9 +56,6 @@ class ProfileServiceImplTest {
@Mock
private ProfileManager profileManager;
@Mock
private ProfileBindManager profileBindManager;
@Mock
private PointManager pointManager;
@@ -186,7 +182,7 @@ class ProfileServiceImplTest {
@Test
void listByDeviceIdReturnsEmptyWhenDeviceMissing() {
when(deviceMapper.selectById(99L)).thenReturn(null);
when(deviceMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);
assertThat(service.listByDeviceId(99L)).isEmpty();
}