diff --git a/build.gradle b/build.gradle index 780477a..d541f1a 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,13 @@ dependencies { testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } + + runtimeOnly('com.h2database:h2') + + compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2' + compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2' + compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1' + compile group: 'commons-io', name: 'commons-io', version: '2.4' } test { diff --git a/settings.gradle b/settings.gradle index 5e6ed4e..0357a43 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'recordary' +rootProject.name = 'recordary' \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/RecordaryApplication.java b/src/main/java/com/fairy_pitt/recordary/RecordaryApplication.java index 7efa51f..604a2bb 100644 --- a/src/main/java/com/fairy_pitt/recordary/RecordaryApplication.java +++ b/src/main/java/com/fairy_pitt/recordary/RecordaryApplication.java @@ -5,9 +5,7 @@ @SpringBootApplication public class RecordaryApplication { - public static void main(String[] args) { SpringApplication.run(RecordaryApplication.class, args); } - -} +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/model/Users.java b/src/main/java/com/fairy_pitt/recordary/common/entity/Users.java similarity index 90% rename from src/main/java/com/fairy_pitt/recordary/model/Users.java rename to src/main/java/com/fairy_pitt/recordary/common/entity/Users.java index 88ccf00..41d6ddf 100644 --- a/src/main/java/com/fairy_pitt/recordary/model/Users.java +++ b/src/main/java/com/fairy_pitt/recordary/common/entity/Users.java @@ -1,4 +1,4 @@ -package com.fairy_pitt.recordary.model; +package com.fairy_pitt.recordary.common.entity; import lombok.Data; import javax.persistence.*; diff --git a/src/main/java/com/fairy_pitt/recordary/repository/UsersRepository.java b/src/main/java/com/fairy_pitt/recordary/common/repository/UsersRepository.java similarity index 79% rename from src/main/java/com/fairy_pitt/recordary/repository/UsersRepository.java rename to src/main/java/com/fairy_pitt/recordary/common/repository/UsersRepository.java index 5d20dd1..e3358b3 100644 --- a/src/main/java/com/fairy_pitt/recordary/repository/UsersRepository.java +++ b/src/main/java/com/fairy_pitt/recordary/common/repository/UsersRepository.java @@ -1,6 +1,6 @@ -package com.fairy_pitt.recordary.repository; +package com.fairy_pitt.recordary.common.repository; -import com.fairy_pitt.recordary.model.Users; +import com.fairy_pitt.recordary.common.entity.Users; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/fairy_pitt/recordary/config/database/DataSourceConfig.java b/src/main/java/com/fairy_pitt/recordary/config/database/DataSourceConfig.java new file mode 100644 index 0000000..630ed19 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/database/DataSourceConfig.java @@ -0,0 +1,80 @@ +package com.fairy_pitt.recordary.config.database; + +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +@Configuration +@EnableJpaRepositories(basePackages = "com.fairy_pitt.recordary.common") +@EnableTransactionManagement +@EnableJpaAuditing +@EnableConfigurationProperties({MasterDataSourceConfig.class, SlaveDataSourceConfig.class}) +@Profile("!test") +public class DataSourceConfig { + private static final String PROMOTION_PERSISTENCE_UNIT_NAME = "recordary"; + + @Bean + @Primary + public DataSource dataSource(MasterDataSourceConfig masterDataSourceConfig, SlaveDataSourceConfig slaveDataSourceConfig) { + + DataSource masterDataSource = new HikariDataSource(masterDataSourceConfig); + DataSource slaveDataSource = new HikariDataSource(slaveDataSourceConfig); + + Map dataSourceMap = new HashMap<>(); + dataSourceMap.put("slave", slaveDataSource); + dataSourceMap.put("master", masterDataSource); + + ReplicationRoutingDataSource routingDataSource = new ReplicationRoutingDataSource(); + routingDataSource.setTargetDataSources(dataSourceMap); + routingDataSource.setDefaultTargetDataSource(masterDataSource); + routingDataSource.afterPropertiesSet(); + + return new LazyConnectionDataSourceProxy(routingDataSource); + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaProperties jpaProperties) { + + HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); + vendorAdapter.setShowSql(true); + + LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); + factory.setJpaVendorAdapter(vendorAdapter); + factory.setDataSource(dataSource); + factory.setPackagesToScan("com.fairy_pitt.recordary.common"); + factory.setPersistenceUnitName(PROMOTION_PERSISTENCE_UNIT_NAME); + + Properties properties = new Properties(); + HibernateSettings hibernateSettings = new HibernateSettings() + .ddlAuto(() -> "none"); + properties.putAll(new HibernateProperties().determineHibernateProperties(jpaProperties.getProperties(), hibernateSettings)); + factory.setJpaProperties(properties); + + return factory; + } + + @Bean + public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + return new JpaTransactionManager(entityManagerFactory); + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/database/MasterDataSourceConfig.java b/src/main/java/com/fairy_pitt/recordary/config/database/MasterDataSourceConfig.java new file mode 100644 index 0000000..d660543 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/database/MasterDataSourceConfig.java @@ -0,0 +1,17 @@ +package com.fairy_pitt.recordary.config.database; + +import com.zaxxer.hikari.HikariConfig; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.PropertySource; + +@Getter +@Setter +@Configuration +@PropertySource("classpath:/database/database-${spring.profiles.active}.properties") +@ConfigurationProperties(prefix = "datasource.master") +@Profile("!test") +public class MasterDataSourceConfig extends HikariConfig { } \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/database/ReplicationRoutingDataSource.java b/src/main/java/com/fairy_pitt/recordary/config/database/ReplicationRoutingDataSource.java new file mode 100644 index 0000000..b37a27f --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/database/ReplicationRoutingDataSource.java @@ -0,0 +1,19 @@ +package com.fairy_pitt.recordary.config.database; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +@Slf4j +public class ReplicationRoutingDataSource extends AbstractRoutingDataSource { + @Override + protected Object determineCurrentLookupKey() { + if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + log.info("slave is selected!!!"); + return "slave"; + } + + log.info("master is selected!!!"); + return "master"; + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/database/SlaveDataSourceConfig.java b/src/main/java/com/fairy_pitt/recordary/config/database/SlaveDataSourceConfig.java new file mode 100644 index 0000000..5902e78 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/database/SlaveDataSourceConfig.java @@ -0,0 +1,17 @@ +package com.fairy_pitt.recordary.config.database; + +import com.zaxxer.hikari.HikariConfig; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.PropertySource; + +@Getter +@Setter +@Configuration +@PropertySource("classpath:/database/database-${spring.profiles.active}.properties") +@ConfigurationProperties(prefix = "datasource.slave") +@Profile("!test") +public class SlaveDataSourceConfig extends HikariConfig { } \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/exception/ExceptionAdvice.java b/src/main/java/com/fairy_pitt/recordary/config/exception/ExceptionAdvice.java new file mode 100644 index 0000000..4fe34f8 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/exception/ExceptionAdvice.java @@ -0,0 +1,20 @@ +package com.fairy_pitt.recordary.config.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Slf4j +@ControllerAdvice +public class ExceptionAdvice { + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public String handleDefaultException(Exception ex) { + log.error("{}", ex.toString(), ex); + +// return BaseResponse.error(ex.getMessage()); + return ex.getMessage(); + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/helper/JsonHelper.java b/src/main/java/com/fairy_pitt/recordary/config/helper/JsonHelper.java new file mode 100644 index 0000000..e4832d2 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/helper/JsonHelper.java @@ -0,0 +1,92 @@ +package com.fairy_pitt.recordary.config.helper; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JsonHelper { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + // QuattroServiceAdaptor.getOrderCompleteInfo()의 approvedAt serialize할때 사용 + MAPPER.registerModule(createLocalDateTimeSerializeModule()); + } + + // LocalDateTime to milliseconds + private static JavaTimeModule createLocalDateTimeSerializeModule() { + JavaTimeModule javaTimeModule = new JavaTimeModule(); + + javaTimeModule.addSerializer(LocalDateTime.class, new JsonSerializer() { + @Override + public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeNumber(value.atZone(ZoneOffset.UTC).toInstant().toEpochMilli()); + } + }); + + return javaTimeModule; + } + + /** + * JsonNode를 Class로 반환 + * + * @@param JsonNode. Class + * @@return Class + */ + public static T getClassByJsonNode(JsonNode j, Class t) { + return MAPPER.convertValue(j, t); + } + + public static JsonNode jsonNodeFromObject(Object object) { + return MAPPER.convertValue(object, JsonNode.class); + } + + public static JsonNode convertStringToJsonNode(String plainText) { + try { + return MAPPER.readTree(plainText); + } catch (IOException e) { + log.error("{} : {}", e.getMessage(), plainText, e); + return null; + } + } + + public static T convertStringToObject(String plainText, Class t) { + try { + return MAPPER.readValue(plainText, t); + } catch (IOException e) { + log.error("{} : {}", e.getMessage(), plainText, e); + return null; + } + } + + public static T convertStringToObject(String plainText, TypeReference t) { + try { + return (T) MAPPER.readValue(plainText, t); + } catch (IOException e) { + log.error("{} : {}", e.getMessage(), plainText, e); + return null; + } + } + + public static String getStringFromObject(Object obj) { + try { + return MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.error("{} : {}", e.getMessage(), obj, e); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/logging/BodyFetchedHttpRequestWrapper.java b/src/main/java/com/fairy_pitt/recordary/config/logging/BodyFetchedHttpRequestWrapper.java new file mode 100644 index 0000000..517d084 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/logging/BodyFetchedHttpRequestWrapper.java @@ -0,0 +1,59 @@ +package com.fairy_pitt.recordary.config.logging; + +import org.apache.commons.io.IOUtils; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class BodyFetchedHttpRequestWrapper extends HttpServletRequestWrapper { + private byte[] bodyData; + + public BodyFetchedHttpRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + InputStream is = super.getInputStream(); + bodyData = IOUtils.toByteArray(is); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream bis = new ByteArrayInputStream(bodyData); + return new SimpleServletInputStream(bis); + } +} + +class SimpleServletInputStream extends ServletInputStream { + private InputStream is; + + SimpleServletInputStream(InputStream bis) { + is = bis; + } + + @Override + public int read() throws IOException { + return is.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return is.read(b); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(final ReadListener readListener) { + + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/logging/IOLoggingFilter.java b/src/main/java/com/fairy_pitt/recordary/config/logging/IOLoggingFilter.java new file mode 100644 index 0000000..be05ba6 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/logging/IOLoggingFilter.java @@ -0,0 +1,49 @@ +package com.fairy_pitt.recordary.config.logging; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jboss.logging.MDC; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.UUID; + +@Slf4j +@Component +@Profile("!test") +@RequiredArgsConstructor +public class IOLoggingFilter implements Filter { + private final OperationLogger operationLogger; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { + LocalDateTime startTime = LocalDateTime.now(); + + BodyFetchedHttpRequestWrapper request = new BodyFetchedHttpRequestWrapper((HttpServletRequest) servletRequest); + + MDC.put("REQUEST_ID", UUID.randomUUID().toString().substring(0, 7)); + OutputStreamCopiedHttpResponseWrapper response = new OutputStreamCopiedHttpResponseWrapper((HttpServletResponse) servletResponse); + + try { + chain.doFilter(request, response); + + response.flushBuffer(); + } catch (Exception ex) { + log.error("Error Applying Filter. : {}", ex.getMessage(), ex); + } finally { + String uri = ((HttpServletRequest) servletRequest).getRequestURI(); + + if (!uri.contains("swagger")) { + operationLogger.info(startTime, request, response); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/logging/LoggingHelper.java b/src/main/java/com/fairy_pitt/recordary/config/logging/LoggingHelper.java new file mode 100644 index 0000000..1bda769 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/logging/LoggingHelper.java @@ -0,0 +1,110 @@ +package com.fairy_pitt.recordary.config.logging; + +import com.fairy_pitt.recordary.config.helper.JsonHelper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + +public class LoggingHelper { + public static boolean isWebJar(HttpServletRequest request) { + return request.getRequestURI().startsWith("/webjars"); + } + + public static boolean isSwagger(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + return requestURI.startsWith("/swagger") || + requestURI.startsWith("/v2/api-docs"); + } + + public static String getParametersFrom(ServletRequest request) { + return Collections.list(request.getParameterNames()) + .stream() + .map(name -> StringUtils.join(name, ":", request.getParameter(name))) + .reduce((x, y) -> StringUtils.join(x, ", ", y)) + .orElse(""); + } + + public static Map getHeadersFrom(BodyFetchedHttpRequestWrapper request) { + Map map = new HashMap<>(); + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String key = (String) headerNames.nextElement(); + String value = request.getHeader(key); + map.put(key, value); + } + + return map; + } + + public static Map getHeadersFrom(OutputStreamCopiedHttpResponseWrapper response) { + Map map = new HashMap<>(); + + Collection headerNames = response.getHeaderNames(); + headerNames.forEach(key -> { + String value = response.getHeader(key); + map.put(key, value); + }); + + return map; + } + + public static String getBodyFrom(HttpServletRequest request) throws IOException { + String requestBody = getPlainBodyFrom(request.getInputStream()); + if (requestBody.isEmpty()) + return ""; + + JsonNode requestJsonBody = JsonHelper.convertStringToJsonNode(requestBody); + + return JsonHelper.getStringFromObject(requestJsonBody); + } + + public static String getBodyFrom(OutputStreamCopiedHttpResponseWrapper response) throws IOException { + return new String(response.getCopy(), response.getResponse().getCharacterEncoding()); + } + + private static String getPlainBodyFrom(InputStream inputStream) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = null; + + try { + if (inputStream != null) { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + char[] charBuffer = new char[128]; + int bytesRead; + while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { + stringBuilder.append(charBuffer, 0, bytesRead); + } + } + } catch (IOException ex) { + throw ex; + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException ex) { + throw ex; + } + } + } + return stringBuilder.toString(); + } + + private static void change(JsonNode parent, String fieldName, String newValue) { + if (parent.has(fieldName)) { + ((ObjectNode) parent).put(fieldName, newValue); + } + + for (JsonNode child : parent) { + change(child, fieldName, newValue); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/logging/OperationLogger.java b/src/main/java/com/fairy_pitt/recordary/config/logging/OperationLogger.java new file mode 100644 index 0000000..34696ad --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/logging/OperationLogger.java @@ -0,0 +1,56 @@ +package com.fairy_pitt.recordary.config.logging; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +@Slf4j +@Component +public class OperationLogger { + private static final String LOGGING_FORMAT = "\n" + + "Started {} {} for {} at {}\n" + + "Headers : {}\n" + + "Params : {}\n" + + "Request body : {}\n" + + "Response body : {}\n" + + "Completed {} in {}ms"; + + private static final String ERROR_LOGGING_FORMAT = "{} {} at {}\n" + + "Request body : {}\n" + + "Response body : {}\n" + + "Completed {} in {}ms"; + + public void info(LocalDateTime startTime, + BodyFetchedHttpRequestWrapper request, + OutputStreamCopiedHttpResponseWrapper response) { + try { + if (ChronoUnit.SECONDS.between(startTime, LocalDateTime.now()) > 10) { + log.error(ERROR_LOGGING_FORMAT, + request.getMethod(), + request.getRequestURI(), + startTime, + LoggingHelper.getBodyFrom(request), + LoggingHelper.getBodyFrom(response), + response.getStatus(), + ChronoUnit.MILLIS.between(startTime, LocalDateTime.now())); + } + + log.info(LOGGING_FORMAT, + request.getMethod(), + request.getRequestURI(), + request.getRemoteAddr(), + startTime, + LoggingHelper.getHeadersFrom(request), + LoggingHelper.getParametersFrom(request), + LoggingHelper.getBodyFrom(request), + LoggingHelper.getBodyFrom(response), + response.getStatus(), + ChronoUnit.MILLIS.between(startTime, LocalDateTime.now())); + } catch (IOException e) { + log.error("{}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/logging/OutputStreamCopiedHttpResponseWrapper.java b/src/main/java/com/fairy_pitt/recordary/config/logging/OutputStreamCopiedHttpResponseWrapper.java new file mode 100644 index 0000000..3175da3 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/logging/OutputStreamCopiedHttpResponseWrapper.java @@ -0,0 +1,92 @@ +package com.fairy_pitt.recordary.config.logging; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import java.io.*; + +public class OutputStreamCopiedHttpResponseWrapper extends HttpServletResponseWrapper { + private ServletOutputStream outputStream; + private PrintWriter writer; + private OutputStreamCopier copier; + + public OutputStreamCopiedHttpResponseWrapper(HttpServletResponse response) { + super(response); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + if (writer != null) { + throw new IllegalStateException("getWriter() has already been called on this response."); + } + + if (outputStream == null) { + outputStream = getResponse().getOutputStream(); + copier = new OutputStreamCopier(outputStream); + } + + return copier; + } + + @Override + public PrintWriter getWriter() throws IOException { + if (outputStream != null) { + throw new IllegalStateException("getOutputStream() has already been called on this response."); + } + + if (writer == null) { + copier = new OutputStreamCopier(getResponse().getOutputStream()); + writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true); + } + + return writer; + } + + @Override + public void flushBuffer() throws IOException { + if (writer != null) { + writer.flush(); + } else if (outputStream != null) { + copier.flush(); + } + } + + public byte[] getCopy() { + if (copier != null) { + return copier.getCopy(); + } else { + return new byte[0]; + } + } +} + +class OutputStreamCopier extends ServletOutputStream { + private OutputStream outputStream; + private ByteArrayOutputStream copy; + + public OutputStreamCopier(OutputStream outputStream) { + this.outputStream = outputStream; + this.copy = new ByteArrayOutputStream(1024); + } + + @Override + public void write(int b) throws IOException { + outputStream.write(b); + copy.write(b); + } + + public byte[] getCopy() { + return copy.toByteArray(); + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setWriteListener(WriteListener listener) { + + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/config/swagger/SwaggerConfig.java b/src/main/java/com/fairy_pitt/recordary/config/swagger/SwaggerConfig.java new file mode 100644 index 0000000..3806f63 --- /dev/null +++ b/src/main/java/com/fairy_pitt/recordary/config/swagger/SwaggerConfig.java @@ -0,0 +1,65 @@ +package com.fairy_pitt.recordary.config.swagger; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger.web.UiConfiguration; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.nio.charset.Charset; + +@Configuration +@EnableSwagger2 +@Profile({"local"}) +public class SwaggerConfig extends WebMvcConfigurationSupport { + @Bean + public Docket docket() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.basePackage("com.fairy_pitt.recordary.endpoint")) + .build() + .apiInfo(metaData()); + } + + private ApiInfo metaData() { + return new ApiInfoBuilder() + .title("프로젝트 이름") + .version("1.0.0") + .build(); + } + + @Override + protected void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("swagger-ui.html") + .addResourceLocations("classpath:/META-INF/resources/"); + + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + } + + @Bean + public HttpMessageConverter responseBodyConverter() { + return new StringHttpMessageConverter(Charset.forName("UTF-8")); + } + + @Bean + public UiConfiguration uiConfiguration() { + return new UiConfiguration(null + , "list" + , "alpha" + , "schema" + , UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS + , true + , true + , 10000L); + } +} \ No newline at end of file diff --git a/src/main/java/com/fairy_pitt/recordary/controller/MainController.java b/src/main/java/com/fairy_pitt/recordary/endpoint/main/MainController.java similarity index 92% rename from src/main/java/com/fairy_pitt/recordary/controller/MainController.java rename to src/main/java/com/fairy_pitt/recordary/endpoint/main/MainController.java index 6979250..41a4fb0 100644 --- a/src/main/java/com/fairy_pitt/recordary/controller/MainController.java +++ b/src/main/java/com/fairy_pitt/recordary/endpoint/main/MainController.java @@ -1,14 +1,12 @@ -package com.fairy_pitt.recordary.controller; +package com.fairy_pitt.recordary.endpoint.main; -import com.fairy_pitt.recordary.model.Users; -import com.fairy_pitt.recordary.service.User.UsersInfoService; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fairy_pitt.recordary.common.entity.Users; +import com.fairy_pitt.recordary.endpoint.user.service.UsersInfoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; -import javax.transaction.Transactional; import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/src/main/java/com/fairy_pitt/recordary/controller/UsersController.java b/src/main/java/com/fairy_pitt/recordary/endpoint/user/UsersController.java similarity index 82% rename from src/main/java/com/fairy_pitt/recordary/controller/UsersController.java rename to src/main/java/com/fairy_pitt/recordary/endpoint/user/UsersController.java index bef95b5..25ccf17 100644 --- a/src/main/java/com/fairy_pitt/recordary/controller/UsersController.java +++ b/src/main/java/com/fairy_pitt/recordary/endpoint/user/UsersController.java @@ -1,17 +1,13 @@ -package com.fairy_pitt.recordary.controller; - -import com.fairy_pitt.recordary.model.Users; -import com.fairy_pitt.recordary.repository.UsersRepository; -import com.fairy_pitt.recordary.service.User.JoinService; -import com.fairy_pitt.recordary.service.User.LoginService; -import com.fairy_pitt.recordary.service.User.UsersInfoService; -import com.sun.org.apache.xpath.internal.operations.Bool; -import org.apache.tomcat.util.net.openssl.ciphers.Authentication; +package com.fairy_pitt.recordary.endpoint.user; + +import com.fairy_pitt.recordary.common.entity.Users; +import com.fairy_pitt.recordary.common.repository.UsersRepository; +import com.fairy_pitt.recordary.endpoint.user.service.JoinService; +import com.fairy_pitt.recordary.endpoint.user.service.LoginService; +import com.fairy_pitt.recordary.endpoint.user.service.UsersInfoService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpSession; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/fairy_pitt/recordary/service/User/JoinService.java b/src/main/java/com/fairy_pitt/recordary/endpoint/user/service/JoinService.java similarity index 72% rename from src/main/java/com/fairy_pitt/recordary/service/User/JoinService.java rename to src/main/java/com/fairy_pitt/recordary/endpoint/user/service/JoinService.java index 8ffc8f5..79a2494 100644 --- a/src/main/java/com/fairy_pitt/recordary/service/User/JoinService.java +++ b/src/main/java/com/fairy_pitt/recordary/endpoint/user/service/JoinService.java @@ -1,14 +1,9 @@ -package com.fairy_pitt.recordary.service.User; +package com.fairy_pitt.recordary.endpoint.user.service; -import com.fairy_pitt.recordary.model.Users; -import com.fairy_pitt.recordary.repository.UsersRepository; +import com.fairy_pitt.recordary.common.entity.Users; +import com.fairy_pitt.recordary.common.repository.UsersRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; - -import java.util.HashMap; -import java.util.Map; @Service public class JoinService { diff --git a/src/main/java/com/fairy_pitt/recordary/service/User/LoginService.java b/src/main/java/com/fairy_pitt/recordary/endpoint/user/service/LoginService.java similarity index 81% rename from src/main/java/com/fairy_pitt/recordary/service/User/LoginService.java rename to src/main/java/com/fairy_pitt/recordary/endpoint/user/service/LoginService.java index cfdcdbd..90fc41e 100644 --- a/src/main/java/com/fairy_pitt/recordary/service/User/LoginService.java +++ b/src/main/java/com/fairy_pitt/recordary/endpoint/user/service/LoginService.java @@ -1,7 +1,7 @@ -package com.fairy_pitt.recordary.service.User; +package com.fairy_pitt.recordary.endpoint.user.service; -import com.fairy_pitt.recordary.model.Users; -import com.fairy_pitt.recordary.repository.UsersRepository; +import com.fairy_pitt.recordary.common.entity.Users; +import com.fairy_pitt.recordary.common.repository.UsersRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/fairy_pitt/recordary/service/User/UserPasswordHashService.java b/src/main/java/com/fairy_pitt/recordary/endpoint/user/service/UserPasswordHashService.java similarity index 93% rename from src/main/java/com/fairy_pitt/recordary/service/User/UserPasswordHashService.java rename to src/main/java/com/fairy_pitt/recordary/endpoint/user/service/UserPasswordHashService.java index a2c5d35..15d9451 100644 --- a/src/main/java/com/fairy_pitt/recordary/service/User/UserPasswordHashService.java +++ b/src/main/java/com/fairy_pitt/recordary/endpoint/user/service/UserPasswordHashService.java @@ -1,4 +1,4 @@ -package com.fairy_pitt.recordary.service.User; +package com.fairy_pitt.recordary.endpoint.user.service; import org.springframework.stereotype.Service; import java.security.MessageDigest; diff --git a/src/main/java/com/fairy_pitt/recordary/service/User/UsersInfoService.java b/src/main/java/com/fairy_pitt/recordary/endpoint/user/service/UsersInfoService.java similarity index 81% rename from src/main/java/com/fairy_pitt/recordary/service/User/UsersInfoService.java rename to src/main/java/com/fairy_pitt/recordary/endpoint/user/service/UsersInfoService.java index a05c330..14fd239 100644 --- a/src/main/java/com/fairy_pitt/recordary/service/User/UsersInfoService.java +++ b/src/main/java/com/fairy_pitt/recordary/endpoint/user/service/UsersInfoService.java @@ -1,13 +1,10 @@ -package com.fairy_pitt.recordary.service.User; +package com.fairy_pitt.recordary.endpoint.user.service; -import com.fairy_pitt.recordary.model.Users; -import com.fairy_pitt.recordary.repository.UsersRepository; +import com.fairy_pitt.recordary.common.entity.Users; +import com.fairy_pitt.recordary.common.repository.UsersRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; - @Service public class UsersInfoService { @Autowired private UsersRepository usersRepository; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d77ab2a..84008f0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,24 +1,40 @@ -spring: +server: + port: 8080 + error: + whitelabel: + enabled: false + +spring: profiles: active: local - datasource: - driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/recordary?serverTimezone=UTC - username: recordary-admin - password: recordary +--- +spring: + profiles: local - jpa: - database-platform: org.hibernate.dialect.MySQL5InnoDBDialect - open-in-view: false - hibernate: - ddl-auto: create - use-new-id-generator-mappings: false +# database -> 파일로 이동 -server: - error: - whitelabel: - enabled: false +# datasource: +# driverClassName: com.mysql.cj.jdbc.Driver +# url: jdbc:mysql://localhost:3306/recordary?serverTimezone=UTC +# username: recordary-admin +# password: recordary +# +# jpa: +# database-platform: org.hibernate.dialect.MySQL5InnoDBDialect +# open-in-view: false +# hibernate: +# ddl-auto: create +# use-new-id-generator-mappings: false + + h2: + console: + enabled: true + path: /console - port: 8888 \ No newline at end of file + jpa: + database-platform: H2 + show-sql: false + hibernate: + ddl-auto: create-drop \ No newline at end of file diff --git a/src/main/resources/database/database-local.properties b/src/main/resources/database/database-local.properties new file mode 100644 index 0000000..2451482 --- /dev/null +++ b/src/main/resources/database/database-local.properties @@ -0,0 +1,18 @@ +## master db +datasource.master.driver-class-name=org.h2.Driver +datasource.master.jdbc-url=jdbc:h2:~/test;AUTO_SERVER=TRUE +datasource.master.user-name=sa +datasource.master.password= +datasource.master.maximum-pool-size=10 +datasource.master.minimum-idle=10 +datasource.master.read-only=false + +## slave db +datasource.slave.driver-class-name=org.h2.Driver +datasource.slave.jdbc-url=jdbc:h2:~/test;AUTO_SERVER=TRUE +datasource.slave.user-name=sa +datasource.slave.password= +datasource.slave.maximum-pool-size=10 +datasource.slave.minimum-idle=10 +datasource.slave.read-only=true +logging.level.org.hibernate.SQL=info \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..4ebdd5d --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,49 @@ + + + + + + + + + + logs/application.${running.port:-default}.log + + logs/application.${running.port:-default}.log.%i.%d{yyyy-MM-dd}.gz + 500MB + 180 + + + [%-5level:%X{REQUEST_CUSTOM_ID}] [${HOSTNAME}:%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%logger{5}:%method:%L] %msg%n + + + + + + [%-5level:%X{REQUEST_CUSTOM_ID}] [${HOSTNAME}:%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%logger{5}:%method:%L] %msg%n + utf8 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file