20260730:平台对接pass另外一套方案

This commit is contained in:
zhugongping 2026-07-30 09:29:20 +08:00
parent 7963bcd5a6
commit 99e91ffac1
24 changed files with 331 additions and 2781 deletions

View File

@ -1,196 +0,0 @@
package com.fastbee.controller.integration;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.annotation.Anonymous;
import com.fastbee.common.core.controller.BaseController;
import com.fastbee.common.core.domain.AjaxResult;
import com.fastbee.common.core.page.TableDataInfo;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.iot.domain.integration.IntegrationApi;
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
import com.fastbee.iot.domain.integration.IntegrationLog;
import com.fastbee.iot.domain.integration.IntegrationPlatform;
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
import com.fastbee.iot.service.integration.IIntegrationPlatformService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 第三方平台接入。
*/
@Api(tags = "第三方平台接入")
@RestController
@RequestMapping("/iot/integration")
public class IntegrationPlatformController extends BaseController {
@Resource
private IIntegrationPlatformService integrationPlatformService;
@ApiOperation("查询平台接入配置")
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
@GetMapping("/platform/list")
public TableDataInfo list(IntegrationPlatform query) {
startPage();
List<IntegrationPlatform> list = integrationPlatformService.selectPlatformList(query);
return getDataTable(list);
}
@ApiOperation("查询平台接入配置详情")
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
@GetMapping("/platform/{id}")
public AjaxResult getPlatform(@PathVariable Long id) {
return AjaxResult.success(integrationPlatformService.selectPlatformById(id));
}
@ApiOperation("新增平台接入配置")
@PreAuthorize("@ss.hasPermi('iot:integration:add')")
@PostMapping("/platform")
public AjaxResult addPlatform(@RequestBody IntegrationPlatform platform) {
return toAjax(integrationPlatformService.insertPlatform(platform));
}
@ApiOperation("修改平台接入配置")
@PreAuthorize("@ss.hasPermi('iot:integration:edit')")
@PutMapping("/platform")
public AjaxResult editPlatform(@RequestBody IntegrationPlatform platform) {
return toAjax(integrationPlatformService.updatePlatform(platform));
}
@ApiOperation("删除平台接入配置")
@PreAuthorize("@ss.hasPermi('iot:integration:remove')")
@DeleteMapping("/platform/{ids}")
public AjaxResult removePlatform(@PathVariable Long[] ids) {
return toAjax(integrationPlatformService.deletePlatformByIds(ids));
}
@ApiOperation("查询平台接口定义")
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
@GetMapping("/api/list")
public TableDataInfo apiList(IntegrationApi query) {
startPage();
List<IntegrationApi> list = integrationPlatformService.selectApiList(query);
return getDataTable(list);
}
@ApiOperation("查询平台接口定义详情")
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
@GetMapping("/api/{id}")
public AjaxResult getApi(@PathVariable Long id) {
return AjaxResult.success(integrationPlatformService.selectApiById(id));
}
@ApiOperation("新增平台接口定义")
@PreAuthorize("@ss.hasPermi('iot:integration:add')")
@PostMapping("/api")
public AjaxResult addApi(@RequestBody IntegrationApi api) {
return toAjax(integrationPlatformService.insertApi(api));
}
@ApiOperation("修改平台接口定义")
@PreAuthorize("@ss.hasPermi('iot:integration:edit')")
@PutMapping("/api")
public AjaxResult editApi(@RequestBody IntegrationApi api) {
return toAjax(integrationPlatformService.updateApi(api));
}
@ApiOperation("删除平台接口定义")
@PreAuthorize("@ss.hasPermi('iot:integration:remove')")
@DeleteMapping("/api/{ids}")
public AjaxResult removeApi(@PathVariable Long[] ids) {
return toAjax(integrationPlatformService.deleteApiByIds(ids));
}
@ApiOperation("查询平台设备映射")
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
@GetMapping("/device-mapping/list")
public TableDataInfo deviceMappingList(IntegrationDeviceMapping query) {
startPage();
List<IntegrationDeviceMapping> list = integrationPlatformService.selectDeviceMappingList(query);
return getDataTable(list);
}
@ApiOperation("查询平台设备映射详情")
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
@GetMapping("/device-mapping/{id}")
public AjaxResult getDeviceMapping(@PathVariable Long id) {
return AjaxResult.success(integrationPlatformService.selectDeviceMappingById(id));
}
@ApiOperation("新增平台设备映射")
@PreAuthorize("@ss.hasPermi('iot:integration:add')")
@PostMapping("/device-mapping")
public AjaxResult addDeviceMapping(@RequestBody IntegrationDeviceMapping mapping) {
return toAjax(integrationPlatformService.insertDeviceMapping(mapping));
}
@ApiOperation("修改平台设备映射")
@PreAuthorize("@ss.hasPermi('iot:integration:edit')")
@PutMapping("/device-mapping")
public AjaxResult editDeviceMapping(@RequestBody IntegrationDeviceMapping mapping) {
return toAjax(integrationPlatformService.updateDeviceMapping(mapping));
}
@ApiOperation("删除平台设备映射")
@PreAuthorize("@ss.hasPermi('iot:integration:remove')")
@DeleteMapping("/device-mapping/{ids}")
public AjaxResult removeDeviceMapping(@PathVariable Long[] ids) {
return toAjax(integrationPlatformService.deleteDeviceMappingByIds(ids));
}
@ApiOperation("查询平台同步日志")
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
@GetMapping("/log/list")
public TableDataInfo logList(IntegrationLog query) {
startPage();
List<IntegrationLog> list = integrationPlatformService.selectLogList(query);
return getDataTable(list);
}
@ApiOperation("触发平台数据同步")
@PreAuthorize("@ss.hasPermi('iot:integration:sync')")
@PostMapping("/sync")
public AjaxResult sync(@RequestBody IntegrationSyncRequest request) {
return AjaxResult.success(integrationPlatformService.sync(request));
}
@Anonymous
@ApiOperation("第三方触发平台数据同步")
@PostMapping("/open/sync")
public AjaxResult openSync(@RequestHeader(value = "X-Integration-Key", required = false) String integrationKey,
@RequestBody IntegrationSyncRequest request) {
checkOpenKey(request, integrationKey);
return AjaxResult.success(integrationPlatformService.sync(request));
}
@ApiOperation("平台适配器自测")
@PreAuthorize("@ss.hasPermi('iot:integration:sync')")
@PostMapping("/self-test/{platformCode}")
public AjaxResult selfTest(@PathVariable String platformCode) {
return AjaxResult.success(integrationPlatformService.selfTest(platformCode));
}
private void checkOpenKey(IntegrationSyncRequest request, String integrationKey) {
if (request == null || StringUtils.isBlank(request.getPlatformCode())) {
throw new ServiceException("平台编码不能为空");
}
IntegrationPlatform platform = integrationPlatformService.selectPlatformByCode(request.getPlatformCode());
if (platform == null) {
throw new ServiceException("平台不存在");
}
JSONObject credential = StringUtils.isBlank(platform.getCredentialJson()) ? new JSONObject() : JSON.parseObject(platform.getCredentialJson());
String openApiKey = credential.getString("openApiKey");
if (StringUtils.isBlank(openApiKey)) {
throw new ServiceException("平台未启用开放调度密钥");
}
if (!openApiKey.equals(integrationKey)) {
throw new ServiceException("开放调度密钥无效");
}
}
}

View File

@ -1,65 +0,0 @@
package com.fastbee.controller.integration.dcp;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.annotation.Anonymous;
import com.fastbee.iot.service.integration.IDcpOpenApiService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* DCP轴力计兼容开放接口。
*
* <p>接口路径与《DCP接口文档(轴力计)》保持一致,前缀为 /open/dcp。</p>
*/
@Anonymous
@Api(tags = "DCP轴力计兼容开放接口")
@RestController
@RequestMapping("/open/dcp")
public class DcpOpenApiController {
@Resource
private IDcpOpenApiService dcpOpenApiService;
@ApiOperation("获取访问凭证")
@PostMapping("/connect/token")
public JSONObject token(@RequestParam Map<String, String> form) {
return dcpOpenApiService.createToken(form == null ? new HashMap<>() : form);
}
@ApiOperation("获取用户信息")
@GetMapping("/api/app/user/user-id")
public JSONObject userId(@RequestHeader("Authorization") String authorization) {
return dcpOpenApiService.getUser(authorization);
}
@ApiOperation("获取项目信息")
@GetMapping("/api/app/project/by-user-id/{userId}")
public JSONArray projects(@RequestHeader("Authorization") String authorization,
@PathVariable String userId) {
return dcpOpenApiService.getProjects(authorization, userId);
}
@ApiOperation("获取项目轴力计测点")
@PostMapping("/api/app/stress-sensor/by-project-id/{projectId}")
public JSONArray stressSensors(@RequestHeader("Authorization") String authorization,
@PathVariable String projectId) {
return dcpOpenApiService.getStressSensors(authorization, projectId);
}
@ApiOperation("批量获取轴力计数据")
@PostMapping("/api/app/inclino-data/load-dataset-by-device-ids")
public JSONObject dataset(@RequestHeader("Authorization") String authorization,
@RequestParam String startTime,
@RequestParam String endTime,
@RequestParam(defaultValue = "true") Boolean isOriginal,
@RequestBody List<String> deviceIds) {
return dcpOpenApiService.getDataset(authorization, startTime, endTime, isOriginal, deviceIds);
}
}

View File

@ -1,8 +1,4 @@
package com.fastbee.controller.platform.dcp;/**
* @description:
* @author Sumuxi
* @date 2026/5/25
*/
package com.fastbee.controller.platform.dcp;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.RequestMapping;

View File

@ -102,7 +102,7 @@ public class PubMqttCallBack implements MqttCallbackExtended {
" ▌▌▌▌▌▌ ▌▌▌▌ ▌▌▌ ▍▌▌▌▏▌▌▌▌ ▌▌▌ ▌▌▌ ▌▌▌ ▌▌▌▌▌▌▌▌ ▌▌▌▌\n" +
" ▌▌▌▍ ▌▌▌▍▍▍▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌▌ ▌▌▌▌▍▍▍▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌▌▌\n" +
" ▌▌▌▏ ▌▌▌▌▌▌▌▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌ ▌▌▌▌▌▌▌▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌\n" +
" ▌▌▌▏ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌"
" ▌▌▌▏ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌\n"
);

View File

@ -1,68 +0,0 @@
package com.fastbee.iot.domain.integration;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 第三方平台接口定义。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("iot_integration_api")
@ApiModel(value = "IntegrationApi", description = "第三方平台接口定义")
public class IntegrationApi extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty("主键")
private Long id;
@ApiModelProperty("平台ID")
private Long platformId;
@ApiModelProperty("接口编码")
private String apiCode;
@ApiModelProperty("接口名称")
private String apiName;
@ApiModelProperty("请求方法")
private String method;
@ApiModelProperty("接口 URI")
private String uri;
@ApiModelProperty("Content-Type")
private String contentType;
@ApiModelProperty("请求头模板 JSON")
private String headersJson;
@ApiModelProperty("Query 模板 JSON")
private String queryJson;
@ApiModelProperty("Body 模板 JSON")
private String bodyJson;
@ApiModelProperty("返回数据路径")
private String responsePath;
@ApiModelProperty("依赖接口编码")
private String dependsOn;
@ApiModelProperty("排序")
private Integer sortOrder;
@ApiModelProperty("状态:0=停用 1=启用")
private Integer status;
@ApiModelProperty("删除标志:0=存在 2=删除")
private String delFlag;
}

View File

@ -1,50 +0,0 @@
package com.fastbee.iot.domain.integration;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 第三方设备与本系统设备映射。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("iot_integration_device_mapping")
@ApiModel(value = "IntegrationDeviceMapping", description = "第三方设备映射")
public class IntegrationDeviceMapping extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty("主键")
private Long id;
@ApiModelProperty("平台ID")
private Long platformId;
@ApiModelProperty("第三方设备ID")
private String externalDeviceId;
@ApiModelProperty("第三方设备名称")
private String externalDeviceName;
@ApiModelProperty("本系统产品ID")
private Long productId;
@ApiModelProperty("本系统设备编号")
private String serialNumber;
@ApiModelProperty("指标映射 JSON,sourceField -> identifier")
private String identifierMappingJson;
@ApiModelProperty("状态:0=停用 1=启用")
private Integer status;
@ApiModelProperty("删除标志:0=存在 2=删除")
private String delFlag;
}

View File

@ -1,61 +0,0 @@
package com.fastbee.iot.domain.integration;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 第三方平台接入执行日志。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("iot_integration_log")
@ApiModel(value = "IntegrationLog", description = "第三方平台接入执行日志")
public class IntegrationLog extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty("主键")
private Long id;
@ApiModelProperty("平台ID")
private Long platformId;
@ApiModelProperty("平台编码")
private String platformCode;
@ApiModelProperty("流程编码")
private String flowCode;
@ApiModelProperty("请求ID")
private String requestId;
@ApiModelProperty("开始时间")
private Date startTime;
@ApiModelProperty("结束时间")
private Date endTime;
@ApiModelProperty("状态:0=失败 1=成功 2=执行中")
private Integer status;
@ApiModelProperty("成功数量")
private Integer successCount;
@ApiModelProperty("失败数量")
private Integer failCount;
@ApiModelProperty("错误信息")
private String errorMsg;
@ApiModelProperty("详情 JSON")
private String detailJson;
}

View File

@ -1,47 +0,0 @@
package com.fastbee.iot.domain.integration;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fastbee.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 第三方平台接入配置。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("iot_integration_platform")
@ApiModel(value = "IntegrationPlatform", description = "第三方平台接入配置")
public class IntegrationPlatform extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty("主键")
private Long id;
@ApiModelProperty("平台编码")
private String platformCode;
@ApiModelProperty("平台名称")
private String platformName;
@ApiModelProperty("基础地址")
private String baseUrl;
@ApiModelProperty("认证类型")
private String authType;
@ApiModelProperty("凭证配置 JSON")
private String credentialJson;
@ApiModelProperty("状态:0=停用 1=启用")
private Integer status;
@ApiModelProperty("删除标志:0=存在 2=删除")
private String delFlag;
}

View File

@ -1,9 +0,0 @@
package com.fastbee.iot.mapper.integration;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.iot.domain.integration.IntegrationApi;
import org.springframework.stereotype.Repository;
@Repository
public interface IntegrationApiMapper extends BaseMapperX<IntegrationApi> {
}

View File

@ -1,9 +0,0 @@
package com.fastbee.iot.mapper.integration;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
import org.springframework.stereotype.Repository;
@Repository
public interface IntegrationDeviceMappingMapper extends BaseMapperX<IntegrationDeviceMapping> {
}

View File

@ -1,9 +0,0 @@
package com.fastbee.iot.mapper.integration;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.iot.domain.integration.IntegrationLog;
import org.springframework.stereotype.Repository;
@Repository
public interface IntegrationLogMapper extends BaseMapperX<IntegrationLog> {
}

View File

@ -1,9 +0,0 @@
package com.fastbee.iot.mapper.integration;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.iot.domain.integration.IntegrationPlatform;
import org.springframework.stereotype.Repository;
@Repository
public interface IntegrationPlatformMapper extends BaseMapperX<IntegrationPlatform> {
}

View File

@ -1,40 +0,0 @@
package com.fastbee.iot.model.integration;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 第三方平台同步请求。
*/
@Data
@ApiModel(value = "IntegrationSyncRequest", description = "第三方平台同步请求")
public class IntegrationSyncRequest {
@ApiModelProperty("平台编码")
private String platformCode;
@ApiModelProperty("开始时间,ISO-8601 格式")
private String startTime;
@ApiModelProperty("结束时间,ISO-8601 格式")
private String endTime;
@ApiModelProperty("是否原始数据")
private Boolean original;
@ApiModelProperty("是否只演练不写入")
private Boolean dryRun;
@ApiModelProperty("第三方项目ID,未传则使用平台流程获取")
private String projectId;
@ApiModelProperty("指定第三方设备ID")
private List<String> externalDeviceIds;
@ApiModelProperty("运行时参数")
private Map<String, Object> params;
}

View File

@ -1,57 +0,0 @@
package com.fastbee.iot.model.integration;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 第三方平台同步结果。
*/
@Data
@ApiModel(value = "IntegrationSyncResult", description = "第三方平台同步结果")
public class IntegrationSyncResult {
@ApiModelProperty("平台编码")
private String platformCode;
@ApiModelProperty("流程编码")
private String flowCode;
@ApiModelProperty("请求ID")
private String requestId;
@ApiModelProperty("是否演练")
private boolean dryRun;
@ApiModelProperty("成功数量")
private int successCount;
@ApiModelProperty("失败数量")
private int failCount;
@ApiModelProperty("设备数量")
private int deviceCount;
@ApiModelProperty("数据数量")
private int dataCount;
@ApiModelProperty("消息")
private String message;
@ApiModelProperty("详情")
private List<String> details = new ArrayList<>();
public void addSuccess() {
successCount++;
}
public void addFail(String detail) {
failCount++;
if (detail != null) {
details.add(detail);
}
}
}

View File

@ -1,26 +0,0 @@
package com.fastbee.iot.model.integration;
import lombok.Data;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 平台接入后的统一遥测数据。
*/
@Data
public class UnifiedTelemetry {
private String platformCode;
private String externalDeviceId;
private String externalDeviceName;
private Date reportTime;
private Map<String, Object> values = new LinkedHashMap<>();
private Object raw;
}

View File

@ -1,434 +0,0 @@
package com.fastbee.iot.service.impl.integration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.iot.domain.integration.IntegrationApi;
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
import com.fastbee.iot.domain.integration.IntegrationLog;
import com.fastbee.iot.domain.integration.IntegrationPlatform;
import com.fastbee.iot.mapper.integration.IntegrationApiMapper;
import com.fastbee.iot.mapper.integration.IntegrationDeviceMappingMapper;
import com.fastbee.iot.mapper.integration.IntegrationLogMapper;
import com.fastbee.iot.mapper.integration.IntegrationPlatformMapper;
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
import com.fastbee.iot.model.integration.IntegrationSyncResult;
import com.fastbee.iot.service.integration.IIntegrationPlatformService;
import com.fastbee.iot.service.integration.PlatformAdapter;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* 第三方平台接入服务。
*/
@Service
public class IntegrationPlatformServiceImpl implements IIntegrationPlatformService {
@Resource
private IntegrationPlatformMapper platformMapper;
@Resource
private IntegrationApiMapper apiMapper;
@Resource
private IntegrationDeviceMappingMapper deviceMappingMapper;
@Resource
private IntegrationLogMapper logMapper;
private final Map<String, PlatformAdapter> adapterMap = new HashMap<>();
public IntegrationPlatformServiceImpl(List<PlatformAdapter> adapters) {
if (adapters != null) {
for (PlatformAdapter adapter : adapters) {
adapterMap.put(adapter.platformCode().toUpperCase(), adapter);
}
}
}
@Override
public IntegrationPlatform selectPlatformByCode(String platformCode) {
if (StringUtils.isBlank(platformCode)) {
return null;
}
LambdaQueryWrapper<IntegrationPlatform> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationPlatform::getPlatformCode, platformCode.toUpperCase())
.eq(IntegrationPlatform::getDelFlag, "0")
.last("limit 1");
return platformMapper.selectOne(wrapper);
}
@Override
public IntegrationPlatform selectPlatformById(Long id) {
if (id == null) {
return null;
}
LambdaQueryWrapper<IntegrationPlatform> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationPlatform::getId, id)
.eq(IntegrationPlatform::getDelFlag, "0")
.last("limit 1");
return platformMapper.selectOne(wrapper);
}
@Override
public List<IntegrationPlatform> selectPlatformList(IntegrationPlatform query) {
LambdaQueryWrapper<IntegrationPlatform> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationPlatform::getDelFlag, "0");
if (query != null) {
wrapper.like(StringUtils.isNotBlank(query.getPlatformName()), IntegrationPlatform::getPlatformName, query.getPlatformName());
wrapper.eq(StringUtils.isNotBlank(query.getPlatformCode()), IntegrationPlatform::getPlatformCode, query.getPlatformCode());
wrapper.eq(query.getStatus() != null, IntegrationPlatform::getStatus, query.getStatus());
}
wrapper.orderByDesc(IntegrationPlatform::getCreateTime);
return platformMapper.selectList(wrapper);
}
@Override
public int insertPlatform(IntegrationPlatform platform) {
checkPlatform(platform);
String platformCode = platform.getPlatformCode().toUpperCase();
if (selectPlatformByCode(platformCode) != null) {
throw new ServiceException("平台编码已存在:" + platformCode);
}
platform.setPlatformCode(platformCode);
platform.setCredentialJson(normalizeJson(platform.getCredentialJson(), "凭证配置"));
if (platform.getStatus() == null) {
platform.setStatus(0);
}
platform.setDelFlag("0");
platform.setCreateTime(new Date());
return platformMapper.insert(platform);
}
@Override
public int updatePlatform(IntegrationPlatform platform) {
if (platform == null || platform.getId() == null) {
throw new ServiceException("平台ID不能为空");
}
checkPlatform(platform);
String platformCode = platform.getPlatformCode().toUpperCase();
IntegrationPlatform exists = selectPlatformByCode(platformCode);
if (exists != null && !exists.getId().equals(platform.getId())) {
throw new ServiceException("平台编码已存在:" + platformCode);
}
platform.setPlatformCode(platformCode);
platform.setCredentialJson(normalizeJson(platform.getCredentialJson(), "凭证配置"));
platform.setUpdateTime(new Date());
return platformMapper.updateById(platform);
}
@Override
public int deletePlatformByIds(Long[] ids) {
int rows = 0;
if (ids == null) {
return rows;
}
for (Long id : ids) {
if (id == null) {
continue;
}
IntegrationPlatform platform = new IntegrationPlatform();
platform.setId(id);
platform.setDelFlag("2");
platform.setUpdateTime(new Date());
rows += platformMapper.updateById(platform);
}
return rows;
}
@Override
public List<IntegrationApi> selectApiList(IntegrationApi query) {
LambdaQueryWrapper<IntegrationApi> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationApi::getDelFlag, "0");
if (query != null) {
wrapper.eq(query.getPlatformId() != null, IntegrationApi::getPlatformId, query.getPlatformId());
wrapper.like(StringUtils.isNotBlank(query.getApiCode()), IntegrationApi::getApiCode, query.getApiCode());
wrapper.like(StringUtils.isNotBlank(query.getApiName()), IntegrationApi::getApiName, query.getApiName());
wrapper.eq(query.getStatus() != null, IntegrationApi::getStatus, query.getStatus());
}
wrapper.orderByAsc(IntegrationApi::getSortOrder).orderByAsc(IntegrationApi::getId);
return apiMapper.selectList(wrapper);
}
@Override
public IntegrationApi selectApiById(Long id) {
if (id == null) {
return null;
}
LambdaQueryWrapper<IntegrationApi> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationApi::getId, id)
.eq(IntegrationApi::getDelFlag, "0")
.last("limit 1");
return apiMapper.selectOne(wrapper);
}
@Override
public int insertApi(IntegrationApi api) {
checkApi(api);
if (api.getStatus() == null) {
api.setStatus(1);
}
if (api.getSortOrder() == null) {
api.setSortOrder(0);
}
api.setApiCode(api.getApiCode().toUpperCase());
api.setMethod(api.getMethod().toUpperCase());
api.setHeadersJson(normalizeJson(api.getHeadersJson(), "请求头模板"));
api.setQueryJson(normalizeJson(api.getQueryJson(), "Query模板"));
api.setBodyJson(normalizeJson(api.getBodyJson(), "Body模板"));
api.setDelFlag("0");
api.setCreateTime(new Date());
return apiMapper.insert(api);
}
@Override
public int updateApi(IntegrationApi api) {
if (api == null || api.getId() == null) {
throw new ServiceException("接口ID不能为空");
}
checkApi(api);
api.setApiCode(api.getApiCode().toUpperCase());
api.setMethod(api.getMethod().toUpperCase());
api.setHeadersJson(normalizeJson(api.getHeadersJson(), "请求头模板"));
api.setQueryJson(normalizeJson(api.getQueryJson(), "Query模板"));
api.setBodyJson(normalizeJson(api.getBodyJson(), "Body模板"));
api.setUpdateTime(new Date());
return apiMapper.updateById(api);
}
@Override
public int deleteApiByIds(Long[] ids) {
int rows = 0;
if (ids == null) {
return rows;
}
for (Long id : ids) {
if (id == null) {
continue;
}
IntegrationApi api = new IntegrationApi();
api.setId(id);
api.setDelFlag("2");
api.setUpdateTime(new Date());
rows += apiMapper.updateById(api);
}
return rows;
}
@Override
public List<IntegrationDeviceMapping> selectDeviceMappingList(IntegrationDeviceMapping query) {
LambdaQueryWrapper<IntegrationDeviceMapping> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationDeviceMapping::getDelFlag, "0");
if (query != null) {
wrapper.eq(query.getPlatformId() != null, IntegrationDeviceMapping::getPlatformId, query.getPlatformId());
wrapper.like(StringUtils.isNotBlank(query.getExternalDeviceId()), IntegrationDeviceMapping::getExternalDeviceId, query.getExternalDeviceId());
wrapper.like(StringUtils.isNotBlank(query.getExternalDeviceName()), IntegrationDeviceMapping::getExternalDeviceName, query.getExternalDeviceName());
wrapper.eq(query.getProductId() != null, IntegrationDeviceMapping::getProductId, query.getProductId());
wrapper.like(StringUtils.isNotBlank(query.getSerialNumber()), IntegrationDeviceMapping::getSerialNumber, query.getSerialNumber());
wrapper.eq(query.getStatus() != null, IntegrationDeviceMapping::getStatus, query.getStatus());
}
wrapper.orderByDesc(IntegrationDeviceMapping::getCreateTime);
return deviceMappingMapper.selectList(wrapper);
}
@Override
public IntegrationDeviceMapping selectDeviceMappingById(Long id) {
if (id == null) {
return null;
}
LambdaQueryWrapper<IntegrationDeviceMapping> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationDeviceMapping::getId, id)
.eq(IntegrationDeviceMapping::getDelFlag, "0")
.last("limit 1");
return deviceMappingMapper.selectOne(wrapper);
}
@Override
public int insertDeviceMapping(IntegrationDeviceMapping mapping) {
checkDeviceMapping(mapping);
if (mapping.getStatus() == null) {
mapping.setStatus(1);
}
mapping.setIdentifierMappingJson(normalizeJson(mapping.getIdentifierMappingJson(), "指标映射"));
mapping.setDelFlag("0");
mapping.setCreateTime(new Date());
return deviceMappingMapper.insert(mapping);
}
@Override
public int updateDeviceMapping(IntegrationDeviceMapping mapping) {
if (mapping == null || mapping.getId() == null) {
throw new ServiceException("映射ID不能为空");
}
checkDeviceMapping(mapping);
mapping.setIdentifierMappingJson(normalizeJson(mapping.getIdentifierMappingJson(), "指标映射"));
mapping.setUpdateTime(new Date());
return deviceMappingMapper.updateById(mapping);
}
@Override
public int deleteDeviceMappingByIds(Long[] ids) {
int rows = 0;
if (ids == null) {
return rows;
}
for (Long id : ids) {
if (id == null) {
continue;
}
IntegrationDeviceMapping mapping = new IntegrationDeviceMapping();
mapping.setId(id);
mapping.setDelFlag("2");
mapping.setUpdateTime(new Date());
rows += deviceMappingMapper.updateById(mapping);
}
return rows;
}
@Override
public List<IntegrationLog> selectLogList(IntegrationLog query) {
LambdaQueryWrapper<IntegrationLog> wrapper = new LambdaQueryWrapper<>();
if (query != null) {
wrapper.eq(query.getPlatformId() != null, IntegrationLog::getPlatformId, query.getPlatformId());
wrapper.like(StringUtils.isNotBlank(query.getPlatformCode()), IntegrationLog::getPlatformCode, query.getPlatformCode());
wrapper.like(StringUtils.isNotBlank(query.getFlowCode()), IntegrationLog::getFlowCode, query.getFlowCode());
wrapper.like(StringUtils.isNotBlank(query.getRequestId()), IntegrationLog::getRequestId, query.getRequestId());
wrapper.eq(query.getStatus() != null, IntegrationLog::getStatus, query.getStatus());
}
wrapper.orderByDesc(IntegrationLog::getStartTime);
return logMapper.selectList(wrapper);
}
@Override
public IntegrationSyncResult sync(IntegrationSyncRequest request) {
if (request == null || StringUtils.isBlank(request.getPlatformCode())) {
throw new ServiceException("平台编码不能为空");
}
String platformCode = request.getPlatformCode().toUpperCase();
IntegrationPlatform platform = selectPlatformByCode(platformCode);
if (platform == null || platform.getStatus() == null || platform.getStatus() != 1) {
throw new ServiceException("平台不存在或未启用:" + platformCode);
}
PlatformAdapter adapter = adapterMap.get(platformCode);
if (adapter == null) {
throw new ServiceException("未找到平台适配器:" + platformCode);
}
String requestId = UUID.randomUUID().toString().replace("-", "");
IntegrationLog log = new IntegrationLog();
log.setPlatformId(platform.getId());
log.setPlatformCode(platformCode);
log.setFlowCode(platformCode + "_SYNC");
log.setRequestId(requestId);
log.setStartTime(new Date());
log.setStatus(2);
log.setSuccessCount(0);
log.setFailCount(0);
logMapper.insert(log);
try {
IntegrationSyncResult result = adapter.sync(platform, request);
result.setRequestId(requestId);
log.setStatus(result.getFailCount() == 0 ? 1 : 0);
log.setSuccessCount(result.getSuccessCount());
log.setFailCount(result.getFailCount());
log.setDetailJson(com.alibaba.fastjson2.JSON.toJSONString(result.getDetails()));
log.setEndTime(new Date());
logMapper.updateById(log);
return result;
} catch (Exception e) {
log.setStatus(0);
log.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 1000));
log.setEndTime(new Date());
logMapper.updateById(log);
throw e;
}
}
@Override
public IntegrationSyncResult selfTest(String platformCode) {
String code = StringUtils.isBlank(platformCode) ? "DCP" : platformCode.toUpperCase();
PlatformAdapter adapter = adapterMap.get(code);
if (adapter == null) {
throw new ServiceException("未找到平台适配器:" + code);
}
IntegrationPlatform platform = selectPlatformByCode(code);
if (platform == null) {
platform = new IntegrationPlatform();
platform.setPlatformCode(code);
platform.setPlatformName(code + " 自测平台");
platform.setBaseUrl("http://example.com");
platform.setStatus(1);
}
IntegrationSyncResult result = adapter.selfTest(platform);
result.setRequestId(UUID.randomUUID().toString().replace("-", ""));
return result;
}
private void checkPlatform(IntegrationPlatform platform) {
if (platform == null) {
throw new ServiceException("平台配置不能为空");
}
if (StringUtils.isBlank(platform.getPlatformCode())) {
throw new ServiceException("平台编码不能为空");
}
if (StringUtils.isBlank(platform.getPlatformName())) {
throw new ServiceException("平台名称不能为空");
}
if (StringUtils.isBlank(platform.getBaseUrl())) {
throw new ServiceException("平台基础地址不能为空");
}
}
private void checkApi(IntegrationApi api) {
if (api == null) {
throw new ServiceException("接口配置不能为空");
}
if (api.getPlatformId() == null) {
throw new ServiceException("平台ID不能为空");
}
if (StringUtils.isBlank(api.getApiCode())) {
throw new ServiceException("接口编码不能为空");
}
if (StringUtils.isBlank(api.getMethod())) {
throw new ServiceException("请求方法不能为空");
}
if (StringUtils.isBlank(api.getUri())) {
throw new ServiceException("接口URI不能为空");
}
}
private void checkDeviceMapping(IntegrationDeviceMapping mapping) {
if (mapping == null) {
throw new ServiceException("设备映射不能为空");
}
if (mapping.getPlatformId() == null) {
throw new ServiceException("平台ID不能为空");
}
if (StringUtils.isBlank(mapping.getExternalDeviceId())) {
throw new ServiceException("第三方设备ID不能为空");
}
if (StringUtils.isBlank(mapping.getSerialNumber())) {
throw new ServiceException("本系统设备编号不能为空");
}
}
private String normalizeJson(String json, String label) {
if (StringUtils.isBlank(json)) {
return null;
}
String normalized = json.trim();
try {
com.alibaba.fastjson2.JSON.parse(normalized);
return normalized;
} catch (Exception e) {
throw new ServiceException(label + "必须是合法JSON");
}
}
}

View File

@ -1,117 +0,0 @@
package com.fastbee.iot.service.impl.integration.dcp;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.iot.domain.integration.IntegrationPlatform;
import com.fastbee.iot.service.integration.IDcpOpenApiService;
import com.fastbee.iot.service.integration.IIntegrationPlatformService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* DCP兼容开放接口服务。
*/
@Service
public class DcpOpenApiServiceImpl implements IDcpOpenApiService {
private static final String PLATFORM_CODE = "DCP";
private static final String TOKEN_PREFIX = "iot:integration:open:dcp:token:";
private static final int TOKEN_SECONDS = 7200;
@Resource
private IIntegrationPlatformService integrationPlatformService;
@Resource
private DcpPlatformAdapter dcpPlatformAdapter;
@Resource
private RedisCache redisCache;
@Override
public JSONObject createToken(Map<String, String> form) {
IntegrationPlatform platform = requirePlatform();
JSONObject credential = StringUtils.isBlank(platform.getCredentialJson()) ? new JSONObject() : JSON.parseObject(platform.getCredentialJson());
String expectedClientId = credential.getString("openClientId");
String expectedUserName = credential.getString("openUserName");
String expectedPassword = credential.getString("openPassword");
if (StringUtils.isAnyBlank(expectedClientId, expectedUserName, expectedPassword)) {
throw new ServiceException("DCP开放接口账号未配置");
}
if (!"password".equals(form.get("grant_type"))) {
throw new ServiceException("grant_type不支持");
}
if (!expectedClientId.equals(form.get("client_id"))
|| !expectedUserName.equals(form.get("userName"))
|| !expectedPassword.equals(form.get("password"))) {
throw new ServiceException("DCP开放接口账号或密码错误");
}
String token = UUID.randomUUID().toString().replace("-", "");
redisCache.setCacheObject(TOKEN_PREFIX + token, platform.getId(), TOKEN_SECONDS, TimeUnit.SECONDS);
JSONObject result = new JSONObject();
result.put("access_token", token);
result.put("expires_in", TOKEN_SECONDS);
result.put("token_type", "Bearer");
result.put("scope", StringUtils.defaultIfBlank(form.get("scope"), "DACP"));
return result;
}
@Override
public JSONObject getUser(String authorization) {
return dcpPlatformAdapter.fetchUser(requireAuthorizedPlatform(authorization));
}
@Override
public JSONArray getProjects(String authorization, String userId) {
if (StringUtils.isBlank(userId)) {
throw new ServiceException("userId不能为空");
}
return dcpPlatformAdapter.fetchProjects(requireAuthorizedPlatform(authorization), userId);
}
@Override
public JSONArray getStressSensors(String authorization, String projectId) {
if (StringUtils.isBlank(projectId)) {
throw new ServiceException("projectId不能为空");
}
return dcpPlatformAdapter.fetchStressSensors(requireAuthorizedPlatform(authorization), projectId);
}
@Override
public JSONObject getDataset(String authorization, String startTime, String endTime, Boolean original, List<String> deviceIds) {
if (deviceIds == null || deviceIds.isEmpty()) {
throw new ServiceException("deviceId数组不能为空");
}
return dcpPlatformAdapter.fetchDataset(requireAuthorizedPlatform(authorization), startTime, endTime, original, deviceIds);
}
private IntegrationPlatform requireAuthorizedPlatform(String authorization) {
if (StringUtils.isBlank(authorization) || !authorization.startsWith("Bearer ")) {
throw new ServiceException("Authorization不能为空");
}
String token = authorization.substring("Bearer ".length());
Long platformId = redisCache.getCacheObject(TOKEN_PREFIX + token);
IntegrationPlatform platform = requirePlatform();
if (platformId == null || !platform.getId().equals(platformId)) {
throw new ServiceException("Authorization无效或已过期");
}
return platform;
}
private IntegrationPlatform requirePlatform() {
IntegrationPlatform platform = integrationPlatformService.selectPlatformByCode(PLATFORM_CODE);
if (platform == null || platform.getStatus() == null || platform.getStatus() != 1) {
throw new ServiceException("DCP平台未启用");
}
return platform;
}
}

View File

@ -1,547 +0,0 @@
package com.fastbee.iot.service.impl.integration.dcp;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
import com.fastbee.common.core.thingsModel.ThingsModelValuesInput;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.iot.domain.integration.IntegrationApi;
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
import com.fastbee.iot.domain.integration.IntegrationPlatform;
import com.fastbee.iot.mapper.integration.IntegrationApiMapper;
import com.fastbee.iot.mapper.integration.IntegrationDeviceMappingMapper;
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
import com.fastbee.iot.model.integration.IntegrationSyncResult;
import com.fastbee.iot.model.integration.UnifiedTelemetry;
import com.fastbee.iot.service.IDeviceService;
import com.fastbee.iot.service.integration.PlatformAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* DCP 轴力计平台适配器。
*/
@Slf4j
@Component
public class DcpPlatformAdapter implements PlatformAdapter {
private static final String PLATFORM_CODE = "DCP";
private static final String API_TOKEN = "dcp_token";
private static final String API_USER = "dcp_user_id";
private static final String API_PROJECT = "dcp_project_list";
private static final String API_DEVICE = "dcp_stress_sensor_list";
private static final String API_DATA = "dcp_stress_sensor_data";
@Resource
private IntegrationApiMapper apiMapper;
@Resource
private IntegrationDeviceMappingMapper deviceMappingMapper;
@Resource
private IDeviceService deviceService;
@Resource
private RedisCache redisCache;
private final RestTemplate restTemplate = new RestTemplate();
@Override
public String platformCode() {
return PLATFORM_CODE;
}
@Override
public IntegrationSyncResult sync(IntegrationPlatform platform, IntegrationSyncRequest request) {
IntegrationSyncResult result = new IntegrationSyncResult();
result.setPlatformCode(PLATFORM_CODE);
result.setFlowCode("DCP_STRESS_SENSOR_SYNC");
result.setDryRun(Boolean.TRUE.equals(request.getDryRun()));
JSONObject credential = parseObject(platform.getCredentialJson());
String authorization = resolveAuthorization(platform, credential);
String userId = getUserId(platform, authorization);
String projectId = StringUtils.isNotBlank(request.getProjectId()) ? request.getProjectId() : getFirstProjectId(platform, authorization, userId);
if (StringUtils.isBlank(projectId)) {
throw new ServiceException("DCP项目ID为空");
}
List<JSONObject> devices = getDevices(platform, authorization, projectId);
List<String> targetDeviceIds = selectTargetDeviceIds(devices, request.getExternalDeviceIds());
result.setDeviceCount(targetDeviceIds.size());
if (targetDeviceIds.isEmpty()) {
result.setMessage("未获取到可同步的DCP轴力计测点");
return result;
}
Map<String, List<JSONObject>> dataMap = getDataset(platform, authorization, request, targetDeviceIds);
Map<String, IntegrationDeviceMapping> mappings = loadMappings(platform.getId(), targetDeviceIds);
int dataCount = 0;
for (String externalDeviceId : targetDeviceIds) {
List<JSONObject> rows = dataMap.get(externalDeviceId);
if (rows == null) {
continue;
}
IntegrationDeviceMapping mapping = mappings.get(externalDeviceId);
for (JSONObject row : rows) {
dataCount++;
if (mapping == null) {
result.addFail("未配置设备映射 externalDeviceId=" + externalDeviceId);
continue;
}
try {
UnifiedTelemetry telemetry = convertTelemetry(externalDeviceId, findDeviceName(devices, externalDeviceId), row);
if (!result.isDryRun()) {
writeThingsModel(mapping, telemetry);
}
result.addSuccess();
} catch (Exception e) {
result.addFail("写入失败 externalDeviceId=" + externalDeviceId + ", error=" + e.getMessage());
}
}
}
result.setDataCount(dataCount);
result.setMessage(result.isDryRun() ? "DCP同步演练完成" : "DCP同步完成");
return result;
}
@Override
public IntegrationSyncResult selfTest(IntegrationPlatform platform) {
IntegrationSyncResult result = new IntegrationSyncResult();
result.setPlatformCode(PLATFORM_CODE);
result.setFlowCode("DCP_STRESS_SENSOR_SELF_TEST");
result.setDryRun(true);
JSONObject sample = JSON.parseObject("{\"isDeleted\":false,\"epoch\":\"2026-04-09T08:05:12+08:00\",\"deviceId\":\"3a1e1d88-c0ed-5f77-56f1-7704351b5a33\",\"e\":-323.26,\"fi\":0,\"ti\":0,\"span\":0,\"isOriginal\":true,\"temperature\":0,\"ct\":0,\"pt\":0}");
UnifiedTelemetry telemetry = convertTelemetry(sample.getString("deviceId"), "第二层支护结构DK138+931", sample);
if (!"3a1e1d88-c0ed-5f77-56f1-7704351b5a33".equals(telemetry.getExternalDeviceId())) {
result.addFail("设备ID解析失败");
}
if (!telemetry.getValues().containsKey("e") || !telemetry.getValues().containsKey("temperature")) {
result.addFail("轴力计数据字段解析失败");
}
if (telemetry.getReportTime() == null) {
result.addFail("数据时间解析失败");
}
if (result.getFailCount() == 0) {
result.addSuccess();
result.setDataCount(1);
result.setDeviceCount(1);
result.setMessage("DCP适配器自测通过");
} else {
result.setMessage("DCP适配器自测失败");
}
return result;
}
public JSONObject fetchUser(IntegrationPlatform platform) {
String authorization = resolveAuthorization(platform, parseObject(platform.getCredentialJson()));
IntegrationApi api = getApi(platform.getId(), API_USER);
return exchangeJson(platform, api, authorization, null, null);
}
public JSONArray fetchProjects(IntegrationPlatform platform, String userId) {
String authorization = resolveAuthorization(platform, parseObject(platform.getCredentialJson()));
IntegrationApi api = getApi(platform.getId(), API_PROJECT);
Map<String, Object> pathVars = new HashMap<>();
pathVars.put("userId", userId);
return exchangeArray(platform, api, authorization, pathVars, null);
}
public JSONArray fetchStressSensors(IntegrationPlatform platform, String projectId) {
String authorization = resolveAuthorization(platform, parseObject(platform.getCredentialJson()));
IntegrationApi api = getApi(platform.getId(), API_DEVICE);
Map<String, Object> pathVars = new HashMap<>();
pathVars.put("projectId", projectId);
pathVars.put("projcetId", projectId);
return exchangeArray(platform, api, authorization, pathVars, null);
}
public JSONObject fetchDataset(IntegrationPlatform platform, String startTime, String endTime, Boolean original, List<String> deviceIds) {
String authorization = resolveAuthorization(platform, parseObject(platform.getCredentialJson()));
IntegrationApi api = getApi(platform.getId(), API_DATA);
Map<String, Object> query = new HashMap<>();
query.put("startTime", defaultStartTime(startTime));
query.put("endTime", defaultEndTime(endTime));
query.put("isOriginal", String.valueOf(original == null || original));
return exchangeJson(platform, api, authorization, query, deviceIds == null ? Collections.emptyList() : deviceIds);
}
private String resolveAuthorization(IntegrationPlatform platform, JSONObject credential) {
String cacheKey = "iot:integration:dcp:token:" + platform.getId();
try {
String cached = redisCache.getCacheObject(cacheKey);
if (StringUtils.isNotBlank(cached)) {
return cached;
}
} catch (Exception e) {
log.debug("读取DCP token缓存失败,将直接请求token:{}", e.getMessage());
}
IntegrationApi api = getApi(platform.getId(), API_TOKEN);
String url = buildUrl(platform.getBaseUrl(), api.getUri());
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", value(credential, "grant_type", "grantType", "password"));
body.add("scope", value(credential, "scope", null, "DACP"));
body.add("client_id", value(credential, "client_id", "clientId", "DACP_App"));
body.add("userName", value(credential, "userName", "username", null));
body.add("password", value(credential, "password", null, null));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
JSONObject json = JSON.parseObject(response.getBody());
String token = json.getString("access_token");
String tokenType = json.getString("token_type");
if (StringUtils.isBlank(token)) {
throw new ServiceException("DCP token获取失败");
}
String authorization = (StringUtils.isBlank(tokenType) ? "Bearer" : tokenType) + " " + token;
int expires = json.getIntValue("expires_in");
try {
redisCache.setCacheObject(cacheKey, authorization, Math.max(expires - 60, 60), TimeUnit.SECONDS);
} catch (Exception e) {
log.debug("写入DCP token缓存失败:{}", e.getMessage());
}
return authorization;
}
private String getUserId(IntegrationPlatform platform, String authorization) {
IntegrationApi api = getApi(platform.getId(), API_USER);
JSONObject json = exchangeJson(platform, api, authorization, null, null);
String id = json.getString("id");
if (StringUtils.isBlank(id)) {
throw new ServiceException("DCP用户ID获取失败");
}
return id;
}
private String getFirstProjectId(IntegrationPlatform platform, String authorization, String userId) {
IntegrationApi api = getApi(platform.getId(), API_PROJECT);
Map<String, Object> pathVars = new HashMap<>();
pathVars.put("userId", userId);
JSONArray array = exchangeArray(platform, api, authorization, pathVars, null);
if (array == null || array.isEmpty()) {
return null;
}
return array.getJSONObject(0).getString("id");
}
private List<JSONObject> getDevices(IntegrationPlatform platform, String authorization, String projectId) {
IntegrationApi api = getApi(platform.getId(), API_DEVICE);
Map<String, Object> pathVars = new HashMap<>();
pathVars.put("projectId", projectId);
pathVars.put("projcetId", projectId);
JSONArray array = exchangeArray(platform, api, authorization, pathVars, null);
return toObjectList(array);
}
private Map<String, List<JSONObject>> getDataset(IntegrationPlatform platform, String authorization, IntegrationSyncRequest request, List<String> deviceIds) {
IntegrationApi api = getApi(platform.getId(), API_DATA);
Map<String, Object> query = new HashMap<>();
query.put("startTime", defaultStartTime(request.getStartTime()));
query.put("endTime", defaultEndTime(request.getEndTime()));
query.put("isOriginal", String.valueOf(request.getOriginal() == null || request.getOriginal()));
JSONObject json = exchangeJson(platform, api, authorization, query, deviceIds);
Map<String, List<JSONObject>> result = new LinkedHashMap<>();
for (String key : json.keySet()) {
result.put(key, toObjectList(json.getJSONArray(key)));
}
return result;
}
private JSONObject exchangeJson(IntegrationPlatform platform, IntegrationApi api, String authorization, Map<String, Object> varsOrQuery, Object body) {
String response = exchange(platform, api, authorization, varsOrQuery, body);
if (StringUtils.isBlank(response)) {
return new JSONObject();
}
return JSON.parseObject(response);
}
private JSONArray exchangeArray(IntegrationPlatform platform, IntegrationApi api, String authorization, Map<String, Object> varsOrQuery, Object body) {
String response = exchange(platform, api, authorization, varsOrQuery, body);
if (StringUtils.isBlank(response)) {
return new JSONArray();
}
return JSON.parseArray(response);
}
private String exchange(IntegrationPlatform platform, IntegrationApi api, String authorization, Map<String, Object> varsOrQuery, Object body) {
String uri = render(api.getUri(), varsOrQuery);
String url = buildUrl(platform.getBaseUrl(), uri);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", authorization);
if ("application/x-www-form-urlencoded".equalsIgnoreCase(api.getContentType())) {
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
} else {
headers.setContentType(MediaType.APPLICATION_JSON);
}
if ("GET".equalsIgnoreCase(api.getMethod())) {
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(headers), String.class);
return response.getBody();
}
if (API_DATA.equals(api.getApiCode()) && varsOrQuery != null && !varsOrQuery.isEmpty()) {
url = appendQuery(url, varsOrQuery);
}
HttpMethod method = HttpMethod.resolve(api.getMethod());
if (method == null) {
throw new ServiceException("不支持的请求方法:" + api.getMethod());
}
ResponseEntity<String> response = restTemplate.exchange(url, method, new HttpEntity<>(body, headers), String.class);
return response.getBody();
}
private void writeThingsModel(IntegrationDeviceMapping mapping, UnifiedTelemetry telemetry) {
Map<String, String> identifierMapping = parseStringMap(mapping.getIdentifierMappingJson());
if (identifierMapping.isEmpty()) {
identifierMapping.put("e", "axialForce");
identifierMapping.put("temperature", "temperature");
identifierMapping.put("ct", "ct");
identifierMapping.put("pt", "pt");
}
List<ThingsModelSimpleItem> items = new ArrayList<>();
for (Map.Entry<String, Object> entry : telemetry.getValues().entrySet()) {
String identifier = identifierMapping.get(entry.getKey());
if (StringUtils.isBlank(identifier) || entry.getValue() == null) {
continue;
}
ThingsModelSimpleItem item = new ThingsModelSimpleItem();
item.setId(identifier);
item.setValue(String.valueOf(entry.getValue()));
item.setTs(telemetry.getReportTime());
item.setRemark("integration:" + PLATFORM_CODE);
items.add(item);
}
if (items.isEmpty()) {
return;
}
ThingsModelValuesInput input = new ThingsModelValuesInput();
input.setProductId(mapping.getProductId());
input.setDeviceNumber(mapping.getSerialNumber());
input.setThingsModelValueRemarkItem(items);
deviceService.reportDeviceThingsModelValue(input, 1, false);
}
private UnifiedTelemetry convertTelemetry(String externalDeviceId, String externalDeviceName, JSONObject row) {
UnifiedTelemetry telemetry = new UnifiedTelemetry();
telemetry.setPlatformCode(PLATFORM_CODE);
telemetry.setExternalDeviceId(externalDeviceId);
telemetry.setExternalDeviceName(externalDeviceName);
telemetry.setRaw(row);
telemetry.setReportTime(parseDate(row.getString("epoch")));
putIfPresent(telemetry.getValues(), "e", row.get("e"));
putIfPresent(telemetry.getValues(), "temperature", row.get("temperature"));
putIfPresent(telemetry.getValues(), "ct", row.get("ct"));
putIfPresent(telemetry.getValues(), "pt", row.get("pt"));
putIfPresent(telemetry.getValues(), "fi", row.get("fi"));
putIfPresent(telemetry.getValues(), "ti", row.get("ti"));
putIfPresent(telemetry.getValues(), "span", row.get("span"));
return telemetry;
}
private Map<String, IntegrationDeviceMapping> loadMappings(Long platformId, List<String> externalDeviceIds) {
if (externalDeviceIds.isEmpty()) {
return Collections.emptyMap();
}
LambdaQueryWrapper<IntegrationDeviceMapping> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationDeviceMapping::getPlatformId, platformId)
.eq(IntegrationDeviceMapping::getStatus, 1)
.eq(IntegrationDeviceMapping::getDelFlag, "0")
.in(IntegrationDeviceMapping::getExternalDeviceId, externalDeviceIds);
List<IntegrationDeviceMapping> list = deviceMappingMapper.selectList(wrapper);
Map<String, IntegrationDeviceMapping> result = new HashMap<>();
for (IntegrationDeviceMapping item : list) {
result.put(item.getExternalDeviceId(), item);
}
return result;
}
private IntegrationApi getApi(Long platformId, String apiCode) {
LambdaQueryWrapper<IntegrationApi> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(IntegrationApi::getPlatformId, platformId)
.eq(IntegrationApi::getApiCode, apiCode)
.eq(IntegrationApi::getDelFlag, "0")
.last("limit 1");
IntegrationApi api = apiMapper.selectOne(wrapper);
return api == null ? defaultApi(apiCode) : api;
}
private IntegrationApi defaultApi(String apiCode) {
IntegrationApi api = new IntegrationApi();
api.setApiCode(apiCode);
api.setStatus(1);
api.setContentType(MediaType.APPLICATION_JSON_VALUE);
if (API_TOKEN.equals(apiCode)) {
api.setMethod("POST");
api.setUri("/connect/token");
api.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
} else if (API_USER.equals(apiCode)) {
api.setMethod("GET");
api.setUri("/api/app/user/user-id");
} else if (API_PROJECT.equals(apiCode)) {
api.setMethod("GET");
api.setUri("/api/app/project/by-user-id/{userId}");
} else if (API_DEVICE.equals(apiCode)) {
api.setMethod("POST");
api.setUri("/api/app/stress-sensor/by-project-id/{projectId}");
} else if (API_DATA.equals(apiCode)) {
api.setMethod("POST");
api.setUri("/api/app/inclino-data/load-dataset-by-device-ids");
} else {
throw new ServiceException("未知DCP接口编码:" + apiCode);
}
return api;
}
private List<String> selectTargetDeviceIds(List<JSONObject> devices, List<String> requested) {
if (requested != null && !requested.isEmpty()) {
return requested;
}
List<String> ids = new ArrayList<>();
for (JSONObject device : devices) {
String id = device.getString("id");
if (StringUtils.isNotBlank(id)) {
ids.add(id);
}
}
return ids;
}
private String findDeviceName(List<JSONObject> devices, String id) {
for (JSONObject device : devices) {
if (id.equals(device.getString("id"))) {
return device.getString("name");
}
}
return null;
}
private List<JSONObject> toObjectList(JSONArray array) {
List<JSONObject> result = new ArrayList<>();
if (array == null) {
return result;
}
for (int i = 0; i < array.size(); i++) {
result.add(array.getJSONObject(i));
}
return result;
}
private JSONObject parseObject(String json) {
return StringUtils.isBlank(json) ? new JSONObject() : JSON.parseObject(json);
}
private Map<String, String> parseStringMap(String json) {
Map<String, String> result = new LinkedHashMap<>();
if (StringUtils.isBlank(json)) {
return result;
}
JSONObject object = JSON.parseObject(json);
for (String key : object.keySet()) {
result.put(key, object.getString(key));
}
return result;
}
private String value(JSONObject object, String key, String alias, String defaultValue) {
String value = object.getString(key);
if (StringUtils.isBlank(value) && StringUtils.isNotBlank(alias)) {
value = object.getString(alias);
}
return StringUtils.isBlank(value) ? defaultValue : value;
}
private Date parseDate(String text) {
if (StringUtils.isBlank(text)) {
return new Date();
}
return Date.from(OffsetDateTime.parse(text).toInstant());
}
private void putIfPresent(Map<String, Object> values, String key, Object value) {
if (value == null) {
return;
}
if (value instanceof BigDecimal) {
values.put(key, ((BigDecimal) value).stripTrailingZeros().toPlainString());
} else {
values.put(key, value);
}
}
private String defaultStartTime(String value) {
if (StringUtils.isNotBlank(value)) {
return value;
}
return OffsetDateTime.now().minusDays(1).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
private String defaultEndTime(String value) {
if (StringUtils.isNotBlank(value)) {
return value;
}
return OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
private String buildUrl(String baseUrl, String uri) {
String base = baseUrl == null ? "" : baseUrl;
while (base.endsWith("/")) {
base = base.substring(0, base.length() - 1);
}
String path = uri.startsWith("/") ? uri : "/" + uri;
return base + path;
}
private String render(String template, Map<String, Object> vars) {
if (template == null || vars == null) {
return template;
}
String result = template;
for (Map.Entry<String, Object> entry : vars.entrySet()) {
String value = String.valueOf(entry.getValue());
result = result.replace("{" + entry.getKey() + "}", value);
result = result.replace("${" + entry.getKey() + "}", value);
}
return result;
}
private String appendQuery(String url, Map<String, Object> query) {
StringBuilder builder = new StringBuilder(url);
builder.append(url.contains("?") ? "&" : "?");
boolean first = true;
for (Map.Entry<String, Object> entry : query.entrySet()) {
if (!first) {
builder.append("&");
}
builder.append(encode(entry.getKey())).append("=").append(encode(String.valueOf(entry.getValue())));
first = false;
}
return builder.toString();
}
private String encode(String value) {
try {
return URLEncoder.encode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ServiceException("URL参数编码失败");
}
}
}

View File

@ -1,20 +0,0 @@
package com.fastbee.iot.service.integration;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import java.util.List;
import java.util.Map;
public interface IDcpOpenApiService {
JSONObject createToken(Map<String, String> form);
JSONObject getUser(String authorization);
JSONArray getProjects(String authorization, String userId);
JSONArray getStressSensors(String authorization, String projectId);
JSONObject getDataset(String authorization, String startTime, String endTime, Boolean original, List<String> deviceIds);
}

View File

@ -1,51 +0,0 @@
package com.fastbee.iot.service.integration;
import com.fastbee.iot.domain.integration.IntegrationPlatform;
import com.fastbee.iot.domain.integration.IntegrationApi;
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
import com.fastbee.iot.domain.integration.IntegrationLog;
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
import com.fastbee.iot.model.integration.IntegrationSyncResult;
import java.util.List;
public interface IIntegrationPlatformService {
IntegrationPlatform selectPlatformByCode(String platformCode);
IntegrationPlatform selectPlatformById(Long id);
List<IntegrationPlatform> selectPlatformList(IntegrationPlatform query);
int insertPlatform(IntegrationPlatform platform);
int updatePlatform(IntegrationPlatform platform);
int deletePlatformByIds(Long[] ids);
List<IntegrationApi> selectApiList(IntegrationApi query);
IntegrationApi selectApiById(Long id);
int insertApi(IntegrationApi api);
int updateApi(IntegrationApi api);
int deleteApiByIds(Long[] ids);
List<IntegrationDeviceMapping> selectDeviceMappingList(IntegrationDeviceMapping query);
IntegrationDeviceMapping selectDeviceMappingById(Long id);
int insertDeviceMapping(IntegrationDeviceMapping mapping);
int updateDeviceMapping(IntegrationDeviceMapping mapping);
int deleteDeviceMappingByIds(Long[] ids);
List<IntegrationLog> selectLogList(IntegrationLog query);
IntegrationSyncResult sync(IntegrationSyncRequest request);
IntegrationSyncResult selfTest(String platformCode);
}

View File

@ -1,14 +0,0 @@
package com.fastbee.iot.service.integration;
import com.fastbee.iot.domain.integration.IntegrationPlatform;
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
import com.fastbee.iot.model.integration.IntegrationSyncResult;
public interface PlatformAdapter {
String platformCode();
IntegrationSyncResult sync(IntegrationPlatform platform, IntegrationSyncRequest request);
IntegrationSyncResult selfTest(IntegrationPlatform platform);
}

View File

@ -238,3 +238,332 @@ CREATE TABLE IF NOT EXISTS `platform_push_log` (
-- 注意:platform_id 需在创建 DCP 平台实例后更新,此处用占位符 0
-- 实际由 DcpPlatformAdapter 在初始化时写入
-- 平台对接另外一套方案
-- fastbee.iot_platform_api definition
CREATE TABLE `iot_platform_api` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`template_id` bigint NOT NULL COMMENT '平台模板ID',
`api_code` varchar(64) NOT NULL COMMENT '接口编码',
`api_name` varchar(128) NOT NULL COMMENT '接口名称',
`api_type` varchar(32) NOT NULL COMMENT '接口类型',
`method` varchar(16) DEFAULT NULL COMMENT '请求方法',
`path_template` varchar(512) DEFAULT NULL COMMENT '路径模板',
`content_type` varchar(64) DEFAULT NULL COMMENT '内容类型',
`header_template` json DEFAULT NULL COMMENT 'Header模板',
`query_template` json DEFAULT NULL COMMENT 'Query模板',
`body_template` json DEFAULT NULL COMMENT 'Body模板',
`auth_config` json DEFAULT NULL COMMENT '接口认证配置',
`idempotency_config` json DEFAULT NULL COMMENT '幂等配置',
`success_condition` json DEFAULT NULL COMMENT '成功条件',
`response_mapping` json DEFAULT NULL COMMENT '响应映射',
`retry_policy` json DEFAULT NULL COMMENT '重试策略',
`timeout_ms` int DEFAULT NULL COMMENT '超时时间毫秒',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_template_api_code` (`template_id`,`api_code`),
KEY `idx_template_api_type` (`template_id`,`api_type`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-接口定义';
-- fastbee.iot_platform_api_field definition
CREATE TABLE `iot_platform_api_field` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`api_id` bigint NOT NULL COMMENT '接口ID',
`direction` varchar(16) NOT NULL COMMENT '方向(REQUEST RESPONSE)',
`field_path` varchar(256) NOT NULL COMMENT '字段路径',
`field_name` varchar(128) DEFAULT NULL COMMENT '字段名称',
`data_type` varchar(32) DEFAULT NULL COMMENT '数据类型',
`required` tinyint(1) DEFAULT '0' COMMENT '是否必填',
`default_value` varchar(512) DEFAULT NULL COMMENT '默认值',
`example_value` varchar(512) DEFAULT NULL COMMENT '示例值',
`description` varchar(500) DEFAULT NULL COMMENT '说明',
`sort_order` int DEFAULT '0' COMMENT '排序',
PRIMARY KEY (`id`),
KEY `idx_api_direction` (`api_id`,`direction`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-接口字段';
-- fastbee.iot_platform_binding definition
CREATE TABLE `iot_platform_binding` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`tenant_channel_id` bigint NOT NULL COMMENT '租户平台实例ID',
`flow_id` bigint NOT NULL COMMENT '平台流程ID',
`product_id` bigint DEFAULT NULL COMMENT '产品ID',
`product_name` varchar(128) DEFAULT NULL COMMENT '产品名称',
`device_id` bigint DEFAULT NULL COMMENT 'FastBee设备ID',
`device_name` varchar(128) DEFAULT NULL COMMENT 'FastBee设备名称',
`serial_number` varchar(64) NOT NULL COMMENT 'FastBee设备编号',
`external_user_id` varchar(128) DEFAULT NULL COMMENT '绑定级第三方用户ID',
`external_project_id` varchar(128) DEFAULT NULL COMMENT '绑定级第三方项目ID',
`external_device_type` varchar(32) DEFAULT NULL COMMENT '第三方设备类型',
`binding_name` varchar(128) DEFAULT NULL COMMENT '绑定名称',
`exchange_mode` varchar(32) NOT NULL COMMENT '交换模式',
`config_json` json DEFAULT NULL COMMENT '绑定配置',
`last_success_time` datetime DEFAULT NULL COMMENT '最近成功时间',
`last_success_value` varchar(128) DEFAULT NULL COMMENT '最近成功值',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_binding` (`tenant_channel_id`,`flow_id`,`serial_number`,`del_flag`),
KEY `idx_binding_device` (`tenant_id`,`product_id`,`serial_number`),
KEY `idx_binding_channel_flow` (`tenant_channel_id`,`flow_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-设备绑定';
-- fastbee.iot_platform_exchange_attempt definition
CREATE TABLE `iot_platform_exchange_attempt` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`record_id` bigint NOT NULL COMMENT '交换记录ID',
`attempt_no` int NOT NULL COMMENT '尝试次数',
`direction` varchar(32) NOT NULL COMMENT '方向',
`request_url` varchar(1024) DEFAULT NULL COMMENT '请求地址或接收入口',
`request_headers` json DEFAULT NULL COMMENT '请求头',
`request_payload` json DEFAULT NULL COMMENT '请求体',
`response_status` int DEFAULT NULL COMMENT '响应状态码',
`response_body` mediumtext COMMENT '响应内容',
`success` tinyint(1) DEFAULT '0' COMMENT '是否成功',
`error_msg` varchar(1000) DEFAULT NULL COMMENT '错误信息',
`duration_ms` int DEFAULT NULL COMMENT '耗时毫秒',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_attempt_record` (`record_id`,`attempt_no`),
KEY `idx_attempt_tenant` (`tenant_id`,`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-数据交换尝试日志';
-- fastbee.iot_platform_exchange_record definition
CREATE TABLE `iot_platform_exchange_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`tenant_channel_id` bigint NOT NULL COMMENT '租户平台实例ID',
`flow_id` bigint NOT NULL COMMENT '流程ID',
`binding_id` bigint DEFAULT NULL COMMENT '绑定ID',
`task_id` bigint DEFAULT NULL COMMENT '任务ID',
`direction` varchar(32) NOT NULL COMMENT '方向',
`source_type` varchar(32) DEFAULT NULL COMMENT '来源类型',
`sink_type` varchar(32) DEFAULT NULL COMMENT '落点类型',
`source_device_id` bigint DEFAULT NULL COMMENT 'FastBee设备ID',
`serial_number` varchar(64) DEFAULT NULL COMMENT '设备编号',
`external_message_id` varchar(128) DEFAULT NULL COMMENT '第三方消息ID',
`source_ref_id` varchar(128) DEFAULT NULL COMMENT '来源ID',
`source_time` datetime DEFAULT NULL COMMENT '数据时间',
`time_window_start` datetime DEFAULT NULL COMMENT '窗口开始',
`time_window_end` datetime DEFAULT NULL COMMENT '窗口结束',
`raw_payload` json DEFAULT NULL COMMENT '原始输入',
`standard_payload` json DEFAULT NULL COMMENT '标准消息',
`request_payload` json DEFAULT NULL COMMENT '请求载荷',
`response_payload` json DEFAULT NULL COMMENT '响应载荷',
`payload_hash` varchar(64) DEFAULT NULL COMMENT 'payload哈希',
`idempotency_key` varchar(128) NOT NULL COMMENT '幂等键',
`status` varchar(32) NOT NULL DEFAULT 'PENDING' COMMENT '状态',
`retry_count` int DEFAULT '0' COMMENT '重试次数',
`max_retry` int DEFAULT '3' COMMENT '最大重试次数',
`next_retry_time` datetime DEFAULT NULL COMMENT '下次重试时间',
`last_error` varchar(1000) DEFAULT NULL COMMENT '最近错误',
`locked_by` varchar(64) DEFAULT NULL COMMENT 'worker锁',
`locked_time` datetime DEFAULT NULL COMMENT '锁定时间',
`success_time` datetime DEFAULT NULL COMMENT '成功时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_idempotency` (`idempotency_key`),
KEY `idx_record_poll` (`status`,`next_retry_time`),
KEY `idx_record_direction` (`tenant_channel_id`,`direction`,`source_time`),
KEY `idx_record_device` (`tenant_channel_id`,`serial_number`,`source_time`),
KEY `idx_record_binding` (`binding_id`,`source_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-数据交换记录';
-- fastbee.iot_platform_exchange_task definition
CREATE TABLE `iot_platform_exchange_task` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`tenant_channel_id` bigint NOT NULL COMMENT '租户平台实例ID',
`flow_id` bigint NOT NULL COMMENT '流程ID',
`task_name` varchar(128) NOT NULL COMMENT '任务名称',
`task_type` varchar(32) NOT NULL COMMENT '任务类型',
`cron_expression` varchar(128) DEFAULT NULL COMMENT 'Cron表达式',
`window_type` varchar(32) DEFAULT NULL COMMENT '窗口类型',
`window_config` json DEFAULT NULL COMMENT '窗口配置',
`cursor_json` json DEFAULT NULL COMMENT '拉取游标',
`sink_type` varchar(32) DEFAULT NULL COMMENT '入站数据落点',
`batch_size` int DEFAULT '100' COMMENT '批次大小',
`max_retry` int DEFAULT '3' COMMENT '最大重试次数',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
`last_run_time` datetime DEFAULT NULL COMMENT '最近执行时间',
`next_run_time` datetime DEFAULT NULL COMMENT '下次执行时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_task_channel_type` (`tenant_channel_id`,`task_type`),
KEY `idx_task_next_run` (`status`,`next_run_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-数据交换任务';
-- fastbee.iot_platform_field_mapping definition
CREATE TABLE `iot_platform_field_mapping` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`binding_id` bigint NOT NULL COMMENT '绑定ID',
`direction` varchar(32) NOT NULL COMMENT '方向',
`target_path` varchar(256) NOT NULL COMMENT '目标字段路径',
`target_name` varchar(128) DEFAULT NULL COMMENT '目标字段名称',
`source_type` varchar(32) NOT NULL COMMENT '来源类型',
`source_key` varchar(256) DEFAULT NULL COMMENT '来源键',
`source_name` varchar(128) DEFAULT NULL COMMENT '来源字段名称',
`transform_type` varchar(32) DEFAULT 'DIRECT' COMMENT '转换类型',
`transform_config` json DEFAULT NULL COMMENT '转换配置',
`default_value` varchar(512) DEFAULT NULL COMMENT '默认值',
`required` tinyint(1) DEFAULT '0' COMMENT '是否必填',
`sort_order` int DEFAULT '0' COMMENT '排序',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
PRIMARY KEY (`id`),
KEY `idx_mapping_binding` (`binding_id`,`direction`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-字段映射';
-- fastbee.iot_platform_flow definition
CREATE TABLE `iot_platform_flow` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`template_id` bigint NOT NULL COMMENT '平台模板ID',
`flow_code` varchar(64) NOT NULL COMMENT '流程编码',
`flow_name` varchar(128) NOT NULL COMMENT '流程名称',
`flow_type` varchar(32) NOT NULL COMMENT '流程类型',
`adapter_code` varchar(64) DEFAULT NULL COMMENT '适配器编码',
`sink_type` varchar(32) DEFAULT NULL COMMENT '入站数据落点',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_template_flow_code` (`template_id`,`flow_code`),
KEY `idx_template_flow_type` (`template_id`,`flow_type`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-流程模板';
-- fastbee.iot_platform_flow_step definition
CREATE TABLE `iot_platform_flow_step` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`flow_id` bigint NOT NULL COMMENT '流程ID',
`step_code` varchar(64) NOT NULL COMMENT '步骤编码',
`step_name` varchar(128) NOT NULL COMMENT '步骤名称',
`step_type` varchar(32) NOT NULL COMMENT '步骤类型',
`api_id` bigint DEFAULT NULL COMMENT '接口ID',
`adapter_method` varchar(128) DEFAULT NULL COMMENT '适配器方法',
`input_mapping` json DEFAULT NULL COMMENT '入参映射',
`output_mapping` json DEFAULT NULL COMMENT '输出映射',
`condition_expr` varchar(512) DEFAULT NULL COMMENT '执行条件',
`on_fail` varchar(32) DEFAULT 'STOP' COMMENT '失败策略',
`sort_order` int DEFAULT '0' COMMENT '排序',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
PRIMARY KEY (`id`),
KEY `idx_flow_sort` (`flow_id`,`sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-流程步骤';
-- fastbee.iot_platform_resource_mapping definition
CREATE TABLE `iot_platform_resource_mapping` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`tenant_channel_id` bigint NOT NULL COMMENT '租户平台实例ID',
`binding_id` bigint DEFAULT NULL COMMENT '绑定ID',
`resource_scope` varchar(32) NOT NULL COMMENT '资源范围',
`resource_type` varchar(64) NOT NULL COMMENT '资源类型',
`local_key` varchar(128) NOT NULL COMMENT '本地唯一键',
`external_id` varchar(128) DEFAULT NULL COMMENT '外部资源ID',
`external_code` varchar(128) DEFAULT NULL COMMENT '外部资源编码',
`external_name` varchar(128) DEFAULT NULL COMMENT '外部资源名称',
`resource_payload` json DEFAULT NULL COMMENT '外部资源详情',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0失效 1有效)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_resource` (`tenant_channel_id`,`binding_id`,`resource_type`,`local_key`),
KEY `idx_resource_tenant_type` (`tenant_id`,`tenant_channel_id`,`resource_type`),
KEY `idx_external_id` (`tenant_channel_id`,`resource_type`,`external_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-资源映射';
-- fastbee.iot_platform_task_binding definition
CREATE TABLE `iot_platform_task_binding` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`task_id` bigint NOT NULL COMMENT '任务ID',
`binding_id` bigint NOT NULL COMMENT '绑定ID',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_task_binding` (`task_id`,`binding_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-任务绑定';
-- fastbee.iot_platform_template definition
CREATE TABLE `iot_platform_template` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`platform_code` varchar(64) NOT NULL COMMENT '平台编码',
`platform_name` varchar(128) NOT NULL COMMENT '平台名称',
`platform_type` varchar(32) NOT NULL COMMENT '平台类型',
`adapter_code` varchar(64) DEFAULT NULL COMMENT '适配器编码',
`base_url_template` varchar(512) DEFAULT NULL COMMENT '默认基础地址模板',
`auth_type` varchar(32) DEFAULT NULL COMMENT '认证类型',
`auth_schema` json DEFAULT NULL COMMENT '授权配置字段定义',
`request_config` json DEFAULT NULL COMMENT '默认请求配置',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_platform_code` (`platform_code`),
KEY `idx_platform_type` (`platform_type`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-平台模板';
-- fastbee.iot_platform_tenant_channel definition
CREATE TABLE `iot_platform_tenant_channel` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`tenant_name` varchar(64) DEFAULT '' COMMENT '租户名称',
`template_id` bigint NOT NULL COMMENT '平台模板ID',
`channel_code` varchar(64) NOT NULL COMMENT '租户平台实例编码',
`channel_name` varchar(128) NOT NULL COMMENT '租户平台实例名称',
`base_url` varchar(512) DEFAULT NULL COMMENT '实际基础地址',
`auth_config` json DEFAULT NULL COMMENT '租户授权配置',
`receive_config` json DEFAULT NULL COMMENT '接收安全配置',
`default_user_id` varchar(128) DEFAULT NULL COMMENT '默认第三方用户ID',
`default_project_id` varchar(128) DEFAULT NULL COMMENT '默认第三方项目ID',
`config_json` json DEFAULT NULL COMMENT '扩展配置',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(0停用 1启用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_channel_code` (`tenant_id`,`channel_code`,`del_flag`),
KEY `idx_tenant_template` (`tenant_id`,`template_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台对接-租户平台实例';

View File

@ -1,138 +0,0 @@
import request from '@/utils/request'
export function listPlatform(query) {
return request({
url: '/iot/integration/platform/list',
method: 'get',
params: query
})
}
export function getPlatform(id) {
return request({
url: '/iot/integration/platform/' + id,
method: 'get'
})
}
export function addPlatform(data) {
return request({
url: '/iot/integration/platform',
method: 'post',
data: data
})
}
export function updatePlatform(data) {
return request({
url: '/iot/integration/platform',
method: 'put',
data: data
})
}
export function delPlatform(id) {
return request({
url: '/iot/integration/platform/' + id,
method: 'delete'
})
}
export function listIntegrationApi(query) {
return request({
url: '/iot/integration/api/list',
method: 'get',
params: query
})
}
export function getIntegrationApi(id) {
return request({
url: '/iot/integration/api/' + id,
method: 'get'
})
}
export function addIntegrationApi(data) {
return request({
url: '/iot/integration/api',
method: 'post',
data: data
})
}
export function updateIntegrationApi(data) {
return request({
url: '/iot/integration/api',
method: 'put',
data: data
})
}
export function delIntegrationApi(id) {
return request({
url: '/iot/integration/api/' + id,
method: 'delete'
})
}
export function listDeviceMapping(query) {
return request({
url: '/iot/integration/device-mapping/list',
method: 'get',
params: query
})
}
export function getDeviceMapping(id) {
return request({
url: '/iot/integration/device-mapping/' + id,
method: 'get'
})
}
export function addDeviceMapping(data) {
return request({
url: '/iot/integration/device-mapping',
method: 'post',
data: data
})
}
export function updateDeviceMapping(data) {
return request({
url: '/iot/integration/device-mapping',
method: 'put',
data: data
})
}
export function delDeviceMapping(id) {
return request({
url: '/iot/integration/device-mapping/' + id,
method: 'delete'
})
}
export function listIntegrationLog(query) {
return request({
url: '/iot/integration/log/list',
method: 'get',
params: query
})
}
export function syncIntegration(data) {
return request({
url: '/iot/integration/sync',
method: 'post',
data: data
})
}
export function selfTestIntegration(platformCode) {
return request({
url: '/iot/integration/self-test/' + platformCode,
method: 'post'
})
}

View File

@ -1,808 +0,0 @@
<template>
<div class="app-container integration-page">
<el-form ref="queryForm" :model="queryParams" :inline="true" label-width="80px">
<el-form-item label="平台编码" prop="platformCode">
<el-input
v-model="queryParams.platformCode"
placeholder="请输入平台编码"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="平台名称" prop="platformName">
<el-input
v-model="queryParams.platformName"
placeholder="请输入平台名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="全部" clearable size="small">
<el-option label="启用" :value="1" />
<el-option label="停用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" v-hasPermi="['iot:integration:add']" @click="handleAddPlatform">新增平台</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" v-hasPermi="['iot:integration:edit']" @click="handleUpdatePlatform">修改平台</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" v-hasPermi="['iot:integration:remove']" @click="handleDeletePlatform">删除平台</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-video-play" size="mini" :disabled="!currentPlatform" v-hasPermi="['iot:integration:sync']" @click="openSyncDialog(false)">同步</el-button>
</el-col>
<el-col :span="1.5">
<el-button plain icon="el-icon-check" size="mini" :disabled="!currentPlatform" v-hasPermi="['iot:integration:sync']" @click="handleSelfTest">自测</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table
v-loading="loading"
:data="platformList"
highlight-current-row
@selection-change="handleSelectionChange"
@current-change="handleCurrentPlatformChange"
@row-click="selectPlatform"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="平台编码" align="center" prop="platformCode" width="120" />
<el-table-column label="平台名称" align="center" prop="platformName" min-width="160" />
<el-table-column label="基础地址" align="center" prop="baseUrl" min-width="240" show-overflow-tooltip />
<el-table-column label="认证类型" align="center" prop="authType" width="140" />
<el-table-column label="状态" align="center" width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">{{ scope.row.status === 1 ? '启用' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="210">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" v-hasPermi="['iot:integration:edit']" @click.stop="handleUpdatePlatform(scope.row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-refresh" v-hasPermi="['iot:integration:sync']" @click.stop="openSyncDialog(false, scope.row)">同步</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" v-hasPermi="['iot:integration:remove']" @click.stop="handleDeletePlatform(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div v-if="currentPlatform" class="integration-detail">
<div class="detail-header">
<span class="detail-title">{{ currentPlatform.platformName }}</span>
<span class="detail-subtitle">{{ currentPlatform.platformCode }}</span>
</div>
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="接口定义" name="api">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" v-hasPermi="['iot:integration:add']" @click="handleAddApi">新增接口</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="apiSingle" v-hasPermi="['iot:integration:edit']" @click="handleUpdateApi">修改接口</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="apiMultiple" v-hasPermi="['iot:integration:remove']" @click="handleDeleteApi">删除接口</el-button>
</el-col>
</el-row>
<el-table v-loading="apiLoading" :data="apiList" @selection-change="handleApiSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="编码" align="center" prop="apiCode" min-width="160" />
<el-table-column label="名称" align="center" prop="apiName" min-width="160" />
<el-table-column label="方法" align="center" prop="method" width="90" />
<el-table-column label="URI" align="center" prop="uri" min-width="260" show-overflow-tooltip />
<el-table-column label="依赖" align="center" prop="dependsOn" width="150" />
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
<el-table-column label="状态" align="center" width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">{{ scope.row.status === 1 ? '启用' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" v-hasPermi="['iot:integration:edit']" @click="handleUpdateApi(scope.row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" v-hasPermi="['iot:integration:remove']" @click="handleDeleteApi(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="设备映射" name="mapping">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" v-hasPermi="['iot:integration:add']" @click="handleAddMapping">新增映射</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="mappingSingle" v-hasPermi="['iot:integration:edit']" @click="handleUpdateMapping">修改映射</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="mappingMultiple" v-hasPermi="['iot:integration:remove']" @click="handleDeleteMapping">删除映射</el-button>
</el-col>
</el-row>
<el-table v-loading="mappingLoading" :data="mappingList" @selection-change="handleMappingSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="第三方设备ID" align="center" prop="externalDeviceId" min-width="170" show-overflow-tooltip />
<el-table-column label="第三方设备名称" align="center" prop="externalDeviceName" min-width="160" show-overflow-tooltip />
<el-table-column label="产品ID" align="center" prop="productId" width="100" />
<el-table-column label="设备编号" align="center" prop="serialNumber" min-width="160" show-overflow-tooltip />
<el-table-column label="指标映射" align="center" prop="identifierMappingJson" min-width="220" show-overflow-tooltip />
<el-table-column label="状态" align="center" width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">{{ scope.row.status === 1 ? '启用' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" v-hasPermi="['iot:integration:edit']" @click="handleUpdateMapping(scope.row)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" v-hasPermi="['iot:integration:remove']" @click="handleDeleteMapping(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="同步日志" name="log">
<el-table v-loading="logLoading" :data="logList">
<el-table-column label="请求ID" align="center" prop="requestId" min-width="180" show-overflow-tooltip />
<el-table-column label="流程" align="center" prop="flowCode" width="140" />
<el-table-column label="开始时间" align="center" prop="startTime" width="160" />
<el-table-column label="结束时间" align="center" prop="endTime" width="160" />
<el-table-column label="状态" align="center" width="90">
<template slot-scope="scope">
<el-tag :type="logStatusTag(scope.row.status)">{{ logStatusText(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="成功" align="center" prop="successCount" width="80" />
<el-table-column label="失败" align="center" prop="failCount" width="80" />
<el-table-column label="错误信息" align="center" prop="errorMsg" min-width="220" show-overflow-tooltip />
</el-table>
</el-tab-pane>
</el-tabs>
</div>
<el-dialog :title="platformTitle" :visible.sync="platformOpen" width="760px" append-to-body>
<el-form ref="platformForm" :model="platformForm" :rules="platformRules" label-width="110px">
<el-row>
<el-col :span="12">
<el-form-item label="平台编码" prop="platformCode">
<el-input v-model="platformForm.platformCode" placeholder="如 DCP" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="平台名称" prop="platformName">
<el-input v-model="platformForm.platformName" placeholder="请输入平台名称" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="基础地址" prop="baseUrl">
<el-input v-model="platformForm.baseUrl" placeholder="请输入第三方平台基础地址" />
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="认证类型" prop="authType">
<el-input v-model="platformForm.authType" placeholder="如 PASSWORD_TOKEN" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-switch v-model="platformForm.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="停用" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="凭证配置" prop="credentialJson">
<el-input v-model="platformForm.credentialJson" type="textarea" :rows="8" placeholder="请输入 JSON 格式凭证配置" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitPlatformForm">确 定</el-button>
<el-button @click="platformOpen = false">取 消</el-button>
</div>
</el-dialog>
<el-dialog :title="apiTitle" :visible.sync="apiOpen" width="820px" append-to-body>
<el-form ref="apiForm" :model="apiForm" :rules="apiRules" label-width="110px">
<el-row>
<el-col :span="12">
<el-form-item label="接口编码" prop="apiCode">
<el-input v-model="apiForm.apiCode" placeholder="如 dcp_token" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="接口名称" prop="apiName">
<el-input v-model="apiForm.apiName" placeholder="请输入接口名称" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="请求方法" prop="method">
<el-select v-model="apiForm.method" placeholder="方法">
<el-option label="GET" value="GET" />
<el-option label="POST" value="POST" />
<el-option label="PUT" value="PUT" />
<el-option label="DELETE" value="DELETE" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="Content-Type" prop="contentType">
<el-input v-model="apiForm.contentType" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="排序" prop="sortOrder">
<el-input-number v-model="apiForm.sortOrder" :min="0" controls-position="right" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="URI" prop="uri">
<el-input v-model="apiForm.uri" placeholder="/api/path/{id}" />
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="依赖接口">
<el-input v-model="apiForm.dependsOn" placeholder="可为空" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-switch v-model="apiForm.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="停用" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="返回路径">
<el-input v-model="apiForm.responsePath" placeholder="如 data.items" />
</el-form-item>
<el-row>
<el-col :span="8">
<el-form-item label="请求头模板">
<el-input v-model="apiForm.headersJson" type="textarea" :rows="5" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="Query模板">
<el-input v-model="apiForm.queryJson" type="textarea" :rows="5" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="Body模板">
<el-input v-model="apiForm.bodyJson" type="textarea" :rows="5" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitApiForm">确 定</el-button>
<el-button @click="apiOpen = false">取 消</el-button>
</div>
</el-dialog>
<el-dialog :title="mappingTitle" :visible.sync="mappingOpen" width="760px" append-to-body>
<el-form ref="mappingForm" :model="mappingForm" :rules="mappingRules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="第三方设备ID" prop="externalDeviceId">
<el-input v-model="mappingForm.externalDeviceId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="第三方设备名">
<el-input v-model="mappingForm.externalDeviceName" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="产品ID" prop="productId">
<el-input-number v-model="mappingForm.productId" :min="1" controls-position="right" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备编号" prop="serialNumber">
<el-input v-model="mappingForm.serialNumber" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="指标映射">
<el-input v-model="mappingForm.identifierMappingJson" type="textarea" :rows="6" placeholder='如 {"e":"axialForce","temperature":"temperature"}' />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="mappingForm.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="停用" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitMappingForm">确 定</el-button>
<el-button @click="mappingOpen = false">取 消</el-button>
</div>
</el-dialog>
<el-dialog title="平台数据同步" :visible.sync="syncOpen" width="620px" append-to-body>
<el-form ref="syncForm" :model="syncForm" label-width="110px">
<el-form-item label="平台编码">
<el-input v-model="syncForm.platformCode" disabled />
</el-form-item>
<el-form-item label="项目ID">
<el-input v-model="syncForm.projectId" placeholder="可选,不填则按平台默认拉取" />
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="开始时间">
<el-date-picker v-model="syncForm.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始时间" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束时间">
<el-date-picker v-model="syncForm.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束时间" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="第三方设备ID">
<el-input v-model="syncForm.externalDeviceIdsText" type="textarea" :rows="5" placeholder="可选,一行一个设备ID" />
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="原始数据">
<el-switch v-model="syncForm.original" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="只预演">
<el-switch v-model="syncForm.dryRun" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitSync">执 行</el-button>
<el-button @click="syncOpen = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
addDeviceMapping,
addIntegrationApi,
addPlatform,
delDeviceMapping,
delIntegrationApi,
delPlatform,
getDeviceMapping,
getIntegrationApi,
getPlatform,
listDeviceMapping,
listIntegrationApi,
listIntegrationLog,
listPlatform,
selfTestIntegration,
syncIntegration,
updateDeviceMapping,
updateIntegrationApi,
updatePlatform
} from '@/api/iot/integration'
export default {
name: 'Integration',
data() {
return {
showSearch: true,
loading: false,
apiLoading: false,
mappingLoading: false,
logLoading: false,
total: 0,
platformList: [],
apiList: [],
mappingList: [],
logList: [],
ids: [],
apiIds: [],
mappingIds: [],
single: true,
multiple: true,
apiSingle: true,
apiMultiple: true,
mappingSingle: true,
mappingMultiple: true,
currentPlatform: null,
activeTab: 'api',
queryParams: {
pageNum: 1,
pageSize: 10,
platformCode: null,
platformName: null,
status: null
},
platformOpen: false,
platformTitle: '',
platformForm: {},
platformRules: {
platformCode: [{ required: true, message: '平台编码不能为空', trigger: 'blur' }],
platformName: [{ required: true, message: '平台名称不能为空', trigger: 'blur' }],
baseUrl: [{ required: true, message: '基础地址不能为空', trigger: 'blur' }]
},
apiOpen: false,
apiTitle: '',
apiForm: {},
apiRules: {
apiCode: [{ required: true, message: '接口编码不能为空', trigger: 'blur' }],
apiName: [{ required: true, message: '接口名称不能为空', trigger: 'blur' }],
method: [{ required: true, message: '请求方法不能为空', trigger: 'change' }],
uri: [{ required: true, message: '接口URI不能为空', trigger: 'blur' }]
},
mappingOpen: false,
mappingTitle: '',
mappingForm: {},
mappingRules: {
externalDeviceId: [{ required: true, message: '第三方设备ID不能为空', trigger: 'blur' }],
productId: [{ required: true, message: '产品ID不能为空', trigger: 'blur' }],
serialNumber: [{ required: true, message: '设备编号不能为空', trigger: 'blur' }]
},
syncOpen: false,
syncForm: {}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.loading = true
listPlatform(this.queryParams).then(response => {
this.platformList = response.rows || []
this.total = response.total || 0
this.loading = false
if (!this.currentPlatform && this.platformList.length > 0) {
this.selectPlatform(this.platformList[0])
}
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.currentPlatform = null
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
handleCurrentPlatformChange(row) {
if (row) {
this.selectPlatform(row)
}
},
selectPlatform(row) {
this.currentPlatform = row
this.apiIds = []
this.mappingIds = []
this.apiSingle = true
this.apiMultiple = true
this.mappingSingle = true
this.mappingMultiple = true
this.getApiList()
this.getMappingList()
this.getLogList()
},
handleTabClick() {
if (!this.currentPlatform) {
return
}
if (this.activeTab === 'api') {
this.getApiList()
} else if (this.activeTab === 'mapping') {
this.getMappingList()
} else {
this.getLogList()
}
},
resetPlatformForm() {
this.platformForm = {
id: null,
platformCode: null,
platformName: null,
baseUrl: null,
authType: 'PASSWORD_TOKEN',
credentialJson: '{}',
status: 0
}
this.resetForm('platformForm')
},
handleAddPlatform() {
this.resetPlatformForm()
this.platformOpen = true
this.platformTitle = '新增接入平台'
},
handleUpdatePlatform(row) {
this.resetPlatformForm()
const id = row.id || this.ids
getPlatform(id).then(response => {
this.platformForm = response.data
this.platformOpen = true
this.platformTitle = '修改接入平台'
})
},
submitPlatformForm() {
this.$refs.platformForm.validate(valid => {
if (!valid) {
return
}
const request = this.platformForm.id != null ? updatePlatform(this.platformForm) : addPlatform(this.platformForm)
request.then(() => {
this.$modal.msgSuccess(this.platformForm.id != null ? '修改成功' : '新增成功')
this.platformOpen = false
this.currentPlatform = null
this.getList()
})
})
},
handleDeletePlatform(row) {
const ids = row.id || this.ids
this.$modal.confirm('是否确认删除平台编号为 "' + ids + '" 的数据项?').then(function() {
return delPlatform(ids)
}).then(() => {
this.currentPlatform = null
this.getList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {})
},
getApiList() {
if (!this.currentPlatform) {
this.apiList = []
return
}
this.apiLoading = true
listIntegrationApi({ platformId: this.currentPlatform.id, pageNum: 1, pageSize: 100 }).then(response => {
this.apiList = response.rows || []
this.apiLoading = false
})
},
handleApiSelectionChange(selection) {
this.apiIds = selection.map(item => item.id)
this.apiSingle = selection.length !== 1
this.apiMultiple = !selection.length
},
resetApiForm() {
this.apiForm = {
id: null,
platformId: this.currentPlatform ? this.currentPlatform.id : null,
apiCode: null,
apiName: null,
method: 'GET',
uri: null,
contentType: 'application/json',
headersJson: '{}',
queryJson: '{}',
bodyJson: '{}',
responsePath: null,
dependsOn: null,
sortOrder: 0,
status: 1
}
this.resetForm('apiForm')
},
handleAddApi() {
this.resetApiForm()
this.apiOpen = true
this.apiTitle = '新增接口定义'
},
handleUpdateApi(row) {
this.resetApiForm()
const id = row.id || this.apiIds
getIntegrationApi(id).then(response => {
this.apiForm = response.data
this.apiOpen = true
this.apiTitle = '修改接口定义'
})
},
submitApiForm() {
this.$refs.apiForm.validate(valid => {
if (!valid) {
return
}
const request = this.apiForm.id != null ? updateIntegrationApi(this.apiForm) : addIntegrationApi(this.apiForm)
request.then(() => {
this.$modal.msgSuccess(this.apiForm.id != null ? '修改成功' : '新增成功')
this.apiOpen = false
this.getApiList()
})
})
},
handleDeleteApi(row) {
const ids = row.id || this.apiIds
this.$modal.confirm('是否确认删除接口编号为 "' + ids + '" 的数据项?').then(function() {
return delIntegrationApi(ids)
}).then(() => {
this.getApiList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {})
},
getMappingList() {
if (!this.currentPlatform) {
this.mappingList = []
return
}
this.mappingLoading = true
listDeviceMapping({ platformId: this.currentPlatform.id, pageNum: 1, pageSize: 100 }).then(response => {
this.mappingList = response.rows || []
this.mappingLoading = false
})
},
handleMappingSelectionChange(selection) {
this.mappingIds = selection.map(item => item.id)
this.mappingSingle = selection.length !== 1
this.mappingMultiple = !selection.length
},
resetMappingForm() {
this.mappingForm = {
id: null,
platformId: this.currentPlatform ? this.currentPlatform.id : null,
externalDeviceId: null,
externalDeviceName: null,
productId: null,
serialNumber: null,
identifierMappingJson: '{"e":"axialForce","temperature":"temperature","ct":"ct","pt":"pt"}',
status: 1
}
this.resetForm('mappingForm')
},
handleAddMapping() {
this.resetMappingForm()
this.mappingOpen = true
this.mappingTitle = '新增设备映射'
},
handleUpdateMapping(row) {
this.resetMappingForm()
const id = row.id || this.mappingIds
getDeviceMapping(id).then(response => {
this.mappingForm = response.data
this.mappingOpen = true
this.mappingTitle = '修改设备映射'
})
},
submitMappingForm() {
this.$refs.mappingForm.validate(valid => {
if (!valid) {
return
}
const request = this.mappingForm.id != null ? updateDeviceMapping(this.mappingForm) : addDeviceMapping(this.mappingForm)
request.then(() => {
this.$modal.msgSuccess(this.mappingForm.id != null ? '修改成功' : '新增成功')
this.mappingOpen = false
this.getMappingList()
})
})
},
handleDeleteMapping(row) {
const ids = row.id || this.mappingIds
this.$modal.confirm('是否确认删除映射编号为 "' + ids + '" 的数据项?').then(function() {
return delDeviceMapping(ids)
}).then(() => {
this.getMappingList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {})
},
getLogList() {
if (!this.currentPlatform) {
this.logList = []
return
}
this.logLoading = true
listIntegrationLog({ platformId: this.currentPlatform.id, pageNum: 1, pageSize: 20 }).then(response => {
this.logList = response.rows || []
this.logLoading = false
})
},
openSyncDialog(dryRun, row) {
const platform = row || this.currentPlatform
if (!platform) {
return
}
this.syncForm = {
platformCode: platform.platformCode,
projectId: null,
startTime: null,
endTime: null,
original: false,
dryRun: !!dryRun,
externalDeviceIdsText: ''
}
this.syncOpen = true
},
submitSync() {
const externalDeviceIds = (this.syncForm.externalDeviceIdsText || '').split(/\r?\n/).map(item => item.trim()).filter(item => item)
const data = {
platformCode: this.syncForm.platformCode,
projectId: this.syncForm.projectId,
startTime: this.syncForm.startTime,
endTime: this.syncForm.endTime,
original: this.syncForm.original,
dryRun: this.syncForm.dryRun,
externalDeviceIds: externalDeviceIds
}
syncIntegration(data).then(response => {
this.$modal.msgSuccess('执行完成,成功 ' + response.data.successCount + ' 条,失败 ' + response.data.failCount + ' 条')
this.syncOpen = false
this.getLogList()
})
},
handleSelfTest() {
if (!this.currentPlatform) {
return
}
selfTestIntegration(this.currentPlatform.platformCode).then(response => {
this.$modal.msgSuccess('自测完成,成功 ' + response.data.successCount + ' 条,失败 ' + response.data.failCount + ' 条')
})
},
logStatusText(status) {
if (status === 1) {
return '成功'
}
if (status === 2) {
return '执行中'
}
return '失败'
},
logStatusTag(status) {
if (status === 1) {
return 'success'
}
if (status === 2) {
return 'warning'
}
return 'danger'
}
}
}
</script>
<style lang="scss" scoped>
.integration-page {
.integration-detail {
margin-top: 18px;
padding-top: 14px;
border-top: 1px solid #ebeef5;
}
.detail-header {
display: flex;
align-items: baseline;
margin-bottom: 10px;
}
.detail-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.detail-subtitle {
margin-left: 10px;
font-size: 13px;
color: #909399;
}
}
</style>