fix: 设备最后上数时间,新增字段,接口调用,小程序显示。
This commit is contained in:
@@ -243,5 +243,10 @@ public class Device extends TenantEntity {
|
|||||||
*/
|
*/
|
||||||
private Integer valueBattery;
|
private Integer valueBattery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后上数时间
|
||||||
|
*/
|
||||||
|
private Date lastReportTime;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,5 +270,10 @@ public class DeviceBo extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private Long batteryWarnLower;
|
private Long batteryWarnLower;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后上数时间
|
||||||
|
*/
|
||||||
|
private Date lastReportTime;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,12 @@ public class DeviceVo implements Serializable {
|
|||||||
@ExcelProperty(value = "电量值")
|
@ExcelProperty(value = "电量值")
|
||||||
private Integer valueBattery;
|
private Integer valueBattery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后上数时间
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "最后上数时间")
|
||||||
|
private Date lastReportTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手机号
|
* 手机号
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.intc.iot.controller;
|
package com.intc.iot.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import com.intc.common.core.domain.R;
|
import com.intc.common.core.domain.R;
|
||||||
import com.intc.common.mybatis.core.page.PageQuery;
|
import com.intc.common.mybatis.core.page.PageQuery;
|
||||||
import com.intc.common.mybatis.core.page.TableDataInfo;
|
import com.intc.common.mybatis.core.page.TableDataInfo;
|
||||||
import com.intc.common.satoken.utils.LoginHelper;
|
import com.intc.common.satoken.utils.LoginHelper;
|
||||||
|
import com.intc.common.tenant.helper.TenantHelper;
|
||||||
import com.intc.common.web.core.BaseController;
|
import com.intc.common.web.core.BaseController;
|
||||||
import com.intc.common.core.config.properties.DeviceTypeProperties;
|
import com.intc.common.core.config.properties.DeviceTypeProperties;
|
||||||
import com.intc.fishery.domain.Device;
|
import com.intc.fishery.domain.Device;
|
||||||
@@ -46,6 +48,7 @@ import com.intc.iot.utils.ControllerHelper;
|
|||||||
import com.intc.iot.service.IotCloudService;
|
import com.intc.iot.service.IotCloudService;
|
||||||
import com.intc.iot.constant.IOTPropertyName;
|
import com.intc.iot.constant.IOTPropertyName;
|
||||||
import com.intc.fishery.utils.MessageOpRecordUtil;
|
import com.intc.fishery.utils.MessageOpRecordUtil;
|
||||||
|
import com.intc.tdengine.service.IDeviceSensorDataService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -57,6 +60,9 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -72,6 +78,8 @@ import java.util.Map;
|
|||||||
@Tag(name = "生活物联网平台管理", description = "阿里云飞燕平台对接接口")
|
@Tag(name = "生活物联网平台管理", description = "阿里云飞燕平台对接接口")
|
||||||
public class IotController extends BaseController {
|
public class IotController extends BaseController {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
private final DeviceTypeProperties deviceTypeProperties;
|
private final DeviceTypeProperties deviceTypeProperties;
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
@@ -101,6 +109,9 @@ public class IotController extends BaseController {
|
|||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private DeviceDataHandler deviceDataHandler;
|
private DeviceDataHandler deviceDataHandler;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private IDeviceSensorDataService deviceSensorDataService;
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private javax.jms.Connection amqpConnection;
|
private javax.jms.Connection amqpConnection;
|
||||||
|
|
||||||
@@ -191,6 +202,82 @@ public class IotController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "回填设备最后上数时间")
|
||||||
|
@SaCheckPermission("fishery:device:edit")
|
||||||
|
@PostMapping("/device/backfill-last-report-time")
|
||||||
|
public R<Map<String, Object>> backfillLastReportTime(
|
||||||
|
@RequestParam(defaultValue = "false") boolean ignoreTenant) {
|
||||||
|
if (deviceMapper == null || deviceSensorDataService == null) {
|
||||||
|
return R.fail("设备或TDengine服务未启用");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = ignoreTenant
|
||||||
|
? TenantHelper.ignore((java.util.function.Supplier<Map<String, Object>>) this::doBackfillLastReportTime)
|
||||||
|
: doBackfillLastReportTime();
|
||||||
|
result.put("ignoreTenant", ignoreTenant);
|
||||||
|
return R.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> doBackfillLastReportTime() {
|
||||||
|
int total = 0;
|
||||||
|
int updated = 0;
|
||||||
|
int skipped = 0;
|
||||||
|
int failed = 0;
|
||||||
|
|
||||||
|
List<Device> devices = deviceMapper.selectList(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||||
|
.isNotNull(Device::getSerialNum)
|
||||||
|
.ne(Device::getSerialNum, "")
|
||||||
|
.select(Device::getId, Device::getSerialNum)
|
||||||
|
);
|
||||||
|
|
||||||
|
total = devices.size();
|
||||||
|
for (Device device : devices) {
|
||||||
|
try {
|
||||||
|
String latestTime = deviceSensorDataService.getLatestReportTime(device.getSerialNum());
|
||||||
|
if (latestTime == null || latestTime.isBlank()) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Device updateDevice = new Device();
|
||||||
|
updateDevice.setId(device.getId());
|
||||||
|
updateDevice.setLastReportTime(parseReportTime(latestTime));
|
||||||
|
if (deviceMapper.updateById(updateDevice) > 0) {
|
||||||
|
updated++;
|
||||||
|
} else {
|
||||||
|
skipped++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
failed++;
|
||||||
|
log.warn("回填设备最后上数时间失败: deviceId={}, serialNum={}, error={}",
|
||||||
|
device.getId(), device.getSerialNum(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new java.util.HashMap<>();
|
||||||
|
result.put("total", total);
|
||||||
|
result.put("updated", updated);
|
||||||
|
result.put("skipped", skipped);
|
||||||
|
result.put("failed", failed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date parseReportTime(String time) {
|
||||||
|
String normalized = normalizeReportTime(time);
|
||||||
|
return Date.from(LocalDateTime.parse(normalized, DATETIME_FORMATTER)
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeReportTime(String time) {
|
||||||
|
String normalized = time.trim();
|
||||||
|
if (normalized.length() > 19) {
|
||||||
|
normalized = normalized.substring(0, 19);
|
||||||
|
}
|
||||||
|
return normalized.replace('T', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "根据 ProductKey 和 DeviceName 查询设备信息")
|
@Operation(summary = "根据 ProductKey 和 DeviceName 查询设备信息")
|
||||||
@GetMapping("/device/find")
|
@GetMapping("/device/find")
|
||||||
public R<Map<String, Object>> findDeviceByProductKeyAndName(
|
public R<Map<String, Object>> findDeviceByProductKeyAndName(
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -2595,6 +2597,7 @@ public class DeviceDataHandler {
|
|||||||
if (sensorData.getBattery() != null) {
|
if (sensorData.getBattery() != null) {
|
||||||
updateDevice.setValueBattery(sensorData.getBattery().intValue());
|
updateDevice.setValueBattery(sensorData.getBattery().intValue());
|
||||||
}
|
}
|
||||||
|
updateDevice.setLastReportTime(parseSensorDataTime(sensorData));
|
||||||
int updated = deviceMapper.updateById(updateDevice);
|
int updated = deviceMapper.updateById(updateDevice);
|
||||||
if (updated > 0) {
|
if (updated > 0) {
|
||||||
log.debug("设备实时数据已更新: {}", deviceName);
|
log.debug("设备实时数据已更新: {}", deviceName);
|
||||||
@@ -2604,6 +2607,29 @@ public class DeviceDataHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Date parseSensorDataTime(DeviceSensorData sensorData) {
|
||||||
|
String time = StrUtil.blankToDefault(sensorData.getCreateTime(), sensorData.getTime());
|
||||||
|
if (StrUtil.isBlank(time)) {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Date.from(LocalDateTime.parse(normalizeSensorDataTime(time), DATETIME_FORMATTER)
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.toInstant());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析传感器上数时间失败,使用当前服务器时间: {}", time);
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeSensorDataTime(String time) {
|
||||||
|
String normalized = time.trim();
|
||||||
|
if (normalized.length() > 19) {
|
||||||
|
normalized = normalized.substring(0, 19);
|
||||||
|
}
|
||||||
|
return normalized.replace('T', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 溶解氧联动控制(参考 C# SetDeviceSwitchLinkedOpen 逻辑)
|
* 溶解氧联动控制(参考 C# SetDeviceSwitchLinkedOpen 逻辑)
|
||||||
* <ul>
|
* <ul>
|
||||||
|
|||||||
@@ -77,4 +77,14 @@ public interface DeviceSensorDataMapper {
|
|||||||
@Param("endTime") String endTime,
|
@Param("endTime") String endTime,
|
||||||
@Param("intervalType") Integer intervalType);
|
@Param("intervalType") Integer intervalType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备最新上数时间
|
||||||
|
*
|
||||||
|
* @param serialNum 设备序列号
|
||||||
|
* @return 最新传感器数据
|
||||||
|
*/
|
||||||
|
@DS("taos")
|
||||||
|
@InterceptorIgnore(tenantLine = "true")
|
||||||
|
DeviceSensorData getLatestReportTime(@Param("serialNum") String serialNum);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,4 +38,12 @@ public interface IDeviceSensorDataService {
|
|||||||
* @return 设备传感器数据列表
|
* @return 设备传感器数据列表
|
||||||
*/
|
*/
|
||||||
public List<DeviceSensorData> getHistoryDataList(String serialNum, String startTime, String endTime, Integer intervalType);
|
public List<DeviceSensorData> getHistoryDataList(String serialNum, String startTime, String endTime, Integer intervalType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备最新上数时间
|
||||||
|
*
|
||||||
|
* @param serialNum 设备序列号
|
||||||
|
* @return 最新上数时间字符串,格式 yyyy-MM-dd HH:mm:ss;无数据时返回 null
|
||||||
|
*/
|
||||||
|
String getLatestReportTime(String serialNum);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,6 +257,30 @@ public class DeviceSensorDataService implements IDeviceSensorDataService {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备最新上数时间
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getLatestReportTime(String serialNum) {
|
||||||
|
if (serialNum == null || serialNum.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Integer exists = deviceSensorDataMapper.checkTableExists(serialNum);
|
||||||
|
if (exists == null || exists == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
DeviceSensorData latest = deviceSensorDataMapper.getLatestReportTime(serialNum);
|
||||||
|
if (latest == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return latest.getCreateTime() != null ? latest.getCreateTime() : latest.getTime();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("查询设备最新上数时间失败: serialNum={}, error={}", serialNum, e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取间隔类型描述
|
* 获取间隔类型描述
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -111,4 +111,13 @@
|
|||||||
</choose>
|
</choose>
|
||||||
</if>
|
</if>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="getLatestReportTime" resultType="com.intc.tdengine.domain.DeviceSensorData">
|
||||||
|
SELECT
|
||||||
|
`time`,
|
||||||
|
createTime
|
||||||
|
FROM `fishery`.`t_${serialNum}`
|
||||||
|
ORDER BY `time` DESC
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ public class WeixinController extends BaseController {
|
|||||||
// 9. 构建返回结果
|
// 9. 构建返回结果
|
||||||
WxPayOrderVo result = new WxPayOrderVo();
|
WxPayOrderVo result = new WxPayOrderVo();
|
||||||
result.setPrepayId(prepayId);
|
result.setPrepayId(prepayId);
|
||||||
|
result.setOutTradeNumber(order.getOutTradeNumber());
|
||||||
result.setTotalAmount(order.getTotalAmount());
|
result.setTotalAmount(order.getTotalAmount());
|
||||||
result.setTimestamp(payParams.get("timeStamp"));
|
result.setTimestamp(payParams.get("timeStamp"));
|
||||||
result.setNonceStr(payParams.get("nonceStr"));
|
result.setNonceStr(payParams.get("nonceStr"));
|
||||||
@@ -278,6 +279,27 @@ public class WeixinController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "主动确认微信支付订单")
|
||||||
|
@PostMapping("/pay/confirm_order")
|
||||||
|
public R<Boolean> confirmOrder(@RequestBody Map<String, String> request) {
|
||||||
|
try {
|
||||||
|
if (payOrderBusinessService == null) {
|
||||||
|
return R.fail("支付服务未启用");
|
||||||
|
}
|
||||||
|
|
||||||
|
String outTradeNumber = request != null ? request.get("outTradeNumber") : null;
|
||||||
|
if (outTradeNumber == null || outTradeNumber.isEmpty()) {
|
||||||
|
return R.fail("商户订单号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = payOrderBusinessService.confirmPaidOrder(outTradeNumber);
|
||||||
|
return success ? R.ok(true) : R.fail("订单未支付成功或确认失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("确认支付订单失败", e);
|
||||||
|
return R.fail("确认支付订单失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "微信支付回调通知")
|
@Operation(summary = "微信支付回调通知")
|
||||||
@PostMapping("/pay_notify")
|
@PostMapping("/pay_notify")
|
||||||
public Map<String, String> payNotify(
|
public Map<String, String> payNotify(
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ public class WxPayOrderVo implements Serializable {
|
|||||||
@Schema(description = "预支付ID")
|
@Schema(description = "预支付ID")
|
||||||
private String prepayId;
|
private String prepayId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户系统内部订单号
|
||||||
|
*/
|
||||||
|
@Schema(description = "商户订单号")
|
||||||
|
private String outTradeNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 签名类型
|
* 签名类型
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -92,6 +92,14 @@ public interface PayOrderBusinessService {
|
|||||||
*/
|
*/
|
||||||
int compensateUnpaidOrders();
|
int compensateUnpaidOrders();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主动确认单笔订单支付状态。
|
||||||
|
*
|
||||||
|
* @param outTradeNumber 商户订单号
|
||||||
|
* @return 是否已确认并处理支付成功
|
||||||
|
*/
|
||||||
|
boolean confirmPaidOrder(String outTradeNumber);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修复已支付订单中设备有效期未落库的异常数据。
|
* 修复已支付订单中设备有效期未落库的异常数据。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -570,6 +570,49 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService {
|
|||||||
return Boolean.TRUE.equals(success);
|
return Boolean.TRUE.equals(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmPaidOrder(String outTradeNumber) {
|
||||||
|
if (outTradeNumber == null || outTradeNumber.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PayOrder order = queryByOutTradeNumber(outTradeNumber);
|
||||||
|
if (order == null) {
|
||||||
|
log.warn("[支付确认] 本地订单不存在: outTradeNumber={}", outTradeNumber);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (PayOrderStatus.SUCCESS.equals(order.getOrderStatus())) {
|
||||||
|
log.info("[支付确认] 订单已成功: orderId={}, outTradeNumber={}", order.getId(), outTradeNumber);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String queryResult = wxPayService.queryOrderByOutTradeNo(outTradeNumber);
|
||||||
|
if (queryResult == null) {
|
||||||
|
log.warn("[支付确认] 查询微信订单失败: orderId={}, outTradeNumber={}", order.getId(), outTradeNumber);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject resultJson = JSONUtil.parseObj(queryResult);
|
||||||
|
String tradeState = resultJson.getStr("trade_state");
|
||||||
|
if ("SUCCESS".equalsIgnoreCase(tradeState)) {
|
||||||
|
return compensateSingleOrder(order, resultJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("CLOSED".equalsIgnoreCase(tradeState)) {
|
||||||
|
payOrderMapper.update(null,
|
||||||
|
new LambdaUpdateWrapper<PayOrder>()
|
||||||
|
.eq(PayOrder::getId, order.getId())
|
||||||
|
.ne(PayOrder::getOrderStatus, PayOrderStatus.SUCCESS)
|
||||||
|
.set(PayOrder::getOrderStatus, PayOrderStatus.CLOSED)
|
||||||
|
.set(PayOrder::getTradeState, "CLOSED")
|
||||||
|
.set(PayOrder::getTradeStateDescription, "微信侧已关闭")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
log.info("[支付确认] 订单未支付成功: orderId={}, outTradeNumber={}, tradeState={}",
|
||||||
|
order.getId(), outTradeNumber, tradeState);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int repairPaidOrderDeviceDeadTime() {
|
public int repairPaidOrderDeviceDeadTime() {
|
||||||
|
|||||||
Reference in New Issue
Block a user