PatrixCrud, PatrixMqtt, PatrixDemo

This commit is contained in:
Patrick Haßel 2025-04-15 14:34:57 +02:00
commit 0fa2238557
31 changed files with 971 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

42
PatrixCrud/pom.xml Normal file
View File

@ -0,0 +1,42 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<artifactId>PatrixCrud</artifactId>
<parent>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixParent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,5 @@
package de.ph87.patrix.crud;
public enum CrudAction {
CREATED, MODIFIED, DELETED
}

View File

@ -0,0 +1,47 @@
package de.ph87.patrix.crud;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequiredArgsConstructor
public abstract class CrudController<Entity, ID, Create, Dto, Repository extends CrudRepository<Entity, ID, Dto>, Service extends CrudService<Entity, ID, Create, Dto, Repository>> {
protected final Service service;
protected final Repository repository;
@NonNull
@GetMapping("{id}")
public Dto byId(@PathVariable @NonNull final ID id) {
return repository.getDtoById(id);
}
@NonNull
@PostMapping("list")
public List<Dto> list(@RequestBody @NonNull final CrudFilter<Entity> filter) {
return repository.findAllDto(filter.getSpecification(), filter.getSort());
}
@NonNull
@PostMapping("page")
public Page<Dto> page(@RequestBody @NonNull final CrudFilter<Entity> filter) {
return repository.findAllDto(filter.getSpecification(), filter.getPageable());
}
@NonNull
@PostMapping("create")
public Dto create(@RequestBody @NonNull final Create create) {
return service.create(create);
}
@NonNull
@DeleteMapping("{id}")
public Dto delete(@PathVariable @NonNull final ID id) {
return service.delete(id, null);
}
}

View File

@ -0,0 +1,23 @@
package de.ph87.patrix.crud;
import lombok.Data;
import lombok.NonNull;
import java.time.ZonedDateTime;
@Data
public class CrudEvent<T> {
@NonNull
public final ZonedDateTime timestamp = ZonedDateTime.now();
@NonNull
public final String topic;
@NonNull
public final T payload;
@NonNull
public final CrudAction action;
}

View File

@ -0,0 +1,50 @@
package de.ph87.patrix.crud;
import lombok.*;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import java.util.List;
@Getter
@ToString
@AllArgsConstructor
public class CrudFilter<T> {
public final int page;
public final int size;
public final List<Order> orders;
@NonNull
public Sort getSort() {
return Sort.by(orders.stream().map(Order::toSortOrder).toList());
}
@Data
public static class Order {
public final String property;
public final Sort.Direction direction;
public Sort.Order toSortOrder() {
return new Sort.Order(direction, property);
}
}
@NonNull
public Pageable getPageable() {
return PageRequest.of(page, size, getSort());
}
@NonNull
public Specification<T> getSpecification() {
return Specification.where(null);
}
}

View File

@ -0,0 +1,36 @@
package de.ph87.patrix.crud;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import java.util.List;
@NoRepositoryBean
public interface CrudRepository<Entity, ID, Dto> extends ListCrudRepository<Entity, ID>, JpaSpecificationExecutor<Entity> {
@NonNull
default List<Dto> findAllDto(@Nullable final Specification<Entity> specification, @NonNull final Sort sort) {
return findAll(specification, sort).stream().map(this::toDto).toList();
}
@NonNull
default Page<Dto> findAllDto(@NonNull final Specification<Entity> specification, @NonNull final Pageable pageable) {
return findAll(specification, pageable).map(this::toDto);
}
@NonNull
default Dto getDtoById(@NonNull ID id) {
return findById(id).map(this::toDto).orElseThrow();
}
@NonNull
Dto toDto(@NonNull final Entity entity);
}

View File

@ -0,0 +1,57 @@
package de.ph87.patrix.crud;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.transaction.annotation.Transactional;
import java.util.function.Consumer;
@Slf4j
@RequiredArgsConstructor
public abstract class CrudService<Entity, ID, Create, Dto, Repository extends CrudRepository<Entity, ID, Dto>> {
protected final ApplicationEventPublisher applicationEventPublisher;
protected final Repository repository;
@NonNull
@Transactional
public Dto create(@NonNull final Create create) {
return publish(repository.save(toEntity(create)), CrudAction.CREATED);
}
@NonNull
@Transactional
public Dto modify(@NonNull final ID id, @NonNull final Consumer<Entity> modifier) {
final Entity entity = repository.findById(id).orElseThrow();
modifier.accept(entity);
return publish(entity, CrudAction.MODIFIED);
}
@NonNull
@Transactional
public Dto delete(@NonNull final ID id, @Nullable final Consumer<Entity> preDelete) {
final Entity entity = repository.findById(id).orElseThrow();
final Dto dto = publish(entity, CrudAction.DELETED);
if (preDelete != null) {
preDelete.accept(entity);
}
repository.delete(entity);
return dto;
}
@NonNull
protected Dto publish(@NonNull final Entity entity, @NonNull final CrudAction action) {
final Dto dto = repository.toDto(entity);
log.info("{} {}: {}", entity.getClass().getSimpleName(), action, dto);
applicationEventPublisher.publishEvent(new CrudEvent<>(entity.getClass().getSimpleName(), dto, action));
return dto;
}
@NonNull
protected abstract Entity toEntity(@NonNull final Create create);
}

View File

@ -0,0 +1,16 @@
package de.ph87.patrix.crud;
import lombok.NonNull;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CrudWebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(@NonNull final CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("*");
}
}

View File

@ -0,0 +1,32 @@
package de.ph87.patrix.crud;
import lombok.NonNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class CrudWebsocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(@NonNull final StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").setAllowedOrigins("*");
}
@Override
public void configureMessageBroker(@NonNull final MessageBrokerRegistry config) {
config.enableSimpleBroker().setHeartbeatValue(new long[]{2000, 2000}).setTaskScheduler(heartBeatScheduler());
}
@Bean
public TaskScheduler heartBeatScheduler() {
return new ThreadPoolTaskScheduler();
}
}

View File

@ -0,0 +1,22 @@
package de.ph87.patrix.crud;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;
@Slf4j
@Service
@RequiredArgsConstructor
public class CrudWebsocketService {
private final SimpMessageSendingOperations simpMessageSendingOperations;
@TransactionalEventListener(CrudEvent.class)
public void send(@NonNull final CrudEvent<?> event) {
simpMessageSendingOperations.convertAndSend(event.getTopic(), event);
}
}

View File

@ -0,0 +1,10 @@
logging.level.root=WARN
logging.level.de.ph87=INFO
#-
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false
#-
spring.jackson.serialization.indent_output=true
#-
spring.main.banner-mode=off

32
PatrixDemo/pom.xml Normal file
View File

@ -0,0 +1,32 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<artifactId>PatrixDemo</artifactId>
<parent>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixParent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixCrud</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixMqtt</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,23 @@
package de.ph87.patrix.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootApplication
@ComponentScan(basePackages = {"de.ph87.patrix"})
public class Backend {
public static void main(String[] args) {
SpringApplication.run(Backend.class, args);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,25 @@
package de.ph87.patrix.demo;
import de.ph87.patrix.demo.user.UserController;
import de.ph87.patrix.demo.user.UserCreate;
import de.ph87.patrix.demo.user.UserDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class DemoService {
private final UserController userController;
@EventListener(ApplicationStartedEvent.class)
public void insertDemoData() {
final UserDto patrick = userController.create(new UserCreate(true, "patrick", "1234", "Patrick", "Haßel"));
final UserDto katrin = userController.create(new UserCreate(true, "katrin", null, "Katrin", "Haßel"));
}
}

View File

@ -0,0 +1,13 @@
package de.ph87.patrix.demo.user;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class DuplicateUser extends RuntimeException {
public DuplicateUser() {
super("Duplicate user");
}
}

View File

@ -0,0 +1,13 @@
package de.ph87.patrix.demo.user;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class PasswordNotAllowed extends RuntimeException {
public PasswordNotAllowed(final String reason) {
super("Password not allowed: " + reason);
}
}

View File

@ -0,0 +1,71 @@
package de.ph87.patrix.demo.user;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.time.ZonedDateTime;
@Entity
@Getter
@ToString
@NoArgsConstructor
@Table(name = "`user`")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Version
private long version;
@NonNull
@Column(nullable = false, updatable = false)
private ZonedDateTime created = ZonedDateTime.now();
@Column(nullable = false)
private boolean enabled;
@Setter
@NonNull
@Column(nullable = false, unique = true)
private String name;
@Setter
@NonNull
@Column(nullable = false)
private String password;
@Setter
@NonNull
@Column(nullable = false)
private String firstname = "";
@Setter
@NonNull
@Column(nullable = false)
private String lastname = "";
public User(@NonNull final UserCreate create, @NonNull final PasswordEncoder passwordEncoder) {
this.enabled = create.enabled;
this.name = create.name;
this.setPassword(create.password, passwordEncoder);
this.firstname = create.firstname;
this.lastname = create.lastname;
}
@SneakyThrows
public void setPassword(@Nullable final String password, @NonNull final PasswordEncoder passwordEncoder) {
if (password == null || password.isEmpty()) {
this.password = "";
return;
}
if (password.length() < 4) {
throw new PasswordNotAllowed("Too short");
}
this.password = passwordEncoder.encode(password);
}
}

View File

@ -0,0 +1,48 @@
package de.ph87.patrix.demo.user;
import de.ph87.patrix.crud.CrudController;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("User")
public class UserController extends CrudController<User, Long, UserCreate, UserDto, UserRepository, UserService> {
private final PasswordEncoder passwordEncoder;
public UserController(final UserService service, final UserRepository repository, final PasswordEncoder passwordEncoder) {
super(service, repository);
this.passwordEncoder = passwordEncoder;
}
@PostMapping("{id}/name")
public UserDto name(@PathVariable final long id, @RequestBody @NonNull final String name) {
return service.modify(id, user -> {
if (user.getName().equals(name)) {
return;
}
if (repository.existsByName(name)) {
throw new DuplicateUser();
}
user.setName(name);
});
}
@PostMapping("{id}/password")
public UserDto password(@PathVariable final long id, @RequestBody(required = false) @Nullable final String password) {
return service.modify(id, user -> user.setPassword(password, passwordEncoder));
}
@PostMapping("{id}/firstname")
public UserDto firstname(@PathVariable final long id, @RequestBody @NonNull final String firstname) {
return service.modify(id, user -> user.setFirstname(firstname));
}
@PostMapping("{id}/lastname")
public UserDto lastname(@PathVariable final long id, @RequestBody @NonNull final String lastname) {
return service.modify(id, user -> user.setLastname(lastname));
}
}

View File

@ -0,0 +1,28 @@
package de.ph87.patrix.demo.user;
import jakarta.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
@AllArgsConstructor
public class UserCreate {
public final boolean enabled;
@NonNull
public final String name;
@Nullable
public final String password;
@NonNull
public final String firstname;
@NonNull
public final String lastname;
}

View File

@ -0,0 +1,45 @@
package de.ph87.patrix.demo.user;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import java.time.ZonedDateTime;
@Getter
@ToString
public class UserDto {
public final long id;
public final long version;
@NonNull
@ToString.Exclude
public final ZonedDateTime created;
public final boolean enabled;
@NonNull
public final String name;
public final boolean password;
@NonNull
public final String firstname;
@NonNull
public final String lastname;
public UserDto(@NonNull final User user) {
this.id = user.getId();
this.version = user.getVersion();
this.created = user.getCreated();
this.enabled = user.isEnabled();
this.name = user.getName();
this.password = !user.getPassword().isEmpty();
this.firstname = user.getFirstname();
this.lastname = user.getLastname();
}
}

View File

@ -0,0 +1,17 @@
package de.ph87.patrix.demo.user;
import de.ph87.patrix.crud.CrudFilter;
import lombok.Getter;
import lombok.ToString;
import java.util.List;
@Getter
@ToString
public class UserFilter extends CrudFilter<User> {
public UserFilter(final int page, final int size, final List<Order> orders) {
super(page, size, orders);
}
}

View File

@ -0,0 +1,16 @@
package de.ph87.patrix.demo.user;
import de.ph87.patrix.crud.CrudRepository;
import lombok.NonNull;
public interface UserRepository extends CrudRepository<User, Long, UserDto> {
boolean existsByName(@NonNull String name);
@NonNull
@Override
default UserDto toDto(@NonNull final User user) {
return new UserDto(user);
}
}

View File

@ -0,0 +1,36 @@
package de.ph87.patrix.demo.user;
import de.ph87.patrix.crud.CrudService;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class UserService extends CrudService<User, Long, UserCreate, UserDto, UserRepository> {
private final PasswordEncoder passwordEncoder;
public UserService(final UserRepository repository, final ApplicationEventPublisher applicationEventPublisher, final PasswordEncoder passwordEncoder) {
super(applicationEventPublisher, repository);
this.passwordEncoder = passwordEncoder;
}
@NonNull
@Override
public UserDto create(@NonNull final UserCreate userCreate) {
if (repository.existsByName(userCreate.name)) {
throw new DuplicateUser();
}
return super.create(userCreate);
}
@NonNull
@Override
protected User toEntity(@NonNull final UserCreate create) {
return new User(create, passwordEncoder);
}
}

View File

@ -0,0 +1,10 @@
logging.level.root=WARN
logging.level.de.ph87=INFO
#-
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false
#-
spring.jackson.serialization.indent_output=true
#-
spring.main.banner-mode=off

31
PatrixMqtt/pom.xml Normal file
View File

@ -0,0 +1,31 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<artifactId>PatrixMqtt</artifactId>
<parent>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixParent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.hivemq</groupId>
<artifactId>hivemq-mqtt-client</artifactId>
<version>1.3.5</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,32 @@
package de.ph87.patrix.mqtt;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Data
@Slf4j
@Component
@ConfigurationProperties(prefix = "de.ph87.patrix.mqtt")
public class MqttConfig {
@NonNull
private String host = "";
private int port = 1883;
@NonNull
private String clientId = "TEMPORARY-" + UUID.randomUUID();
private boolean cleanSession = true;
@NonNull
public String getUrl() {
return "tcp://" + host + ":" + port;
}
}

View File

@ -0,0 +1,30 @@
package de.ph87.patrix.mqtt;
import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
@Getter
@ToString
public class MqttEvent {
public final ZonedDateTime received = ZonedDateTime.now();
public final String topic;
@ToString.Exclude
public final String payload;
public final String payloadLoggable;
public MqttEvent(@NonNull final Mqtt3Publish publish) {
this.topic = publish.getTopic().toString();
this.payload = new String(publish.getPayloadAsBytes(), StandardCharsets.UTF_8);
this.payloadLoggable = this.payload.replace("\n", "\\n").replace("\r", "\\r");
}
}

View File

@ -0,0 +1,62 @@
package de.ph87.patrix.mqtt;
import com.hivemq.client.mqtt.MqttClient;
import com.hivemq.client.mqtt.datatypes.MqttQos;
import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient;
import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish;
import jakarta.annotation.PreDestroy;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class MqttService {
private final ApplicationEventPublisher applicationEventPublisher;
private final MqttConfig config;
private Mqtt3AsyncClient client;
@EventListener(ApplicationReadyEvent.class)
public void connect() {
if (client != null) {
throw new RuntimeException();
}
client = MqttClient.builder()
.useMqttVersion3()
.identifier(config.getClientId())
.serverHost(config.getHost())
.serverPort(config.getPort())
.buildAsync();
client.connectWith().cleanSession(config.isCleanSession()).send();
client.subscribeWith().topicFilter("#").qos(MqttQos.AT_LEAST_ONCE).callback(this::receive).send();
log.info("Connected: {} as {} {}", config.getUrl(), config.getClientId(), config.isCleanSession() ? "[CLEAN SESSION]" : "[PERSISTENT SESSION]");
}
@PreDestroy
public void shutdown() {
if (client != null) {
log.info("Disconnecting...");
client.disconnect();
client = null;
}
}
public void receive(@NonNull final Mqtt3Publish publish) {
try {
final MqttEvent event = new MqttEvent(publish);
log.debug("Message received: topic={}, payload={}", event.getTopic(), event.getPayloadLoggable());
applicationEventPublisher.publishEvent(event);
} catch (Exception e) {
log.error("Unexpected Exception publishing MqttEvent.", e);
}
}
}

10
application.properties Normal file
View File

@ -0,0 +1,10 @@
logging.level.de.ph87=DEBUG
#-
spring.datasource.url=jdbc:h2:./database;AUTO_SERVER=TRUE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
#-
spring.jpa.hibernate.ddl-auto=create
#-
de.ph87.patrix.mqtt.host=10.0.0.50

51
pom.xml Normal file
View File

@ -0,0 +1,51 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixParent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>PatrixCrud</module>
<module>PatrixDemo</module>
<module>PatrixMqtt</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixCrud</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixMqtt</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>de.ph87.patrix</groupId>
<artifactId>PatrixDemo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>