fix(B1): 修复 resolveProductId 参数错位污染产品字段 + product_key 确定性化
根因: TuyaController 两处调用把「涂鸦 productId」填进 model 位、把字面量 'tuya' 填进 manufacturer 位, 导致 product.model / manufacturer 被写入 平台产品ID (生产数据已出现 P_tuya_y1ouxiqr_42bff218 的脏记录)。 修复: - TuyaController: 2 处调用改为 model=null, manufacturer=null (涂鸦 OpenAPI 确实未提供真实型号/厂商, 此前是错位而非真实值) - ProductServiceImpl.resolveProductId: 新增 sanitizeDimension() 防御, 对畸形入参(等于平台产品ID / 等于平台编码)统一置空, 杜绝同类错位复发 - resolveProductId 入口 platform 经 PlatformType.normalize() 归一 product_key 确定性化: - 旧: P_<platform>_<model>_<随机UUID8> -> 不可读、无法反查 - 新: P_<platform>_<platformProductId|model> -> 恒定、可反查 (超 56 字符截断并追加短哈希防碰撞) 验证: mvn -pl iot-common,iot-device -am compile 通过
This commit is contained in:
parent
a932633c50
commit
9786286ad3
@ -355,8 +355,10 @@ public class TuyaController {
|
||||
? tuyaDevice.getIcon() : existing.getIcon());
|
||||
update.setRemark("涂鸦设备");
|
||||
if (existing.getProductId() == null) {
|
||||
// model / manufacturer 传 null:涂鸦 OpenAPI 未提供真实型号与厂商,
|
||||
// 历史误把 productId 填进 model 位,污染了 product 表的型号字段。
|
||||
update.setProductId(productService.resolveProductId("tuya", tuyaDevice.getProductId(),
|
||||
tuyaDevice.getProductId(), "tuya", tuyaDevice.getCategory(), tuyaDevice.getDeviceName()));
|
||||
null, null, tuyaDevice.getCategory(), tuyaDevice.getDeviceName()));
|
||||
}
|
||||
deviceMapper.updateById(update);
|
||||
count++;
|
||||
@ -382,7 +384,7 @@ public class TuyaController {
|
||||
localDevice.setIcon(tuyaDevice.getIcon());
|
||||
localDevice.setRemark("涂鸦设备");
|
||||
localDevice.setProductId(productService.resolveProductId("tuya", tuyaDevice.getProductId(),
|
||||
tuyaDevice.getProductId(), "tuya", tuyaDevice.getCategory(), tuyaDevice.getDeviceName()));
|
||||
null, null, tuyaDevice.getCategory(), tuyaDevice.getDeviceName()));
|
||||
deviceMapper.insert(localDevice);
|
||||
count++;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.iot.device.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.iot.common.constants.PlatformType;
|
||||
import com.iot.device.dto.ProductSpec;
|
||||
import com.iot.device.entity.Device;
|
||||
import com.iot.device.entity.Product;
|
||||
@ -42,7 +43,7 @@ public class ProductServiceImpl implements ProductService {
|
||||
@Transactional
|
||||
public Product createProduct(Product product) {
|
||||
if (!StringUtils.hasText(product.getProductKey())) {
|
||||
product.setProductKey(genKey(product.getPlatform(), product.getModel()));
|
||||
product.setProductKey(genKey(product.getPlatform(), product.getPlatformProductId(), product.getModel()));
|
||||
}
|
||||
if (product.getSpecVersion() == null) {
|
||||
product.setSpecVersion(1);
|
||||
@ -198,6 +199,13 @@ public class ProductServiceImpl implements ProductService {
|
||||
@Transactional
|
||||
public Long resolveProductId(String platform, String platformProductId, String model,
|
||||
String manufacturer, String category, String name) {
|
||||
platform = PlatformType.normalize(platform);
|
||||
// 防错位:历史调用方曾把「涂鸦 productId」填进 model 位、
|
||||
// 把字面量 "tuya" 填进 manufacturer 位,导致 product 表型号/厂商被污染。
|
||||
// 这里对明显畸形的入参做归一,杜绝同类问题复发。
|
||||
model = sanitizeDimension(model, platformProductId);
|
||||
manufacturer = sanitizeDimension(manufacturer, platformProductId);
|
||||
|
||||
if (StringUtils.hasText(platformProductId)) {
|
||||
Product p = productMapper.selectOne(new LambdaQueryWrapper<Product>()
|
||||
.eq(Product::getPlatform, platform)
|
||||
@ -222,13 +230,30 @@ public class ProductServiceImpl implements ProductService {
|
||||
np.setManufacturer(manufacturer);
|
||||
np.setCategory(category);
|
||||
np.setName(StringUtils.hasText(name) ? name : (StringUtils.hasText(model) ? model : "未命名产品"));
|
||||
np.setProductKey(genKey(platform, model));
|
||||
np.setProductKey(genKey(platform, platformProductId, model));
|
||||
np.setStatus("published");
|
||||
np.setSpecVersion(1);
|
||||
productMapper.insert(np);
|
||||
return np.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清洗型号/厂商入参:与平台产品ID相同、或等于平台编码的,判定为历史错位传入,置空。
|
||||
*/
|
||||
private String sanitizeDimension(String value, String platformProductId) {
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
String v = value.trim();
|
||||
if (StringUtils.hasText(platformProductId) && v.equals(platformProductId.trim())) {
|
||||
return null;
|
||||
}
|
||||
if (v.equalsIgnoreCase("tuya") || v.equalsIgnoreCase("local") || v.equalsIgnoreCase("matter")) {
|
||||
return null;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int reconcileDevices() {
|
||||
@ -338,8 +363,30 @@ public class ProductServiceImpl implements ProductService {
|
||||
return (n != null && !n.isNull()) ? n.asText() : null;
|
||||
}
|
||||
|
||||
private String genKey(String platform, String model) {
|
||||
String base = "P_" + (platform == null ? "" : platform) + "_" + (model == null ? "" : model);
|
||||
return (base + "_" + UUID.randomUUID().toString().substring(0, 8)).replaceAll("\\s", "");
|
||||
/**
|
||||
* 生成产品业务编码。
|
||||
*
|
||||
* <p><b>确定性</b>:同一平台同一产品恒定得到同一编码,可从编码反查平台与平台产品ID。
|
||||
* 历史实现使用随机 UUID 后缀(如 {@code P_tuya__8a50357a}),既不可读也无法反查。</p>
|
||||
*
|
||||
* <p>优先级:平台产品ID > 型号 > 品类;均缺失时用短随机串兜底(此时无法保证确定性)。</p>
|
||||
*/
|
||||
private String genKey(String platform, String platformProductId, String model) {
|
||||
String suffix;
|
||||
if (StringUtils.hasText(platformProductId)) {
|
||||
suffix = platformProductId.trim();
|
||||
} else if (StringUtils.hasText(model)) {
|
||||
suffix = model.trim();
|
||||
} else {
|
||||
suffix = UUID.randomUUID().toString().substring(0, 8);
|
||||
}
|
||||
String base = "P_" + (StringUtils.hasText(platform) ? platform.trim() : "unknown")
|
||||
+ "_" + suffix.replaceAll("\\s", "");
|
||||
// product_key 列长 64,超长时截断并追加短哈希以降低碰撞概率
|
||||
if (base.length() > 56) {
|
||||
String h = Integer.toHexString(base.hashCode());
|
||||
base = base.substring(0, 47) + "_" + h;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user