Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1d6d1f2
🗑️ Chore : import 변경
Si-rauis Mar 19, 2025
db3ff24
🗑️ Chore : gitIgnore 설정 변경
Si-rauis Mar 25, 2025
089e540
✨ Feat : 유저, 카드 함수 파일 <K6용>
Si-rauis Mar 25, 2025
a717e87
🗑️ Chore : 빌드 파일 주석 띄어쓰기
Si-rauis Mar 26, 2025
8b662b2
✨ Feat : 카드 기능 <K6용>
Si-rauis Mar 26, 2025
d5ddf7c
✨ Feat : CardController 단위 테스트
Si-rauis Mar 26, 2025
e33e288
✨ Feat : Card DTO 응답값 cardID 추가
Si-rauis Mar 26, 2025
1856305
🗑️ Chore : K6 실행 파일 test.js로 변경
Si-rauis Mar 26, 2025
d8adc1c
♻️ Refactor : User 지갑 fetch = FetchType.EAGER 으로 변경
Si-rauis Mar 26, 2025
1818d17
🗑️ Chore : 카드 결제 요청 도메인 시큐리티 설정
Si-rauis Apr 7, 2025
4386429
✨ Feat : 카드 결제 거래내역 연관관계 추가
Si-rauis Apr 7, 2025
066687f
✨ Feat : 이름, 전화번호로 User 검색 추가
Si-rauis Apr 7, 2025
37340f1
✨ Feat : 카드 거래내역 Entity
Si-rauis Apr 7, 2025
1f6c87a
🗑️ Chore : 카드 결제 요청 도메인 시큐리티 설정
Si-rauis Apr 7, 2025
a4f0678
✨ Feat : 이벤트 발행 유틸리티 클래스 EventPublisher 추가
Si-rauis Apr 7, 2025
1a87e8a
♻️ Refactor : 실물카드 한 번만 발급 가능하도록 변경
Si-rauis Apr 7, 2025
0ce9943
✅ Test : 카드 수정 테스트 추가
Si-rauis Apr 7, 2025
64a2a37
✨ Feat : 카드 결제 요청 Service, Controller단 추가
Si-rauis Apr 7, 2025
5164072
✨ Feat : 카드 결제 요청 DTO 추가
Si-rauis Apr 7, 2025
1c8b1a6
✨ Feat : 카드 거래내역 DTO 추가
Si-rauis Apr 7, 2025
54af88f
✨ Feat : 카드 거래내역 Repository 추가
Si-rauis Apr 7, 2025
9e9b6f5
✨ Feat : 카드 거래내역 DTO 추가
Si-rauis Apr 7, 2025
e7ff7a9
✨ Feat : 카드 거래내역 Service 추가
Si-rauis Apr 7, 2025
b2a4b00
✨ Feat : 카드 거래내역 Controller 추가
Si-rauis Apr 7, 2025
988edad
✨ Feat : 카드 관련 에러코드 추가
Si-rauis Apr 7, 2025
a7ed360
Merge branch 'develop' into feature/JIRA-kan-69-카드-결제-성능-체크
Si-rauis Apr 7, 2025
ac00809
♻️ Refactor : 시큐리티 변경에 따른 수정
Si-rauis Apr 7, 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,4 @@ src/test/resources/application-test-secrets.yml
src/main/resources/data.sql
/scripts/
init.sql
k6-scripts/
k6-scripts/test.js
58 changes: 29 additions & 29 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ repositories {

dependencies {
// Spring Boot 핵심 기능 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // JPA 및 Hibernate를 통한 데이터 접근 지원
implementation 'org.springframework.boot:spring-boot-starter-validation' // 데이터 검증을 위한 라이브러리 (Bean Validation)
implementation 'org.springframework.boot:spring-boot-starter-web' // 웹 애플리케이션 개발을 위한 라이브러리 (Spring MVC 포함)
implementation 'org.springframework.boot:spring-boot-starter-websocket' // WebSocket 기능을 제공하는 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebFlux 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-batch' // Spring Batch 의존성
implementation 'org.springframework.boot:spring-boot-starter-actuator' // Spring 모니터링
implementation 'org.springframework.boot:spring-boot-starter-aop' // Spring AOP
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // JPA 및 Hibernate를 통한 데이터 접근 지원
implementation 'org.springframework.boot:spring-boot-starter-validation' // 데이터 검증을 위한 라이브러리 (Bean Validation)
implementation 'org.springframework.boot:spring-boot-starter-web' // 웹 애플리케이션 개발을 위한 라이브러리 (Spring MVC 포함)
implementation 'org.springframework.boot:spring-boot-starter-websocket' // WebSocket 기능을 제공하는 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebFlux 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-batch' // Spring Batch 의존성
implementation 'org.springframework.boot:spring-boot-starter-actuator' // Spring 모니터링
implementation 'org.springframework.boot:spring-boot-starter-aop' // Spring AOP

// Spring Security (인증 및 인가 관련 보안 기능)
implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security 기본 설정 및 기능 제공
implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security 기본 설정 및 기능 제공
testImplementation 'org.springframework.security:spring-security-test'

// JWT (JSON Web Token) 관련 라이브러리
Expand All @@ -44,42 +44,42 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// 테스트
testImplementation 'org.springframework.boot:spring-boot-starter-test' // Spring Boot의 테스트 도구 모음 (JUnit, Mockito 포함)
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // JUnit 플랫폼 런처 (테스트 실행 환경 제공)
testImplementation "org.testcontainers:testcontainers:1.19.2" // Docker 기반 통합 테스트 지원 (Testcontainers 기본 라이브러리)
testImplementation "org.testcontainers:junit-jupiter:1.19.2" // JUnit 5(Testcontainers 연동) 지원
testImplementation 'org.springframework.boot:spring-boot-starter-test' // Spring Boot의 테스트 도구 모음 (JUnit, Mockito 포함)
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // JUnit 플랫폼 런처 (테스트 실행 환경 제공)
testImplementation "org.testcontainers:testcontainers:1.19.2" // Docker 기반 통합 테스트 지원 (Testcontainers 기본 라이브러리)
testImplementation "org.testcontainers:junit-jupiter:1.19.2" // JUnit 5(Testcontainers 연동) 지원
testImplementation 'org.testcontainers:postgresql:1.19.2'

// 데이터베이스
runtimeOnly 'com.h2database:h2' // 인메모리 데이터베이스a H2 (테스트 및 개발 환경용)
runtimeOnly 'org.postgresql:postgresql:42.5.2' // PostgreSQL 데이터베이스 드라이버 (운영 환경에서 사용)
runtimeOnly 'com.h2database:h2' // 인메모리 데이터베이스a H2 (테스트 및 개발 환경용)
runtimeOnly 'org.postgresql:postgresql:42.5.2' // PostgreSQL 데이터베이스 드라이버 (운영 환경에서 사용)

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis' // Spring Data Redis 라이브러리
testImplementation 'com.redis:testcontainers-redis:2.2.2' // Redis용 Testcontainers (테스트 환경에서 Redis 컨테이너 실행)
implementation 'org.redisson:redisson-spring-boot-starter:3.23.4' // Redisson 락 적용
implementation 'org.springframework.boot:spring-boot-starter-data-redis' // Spring Data Redis 라이브러리
testImplementation 'com.redis:testcontainers-redis:2.2.2' // Redis용 Testcontainers (테스트 환경에서 Redis 컨테이너 실행)
implementation 'org.redisson:redisson-spring-boot-starter:3.23.4' // Redisson 락 적용

// 쿼리DSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' // QueryDSL의 JPA 모듈
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" // QueryDSL 코드 자동 생성 도구
annotationProcessor "jakarta.annotation:jakarta.annotation-api" // Jakarta Annotation API (자바 표준 어노테이션 지원)
annotationProcessor "jakarta.persistence:jakarta.persistence-api" // Jakarta Persistence API (JPA 관련 표준 API 지원)
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' // QueryDSL의 JPA 모듈
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" // QueryDSL 코드 자동 생성 도구
annotationProcessor "jakarta.annotation:jakarta.annotation-api" // Jakarta Annotation API (자바 표준 어노테이션 지원)
annotationProcessor "jakarta.persistence:jakarta.persistence-api" // Jakarta Persistence API (JPA 관련 표준 API 지원)

// AWS
implementation 'software.amazon.awssdk:kms:2.20.0' // AWS KMS 의존성
implementation 'software.amazon.awssdk:kms:2.20.0' // AWS KMS 의존성

// RabbitMQ
implementation 'org.springframework.boot:spring-boot-starter-amqp'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
testImplementation 'org.testcontainers:rabbitmq'

// 기타
compileOnly 'org.projectlombok:lombok' // Lombok (Getter, Setter, Constructor 자동 생성)
annotationProcessor 'org.projectlombok:lombok' // Lombok 사용을 위한 어노테이션 프로세서
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.4' // Swagger Ui
implementation 'io.projectreactor.netty:reactor-netty:1.1.22' // Reactor Netty 의존성
implementation 'com.fasterxml.jackson.core:jackson-databind' // JSON 파싱을 위한 의존성
implementation 'com.google.code.gson:gson:2.10.1' // JSON 데이터를 다룰 수 있는 클래스
compileOnly 'org.projectlombok:lombok' // Lombok (Getter, Setter, Constructor 자동 생성)
annotationProcessor 'org.projectlombok:lombok' // Lombok 사용을 위한 어노테이션 프로세서
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.4' // Swagger Ui
implementation 'io.projectreactor.netty:reactor-netty:1.1.22' // Reactor Netty 의존성
implementation 'com.fasterxml.jackson.core:jackson-databind' // JSON 파싱을 위한 의존성
implementation 'com.google.code.gson:gson:2.10.1' // JSON 데이터를 다룰 수 있는 클래스
}

tasks.named('test') {
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ services:
- ./k6-scripts:/k6-scripts # K6 스크립트가 있는 디렉토리
environment:
- K6_OUT=influxdb=http://influxdb:8086/k6
command: ["run", "/k6-scripts/.."]
command: ["run", "/k6-scripts/test.js"]

# 🔹 Spring Boot 애플리케이션
app:
Expand Down
112 changes: 112 additions & 0 deletions k6-scripts/cardFunctions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import http from 'k6/http';
import { check, sleep } from 'k6';

// 실물 카드 발급 함수
export function generatePhysicalCard(BASE_URL, authParams) {
let generateCardRes = http.post(`${BASE_URL}/api/v1/card/physical`, null, authParams, {
tags: { method: 'POST', endpoint: 'generatePhysicalCard' },
});

check(generateCardRes, {
'Card Generation Success': (r) => r.status === 201,
});

if (generateCardRes.status !== 201) {
return null;
}

return generateCardRes;
}

// 카드 상태 변경 함수
export function changeCardStatus(BASE_URL, authParams, cardType, status) {
let changeCardStatusPayload = JSON.stringify({
cardType: cardType,
status: status
});

let changeCardStatusRes = http.put(`${BASE_URL}/api/v1/card/status`, changeCardStatusPayload, authParams, {
tags: { method: 'PUT', endpoint: 'changeCardStatus' },
});

check(changeCardStatusRes, {
'Card Status Change Success': (r) => r.status === 204,
});

if (changeCardStatusRes.status !== 204) {
return null;
}

return changeCardStatusRes;
}

// 카드 목록 조회 함수
export function getCardList(BASE_URL, authParams) {
let cardListRes = http.get(`${BASE_URL}/api/v1/card`, authParams, {
tags: {method: 'GET', endpoint: 'getCardList'},
});

check(cardListRes, {
'Card List Fetch Success': (r) => r.status === 200,
});

if (cardListRes.status !== 200) {
return null;
}

let cards = JSON.parse(cardListRes.body);

if (cards.length === 0) {
return null;
}

// 첫 번째 카드의 cardId 추출
let firstCard = cards[0];
let cardId = firstCard.cardId;

if (!cardId) {
return null;
}

return {
cardId: cardId
};
}

// 카드 상세 정보 조회 함수
export function getCardDetail(BASE_URL, authParams, cardId) {
let cardDetailRes = http.get(`${BASE_URL}/api/v1/card/${cardId}`, authParams, {
tags: { method: 'GET', endpoint: 'getCardDetail' },
});

check(cardDetailRes, {
'Card Detail Fetch Success': (r) => r.status === 200,
});

if (cardDetailRes.status !== 200) {
return null;
}

return cardDetailRes;
}

// 카드 상세 정보 조회 100번 반복 함수
export function getCardDetailRepeated(BASE_URL, authParams, cardId, repeatCount) {
for (let i = 0; i < repeatCount; i++) {
let cardDetailRes = http.get(`${BASE_URL}/api/v1/card/${cardId}`, authParams, {
tags: { method: 'GET', endpoint: 'getCardDetail' },
});

check(cardDetailRes, {
'Card Detail Fetch Success': (r) => r.status === 200,
});

if (cardDetailRes.status === 200) {
console.error(`[${i + 1}/${repeatCount}] 카드 상세 정보 조회 성공`);
} else {
console.error(`[${i + 1}/${repeatCount}] 카드 상세 정보 조회 실패`);
}

sleep(0.1);
}
}
146 changes: 146 additions & 0 deletions k6-scripts/userFunctions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import http from 'k6/http';
import { randomString, randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
import { check } from 'k6';

// 회원가입 함수
export function signupUser(BASE_URL) {
let userEmail = `user_${randomString(10)}@test.com`;
let userPwd = 'Test1234!';
let userName = `홍길동`;
let userPhoneNumber = `010-${randomIntBetween(1000, 9999)}-${randomIntBetween(1000, 9999)}`;
let userSex = 'MALE';
let walletPassword = '1234';

let signupPayload = JSON.stringify({
userEmail: userEmail,
userPwd: userPwd,
userName: userName,
userPhoneNumber: userPhoneNumber,
userSex: userSex,
walletPassword: walletPassword
});

let params = { headers: { 'Content-Type': 'application/json' } };

let signupRes = http.post(`${BASE_URL}/api/v1/user/signup`, signupPayload, params, {
tags: { method: 'POST', endpoint: 'signup' },
});

check(signupRes, {
'Signup success': (r) => r.status === 201,
});

if (signupRes.status !== 201) {
return null; // 회원가입 실패 시 null 반환
}

return { userEmail, userPwd }; // 로그인에 필요한 userEmail, userPwd 반환
}

// 로그인 함수
export function loginUser(BASE_URL, userEmail, userPwd) {
let loginPayload = JSON.stringify({
userEmail: userEmail,
password: userPwd
});

let params = { headers: { 'Content-Type': 'application/json' } };

let loginRes = http.post(`${BASE_URL}/login`, loginPayload, params, {
tags: { method: 'POST', endpoint: 'login' },
});

check(loginRes, {
'Login success': (r) => r.status === 200,
});

if (loginRes.status !== 200) {
return null; // 로그인 실패 시 null 반환
}

let authToken = JSON.parse(loginRes.body).accessToken;

if (!authToken) {
return null; // JWT 토큰 없을 경우 null 반환
}

return authToken; // 로그인 후 JWT 토큰 반환
}

// 내 정보 조회 함수
export function getUserInfo(BASE_URL, authToken) {
let authParams = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
}
};

let userRes = http.get(`${BASE_URL}/api/v1/user`, authParams, {
tags: { method: 'GET', endpoint: 'user' },
});

check(userRes, {
'Get User Info success': (r) => r.status === 200,
});

if (userRes.status !== 200) {
return null; // 정보 조회 실패 시 null 반환
}

return userRes.body; // 응답 본문 반환
}

// 내 정보 수정 함수
export function updateUserInfo(BASE_URL, authToken) {
let updatePayload = JSON.stringify({
userNickname: `Test_${randomString(8)}`,
userAge: 24,
userSex: "FEMALE"
});

let authParams = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
}
};

let updateRes = http.put(`${BASE_URL}/api/v1/user`, updatePayload, authParams, {
tags: { method: 'PUT', endpoint: 'user' },
});

check(updateRes, {
'Update User Info success': (r) => r.status === 204, // No Content 응답 예상
});

if (updateRes.status !== 204) {
return null; // 수정 실패 시 null 반환
}

return updateRes.status;
}

// 회원 탈퇴 함수
export function deleteUser(BASE_URL, authToken) {
let authParams = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
}
};

let deleteRes = http.delete(`${BASE_URL}/api/v1/user`, null, authParams, {
tags: { method: 'DELETE', endpoint: 'user' },
});

check(deleteRes, {
'Delete User success': (r) => r.status === 204, // No Content 응답 예상
});

if (deleteRes.status !== 204) {
return null; // 탈퇴 실패 시 null 반환
}

return deleteRes.status;
}
Loading