From bc3463cdd3ceef1701a4f3e68e8dd037c0112f87 Mon Sep 17 00:00:00 2001 From: "1378012178@qq.com" <1378012178@qq.com> Date: Thu, 2 Apr 2026 14:52:23 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=A2=9E=E5=8A=A0=E6=AF=8F=E5=A4=A92?= =?UTF-8?q?3:59:50=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=EF=BC=8C=E5=B0=86?= =?UTF-8?q?=E5=BD=93=E5=A4=A9=E7=9A=84=E5=B7=A5=E5=8D=95=20-=20=E5=B7=B2?= =?UTF-8?q?=E5=BC=80=E5=A7=8B=E6=9C=AA=E7=BB=93=E6=9D=9F=E7=9A=84=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E6=97=B6=E9=97=B4=E8=AE=BE=E7=BD=AE=E4=B8=BA23:59?= =?UTF-8?q?=EF=BC=9A59=202=E3=80=81=E7=94=9F=E6=88=90=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E5=A2=9E=E5=8A=A0=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=A4=84=E7=90=86=EF=BC=9A=E6=89=8B=E5=8A=A8=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E5=A6=82=E6=9E=9C=E5=B7=B2=E7=BB=8F=E6=B4=BE=E5=8F=91=E8=BF=87?= =?UTF-8?q?=E5=AF=B9=E5=BA=94=E5=B7=A5=E5=8D=95=E4=B8=8D=E5=86=8D=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E6=B4=BE=E5=8F=91=203=E3=80=81=E6=B6=82=E9=B8=A6?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nu/modules/commonutils/TuyaAirconApi.java | 303 ++++++++++++---- nursing-unit-common/pom.xml | 2 +- .../nu/connector/AirConditionerConnector.java | 23 ++ .../com/nu/connector/DeviceConnector.java | 60 ++++ .../java/com/nu/entity/CommandParams.java | 9 + .../java/com/nu/entity/CommandResponse.java | 11 + .../main/java/com/nu/utils/TuyaApiUtil.java | 330 ------------------ .../plan/care/job/DirectiveEndOrderJob.java | 45 +++ .../impl/DirectivePlanDateServiceImpl.java | 73 ++-- .../java/org/jeecg/NUSystemApplication.java | 2 + 10 files changed, 425 insertions(+), 433 deletions(-) create mode 100644 nursing-unit-common/src/main/java/com/nu/connector/AirConditionerConnector.java create mode 100644 nursing-unit-common/src/main/java/com/nu/connector/DeviceConnector.java create mode 100644 nursing-unit-common/src/main/java/com/nu/entity/CommandParams.java create mode 100644 nursing-unit-common/src/main/java/com/nu/entity/CommandResponse.java delete mode 100644 nursing-unit-common/src/main/java/com/nu/utils/TuyaApiUtil.java create mode 100644 nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/plan/care/job/DirectiveEndOrderJob.java diff --git a/nursing-unit-api/src/main/java/com/nu/modules/commonutils/TuyaAirconApi.java b/nursing-unit-api/src/main/java/com/nu/modules/commonutils/TuyaAirconApi.java index ac8ccc82..c51dd48e 100644 --- a/nursing-unit-api/src/main/java/com/nu/modules/commonutils/TuyaAirconApi.java +++ b/nursing-unit-api/src/main/java/com/nu/modules/commonutils/TuyaAirconApi.java @@ -1,7 +1,9 @@ package com.nu.modules.commonutils; -import com.fasterxml.jackson.databind.JsonNode; -import com.nu.utils.TuyaApiUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nu.connector.AirConditionerConnector; +import com.nu.connector.DeviceConnector; +import com.nu.entity.CommandResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -12,112 +14,271 @@ import java.util.*; public class TuyaAirconApi { @Autowired - private TuyaApiUtil tuyaApiUtil; + private DeviceConnector deviceConnector; - private String deviceId = "6c36cb2d2c352683bfnsm1"; + @Autowired + private AirConditionerConnector airConditionerConnector; /** - * 测试连接 + * 获取设备信息 + * + * @param deviceId 设备ID */ - @GetMapping("/test") - public Map test() { - Map result = new HashMap<>(); + @GetMapping("/getDeviceById") + public Map getDeviceById(@RequestParam String deviceId) { try { - String token = tuyaApiUtil.getAccessToken(); - result.put("success", true); - result.put("message", "连接成功"); - result.put("token", token); + Map device = deviceConnector.getDeviceById(deviceId); + return Map.of( + "success", true, + "message", "连接成功", + "data", device + ); } catch (Exception e) { - result.put("success", false); - result.put("message", "连接失败: " + e.getMessage()); - e.printStackTrace(); + return Map.of( + "success", false, + "message", "连接失败: " + e.getMessage() + ); } - return result; } /** - * 获取设备状态 + * 获取设备属性 + * + * @param deviceId 设备ID */ - @GetMapping("/status") - public Map getDeviceStatus() { - Map result = new HashMap<>(); + @GetMapping("/getDeviceProperties") + public Map getDeviceProperties(@RequestParam String deviceId) { try { - JsonNode response = tuyaApiUtil.getDeviceStatus(deviceId); - result.put("success", true); - result.put("data", response); + Map device = deviceConnector.getDeviceProperties(deviceId); + return Map.of( + "success", true, + "message", "查询属性成功", + "data", device + ); } catch (Exception e) { - result.put("success", false); - result.put("message", "获取设备状态失败: " + e.getMessage()); - e.printStackTrace(); + return Map.of( + "success", false, + "message", "查询属性失败: " + e.getMessage() + ); } - return result; } /** - * 获取设备功能点规格 + * 获取设备期望属性 + * + * @param deviceId 设备ID */ - @GetMapping("/specifications") - public Map getDeviceSpecifications() { - Map result = new HashMap<>(); + @GetMapping("/getDeviceDesiredProperties") + public Map getDeviceDesiredProperties(@RequestParam String deviceId) { try { - JsonNode response = tuyaApiUtil.getDeviceSpecifications(deviceId); - result.put("success", true); - result.put("data", response); + Map device = deviceConnector.getDeviceDesiredProperties(deviceId); + return Map.of( + "success", true, + "message", "查询期望成功", + "data", device + ); } catch (Exception e) { - result.put("success", false); - result.put("message", "获取设备规格失败: " + e.getMessage()); - e.printStackTrace(); + return Map.of( + "success", false, + "message", "查询期望失败: " + e.getMessage() + ); } - return result; } /** - * 发送控制指令(单个指令) - * @param code 功能点code - * @param value 功能点值 + * 获取设备期望属性 + * + * @param deviceId 设备ID */ - @PostMapping("/control/single") - public Map sendSingleCommand( + @GetMapping("/getDeviceFunctions") + public Map getDeviceFunctions(@RequestParam String deviceId) { + try { + Map device = deviceConnector.getDeviceFunctions(deviceId); + return Map.of( + "success", true, + "message", "查询设备指令集成功", + "data", device + ); + } catch (Exception e) { + return Map.of( + "success", false, + "message", "查询设备指令集失败: " + e.getMessage() + ); + } + } + + /** + * 发送控制指令(单个) + * + * @param deviceId 设备ID + * @param code 功能点code + * @param value 功能点值 + */ + @PostMapping("/control") + public Map sendCommand( + @RequestParam String deviceId, @RequestParam String code, - @RequestParam Object value) { - Map result = new HashMap<>(); + @RequestParam String value) { try { - Map command = new HashMap<>(); - command.put("code", code); - command.put("value", value); + // 1. 构建 input_params(注意是 JSON 字符串!!) + Map params = new HashMap<>(); + params.put(code, value); - List> commands = new ArrayList<>(); - commands.add(command); + // 👉 转成 JSON 字符串 + String inputParams = new com.fasterxml.jackson.databind.ObjectMapper() + .writeValueAsString(params); + + // 2. 构建 body + Map body = new HashMap<>(); + body.put("code", code); // 动作 code + body.put("input_params", inputParams); // JSON字符串 + + // 3. 调用 + Map result = deviceConnector.sendAction(deviceId, body); + + return result; - JsonNode response = tuyaApiUtil.sendControlCommand(deviceId, commands); - result.put("success", true); - result.put("message", "指令发送成功"); - result.put("data", response); } catch (Exception e) { - result.put("success", false); - result.put("message", "指令发送失败: " + e.getMessage()); - e.printStackTrace(); + return Map.of("success", false, "message", e.getMessage()); + } + } + + @PostMapping("/switch") + public Map switchAircon( + @RequestParam String deviceId, + @RequestParam Boolean on) { + try { + + String code = on ? "PowerOn" : "PowerOff"; + + Map params = new HashMap<>(); + params.put(code, code); + + String inputParams = new ObjectMapper().writeValueAsString(params); + + Map body = new HashMap<>(); + body.put("code", code); + body.put("input_params", inputParams); + + Map result = deviceConnector.sendAction(deviceId, body); + + return result; + + } catch (Exception e) { + return Map.of("success", false, "message", e.getMessage()); } - return result; } /** - * 发送批量控制指令(多个指令) - * @param commands 指令列表,格式:[{"code":"switch","value":true},{"code":"temp_set","value":26}] + * 空调组合按键下发 + * 通过组合命令控制空调 + * + * @param infraredId 设备ID (infrared_id) + * @param remoteId 遥控器ID (remote_id) + * @param power 电源:0-关闭,1-打开(必填) + * @param mode 模式:0-制冷,1-制热,2-自动,3-送风,4-除湿(可选) + * @param temp 温度(可选) + * @param wind 风速:0-自动,1-低,2-中,3-高(可选) + * @param remoteIndex 遥控器索引ID(可选) + * @param categoryId 品类ID(可选) */ - @PostMapping("/control/batch") - public Map sendBatchCommands(@RequestBody List> commands) { - Map result = new HashMap<>(); + @PostMapping("/sendCommand") + public Map sendAirconCommand( + @RequestParam String infraredId, + @RequestParam String remoteId, + @RequestParam Integer power, + @RequestParam(required = false) Integer mode, + @RequestParam(required = false) Integer temp, + @RequestParam(required = false) Integer wind, + @RequestParam(required = false) Integer remoteIndex, + @RequestParam(required = false) Integer categoryId) { try { - JsonNode response = tuyaApiUtil.sendControlCommand(deviceId, commands); - result.put("success", true); - result.put("message", "批量指令发送成功"); - result.put("data", response); + // 构建请求体 + Map body = new HashMap<>(); + body.put("power", power); + + if (mode != null) { + body.put("mode", mode); + } + if (temp != null) { + body.put("temp", temp); + } + if (wind != null) { + body.put("wind", wind); + } + if (remoteIndex != null) { + body.put("remote_index", remoteIndex); + } + if (categoryId != null) { + body.put("category_id", categoryId); + } + + // 调用 connector 发送命令 + Map result = deviceConnector.sendCommandKT(infraredId, remoteId, body); + + // 处理返回结果 + if (result != null && Boolean.TRUE.equals(result.get("success"))) { + return Map.of( + "success", true, + "message", "空调控制指令发送成功", + "data", result + ); + } else { + String errorMsg = result != null ? result.getOrDefault("message", "未知错误").toString() : "返回结果为空"; + return Map.of( + "success", false, + "message", "空调控制指令发送失败: " + errorMsg + ); + } } catch (Exception e) { - result.put("success", false); - result.put("message", "批量指令发送失败: " + e.getMessage()); - e.printStackTrace(); + return Map.of( + "success", false, + "message", "空调控制指令发送异常: " + e.getMessage() + ); + } + } + + /** + * 空调组合按键下发(使用Body方式传参) + * + * @param infraredId 设备ID + * @param remoteId 遥控器ID + * @param body 请求参数体 + */ + @PostMapping("/sendCommandWithBody") + public Map sendAirconCommandWithBody( + @RequestParam String infraredId, + @RequestParam String remoteId, + @RequestBody Map body) { + try { + // 验证必填参数 power + if (body == null || !body.containsKey("power")) { + return Map.of( + "success", false, + "message", "缺少必填参数: power" + ); + } + + Map result = deviceConnector.sendCommandKT(infraredId, remoteId, body); + + if (result != null && Boolean.TRUE.equals(result.get("success"))) { + return Map.of( + "success", true, + "message", "空调控制指令发送成功", + "data", result + ); + } else { + String errorMsg = result != null ? result.getOrDefault("message", "未知错误").toString() : "返回结果为空"; + return Map.of( + "success", false, + "message", "空调控制指令发送失败: " + errorMsg + ); + } + } catch (Exception e) { + return Map.of( + "success", false, + "message", "空调控制指令发送异常: " + e.getMessage() + ); } - return result; } } diff --git a/nursing-unit-common/pom.xml b/nursing-unit-common/pom.xml index 447ab926..d7559a8b 100644 --- a/nursing-unit-common/pom.xml +++ b/nursing-unit-common/pom.xml @@ -46,7 +46,7 @@ com.tuya tuya-spring-boot-starter - 1.0.0 + 1.5.4 diff --git a/nursing-unit-common/src/main/java/com/nu/connector/AirConditionerConnector.java b/nursing-unit-common/src/main/java/com/nu/connector/AirConditionerConnector.java new file mode 100644 index 00000000..7d716d18 --- /dev/null +++ b/nursing-unit-common/src/main/java/com/nu/connector/AirConditionerConnector.java @@ -0,0 +1,23 @@ +package com.nu.connector; + +import com.tuya.connector.api.annotations.GET; +import com.tuya.connector.api.annotations.POST; +import com.tuya.connector.api.annotations.Path; +import com.tuya.connector.api.annotations.Body; +import java.util.List; +import java.util.Map; + +public interface AirConditionerConnector { + + /** + * 获取设备列表 + */ + @GET("/v1.0/devices") + Map getDeviceList(); + + /** + * 空调控制 + */ + @POST("/v1.0/devices/{device_id}/commands") + Map control(@Path("device_id") String deviceId, @Body Map command); +} diff --git a/nursing-unit-common/src/main/java/com/nu/connector/DeviceConnector.java b/nursing-unit-common/src/main/java/com/nu/connector/DeviceConnector.java new file mode 100644 index 00000000..2570d78b --- /dev/null +++ b/nursing-unit-common/src/main/java/com/nu/connector/DeviceConnector.java @@ -0,0 +1,60 @@ +package com.nu.connector; + +import com.nu.entity.CommandResponse; +import com.tuya.connector.api.annotations.Body; +import com.tuya.connector.api.annotations.GET; +import com.tuya.connector.api.annotations.POST; +import com.tuya.connector.api.annotations.Path; + +import java.util.List; +import java.util.Map; + +public interface DeviceConnector { + + /** + * 获取设备详情 + */ + @GET("/v2.0/cloud/thing/{device_id}") + Map getDeviceById(@Path("device_id") String deviceId); + + /** + * 查询属性 + */ + @GET("/v2.0/cloud/thing/{device_id}/shadow/properties") + Map getDeviceProperties(@Path("device_id") String deviceId); + + /** + * 查询期望属性 + */ + @GET("/v2.0/cloud/thing/{device_id}/shadow/properties/desired") + Map getDeviceDesiredProperties(@Path("device_id") String deviceId); + + /** + * 获取设备支持的指令集 + */ + @GET("/v1.0/iot-03/devices/{device_id}/functions") + Map getDeviceFunctions(@Path("device_id") String deviceId); + + /** + * 发送控制指令 + */ + @POST("/v2.0/cloud/thing/{device_id}/shadow/actions") + Map sendAction(@Path("device_id") String deviceId, @Body Map body); + + /** + * 发送控制指令 + */ + @POST("/v1.0/iot-03/devices/{device_id}/commands") + Map sendCommands2(@Path("device_id") String deviceId, @Body Map body); + + /** + * 发送控制指令 + */ + // 在 AirConditionerConnector 中修改为: + @POST("/v2.0/infrareds/{infrared_id}/air-conditioners/{remote_id}/scenes/command") + Map sendCommandKT( + @Path("infrared_id") String infraredId, + @Path("remote_id") String remoteId, + @Body Map body + ); +} diff --git a/nursing-unit-common/src/main/java/com/nu/entity/CommandParams.java b/nursing-unit-common/src/main/java/com/nu/entity/CommandParams.java new file mode 100644 index 00000000..f7d149fe --- /dev/null +++ b/nursing-unit-common/src/main/java/com/nu/entity/CommandParams.java @@ -0,0 +1,9 @@ +package com.nu.entity; + +import lombok.Data; + +@Data +public class CommandParams { + private String code; + private Object value; +} diff --git a/nursing-unit-common/src/main/java/com/nu/entity/CommandResponse.java b/nursing-unit-common/src/main/java/com/nu/entity/CommandResponse.java new file mode 100644 index 00000000..f2ee0071 --- /dev/null +++ b/nursing-unit-common/src/main/java/com/nu/entity/CommandResponse.java @@ -0,0 +1,11 @@ +package com.nu.entity; + +import lombok.Data; + +@Data +public class CommandResponse { + private Boolean result; + private Long t; + private Boolean success; + private String tid; +} diff --git a/nursing-unit-common/src/main/java/com/nu/utils/TuyaApiUtil.java b/nursing-unit-common/src/main/java/com/nu/utils/TuyaApiUtil.java deleted file mode 100644 index 42a071b4..00000000 --- a/nursing-unit-common/src/main/java/com/nu/utils/TuyaApiUtil.java +++ /dev/null @@ -1,330 +0,0 @@ -package com.nu.utils; - -import cn.hutool.core.util.HexUtil; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.*; - -@Slf4j -@Component -public class TuyaApiUtil { - - @Value("${connector.ak}") - private String clientId; - - @Value("${connector.sk}") - private String secret; - - @Value("${connector.region:CN}") - private String region; - - private final RestTemplate restTemplate; - private final ObjectMapper objectMapper; - - private volatile String accessToken; - private volatile long tokenExpireTime; - private final Object tokenLock = new Object(); - - public TuyaApiUtil() { - this.restTemplate = new RestTemplate(); - this.objectMapper = new ObjectMapper(); - } - - private String getApiHost() { - switch (region.toUpperCase()) { - case "CN": - return "https://openapi.tuyacn.com"; - case "US": - return "https://openapi.tuyaus.com"; - case "EU": - return "https://openapi.tuyaeu.com"; - case "IN": - return "https://openapi.tuyain.com"; - default: - return "https://openapi.tuyacn.com"; - } - } - - /** - * 生成签名 - 按照涂鸦官方规范 - * 签名字符串格式: METHOD\n CONTENT-SHA256\n HEADERS\n PATH - */ - public String generateSign(String method, String url, Map headers, String body) { - try { - // 1. 获取路径和查询参数 - String path = getPathAndQuery(url); - - // 2. 计算body的SHA256 - String contentSha256 = getContentSha256(body); - - // 3. 构建header字符串(只包含client_id, t, sign_method, nonce等) - String headerString = buildHeaderString(headers); - - // 4. 构建签名字符串 - StringBuilder sb = new StringBuilder(); - sb.append(method.toUpperCase()).append("\n"); - sb.append(contentSha256).append("\n"); - sb.append(headerString).append("\n"); - sb.append(path); - - String signStr = sb.toString(); - log.debug("签名字符串: \n{}", signStr); - - // 5. HMAC-SHA256加密 - Mac mac = Mac.getInstance("HmacSHA256"); - SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); - mac.init(secretKeySpec); - byte[] signBytes = mac.doFinal(signStr.getBytes(StandardCharsets.UTF_8)); - - return HexUtil.encodeHexStr(signBytes).toUpperCase(); - } catch (Exception e) { - log.error("签名生成失败", e); - throw new RuntimeException("签名生成失败", e); - } - } - - private String getPathAndQuery(String url) { - try { - // 移除协议和域名,只保留路径和查询参数 - int protocolEnd = url.indexOf("://"); - if (protocolEnd != -1) { - int pathStart = url.indexOf("/", protocolEnd + 3); - if (pathStart != -1) { - return url.substring(pathStart); - } - } - return "/"; - } catch (Exception e) { - log.error("解析URL失败: {}", url, e); - return url; - } - } - - private String getContentSha256(String body) { - try { - if (body == null || body.isEmpty()) { - return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - } - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] hash = digest.digest(body.getBytes(StandardCharsets.UTF_8)); - return HexUtil.encodeHexStr(hash).toLowerCase(); - } catch (NoSuchAlgorithmException e) { - log.error("计算SHA256失败", e); - return ""; - } - } - - private String buildHeaderString(Map headers) { - if (headers == null || headers.isEmpty()) { - return ""; - } - - // 按key排序 - List sortedKeys = new ArrayList<>(headers.keySet()); - Collections.sort(sortedKeys); - - StringBuilder sb = new StringBuilder(); - for (String key : sortedKeys) { - sb.append(key).append(":").append(headers.get(key)).append("\n"); - } - - // 移除最后一个换行符 - if (sb.length() > 0) { - sb.setLength(sb.length() - 1); - } - - return sb.toString(); - } - - /** - * 获取Access Token - */ - public String getAccessToken() { - if (accessToken != null && System.currentTimeMillis() < tokenExpireTime) { - return accessToken; - } - - synchronized (tokenLock) { - if (accessToken != null && System.currentTimeMillis() < tokenExpireTime) { - return accessToken; - } - - try { - long timestamp = Instant.now().toEpochMilli(); - String nonce = UUID.randomUUID().toString().replace("-", ""); - String url = getApiHost() + "/v1.0/token?grant_type=1"; - - // 构建签名需要的headers - Map signHeaders = new LinkedHashMap<>(); - signHeaders.put("client_id", clientId); - signHeaders.put("sign_method", "HMAC-SHA256"); - signHeaders.put("t", String.valueOf(timestamp)); - signHeaders.put("nonce", nonce); - - // 生成签名 - String sign = generateSign("GET", url, signHeaders, null); - - // 构建请求头 - HttpHeaders headers = new HttpHeaders(); - headers.set("client_id", clientId); - headers.set("sign", sign); - headers.set("t", String.valueOf(timestamp)); - headers.set("sign_method", "HMAC-SHA256"); - headers.set("nonce", nonce); - - HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); - - log.info("Token响应: {}", response.getBody()); - - if (response.getStatusCode() == HttpStatus.OK) { - JsonNode jsonNode = objectMapper.readTree(response.getBody()); - if (jsonNode.has("success") && jsonNode.get("success").asBoolean()) { - JsonNode result = jsonNode.get("result"); - accessToken = result.get("access_token").asText(); - long expireTime = result.get("expire_time").asLong(); - tokenExpireTime = System.currentTimeMillis() + (expireTime * 1000) - 300000; - log.info("获取Access Token成功,过期时间: {}", new Date(tokenExpireTime)); - return accessToken; - } else { - String msg = jsonNode.has("msg") ? jsonNode.get("msg").asText() : "未知错误"; - throw new RuntimeException("获取Token失败: " + msg); - } - } else { - throw new RuntimeException("获取Token HTTP错误: " + response.getStatusCode()); - } - } catch (Exception e) { - log.error("获取Access Token异常", e); - throw new RuntimeException("获取Access Token异常", e); - } - } - } - - /** - * 发送设备控制指令 - */ - public JsonNode sendControlCommand(String deviceId, List> commands) throws Exception { - Map body = new HashMap<>(); - body.put("commands", commands); - - String path = "/v1.0/devices/" + deviceId + "/commands"; - return post(path, body); - } - - /** - * 获取设备状态 - */ - public JsonNode getDeviceStatus(String deviceId) throws Exception { - String path = "/v1.0/devices/" + deviceId; - return get(path, null); - } - - /** - * 获取设备功能点规格 - */ - public JsonNode getDeviceSpecifications(String deviceId) throws Exception { - String path = "/v1.0/devices/" + deviceId + "/specifications"; - return get(path, null); - } - - /** - * 获取设备列表 - */ - public JsonNode getDeviceList(int pageNo, int pageSize) throws Exception { - Map params = new HashMap<>(); - params.put("page_no", pageNo); - params.put("page_size", pageSize); - String path = "/v1.0/devices"; - return get(path, params); - } - - /** - * POST请求 - */ - public JsonNode post(String path, Object body) throws Exception { - String bodyJson = body == null ? null : objectMapper.writeValueAsString(body); - String url = getApiHost() + path; - return executeRequest(HttpMethod.POST, url, bodyJson); - } - - /** - * GET请求 - */ - public JsonNode get(String path, Map params) throws Exception { - String url = buildUrl(path, params); - return executeRequest(HttpMethod.GET, url, null); - } - - private String buildUrl(String path, Map params) { - String baseUrl = getApiHost() + path; - if (params == null || params.isEmpty()) { - return baseUrl; - } - - UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(baseUrl); - for (Map.Entry entry : params.entrySet()) { - builder.queryParam(entry.getKey(), entry.getValue()); - } - return builder.build().toUriString(); - } - - private JsonNode executeRequest(HttpMethod method, String url, String body) throws Exception { - long timestamp = Instant.now().toEpochMilli(); - String nonce = UUID.randomUUID().toString().replace("-", ""); - - // 构建签名需要的headers - Map signHeaders = new LinkedHashMap<>(); - signHeaders.put("client_id", clientId); - signHeaders.put("sign_method", "HMAC-SHA256"); - signHeaders.put("t", String.valueOf(timestamp)); - signHeaders.put("nonce", nonce); - - // 如果需要access_token - String token = getAccessToken(); - signHeaders.put("access_token", token); - - // 生成签名 - String sign = generateSign(method.name(), url, signHeaders, body); - - // 构建请求头 - HttpHeaders headers = new HttpHeaders(); - headers.set("client_id", clientId); - headers.set("sign", sign); - headers.set("t", String.valueOf(timestamp)); - headers.set("sign_method", "HMAC-SHA256"); - headers.set("nonce", nonce); - headers.set("access_token", token); - headers.set("Content-Type", "application/json"); - - HttpEntity entity = new HttpEntity<>(body, headers); - ResponseEntity response = restTemplate.exchange(url, method, entity, String.class); - - log.debug("响应: {}", response.getBody()); - - if (response.getStatusCode() == HttpStatus.OK) { - JsonNode jsonNode = objectMapper.readTree(response.getBody()); - if (jsonNode.has("success") && jsonNode.get("success").asBoolean()) { - return jsonNode; - } else { - String errorMsg = jsonNode.has("msg") ? jsonNode.get("msg").asText() : "未知错误"; - throw new RuntimeException("API调用失败: " + errorMsg); - } - } else { - throw new RuntimeException("HTTP请求失败: " + response.getStatusCode()); - } - } -} diff --git a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/plan/care/job/DirectiveEndOrderJob.java b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/plan/care/job/DirectiveEndOrderJob.java new file mode 100644 index 00000000..aa7c0c95 --- /dev/null +++ b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/plan/care/job/DirectiveEndOrderJob.java @@ -0,0 +1,45 @@ +package com.nu.modules.biz.plan.care.job; + +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.nu.modules.biz.order.entity.DirectiveOrder; +import com.nu.modules.biz.order.service.IDirectiveOrderService; +import lombok.extern.slf4j.Slf4j; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Date; + +/** + * 结束工单定时 每天23:59:50把当天已开始未结束的 结束时间设置为23:59:59 + */ +@Slf4j +public class DirectiveEndOrderJob implements Job { + @Autowired + private IDirectiveOrderService directiveOrderService; + + @Override + public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { + DirectiveOrder upData = new DirectiveOrder(); + //设置为当天23:59:59 + Calendar calendar = Calendar.getInstance(); + upData.setEmpEndTime(new Date(calendar.get(Calendar.YEAR) - 1900, + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH), + 23, 59, 59)); + + //当天任务且已开始的 + UpdateWrapper uw = new UpdateWrapper<>(); + uw.apply("DATE(serv_start_time) = CURDATE()"); + uw.isNotNull("emp_start_time"); + uw.isNull("emp_end_time"); + directiveOrderService.update(upData,uw); + } + + +} diff --git a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/plan/care/service/impl/DirectivePlanDateServiceImpl.java b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/plan/care/service/impl/DirectivePlanDateServiceImpl.java index e191928b..5f3a7048 100644 --- a/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/plan/care/service/impl/DirectivePlanDateServiceImpl.java +++ b/nursing-unit-services/nu-services-biz/src/main/java/com/nu/modules/biz/plan/care/service/impl/DirectivePlanDateServiceImpl.java @@ -2,6 +2,7 @@ package com.nu.modules.biz.plan.care.service.impl; import cn.hutool.core.bean.BeanUtil; import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.nu.entity.CareDirectiveEntity; import com.nu.entity.DirectivePlanDateEntity; import com.nu.modules.biz.order.entity.DirectiveOrder; @@ -12,13 +13,17 @@ import com.nu.modules.biz.plan.care.service.IDirectivePlanDateService; import com.nu.modules.care.api.IDirectivePlanDateApi; import com.nu.websocket.SdWebsocket; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.Lists; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.util.CollectionUtils; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; /** * @Description: 服务指令编排-日期-快照表 @@ -46,40 +51,46 @@ public class DirectivePlanDateServiceImpl extends ServiceImpl diretiveList = baseMapper.queryAllTaskByDateTime(queryParam); - - //已派发指令不再重复派发逻辑 首先排除即时指令干扰(不差cycleTypeId = 2的) 但是已派发出去的工单不知道是不是即时指令(因为没有cycleTypeId) - //查nuid+directive完全一致的 - - List orders = BeanUtil.copyToList(diretiveList, DirectiveOrder.class); - orders.stream().forEach(item -> { - item.setId(null); - item.setEmployeeId(empId); - item.setEmployeeName("王伟东"); - item.setOptType("1");//单人执行 - item.setServStartTime(item.getStartTime());//服务开始时间 - item.setServEndTime(item.getEndTime());//服务结束时间 - item.setOrderStartTime(new Date());//工单开始时间 - item.setCreateBy("工单定时任务"); - }); - directiveOrderService.saveBatch(orders); + //查询这个时间点的已派发指令 + QueryWrapper orderQW = new QueryWrapper<>(); + orderQW.eq("serv_start_time", queryParam.getStartTime()); + List havingOrderList = directiveOrderService.list(orderQW); + //已派发指令不再重复派发逻辑 不用考虑即时指令 即时指令属于客户主动触发行为 + //如何判断重复: 查servStartTime+nuid+directive完全一致的 (查询时已经是同一年月日时分秒了 所以不用特意再判断) + List genOrders = orders.stream().filter(item -> havingOrderList.stream().noneMatch(existingOrder -> existingOrder.getNuId().equals(item.getNuId()) && existingOrder.getDirectiveId().equals(item.getDirectiveId()))).collect(Collectors.toList()); - //发送websocket消息 - try { - // 发送数据 - JSONObject message = new JSONObject(); - message.put("type", "directiveOrder");//消息类型 - message.put("timestamp", System.currentTimeMillis()); - message.put("from", "system");//发送者 - message.put("to", empId);//先固定都发给伟东 - message.put("data", orders);//业务数据 - String messageJson = message.toJSONString(); - //发送给单个用户 - sdWebsocket.sendMessage(empId, messageJson); - log.info("【ws消息推送】发送给用户 {}: {}", empId, messageJson); - } catch (Exception e) { - log.error("ws发送消息失败", e); + if (!CollectionUtils.isEmpty(genOrders)) { + genOrders.stream().forEach(item -> { + item.setId(null); + item.setEmployeeId(empId); + item.setEmployeeName("王伟东"); + item.setOptType("1");//单人执行 + item.setServStartTime(item.getStartTime());//服务开始时间 + item.setServEndTime(item.getEndTime());//服务结束时间 + item.setOrderStartTime(new Date());//工单开始时间 + item.setCreateBy("工单定时任务"); + }); + directiveOrderService.saveBatch(genOrders); + //发送websocket消息 + try { + // 发送数据 + JSONObject message = new JSONObject(); + message.put("type", "directiveOrder");//消息类型 + message.put("timestamp", System.currentTimeMillis()); + message.put("from", "system");//发送者 + message.put("to", empId);//先固定都发给伟东 + message.put("data", genOrders);//业务数据 + String messageJson = message.toJSONString(); + //发送给单个用户 + sdWebsocket.sendMessage(empId, messageJson); + log.info("【ws消息推送】发送给用户 {}: {}", empId, messageJson); + } catch (Exception e) { + log.error("ws发送消息失败", e); + } } + + } } diff --git a/nursing-unit-system/nu-system-start/src/main/java/org/jeecg/NUSystemApplication.java b/nursing-unit-system/nu-system-start/src/main/java/org/jeecg/NUSystemApplication.java index cbbf61be..90adf3ec 100644 --- a/nursing-unit-system/nu-system-start/src/main/java/org/jeecg/NUSystemApplication.java +++ b/nursing-unit-system/nu-system-start/src/main/java/org/jeecg/NUSystemApplication.java @@ -1,5 +1,6 @@ package org.jeecg; +import com.tuya.connector.spring.annotations.ConnectorScan; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.util.oConvertUtils; import org.mybatis.spring.annotation.MapperScan; @@ -24,6 +25,7 @@ import java.util.Map; @EnableAsync @SpringBootApplication @ComponentScan(basePackages = {"com.nu","org.jeecg"}) +@ConnectorScan(basePackages = "com.nu.connector") @MapperScan({"com.nu.**.mapper","org.jeecg.**.mapper"}) public class NUSystemApplication extends SpringBootServletInitializer {