这里docker部署,生成一个初始化服务。docker启动后,执行一次,将使用的协议包,网络,产品直接部署。当然还有设备,我这里没有设备,设备自动注册的。
主要实现,就是jetlinks的api调用。
java
package cn.zwjl.iot.service;
import cn.zwjl.iot.config.JetLinksConfig;
import cn.zwjl.iot.util.IdGeneratorUtil;
import cn.zwjl.iot.util.WebClientUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class JetLinksInitServiceImpl implements JetLinksInitService {
@Resource
private JetLinksConfig jetLinksConfig;
// 默认请求头(包含认证Token)
private Map<String, String> getHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
// 添加认证Token
if (WebClientUtil.getJwtToken() != null) {
headers.put("Authorization", "Bearer " + WebClientUtil.getJwtToken());
}
return headers;
}
/**
* 实现接口:初始化所有模板
*/
@Override
public void initJetLinks() throws Exception {
log.info("开始初始化JetLinks多模板配置...");
if (jetLinksConfig.getTemplates() != null && !jetLinksConfig.getTemplates().isEmpty()) {
jetLinksConfig.getTemplates().forEach((templateName, template) -> {
log.info("========== 开始初始化模板:{} ==========", templateName);
try {
// 生成动态唯一ID
generateDynamicIds(template);
// 初始化单个模板
initTemplate(template);
} catch (Exception e) {
log.error("模板[{}]初始化失败", templateName, e);
throw new RuntimeException("模板初始化失败:" + templateName, e);
}
log.info("========== 模板:{} 初始化完成 ==========\n", templateName);
});
}
log.info("所有模板初始化完成!");
}
/**
* 实现接口:初始化单个模板
*/
@Override
public void initTemplate(JetLinksConfig.TemplateConfig template) throws Exception {
// 生成动态ID
String protocolId = jetLinksConfig.generateProtocolId(template.getProtocol());
String networkId = jetLinksConfig.generateNetworkId(template.getNetwork());
String gatewayId = jetLinksConfig.generateGatewayId(template.getGateway());
String productId = jetLinksConfig.generateProductId(template.getProduct());
// 关联依赖ID
template.getProtocol().setId(protocolId);
template.getGateway().setChannelId(networkId);
template.getGateway().setProtocol(protocolId);
template.getProduct().setAccessId(gatewayId);
template.getProduct().setProtocol(protocolId);
template.getProduct().setMessageProtocol(protocolId);
template.getProduct().getConfiguration().put("productId", productId);
// 执行初始化步骤(新增创建协议配置)
uploadProtocol(template.getProtocol());
createProtocolConfig(template.getProtocol()); // 新增步骤
createNetworkConfig(template.getNetwork());
createGateway(template.getGateway());
createProduct(template.getProduct());
updateProduct(template.getProduct());
importProductMetadata(template.getProduct()); // 新增导入物模型步骤
}
/**
* 生成动态唯一ID(使用自定义工具类)
*/
private void generateDynamicIds(JetLinksConfig.TemplateConfig template) {
// 协议ID
if (template.getProtocol().getId() == null || template.getProtocol().getId().isEmpty()) {
template.getProtocol().setId(IdGeneratorUtil.generateId("protocol"));
}
// 网络组件ID
if (template.getNetwork().getId() == null || template.getNetwork().getId().isEmpty()) {
template.getNetwork().setId(IdGeneratorUtil.generateId("network"));
}
// 网关ID
if (template.getGateway().getId() == null || template.getGateway().getId().isEmpty()) {
template.getGateway().setId(IdGeneratorUtil.generateId("gateway"));
}
// 产品ID
if (template.getProduct().getId() == null || template.getProduct().getId().isEmpty()) {
template.getProduct().setId(IdGeneratorUtil.generateId("product"));
}
log.debug("动态ID生成完成:");
log.debug(" - 协议ID:{}", template.getProtocol().getId());
log.debug(" - 网络ID:{}", template.getNetwork().getId());
log.debug(" - 网关ID:{}", template.getGateway().getId());
log.debug(" - 产品ID:{}", template.getProduct().getId());
}
/**
* 实现接口:上传协议包
*/
@Override
public void uploadProtocol(JetLinksConfig.ProtocolConfig protocol) throws Exception {
log.info("上传协议包[{}]...", protocol.getId());
String url = jetLinksConfig.getBaseUrl() + "/file/upload";
Map<String, String> params = new HashMap<>();
params.put("directory", "protocol");
params.put("name", protocol.getName());
try {
String result = WebClientUtil.uploadFile(url, protocol.getJarPath(), params, getHeaders());
log.info("协议包[{}]上传结果:{}", protocol.getId(), result);
// 解析上传结果获取文件ID
JSONObject resultJson = JSONObject.parseObject(result);
if (resultJson.getJSONObject("result") != null) {
// 存储文件ID到临时变量(不修改ProtocolConfig)
String fileId = resultJson.getJSONObject("result").getString("id");
protocol.setFileId(fileId); // 如需此方法需确保ProtocolConfig有fileId字段
log.info("协议包文件ID:{}", fileId);
}
} catch (Exception e) {
log.error("协议包[{}]上传失败", protocol.getId(), e);
throw e;
}
}
/**
* 新增:创建协议配置
*/
private void createProtocolConfig(JetLinksConfig.ProtocolConfig protocol) throws Exception {
log.info("创建协议配置[{}]...", protocol.getId());
String url = jetLinksConfig.getBaseUrl() + "/protocol";
Map<String, Object> body = new HashMap<>();
body.put("id", protocol.getId());
body.put("name", protocol.getName() != null ? protocol.getName() : "ZWJL通信处理协议");
body.put("type", "jar");
body.put("state", 1);
body.put("description", protocol.getDescription() != null ? protocol.getDescription() : "用于ZWJL智能设备的自定义通信协议");
// 配置项
Map<String, Object> configuration = new HashMap<>();
configuration.put("fileId", protocol.getFileId()); // 使用上传获取的文件ID
configuration.put("provider", "cn.zwjl.iot.MyProtocolSupportProvider");
body.put("configuration", configuration);
try {
String result = WebClientUtil.postJson(url, JSON.toJSONString(body), getHeaders());
log.info("协议配置[{}]创建结果:{}", protocol.getId(), result);
} catch (RuntimeException e) {
if (e.getMessage().contains("duplicate_key") || e.getMessage().contains("已存在重复的数据")) {
log.warn("协议配置[{}]已存在,跳过创建", protocol.getId());
} else {
throw e;
}
}
}
/**
* 实现接口:创建网络组件
*/
@Override
public void createNetworkConfig(JetLinksConfig.NetworkConfig network) throws Exception {
log.info("创建网络组件[{}]...", network.getId());
String url = jetLinksConfig.getBaseUrl() + "/network/config";
Map<String, Object> body = new HashMap<>();
body.put("id", network.getId());
body.put("name", network.getName());
body.put("type", network.getType());
body.put("state", network.getState() != null ? network.getState() : "ENABLED");
body.put("configuration", network.getConfiguration());
try {
String result = WebClientUtil.postJson(url, JSON.toJSONString(body), getHeaders());
log.info("网络组件[{}]创建结果:{}", network.getId(), result);
} catch (RuntimeException e) {
if (e.getMessage().contains("duplicate_key") || e.getMessage().contains("已存在重复的数据")) {
log.warn("网络组件[{}]已存在,跳过创建", network.getId());
} else {
throw e;
}
}
}
/**
* 实现接口:创建接入网关
*/
@Override
public void createGateway(JetLinksConfig.GatewayConfig gateway) throws Exception {
log.info("创建接入网关[{}]...", gateway.getId());
String url = jetLinksConfig.getBaseUrl() + "/gateway/device";
Map<String, Object> body = new HashMap<>();
body.put("id", gateway.getId());
body.put("name", gateway.getName());
body.put("provider", gateway.getProvider() != null ? gateway.getProvider() : "default");
body.put("channelId", gateway.getChannelId());
body.put("protocol", gateway.getProtocol());
body.put("transport", gateway.getTransport() != null ? gateway.getTransport() : "TCP");
body.put("state", gateway.getState() != null ? gateway.getState() : "ENABLED");
body.put("configuration", gateway.getConfiguration());
try {
String result = WebClientUtil.postJson(url, JSON.toJSONString(body), getHeaders());
log.info("接入网关[{}]创建结果:{}", gateway.getId(), result);
} catch (RuntimeException e) {
if (e.getMessage().contains("duplicate_key") || e.getMessage().contains("已存在重复的数据")) {
log.warn("接入网关[{}]已存在,跳过创建", gateway.getId());
} else {
throw e;
}
}
}
/**
* 实现接口:创建产品
*/
@Override
public void createProduct(JetLinksConfig.ProductConfig product) throws Exception {
log.info("创建产品[{}]...", product.getId());
// 校验必填参数
if (product.getId() == null) {
throw new IllegalArgumentException("产品ID不能为空");
}
if (product.getName() == null) {
throw new IllegalArgumentException("产品名称不能为空");
}
if (product.getProtocol() == null) {
throw new IllegalArgumentException("协议配置不能为空");
}
String url = jetLinksConfig.getBaseUrl() + "/device/product";
Map<String, Object> body = new HashMap<>();
body.put("id", product.getId());
body.put("name", product.getName());
body.put("deviceType", product.getDeviceType() != null ? product.getDeviceType() : "DEVICE");
body.put("transportProtocol", product.getTransportProtocol() != null ? product.getTransportProtocol() : "TCP");
body.put("protocol", product.getProtocol());
body.put("messageProtocol", product.getMessageProtocol());
body.put("accessId", product.getAccessId());
body.put("accessProvider", product.getAccessProvider() != null ? product.getAccessProvider() : "default");
body.put("storePolicy", product.getStorePolicy() != null ? product.getStorePolicy() : "DEFAULT");
body.put("metadata", product.getMetadata());
body.put("state", product.getState() != null ? product.getState() : "ENABLED");
body.put("configuration", product.getConfiguration());
// 调试日志
log.debug("产品创建请求参数:{}", JSON.toJSONString(body));
try {
String result = WebClientUtil.postJson(url, JSON.toJSONString(body), getHeaders());
log.info("产品[{}]创建结果:{}", product.getId(), result);
} catch (RuntimeException e) {
if (e.getMessage().contains("duplicate_key") || e.getMessage().contains("已存在重复的数据")) {
log.warn("产品[{}]已存在,跳过创建", product.getId());
} else {
throw e;
}
}
}
/**
* 实现接口:更新产品
*/
@Override
public void updateProduct(JetLinksConfig.ProductConfig product) throws Exception {
log.info("更新产品[{}]...", product.getId());
String url = jetLinksConfig.getBaseUrl() + "/device/product/" + product.getId();
Map<String, Object> body = new HashMap<>();
body.put("id", product.getId());
body.put("name", product.getName());
body.put("deviceType", product.getDeviceType());
body.put("transportProtocol", product.getTransportProtocol());
body.put("protocol", product.getProtocol());
body.put("messageProtocol", product.getMessageProtocol());
body.put("accessId", product.getAccessId());
body.put("accessProvider", product.getAccessProvider());
body.put("storePolicy", product.getStorePolicy());
body.put("metadata", product.getMetadata());
body.put("state", product.getState());
body.put("configuration", product.getConfiguration());
try {
String result = WebClientUtil.putJson(url, JSON.toJSONString(body), getHeaders());
log.info("产品[{}]更新结果:{}", product.getId(), result);
} catch (Exception e) {
log.warn("产品[{}]更新失败(可能不存在):{}", product.getId(), e.getMessage());
}
}
/**
* 导入产品物模型(使用URL查询参数传递fileUrl)
*/
private void importProductMetadata(JetLinksConfig.ProductConfig product) throws Exception {
log.info("开始产品[{}]物模型导入(直接上传解析)...", product.getId());
// 1. 检查本地CSV文件路径
String csvFilePath = product.getMetadataFile();
if (csvFilePath == null || csvFilePath.isEmpty()) {
log.warn("产品[{}]未配置CSV物模型文件路径,跳过导入", product.getId());
return;
}
try {
// 2. 第一步:直接上传CSV到解析接口,获取物模型结构
JSONObject metadataJson = analyzeMetadataByUpload(csvFilePath, product.getId());
log.info("产品[{}]物模型文件解析成功,结构:{}", product.getId(), metadataJson.toJSONString());
// 3. 第二步:用解析结果更新产品物模型
updateProductMetadata(product, metadataJson);
log.info("产品[{}]物模型导入完成!", product.getId());
} catch (Exception e) {
log.error("产品[{}]物模型导入失败", product.getId(), e);
throw e;
}
}
private JSONObject analyzeMetadataByUpload(String csvFilePath, String productId) throws Exception {
log.info("上传CSV文件到解析接口:{}", csvFilePath);
String analyzeUrl = jetLinksConfig.getBaseUrl() + "/device/instance/" + productId + "/property-metadata/file/analyze";
// 构建form-data请求,直接上传CSV文件
Map<String, String> params = new HashMap<>();
params.put("fileType", "csv"); // 明确文件类型(可选)
// 调用文件上传方法,直接上传到解析接口
String analyzeResult = WebClientUtil.uploadFile(analyzeUrl, csvFilePath, params, getHeaders());
// 解析接口返回的物模型结构
JSONObject resultJson = JSONObject.parseObject(analyzeResult);
if (!"success".equals(resultJson.getString("message")) && !"200".equals(resultJson.getString("status"))) {
throw new RuntimeException("物模型解析失败:" + resultJson.getString("message"));
}
// 提取物模型核心数据(根据实际返回字段调整)
JSONObject metadata = resultJson.getJSONObject("result");
if (metadata == null || metadata.isEmpty()) {
throw new RuntimeException("解析结果无有效物模型数据:" + analyzeResult);
}
return metadata;
}
private void updateProductMetadata(JetLinksConfig.ProductConfig product, JSONObject metadata) throws Exception {
log.info("更新产品[{}]物模型(修正格式)...", product.getId());
String updateUrl = jetLinksConfig.getBaseUrl() + "/device-product";
// 2. 构建完全匹配示例的请求体(metadata转JSON字符串)
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("id", product.getId());
requestBody.put("name", product.getName());
// requestBody.put("description", product.getDescription() != null ? product.getDescription() : "太阳能监测设备");
requestBody.put("metadata", JSON.toJSONString(metadata)); // 关键:转为字符串格式
Map<String, String> headers = getHeaders();
headers.put("Accept", "application/json"); // 明确接受JSON响应
headers.put("Content-Type", "application/json;charset=UTF-8"); // 确保编码
// 3. 严格使用指定接口(不修改路径)
String apiUrl = jetLinksConfig.getBaseUrl() + "/device-product";
String result;
String updateResult = WebClientUtil.patchJson(updateUrl, JSON.toJSONString(requestBody), headers);
log.info("产品[{}]物模型更新响应:{}", product.getId(), updateResult);
}
/**
* 上传CSV文件到文件服务器,返回accessUrl
*/
private String uploadCsvFile(String csvFilePath) throws Exception {
log.info("上传CSV文件:{}", csvFilePath);
String uploadUrl = jetLinksConfig.getBaseUrl() + "/file/upload";
Map<String, String> params = new HashMap<>();
params.put("directory", "metadata");
String uploadResult = WebClientUtil.uploadFile(uploadUrl, csvFilePath, params, getHeaders());
// 解析返回结果获取文件ID(而非完整URL)
JSONObject resultJson = JSONObject.parseObject(uploadResult);
if (resultJson.containsKey("result") && resultJson.getJSONObject("result") != null) {
return resultJson.getJSONObject("result").getString("id"); // 提取文件ID
}
throw new RuntimeException("上传CSV文件后未获取到文件ID");
}
}