feat(iot): 实现设备 API 接口及更新网关处理逻辑

This commit is contained in:
lzh
2026-01-15 16:14:25 +08:00
parent de08aea83f
commit 7b3e028bea
8 changed files with 1936 additions and 1417 deletions

View File

@@ -1,75 +1,75 @@
package com.viewsh.module.iot.api.device;
import com.viewsh.framework.common.enums.RpcConstants;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.framework.common.util.object.BeanUtils;
import com.viewsh.module.iot.core.biz.IotDeviceCommonApi;
import com.viewsh.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import com.viewsh.module.iot.core.biz.dto.IotDeviceGetReqDTO;
import com.viewsh.module.iot.core.biz.dto.IotDeviceRespDTO;
import com.viewsh.module.iot.dal.dataobject.device.IotDeviceDO;
import com.viewsh.module.iot.dal.dataobject.product.IotProductDO;
import com.viewsh.module.iot.service.device.IotDeviceService;
import com.viewsh.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* IoT 设备 API 实现类
*
* @author haohao
*/
@RestController
@Validated
@Primary // 保证优先匹配,因为 viewsh-iot-gateway 也有 IotDeviceCommonApi 的实现,并且也可能会被 biz 引入
public class IoTDeviceApiImpl implements IotDeviceCommonApi {
@Resource
private IotDeviceService deviceService;
@Resource
private IotProductService productService;
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
@PermitAll
public CommonResult<Boolean> authDevice(@RequestBody IotDeviceAuthReqDTO authReqDTO) {
return success(deviceService.authDevice(authReqDTO));
}
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST实际更推荐 GET
@PermitAll
public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
IotDeviceDO device;
// 查询优先级id > (productKey + deviceName) > deviceName
if (getReqDTO.getId() != null) {
// 通过设备 ID 查询
device = deviceService.getDeviceFromCache(getReqDTO.getId());
} else if (getReqDTO.getProductKey() != null && getReqDTO.getDeviceName() != null) {
// 通过 productKey + deviceName 查询
device = deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
} else if (getReqDTO.getDeviceName() != null) {
// 仅通过 deviceName 查询(用于 JT808 等协议,终端手机号应该是全局唯一的)
device = deviceService.getDeviceFromCacheByDeviceName(getReqDTO.getDeviceName());
} else {
device = null;
}
return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
if (product != null) {
deviceDTO.setCodecType(product.getCodecType());
deviceDTO.setProductAuthType(product.getAuthType());
}
}));
}
package com.viewsh.module.iot.api.device;
import com.viewsh.framework.common.enums.RpcConstants;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.framework.common.util.object.BeanUtils;
import com.viewsh.module.iot.core.biz.IotDeviceCommonApi;
import com.viewsh.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import com.viewsh.module.iot.core.biz.dto.IotDeviceGetReqDTO;
import com.viewsh.module.iot.core.biz.dto.IotDeviceRespDTO;
import com.viewsh.module.iot.dal.dataobject.device.IotDeviceDO;
import com.viewsh.module.iot.dal.dataobject.product.IotProductDO;
import com.viewsh.module.iot.service.device.IotDeviceService;
import com.viewsh.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* IoT 设备 API 实现类供IOT GATEWAY使用
*
* @author haohao
*/
@RestController
@Validated
@Primary // 保证优先匹配,因为 viewsh-iot-gateway 也有 IotDeviceCommonApi 的实现,并且也可能会被 biz 引入
public class IoTDeviceApiImpl implements IotDeviceCommonApi {
@Resource
private IotDeviceService deviceService;
@Resource
private IotProductService productService;
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
@PermitAll
public CommonResult<Boolean> authDevice(@RequestBody IotDeviceAuthReqDTO authReqDTO) {
return success(deviceService.authDevice(authReqDTO));
}
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST实际更推荐 GET
@PermitAll
public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
IotDeviceDO device;
// 查询优先级id > (productKey + deviceName) > deviceName
if (getReqDTO.getId() != null) {
// 通过设备 ID 查询
device = deviceService.getDeviceFromCache(getReqDTO.getId());
} else if (getReqDTO.getProductKey() != null && getReqDTO.getDeviceName() != null) {
// 通过 productKey + deviceName 查询
device = deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
} else if (getReqDTO.getDeviceName() != null) {
// 仅通过 deviceName 查询(用于 JT808 等协议,终端手机号应该是全局唯一的)
device = deviceService.getDeviceFromCacheByDeviceName(getReqDTO.getDeviceName());
} else {
device = null;
}
return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
if (product != null) {
deviceDTO.setCodecType(product.getCodecType());
deviceDTO.setProductAuthType(product.getAuthType());
}
}));
}
}

View File

@@ -0,0 +1,123 @@
package com.viewsh.module.iot.api.device;
import cn.hutool.core.map.MapUtil;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.iot.api.device.dto.IotDeviceServiceInvokeReqDTO;
import com.viewsh.module.iot.api.device.dto.IotDeviceServiceInvokeRespDTO;
import com.viewsh.module.iot.core.enums.IotDeviceMessageMethodEnum;
import com.viewsh.module.iot.core.enums.IotDeviceStateEnum;
import com.viewsh.module.iot.core.mq.message.IotDeviceMessage;
import com.viewsh.module.iot.dal.dataobject.device.IotDeviceDO;
import com.viewsh.module.iot.service.device.IotDeviceService;
import com.viewsh.module.iot.service.device.message.IotDeviceMessageService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* IoT 设备控制 API 实现类
* <p>
* 提供 RPC 接口供其他模块调用 IoT 设备服务
*
* @author lzh
*/
@RestController
@Validated
@Primary
@Slf4j
public class IotDeviceControlApiImpl implements IotDeviceControlApi {
@Resource
private IotDeviceService deviceService;
@Resource
private IotDeviceMessageService deviceMessageService;
@Override
@PostMapping(PREFIX + "/invoke-service")
@Operation(summary = "调用设备服务")
public CommonResult<IotDeviceServiceInvokeRespDTO> invokeService(@RequestBody IotDeviceServiceInvokeReqDTO reqDTO) {
try {
// 1. 获取设备信息
IotDeviceDO device = deviceService.getDeviceFromCache(reqDTO.getDeviceId());
if (device == null) {
return success(IotDeviceServiceInvokeRespDTO.builder()
.success(false)
.code(404)
.errorMsg("设备不存在")
.responseTime(LocalDateTime.now())
.build());
}
// 2. 检查设备是否在线
if (!IotDeviceStateEnum.ONLINE.getState().equals(device.getState())) {
return success(IotDeviceServiceInvokeRespDTO.builder()
.success(false)
.code(400)
.errorMsg("设备不在线,当前状态: " + device.getState())
.responseTime(LocalDateTime.now())
.build());
}
// 3. 构建服务调用消息
@SuppressWarnings("unchecked")
Map<String, Object> params = (Map<String, Object>) (Map<?, ?>) MapUtil.builder()
.put("identifier", reqDTO.getIdentifier())
.put("params", reqDTO.getParams() != null ? reqDTO.getParams() : new HashMap<>())
.build();
IotDeviceMessage message = IotDeviceMessage.requestOf(
IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(), params);
// 4. 发送消息
IotDeviceMessage result = deviceMessageService.sendDeviceMessage(message, device);
// 5. 返回结果异步模式立即返回消息ID
return success(IotDeviceServiceInvokeRespDTO.builder()
.messageId(result.getId())
.success(true)
.data(null)
.responseTime(LocalDateTime.now())
.build());
} catch (Exception e) {
log.error("[invokeService] 设备服务调用失败: deviceId={}, identifier={}",
reqDTO.getDeviceId(), reqDTO.getIdentifier(), e);
return success(IotDeviceServiceInvokeRespDTO.builder()
.success(false)
.code(500)
.errorMsg(e.getMessage())
.responseTime(LocalDateTime.now())
.build());
}
}
@Override
@PostMapping(PREFIX + "/invoke-service-batch")
@Operation(summary = "批量调用设备服务")
public CommonResult<List<IotDeviceServiceInvokeRespDTO>> invokeServiceBatch(
@RequestBody List<IotDeviceServiceInvokeReqDTO> reqDTOList) {
List<IotDeviceServiceInvokeRespDTO> results = new java.util.ArrayList<>();
for (IotDeviceServiceInvokeReqDTO reqDTO : reqDTOList) {
CommonResult<IotDeviceServiceInvokeRespDTO> result = invokeService(reqDTO);
results.add(result.getData());
}
return success(results);
}
}

View File

@@ -0,0 +1,175 @@
package com.viewsh.module.iot.api.device;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.iot.api.device.dto.property.DevicePropertyBatchQueryReqDTO;
import com.viewsh.module.iot.api.device.dto.property.DevicePropertyHistoryQueryReqDTO;
import com.viewsh.module.iot.api.device.dto.property.DevicePropertyHistoryRespDTO;
import com.viewsh.module.iot.api.device.dto.property.DevicePropertyRespDTO;
import com.viewsh.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
import com.viewsh.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
import com.viewsh.module.iot.dal.dataobject.device.IotDeviceDO;
import com.viewsh.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import com.viewsh.module.iot.service.device.IotDeviceService;
import com.viewsh.module.iot.service.device.property.IotDevicePropertyService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* IoT 设备属性查询 API 实现类
* <p>
* 提供 RPC 接口供其他模块(如 Ops 模块)查询设备属性
*
* @author lzh
*/
@RestController
@Validated
@Primary
@Slf4j
public class IotDevicePropertyQueryApiImpl implements IotDevicePropertyQueryApi {
@Resource
private IotDeviceService deviceService;
@Resource
private IotDevicePropertyService devicePropertyService;
@Override
@GetMapping(PREFIX + "/get")
@Operation(summary = "获取设备单个属性")
public CommonResult<DevicePropertyRespDTO> getProperty(@RequestParam("deviceId") Long deviceId,
@RequestParam("identifier") String identifier) {
try {
Map<String, IotDevicePropertyDO> properties = devicePropertyService.getLatestDeviceProperties(deviceId);
IotDevicePropertyDO property = properties.get(identifier);
if (property == null) {
return success(DevicePropertyRespDTO.builder()
.identifier(identifier)
.value(null)
.updateTime(null)
.build());
}
return success(DevicePropertyRespDTO.builder()
.identifier(identifier)
.value(property.getValue())
.updateTime(property.getUpdateTime() != null ?
property.getUpdateTime().atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
.build());
} catch (Exception e) {
log.error("[getProperty] 获取设备属性失败: deviceId={}, identifier={}", deviceId, identifier, e);
return success(DevicePropertyRespDTO.builder()
.identifier(identifier)
.value(null)
.updateTime(null)
.build());
}
}
@Override
@GetMapping(PREFIX + "/get-latest")
@Operation(summary = "获取设备最新属性")
public CommonResult<Map<String, Object>> getLatestProperties(@RequestParam("deviceId") Long deviceId) {
try {
// 验证设备是否存在
IotDeviceDO device = deviceService.getDeviceFromCache(deviceId);
if (device == null) {
log.warn("[getLatestProperties] 设备不存在: deviceId={}", deviceId);
return success(Map.of());
}
Map<String, IotDevicePropertyDO> properties = devicePropertyService.getLatestDeviceProperties(deviceId);
Map<String, Object> result = properties.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue()));
return success(result);
} catch (Exception e) {
log.error("[getLatestProperties] 获取设备最新属性失败: deviceId={}", deviceId, e);
return success(Map.of());
}
}
@Override
@PostMapping(PREFIX + "/batch-get")
@Operation(summary = "批量获取多个设备的指定属性")
public CommonResult<Map<Long, Map<String, Object>>> batchGetProperties(
@RequestBody DevicePropertyBatchQueryReqDTO reqDTO) {
try {
Map<Long, Map<String, Object>> result = reqDTO.getDeviceIds().stream()
.collect(Collectors.toMap(
deviceId -> deviceId,
deviceId -> {
Map<String, IotDevicePropertyDO> properties = devicePropertyService.getLatestDeviceProperties(deviceId);
if (reqDTO.getIdentifiers() == null || reqDTO.getIdentifiers().isEmpty()) {
// 返回所有属性
return properties.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue()));
} else {
// 只返回指定的属性
return reqDTO.getIdentifiers().stream()
.filter(properties::containsKey)
.collect(Collectors.toMap(
identifier -> identifier,
identifier -> properties.get(identifier).getValue()));
}
}
));
return success(result);
} catch (Exception e) {
log.error("[batchGetProperties] 批量获取设备属性失败: deviceIds={}", reqDTO.getDeviceIds(), e);
return success(Map.of());
}
}
@Override
@PostMapping(PREFIX + "/history")
@Operation(summary = "查询设备属性历史数据")
public CommonResult<List<DevicePropertyHistoryRespDTO>> getPropertyHistory(
@RequestBody DevicePropertyHistoryQueryReqDTO reqDTO) {
try {
// 构建查询请求
IotDevicePropertyHistoryListReqVO listReqVO = new IotDevicePropertyHistoryListReqVO();
listReqVO.setDeviceId(reqDTO.getDeviceId());
listReqVO.setIdentifier(reqDTO.getIdentifier());
if (reqDTO.getStartTime() != null && reqDTO.getEndTime() != null) {
listReqVO.setTimes(new LocalDateTime[]{reqDTO.getStartTime(), reqDTO.getEndTime()});
}
// listReqVO.setPageSize(reqDTO.getLimit()); // 暂不支持分页
List<IotDevicePropertyRespVO> historyList = devicePropertyService.getHistoryDevicePropertyList(listReqVO);
List<DevicePropertyHistoryRespDTO> result = historyList.stream()
.map(vo -> DevicePropertyHistoryRespDTO.builder()
.identifier(vo.getIdentifier())
.value(vo.getValue())
.timestamp(vo.getUpdateTime())
.build())
.collect(Collectors.toList());
return success(result);
} catch (Exception e) {
log.error("[getPropertyHistory] 查询设备属性历史失败: deviceId={}, identifier={}",
reqDTO.getDeviceId(), reqDTO.getIdentifier(), e);
return success(List.of());
}
}
}

View File

@@ -0,0 +1,66 @@
package com.viewsh.module.iot.api.device;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.iot.api.device.dto.status.DeviceStatusRespDTO;
import com.viewsh.module.iot.core.enums.IotDeviceStateEnum;
import com.viewsh.module.iot.dal.dataobject.device.IotDeviceDO;
import com.viewsh.module.iot.service.device.IotDeviceService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* IoT 设备状态查询 API 实现类
* <p>
* 提供 RPC 接口供其他模块查询设备状态
*
* @author lzh
*/
@RestController
@Validated
@Primary
@Slf4j
public class IotDeviceStatusQueryApiImpl implements IotDeviceStatusQueryApi {
@Resource
private IotDeviceService deviceService;
@Override
@GetMapping(PREFIX + "/is-online")
@Operation(summary = "检查设备是否在线")
public CommonResult<Boolean> isDeviceOnline(@RequestParam("deviceId") Long deviceId) {
IotDeviceDO device = deviceService.getDeviceFromCache(deviceId);
if (device == null) {
return success(false);
}
return success(IotDeviceStateEnum.ONLINE.getState().equals(device.getState()));
}
@Override
@GetMapping(PREFIX + "/get-status")
@Operation(summary = "获取设备状态")
public CommonResult<DeviceStatusRespDTO> getDeviceStatus(@RequestParam("deviceId") Long deviceId) {
IotDeviceDO device = deviceService.getDeviceFromCache(deviceId);
if (device == null) {
return success(DeviceStatusRespDTO.builder()
.deviceId(deviceId)
.status(IotDeviceStateEnum.OFFLINE.getState())
.build());
}
return success(DeviceStatusRespDTO.builder()
.deviceId(device.getId())
.deviceCode(device.getSerialNumber())
.status(device.getState())
.statusChangeTime(IotDeviceStateEnum.ONLINE.getState().equals(device.getState()) ?
device.getOnlineTime() : device.getOfflineTime())
.build());
}
}

View File

@@ -1,209 +1,274 @@
package com.viewsh.module.iot.service.device.property;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.viewsh.framework.common.util.json.JsonUtils;
import com.viewsh.framework.common.util.object.ObjectUtils;
import com.viewsh.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
import com.viewsh.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
import com.viewsh.module.iot.core.mq.message.IotDeviceMessage;
import com.viewsh.module.iot.dal.dataobject.device.IotDeviceDO;
import com.viewsh.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import com.viewsh.module.iot.dal.dataobject.product.IotProductDO;
import com.viewsh.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import com.viewsh.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
import com.viewsh.module.iot.dal.redis.device.DevicePropertyRedisDAO;
import com.viewsh.module.iot.dal.redis.device.DeviceReportTimeRedisDAO;
import com.viewsh.module.iot.dal.redis.device.DeviceServerIdRedisDAO;
import com.viewsh.module.iot.dal.tdengine.IotDevicePropertyMapper;
import com.viewsh.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
import com.viewsh.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import com.viewsh.module.iot.framework.tdengine.core.TDengineTableField;
import com.viewsh.module.iot.service.product.IotProductService;
import com.viewsh.module.iot.service.thingmodel.IotThingModelService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.*;
import static com.viewsh.framework.common.util.collection.CollectionUtils.*;
/**
* IoT 设备【属性】数据 Service 实现类
*
* @author 芋道源码
*/
@Service
@Slf4j
public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
/**
* 物模型的数据类型,与 TDengine 数据类型的映射关系
*
* @see <a href="https://docs.taosdata.com/reference/taos-sql/data-type/">TDEngine 数据类型</a>
*/
private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
.put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)
.put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
.put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
.put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT)
.put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT)
.put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_VARCHAR)
.put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
.put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_VARCHAR)
.put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_VARCHAR)
.build();
@Resource
private IotThingModelService thingModelService;
@Resource
@Lazy // 延迟加载,解决循环依赖
private IotProductService productService;
@Resource
private DevicePropertyRedisDAO deviceDataRedisDAO;
@Resource
private DeviceReportTimeRedisDAO deviceReportTimeRedisDAO;
@Resource
private DeviceServerIdRedisDAO deviceServerIdRedisDAO;
@Resource
private IotDevicePropertyMapper devicePropertyMapper;
// ========== 设备属性相关操作 ==========
@Override
public void defineDevicePropertyData(Long productId) {
// 1.1 查询产品和物模型
IotProductDO product = productService.validateProductExists(productId);
List<IotThingModelDO> thingModels = filterList(thingModelService.getThingModelListByProductId(productId),
thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
// 1.2 解析 DB 里的字段
List<TDengineTableField> oldFields = new ArrayList<>();
try {
oldFields.addAll(devicePropertyMapper.getProductPropertySTableFieldList(product.getId()));
} catch (Exception e) {
if (!e.getMessage().contains("Table does not exist")) {
throw e;
}
}
// 2.1 情况一:如果是新增的时候,需要创建表
List<TDengineTableField> newFields = buildTableFieldList(thingModels);
if (CollUtil.isEmpty(oldFields)) {
if (CollUtil.isEmpty(newFields)) {
log.info("[defineDevicePropertyData][productId({}) 没有需要定义的属性]", productId);
return;
}
devicePropertyMapper.createProductPropertySTable(product.getId(), newFields);
return;
}
// 2.2 情况二:如果是修改的时候,需要更新表
devicePropertyMapper.alterProductPropertySTable(product.getId(), oldFields, newFields);
}
private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
return convertList(thingModels, thingModel -> {
TDengineTableField field = new TDengineTableField(
StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
String dataType = thingModel.getProperty().getDataType();
if (Objects.equals(dataType, IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());
} else if (ObjectUtils.equalsAny(dataType, IotDataSpecsDataTypeEnum.STRUCT.getDataType(),
IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
field.setLength(TDengineTableField.LENGTH_VARCHAR);
}
return field;
});
}
@Override
public void saveDeviceProperty(IotDeviceDO device, IotDeviceMessage message) {
if (!(message.getParams() instanceof Map)) {
log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message);
return;
}
// 1. 根据物模型,拼接合法的属性
// TODO @芋艿:【待定 004】赋能后属性到底以 thingModel 为准ik还是 db 的表结构为准tl
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductIdFromCache(device.getProductId());
Map<String, Object> properties = new HashMap<>();
((Map<?, ?>) message.getParams()).forEach((key, value) -> {
IotThingModelDO thingModel = CollUtil.findOne(thingModels, o -> o.getIdentifier().equals(key));
if (thingModel == null || thingModel.getProperty() == null) {
log.error("[saveDeviceProperty][消息({}) 的属性({}) 不存在]", message, key);
return;
}
if (ObjectUtils.equalsAny(thingModel.getProperty().getDataType(),
IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
// 特殊STRUCT 和 ARRAY 类型,在 TDengine 里,有没对应数据类型,只能通过 JSON 来存储
properties.put((String) key, JsonUtils.toJsonString(value));
} else {
properties.put((String) key, value);
}
});
if (CollUtil.isEmpty(properties)) {
log.error("[saveDeviceProperty][消息({}) 没有合法的属性]", message);
return;
}
// 2.1 保存设备属性【数据】
devicePropertyMapper.insert(device, properties, LocalDateTimeUtil.toEpochMilli(message.getReportTime()));
// 2.2 保存设备属性【日志】
Map<String, IotDevicePropertyDO> properties2 = convertMap(properties.entrySet(), Map.Entry::getKey, entry ->
IotDevicePropertyDO.builder().value(entry.getValue()).updateTime(message.getReportTime()).build());
deviceDataRedisDAO.putAll(device.getId(), properties2);
}
@Override
public Map<String, IotDevicePropertyDO> getLatestDeviceProperties(Long deviceId) {
return deviceDataRedisDAO.get(deviceId);
}
@Override
public List<IotDevicePropertyRespVO> getHistoryDevicePropertyList(IotDevicePropertyHistoryListReqVO listReqVO) {
try {
return devicePropertyMapper.selectListByHistory(listReqVO);
} catch (Exception exception) {
if (exception.getMessage().contains("Table does not exist")) {
return Collections.emptyList();
}
throw exception;
}
}
// ========== 设备时间相关操作 ==========
@Override
public Set<Long> getDeviceIdListByReportTime(LocalDateTime maxReportTime) {
return deviceReportTimeRedisDAO.range(maxReportTime);
}
@Override
@Async
public void updateDeviceReportTimeAsync(Long id, LocalDateTime reportTime) {
deviceReportTimeRedisDAO.update(id, reportTime);
}
@Override
public void updateDeviceServerIdAsync(Long id, String serverId) {
if (StrUtil.isEmpty(serverId)) {
return;
}
deviceServerIdRedisDAO.update(id, serverId);
}
@Override
public String getDeviceServerId(Long id) {
return deviceServerIdRedisDAO.get(id);
}
package com.viewsh.module.iot.service.device.property;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.viewsh.framework.common.util.json.JsonUtils;
import com.viewsh.framework.common.util.object.ObjectUtils;
import com.viewsh.framework.mq.redis.core.RedisMQTemplate;
import com.viewsh.module.iot.core.integration.event.DevicePropertyChangedEvent;
import com.viewsh.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
import com.viewsh.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
import com.viewsh.module.iot.core.integration.publisher.IntegrationEventPublisher;
import com.viewsh.module.iot.core.mq.message.IotDeviceMessage;
import com.viewsh.module.iot.dal.dataobject.device.IotDeviceDO;
import com.viewsh.module.iot.dal.dataobject.device.IotDevicePropertyDO;
import com.viewsh.module.iot.dal.dataobject.product.IotProductDO;
import com.viewsh.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import com.viewsh.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
import com.viewsh.module.iot.dal.redis.device.DevicePropertyRedisDAO;
import com.viewsh.module.iot.dal.redis.device.DeviceReportTimeRedisDAO;
import com.viewsh.module.iot.dal.redis.device.DeviceServerIdRedisDAO;
import com.viewsh.module.iot.dal.tdengine.IotDevicePropertyMapper;
import com.viewsh.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
import com.viewsh.module.iot.enums.thingmodel.IotThingModelTypeEnum;
import com.viewsh.module.iot.framework.tdengine.core.TDengineTableField;
import com.viewsh.module.iot.service.product.IotProductService;
import com.viewsh.module.iot.service.thingmodel.IotThingModelService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.*;
import static com.viewsh.framework.common.util.collection.CollectionUtils.*;
/**
* IoT 设备【属性】数据 Service 实现类
*
* @author 芋道源码
*/
@Service
@Slf4j
public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
/**
* 物模型的数据类型,与 TDengine 数据类型的映射关系
*
* @see <a href="https://docs.taosdata.com/reference/taos-sql/data-type/">TDEngine 数据类型</a>
*/
private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
.put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)
.put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
.put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
.put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT)
.put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT)
.put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_VARCHAR)
.put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
.put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_VARCHAR)
.put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_VARCHAR)
.build();
@Resource
private IotThingModelService thingModelService;
@Resource
@Lazy // 延迟加载,解决循环依赖
private IotProductService productService;
@Resource
private DevicePropertyRedisDAO deviceDataRedisDAO;
@Resource
private DeviceReportTimeRedisDAO deviceReportTimeRedisDAO;
@Resource
private DeviceServerIdRedisDAO deviceServerIdRedisDAO;
@Resource
private IotDevicePropertyMapper devicePropertyMapper;
@Resource
private RedisMQTemplate redisMQTemplate;
@Resource
private IntegrationEventPublisher integrationEventPublisher;
// ========== 设备属性相关操作 ==========
@Override
public void defineDevicePropertyData(Long productId) {
// 1.1 查询产品和物模型
IotProductDO product = productService.validateProductExists(productId);
List<IotThingModelDO> thingModels = filterList(thingModelService.getThingModelListByProductId(productId),
thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
// 1.2 解析 DB 里的字段
List<TDengineTableField> oldFields = new ArrayList<>();
try {
oldFields.addAll(devicePropertyMapper.getProductPropertySTableFieldList(product.getId()));
} catch (Exception e) {
if (!e.getMessage().contains("Table does not exist")) {
throw e;
}
}
// 2.1 情况一:如果是新增的时候,需要创建表
List<TDengineTableField> newFields = buildTableFieldList(thingModels);
if (CollUtil.isEmpty(oldFields)) {
if (CollUtil.isEmpty(newFields)) {
log.info("[defineDevicePropertyData][productId({}) 没有需要定义的属性]", productId);
return;
}
devicePropertyMapper.createProductPropertySTable(product.getId(), newFields);
return;
}
// 2.2 情况二:如果是修改的时候,需要更新表
devicePropertyMapper.alterProductPropertySTable(product.getId(), oldFields, newFields);
}
private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
return convertList(thingModels, thingModel -> {
TDengineTableField field = new TDengineTableField(
StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
String dataType = thingModel.getProperty().getDataType();
if (Objects.equals(dataType, IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());
} else if (ObjectUtils.equalsAny(dataType, IotDataSpecsDataTypeEnum.STRUCT.getDataType(),
IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
field.setLength(TDengineTableField.LENGTH_VARCHAR);
}
return field;
});
}
@Override
public void saveDeviceProperty(IotDeviceDO device, IotDeviceMessage message) {
if (!(message.getParams() instanceof Map)) {
log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message);
return;
}
// 1. 根据物模型,拼接合法的属性
// TODO @芋艿:【待定 004】赋能后属性到底以 thingModel 为准ik还是 db 的表结构为准tl
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductIdFromCache(device.getProductId());
Map<String, Object> properties = new HashMap<>();
((Map<?, ?>) message.getParams()).forEach((key, value) -> {
IotThingModelDO thingModel = CollUtil.findOne(thingModels, o -> o.getIdentifier().equals(key));
if (thingModel == null || thingModel.getProperty() == null) {
log.error("[saveDeviceProperty][消息({}) 的属性({}) 不存在]", message, key);
return;
}
if (ObjectUtils.equalsAny(thingModel.getProperty().getDataType(),
IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) {
// 特殊STRUCT 和 ARRAY 类型,在 TDengine 里,有没对应数据类型,只能通过 JSON 来存储
properties.put((String) key, JsonUtils.toJsonString(value));
} else {
properties.put((String) key, value);
}
});
if (CollUtil.isEmpty(properties)) {
log.error("[saveDeviceProperty][消息({}) 没有合法的属性]", message);
return;
}
// 2.1 保存设备属性【数据】
devicePropertyMapper.insert(device, properties, LocalDateTimeUtil.toEpochMilli(message.getReportTime()));
// 2.2 保存设备属性【日志】
Map<String, IotDevicePropertyDO> properties2 = convertMap(properties.entrySet(), Map.Entry::getKey, entry ->
IotDevicePropertyDO.builder().value(entry.getValue()).updateTime(message.getReportTime()).build());
deviceDataRedisDAO.putAll(device.getId(), properties2);
// 2.3 发布属性消息到 Redis Stream供其他模块如 Ops 订阅)
publishPropertyMessage(device, properties, message.getReportTime());
}
/**
* 发布设备属性消息
* <p>
* 同时发布到 Redis Stream旧方式保持兼容和 RocketMQ IntegrationEventBus新方式
*
* @param device 设备信息
* @param properties 属性数据
* @param reportTime 上报时间
*/
private void publishPropertyMessage(IotDeviceDO device, Map<String, Object> properties, LocalDateTime reportTime) {
try {
// 获取产品信息
IotProductDO product = productService.getProductFromCache(device.getProductId());
String productKey = product != null ? product.getProductKey() : "unknown";
// 发布到 IntegrationEventBusRocketMQ
publishToIntegrationEventBus(device, productKey, properties, reportTime);
} catch (Exception e) {
log.error("[publishPropertyMessage] 属性消息发布失败: deviceId={}", device.getId(), e);
}
}
/**
* 发布跨模块属性变更事件到 RocketMQ
*
* @param device 设备信息
* @param productKey 产品标识符(用作 RocketMQ Tag
* @param properties 属性数据
* @param reportTime 上报时间
*/
private void publishToIntegrationEventBus(IotDeviceDO device, String productKey,
Map<String, Object> properties, LocalDateTime reportTime) {
try {
DevicePropertyChangedEvent event = DevicePropertyChangedEvent.builder()
.deviceId(device.getId())
.deviceName(device.getDeviceName())
.productId(device.getProductId())
.productKey(productKey)
.tenantId(device.getTenantId())
.properties(properties)
.changedIdentifiers(properties.keySet())
.eventTime(reportTime)
.build();
integrationEventPublisher.publishPropertyChanged(event);
log.debug("[publishToIntegrationEventBus] 跨模块属性变更事件已发布: eventId={}, deviceId={}, productKey={}, properties={}",
event.getEventId(), device.getId(), productKey, properties.keySet());
} catch (Exception e) {
log.error("[publishToIntegrationEventBus] 跨模块属性变更事件发布失败: deviceId={}", device.getId(), e);
}
}
@Override
public Map<String, IotDevicePropertyDO> getLatestDeviceProperties(Long deviceId) {
return deviceDataRedisDAO.get(deviceId);
}
@Override
public List<IotDevicePropertyRespVO> getHistoryDevicePropertyList(IotDevicePropertyHistoryListReqVO listReqVO) {
try {
return devicePropertyMapper.selectListByHistory(listReqVO);
} catch (Exception exception) {
if (exception.getMessage().contains("Table does not exist")) {
return Collections.emptyList();
}
throw exception;
}
}
// ========== 设备时间相关操作 ==========
@Override
public Set<Long> getDeviceIdListByReportTime(LocalDateTime maxReportTime) {
return deviceReportTimeRedisDAO.range(maxReportTime);
}
@Override
@Async
public void updateDeviceReportTimeAsync(Long id, LocalDateTime reportTime) {
deviceReportTimeRedisDAO.update(id, reportTime);
}
@Override
public void updateDeviceServerIdAsync(Long id, String serverId) {
if (StrUtil.isEmpty(serverId)) {
return;
}
deviceServerIdRedisDAO.update(id, serverId);
}
@Override
public String getDeviceServerId(Long id) {
return deviceServerIdRedisDAO.get(id);
}
}

View File

@@ -1,157 +1,163 @@
spring:
application:
name: iot-server
profiles:
active: local
main:
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
config:
import:
- optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置
# Servlet 配置
servlet:
# 文件上传相关配置项
multipart:
max-file-size: 16MB # 单个文件大小
max-request-size: 32MB # 设置总上传的文件大小
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
# Cache 配置项
cache:
type: REDIS
redis:
time-to-live: 1h # 设置过期时间为 1 小时
server:
port: 48091
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
--- #################### 接口文档配置 ####################
springdoc:
api-docs:
enabled: true # 1. 是否开启 Swagger 接文档的元数据
path: /v3/api-docs
swagger-ui:
enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面
path: /swagger-ui
default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
knife4j:
enable: true
setting:
language: zh_cn
# MyBatis Plus 的配置项
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
# id-type: AUTO # 自增 ID适合 MySQL 等直接自增的数据库
# id-type: INPUT # 用户输入 ID适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
# id-type: ASSIGN_ID # 分配 ID默认使用雪花算法。注意Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
banner: false # 关闭控制台的 Banner 打印
type-aliases-package: ${viewsh.info.base-package}.dal.dataobject
encryptor:
password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
mybatis-plus-join:
banner: false # 关闭控制台的 Banner 打印
# Spring Data Redis 配置
spring:
data:
redis:
repositories:
enabled: false # 项目未使用到 Spring Data Redis 的 Repository所以直接禁用保证启动速度
# VO 转换(数据翻译)相关
easy-trans:
is-enable-global: false # 【默认禁用,对性能确认压力大】启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
--- #################### RPC 远程调用相关配置 ####################
--- #################### 消息队列相关 ####################
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
# Producer 配置项
producer:
group: ${spring.application.name}_PRODUCER # 生产者分组
spring:
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
# Kafka Producer 配置项
producer:
acks: 1 # 0-不应答。1-leader 应答。all-所有 leader 和 follower 应答。
retries: 3 # 发送失败时,重试发送的次数
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer # 消息的 value 的序列化
# Kafka Consumer 配置项
consumer:
auto-offset-reset: earliest # 设置消费者分组最初的消费进度为 earliest 。可参考博客 https://blog.csdn.net/lishuangzhe7047/article/details/74530417 理解
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: '*'
# Kafka Consumer Listener 监听器配置
listener:
missing-topics-fatal: false # 消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
--- #################### 定时任务相关配置 ####################
xxl:
job:
executor:
appname: ${spring.application.name} # 执行器 AppName
logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
accessToken: default_token # 执行器通讯TOKEN
--- #################### 芋道相关配置 ####################
viewsh:
info:
version: 1.0.0
base-package: com.viewsh.module.iot
web:
admin-ui:
url: http://dashboard.viewsh.iocoder.cn # Admin 管理后台 UI 的地址
xss:
enable: false
exclude-urls: # 如下 url仅仅是为了演示去掉配置也没关系
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
swagger:
title: 管理后台
description: 提供管理员管理的所有功能
version: ${viewsh.info.version}
tenant: # 多租户相关配置项
enable: true
ignore-urls:
ignore-tables:
ignore-caches:
- iot:device
- iot:thing_model_list
iot:
message-bus:
type: redis # 消息总线的类型
spring:
application:
name: iot-server
profiles:
active: local
main:
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
config:
import:
- optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置
# Servlet 配置
servlet:
# 文件上传相关配置项
multipart:
max-file-size: 16MB # 单个文件大小
max-request-size: 32MB # 设置总上传的文件大小
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
# Cache 配置项
cache:
type: REDIS
redis:
time-to-live: 1h # 设置过期时间为 1 小时
server:
port: 48091
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
--- #################### 接口文档配置 ####################
springdoc:
api-docs:
enabled: true # 1. 是否开启 Swagger 接文档的元数据
path: /v3/api-docs
swagger-ui:
enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面
path: /swagger-ui
default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
knife4j:
enable: true
setting:
language: zh_cn
# MyBatis Plus 的配置项
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
# id-type: AUTO # 自增 ID适合 MySQL 等直接自增的数据库
# id-type: INPUT # 用户输入 ID适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
# id-type: ASSIGN_ID # 分配 ID默认使用雪花算法。注意Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
banner: false # 关闭控制台的 Banner 打印
type-aliases-package: ${viewsh.info.base-package}.dal.dataobject
encryptor:
password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
mybatis-plus-join:
banner: false # 关闭控制台的 Banner 打印
# Spring Data Redis 配置
spring:
data:
redis:
repositories:
enabled: false # 项目未使用到 Spring Data Redis 的 Repository所以直接禁用保证启动速度
# VO 转换(数据翻译)相关
easy-trans:
is-enable-global: false # 【默认禁用,对性能确认压力大】启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
--- #################### RPC 远程调用相关配置 ####################
--- #################### 消息队列相关 ####################
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
# Producer 配置项
producer:
group: ${spring.application.name}_PRODUCER # 生产者分组
spring:
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
# Kafka Producer 配置项
producer:
acks: 1 # 0-不应答。1-leader 应答。all-所有 leader 和 follower 应答。
retries: 3 # 发送失败时,重试发送的次数
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer # 消息的 value 的序列化
# Kafka Consumer 配置项
consumer:
auto-offset-reset: earliest # 设置消费者分组最初的消费进度为 earliest 。可参考博客 https://blog.csdn.net/lishuangzhe7047/article/details/74530417 理解
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: '*'
# Kafka Consumer Listener 监听器配置
listener:
missing-topics-fatal: false # 消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
--- #################### 定时任务相关配置 ####################
xxl:
job:
executor:
appname: ${spring.application.name} # 执行器 AppName
logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
accessToken: default_token # 执行器通讯TOKEN
--- #################### 芋道相关配置 ####################
viewsh:
info:
version: 1.0.0
base-package: com.viewsh.module.iot
web:
admin-ui:
url: http://dashboard.viewsh.iocoder.cn # Admin 管理后台 UI 的地址
xss:
enable: false
exclude-urls: # 如下 url仅仅是为了演示去掉配置也没关系
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
swagger:
title: 管理后台
description: 提供管理员管理的所有功能
version: ${viewsh.info.version}
tenant: # 多租户相关配置项
enable: true
ignore-urls:
ignore-tables:
ignore-caches:
- iot:device
- iot:thing_model_list
iot:
message-bus:
type: redis # 消息总线的类型
# 跨模块事件总线配置IntegrationEventBus
integration:
mq:
enabled: true # 是否启用跨模块事件发布
producer-group: integration-event-producer # 生产者组名
send-timeout-ms: 3000 # 发送超时时间(毫秒)
debug: false