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/nyh365 step3 #31

Open
wants to merge 54 commits into
base: base/nyh365
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
15352e9
chore: 프로젝트 설정
nyh365 Jan 27, 2025
e9a9162
chore: 프로젝트 설정
nyh365 Jan 27, 2025
4f37766
feat: baseEntity 추가
nyh365 Jan 27, 2025
98b35b0
feat: 예외처리 추가
nyh365 Jan 27, 2025
940eaec
feat: user 엔티티 추가
nyh365 Jan 27, 2025
31752c4
feat: account 엔티티 추가
nyh365 Jan 27, 2025
8b168a8
feat: 회원 등록 기능 추가
nyh365 Jan 27, 2025
0bb7b18
feat: 이메일로 회원 조회 기능 추가
nyh365 Jan 27, 2025
b39c88e
feat: 메인 계좌 생성 기능 추가
nyh365 Jan 27, 2025
ae4b0bf
feat: 적금 계좌 생성 기능 추가
nyh365 Jan 27, 2025
d8797fc
feat: 예외처리 추가
nyh365 Jan 27, 2025
0478f01
feat: 충전 한도 필드 추가
nyh365 Jan 27, 2025
245990d
feat: 이메일 존재 여부 함수 코드 단으로 변경
nyh365 Jan 27, 2025
37a4adc
feat: 메인 계좌 필드 추가
nyh365 Jan 27, 2025
1a6e47b
feat: 메인 계좌 충전 기능 추가
nyh365 Jan 27, 2025
def1910
feat: 인출 요청 및 응답 dto 추가
nyh365 Jan 28, 2025
bb9f31c
feat: 메인 계좌에서 적금 계좌로 인출하는 기능 추가
nyh365 Jan 28, 2025
49edc05
feat: 충전 한도 타입 수정
nyh365 Feb 2, 2025
9029292
feat: 충전 한도 타입 수정
nyh365 Feb 2, 2025
a39e1d5
chore: 프로젝트 설정
nyh365 Feb 2, 2025
53560c5
feat: 메세지큐 설정 추가
nyh365 Feb 2, 2025
cca982f
refactor: 충전 함수로 리팩토링
nyh365 Feb 2, 2025
6a14bdc
feat: 메인계좌 존재 여부 확인 기능 추가
nyh365 Feb 2, 2025
b0e4b4d
feat: 송금 기능 추가
nyh365 Feb 2, 2025
be1fb48
feat: 이벤트 발행 기능 추가
nyh365 Feb 3, 2025
a980471
feat: 비동기 실행을 위한 설정 추가
nyh365 Feb 3, 2025
4987099
feat: 송금 트랜잭션 엔티티 추가
nyh365 Feb 3, 2025
87eb9c6
feat: 송금 기능 추가
nyh365 Feb 3, 2025
18de610
feat: 송금 기능 추가
nyh365 Feb 3, 2025
0b47496
feat: 누락된 송금 내역 처리를 위한 스케줄러 추가
nyh365 Feb 3, 2025
2d85cde
feat: 충전 한도 0시 00분을 기점으로 초기화하는 스케줄러 추가
nyh365 Feb 3, 2025
7915c2c
refactor: 미사용 코드 제거
nyh365 Feb 3, 2025
aeed0bb
refactor: setter 제거
nyh365 Feb 3, 2025
965c0b2
refactor: 유효성 검증 코드 제거
nyh365 Feb 3, 2025
7aee74a
style: 주석 추가
nyh365 Feb 3, 2025
a0229d2
refactor: 변수명 변경 및 함수화
nyh365 Feb 3, 2025
7dcf5b4
refactor: 함수 위치 변경
nyh365 Feb 3, 2025
01e9522
refactor: 미사용 코드 제거
nyh365 Feb 3, 2025
c704a69
refactor: 미사용 코드 제거
nyh365 Feb 3, 2025
8e88df9
feat: 일일 한도 변경 쿼리 변경
nyh365 Feb 4, 2025
53eaf2c
chore: 메세지 큐 사용을 위한 설정 추가
nyh365 Feb 7, 2025
c4e4638
fix: 적금 계좌 조회 시 락 걸도록 수정
nyh365 Feb 7, 2025
0bcf0f1
feat: 대상 유저 수 조회 기능 추가
nyh365 Feb 10, 2025
83d0b55
feat: 정산 요청 내역 저장 기능 추가
nyh365 Feb 10, 2025
14eb44e
feat: 정산 상세 내역 저장 기능 추가
nyh365 Feb 10, 2025
a0ea42f
feat: 정산 엔티티 추가
nyh365 Feb 10, 2025
b8eaa19
feat: 정산 상세 내역 엔티티 추가
nyh365 Feb 10, 2025
e748dd4
feat: 정산 기능 추가
nyh365 Feb 10, 2025
babdff7
fix: 응답 코드 수정
nyh365 Feb 16, 2025
2c5b703
fix: 상수 타입 수정
nyh365 Feb 16, 2025
a15b04d
feat: 메서드 스코프 수정
nyh365 Feb 16, 2025
6c26829
refactor: 변수명 변경
nyh365 Feb 16, 2025
9db7511
refactor: 변수명 변경
nyh365 Feb 16, 2025
532c732
fix: SecureRandom으로 변경
nyh365 Feb 16, 2025
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
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.commons:commons-lang3:3.13.0'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
runtimeOnly 'com.mysql:mysql-connector-j'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-amqp'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
}

checkstyle {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableJpaAuditing
@EnableScheduling
@SpringBootApplication
public class AssignmentApplication {

Expand Down
36 changes: 36 additions & 0 deletions src/main/java/org/c4marathon/assignment/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.c4marathon.assignment.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfig {
public static final String ASYNC_LISTENER_TASK_EXECUTOR_NAME = "AsyncListenerTaskExecutor";
public static final String ASYNC_SCHEDULER_TASK_EXECUTOR_NAME = "AsyncSchedulerTaskExecutor";
private static final int CORE_POOL_SIZE = 2;
private static final int MAX_POOL_SIZE = 4;

@Bean(name = ASYNC_LISTENER_TASK_EXECUTOR_NAME)
public ThreadPoolTaskExecutor asyncListenerTaskExecutor() {
return getThreadPoolTaskExecutor(ASYNC_LISTENER_TASK_EXECUTOR_NAME);
}

@Bean(name = ASYNC_SCHEDULER_TASK_EXECUTOR_NAME)
public ThreadPoolTaskExecutor asyncSchedulerTaskExecutor() {
return getThreadPoolTaskExecutor(ASYNC_SCHEDULER_TASK_EXECUTOR_NAME);
}
Comment on lines +16 to +24
Copy link

Choose a reason for hiding this comment

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

스레드 풀을 하나로 쓰지 않고 둘로 나눈 이유가 있나요?

Copy link
Author

Choose a reason for hiding this comment

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

아직 학습 중이라 코드상에는 반영을 못했습니다. 스레드 풀을 분리한 이유는 이벤트 처리로 인한 작업량이 많을 것 같아 스레드 풀을 분리하여 각 기능에 적합하게 구성하고자 했습니다.


private ThreadPoolTaskExecutor getThreadPoolTaskExecutor(String asyncSchedulerTaskExecutorName) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setThreadNamePrefix(asyncSchedulerTaskExecutorName);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(10);
executor.initialize();
return executor;
}
}
76 changes: 76 additions & 0 deletions src/main/java/org/c4marathon/assignment/config/RabbitmqConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.c4marathon.assignment.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitmqConfig {

@Value("${spring.rabbitmq.host}")
private String host;

@Value("${spring.rabbitmq.username}")
private String username;

@Value("${spring.rabbitmq.password}")
private String password;

@Value("${spring.rabbitmq.port}")
private int port;

@Value("${rabbitmq.queue.name}")
private String queueName;

@Value("${rabbitmq.exchange.name}")
private String exchangeName;

@Value("${rabbitmq.routing.key}")
private String routingKey;

@Bean
DirectExchange directExchange() {
return new DirectExchange(exchangeName);
}

@Bean
Queue queue() {
return new Queue(queueName);
}

@Bean
Binding binding(DirectExchange directExchange, Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with(routingKey);
}

@Bean
ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost(host);
connectionFactory.setPort(port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
return connectionFactory;
}

@Bean
MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}

@Bean
RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.c4marathon.assignment.controller;

import org.c4marathon.assignment.dto.request.PostMainAccountReq;
import org.c4marathon.assignment.dto.request.PostSavingsAccountReq;
import org.c4marathon.assignment.dto.request.TransferReq;
import org.c4marathon.assignment.dto.request.WithdrawMainAccountReq;
import org.c4marathon.assignment.dto.response.MainAccountInfoRes;
import org.c4marathon.assignment.dto.response.TransferRes;
import org.c4marathon.assignment.dto.response.WithdrawInfoRes;
import org.c4marathon.assignment.service.AccountService;
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;

@RestController
@RequestMapping("/v1/accounts")
@RequiredArgsConstructor
public class AccountController {
private final AccountService accountService;

@PostMapping("/savings")
public ResponseEntity<Void> createSavingsAccount(@RequestBody @Valid PostSavingsAccountReq postSavingsAccountReq) {
accountService.createSavingsAccount(postSavingsAccountReq);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@PostMapping("/main/deposit")
public MainAccountInfoRes depositMainAccount(@RequestBody @Valid PostMainAccountReq postMainAccountReq) {
Copy link

Choose a reason for hiding this comment

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

@ResponseStatus나 ResponseEntity를 사용하지 않은 이유가 어차피 200 반환해서 굳이 사용 안하신건가요??

Copy link
Author

Choose a reason for hiding this comment

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

네 그렇긴 한데..그러다 보니 create 같은 요청에 적절한 응답 코드를 반환하는 걸 놓쳤네요. 명시적으로 반환하도록 수정하여 반영하겠습니다.

return accountService.depositMainAccount(postMainAccountReq);
}
@PostMapping("/savings/withdraw")
public WithdrawInfoRes withdrawForSavings(@RequestBody @Valid WithdrawMainAccountReq withdrawMainAccountReq) {
return accountService.withdrawForSavings(withdrawMainAccountReq);
}

@PostMapping("/main/transfer")
public TransferRes transfer(@RequestBody @Valid TransferReq transferReq) {
return accountService.transfer(transferReq);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.c4marathon.assignment.controller;

import org.c4marathon.assignment.dto.request.PostSettlementReq;
import org.c4marathon.assignment.dto.response.SettlementInfoRes;
import org.c4marathon.assignment.service.SettlementService;
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;

@RestController
@RequestMapping("/v1/settlement")
@RequiredArgsConstructor
public class SettlementController {
private final SettlementService settlementService;

@PostMapping
public SettlementInfoRes requestSettlement(@RequestBody @Valid PostSettlementReq settlementReq) {
return settlementService.requestSettlement(settlementReq);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.c4marathon.assignment.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.c4marathon.assignment.dto.request.PostUserReq;
import org.c4marathon.assignment.service.UserService;
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;

@RestController
@RequestMapping("/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;

@PostMapping()
public ResponseEntity<Void> registerUser(@RequestBody @Valid PostUserReq postUserReq) {
userService.registerUser(postUserReq);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
27 changes: 27 additions & 0 deletions src/main/java/org/c4marathon/assignment/dto/MessageDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.c4marathon.assignment.dto;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MessageDto {
long transferTransactionId;

long senderMainAccount;

long receiverMainAccount;
long amount;

@Builder
Copy link

Choose a reason for hiding this comment

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

DTO에 빌더 패턴을 적용하신 이유가 있으실까요?

Copy link
Author

Choose a reason for hiding this comment

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

단순히 가독성 때문에 사용했습니다.

public MessageDto(long transferTransactionId, long senderMainAccount, long receiverMainAccount, long amount) {
this.transferTransactionId = transferTransactionId;
this.senderMainAccount = senderMainAccount;
this.receiverMainAccount = receiverMainAccount;
this.amount = amount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.c4marathon.assignment.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
public class TransferTransactionEvent {
private static final int INIT_TRANSFER_TRANSACTION_ID = -1;
String userName;
long transferTransactionId;
long senderMainAccount;
long receiverMainAccount;
long amount;

@Builder
public TransferTransactionEvent(String userName, long senderMainAccount, long receiverMainAccount, long amount) {
this.userName = userName;
this.transferTransactionId = INIT_TRANSFER_TRANSACTION_ID;
this.senderMainAccount = senderMainAccount;
this.receiverMainAccount = receiverMainAccount;
this.amount = amount;
}

public void updateTransferTransactionId(long transferTransactionId) {
this.transferTransactionId = transferTransactionId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.c4marathon.assignment.dto.request;

import jakarta.validation.constraints.Positive;

public record PostMainAccountReq(
@Positive(message = "회원 번호는 양수가 되어야 합니다.")
long userId,

@Positive(message = "이체 금액은 양수가 되어야 합니다.")
long amount
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.c4marathon.assignment.dto.request;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record PostSavingsAccountReq(
@Email
@NotBlank
String email
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.c4marathon.assignment.dto.request;

import java.util.List;

import org.c4marathon.assignment.entity.SettlementType;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

public record PostSettlementReq(
@Positive(message = "회원 번호는 양수가 되어야 합니다.")
long requester,

@Positive(message = "정산 금액은 양수가 되어야 합니다.")
long totalAmount,

@NotNull
SettlementType type,

@NotNull
List<Long> userIds
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.c4marathon.assignment.dto.request;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record PostUserReq(
@Size(max = 100)
@NotBlank
String username,

@Email
@NotBlank
String email,

@Size(max = 100)
@NotBlank
String nickname
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.c4marathon.assignment.dto.request;

import jakarta.validation.constraints.Positive;

public record TransferReq(
@Positive(message = "사용자 번호는 양수가 되어야 합니다.")
long senderId,

@Positive(message = "메인 계좌번호는 양수가 되어야 합니다.")
long receiverMainAccount,

@Positive(message = "송금 금액은 양수가 되어야 합니다.")
long amount
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.c4marathon.assignment.dto.request;

import jakarta.validation.constraints.Positive;

public record WithdrawMainAccountReq(
@Positive(message = "회원 번호는 양수가 되어야 합니다.")
long userId,
@Positive(message = "적금 계좌번호는 양수가 되어야 합니다.")
long savingsAccount,
@Positive(message = " 금액은 양수가 되어야 합니다.")
long amount
) {
}
Loading
Loading