Конфигурация - oyboy/Jora GitHub Wiki
Настройки проекта во время разработки и деплоя несколько отличаются. Например, в первом случае требуются подробные логи, а в другом иные настройки подключения к другим сервисам. Поэтому для удобство созданы два профиля: dev и prod. Для активации профиля dev во время запуска проекта объявляется переменная среды --spring.profiles.active=dev
, а во время сборки для продакшена - SPRING_PROFILES_ACTIVE: prod
.
application-prod.yml
spring:
application:
name: Jora
freemarker:
expose-request-attributes: true
mvc:
static-path-pattern: /static/**
datasource:
generate-unique-name: false
url: jdbc:mysql://jora-mysql-db:3306/joradb
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: false
properties:
hibernate:
cache:
use_second_level_cache: false
use_query_cache: false
data:
mongodb:
host: jora-mongo-db
database: joradb
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
cache:
redis:
time-to-live: 900000 #ttl 15 min
server:
address: 0.0.0.0
port: 8081
logging:
level:
root: INFO
org.mongodb.driver: WARN
com.mongodb: WARN
#org.springframework.messaging: DEBUG
#org.springframework.web: DEBUG
Как можно заметить, некоторые переменные должны быть сокрыты от всегообщего обозрения, потому создан скрытый файл .env:
DATASOURCE_USERNAME=your_username
DATASOURCE_PASSWORD=your_password
MYSQL_ROOT_PASSWORD=your_root_password
В resources создан новый файл application-test.properties.
Единственная разница с предыдущим - обращение к другой базе: spring.datasource.url=jdbc:mysql://localhost:3306/joratestdb
Файл для перенаправления get-запроса с корневой страницы на страницу "/home" (подразумевается, что последняя является главной).
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/home");
}
}
Ситуация: пользователь оказался на удалённой странице и у него вылетела ошибка 500. Я решил обработать эту ситуацию, чтобы его автоматически перенаправляло на страницу, которая гарантированно не может быть удалена. С точки зрения разработчика удалённой страницей может оказаться запись в таблице Project.
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleSQLException(SQLSyntaxErrorException ex) {
log.info("SQL Syntax Error: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
return "redirect:/home";
}
}
Отправка файлов большого размера весьма распространённая практика. При этом желательно, чтобы в логах такая ситуация была изложена весьма кратко. Для этого создан второй перехватчик исключения.
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
public ResponseEntity<String> handleMaxSizeException(MaxUploadSizeExceededException exc) {
log.error("Upload size Error: " + exc.getMessage(), HttpStatus.PAYLOAD_TOO_LARGE);
return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
.body("Файл слишком большого размера! Максимальный размер: " + exc.getMaxUploadSize());
}
Может возникнуть ситуация, когда пользователь попадает к ресурсу, к которому он не имеет доступа. Такую ситуацию можно обработать, отправив пользователя на специальную страницу, в которой чётко будет написано, что он не имеет соответсвующих прав доступа.
@ExceptionHandler(AccessDeniedException.class)
public ModelAndView handleAccessDeniedException(AccessDeniedException ex, Model model) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/error/access-denied-error");
return modelAndView;
}
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/home", "/registration", "/static/css/**").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/home")
)
.logout((logout) -> logout.permitAll())
.exceptionHandling((exception) -> exception
.authenticationEntryPoint((request, response, authException) -> {
response.sendRedirect("/login");
})
);
return http.build();
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
public class CustomException{
//Ошибка присоединение к проекту (пользователь уже там есть)
public static class UserAlreadyJoinedException extends Exception{
public UserAlreadyJoinedException(String message) {
super(message);
}
}
//Ошибка присоединение к проекту (пользователь забанен)
public static class UserBannedException extends Exception{
public UserBannedException(String message){
super(message);
}
}
//Выбрасывается, когда размер создаваемого объекта превышает допустимое значение
//Выбрасывается функцией createTag в groupService
public static class LargeSizeException extends Exception{
public LargeSizeException(String message) {super(message);}
}
//Проверка на наличие уже созданного объекта. Например, может быть вызван также методом createTag или
//методом sendNotificationTo в notificationService, если такой запрос уже существует
public static class ObjectExistsException extends Exception{
public ObjectExistsException(String message){super(message);}
}
//Очевидно, если пользователь для какой-то задачи не был найден. Выбрасывается в методе sendHelp в taskService
public static class UserNotFoundException extends Exception{
public UserNotFoundException(String message){super(message);}
}
}
Для работы с сокетами нужно в Spring создать конфигурационный файл, в котором будут указаны енд-поинты и включены брокеры сообщений.
package com.main.Jora.notifications;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
}
Логика подключения в найстроках приложения к базе Redis является устаревшей, потому требуется создать отдельный класс с необходимыи bean-компонентами.
@Configuration
@RequiredArgsConstructor
@EnableCaching
public class RedisConfig {
//В зависимости от выбранного профиля (dev или prod) будет использоваться ссылка на нужный хост
@Value("${API_REDIS_HOST:localhost}")
private String redisHost;
@Bean
public RedisTemplate<Long, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Long, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
//Настройки подключения
@Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHost, 6379);
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
}