From 8fd28e727ef6f87567dff612f9a990b1c69620d5 Mon Sep 17 00:00:00 2001 From: tianyongbao Date: Wed, 5 Nov 2025 00:35:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=8A=9F=E8=83=BD=E5=BC=80?= =?UTF-8?q?=E5=8F=91=EF=BC=8C=E7=9B=91=E6=B5=8B=E5=8E=86=E5=8F=B2=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E3=80=82=E5=BE=AE=E4=BF=A1=E5=92=8C=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E5=B9=B3=E5=8F=B0=EF=BC=8C=E6=A8=A1=E5=9D=97=E6=90=AD?= =?UTF-8?q?=E5=BB=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- intc-admin/pom.xml | 5 + .../src/main/resources/application-dev.yml | 22 +- intc-modules/intc-iot/pom.xml | 91 ++++++++ .../iot/config/AliyunIotConfiguration.java | 41 ++++ .../intc/iot/config/AliyunIotProperties.java | 100 +++++++++ .../intc/iot/config/MqttConfiguration.java | 49 ++++ .../intc/iot/controller/IotController.java | 212 ++++++++++++++++++ .../java/com/intc/iot/domain/IotDevice.java | 69 ++++++ .../intc/iot/handler/DeviceDataHandler.java | 134 +++++++++++ .../intc/iot/service/DeviceDataService.java | 42 ++++ .../intc/iot/service/IotDeviceService.java | 70 ++++++ .../com/intc/iot/service/MqttService.java | 37 +++ .../service/impl/DeviceDataServiceImpl.java | 85 +++++++ .../service/impl/IotDeviceServiceImpl.java | 125 +++++++++++ .../iot/service/impl/MqttServiceImpl.java | 87 +++++++ .../src/main/resources/application.yml | 36 +++ intc-modules/intc-tdengine/pom.xml | 5 - .../DeviceSensorDataController.java | 7 +- .../tdengine/domain/DeviceSensorData.java | 73 +++++- .../mapper/DeviceSensorDataMapper.java | 18 +- .../service/IDeviceSensorDataService.java | 10 +- .../service/impl/DeviceSensorDataService.java | 72 +++++- .../tdengine/DeviceSensorDataMapper.xml | 61 ++++- intc-modules/intc-weixin/pom.xml | 88 ++++++++ .../intc/weixin/config/WxMaConfiguration.java | 37 +++ .../intc/weixin/config/WxMaProperties.java | 42 ++++ .../intc/weixin/config/WxMpConfiguration.java | 36 +++ .../intc/weixin/config/WxMpProperties.java | 37 +++ .../weixin/config/WxPayConfiguration.java | 39 ++++ .../intc/weixin/config/WxPayProperties.java | 52 +++++ .../weixin/controller/WeixinController.java | 109 +++++++++ .../java/com/intc/weixin/domain/WxUser.java | 74 ++++++ .../com/intc/weixin/service/WxMaService.java | 31 +++ .../com/intc/weixin/service/WxMpService.java | 31 +++ .../weixin/service/impl/WxMaServiceImpl.java | 38 ++++ .../weixin/service/impl/WxMpServiceImpl.java | 39 ++++ .../src/main/resources/application.yml | 42 ++++ intc-modules/pom.xml | 2 + 38 files changed, 2099 insertions(+), 49 deletions(-) create mode 100644 intc-modules/intc-iot/pom.xml create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/config/AliyunIotConfiguration.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/config/AliyunIotProperties.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/config/MqttConfiguration.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/controller/IotController.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/domain/IotDevice.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/service/DeviceDataService.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/service/IotDeviceService.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/service/MqttService.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/DeviceDataServiceImpl.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/IotDeviceServiceImpl.java create mode 100644 intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/MqttServiceImpl.java create mode 100644 intc-modules/intc-iot/src/main/resources/application.yml create mode 100644 intc-modules/intc-weixin/pom.xml create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaConfiguration.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaProperties.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMpConfiguration.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMpProperties.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxPayConfiguration.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxPayProperties.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/controller/WeixinController.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/domain/WxUser.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/WxMaService.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/WxMpService.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/WxMaServiceImpl.java create mode 100644 intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/WxMpServiceImpl.java create mode 100644 intc-modules/intc-weixin/src/main/resources/application.yml diff --git a/intc-admin/pom.xml b/intc-admin/pom.xml index 99e51cd..54c3ee2 100644 --- a/intc-admin/pom.xml +++ b/intc-admin/pom.xml @@ -22,6 +22,11 @@ com.mysql mysql-connector-j + + com.taosdata.jdbc + taos-jdbcdriver + 2.0.38 + diff --git a/intc-admin/src/main/resources/application-dev.yml b/intc-admin/src/main/resources/application-dev.yml index 15c5052..8f93b73 100644 --- a/intc-admin/src/main/resources/application-dev.yml +++ b/intc-admin/src/main/resources/application-dev.yml @@ -54,14 +54,20 @@ spring: url: jdbc:postgresql://154.8.147.51:15432/fishery_dev?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true username: postgres password: intc@123987 -# # 从库数据源 -# slave: -# lazy: true -# type: ${spring.datasource.type} -# driverClassName: com.taosdata.jdbc.rs.RestfulDriver -# url: jdbc:TAOS-RS://117.72.197.29:6041/log?timezone=Shanghai&charset=UTF-8&locale=en_US.UTF-8 -# username: root -# password: taosdata + # 从库数据源 - TDengine + taos: + lazy: false + type: ${spring.datasource.type} + driverClassName: com.taosdata.jdbc.rs.RestfulDriver + # 不指定数据库名,在 SQL 中使用完整路径 fishery.table_name + url: jdbc:TAOS-RS://154.8.147.51:6041?timezone=Shanghai&charset=UTF-8&locale=en_US.UTF-8 + username: root + password: intc@123456 + hikari: + connection-timeout: 60000 + validation-timeout: 10000 + max-pool-size: 5 + min-idle: 2 # oracle: # type: ${spring.datasource.type} # driverClassName: oracle.jdbc.OracleDriver diff --git a/intc-modules/intc-iot/pom.xml b/intc-modules/intc-iot/pom.xml new file mode 100644 index 0000000..5f33d62 --- /dev/null +++ b/intc-modules/intc-iot/pom.xml @@ -0,0 +1,91 @@ + + + + com.intc + intc-modules + ${revision} + + 4.0.0 + + intc-iot + + + 阿里云生活物联网平台(飞燕平台)对接模块 + + + + + + + com.aliyun + aliyun-java-sdk-core + 4.6.4 + + + + + com.aliyun + aliyun-java-sdk-iot + 7.46.0 + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + + com.intc + intc-common-core + + + + com.intc + intc-common-doc + + + + com.intc + intc-common-redis + + + + com.intc + intc-common-mybatis + + + + com.intc + intc-common-log + + + + com.intc + intc-common-security + + + + com.intc + intc-common-web + + + + com.intc + intc-common-tenant + + + + + diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/config/AliyunIotConfiguration.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/config/AliyunIotConfiguration.java new file mode 100644 index 0000000..a8af4d3 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/config/AliyunIotConfiguration.java @@ -0,0 +1,41 @@ +package com.intc.iot.config; + +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.profile.DefaultProfile; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 阿里云生活物联网平台(飞燕平台)配置 + * + * @author intc + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "aliyun.living-iot", name = "access-key-id") +public class AliyunIotConfiguration { + + private final AliyunIotProperties iotProperties; + + /** + * 创建阿里云飞燕平台客户端 + */ + @Bean + public IAcsClient livingIotClient() { + DefaultProfile profile = DefaultProfile.getProfile( + iotProperties.getRegionId(), + iotProperties.getAccessKeyId(), + iotProperties.getAccessKeySecret() + ); + + IAcsClient client = new DefaultAcsClient(profile); + log.info("阿里云生活物联网平台客户端初始化成功,RegionId: {}", iotProperties.getRegionId()); + return client; + } + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/config/AliyunIotProperties.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/config/AliyunIotProperties.java new file mode 100644 index 0000000..475a573 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/config/AliyunIotProperties.java @@ -0,0 +1,100 @@ +package com.intc.iot.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 阿里云生活物联网平台(飞燕平台)配置属性 + * + * @author intc + */ +@Data +@Component +@ConfigurationProperties(prefix = "aliyun.living-iot") +public class AliyunIotProperties { + + /** + * 阿里云AccessKey ID + */ + private String accessKeyId; + + /** + * 阿里云AccessKey Secret + */ + private String accessKeySecret; + + /** + * 地域节点(如:cn-shanghai) + */ + private String regionId; + + /** + * 飞燕平台项目ID(Project ID) + */ + private String projectId; + + /** + * App Key + */ + private String appKey; + + /** + * App Secret + */ + private String appSecret; + + /** + * 品类Key + */ + private String categoryKey; + + /** + * MQTT 配置 + */ + private MqttConfig mqtt = new MqttConfig(); + + @Data + public static class MqttConfig { + /** + * MQTT Broker 地址 + */ + private String brokerUrl; + + /** + * 客户端ID + */ + private String clientId; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 连接超时时间(秒) + */ + private Integer connectionTimeout = 30; + + /** + * 保活时间(秒) + */ + private Integer keepAliveInterval = 60; + + /** + * 自动重连 + */ + private Boolean autoReconnect = true; + + /** + * 清除会话 + */ + private Boolean cleanSession = true; + } + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/config/MqttConfiguration.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/config/MqttConfiguration.java new file mode 100644 index 0000000..e768c07 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/config/MqttConfiguration.java @@ -0,0 +1,49 @@ +package com.intc.iot.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * MQTT 客户端配置 + * + * @author intc + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "aliyun.living-iot.mqtt", name = "broker-url") +public class MqttConfiguration { + + private final AliyunIotProperties iotProperties; + + /** + * 创建 MQTT 客户端 + */ + @Bean + public MqttClient mqttClient() throws Exception { + AliyunIotProperties.MqttConfig mqtt = iotProperties.getMqtt(); + + MemoryPersistence persistence = new MemoryPersistence(); + MqttClient client = new MqttClient(mqtt.getBrokerUrl(), mqtt.getClientId(), persistence); + + MqttConnectOptions options = new MqttConnectOptions(); + options.setUserName(mqtt.getUsername()); + options.setPassword(mqtt.getPassword().toCharArray()); + options.setConnectionTimeout(mqtt.getConnectionTimeout()); + options.setKeepAliveInterval(mqtt.getKeepAliveInterval()); + options.setAutomaticReconnect(mqtt.getAutoReconnect()); + options.setCleanSession(mqtt.getCleanSession()); + + client.connect(options); + log.info("MQTT 客户端连接成功,Broker: {}", mqtt.getBrokerUrl()); + + return client; + } + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/controller/IotController.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/controller/IotController.java new file mode 100644 index 0000000..ef79aac --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/controller/IotController.java @@ -0,0 +1,212 @@ +package com.intc.iot.controller; + +import com.intc.common.core.domain.R; +import com.intc.common.web.core.BaseController; +import com.intc.iot.service.DeviceDataService; +import com.intc.iot.service.IotDeviceService; +import com.intc.iot.service.MqttService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 阿里云生活物联网平台(飞燕平台)控制器 + * + * @author intc + */ +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/iot") +@Tag(name = "生活物联网平台管理", description = "阿里云飞燕平台对接接口") +public class IotController extends BaseController { + + @Autowired(required = false) + private IotDeviceService iotDeviceService; + + @Autowired(required = false) + private MqttService mqttService; + + @Autowired(required = false) + private DeviceDataService deviceDataService; + + @Operation(summary = "测试接口") + @GetMapping("/test") + public R test() { + return R.ok("飞燕平台模块测试成功!"); + } + + @Operation(summary = "查询设备列表") + @GetMapping("/device/list") + public R> queryDeviceList( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo, + @Parameter(description = "每页大小") @RequestParam(defaultValue = "20") Integer pageSize) { + try { + if (iotDeviceService == null) { + return R.fail("飞燕平台配置未启用"); + } + Map response = iotDeviceService.queryDeviceList(pageNo, pageSize); + return R.ok(response); + } catch (Exception e) { + log.error("查询设备列表失败", e); + return R.fail("查询设备列表失败: " + e.getMessage()); + } + } + + @Operation(summary = "查询设备详情") + @GetMapping("/device/info") + public R> queryDeviceInfo( + @Parameter(description = "设备ID") @RequestParam String iotId) { + try { + if (iotDeviceService == null) { + return R.fail("飞燕平台配置未启用"); + } + Map response = iotDeviceService.queryDeviceInfo(iotId); + return R.ok(response); + } catch (Exception e) { + log.error("查询设备详情失败", e); + return R.fail("查询设备详情失败: " + e.getMessage()); + } + } + + @Operation(summary = "查询设备属性") + @GetMapping("/device/properties") + public R> queryDeviceProperties( + @Parameter(description = "设备ID") @RequestParam String iotId) { + try { + if (iotDeviceService == null) { + return R.fail("飞燕平台配置未启用"); + } + Map response = iotDeviceService.queryDeviceProperties(iotId); + return R.ok(response); + } catch (Exception e) { + log.error("查询设备属性失败", e); + return R.fail("查询设备属性失败: " + e.getMessage()); + } + } + + @Operation(summary = "设置设备属性") + @PostMapping("/device/property/set") + public R> setDeviceProperty( + @Parameter(description = "设备ID") @RequestParam String iotId, + @Parameter(description = "属性JSON") @RequestParam String properties) { + try { + if (iotDeviceService == null) { + return R.fail("飞燕平台配置未启用"); + } + Map response = iotDeviceService.setDeviceProperty(iotId, properties); + return R.ok(response); + } catch (Exception e) { + log.error("设置设备属性失败", e); + return R.fail("设置设备属性失败: " + e.getMessage()); + } + } + + @Operation(summary = "调用设备服务") + @PostMapping("/device/invoke") + public R> invokeService( + @Parameter(description = "设备ID") @RequestParam String iotId, + @Parameter(description = "服务标识符") @RequestParam String identifier, + @Parameter(description = "参数JSON") @RequestParam String args) { + try { + if (iotDeviceService == null) { + return R.fail("飞燕平台配置未启用"); + } + Map response = iotDeviceService.invokeService(iotId, identifier, args); + return R.ok(response); + } catch (Exception e) { + log.error("调用设备服务失败", e); + return R.fail("调用设备服务失败: " + e.getMessage()); + } + } + + @Operation(summary = "解绑设备") + @DeleteMapping("/device/unbind") + public R> unbindDevice( + @Parameter(description = "设备ID") @RequestParam String iotId) { + try { + if (iotDeviceService == null) { + return R.fail("飞燕平台配置未启用"); + } + Map response = iotDeviceService.unbindDevice(iotId); + return R.ok(response); + } catch (Exception e) { + log.error("解绑设备失败", e); + return R.fail("解绑设备失败: " + e.getMessage()); + } + } + + @Operation(summary = "发布MQTT消息") + @PostMapping("/mqtt/publish") + public R publishMqtt( + @Parameter(description = "主题") @RequestParam String topic, + @Parameter(description = "消息内容") @RequestParam String payload, + @Parameter(description = "QoS等级") @RequestParam(defaultValue = "1") int qos) { + try { + if (mqttService == null) { + return R.fail("MQTT配置未启用"); + } + mqttService.publish(topic, payload, qos); + return R.ok("消息发布成功"); + } catch (Exception e) { + log.error("发布MQTT消息失败", e); + return R.fail("发布MQTT消息失败: " + e.getMessage()); + } + } + + @Operation(summary = "订阅MQTT主题") + @PostMapping("/mqtt/subscribe") + public R subscribeMqtt( + @Parameter(description = "主题") @RequestParam String topic, + @Parameter(description = "QoS等级") @RequestParam(defaultValue = "1") int qos) { + try { + if (mqttService == null) { + return R.fail("MQTT配置未启用"); + } + mqttService.subscribe(topic, qos); + return R.ok("订阅成功"); + } catch (Exception e) { + log.error("订阅MQTT主题失败", e); + return R.fail("订阅MQTT主题失败: " + e.getMessage()); + } + } + + @Operation(summary = "取消订阅MQTT主题") + @PostMapping("/mqtt/unsubscribe") + public R unsubscribeMqtt( + @Parameter(description = "主题") @RequestParam String topic) { + try { + if (mqttService == null) { + return R.fail("MQTT配置未启用"); + } + mqttService.unsubscribe(topic); + return R.ok("取消订阅成功"); + } catch (Exception e) { + log.error("取消订阅MQTT主题失败", e); + return R.fail("取消订阅MQTT主题失败: " + e.getMessage()); + } + } + + @Operation(summary = "订阅设备实时数据(按产品)") + @PostMapping("/device/data/subscribe") + public R subscribeDeviceData( + @Parameter(description = "产品Key") @RequestParam String productKey) { + try { + if (deviceDataService == null) { + return R.fail("设备数据服务未启用"); + } + deviceDataService.subscribeAllDevices(productKey); + return R.ok("订阅成功,设备数据将实时推送"); + } catch (Exception e) { + log.error("订阅设备数据失败", e); + return R.fail("订阅设备数据失败: " + e.getMessage()); + } + } + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/IotDevice.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/IotDevice.java new file mode 100644 index 0000000..231b7b3 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/IotDevice.java @@ -0,0 +1,69 @@ +package com.intc.iot.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 物联网设备信息 + * + * @author intc + */ +@Data +@TableName("iot_device") +public class IotDevice implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId + private Long id; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 产品Key + */ + private String productKey; + + /** + * 设备密钥 + */ + private String deviceSecret; + + /** + * 设备状态:0-未激活 1-在线 2-离线 3-已禁用 + */ + private Integer status; + + /** + * 设备备注 + */ + private String remark; + + /** + * 最后上线时间 + */ + private LocalDateTime lastOnlineTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java new file mode 100644 index 0000000..f70d590 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java @@ -0,0 +1,134 @@ +package com.intc.iot.handler; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 设备数据处理器 + * + * @author intc + */ +@Slf4j +@Component +public class DeviceDataHandler { + + /** + * 处理设备属性上报数据 + * + * @param topic 主题 + * @param payload 消息内容 + */ + public void handlePropertyPost(String topic, String payload) { + log.info("收到设备属性上报,Topic: {}", topic); + + try { + JSONObject data = JSONUtil.parseObj(payload); + + // 解析飞燕平台消息格式 + String method = data.getStr("method"); + String id = data.getStr("id"); + JSONObject params = data.getJSONObject("params"); + + log.info("设备属性数据 - Method: {}, ID: {}, Params: {}", method, id, params); + + // TODO: 这里添加您的业务逻辑 + // 1. 存储到数据库 + // 2. 触发告警 + // 3. 推送到前端(通过 WebSocket/SSE) + // 4. 数据分析处理 + + if (params != null) { + params.forEach((key, value) -> { + log.info("属性: {} = {}", key, value); + // 根据不同属性进行不同处理 + handleProperty(key, value); + }); + } + + } catch (Exception e) { + log.error("处理设备属性数据失败", e); + } + } + + /** + * 处理设备事件上报数据 + * + * @param topic 主题 + * @param payload 消息内容 + */ + public void handleEventPost(String topic, String payload) { + log.info("收到设备事件上报,Topic: {}", topic); + + try { + JSONObject data = JSONUtil.parseObj(payload); + + String method = data.getStr("method"); + String id = data.getStr("id"); + JSONObject params = data.getJSONObject("params"); + + log.info("设备事件数据 - Method: {}, ID: {}, Params: {}", method, id, params); + + // TODO: 处理设备事件 + // 例如:故障告警、状态变化等 + + } catch (Exception e) { + log.error("处理设备事件数据失败", e); + } + } + + /** + * 处理单个属性 + * + * @param propertyName 属性名称 + * @param propertyValue 属性值 + */ + private void handleProperty(String propertyName, Object propertyValue) { + // 根据属性名称进行不同的业务处理 + switch (propertyName) { + case "temperature": + handleTemperature(propertyValue); + break; + case "humidity": + handleHumidity(propertyValue); + break; + case "status": + handleStatus(propertyValue); + break; + default: + log.debug("未处理的属性: {} = {}", propertyName, propertyValue); + } + } + + /** + * 处理温度数据 + */ + private void handleTemperature(Object value) { + // 示例:温度告警 + if (value instanceof Number) { + double temp = ((Number) value).doubleValue(); + if (temp > 80) { + log.warn("温度过高告警: {}°C", temp); + // TODO: 发送告警通知 + } + } + } + + /** + * 处理湿度数据 + */ + private void handleHumidity(Object value) { + // 示例:湿度处理逻辑 + log.debug("湿度数据: {}", value); + } + + /** + * 处理状态数据 + */ + private void handleStatus(Object value) { + // 示例:设备状态变化 + log.info("设备状态变化: {}", value); + } + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/DeviceDataService.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/DeviceDataService.java new file mode 100644 index 0000000..ec975c5 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/DeviceDataService.java @@ -0,0 +1,42 @@ +package com.intc.iot.service; + +/** + * 设备数据服务接口 + * + * @author intc + */ +public interface DeviceDataService { + + /** + * 订阅设备属性上报 + * + * @param iotId 设备ID + * @throws Exception 异常 + */ + void subscribeDeviceProperties(String iotId) throws Exception; + + /** + * 订阅设备事件上报 + * + * @param iotId 设备ID + * @throws Exception 异常 + */ + void subscribeDeviceEvents(String iotId) throws Exception; + + /** + * 订阅所有设备数据 + * + * @param productKey 产品Key + * @throws Exception 异常 + */ + void subscribeAllDevices(String productKey) throws Exception; + + /** + * 取消订阅设备数据 + * + * @param iotId 设备ID + * @throws Exception 异常 + */ + void unsubscribeDevice(String iotId) throws Exception; + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/IotDeviceService.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/IotDeviceService.java new file mode 100644 index 0000000..3dd8aea --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/IotDeviceService.java @@ -0,0 +1,70 @@ +package com.intc.iot.service; + +import java.util.Map; + +/** + * 阿里云生活物联网平台(飞燕平台)设备管理服务 + * + * @author intc + */ +public interface IotDeviceService { + + /** + * 查询设备列表 + * + * @param pageNo 页码 + * @param pageSize 每页大小 + * @return 设备列表 + * @throws Exception 异常 + */ + Map queryDeviceList(Integer pageNo, Integer pageSize) throws Exception; + + /** + * 查询设备详情 + * + * @param iotId 设备ID + * @return 设备详情 + * @throws Exception 异常 + */ + Map queryDeviceInfo(String iotId) throws Exception; + + /** + * 查询设备属性 + * + * @param iotId 设备ID + * @return 设备属性 + * @throws Exception 异常 + */ + Map queryDeviceProperties(String iotId) throws Exception; + + /** + * 设置设备属性 + * + * @param iotId 设备ID + * @param properties 属性JSON + * @return 设置结果 + * @throws Exception 异常 + */ + Map setDeviceProperty(String iotId, String properties) throws Exception; + + /** + * 调用设备服务 + * + * @param iotId 设备ID + * @param identifier 服务标识符 + * @param args 参数 + * @return 调用结果 + * @throws Exception 异常 + */ + Map invokeService(String iotId, String identifier, String args) throws Exception; + + /** + * 解绑设备 + * + * @param iotId 设备ID + * @return 解绑结果 + * @throws Exception 异常 + */ + Map unbindDevice(String iotId) throws Exception; + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/MqttService.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/MqttService.java new file mode 100644 index 0000000..8b48a9b --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/MqttService.java @@ -0,0 +1,37 @@ +package com.intc.iot.service; + +/** + * MQTT 消息服务 + * + * @author intc + */ +public interface MqttService { + + /** + * 发布消息 + * + * @param topic 主题 + * @param payload 消息内容 + * @param qos 服务质量等级(0/1/2) + * @throws Exception 异常 + */ + void publish(String topic, String payload, int qos) throws Exception; + + /** + * 订阅主题 + * + * @param topic 主题 + * @param qos 服务质量等级(0/1/2) + * @throws Exception 异常 + */ + void subscribe(String topic, int qos) throws Exception; + + /** + * 取消订阅 + * + * @param topic 主题 + * @throws Exception 异常 + */ + void unsubscribe(String topic) throws Exception; + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/DeviceDataServiceImpl.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/DeviceDataServiceImpl.java new file mode 100644 index 0000000..3deaaeb --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/DeviceDataServiceImpl.java @@ -0,0 +1,85 @@ +package com.intc.iot.service.impl; + +import cn.hutool.json.JSONUtil; +import com.intc.iot.service.DeviceDataService; +import com.intc.iot.service.MqttService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +/** + * 设备数据服务实现 + * + * @author intc + */ +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnBean(MqttClient.class) +public class DeviceDataServiceImpl implements DeviceDataService { + + private final MqttService mqttService; + + /** + * 飞燕平台设备属性上报 Topic 格式 + * /sys/{productKey}/{deviceName}/thing/event/property/post + */ + private static final String PROPERTY_POST_TOPIC = "/sys/%s/%s/thing/event/property/post"; + + /** + * 飞燕平台设备事件上报 Topic 格式 + * /sys/{productKey}/{deviceName}/thing/event/{eventIdentifier}/post + */ + private static final String EVENT_POST_TOPIC = "/sys/%s/%s/thing/event/+/post"; + + /** + * 所有设备属性上报 Topic(使用通配符) + * /sys/{productKey}/+/thing/event/property/post + */ + private static final String ALL_DEVICES_PROPERTY_TOPIC = "/sys/%s/+/thing/event/property/post"; + + @Override + public void subscribeDeviceProperties(String iotId) throws Exception { + // 注意:需要根据 iotId 获取 productKey 和 deviceName + // 这里简化处理,实际应该从数据库或缓存中查询 + log.info("订阅设备属性上报,IotId: {}", iotId); + + // 示例:假设从设备信息中获取 + // String productKey = getProductKeyByIotId(iotId); + // String deviceName = getDeviceNameByIotId(iotId); + // String topic = String.format(PROPERTY_POST_TOPIC, productKey, deviceName); + + // mqttService.subscribe(topic, 1); + + log.warn("请先实现 iotId 到 productKey/deviceName 的映射逻辑"); + } + + @Override + public void subscribeDeviceEvents(String iotId) throws Exception { + log.info("订阅设备事件上报,IotId: {}", iotId); + + // 类似属性订阅,需要映射关系 + log.warn("请先实现 iotId 到 productKey/deviceName 的映射逻辑"); + } + + @Override + public void subscribeAllDevices(String productKey) throws Exception { + log.info("订阅产品下所有设备属性上报,ProductKey: {}", productKey); + + String topic = String.format(ALL_DEVICES_PROPERTY_TOPIC, productKey); + mqttService.subscribe(topic, 1); + + log.info("成功订阅 Topic: {}", topic); + } + + @Override + public void unsubscribeDevice(String iotId) throws Exception { + log.info("取消订阅设备数据,IotId: {}", iotId); + + // 需要取消对应的 Topic 订阅 + log.warn("请先实现 iotId 到 productKey/deviceName 的映射逻辑"); + } + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/IotDeviceServiceImpl.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/IotDeviceServiceImpl.java new file mode 100644 index 0000000..7e194d7 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/IotDeviceServiceImpl.java @@ -0,0 +1,125 @@ +package com.intc.iot.service.impl; + +import com.aliyuncs.IAcsClient; +import com.aliyuncs.iot.model.v20180120.*; +import com.intc.iot.config.AliyunIotProperties; +import com.intc.iot.service.IotDeviceService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * 阿里云生活物联网平台(飞燕平台)设备管理服务实现 + * + * @author intc + */ +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnBean(IAcsClient.class) +public class IotDeviceServiceImpl implements IotDeviceService { + + private final IAcsClient acsClient; + private final AliyunIotProperties iotProperties; + + @Override + public Map queryDeviceList(Integer pageNo, Integer pageSize) throws Exception { + log.info("查询设备列表,页码: {}, 每页大小: {}", pageNo, pageSize); + + QueryDeviceRequest request = new QueryDeviceRequest(); + request.setCurrentPage(pageNo); + request.setPageSize(pageSize); + + QueryDeviceResponse response = acsClient.getAcsResponse(request); + + Map result = new HashMap<>(); + result.put("success", response.getSuccess()); + result.put("data", response.getData()); + result.put("total", response.getTotal()); + return result; + } + + @Override + public Map queryDeviceInfo(String iotId) throws Exception { + log.info("查询设备详情,IotId: {}", iotId); + + QueryDeviceDetailRequest request = new QueryDeviceDetailRequest(); + request.setIotId(iotId); + + QueryDeviceDetailResponse response = acsClient.getAcsResponse(request); + + Map result = new HashMap<>(); + result.put("success", response.getSuccess()); + result.put("data", response.getData()); + return result; + } + + @Override + public Map queryDeviceProperties(String iotId) throws Exception { + log.info("查询设备属性,IotId: {}", iotId); + + QueryDevicePropertyStatusRequest request = new QueryDevicePropertyStatusRequest(); + request.setIotId(iotId); + + QueryDevicePropertyStatusResponse response = acsClient.getAcsResponse(request); + + Map result = new HashMap<>(); + result.put("success", response.getSuccess()); + result.put("data", response.getData()); + return result; + } + + @Override + public Map setDeviceProperty(String iotId, String properties) throws Exception { + log.info("设置设备属性,IotId: {}, Properties: {}", iotId, properties); + + SetDevicePropertyRequest request = new SetDevicePropertyRequest(); + request.setIotId(iotId); + request.setItems(properties); + + SetDevicePropertyResponse response = acsClient.getAcsResponse(request); + + Map result = new HashMap<>(); + result.put("success", response.getSuccess()); + result.put("data", response.getData()); + return result; + } + + @Override + public Map invokeService(String iotId, String identifier, String args) throws Exception { + log.info("调用设备服务,IotId: {}, Identifier: {}", iotId, identifier); + + InvokeThingServiceRequest request = new InvokeThingServiceRequest(); + request.setIotId(iotId); + request.setIdentifier(identifier); + request.setArgs(args); + + InvokeThingServiceResponse response = acsClient.getAcsResponse(request); + + Map result = new HashMap<>(); + result.put("success", response.getSuccess()); + result.put("data", response.getData()); + return result; + } + + @Override + public Map unbindDevice(String iotId) throws Exception { + log.info("解绑设备,IotId: {}", iotId); + + // 注: 通用IoT SDK没有直接的解绑接口,这里使用删除设备 + DeleteDeviceRequest request = new DeleteDeviceRequest(); + request.setIotId(iotId); + + DeleteDeviceResponse response = acsClient.getAcsResponse(request); + + Map result = new HashMap<>(); + result.put("success", response.getSuccess()); + result.put("errorMessage", response.getErrorMessage()); + return result; + } + +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/MqttServiceImpl.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/MqttServiceImpl.java new file mode 100644 index 0000000..0896639 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/MqttServiceImpl.java @@ -0,0 +1,87 @@ +package com.intc.iot.service.impl; + +import com.intc.iot.handler.DeviceDataHandler; +import com.intc.iot.service.MqttService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.IMqttMessageListener; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +/** + * MQTT 消息服务实现 + * + * @author intc + */ +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnBean(MqttClient.class) +public class MqttServiceImpl implements MqttService { + + private final MqttClient mqttClient; + + @Autowired(required = false) + private DeviceDataHandler deviceDataHandler; + + @Override + public void publish(String topic, String payload, int qos) throws Exception { + if (!mqttClient.isConnected()) { + log.warn("MQTT 客户端未连接,尝试重连..."); + mqttClient.reconnect(); + } + + MqttMessage message = new MqttMessage(payload.getBytes()); + message.setQos(qos); + message.setRetained(false); + + mqttClient.publish(topic, message); + log.info("MQTT 消息发布成功,Topic: {}, Payload: {}", topic, payload); + } + + @Override + public void subscribe(String topic, int qos) throws Exception { + if (!mqttClient.isConnected()) { + log.warn("MQTT 客户端未连接,尝试重连..."); + mqttClient.reconnect(); + } + + mqttClient.subscribe(topic, qos, new IMqttMessageListener() { + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + String payload = new String(message.getPayload()); + log.info("收到 MQTT 消息,Topic: {}, Payload: {}", topic, payload); + + // 根据 Topic 类型分发处理 + if (deviceDataHandler != null) { + if (topic.contains("/thing/event/property/post")) { + // 设备属性上报 + deviceDataHandler.handlePropertyPost(topic, payload); + } else if (topic.contains("/thing/event/") && topic.endsWith("/post")) { + // 设备事件上报 + deviceDataHandler.handleEventPost(topic, payload); + } else { + log.debug("未匹配的 Topic 类型: {}", topic); + } + } + } + }); + + log.info("MQTT 主题订阅成功,Topic: {}", topic); + } + + @Override + public void unsubscribe(String topic) throws Exception { + if (!mqttClient.isConnected()) { + log.warn("MQTT 客户端未连接"); + return; + } + + mqttClient.unsubscribe(topic); + log.info("MQTT 主题取消订阅成功,Topic: {}", topic); + } + +} diff --git a/intc-modules/intc-iot/src/main/resources/application.yml b/intc-modules/intc-iot/src/main/resources/application.yml new file mode 100644 index 0000000..f81482f --- /dev/null +++ b/intc-modules/intc-iot/src/main/resources/application.yml @@ -0,0 +1,36 @@ +# 阿里云生活物联网平台(飞燕平台)配置 +aliyun: + living-iot: + # 阿里云 AccessKey ID(必填) + access-key-id: LTAI5txxxxxxxxxxxxxxxxxx + # 阿里云 AccessKey Secret(必填) + access-key-secret: your_access_key_secret_here_32_chars + # 地域节点(必填,如:cn-shanghai) + region-id: cn-shanghai + # 飞燕平台项目ID(Project ID,必填) + project-id: a1xxxxxx + # App Key(必填) + app-key: your_app_key_here + # App Secret(必填) + app-secret: your_app_secret_here_32_characters + # 品类Key(选填) + category-key: + + # MQTT 配置(可选) + mqtt: + # MQTT Broker 地址(格式:ssl://实例ID.iot-as-mqtt.cn-shanghai.aliyuncs.com:1883) + broker-url: ssl://a1xxxxxx.iot-as-mqtt.cn-shanghai.aliyuncs.com:1883 + # 客户端ID(格式:{ClientID}|securemode=2,signmethod=hmacsha1|) + client-id: your_client_id|securemode=2,signmethod=hmacsha1| + # 用户名(设备名称&产品Key) + username: DeviceName&ProductKey + # 密码(通过MQTT密码工具生成) + password: your_mqtt_password_here + # 连接超时时间(秒) + connection-timeout: 30 + # 保活时间(秒) + keep-alive-interval: 60 + # 自动重连 + auto-reconnect: true + # 清除会话 + clean-session: true diff --git a/intc-modules/intc-tdengine/pom.xml b/intc-modules/intc-tdengine/pom.xml index b48b6d7..b8b4fe1 100644 --- a/intc-modules/intc-tdengine/pom.xml +++ b/intc-modules/intc-tdengine/pom.xml @@ -102,11 +102,6 @@ com.intc intc-common-websocket - - com.taosdata.jdbc - taos-jdbcdriver - 2.0.40 - diff --git a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/controller/DeviceSensorDataController.java b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/controller/DeviceSensorDataController.java index 2c4d377..20c3639 100644 --- a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/controller/DeviceSensorDataController.java +++ b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/controller/DeviceSensorDataController.java @@ -27,8 +27,11 @@ public class DeviceSensorDataController extends BaseController private IDeviceSensorDataService deviceSensorDataService; @GetMapping("/getHistoryData") - public List getHistoryData(@RequestParam("serialNum") String serialNum, @RequestParam("deviceId") Long deviceId, @RequestParam("mobilePhone") String mobilePhone, @RequestParam("deviceType") int deviceType, @RequestParam("startTime") String startTime, @RequestParam("endTime") String endTime) + public List getHistoryData(@RequestParam("serialNum") String serialNum, + @RequestParam("startTime") String startTime, + @RequestParam("endTime") String endTime, + @RequestParam(value = "intervalType", required = false, defaultValue = "1") Integer intervalType) { - return deviceSensorDataService.getHistoryDataList(serialNum,deviceId,mobilePhone,deviceType,startTime,endTime); + return deviceSensorDataService.getHistoryDataList(serialNum, startTime, endTime, intervalType); } } diff --git a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/domain/DeviceSensorData.java b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/domain/DeviceSensorData.java index c6d36d1..3a7bd16 100644 --- a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/domain/DeviceSensorData.java +++ b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/domain/DeviceSensorData.java @@ -1,8 +1,12 @@ package com.intc.tdengine.domain; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDateTime; @Data @NoArgsConstructor // 生成无参构造方法 @@ -10,17 +14,64 @@ import java.time.LocalDateTime; public class DeviceSensorData { // 测量字段(随时间变化的数值) - private LocalDateTime time; // 时序主键时间戳 - private LocalDateTime createTime; // 数据创建时间 - private double dissolvedOxygen; // 溶解氧 - private double temperature; // 温度 - private double saturability; // 饱和度 - private double ph; // pH值 - private double salinity; // 盐度 - private double treference; // 参考值(具体含义需结合业务) - private double tfluorescence; // 荧光值 - private double phaseDifference; // 相位差 - private double battery; // 电池电量 + private String time; // 时序主键时间戳 + private String createTime; // 数据创建时间 + private Double dissolvedOxygen; // 溶解氧 + private Double temperature; // 温度 + private Double saturability; // 饱和度 + private Double ph; // pH值 + private Double salinity; // 盐度 + private Double treference; // 参考值(具体含义需结合业务) + private Double tfluorescence; // 荧光值 + private Double phaseDifference; // 相位差 + private Double battery; // 电池电量 + + // Getter 方法,返回保留两位小数的值 + public Double getDissolvedOxygen() { + return roundToTwoDecimals(dissolvedOxygen); + } + + public Double getTemperature() { + return roundToTwoDecimals(temperature); + } + + public Double getSaturability() { + return roundToTwoDecimals(saturability); + } + + public Double getPh() { + return roundToTwoDecimals(ph); + } + + public Double getSalinity() { + return roundToTwoDecimals(salinity); + } + + public Double getTreference() { + return roundToTwoDecimals(treference); + } + + public Double getTfluorescence() { + return roundToTwoDecimals(tfluorescence); + } + + public Double getPhaseDifference() { + return roundToTwoDecimals(phaseDifference); + } + + public Double getBattery() { + return roundToTwoDecimals(battery); + } + + // 工具方法:保留两位小数 + private Double roundToTwoDecimals(Double value) { + if (value == null) { + return null; + } + return BigDecimal.valueOf(value) + .setScale(2, RoundingMode.HALF_UP) + .doubleValue(); + } // 标签字段(元数据,不随时间频繁变化) private String serialNum; // 设备序列号 diff --git a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/mapper/DeviceSensorDataMapper.java b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/mapper/DeviceSensorDataMapper.java index 7efe2d3..794c476 100644 --- a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/mapper/DeviceSensorDataMapper.java +++ b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/mapper/DeviceSensorDataMapper.java @@ -1,5 +1,7 @@ package com.intc.tdengine.mapper; +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import com.intc.tdengine.domain.DeviceSensorData; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -8,7 +10,6 @@ import org.springframework.stereotype.Repository; import java.util.List; @Repository -@Mapper public interface DeviceSensorDataMapper { /** @@ -46,13 +47,24 @@ public interface DeviceSensorDataMapper { * @param dataList 数据列表 * @return 影响行数 */ + @DS("taos") + @InterceptorIgnore(tenantLine = "true") int batchInsertDeviceSensorData(@Param("dataList") List dataList); /** * 查询数据 * - * @return 影响行数 + * @param serialNum 设备序列号 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param intervalType 间隔类型:1-原始数据,2-10分钟,3-30分钟,4-1小时,5-3小时,6-6小时 + * @return 设备传感器数据列表 */ - List getHistoryDataList(@Param("serialNum") String serialNum,@Param("deviceId") Long deviceId,@Param("mobilePhone") String mobilePhone,@Param("deviceType") int deviceType,@Param("startTime") String startTime,@Param("endTime") String endTime); + @DS("taos") + @InterceptorIgnore(tenantLine = "true") + List getHistoryDataList(@Param("serialNum") String serialNum, + @Param("startTime") String startTime, + @Param("endTime") String endTime, + @Param("intervalType") Integer intervalType); } diff --git a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/IDeviceSensorDataService.java b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/IDeviceSensorDataService.java index 26dde71..18976c9 100644 --- a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/IDeviceSensorDataService.java +++ b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/IDeviceSensorDataService.java @@ -29,9 +29,13 @@ public interface IDeviceSensorDataService { public void batchInsertDeviceSensorData(List dataList); /** - * 查询数据 + * 查询历史数据列表 * - * @return 影响行数 + * @param serialNum 设备序列号 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param intervalType 间隔类型:1-原始数据,2-10分钟,3-30分钟,4-1小时,5-3小时,6-6小时 + * @return 设备传感器数据列表 */ - public List getHistoryDataList(String serialNum, Long deviceId, String mobilePhone, int deviceType, String startTime, String endTime); + public List getHistoryDataList(String serialNum, String startTime, String endTime, Integer intervalType); } diff --git a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/impl/DeviceSensorDataService.java b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/impl/DeviceSensorDataService.java index 819de7f..bc62753 100644 --- a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/impl/DeviceSensorDataService.java +++ b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/impl/DeviceSensorDataService.java @@ -1,14 +1,19 @@ package com.intc.tdengine.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; import com.intc.tdengine.domain.DeviceSensorData; import com.intc.tdengine.mapper.DeviceSensorDataMapper; import com.intc.tdengine.service.IDeviceSensorDataService; import jakarta.annotation.Resource; -import org.apache.ibatis.annotations.Param; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; +@Slf4j +@Service +@DS("taos") // 指定使用 taos 数据源(TDengine) public class DeviceSensorDataService implements IDeviceSensorDataService { @Resource private DeviceSensorDataMapper deviceSensorDataMapper; @@ -37,18 +42,69 @@ public class DeviceSensorDataService implements IDeviceSensorDataService { } /** - * 查询数据 + * 查询历史数据列表 * - * @return 影响行数 + * @param serialNum 设备序列号 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param intervalType 间隔类型:1-原始数据,2-10分钟,3-30分钟,4-1小时,5-3小时,6-6小时 + * @return 设备传感器数据列表 */ @Override - public List getHistoryDataList(String serialNum,Long deviceId, String mobilePhone, int deviceType,String startTime, String endTime) { - List list=new ArrayList<>(); + public List getHistoryDataList(String serialNum, String startTime, String endTime, Integer intervalType) { + List list = new ArrayList<>(); try { - list=deviceSensorDataMapper.getHistoryDataList(serialNum,deviceId,mobilePhone,deviceType,startTime,endTime); - }catch (Exception e){ - + // 默认为原始数据 + if (intervalType == null) { + intervalType = 1; + } + + // 验证intervalType参数 + if (intervalType < 1 || intervalType > 6) { + log.error("无效的intervalType参数: {}, 使用默认值1", intervalType); + intervalType = 1; + } + + // 处理时间格式 + if (startTime != null && !startTime.contains(" ")) { + startTime = startTime + " 00:00:00"; + } + if (endTime != null && !endTime.contains(" ")) { + endTime = endTime + " 23:59:59"; + } + + String intervalDesc = getIntervalDesc(intervalType); + log.info("查询TDengine历史数据: serialNum={}, startTime={}, endTime={}, intervalType={} ({})", + serialNum, startTime, endTime, intervalType, intervalDesc); + + list = deviceSensorDataMapper.getHistoryDataList(serialNum, startTime, endTime, intervalType); + + log.info("查询到 {} 条历史数据 ({})", list.size(), intervalDesc); + } catch (Exception e) { + log.error("查询TDengine历史数据失败", e); + // 打印完整异常链 + Throwable cause = e; + int level = 0; + while (cause != null && level < 10) { + log.error("异常层级 {}: {}", level++, cause.getClass().getName() + ": " + cause.getMessage()); + cause = cause.getCause(); + } } return list; } + + /** + * 获取间隔类型描述 + */ + private String getIntervalDesc(Integer intervalType) { + switch (intervalType) { + case 1: return "原始数据"; + case 2: return "10分钟间隔"; + case 3: return "30分钟间隔"; + case 4: return "1小时间隔"; + case 5: return "3小时间隔"; + case 6: return "6小时间隔"; + default: return "未知"; + } + } } diff --git a/intc-modules/intc-tdengine/src/main/resources/mapper/tdengine/DeviceSensorDataMapper.xml b/intc-modules/intc-tdengine/src/main/resources/mapper/tdengine/DeviceSensorDataMapper.xml index 908ed59..518988c 100644 --- a/intc-modules/intc-tdengine/src/main/resources/mapper/tdengine/DeviceSensorDataMapper.xml +++ b/intc-modules/intc-tdengine/src/main/resources/mapper/tdengine/DeviceSensorDataMapper.xml @@ -52,14 +52,57 @@ - + + + SELECT + `time`, + createTime, + dissolvedOxygen, + temperature, + saturability, + ph, + salinity, + treference, + tfluorescence, + phaseDifference, + battery, + serialNum, + deviceId, + mobilePhone, + deviceType + FROM `fishery`.`t_${serialNum}` + + AND `time` >= #{startTime} + AND `time` <= #{endTime} + + + + + SELECT + _wstart as `time`, + _wstart as createTime, + AVG(dissolvedOxygen) as dissolvedOxygen, + AVG(temperature) as temperature, + AVG(saturability) as saturability, + AVG(ph) as ph, + AVG(salinity) as salinity, + AVG(treference) as treference, + AVG(tfluorescence) as tfluorescence, + AVG(phaseDifference) as phaseDifference, + AVG(battery) as battery + FROM `fishery`.`t_${serialNum}` + + AND `time` >= #{startTime} + AND `time` <= #{endTime} + + + INTERVAL(10m) + INTERVAL(30m) + INTERVAL(1h) + INTERVAL(3h) + INTERVAL(6h) + + diff --git a/intc-modules/intc-weixin/pom.xml b/intc-modules/intc-weixin/pom.xml new file mode 100644 index 0000000..90241a7 --- /dev/null +++ b/intc-modules/intc-weixin/pom.xml @@ -0,0 +1,88 @@ + + + + com.intc + intc-modules + ${revision} + + 4.0.0 + + intc-weixin + + + 微信对接模块 + + + + + + + com.github.binarywang + weixin-java-mp + 4.6.0 + + + + com.github.binarywang + weixin-java-miniapp + 4.6.0 + + + + com.github.binarywang + weixin-java-pay + 4.6.0 + + + + com.github.binarywang + weixin-java-open + 4.6.0 + + + + + com.intc + intc-common-core + + + + com.intc + intc-common-doc + + + + com.intc + intc-common-redis + + + + com.intc + intc-common-mybatis + + + + com.intc + intc-common-log + + + + com.intc + intc-common-security + + + + com.intc + intc-common-web + + + + com.intc + intc-common-tenant + + + + + diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaConfiguration.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaConfiguration.java new file mode 100644 index 0000000..3235874 --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaConfiguration.java @@ -0,0 +1,37 @@ +package com.intc.weixin.config; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 微信小程序配置 + * + * @author intc + */ +@Configuration +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "wx.miniapp", name = "app-id") +public class WxMaConfiguration { + + private final WxMaProperties wxMaProperties; + + @Bean + public WxMaService wxMaService() { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + config.setAppid(wxMaProperties.getAppId()); + config.setSecret(wxMaProperties.getSecret()); + config.setToken(wxMaProperties.getToken()); + config.setAesKey(wxMaProperties.getAesKey()); + config.setMsgDataFormat(wxMaProperties.getMsgDataFormat()); + + WxMaService service = new WxMaServiceImpl(); + service.setWxMaConfig(config); + return service; + } + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaProperties.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaProperties.java new file mode 100644 index 0000000..3e77490 --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaProperties.java @@ -0,0 +1,42 @@ +package com.intc.weixin.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 微信小程序配置属性 + * + * @author intc + */ +@Data +@Component +@ConfigurationProperties(prefix = "wx.miniapp") +public class WxMaProperties { + + /** + * 小程序appId + */ + private String appId; + + /** + * 小程序Secret + */ + private String secret; + + /** + * 小程序token + */ + private String token; + + /** + * 小程序EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON + */ + private String msgDataFormat; + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMpConfiguration.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMpConfiguration.java new file mode 100644 index 0000000..d9ca385 --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMpConfiguration.java @@ -0,0 +1,36 @@ +package com.intc.weixin.config; + +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 微信公众号配置 + * + * @author intc + */ +@Configuration +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "wx.mp", name = "app-id") +public class WxMpConfiguration { + + private final WxMpProperties wxMpProperties; + + @Bean + public WxMpService wxMpService() { + WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); + config.setAppId(wxMpProperties.getAppId()); + config.setSecret(wxMpProperties.getSecret()); + config.setToken(wxMpProperties.getToken()); + config.setAesKey(wxMpProperties.getAesKey()); + + WxMpService service = new WxMpServiceImpl(); + service.setWxMpConfigStorage(config); + return service; + } + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMpProperties.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMpProperties.java new file mode 100644 index 0000000..8fda6dc --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMpProperties.java @@ -0,0 +1,37 @@ +package com.intc.weixin.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 微信公众号配置属性 + * + * @author intc + */ +@Data +@Component +@ConfigurationProperties(prefix = "wx.mp") +public class WxMpProperties { + + /** + * 公众号appId + */ + private String appId; + + /** + * 公众号Secret + */ + private String secret; + + /** + * 公众号token + */ + private String token; + + /** + * 公众号EncodingAESKey + */ + private String aesKey; + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxPayConfiguration.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxPayConfiguration.java new file mode 100644 index 0000000..60f11b3 --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxPayConfiguration.java @@ -0,0 +1,39 @@ +package com.intc.weixin.config; + +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 微信支付配置 + * + * @author intc + */ +@Configuration +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "wx.pay", name = "mch-id") +public class WxPayConfiguration { + + private final WxPayProperties wxPayProperties; + + @Bean + public WxPayService wxPayService() { + WxPayConfig config = new WxPayConfig(); + config.setMchId(wxPayProperties.getMchId()); + config.setMchKey(wxPayProperties.getMchKey()); + config.setKeyPath(wxPayProperties.getKeyPath()); + config.setApiV3Key(wxPayProperties.getApiV3Key()); + config.setCertSerialNo(wxPayProperties.getCertSerialNo()); + config.setPrivateKeyPath(wxPayProperties.getPrivateKeyPath()); + config.setPrivateCertPath(wxPayProperties.getPrivateContent()); + + WxPayService service = new WxPayServiceImpl(); + service.setConfig(config); + return service; + } + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxPayProperties.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxPayProperties.java new file mode 100644 index 0000000..37a911f --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxPayProperties.java @@ -0,0 +1,52 @@ +package com.intc.weixin.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 微信支付配置属性 + * + * @author intc + */ +@Data +@Component +@ConfigurationProperties(prefix = "wx.pay") +public class WxPayProperties { + + /** + * 商户号 + */ + private String mchId; + + /** + * 商户密钥 + */ + private String mchKey; + + /** + * apiclient_cert.p12文件的绝对路径或者以classpath:开头的类路径 + */ + private String keyPath; + + /** + * apiV3秘钥 + */ + private String apiV3Key; + + /** + * 证书序列号 + */ + private String certSerialNo; + + /** + * 私钥路径 + */ + private String privateKeyPath; + + /** + * 私钥内容 + */ + private String privateContent; + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/controller/WeixinController.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/controller/WeixinController.java new file mode 100644 index 0000000..9764751 --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/controller/WeixinController.java @@ -0,0 +1,109 @@ +package com.intc.weixin.controller; + +import com.intc.common.core.domain.R; +import com.intc.common.web.core.BaseController; +import com.intc.weixin.service.WxMaService; +import com.intc.weixin.service.WxMpService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +/** + * 微信对接控制器 + * + * @author intc + */ +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/weixin") +@Tag(name = "微信对接管理", description = "微信对接相关接口") +public class WeixinController extends BaseController { + + @Autowired(required = false) + private WxMpService wxMpService; + + @Autowired(required = false) + private WxMaService wxMaService; + + @Operation(summary = "测试接口") + @GetMapping("/test") + public R test() { + return R.ok("微信模块测试成功!"); + } + + @Operation(summary = "获取公众号用户信息") + @GetMapping("/mp/user/{openId}") + public R getMpUserInfo( + @Parameter(description = "用户openId") @PathVariable String openId) { + try { + if (wxMpService == null) { + return R.fail("公众号配置未启用"); + } + WxMpUser userInfo = wxMpService.getUserInfo(openId); + return R.ok(userInfo); + } catch (WxErrorException e) { + log.error("获取用户信息失败", e); + return R.fail("获取用户信息失败: " + e.getMessage()); + } + } + + @Operation(summary = "生成公众号二维码") + @GetMapping("/mp/qrcode") + public R createMpQrCode( + @Parameter(description = "场景值") @RequestParam String scene) { + try { + if (wxMpService == null) { + return R.fail("公众号配置未启用"); + } + String qrCodeUrl = wxMpService.createQrCode(scene); + return R.ok(qrCodeUrl); + } catch (WxErrorException e) { + log.error("生成二维码失败", e); + return R.fail("生成二维码失败: " + e.getMessage()); + } + } + + @Operation(summary = "小程序登录") + @GetMapping("/ma/login") + public R maLogin( + @Parameter(description = "登录code") @RequestParam String code) { + try { + if (wxMaService == null) { + return R.fail("小程序配置未启用"); + } + String openId = wxMaService.code2Session(code); + return R.ok(openId); + } catch (WxErrorException e) { + log.error("小程序登录失败", e); + return R.fail("小程序登录失败: " + e.getMessage()); + } + } + + @Operation(summary = "生成小程序码") + @GetMapping("/ma/qrcode") + public R createMaQrCode( + @Parameter(description = "场景值") @RequestParam String scene, + @Parameter(description = "页面路径") @RequestParam(required = false) String page) { + try { + if (wxMaService == null) { + return R.fail("小程序配置未启用"); + } + byte[] qrCodeBytes = wxMaService.getUnlimitedQrCode(scene, page); + // 这里可以将字节数组上传到OSS或者直接返回给前端 + return R.ok("二维码生成成功,大小: " + qrCodeBytes.length + " bytes"); + } catch (WxErrorException e) { + log.error("生成小程序码失败", e); + return R.fail("生成小程序码失败: " + e.getMessage()); + } + } + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/domain/WxUser.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/domain/WxUser.java new file mode 100644 index 0000000..c45083a --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/domain/WxUser.java @@ -0,0 +1,74 @@ +package com.intc.weixin.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 微信用户信息 + * + * @author intc + */ +@Data +@TableName("wx_user") +public class WxUser implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId + private Long id; + + /** + * 微信openId + */ + private String openId; + + /** + * 微信unionId + */ + private String unionId; + + /** + * 昵称 + */ + private String nickname; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别 0-未知 1-男 2-女 + */ + private Integer gender; + + /** + * 手机号 + */ + private String mobile; + + /** + * 来源类型:mp-公众号 ma-小程序 + */ + private String sourceType; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/WxMaService.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/WxMaService.java new file mode 100644 index 0000000..8497a30 --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/WxMaService.java @@ -0,0 +1,31 @@ +package com.intc.weixin.service; + +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小程序服务接口 + * + * @author intc + */ +public interface WxMaService { + + /** + * 登录凭证校验 + * + * @param code 登录时获取的 code + * @return sessionKey和openId + * @throws WxErrorException 微信异常 + */ + String code2Session(String code) throws WxErrorException; + + /** + * 获取小程序码 + * + * @param scene 场景值 + * @param page 页面路径 + * @return 二进制图片数据 + * @throws WxErrorException 微信异常 + */ + byte[] getUnlimitedQrCode(String scene, String page) throws WxErrorException; + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/WxMpService.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/WxMpService.java new file mode 100644 index 0000000..f66544a --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/WxMpService.java @@ -0,0 +1,31 @@ +package com.intc.weixin.service; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.result.WxMpUser; + +/** + * 微信公众号服务接口 + * + * @author intc + */ +public interface WxMpService { + + /** + * 获取用户信息 + * + * @param openId 用户openId + * @return 用户信息 + * @throws WxErrorException 微信异常 + */ + WxMpUser getUserInfo(String openId) throws WxErrorException; + + /** + * 生成二维码ticket + * + * @param sceneStr 场景值 + * @return ticket + * @throws WxErrorException 微信异常 + */ + String createQrCode(String sceneStr) throws WxErrorException; + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/WxMaServiceImpl.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/WxMaServiceImpl.java new file mode 100644 index 0000000..1c27246 --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/WxMaServiceImpl.java @@ -0,0 +1,38 @@ +package com.intc.weixin.service.impl; + +import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import com.intc.weixin.service.WxMaService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +/** + * 微信小程序服务实现 + * + * @author intc + */ +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnBean(cn.binarywang.wx.miniapp.api.WxMaService.class) +public class WxMaServiceImpl implements WxMaService { + + private final cn.binarywang.wx.miniapp.api.WxMaService wxMaService; + + @Override + public String code2Session(String code) throws WxErrorException { + log.info("小程序登录, code: {}", code); + WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(code); + return session.getOpenid(); + } + + @Override + public byte[] getUnlimitedQrCode(String scene, String page) throws WxErrorException { + log.info("生成小程序码, scene: {}, page: {}", scene, page); + return wxMaService.getQrcodeService().createWxaCodeUnlimitBytes(scene, page, false, null, 430, true, null, false); + } + +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/WxMpServiceImpl.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/WxMpServiceImpl.java new file mode 100644 index 0000000..739bfad --- /dev/null +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/WxMpServiceImpl.java @@ -0,0 +1,39 @@ +package com.intc.weixin.service.impl; + +import com.intc.weixin.service.WxMpService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +/** + * 微信公众号服务实现 + * + * @author intc + */ +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnBean(me.chanjar.weixin.mp.api.WxMpService.class) +public class WxMpServiceImpl implements WxMpService { + + private final me.chanjar.weixin.mp.api.WxMpService wxMpService; + + @Override + public WxMpUser getUserInfo(String openId) throws WxErrorException { + log.info("获取微信用户信息, openId: {}", openId); + return wxMpService.getUserService().userInfo(openId); + } + + @Override + public String createQrCode(String sceneStr) throws WxErrorException { + log.info("创建微信二维码, sceneStr: {}", sceneStr); + WxMpQrCodeTicket ticket = wxMpService.getQrcodeService() + .qrCodeCreateTmpTicket(sceneStr, 2592000); + return ticket.getUrl(); + } + +} diff --git a/intc-modules/intc-weixin/src/main/resources/application.yml b/intc-modules/intc-weixin/src/main/resources/application.yml new file mode 100644 index 0000000..29f40dc --- /dev/null +++ b/intc-modules/intc-weixin/src/main/resources/application.yml @@ -0,0 +1,42 @@ +# 微信对接模块配置 +wx: + # 微信公众号配置 + mp: + # 公众号appId(必填) + app-id: wx1234567890abcdef + # 公众号Secret(必填) + secret: your_mp_secret_here_32_characters + # 公众号token(选填,用于消息加解密) + token: your_token_here + # 公众号EncodingAESKey(选填,用于消息加解密) + aes-key: your_aes_key_here_43_characters_base64 + + # 微信小程序配置 + miniapp: + # 小程序appId(必填) + app-id: wx0987654321fedcba + # 小程序Secret(必填) + secret: your_miniapp_secret_here_32_chars + # 小程序token(选填,用于消息加解密) + token: your_miniapp_token + # 小程序EncodingAESKey(选填,用于消息加解密) + aes-key: your_miniapp_aes_key_43_characters_b64 + # 消息格式,XML或者JSON + msg-data-format: JSON + + # 微信支付配置 + pay: + # 商户号(必填) + mch-id: 1234567890 + # 商户密钥(V2版本必填) + mch-key: your_mch_key_here_32_characters_md5 + # 证书路径(退款等操作需要,选填) + key-path: classpath:cert/apiclient_cert.p12 + # apiV3秘钥(V3版本必填) + api-v3-key: your_api_v3_key_here_32_characters + # 证书序列号(V3版本必填) + cert-serial-no: 1234567890ABCDEF1234567890ABCDEF12345678 + # 私钥路径(V3版本选填) + private-key-path: classpath:cert/apiclient_key.pem + # 私钥内容(V3版本选填,与private-key-path二选一) + private-content: diff --git a/intc-modules/pom.xml b/intc-modules/pom.xml index a99163b..9dee19d 100644 --- a/intc-modules/pom.xml +++ b/intc-modules/pom.xml @@ -17,6 +17,8 @@ intc-workflow intc-fishery intc-tdengine + intc-weixin + intc-iot intc-modules