jetlinks一键部署

这里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");
    }
}
相关推荐
明达智控技术2 小时前
MR30分布式IO在污水处理厂的应用
分布式·物联网·自动化
Henry Zhu12313 小时前
进阶:VPP NAT44-EI 全面源码解析
网络·物联网·计算机网络·云原生·云计算
亿坊电商1 天前
物联网智慧校园:构筑无人自助打印机的隐私安全防线!
物联网·安全
Coder_Boy_1 天前
【物联网技术】- 基础理论-0001
java·python·物联网·iot
LCG米1 天前
基于Zephyr RTOS与nRF54L15的多协议物联网节点设计(支持BLE/Thread/Matter)
物联网
小龙报1 天前
【嵌入式51单片机】51 单片机中断入门到精通:中断类型、寄存器操作、优先级配置与实战代码
c语言·驱动开发·单片机·嵌入式硬件·物联网·mongodb·51单片机
小李做物联网1 天前
【物联网毕设】76.1单片机stm32菊类智能养护物联网嵌入式项目程序系统开发
stm32·单片机·嵌入式硬件·物联网
TDengine (老段)1 天前
TDengine 地理函数 ST_GeomFromText 用户手册
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
Henry Zhu1231 天前
VPP的NAT插件: NAT44-EI 实战配置指南
网络·物联网·计算机网络·云原生·云计算