2021-08-05

This commit is contained in:
2021-08-05 13:33:52 +09:00
parent d45139dd58
commit 0f4d342354
32 changed files with 743 additions and 4 deletions

View File

@@ -1,7 +1,7 @@
/*
* Spring-boot Examples
* Examples for Spring-boot
*
* Copyright (c) 2021. Elex. All Rights Reserved.
* Copyright (c) 2021-2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/

View File

@@ -12,7 +12,7 @@ plugins {
}
dependencies{
implementation("org.jetbrains:annotations:21.0.1")
//implementation("org.jetbrains:annotations:21.0.1")
}

27
mqtt/build.gradle.kts Normal file
View File

@@ -0,0 +1,27 @@
/*
* Spring-boot Examples
*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
plugins {
id("elex-spring-boot")
id("org.springframework.boot") version "2.5.3"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
}
dependencies {
implementation ("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-mustache")
implementation ("org.springframework.boot:spring-boot-starter-integration")
implementation ("org.springframework.integration:spring-integration-mqtt")
compileOnly ("org.projectlombok:lombok")
developmentOnly ("org.springframework.boot:spring-boot-devtools")
annotationProcessor ("org.projectlombok:lombok")
testImplementation ("org.springframework.boot:spring-boot-starter-test")
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import com.samskivert.mustache.Mustache;
import org.springframework.boot.autoconfigure.mustache.MustacheEnvironmentCollector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class Config {
@Bean
public Mustache.Compiler mustacheCompiler(
Mustache.TemplateLoader templateLoader,
Environment environment) {
MustacheEnvironmentCollector collector
= new MustacheEnvironmentCollector();
collector.setEnvironment(environment);
return Mustache.compiler()
.withLoader(templateLoader)
.withCollector(collector);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
public class HttpController {
@Autowired
private MqttPublisher mqtt;
@GetMapping("/")
public ModelAndView home() {
final Map<String, Object> map = new HashMap<>();
return new ModelAndView("home", map);
}
@GetMapping("/publish")
public void publish(@RequestParam String topic, @RequestParam String message) {
log.info("I'll publish a message: {} {}", topic, message);
mqtt.publish(topic, message);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
/**
* @see "https://docs.spring.io/spring-integration/reference/html/mqtt.html#mqtt"
*/
@Configuration
public class MqttConfig {
private static final String HOST = "tcp://localhost:1883";
private static final String USERNAME = "elex";
private static final String PASSWORD = "test";
private static final String CLIENT_ID = "i-am-a-server";
private static final String TOPIC_FILTER = "#";
private static final String TOPIC_FILTER_2 = "$SYS/#";
private MqttConnectOptions connectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setServerURIs(new String[]{HOST});
options.setKeepAliveInterval(10);
options.setAutomaticReconnect(true);
options.setUserName(USERNAME);
options.setPassword(PASSWORD.toCharArray());
return options;
}
@Bean
public DefaultMqttPahoClientFactory defaultMqttPahoClientFactory() {
DefaultMqttPahoClientFactory clientFactory = new DefaultMqttPahoClientFactory();
clientFactory.setConnectionOptions(connectOptions());
return clientFactory;
}
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
@Bean
public MessageProducer inboundChannel(DefaultMqttPahoClientFactory clientFactory) {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(HOST, CLIENT_ID, clientFactory,
TOPIC_FILTER, TOPIC_FILTER_2);
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler messageHandler() {
return new MqttMessageHandler();
}
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(DefaultMqttPahoClientFactory clientFactory) {
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler(CLIENT_ID + MqttAsyncClient.generateClientId(),
clientFactory);
messageHandler.setAsync(true);
messageHandler.setDefaultQos(1);
return messageHandler;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import lombok.extern.slf4j.Slf4j;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
@Slf4j
public class MqttMessageHandler implements MessageHandler {
/**
* 구독 중인 MQTT 메시지는 여기서 받는다.
*
* @param message
* @throws MessagingException
*/
@Override
public void handleMessage(Message<?> message) throws MessagingException {
String topic = (String) message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC);
log.info("MQTT Rx: {} {}", topic, message.getPayload());
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttPublisher {
/**
* MQTT 메시지는 여기서 발행한다.
*
* @param topic
* @param payload
*/
void publish(@Header(MqttHeaders.TOPIC) String topic, String payload);
}

View File

@@ -0,0 +1,6 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;

View File

@@ -0,0 +1,4 @@
#
# Copyright (c) 2021. Elex. All Rights Reserved.
# https://www.elex-project.com/
#

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2021. Elex. All Rights Reserved.
~ https://www.elex-project.com/
-->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{15} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@@ -0,0 +1,15 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MQTT Spring-boot Integration Examples</title>
</head>
<body>
<h1>MQTT Spring-boot Integration Examples</h1>
<dl>
<dt>Usage</dt>
<dd><code>/publish?topic=TOPIC&message=MESSAGE</code></dd>
</dl>
<p>Copyright $copy; 2021 Elex Project. All Rights Reserved.</p>
</body>
</html>

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ApplicationTests {
@Test
void contextLoads() {
}
}

27
restful/build.gradle.kts Normal file
View File

@@ -0,0 +1,27 @@
/*
* Spring-boot Examples
*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
plugins {
id("elex-spring-boot")
id("org.springframework.boot") version "2.5.3"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-mustache")
compileOnly("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}

View File

@@ -0,0 +1,13 @@
package kr.pe.elex.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,23 @@
package kr.pe.elex.examples;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
@Slf4j
@RestController
public class MainController {
@GetMapping(path = "/{name}", produces = {MimeTypeUtils.APPLICATION_JSON_VALUE})
public ResponseEntity<Person> home(@PathVariable String name) {
return ResponseEntity
.ok(new Person(name, new Random().nextInt()));
}
}

View File

@@ -0,0 +1,14 @@
package kr.pe.elex.examples;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Person {
@JsonProperty
private String name;
@JsonProperty
private int age;
}

View File

@@ -0,0 +1 @@
package kr.pe.elex.examples;

View File

@@ -0,0 +1 @@

View File

@@ -6,4 +6,5 @@
*/
rootProject.name = "spring-boot-examples"
include("file-upload", "security", "security-with-jpa", "validation", "testing")
include("file-upload", "security", "security-with-jpa", "validation", "testing",
"mqtt", "websocket", "restful")

View File

@@ -0,0 +1,25 @@
/*
* Spring-boot Examples
*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
plugins {
id("elex-spring-boot")
id("org.springframework.boot") version "2.5.3"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
}
dependencies {
implementation ("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-mustache")
implementation("org.springframework.boot:spring-boot-starter-websocket")
compileOnly ("org.projectlombok:lombok")
developmentOnly ("org.springframework.boot:spring-boot-devtools")
annotationProcessor ("org.projectlombok:lombok")
testImplementation ("org.springframework.boot:spring-boot-starter-test")
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import com.samskivert.mustache.Mustache;
import org.springframework.boot.autoconfigure.mustache.MustacheEnvironmentCollector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class Config {
@Bean
public Mustache.Compiler mustacheCompiler(
Mustache.TemplateLoader templateLoader,
Environment environment) {
MustacheEnvironmentCollector collector
= new MustacheEnvironmentCollector();
collector.setEnvironment(environment);
return Mustache.compiler()
.withLoader(templateLoader)
.withCollector(collector);
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
public class HttpController {
@GetMapping("/")
public ModelAndView home() {
final Map<String, Object> map = new HashMap<>();
map.put("title", "Websocket Test");
return new ModelAndView("home", map);
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
private final WebSocketHandler webSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/websocket");
//.setAllowedOrigins("*")
// .withSockJS();
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketHandler extends TextWebSocketHandler {
private static Set<WebSocketSession> sessions = new ConcurrentHashMap().newKeySet();
public static void publish(String message) throws IOException {
for (WebSocketSession item : sessions) {
try {
item.sendMessage(new TextMessage(message));
} catch (Throwable e) {
log.error("Couldn't send message over websocket.", e);
}
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
super.afterConnectionEstablished(session);
sessions.add(session);
log.info("Client({}) connected.", session.getRemoteAddress());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
log.info("Message from client({}):{}", session.getRemoteAddress(), message.getPayload());
for (WebSocketSession webSocketSession : sessions) {
//if (session == webSocketSession) continue;
String msg = message.getPayload().toUpperCase();
webSocketSession.sendMessage(new TextMessage(msg));
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);
sessions.remove(session);
log.info("Client({}) disconnect.", session.getRemoteAddress());
}
}

View File

@@ -0,0 +1,6 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;

View File

@@ -0,0 +1,4 @@
#
# Copyright (c) 2021. Elex. All Rights Reserved.
# https://www.elex-project.com/
#

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2021. Elex. All Rights Reserved.
~ https://www.elex-project.com/
-->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{15} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@@ -0,0 +1,108 @@
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{title}}</title>
<style>
* {
box-sizing: border-box;
}
body {
margin:0; padding:0; border:0;
}
.layout {
display: grid;
grid-template-rows: 64px 1fr;
grid-template-columns: 1fr;
grid-template-areas: "controls" "textpane";
width: 100%; height: 100%;
}
.row{
display: flex;
flex-direction: row;
align-items: center;
grid-area: controls;
background-color: orange;
padding: 0 16px;
}
.row h1{
flex-grow: 1;
}
.text-pane {
grid-area: textpane;
width: 100%; height: 100%;
}
textarea {
width: 100%; height: 100%;
overflow: auto;
resize: none;
}
</style>
<script>
const log = function(msg){
const ta = document.querySelector("#text-area");
ta.append(new Date().toLocaleString());
ta.append("\t");
ta.append(msg);
ta.append("\r\n");
ta.scrollTop = ta.scrollHeight;
};
const WS = {
websocket: null,
connect: function(){
this.websocket = new WebSocket("ws://localhost:8080/websocket");
this.websocket.onopen = function(ev){
console.log("Connected.");
log("Connected.");
};
this.websocket.onclose = function(ev){
console.log("Closed.");
log("Closed.");
};
this.websocket.onmessage = function(ev){
console.log("Rx: " + ev.data);
log("Rx: " + ev.data);
};
this.websocket.onerror = function(ev){
console.log("Error! " + ev.data);
log("Error! " + ev.data);
}
},
disconnect: function(){
if (!this.websocket) websocket.close();
console.log("Disconnected.");
log("Disconnected.");
},
sendMessage: function(message){
this.websocket.send(message);
console.log("Tx: " + message);
log("Tx: " + message);
}
};
window.addEventListener('DOMContentLoaded', (event) => {
WS.connect();
});
const send = function(){
const msg = document.querySelector("#text-to-send").value;
WS.sendMessage(msg);
};
</script>
</head>
<body>
<div class="layout">
<div class="row">
<h1>{{title}}</h1>
<input id="text-to-send" type="text" />
<button onclick="send();">Send</button>
<button onclick="WS.connect();">Connect</button>
<button onclick="WS.disconnect();">Disconnect</button>
</div>
<div class="text-pane">
<textarea id="text-area"></textarea>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2021. Elex. All Rights Reserved.
* https://www.elex-project.com/
*/
package kr.pe.elex.examples;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ApplicationTests {
@Test
void contextLoads() {
}
}