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/seunghun step3 #18

Open
wants to merge 17 commits into
base: base/seunghun
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
92b60cc
feat: 1/n 정산 기능 추가
seungh1024 Jun 13, 2024
c1bf39f
feat: 랜덤 정산 기능 추가
seungh1024 Jun 13, 2024
974a8b6
refactor: 정산 요청 기능을 isRandom 필드로 구분하도록 수정
seungh1024 Jun 17, 2024
2ec8814
feat: 정산 내역 리스트 조회 기능 추가
seungh1024 Jun 17, 2024
d15313e
fix: multiple DB를 사용하며 발생한 빈 충돌 문제 제거
seungh1024 Jun 19, 2024
ae8fce5
test: 정산 요청 컨트롤러, 정산 정보 리스트 조회 컨트롤러 테스트 추가
seungh1024 Jun 19, 2024
78c75c6
test: settlementService 테스트 코드 작성
seungh1024 Jun 20, 2024
40cab42
refactor: 랜덤 정산 코드 변경
seungh1024 Nov 19, 2024
7ff4a91
refactor: 최소 정산 금액을 1000원으로 변경
seungh1024 Nov 19, 2024
5d9f482
fix: 테스트 컨테이너 환경에서 실행되지 않던 문제 해결
seungh1024 Nov 19, 2024
4dc2204
style: 불필요한 코드 제거 및 제네릭 타입 명시
seungh1024 Nov 19, 2024
028bc5b
fix: UTC 시간 변경을 위해 9시간 더한 것 제거
seungh1024 Nov 19, 2024
79bec7b
refactor: 정산내역의 id가 timestamp와 date로 직렬화 되지 않도록 변경
seungh1024 Nov 19, 2024
fc0831d
feat: 정산 내역 조회 페이징 처리
seungh1024 Nov 19, 2024
34b5ec7
refactor: Collection for문 처리를 stream 처리로 변경
seungh1024 Nov 20, 2024
0513b2b
refactor: 정산 내역 조회를 날짜 기준 내림차순으로 변경
seungh1024 Nov 20, 2024
7e9e76a
refactor: 테스트 코드의 정산 내역 조회를 날짜 기준 내림차순으로 변경
seungh1024 Nov 21, 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
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ dependencies {
implementation 'org.springframework.security:spring-security-crypto:5.6.3'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

implementation 'org.springframework.data:spring-data-mongodb'
implementation 'org.mongodb:mongodb-driver-sync'

testImplementation 'org.testcontainers:testcontainers:1.19.4'
testImplementation 'org.testcontainers:junit-jupiter:1.19.4'
testImplementation 'org.testcontainers:mysql:1.19.4'
testImplementation 'org.testcontainers:mongodb:1.16.3'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;

/**
Expand All @@ -20,6 +21,7 @@
*/
@Entity
@Getter
@Table(name = "main_account")
public class MainAccount extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -20,6 +21,7 @@
@Entity
@NoArgsConstructor
@Getter
@Table(name = "saving_account")
public class SavingAccount extends BaseEntity {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@Getter
@Table(name = "saving_product")
public class SavingProduct {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
@Getter
@RequiredArgsConstructor
public enum AccountAsyncErrorCode {
SEND_ROLLBACK_FAILED("이체 롤백에 실패했습니다"),
;
SEND_ROLLBACK_FAILED("이체 롤백에 실패했습니다");
private final String message;

public AccountAsyncException accountAsyncException() {
return new AccountAsyncException(name(), getMessage());
}
}



Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@

@Component
@RequiredArgsConstructor
public class RedisStreamConsumer implements StreamListener<String, MapRecord<String, Object, Object>>, InitializingBean,
public class RedisStreamConsumer implements StreamListener<String, MapRecord<String, Object, String>>, InitializingBean,
DisposableBean {

private StreamMessageListenerContainer<String, MapRecord<String, Object, Object>> listenerContainer;
private StreamMessageListenerContainer<String, MapRecord<String, Object, String>> listenerContainer;
private Subscription subscription;

@Value("${redis-stream.stream-key}")
Expand Down Expand Up @@ -76,18 +76,18 @@ public void afterPropertiesSet() throws Exception {
* XADD된 메세지를 먼저 처리한다. 처리와 동시에 consumer-group에 pending 한다.
*/
@Override
public void onMessage(MapRecord<String, Object, Object> message) {
public void onMessage(MapRecord<String, Object, String> message) {
// 테스트에서 ack 처리 되지 않도록 처리
if (isTest) {
return;
}
Set<Map.Entry<Object, Object>> entries = message.getValue().entrySet();
Set<Map.Entry<Object, String>> entries = message.getValue().entrySet();
Long[] accountData = new Long[3];
int index = 0;

// depositData에 send-pk, deposit-pk, money를 차례대로 담는다.
for (Map.Entry<Object, Object> entry : entries) {
String value = (String)entry.getValue();
for (Map.Entry<Object, String> entry : entries) {
String value = entry.getValue();
accountData[index++] = Long.valueOf(value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.data.redis.connection.RedisStreamCommands;
import org.springframework.data.redis.connection.stream.ByteRecord;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.PendingMessage;
import org.springframework.data.redis.connection.stream.PendingMessages;
import org.springframework.data.redis.connection.stream.ReadOffset;
Expand Down Expand Up @@ -62,15 +63,19 @@ public void addStream(String streamKey, long sendPk, long depositPk, long money)
return;
}

RedisAsyncCommands commands = (RedisAsyncCommands)connection.getNativeConnection();
RedisAsyncCommands<String, String> commands = (RedisAsyncCommands)connection.getNativeConnection();

CommandArgs<String, String> commandArgs = new CommandArgs<>(StringCodec.UTF8).addKey(streamKey)
.add("*") // id 자동 생성
.add("send-pk").add(sendPk)
.add("deposit-pk").add(depositPk)
.add("money").add(money);

RedisFuture dispatch = commands.dispatch(CommandType.XADD, new StatusOutput<>(StringCodec.UTF8), commandArgs);
.add("send-pk")
.add(sendPk)
.add("deposit-pk")
.add(depositPk)
.add("money")
.add(money);

RedisFuture<String> dispatch = commands.dispatch(CommandType.XADD, new StatusOutput<>(StringCodec.UTF8),
commandArgs);
dispatch.handle((result, exception) -> {
// 예외가 발생하면 이체 롤백 요청을 보낸다.
if (exception != null) {
Expand Down Expand Up @@ -127,7 +132,7 @@ public void claimMessage(PendingMessage pendingMessage, String streamKey, String
return;
}

RedisAsyncCommands commands = (RedisAsyncCommands)connection.getNativeConnection();
RedisAsyncCommands<String, String> commands = (RedisAsyncCommands)connection.getNativeConnection();

CommandArgs<String, String> commandArgs = new CommandArgs<>(StringCodec.UTF8).addKey(streamKey)
.add(pendingMessage.getGroupName())
Expand All @@ -146,7 +151,7 @@ public void createStreamConsumerGroup(String streamKey, String consumerGroupName
return;
}

RedisAsyncCommands commands = (RedisAsyncCommands)connection.getNativeConnection();
RedisAsyncCommands<String, String> commands = (RedisAsyncCommands)connection.getNativeConnection();

// 사용할 명령어 생성
CommandArgs<String, String> commandArgs = new CommandArgs<>(StringCodec.UTF8).add(CommandKeyword.CREATE)
Expand Down Expand Up @@ -181,7 +186,7 @@ public boolean isStreamConsumerGroupExist(String streamKey, String consumerGroup
return false;
}

public StreamMessageListenerContainer createStreamMessageListenerContainer() {
public StreamMessageListenerContainer<String, MapRecord<String, Object, String>> createStreamMessageListenerContainer() {
return StreamMessageListenerContainer.create(redisTemplate.getConnectionFactory(),
StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
.hashKeySerializer(new StringRedisSerializer())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,82 @@
package org.c4marathon.assignment.common.config;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableJpaAuditing
@EnableJpaRepositories(basePackages = {"org.c4marathon.assignment.*"},
entityManagerFactoryRef = "mysqlEntityManagerFactory",
transactionManagerRef = "mysqlTransactionManager")
@EnableTransactionManagement
public class JpaConfig {
@Value("${spring.jpa.hibernate.ddl-auto}")
private String ddlAuto;

@Value("${spring.datasource.driver-class-name}")
private String driverClassName;

@Value("${spring.datasource.url}")
private String url;

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

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

@Primary
@Bean
public LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(mysqlDatasource()); //Datasource 설정
entityManagerFactoryBean.setPackagesToScan("org.c4marathon.assignment.*.entity"); // MySQL Entity 패키지 경로

HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter); // hibernate 등록

Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", ddlAuto);
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");
entityManagerFactoryBean.setJpaPropertyMap(properties);

return entityManagerFactoryBean;
}

@Primary
@Bean
public DataSource mysqlDatasource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);

return dataSource;
}

@Primary
@Bean
public JpaTransactionManager mysqlTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(mysqlEntityManagerFactory().getObject());

return transactionManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@Getter
@Table(name = "member")
public class Member extends BaseEntity {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public SessionMemberInfo signIn(SignInRequestDto requestDto) {
throw MemberErrorCode.INVALID_PASSWORD.memberException("비밀번호 불일치");
}

return new SessionMemberInfo(member.getMemberPk(), member.getMemberId(), member.getMainAccountPk());
return new SessionMemberInfo(member.getMemberPk(), member.getMemberId(), member.getMemberName(),
member.getMainAccountPk());
}

public MemberInfoResponseDto getMemberInfo(long memberPk) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
public record SessionMemberInfo(
long memberPk,
String memberId,
String memberName,
long mainAccountPk
) implements Serializable {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.c4marathon.assignment.settlement.config;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.data.mongodb.SessionSynchronization;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.WriteResultChecking;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;

@Profile("default")
@Configuration
@EnableMongoRepositories(basePackages = {"org.c4marathon.assignment.settlement"})
public class SettlementMongoConfig extends AbstractMongoClientConfiguration {

@Value("${spring.mongo.database}")
private String databaseName;

@Value("${spring.mongo.uri}")
private String mongoUri;

@Bean
public MongoTransactionManager mongoTransactionManager(
@Qualifier("mongoDbFactory") MongoDatabaseFactory mongoDatabaseFactory) {
return new MongoTransactionManager(mongoDatabaseFactory);
}

@Override
protected String getDatabaseName() {
return databaseName;
}

@Override
public MongoDatabaseFactory mongoDbFactory() {
return super.mongoDbFactory();
}

@Override
public MongoClient mongoClient() {

String uri = String.format(mongoUri);

ConnectionString connectionString = new ConnectionString(uri);

MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyToConnectionPoolSettings(builder -> builder
.maxConnectionIdleTime(10, TimeUnit.SECONDS))
.applyConnectionString(connectionString)
.build();

return MongoClients.create(mongoClientSettings);
}

@Bean
public MongoTemplate mongoTemplate() {
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), databaseName);
mongoTemplate.setSessionSynchronization(SessionSynchronization.ALWAYS);
mongoTemplate.setWriteResultChecking(WriteResultChecking.EXCEPTION); // 삽입에 실패하면 예외가 발생하도록 한다.

return mongoTemplate;
}

}
Loading
Loading