fix(J): 清理孤儿涂鸦设备与测试联动数据, 解绑时联动失效设备与AI档案

数据层(V24 迁移): 逻辑删除无涂鸦绑定记录的孤儿涂鸦设备及其AI档案; 物理删除测试阶段的联动规则、场景与场景执行记录(条件相同但动作矛盾的测试数据); 保留产品/物模型/家庭/成员/活动日志。幂等可重复执行。

代码层(根治): TuyaCloudService.unbindUser 解绑时联动逻辑删除该用户的涂鸦设备与AI档案, 避免解绑后设备仍在列表被联动规则反复命中而刷错误日志; 采用逻辑删除, 用户重新绑定同步时 restoreById 自动恢复。
This commit is contained in:
2995840376 2026-10-27 21:56:45 +08:00
parent 4d8d38909d
commit 91d50eda91
4 changed files with 97 additions and 1 deletions

View File

@ -5,6 +5,7 @@ import com.iot.device.entity.AiDeviceProfile;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
/**
* AI 设备控制档案 Mapper(iot-device 侧写入)
@ -27,4 +28,20 @@ public interface AiDeviceProfileMapper extends BaseMapper<AiDeviceProfile> {
+ "AND deleted = 0 LIMIT 1")
AiDeviceProfile selectByProductAndOwner(@Param("productId") Long productId,
@Param("ownerId") Long ownerId);
/**
* 逻辑删除指定用户的 AI 档案(解绑涂鸦账号时联动失效)
* <p>
* 档案里的指令码(power_code 等)是按涂鸦 DP 识别的,设备失效后这些档案不再可用;
* 保留会指向已失效设备,重新绑定后应由 AI 档案检测流程重新生成。
* <p>
* 按 owner_id 失效而非 JOIN device 比较 device_id:ai_device_profile.device_id 与
* device.device_id 的排序规则不同(utf8mb4_unicode_ci vs utf8mb4_0900_ai_ci),
* 跨表字符串比较会报 ERROR 1267 Illegal mix of collations。
*
* @return 被失效的档案数量
*/
@Update("UPDATE ai_device_profile SET deleted = 1, update_time = NOW() "
+ "WHERE deleted = 0 AND owner_id = #{ownerId}")
int invalidateProfilesByOwner(@Param("ownerId") Long ownerId);
}

View File

@ -44,4 +44,21 @@ public interface DeviceMapper extends BaseMapper<Device> {
*/
@Update("UPDATE device SET deleted = 0 WHERE id = #{id}")
int restoreById(@Param("id") Long id);
/**
* 逻辑删除指定用户的全部涂鸦设备(解绑涂鸦账号时联动失效)
* <p>
* 背景:解绑涂鸦账号后,绑定记录被物理删除,但同步来的设备仍留在设备列表,
* 形成"孤儿涂鸦设备"——设备无法再通过涂鸦云端控制,却仍会被联动规则命中,
* 每条规则触发都会产生一次失败指令与错误日志。
* <p>
* 这里做逻辑删除(deleted=1)而非物理删除:device 表 uk_device_id 唯一索引不含
* deleted,物理删除还会让 device_state / device_activity_log 等历史记录悬空;
* 用户重新绑定并同步时,restoreById 会把记录自动恢复。
*
* @return 被失效的设备数量
*/
@Update("UPDATE device SET deleted = 1, update_time = NOW() "
+ "WHERE deleted = 0 AND platform = 'tuya' AND owner_id = #{ownerId}")
int invalidateTuyaDevicesByOwner(@Param("ownerId") Long ownerId);
}

View File

@ -8,6 +8,7 @@ import com.iot.device.dto.tuya.*;
import com.iot.device.entity.Device;
import com.iot.device.entity.PlatformConfig;
import com.iot.device.entity.TuyaUserBinding;
import com.iot.device.mapper.AiDeviceProfileMapper;
import com.iot.device.mapper.DeviceMapper;
import com.iot.device.mapper.PlatformConfigMapper;
import com.iot.device.mapper.TuyaUserBindingMapper;
@ -45,6 +46,7 @@ public class TuyaCloudService {
private final PlatformConfigMapper platformConfigMapper;
private final TuyaUserBindingMapper tuyaUserBindingMapper;
private final DeviceMapper deviceMapper;
private final AiDeviceProfileMapper aiDeviceProfileMapper;
private final StringRedisTemplate redisTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
@ -435,11 +437,20 @@ public class TuyaCloudService {
/**
* 解绑涂鸦账号(物理删除,避免唯一索引冲突)
* <p>
* 同时联动失效该用户同步来的涂鸦设备与 AI 档案:
* 解绑后这些设备已无法通过涂鸦云端控制,若继续留在设备列表并被联动规则命中,
* 每次规则触发都会产生一条失败指令与错误日志(历史问题:孤儿涂鸦设备)。
* 设备采用逻辑删除,重新绑定并同步时会被自动恢复。
*/
public void unbindUser(Long memberId) {
// 先删除该用户所有绑定记录(防止有重复行导致唯一索引冲突)
tuyaUserBindingMapper.deleteByMemberId(memberId);
log.info("会员 {} 解绑了涂鸦账号(物理删除)", memberId);
// 联动失效:涂鸦设备 + 指向它们的 AI 档案
int invalidatedDevices = deviceMapper.invalidateTuyaDevicesByOwner(memberId);
int invalidatedProfiles = aiDeviceProfileMapper.invalidateProfilesByOwner(memberId);
log.info("会员 {} 解绑了涂鸦账号(物理删除),联动失效涂鸦设备 {} 个、AI 档案 {} 条",
memberId, invalidatedDevices, invalidatedProfiles);
}
// ==================== SMS验证码绑定(正式模式 - 调用涂鸦API) ====================

View File

@ -0,0 +1,51 @@
-- =====================================================================
-- V24 · 清理测试阶段遗留的孤儿涂鸦设备与测试联动数据
-- ---------------------------------------------------------------------
-- 背景:
-- 1) unbindUser 早期只物理删除 tuya_user_binding 绑定记录,不处理已同步的设备,
-- 于是产生"孤儿涂鸦设备":设备仍在设备列表,却已无涂鸦账号可控制。
-- 定时场景每分钟命中的联动规则会持续对它们下发指令,全部失败并刷错误日志。
-- (代码侧已修复:解绑时联动逻辑删除设备与 AI 档案,重新绑定同步时自动恢复)
-- 2) 测试阶段遗留的联动规则与场景:条件全部相同(switch_1 == true)但动作互相矛盾
-- (switch_1=true / switch_1=0 / {"1":"open"} 混用 dpId),会下发自相矛盾的指令。
--
-- 清理范围:
-- - 逻辑删除孤儿涂鸦设备(owner 已无涂鸦绑定记录)
-- - 逻辑删除指向这些设备的 AI 档案
-- - 删除测试联动规则与测试场景(含每分钟 cron 的 bb 场景)
-- 保留:产品与物模型(product / product_property / product_event / product_action),
-- 家庭与成员、设备活动日志(历史审计)等。
--
-- 无存储过程、无 DELIMITER;幂等可重复执行。
-- =====================================================================
-- ---------- 1) 逻辑删除孤儿涂鸦设备 ----------
UPDATE device d
SET d.deleted = 1,
d.update_time = NOW()
WHERE d.deleted = 0
AND d.platform = 'tuya'
AND d.owner_id > 0
AND NOT EXISTS (SELECT 1 FROM tuya_user_binding b WHERE b.member_id = d.owner_id);
-- ---------- 2) 逻辑删除已解绑用户的 AI 档案 ----------
-- 注意:这里按 owner_id 判定,而不是 JOIN device 比较 device_id——
-- ai_device_profile.device_id 与 device.device_id 的排序规则不同
-- (utf8mb4_unicode_ci vs utf8mb4_0900_ai_ci),跨表字符串比较会报
-- ERROR 1267 Illegal mix of collations。owner_id 为数值列,无此问题。
UPDATE ai_device_profile a
SET a.deleted = 1,
a.update_time = NOW()
WHERE a.deleted = 0
AND a.owner_id > 0
AND NOT EXISTS (SELECT 1 FROM tuya_user_binding b WHERE b.member_id = a.owner_id);
-- ---------- 3) 删除测试联动规则 ----------
DELETE FROM linkage_rule WHERE deleted = 0;
-- ---------- 4) 删除测试场景(bb 场景 cron 为每分钟执行) ----------
DELETE FROM scene WHERE deleted = 0;
-- ---------- 5) 清理场景执行记录 ----------
DELETE FROM scene_run;