Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dedbf65
refactor: int 타입을 Long 타입으로 변환
gkdudans Sep 8, 2025
38f8498
refactor: 자동 정산 API 트랜잭션 분리 / 비동기 처리
gkdudans Sep 8, 2025
3f5f28d
refactor: int 타입을 Long 타입으로 변환
gkdudans Sep 8, 2025
ada5219
refactor: 유저별 정산 멀티쓰레드 처리로 수정
gkdudans Sep 9, 2025
57b38a6
feat: Redis Lua 스크립트 추가
gkdudans Sep 12, 2025
744c4c1
refactor: 가상스레드 상한 조정
gkdudans Sep 12, 2025
4983a63
refactor: Kafka 기본 설정 추가
gkdudans Sep 14, 2025
3a70c0b
feat: Kafka 기본 설정 추가
gkdudans Sep 14, 2025
de392e6
refactor: 가상스레드 기반 처리 방법 수정
gkdudans Sep 14, 2025
414c38c
refactor: 배치 크기 제한, 쿼리문 개선
gkdudans Sep 14, 2025
489a8de
refactor: StructuredTaskScope 기반 처리 방법 수정
gkdudans Sep 15, 2025
32223bb
docs: 디버깅용 로그 추가
gkdudans Sep 15, 2025
f6a0ff9
Revert "docs: 디버깅용 로그 추가"
gkdudans Sep 15, 2025
2dbcfd8
docs: 디버깅용 로그 제거
gkdudans Sep 15, 2025
8b163de
docs: 디버깅용 로그 제거
gkdudans Sep 15, 2025
eb7de53
docs: 디버깅용 로그 제거
gkdudans Sep 15, 2025
363cd2a
refactor: Outbox 쿼리문 변경
gkdudans Sep 15, 2025
60d390a
refactor: 인터럽트 가능한 방식으로 수정
gkdudans Sep 15, 2025
9f1d356
refactor: 카프카 관련 코드 변경사항 반영
gkdudans Sep 15, 2025
3cd7da7
docs: .gitignore 업데이트
gkdudans Sep 16, 2025
eb5f87c
Merge branch 'develop' of https://github.com/GoormOnlyOne/OnlyOne-Bac…
gkdudans Sep 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
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.gradle
target
out
node_modules
*.iml
*.log
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,9 @@ grafana-data
prometheus-data
docker-compose.yml
prometheus.yml
redis-data/dump.rdb
.my.cnf
src/main/resources/application-stage.yml
k6/k6_result.json
k6/settlement_test.js
ngrinder-controller
14 changes: 6 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# 사용할 base 이미지 선택
FROM openjdk:21
FROM eclipse-temurin:21-jre
WORKDIR /app

# build/libs/ 에 있는 jar 파일을 JAR_FILE 변수에 저장
ARG JAR_FILE=build/libs/*.jar

# JAR_FILE을 app.jar로 복사
COPY ${JAR_FILE} app.jar
# 실행 가능한 fat jar만 복사
COPY build/libs/onlyone-0.0.1-SNAPSHOT.jar app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "-Duser.timezone=Asia/Seoul", "app.jar"]
ENTRYPOINT ["java","--enable-preview","-jar","app.jar"]

35 changes: 29 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,45 +38,58 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-logging'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'

// Lombok (컴파일 + 테스트 모두 지원)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'com.h2database:h2'

// Jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// Jwt
implementation group: 'com.auth0', name: 'java-jwt', version: '3.14.0'
// Oauth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'io.jsonwebtoken:jjwt-api:0.12.4'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.4'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.4'

// OpenFeign
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'io.github.openfeign:feign-jackson'
//websocket

// websocket, actuator
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter'
//fcm

// fcm
implementation 'com.google.firebase:firebase-admin:9.5.0'

// S3
implementation 'software.amazon.awssdk:s3:2.32.11'

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// Elasticsearch
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
// Spring Retry
implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework:spring-aspects'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:testcontainers'

// jpa 쿼리 확인
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'

Expand All @@ -89,13 +102,17 @@ dependencies {
// 모니터링
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

// kafka
implementation 'org.springframework.kafka:spring-kafka'
}

// QueryDSL 설정 (수정됨)
def querydslDir = layout.buildDirectory.dir('generated/querydsl')

tasks.withType(JavaCompile).configureEach {
options.generatedSourceOutputDirectory = querydslDir
options.compilerArgs += ["--enable-preview"] // ✅ StructuredTaskScope 활성화
}

sourceSets {
Expand All @@ -110,17 +127,23 @@ clean {
delete querydslDir
}

// 나머지는 기존과 동일
// test task preview 옵션 추가
tasks.named('test') {
useJUnitPlatform()
finalizedBy jacocoTestReport
jvmArgs += "--enable-preview" // ✅ test 시 preview 활성화

// notification 관련 테스트 임시 제외
exclude '**/AppNotificationControllerTest.class'
exclude '**/FcmServiceTest.class'
exclude '**/AppNotificationTypeRepositoryTest.class'
}

// bootRun도 preview 활성화
tasks.named("bootRun") {
jvmArgs("--enable-preview")
}

jacoco {
toolVersion = "0.8.13"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public ConfirmTossPayResponse confirm(ConfirmTossPayRequest req) {
Wallet wallet = walletRepository.findByUser(user)
.orElseThrow(() -> new CustomException(ErrorCode.WALLET_NOT_FOUND));

int amount = Math.toIntExact(req.getAmount());
Long amount = req.getAmount();
wallet.updateBalance(wallet.getPostedBalance() + amount);

WalletTransaction walletTransaction = payment.getWalletTransaction();
Expand Down Expand Up @@ -195,7 +195,7 @@ public void reportFail(ConfirmTossPayRequest req) {

WalletTransaction failTx = WalletTransaction.builder()
.type(Type.CHARGE)
.amount(Math.toIntExact(req.getAmount()))
.amount(req.getAmount())
.balance(wallet.getPostedBalance())
.walletTransactionStatus(WalletTransactionStatus.FAILED)
.wallet(wallet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ScheduleRequestDto {
private String location;
@NotNull
@Min(value = 0, message = "정기 모임 금액은 0원 이상이어야 합니다.")
private int cost;
private Long cost;
@NotNull
@Min(value = 1, message = "정기 모임 정원은 1명 이상이어야 합니다.")
@Max(value = 100, message = "정기 모임 정원은 100명 이하여야 합니다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ScheduleDetailResponseDto {
private Long scheduleId;
private String name;
private LocalDateTime scheduleTime;
private int cost;
private Long cost;
private int userLimit;
private String location;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class ScheduleResponseDto {
private String name;
private ScheduleStatus scheduleStatus;
private LocalDateTime scheduleTime;
private int cost;
private Long cost;
private int userLimit;
private int userCount;
private boolean isJoined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class Schedule extends BaseTimeEntity {

@Column(name = "cost")
@NotNull
private int cost;
private Long cost;

@Column(name = "user_limit")
@NotNull
Expand All @@ -60,7 +60,7 @@ public class Schedule extends BaseTimeEntity {
@OneToOne(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true)
private Settlement settlement;

public void update(String name, String location, int cost, int userLimit, LocalDateTime scheduleTime) {
public void update(String name, String location, Long cost, int userLimit, LocalDateTime scheduleTime) {
this.name = name;
this.location = location;
this.cost = cost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public ScheduleCreateResponseDto createSchedule(Long clubId, ScheduleRequestDto
userChatRoomRepository.save(userChatRoom);
Settlement settlement = Settlement.builder()
.schedule(schedule)
.sum(0) // 정산 시작 시 참여자 수 * COST
.sum(0L) // 정산 시작 시 참여자 수 * COST
.totalStatus(TotalStatus.HOLDING)
.receiver(user) // 리더가 receiver
.build();
Expand Down Expand Up @@ -140,7 +140,7 @@ public void updateSchedule(Long clubId, Long scheduleId, ScheduleRequestDto requ
// 정산 금액이 변경되는 경우
if (schedule.getCost() != requestDto.getCost()) {
int memberCount = userScheduleRepository.countBySchedule(schedule) - 1;
int delta = requestDto.getCost() - schedule.getCost();
Long delta = requestDto.getCost() - schedule.getCost();

if (memberCount > 0 && delta != 0) {
List<UserSettlement> targets =
Expand Down Expand Up @@ -256,7 +256,7 @@ public void leaveSchedule(Long clubId, Long scheduleId) {
return;
}

final int amount = schedule.getCost();
final Long amount = schedule.getCost();
int flag = walletRepository.releaseHoldBalance(user.getUserId(), amount);
if (flag == 0) throw new CustomException(ErrorCode.WALLET_HOLD_STATE_CONFLICT);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example.onlyone.domain.settlement.dto.event;

import com.example.onlyone.domain.settlement.entity.OutboxStatus;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;

@Getter @Setter
@Entity @Table(name = "outbox_event", indexes = {
@Index(name = "idx_outbox_new", columnList = "status,id")
})
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class OutboxEvent {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String aggregateType; // "UserSettlement"
private Long aggregateId; // userSettlementId
private String eventType; // "ParticipantSettlementResult"
private String keyString; // partition key (e.g., memberWalletId)

@Lob
private String payload; // JSON

@Enumerated(EnumType.STRING)
private OutboxStatus status;

private LocalDateTime createdAt;
private LocalDateTime publishedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.onlyone.domain.settlement.dto.event;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.List;

@Data
@AllArgsConstructor
public class SettlementProcessEvent {
private final Long settlementId;
private final Long scheduleId;
private final Long clubId;
private final Long leaderId;
private final Long leaderWalletId;
private final Long costPerUser;
private final Long totalAmount;
private final List<Long> targetUserIds;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example.onlyone.domain.settlement.dto.event;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.time.Instant;

@Data
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserSettlementStatusEvent {
public enum ResultType { SUCCESS, FAILED }

private ResultType type; // "SUCCESS" | "FAILED"
private String operationId; // "stl:4:usr:100234:v1"
private Instant occurredAt;

private long settlementId;
private long userSettlementId;
private long participantId;

private long memberWalletId;
private long leaderId;
private long leaderWalletId;
private long amount;


@Data
public static class Snapshots {
private Long memberPostedBalance;
private Long leaderPostedBalance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.onlyone.domain.settlement.dto.event;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class WalletCaptureFailedEvent {
private Long userSettlementId;
private Long memberWalletId;
private Long leaderWalletId;
private Long amount;
private Long memberBalanceBefore;
private Long leaderBalanceBefore;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.onlyone.domain.settlement.dto.event;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class WalletCaptureSucceededEvent {
private Long userSettlementId;
private Long memberWalletId;
private Long leaderWalletId;
private Long amount;
private Long memberBalanceAfter;
private Long leaderBalanceAfter;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.onlyone.domain.settlement.entity;

public enum OutboxStatus {
NEW,
PUBLISHED,
FAILED
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class Settlement extends BaseTimeEntity {

@Column(name = "sum")
@NotNull
private int sum;
private Long sum;

@Column(name = "total_status")
@NotNull
Expand All @@ -56,7 +56,7 @@ public void update(TotalStatus totalStatus, LocalDateTime completedTime) {
this.completedTime = completedTime;
}

public void updateSum(int sum) {
public void updateSum(Long sum) {
this.sum = sum;
}

Expand Down
Loading