feat: 新功能开发,监测历史记录。微信和物联网平台,模块搭建。
This commit is contained in:
@@ -22,6 +22,11 @@
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taosdata.jdbc</groupId>
|
||||
<artifactId>taos-jdbcdriver</artifactId>
|
||||
<version>2.0.38</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <!– mp支持的数据库均支持 只需要增加对应的jdbc依赖即可 –>-->
|
||||
<!-- <!– Oracle –>-->
|
||||
|
||||
@@ -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
|
||||
|
||||
91
intc-modules/intc-iot/pom.xml
Normal file
91
intc-modules/intc-iot/pom.xml
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>intc-iot</artifactId>
|
||||
|
||||
<description>
|
||||
阿里云生活物联网平台(飞燕平台)对接模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- 阿里云生活物联网平台 SDK -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||
<version>4.6.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云 IoT SDK -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-iot</artifactId>
|
||||
<version>7.46.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MQTT 客户端 -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.paho</groupId>
|
||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||
<version>1.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- HTTP 客户端 -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> test() {
|
||||
return R.ok("飞燕平台模块测试成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "查询设备列表")
|
||||
@GetMapping("/device/list")
|
||||
public R<Map<String, Object>> queryDeviceList(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNo,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "20") Integer pageSize) {
|
||||
try {
|
||||
if (iotDeviceService == null) {
|
||||
return R.fail("飞燕平台配置未启用");
|
||||
}
|
||||
Map<String, Object> 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<Map<String, Object>> queryDeviceInfo(
|
||||
@Parameter(description = "设备ID") @RequestParam String iotId) {
|
||||
try {
|
||||
if (iotDeviceService == null) {
|
||||
return R.fail("飞燕平台配置未启用");
|
||||
}
|
||||
Map<String, Object> 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<Map<String, Object>> queryDeviceProperties(
|
||||
@Parameter(description = "设备ID") @RequestParam String iotId) {
|
||||
try {
|
||||
if (iotDeviceService == null) {
|
||||
return R.fail("飞燕平台配置未启用");
|
||||
}
|
||||
Map<String, Object> 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<Map<String, Object>> setDeviceProperty(
|
||||
@Parameter(description = "设备ID") @RequestParam String iotId,
|
||||
@Parameter(description = "属性JSON") @RequestParam String properties) {
|
||||
try {
|
||||
if (iotDeviceService == null) {
|
||||
return R.fail("飞燕平台配置未启用");
|
||||
}
|
||||
Map<String, Object> 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<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> unbindDevice(
|
||||
@Parameter(description = "设备ID") @RequestParam String iotId) {
|
||||
try {
|
||||
if (iotDeviceService == null) {
|
||||
return R.fail("飞燕平台配置未启用");
|
||||
}
|
||||
Map<String, Object> 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<String> 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<String> 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<String> 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<String> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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<String, Object> queryDeviceList(Integer pageNo, Integer pageSize) throws Exception;
|
||||
|
||||
/**
|
||||
* 查询设备详情
|
||||
*
|
||||
* @param iotId 设备ID
|
||||
* @return 设备详情
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
Map<String, Object> queryDeviceInfo(String iotId) throws Exception;
|
||||
|
||||
/**
|
||||
* 查询设备属性
|
||||
*
|
||||
* @param iotId 设备ID
|
||||
* @return 设备属性
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
Map<String, Object> queryDeviceProperties(String iotId) throws Exception;
|
||||
|
||||
/**
|
||||
* 设置设备属性
|
||||
*
|
||||
* @param iotId 设备ID
|
||||
* @param properties 属性JSON
|
||||
* @return 设置结果
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
Map<String, Object> setDeviceProperty(String iotId, String properties) throws Exception;
|
||||
|
||||
/**
|
||||
* 调用设备服务
|
||||
*
|
||||
* @param iotId 设备ID
|
||||
* @param identifier 服务标识符
|
||||
* @param args 参数
|
||||
* @return 调用结果
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
Map<String, Object> invokeService(String iotId, String identifier, String args) throws Exception;
|
||||
|
||||
/**
|
||||
* 解绑设备
|
||||
*
|
||||
* @param iotId 设备ID
|
||||
* @return 解绑结果
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
Map<String, Object> unbindDevice(String iotId) throws Exception;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 的映射逻辑");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Object> 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<String, Object> result = new HashMap<>();
|
||||
result.put("success", response.getSuccess());
|
||||
result.put("data", response.getData());
|
||||
result.put("total", response.getTotal());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> queryDeviceInfo(String iotId) throws Exception {
|
||||
log.info("查询设备详情,IotId: {}", iotId);
|
||||
|
||||
QueryDeviceDetailRequest request = new QueryDeviceDetailRequest();
|
||||
request.setIotId(iotId);
|
||||
|
||||
QueryDeviceDetailResponse response = acsClient.getAcsResponse(request);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", response.getSuccess());
|
||||
result.put("data", response.getData());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> queryDeviceProperties(String iotId) throws Exception {
|
||||
log.info("查询设备属性,IotId: {}", iotId);
|
||||
|
||||
QueryDevicePropertyStatusRequest request = new QueryDevicePropertyStatusRequest();
|
||||
request.setIotId(iotId);
|
||||
|
||||
QueryDevicePropertyStatusResponse response = acsClient.getAcsResponse(request);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", response.getSuccess());
|
||||
result.put("data", response.getData());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> 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<String, Object> result = new HashMap<>();
|
||||
result.put("success", response.getSuccess());
|
||||
result.put("data", response.getData());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> 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<String, Object> result = new HashMap<>();
|
||||
result.put("success", response.getSuccess());
|
||||
result.put("data", response.getData());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> unbindDevice(String iotId) throws Exception {
|
||||
log.info("解绑设备,IotId: {}", iotId);
|
||||
|
||||
// 注: 通用IoT SDK没有直接的解绑接口,这里使用删除设备
|
||||
DeleteDeviceRequest request = new DeleteDeviceRequest();
|
||||
request.setIotId(iotId);
|
||||
|
||||
DeleteDeviceResponse response = acsClient.getAcsResponse(request);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", response.getSuccess());
|
||||
result.put("errorMessage", response.getErrorMessage());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
36
intc-modules/intc-iot/src/main/resources/application.yml
Normal file
36
intc-modules/intc-iot/src/main/resources/application.yml
Normal file
@@ -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
|
||||
@@ -102,11 +102,6 @@
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.taosdata.jdbc</groupId>
|
||||
<artifactId>taos-jdbcdriver</artifactId>
|
||||
<version>2.0.40</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -27,8 +27,11 @@ public class DeviceSensorDataController extends BaseController
|
||||
private IDeviceSensorDataService deviceSensorDataService;
|
||||
|
||||
@GetMapping("/getHistoryData")
|
||||
public List<DeviceSensorData> 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<DeviceSensorData> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; // 设备序列号
|
||||
|
||||
@@ -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<DeviceSensorData> dataList);
|
||||
|
||||
/**
|
||||
* 查询数据
|
||||
*
|
||||
* @return 影响行数
|
||||
* @param serialNum 设备序列号
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param intervalType 间隔类型:1-原始数据,2-10分钟,3-30分钟,4-1小时,5-3小时,6-6小时
|
||||
* @return 设备传感器数据列表
|
||||
*/
|
||||
List<DeviceSensorData> 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<DeviceSensorData> getHistoryDataList(@Param("serialNum") String serialNum,
|
||||
@Param("startTime") String startTime,
|
||||
@Param("endTime") String endTime,
|
||||
@Param("intervalType") Integer intervalType);
|
||||
|
||||
}
|
||||
|
||||
@@ -29,9 +29,13 @@ public interface IDeviceSensorDataService {
|
||||
public void batchInsertDeviceSensorData(List<DeviceSensorData> dataList);
|
||||
|
||||
/**
|
||||
* 查询数据
|
||||
* 查询历史数据列表
|
||||
*
|
||||
* @return 影响行数
|
||||
* @param serialNum 设备序列号
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param intervalType 间隔类型:1-原始数据,2-10分钟,3-30分钟,4-1小时,5-3小时,6-6小时
|
||||
* @return 设备传感器数据列表
|
||||
*/
|
||||
public List<DeviceSensorData> getHistoryDataList(String serialNum, Long deviceId, String mobilePhone, int deviceType, String startTime, String endTime);
|
||||
public List<DeviceSensorData> getHistoryDataList(String serialNum, String startTime, String endTime, Integer intervalType);
|
||||
}
|
||||
|
||||
@@ -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<DeviceSensorData> getHistoryDataList(String serialNum,Long deviceId, String mobilePhone, int deviceType,String startTime, String endTime) {
|
||||
List<DeviceSensorData> list=new ArrayList<>();
|
||||
public List<DeviceSensorData> getHistoryDataList(String serialNum, String startTime, String endTime, Integer intervalType) {
|
||||
List<DeviceSensorData> 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 "未知";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,14 +52,57 @@
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="getHistoryDataList" parameterType="String" resultType="com.intc.tdengine.domain.DeviceSensorData">
|
||||
select time,createTime,dissolvedOxygen,temperature,saturability,ph,salinity,treference,tfluorescence,phaseDifference,battery,serialNum,deviceId,data.mobilePhone,deviceType from fishery.t_#{serialNum}
|
||||
<select id="getHistoryDataList" resultType="com.intc.tdengine.domain.DeviceSensorData">
|
||||
<if test="intervalType == null or intervalType == 1">
|
||||
<!-- 原始数据,不聚合 -->
|
||||
SELECT
|
||||
`time`,
|
||||
createTime,
|
||||
dissolvedOxygen,
|
||||
temperature,
|
||||
saturability,
|
||||
ph,
|
||||
salinity,
|
||||
treference,
|
||||
tfluorescence,
|
||||
phaseDifference,
|
||||
battery,
|
||||
serialNum,
|
||||
deviceId,
|
||||
mobilePhone,
|
||||
deviceType
|
||||
FROM `fishery`.`t_${serialNum}`
|
||||
<where>
|
||||
time >= #{startTime} and time <= #{endTime}
|
||||
<if test="serialNum != null and serialNum != ''"> AND serialNum=#{serialNum}</if>
|
||||
<if test="deviceId != null "> AND deviceId=#{deviceId}</if>
|
||||
<if test="mobilePhone != null and mobilePhone != ''"> AND mobilePhone=#{mobilePhone}</if>
|
||||
<if test="deviceType != null"> AND deviceType=#{deviceType}</if>
|
||||
<if test="startTime != null and startTime != ''">AND `time` >= #{startTime}</if>
|
||||
<if test="endTime != null and endTime != ''">AND `time` <= #{endTime}</if>
|
||||
</where>
|
||||
</if>
|
||||
<if test="intervalType != null and intervalType != 1">
|
||||
<!-- 按时间间隔聚合 -->
|
||||
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}`
|
||||
<where>
|
||||
<if test="startTime != null and startTime != ''">AND `time` >= #{startTime}</if>
|
||||
<if test="endTime != null and endTime != ''">AND `time` <= #{endTime}</if>
|
||||
</where>
|
||||
<choose>
|
||||
<when test="intervalType == 2">INTERVAL(10m)</when>
|
||||
<when test="intervalType == 3">INTERVAL(30m)</when>
|
||||
<when test="intervalType == 4">INTERVAL(1h)</when>
|
||||
<when test="intervalType == 5">INTERVAL(3h)</when>
|
||||
<when test="intervalType == 6">INTERVAL(6h)</when>
|
||||
</choose>
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
88
intc-modules/intc-weixin/pom.xml
Normal file
88
intc-modules/intc-weixin/pom.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>intc-weixin</artifactId>
|
||||
|
||||
<description>
|
||||
微信对接模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- 微信开发 Java SDK -->
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-mp</artifactId>
|
||||
<version>4.6.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-miniapp</artifactId>
|
||||
<version>4.6.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-pay</artifactId>
|
||||
<version>4.6.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-open</artifactId>
|
||||
<version>4.6.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.intc</groupId>
|
||||
<artifactId>intc-common-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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<String> test() {
|
||||
return R.ok("微信模块测试成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "获取公众号用户信息")
|
||||
@GetMapping("/mp/user/{openId}")
|
||||
public R<WxMpUser> 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<String> 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<String> 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<String> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
42
intc-modules/intc-weixin/src/main/resources/application.yml
Normal file
42
intc-modules/intc-weixin/src/main/resources/application.yml
Normal file
@@ -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:
|
||||
@@ -17,6 +17,8 @@
|
||||
<module>intc-workflow</module>
|
||||
<module>intc-fishery</module>
|
||||
<module>intc-tdengine</module>
|
||||
<module>intc-weixin</module>
|
||||
<module>intc-iot</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>intc-modules</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user