大疆sdk接入

This commit is contained in:
sugus 2025-08-07 09:27:34 +08:00
parent 7590c9edc4
commit 09ff987de1
16 changed files with 647 additions and 1 deletions

Binary file not shown.

View File

@ -25,5 +25,13 @@
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.7</version> <version>2.12.7</version>
</dependency> </dependency>
<!--大疆上云API SDK-->
<dependency>
<groupId>com.cloud.sdk</groupId>
<artifactId>cloud-sdk</artifactId>
<version>1.0.3</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/cloud-sdk-1.0.3.jar</systemPath>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,78 @@
package com.aircraft.config.mqtt.config;
import com.dji.sdk.mqtt.ChannelName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.ExecutorChannel;
import org.springframework.messaging.MessageChannel;
import javax.annotation.Resource;
import java.util.concurrent.Executor;
/**
* Definition classes for all channels
* @author sean.zhou
* @date 2021/11/10
* @version 0.1
*/
@Configuration
public class MqttMessageChannel {
@Autowired
@Qualifier("taskScheduler")
private Executor threadPool;
@Bean(name = ChannelName.INBOUND)
public MessageChannel inboundChannel() {
return new ExecutorChannel(threadPool);
}
@Bean(name = ChannelName.DEFAULT)
public MessageChannel defaultChannel() {
return new DirectChannel();
}
@Bean(name = ChannelName.INBOUND_STATUS)
public MessageChannel statusChannel() {
return new DirectChannel();
}
@Bean(name = ChannelName.INBOUND_STATE)
public MessageChannel stateChannel() {
return new DirectChannel();
}
@Bean(name = ChannelName.INBOUND_SERVICES_REPLY)
public MessageChannel serviceReplyChannel() {
return new DirectChannel();
}
@Bean(name = ChannelName.INBOUND_OSD)
public MessageChannel osdChannel() {
return new ExecutorChannel(threadPool);
}
@Bean(name = ChannelName.INBOUND_REQUESTS)
public MessageChannel requestsChannel() {
return new DirectChannel();
}
@Bean(name = ChannelName.INBOUND_EVENTS)
public MessageChannel eventsChannel() {
return new DirectChannel();
}
@Bean(name = ChannelName.INBOUND_PROPERTY_SET_REPLY)
public MessageChannel propertySetReply() {
return new DirectChannel();
}
@Bean(name = ChannelName.INBOUND_DRC_UP)
public MessageChannel drcUp() {
return new DirectChannel();
}
}

View File

@ -0,0 +1,122 @@
package com.aircraft.config.mqtt.config;
import com.aircraft.config.mqtt.util.JwtUtil;
import com.auth0.jwt.algorithms.Algorithm;
import com.aircraft.config.mqtt.model.MqttClientOptions;
import com.aircraft.config.mqtt.model.MqttProtocolEnum;
import com.aircraft.config.mqtt.model.MqttUseEnum;
import com.dji.sdk.cloudapi.control.DrcModeMqttBroker;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.util.StringUtils;
import java.util.Map;
/**
*
* @author sean.zhou
* @date 2021/11/10
* @version 0.1
*/
@Configuration
@Data
@ConfigurationProperties
@Slf4j
public class MqttPropertyConfiguration {
private static Map<MqttUseEnum, MqttClientOptions> mqtt;
public void setMqtt(Map<MqttUseEnum, MqttClientOptions> mqtt) {
MqttPropertyConfiguration.mqtt = mqtt;
}
/**
* Get the configuration options of the basic link of the mqtt client.
* @return
*/
static MqttClientOptions getBasicClientOptions() {
if (!mqtt.containsKey(MqttUseEnum.BASIC)) {
throw new Error("Please configure the basic mqtt connection parameters first, otherwise application cannot be started.");
}
return mqtt.get(MqttUseEnum.BASIC);
}
/**
* Get the mqtt address of the basic link.
* @return
*/
public static String getBasicMqttAddress() {
return getMqttAddress(getBasicClientOptions());
}
/**
* Splice the mqtt address according to the parameters of different clients.
* @param options
* @return
*/
private static String getMqttAddress(MqttClientOptions options) {
StringBuilder addr = new StringBuilder()
.append(options.getProtocol().getProtocolAddr())
.append(options.getHost().trim())
.append(":")
.append(options.getPort());
if ((options.getProtocol() == MqttProtocolEnum.WS || options.getProtocol() == MqttProtocolEnum.WSS)
&& StringUtils.hasText(options.getPath())) {
addr.append(options.getPath());
}
return addr.toString();
}
/**
* Get the connection parameters of the mqtt client of the drc link.
* @param clientId
* @param username
* @param age The validity period of the token. unit: s
* @param map Custom data added in token.
* @return
*/
public static DrcModeMqttBroker getMqttBrokerWithDrc(String clientId, String username, Long age, Map<String, ?> map) {
if (!mqtt.containsKey(MqttUseEnum.DRC)) {
throw new RuntimeException("Please configure the drc link parameters of mqtt in the backend configuration file first.");
}
Algorithm algorithm = JwtUtil.algorithm;
String token = JwtUtil.createToken(map, age, algorithm, null, null);
return new DrcModeMqttBroker()
.setAddress(getMqttAddress(mqtt.get(MqttUseEnum.DRC)))
.setUsername(username)
.setClientId(clientId)
.setExpireTime(System.currentTimeMillis() / 1000 + age)
.setPassword(token)
.setEnableTls(false);
}
@Bean
public MqttConnectOptions mqttConnectOptions() {
MqttClientOptions customizeOptions = getBasicClientOptions();
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setServerURIs(new String[]{ getBasicMqttAddress() });
mqttConnectOptions.setUserName(customizeOptions.getUsername());
mqttConnectOptions.setPassword(StringUtils.hasText(customizeOptions.getPassword()) ?
customizeOptions.getPassword().toCharArray() : new char[0]);
mqttConnectOptions.setAutomaticReconnect(true);
mqttConnectOptions.setKeepAliveInterval(10);
return mqttConnectOptions;
}
@Bean
public MqttPahoClientFactory mqttClientFactory() {
log.info("MqttPahoClientFactory被加载了");
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(mqttConnectOptions());
return factory;
}
}

View File

@ -0,0 +1,88 @@
package com.aircraft.config.mqtt.model;
import com.auth0.jwt.interfaces.Claim;
import com.fasterxml.jackson.annotation.JsonAlias;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* A custom claim for storing custom information in the token.
* @author sean.zhou
* @date 2021/11/16
* @version 0.1
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Slf4j
public class CustomClaim {
/**
* The id of the account.
*/
private String id;
private String username;
@JsonAlias("user_type")
private Integer userType;
@JsonAlias("workspace_id")
private String workspaceId;
/**
* Convert the custom claim data type to the Map type.
* @return map
*/
public ConcurrentHashMap<String, String> convertToMap() {
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(4);
try {
Field[] declaredFields = this.getClass().getDeclaredFields();
for (Field field : declaredFields) {
JsonAlias annotation = field.getAnnotation(JsonAlias.class);
field.setAccessible(true);
// The value of key is named underscore.
map.put(annotation != null ? annotation.value()[0] : field.getName(),
field.get(this).toString());
}
} catch (IllegalAccessException e) {
log.info("CustomClaim converts failed. {}", this.toString());
e.printStackTrace();
}
return map;
}
/**
* Convert the data in Map into a custom claim object.
* @param claimMap
*/
public CustomClaim(Map<String, Claim> claimMap) {
Field[] declaredFields = this.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
JsonAlias annotation = field.getAnnotation(JsonAlias.class);
Claim value = claimMap.get(annotation == null ? field.getName() : annotation.value()[0]);
try {
Class<?> type = field.getType();
if (Integer.class.equals(type)) {
field.set(this, Integer.valueOf(value.asString()));
continue;
}
if (String.class.equals(type)) {
field.set(this, value.asString());
continue;
}
} catch (IllegalAccessException e) {
log.info("Claim parses failed. {}", claimMap.toString());
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,64 @@
package com.aircraft.config.mqtt.model;
import com.dji.sdk.mqtt.events.EventsDataRequest;
import com.dji.sdk.mqtt.events.EventsErrorCode;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.*;
/**
* @author sean
* @version 1.1
* @date 2022/6/9
*/
@EqualsAndHashCode(callSuper = true)
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EventsReceiver<T> extends EventsDataRequest<T> {
private String bid;
private String sn;
@Override
public EventsErrorCode getResult() {
return super.getResult();
}
@Override
public EventsReceiver<T> setResult(EventsErrorCode result) {
super.setResult(result);
return this;
}
@Override
public T getOutput() {
return super.getOutput();
}
@Override
public EventsReceiver<T> setOutput(T output) {
super.setOutput(output);
return this;
}
public String getBid() {
return bid;
}
public EventsReceiver<T> setBid(String bid) {
this.bid = bid;
return this;
}
public String getSn() {
return sn;
}
public EventsReceiver<T> setSn(String sn) {
this.sn = sn;
return this;
}
}

View File

@ -0,0 +1,16 @@
package com.aircraft.config.mqtt.model;
/**
* @author sean
* @version 1.1
* @date 2022/6/14
*/
public final class MapKeyConst {
private MapKeyConst(){
}
public static final String ACL = "acl";
}

View File

@ -0,0 +1,31 @@
package com.aircraft.config.mqtt.model;
import lombok.Data;
/**
* @author sean
* @version 1.3
* @date 2023/1/18
*/
@Data
public class MqttClientOptions {
private MqttProtocolEnum protocol;
private String host;
private Integer port;
private String username;
private String password;
private String clientId;
private String path;
/**
* The topic to subscribe to immediately when client connects. Only required for basic link.
*/
private String inboundTopic;
}

View File

@ -0,0 +1,30 @@
package com.aircraft.config.mqtt.model;
import lombok.Getter;
/**
* @author sean
* @version 1.3
* @date 2023/1/18
*/
@Getter
public enum MqttProtocolEnum {
MQTT("tcp"),
MQTTS("ssl"),
WS("ws"),
WSS("wss");
String protocol;
MqttProtocolEnum(String protocol) {
this.protocol = protocol;
}
public String getProtocolAddr() {
return protocol + "://";
}
}

View File

@ -0,0 +1,19 @@
package com.aircraft.config.mqtt.model;
/**
* @author sean
* @version 1.3
* @date 2023/1/18
*/
public enum MqttUseEnum {
/**
* The broker is used for basic link.
*/
BASIC,
/**
* This broker is used for the drc link.
*/
DRC
}

View File

@ -0,0 +1,144 @@
package com.aircraft.config.mqtt.util;
import com.aircraft.config.mqtt.model.CustomClaim;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.*;
@Slf4j
@Component
public class JwtUtil {
private static String issuer;
private static String subject;
private static long age;
private static String secret;
public static Algorithm algorithm;
@Value("${jwt.issuer: DJI}")
private void setIssuer(String issuer) {
JwtUtil.issuer = issuer;
}
@Value("${jwt.subject: CloudApiSample}")
private void setSubject(String subject) {
JwtUtil.subject = subject;
}
@Value("${jwt.age: 86400}")
private void setAge(long age) {
JwtUtil.age = age * 1000;
}
@Value("${jwt.secret: CloudApiSample}")
private void setSecret(String secret) {
JwtUtil.secret = secret;
setAlgorithm();
}
private void setAlgorithm() {
JwtUtil.algorithm = Algorithm.HMAC256(secret);
}
private JwtUtil() {
}
/**
* Create a token based on custom information.
* @param claims custom information
* @return token
*/
public static String createToken(Map<String, ?> claims) {
return JwtUtil.createToken(claims, age, algorithm, subject, issuer);
}
/**
*
* @param claims
* @param age unit: s
* @param algorithm
* @param subject
* @param issuer
* @return
*/
public static String createToken(Map<String, ?> claims, Long age, Algorithm algorithm, String subject, String issuer) {
if (Objects.isNull(algorithm)) {
throw new IllegalArgumentException();
}
Date now = new Date();
JWTCreator.Builder builder = JWT.create();
// Add custom information to the token's payload segment.
claims.forEach((k, v) -> {
if (Objects.nonNull(v.getClass().getClassLoader())) {
log.error("claim can't be set to a custom object.");
return;
}
if (v instanceof Map) {
builder.withClaim(k, (Map) v);
} else if (v instanceof List) {
builder.withClaim(k, (List) v);
} else {
builder.withClaim(k, String.valueOf(v));
}
});
if (StringUtils.hasText(subject)) {
builder.withSubject(subject);
}
if (StringUtils.hasText(issuer)) {
builder.withIssuer(issuer);
}
if (Objects.nonNull(age)) {
builder.withExpiresAt(new Date(now.getTime() + age));
}
String token = builder
.withIssuedAt(now)
.withNotBefore(now)
.sign(algorithm);
log.debug("token created. " + token);
return token;
}
/**
* Verify that the token is valid.
* @param token
* @return
* @throws TokenExpiredException
*/
public static DecodedJWT verifyToken(String token) {
return JWT.require(algorithm).build().verify(token);
}
/**
* Parses the custom information in the token into a CustomClaim object.
* @param token
* @return custom claim
*/
public static Optional<CustomClaim> parseToken(String token) {
DecodedJWT jwt;
try {
jwt = verifyToken(token);
} catch (Exception e) {
e.printStackTrace();
return Optional.empty();
}
return Optional.of(new CustomClaim(jwt.getClaims()));
}
}

View File

@ -18,6 +18,7 @@ package com.aircraft.config.mybatis;
import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -27,6 +28,7 @@ import org.springframework.context.annotation.Configuration;
* @date 2023-06-12 * @date 2023-06-12
**/ **/
@Configuration @Configuration
@Slf4j
public class MybatisPlusConfig { public class MybatisPlusConfig {
/** /**
@ -34,6 +36,7 @@ public class MybatisPlusConfig {
*/ */
@Bean @Bean
public MybatisPlusInterceptor paginationInterceptor() { public MybatisPlusInterceptor paginationInterceptor() {
log.info("MybatisPlusInterceptor被加载了");
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加MySQL的分页拦截器 //添加MySQL的分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

View File

@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.ApplicationPidFileWriter; import org.springframework.boot.context.ApplicationPidFileWriter;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -38,6 +39,7 @@ import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication @SpringBootApplication
@EnableTransactionManagement @EnableTransactionManagement
@MapperScan("com.aircraft.**.mapper") @MapperScan("com.aircraft.**.mapper")
@ComponentScan(basePackages = {"com.aircraft", "com.dji.sdk"})
public class AppRun { public class AppRun {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -17,6 +17,7 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dji.sdk.mqtt.MqttGatewayPublish;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -42,6 +43,10 @@ public class AircraftDeviceServiceImpl extends ServiceImpl<AircraftDeviceMapper,
private final String BUSINESS_TYPE = "aircraft_device"; private final String BUSINESS_TYPE = "aircraft_device";
private final MqttGatewayPublish mqttGatewayPublish;
@Override @Override
public IPage<AircraftDevicePageVO> page(AircraftDevicePageDTO dto, Page page) { public IPage<AircraftDevicePageVO> page(AircraftDevicePageDTO dto, Page page) {
//飞行端用户只能看到自己名下 //飞行端用户只能看到自己名下

View File

@ -76,4 +76,30 @@ code:
#密码加密传输,前端公钥加密,后端私钥解密 #密码加密传输,前端公钥加密,后端私钥解密
rsa: rsa:
private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
mqtt:
# @see com.dji.sample.component.mqtt.model.MqttUseEnum
# BASIC parameters are required.
BASIC:
protocol: MQTT # @see com.dji.sample.component.mqtt.model.MqttProtocolEnum
host: 127.0.0.1
port: 1883
username: JavaServer
password: 123456
client-id: 123456
# If the protocol is ws/wss, this value is required.
path:
DRC:
protocol: WS # @see com.dji.sample.component.mqtt.model.MqttProtocolEnum
host: 127.0.0.1
port: 8083
path: /mqtt
username: JavaServer
password: 123456
cloud-sdk:
mqtt:
# Topics that need to be subscribed when initially connecting to mqtt, multiple topics are divided by ",".
inbound-topic: sys/product/+/status,thing/product/+/requests

10
pom.xml
View File

@ -231,6 +231,16 @@
<artifactId>commons-text</artifactId> <artifactId>commons-text</artifactId>
<version>1.13.0</version> <version>1.13.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
<version>5.5.5</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.12.1</version>
</dependency>
</dependencies> </dependencies>
<build> <build>