SUPPORT-8957: add websocket service

This commit is contained in:
gulnaz 2025-03-13 00:26:18 +03:00
parent 9db55c846d
commit 1540780cdf
11 changed files with 277 additions and 1 deletions

View file

@ -216,6 +216,14 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>

View file

@ -1,6 +1,8 @@
import java.time.Duration;
import javax.sql.DataSource;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import liquibase.integration.spring.SpringLiquibase;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
@ -81,4 +83,10 @@ public class AppConfig {
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true);
}
}

View file

@ -8,14 +8,18 @@ import ru.micord.ervu.account_applications.security.model.jwt.UserSession;
import ru.micord.ervu.account_applications.security.model.jwt.authentication.JwtTokenAuthentication;
import ru.micord.ervu.account_applications.security.model.jwt.authentication.JwtTokenDummy;
import ru.micord.ervu.account_applications.security.service.JwtTokenService;
import ru.micord.ervu.account_applications.websocket.service.WebSocketService;
@Component
public class ErvuJwtAuthenticationProvider implements AuthenticationProvider {
private final JwtTokenService jwtTokenService;
private final WebSocketService webSocketService;
public ErvuJwtAuthenticationProvider(JwtTokenService jwtTokenService) {
public ErvuJwtAuthenticationProvider(JwtTokenService jwtTokenService,
WebSocketService webSocketService) {
this.jwtTokenService = jwtTokenService;
this.webSocketService = webSocketService;
}
@Override
@ -23,6 +27,7 @@ public class ErvuJwtAuthenticationProvider implements AuthenticationProvider {
JwtTokenDummy jwtTokenDummy = (JwtTokenDummy) authentication;
String jwtToken = jwtTokenDummy.getToken();
UserSession userSession = jwtTokenService.getUserSession(jwtToken);
webSocketService.connectToSocket();
return new JwtTokenAuthentication(userSession, jwtToken);
}

View file

@ -0,0 +1,18 @@
package ru.micord.ervu.account_applications.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
/**
* @author gulnaz
*/
@Configuration
public class WebSocketConfig {
@Bean
public WebSocketClient webSocketStompClient() {
return new StandardWebSocketClient();
}
}

View file

@ -0,0 +1,10 @@
package ru.micord.ervu.account_applications.websocket.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author gulnaz
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record ProcessErrorMsg(String message) {
}

View file

@ -0,0 +1,12 @@
package ru.micord.ervu.account_applications.websocket.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author gulnaz
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record ProcessResponseBody(String type, String userName, @JsonProperty("value") String tempPass,
String secretLink, ProcessErrorMsg msg) {
}

View file

@ -0,0 +1,12 @@
package ru.micord.ervu.account_applications.websocket.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import ru.micord.ervu.account_applications.websocket.enums.ClassName;
/**
* @author gulnaz
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record ProcessResponseDto(String traceId, String forUser, ClassName className,
ProcessResponseBody body) {
}

View file

@ -0,0 +1,31 @@
package ru.micord.ervu.account_applications.websocket.enums;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author gulnaz
*/
public enum ClassName {
@JsonProperty("update")
UPDATE,
@JsonProperty("processError")
PROCESS_ERROR,
@JsonEnumDefaultValue
SKIP
// private String value;
//
// ClassName(String value) {
// this.value = value;
// }
//
// public static ClassName getByValue(String value) {
// return Arrays.stream(ClassName.values())
// .filter(e -> e.value.equals(value))
// .findFirst()
// .orElse(ClassName.SKIP);
// }
}

View file

@ -0,0 +1,110 @@
package ru.micord.ervu.account_applications.websocket.handler;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import ru.micord.ervu.account_applications.service.UserApplicationListService;
import ru.micord.ervu.account_applications.websocket.dto.ProcessResponseDto;
import ru.micord.ervu.account_applications.websocket.service.WebSocketService;
/**
* @author gulnaz
*/
@Component
public class ClientSocketHandler extends TextWebSocketHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(TextWebSocketHandler.class);
private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
private final ObjectMapper objectMapper;
private final UserApplicationListService applicationService;
private final WebSocketService webSocketService;
public ClientSocketHandler(ObjectMapper objectMapper,
UserApplicationListService applicationService,
@Lazy WebSocketService webSocketService) {
this.objectMapper = objectMapper;
this.applicationService = applicationService;
this.webSocketService = webSocketService;
}
@Override
public void afterConnectionEstablished(WebSocketSession session) {
LOGGER.info("established connection {}", session);
sessions.add(session);
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
throws JsonProcessingException {
ProcessResponseDto dto = objectMapper.readValue(message.getPayload().toString(),
ProcessResponseDto.class);
LOGGER.info("received message, type = {}, sessionId = {}, principal = {}",
dto.className(), session.getId(), session.getPrincipal());
String traceId = dto.traceId();
switch (dto.className()) {
case UPDATE -> {
LOGGER.info("update by traceId = {}", traceId);
String tempPass = dto.body().tempPass();
if (StringUtils.hasText(tempPass)) {
applicationService.savePassword(traceId, tempPass);
}
else {
applicationService.saveAcceptedStatus(traceId);
}
}
case PROCESS_ERROR -> {
LOGGER.error("error by traceId = {}", traceId);
applicationService.saveError(traceId, dto.body().msg().message());
}
}
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
LOGGER.info("received text message {}", message.getPayload());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
LOGGER.error("Transport error {}", exception.getMessage());
try {
session.close();
}
catch (IOException e) {
LOGGER.error("Failed to close session on handleTransportError ", e);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
LOGGER.info("Connection closed");
if (!status.equals(CloseStatus.NORMAL)) {
try {
session.close();
}
catch (IOException e) {
LOGGER.error("Failed to close session on afterConnectionClosed ", e);
}
}
sessions.remove(session);
webSocketService.connectToSocket();
}
}

View file

@ -0,0 +1,51 @@
package ru.micord.ervu.account_applications.websocket.service;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketHttpHeaders;
import org.springframework.web.socket.client.WebSocketClient;
/**
* @author gulnaz
*/
@Service
public class WebSocketService {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketService.class);
private final WebSocketClient webSocketClient;
private final WebSocketHandler webSocketHandler;
@Value("${ervu.socket.url:wss://ervu-uat.test.gosuslugi.ru/service/notifier/gateway/notify/notifier.message.send.push}")
private String socketUrl;
public WebSocketService(WebSocketClient webSocketClient,
WebSocketHandler webSocketHandler) {
this.webSocketClient = webSocketClient;
this.webSocketHandler = webSocketHandler;
}
public void connectToSocket() {
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.set("Content-Type", "application/json");
// headers.add("Authorization", "Bearer " + securityContext.getToken());
headers.add("Authorization", "Bearer " + getToken());
try {
webSocketClient.doHandshake(webSocketHandler, headers, URI.create(socketUrl)).get();
}
catch (InterruptedException | ExecutionException e) {
LOGGER.error("Failed to connect socket");
}
}
private String getToken() {
return
"eyJraWQiOiJzc29rZXkxIiwidHlwIjoiSldUIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiI3MDhjMGJkNS05MDk0LTQyZjUtYWQwZS0xZWUyMTBlZTM5MDMiLCJhbXIiOlsiSURQX0xPQ0FMIl0sInJvbGVzIjpbInNlY3VyaXR5X2FkbWluaXN0cmF0b3IiLCLQkdCw0LfQvtCy0LDRjyDRgNC-0LvRjCDQtNC70Y8g0LDQstGC0L7RgNC40LfQsNGG0LjQuCIsItCf0L7Qu9GM0LfQvtCy0LDRgtC10LvRjNGB0LrQsNGPINGA0L7Qu9GMIiwiUzNDbGllbnQiXSwiaXNzIjoiaHR0cHM6Ly9lcnZ1LXVhdC1zc28udGVzdC5nb3N1c2x1Z2kucnUiLCJncm91cHMiOltdLCJkb21haW5faWQiOiI1NTNiOTQ0OS02NDYwLTQ4YzQtODhkNC1mMTgyODNhYmM0YTciLCJhdWQiOiJhcm0iLCJhY2NvdW50SWQiOiI3MDhjMGJkNS05MDk0LTQyZjUtYWQwZS0xZWUyMTBlZTM5MDMiLCJuYmYiOjAsImF6cCI6ImFybSIsImF1dGhfdGltZSI6MCwibmFtZSI6ItCX0LDRgNC40L_QvtCyINCt0LzQuNC70YwiLCJyZWFsbSI6ItCS0L7RgdGM0LzQvtC1INGD0L_RgNCw0LLQu9C10L3QuNC1INCT0Kgg0JLQoSDQoNCkIiwicG9zaXRpb24iOiLQnNC40LrQvtGA0LQg0J_QntCY0JEiLCJleHAiOjE3NDE4MjM4NDgsInNlc3Npb25fc3RhdGUiOiI4M2UyMzdmNS05NzQzLTQ4ZmMtYjY2MS1jN2ZhODc1NjcyNDQiLCJpYXQiOjE3NDE4MDk0NDgsIlVTRVJfSVAiOiIxNzIuMjYuMjcuMTAifQ.em1QK3Tux2WUCxk-ii0I6VApr7KKsKcnWaM7mPfS2T3VEyn9pwZuLo9KQsIaQ1kIf-xsdz3iwuWruyHn0KxUlJQB_s7gAcfLMPy_2XVwydb--XviwvZ9ZRpQbdb_uhoAojhO9_h_UGuQOaKwF3K_g4EfgyWnD9xHyrujLlV9HIoipH6Eixci9jhscelWXv76wP8sbyPyeB9YrKnWTtFyhCvMb3y3FjwSCDs7Hi7JPh0SDQUQ-o8z0h6mOPIuoo53S03AEVr40f3s9nr0APq7HoU3UW3kQfkEI9A060pqO3c2ItnJU5-FE-og0wMVEnEvXeZtHebsSP3IdssJDpKrNg";
}
}

11
pom.xml
View file

@ -16,6 +16,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<enable.version.in.url>false</enable.version.in.url>
<spring.version>5.3.39</spring.version>
<joda-time.version>2.9.2</joda-time.version>
<webbpm-platform.version>3.192.3</webbpm-platform.version>
<wbp.overall-timeout>72000</wbp.overall-timeout>
@ -286,6 +287,16 @@
<version>${webbpm-platform.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>