Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature join #5

Merged
merged 24 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
94d67fe
Chore: 라이브러리 μ˜μ‘΄μ„± μ£Όμž…κ³Ό, ν”„λ‘œμ νŠΈ 디렉터리λͺ… λ³€κ²…
CU7103 Nov 14, 2024
251a7b5
Feat: νšŒμ›κ°€μž… κΈ°λŠ₯ 생성
CU7103 Nov 14, 2024
80c6187
Feat: ResponseEntity 응닡 μ»€μŠ€ν…€ 및 νšŒμ›κ°€μž…μ‹œ μ•”ν˜Έν™” κΈ°λŠ₯μΆ”κ°€
CU7103 Nov 14, 2024
ab31016
Init: P6spy 적용
CU7103 Nov 14, 2024
b8a4576
Init: log4j2 적용
CU7103 Nov 14, 2024
70444a7
Test: Testcontainerλ₯Ό μ‚¬μš©ν•˜μ—¬ ν…ŒμŠ€νŠΈ ν™˜κ²½ ꡬ좕
CU7103 Nov 14, 2024
7e758a9
Test: νšŒμ›κ°€μž… ν…ŒμŠ€νŠΈ 둜직 μž‘μ„±
CU7103 Nov 16, 2024
dcf0726
Refactor: νšŒμ›κ°€μž… κ΄€λ ¨ 클래슀 μˆ˜μ •
CU7103 Nov 16, 2024
a591383
Chore: λΆˆν•„μš”ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œ μ‚­μ œ
CU7103 Nov 17, 2024
2c7b6e4
Refactor: νšŒμ›κ°€μž… 둜직 λ¦¬νŽ™ν„°λ§ 진행
CU7103 Nov 17, 2024
911eea4
Refactor: UserMapper λ¦¬νŽ™ν„°λ§
CU7103 Nov 27, 2024
d7ceb07
Refactor: λ§€μ§λ„˜λ²„ λ³€ν™˜
CU7103 Nov 27, 2024
8cb857e
Refactor: μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” api μ‚­μ œ
CU7103 Nov 30, 2024
a10d23b
Refactor: Message 클래슀 제거
CU7103 Nov 30, 2024
d0f6463
Refactor: DTO 클래슀 λ¦¬νŽ™ν„°λ§
CU7103 Nov 30, 2024
a11dd83
Refactor: UserService 클래슀 λ¦¬νŽ™ν„°λ§
CU7103 Nov 30, 2024
f1391e2
Feat: enum 클래슀 생성
CU7103 Nov 30, 2024
d862cda
Chore: 주석 처리된 μ„€μ • μ‚­μ œ
CU7103 Nov 30, 2024
bd9a434
Refactor: User 클래슀 μˆ˜μ •
CU7103 Nov 30, 2024
24d1dc2
Chore: λ³€κ²½λœ 클래슀λͺ…에 μ˜ν•œ μˆ˜μ • 진행
CU7103 Nov 30, 2024
5eeba06
Refactor: enum 클래슀 μˆ˜μ •
CU7103 Dec 2, 2024
9c921f6
Refactor: ResponseEntity return type μˆ˜μ •
CU7103 Dec 2, 2024
8f0a1d5
Refactor: enum type으둜의 λ³€κ²½
CU7103 Dec 2, 2024
1d20d60
Refactor: dto μ ‘κ·Όμ œμ–΄μž μΆ”κ°€ 및 μˆ˜μ •
CU7103 Dec 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ configurations {
compileOnly {
extendsFrom annotationProcessor
}
configureEach {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
CUCU7103 marked this conversation as resolved.
Show resolved Hide resolved
}

}

repositories {
Expand All @@ -26,10 +30,36 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

// log4j2 적용
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
// p6spy μ„€μ •
implementation 'p6spy:p6spy:3.9.1'
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'


implementation 'org.mindrot:jbcrypt:0.4'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok' // ν…ŒμŠ€νŠΈ μ˜μ‘΄μ„± μΆ”κ°€
testAnnotationProcessor 'org.projectlombok:lombok' // ν…ŒμŠ€νŠΈ μ˜μ‘΄μ„± μΆ”κ°€

runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'

developmentOnly 'org.springframework.boot:spring-boot-devtools'
CUCU7103 marked this conversation as resolved.
Show resolved Hide resolved

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// TestContainers Core
testImplementation 'org.testcontainers:testcontainers:1.20.3'
// TestContainers MariaDB λͺ¨λ“ˆ
testImplementation 'org.testcontainers:mariadb:1.19.0'
// TestContainers Spring Extension
testImplementation 'org.testcontainers:junit-jupiter:1.19.0'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package spring.academyPlatform;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
CUCU7103 marked this conversation as resolved.
Show resolved Hide resolved
@SpringBootApplication
public class AcademyPlatformApplication {

public static void main(String[] args) {
SpringApplication.run(AcademyPlatformApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package spring.academyPlatform.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Profile({"default", "test"})
@Configuration
public class P6SpyConfig {
@Bean
public P6SpyEventListener p6SpyCustomEventListener() {
return new P6SpyEventListener();
}

@Bean
public P6SpyFormatter p6SpyCustomFormatter() {
return new P6SpyFormatter();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package spring.academyPlatform.global.config;

import java.sql.SQLException;

import com.p6spy.engine.common.ConnectionInformation;
import com.p6spy.engine.event.JdbcEventListener;
import com.p6spy.engine.spy.P6SpyOptions;

public class P6SpyEventListener extends JdbcEventListener {
@Override
public void onAfterGetConnection(ConnectionInformation connectionInformation, SQLException e) {
P6SpyOptions.getActiveInstance().setLogMessageFormat(P6SpyFormatter.class.getName());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package spring.academyPlatform.global.config;

import java.util.Locale;

import org.hibernate.engine.jdbc.internal.FormatStyle;

import com.p6spy.engine.logging.Category;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;

public class P6SpyFormatter implements MessageFormattingStrategy {
private static final String NEW_LINE = "\n";
private static final String TAP = "\t";
private static final String CREATE = "create";
private static final String ALTER = "alter";
private static final String DROP = "drop";
private static final String COMMENT = "comment";

@Override
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared,
String sql, String url) {
if (sql.trim().isEmpty()) {
return formatByCommand(category);
}
return formatBySql(sql, category) + getAdditionalMessages(elapsed);
}

private static String formatByCommand(String category) {
return NEW_LINE
+ "Execute Command : "
+ NEW_LINE
+ NEW_LINE
+ TAP
+ category
+ NEW_LINE
+ NEW_LINE
+ "----------------------------------------------------------------------------------------------------";
}

private String formatBySql(String sql, String category) {
if (isStatementDDL(sql, category)) {
return NEW_LINE
+ "Execute DDL : "
+ NEW_LINE
+ FormatStyle.DDL
.getFormatter()
.format(sql);
}
return NEW_LINE
+ "Execute DML : "
+ NEW_LINE
+ FormatStyle.BASIC
.getFormatter()
.format(sql);
}

private String getAdditionalMessages(long elapsed) {
return NEW_LINE
+ NEW_LINE
+ String.format("Execution Time: %s ms", elapsed)
+ NEW_LINE
+ "----------------------------------------------------------------------------------------------------";
}

private boolean isStatementDDL(String sql, String category) {
return isStatement(category) && isDDL(sql.trim().toLowerCase(Locale.ROOT));
}

private boolean isStatement(String category) {
return Category.STATEMENT.getName().equals(category);
}

private boolean isDDL(String lowerSql) {
return lowerSql.startsWith(CREATE)
|| lowerSql.startsWith(ALTER)
|| lowerSql.startsWith(DROP)
|| lowerSql.startsWith(COMMENT);
}
}
5 changes: 5 additions & 0 deletions src/main/java/spring/academyPlatform/global/model/YnCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package spring.academyPlatform.global.model;

public enum YnCode {
Y, N;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package spring.academyPlatform.global.util;

import org.mindrot.jbcrypt.BCrypt;
import org.springframework.stereotype.Component;

@Component
public class BcryptPasswordEncryptor {

static int cost = 8; // 반볡 횟수 (보톡 10~12 ꢌμž₯)

// Bcryptλ₯Ό μ‚¬μš©ν•΄ λΉ„λ°€λ²ˆν˜Έλ₯Ό ν•΄μ‹±ν•˜λŠ” λ©”μ„œλ“œ
public static String hashPassword(String password) {

return BCrypt.hashpw(password, BCrypt.gensalt(cost));
}

// ν•΄μ‹±λœ λΉ„λ°€λ²ˆν˜Έμ™€ μ‚¬μš©μžκ°€ μž…λ ₯ν•œ λΉ„λ°€λ²ˆν˜Έλ₯Ό κ²€μ¦ν•˜λŠ” λ©”μ„œλ“œ
public static boolean checkPassword(String password, String hashedPassword) {
return BCrypt.checkpw(password, hashedPassword);
CUCU7103 marked this conversation as resolved.
Show resolved Hide resolved
}

}
36 changes: 36 additions & 0 deletions src/main/java/spring/academyPlatform/user/api/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package spring.academyPlatform.user.api;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import spring.academyPlatform.user.application.UserService;
import spring.academyPlatform.user.dto.UserCreateRequest;
import spring.academyPlatform.user.dto.UserCreateResponse;

@RestController
@RequestMapping("api/v1/user")
@RequiredArgsConstructor
@Slf4j
public class UserController {

private final UserService userService;

@PostMapping("/user")
public ResponseEntity<UserCreateResponse> getUser(@Valid @RequestBody UserCreateRequest dto) {
try {
UserCreateResponse result = userService.createUser(dto);
return ResponseEntity.status(HttpStatus.OK).body(result);
} catch (Exception e) {
CUCU7103 marked this conversation as resolved.
Show resolved Hide resolved
log.error(e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package spring.academyPlatform.user.application;

import java.util.Arrays;

import org.springframework.stereotype.Service;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import spring.academyPlatform.global.util.BcryptPasswordEncryptor;
import spring.academyPlatform.user.dao.UserRepository;
import spring.academyPlatform.user.domain.User;
import spring.academyPlatform.user.dto.UserCreateRequest;
import spring.academyPlatform.user.dto.UserCreateResponse;
import spring.academyPlatform.user.mapper.UserMapper;
import spring.academyPlatform.user.model.UserTypeCode;

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {

private final UserRepository userRepository;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bean μ£Όμž…λ°©μ‹μ˜ μ„ νƒμ˜ 기쀀도 κΆκΈˆν•©λ‹ˆλ‹€.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μ‚¬μš©ν•œ μ£Όμž…λ°©μ‹μ€ μƒμ„±μž μ£Όμž… λ°©μ‹μž…λ‹ˆλ‹€.
μŠ€ν”„λ§μ—μ„œ μ˜μ‘΄μ„± 방식은 3가지가 μ‘΄μž¬ν•©λ‹ˆλ‹€.

  1. μƒμ„±μž μ£Όμž…
  2. ν•„λ“œ μ£Όμž…
  3. μˆ˜μ •μž μ£Όμž…

κ·Έ μ΄μœ λ‘œλŠ” μƒμ„±μžλ‘œ μ˜μ‘΄μ„±μ„ μ£Όμž…ν• λ•Œ final둜 μ„ μ–Έν•¨μœΌλ‘œ λΆˆλ³€μ„±μ„ κ°€μ§€κ²Œ ν•  μˆ˜μžˆμŠ΅λ‹ˆλ‹€.
λ˜ν•œ 컴파일 μ‹œμ μ— μ˜μ‘΄μ„±μ„ μ£Όμž…λ°›μ§€ μ•ŠμœΌλ©΄ 였λ₯˜κ°€ λ°œμƒν•˜κΈ°μ— μ‹€μˆ˜λ₯Ό 방지할 μˆ˜μžˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ μ΄μœ λ“€λ‘œ μŠ€ν”„λ§ 4.3 이상뢀터 μƒμ„±μž μ£Όμž… 방식을 κ³΅μ‹λ¬Έμ„œμ—μ„œ ꢌμž₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

λ˜λ„λ‘μ΄λ©΄ μƒμ„±μž μ£Όμž… 방식을 μ‚¬μš©ν•˜κ³ μž ν•©λ‹ˆλ‹€.


@Transactional
public UserCreateResponse createUser(UserCreateRequest dto) {

UserTypeCode userTypeCode = convertToEnum(dto.getUserType());
String encodedPassword = BcryptPasswordEncryptor.hashPassword(dto.getUserPassword());
User user = UserMapper.fromDto(dto, encodedPassword, userTypeCode);
log.info("User created: {}", user.toString());
User savedUser = userRepository.save(user);

return UserMapper.fromEntity(savedUser);
}

public UserTypeCode convertToEnum(String userType) {
return Arrays.stream(UserTypeCode.values())
.filter(e -> e.name().equalsIgnoreCase(userType))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("잘λͺ»λœ μœ μ € νƒ€μž…μž…λ‹ˆλ‹€ : " + userType));
}

}
11 changes: 11 additions & 0 deletions src/main/java/spring/academyPlatform/user/dao/UserRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package spring.academyPlatform.user.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import spring.academyPlatform.user.domain.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

}
80 changes: 80 additions & 0 deletions src/main/java/spring/academyPlatform/user/domain/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package spring.academyPlatform.user.domain;

import java.time.LocalDateTime;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import com.fasterxml.jackson.annotation.JsonFormat;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import spring.academyPlatform.global.model.YnCode;
import spring.academyPlatform.user.model.UserTypeCode;

@Entity
@Table(name = "user")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
@EntityListeners(AuditingEntityListener.class)
public class User {

@Id
@Column(name = "user_id")
private String userId;

@Column(name = "user_password")
private String userPassword;

@Column(name = "user_name")
private String userName;

@Enumerated(EnumType.STRING)
@Column(name = "user_type")
private UserTypeCode userType;

@Column(name = "created_at")
@CreatedDate
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime createdAt;

@Column(name = "modified_at")
@LastModifiedDate
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime modifiedAt;

@Column(name = "created_by")
private String createdBy;

@Column(name = "modified_by")
private String modifiedBy;

@Enumerated(EnumType.STRING)
@Column(name = "deleted_yn")
private YnCode deletedYn = YnCode.N;

@Builder
CUCU7103 marked this conversation as resolved.
Show resolved Hide resolved
public User(String userId, String userPassword, String userName, UserTypeCode userType,
String createdBy, String modifiedBy, YnCode deletedYn) {
this.userId = userId;
this.userPassword = userPassword;
this.userName = userName;
this.userType = userType;
this.createdBy = createdBy;
this.modifiedBy = modifiedBy;
this.deletedYn = (deletedYn == null) ? YnCode.N : deletedYn;
}

}
Loading