You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dockerfile, appspec.yml, start.sh, stop.sh 기반 EC2/CodeDeploy 배포 구조
테스트
DTO 유효성 검사 테스트, 동시 카테고리 생성 시나리오 테스트
1-3. 팀 프로젝트에서의 개인 기여도
이 프로젝트는 3인 협업으로 진행됐고, 포트폴리오 관점에서 제가 주도적으로 가져간 영역은 아래와 같습니다.
구분
기여 내용
김도연 주도 구현
지출 API, 수입 API, 월간/주간 집계 응답, Gemini 기반 주간 피드백 API, 피드백 스케줄러, ErrorCode 세분화, 운영/배포 흐름 정리
박재성 주도 구현
이메일 인증 회원가입, JWT 로그인 보안, 프로필 API, 저축 목표 API, 카테고리별 지출 목표 API
정현아 주도 구현
예정 결제 API
공동 작업
ERD 설계, 전체 도메인 연결, API 문서화
1-5. 서비스 전체 비즈니스 흐름
flowchart LR
A["회원가입 / 이메일 인증"] --> B["JWT 로그인"]
B --> C["프로필 조회/수정"]
B --> D["지출 기록"]
B --> E["수입 기록"]
B --> F["예정 결제 기록"]
B --> G["저축 목표 생성"]
D --> H["카테고리별 집계 / 주차별 비교"]
E --> I["월간 통합 거래내역 생성"]
F --> I
G --> J["일별 저축 누적"]
D --> K["카테고리별 목표 대비 소비 비교"]
D --> L["Gemini 주간 피드백"]
H --> M["대시보드 / 그래프"]
I --> M
J --> M
K --> M
L --> M
Loading
1-6. 핵심 메시지
이 프로젝트의 강점은 "기록" 자체가 아니라,
기록된 거래를 검증 가능한 데이터, 비교 가능한 숫자, 행동을 유도하는 피드백으로 바꾸는 백엔드 조립력에 있습니다.
1-7. 이 프로젝트를 강하게 만드는 설계 원칙
설계 원칙
실제 코드에서 보이는 구현
식별 가능성
이메일 인증코드, JWT claim, userId 기반 사용자 식별
정합성 우선
사용자 소유권 검증, 커스텀 카테고리 named lock
읽히는 데이터
주차 비교, 카테고리별 비교, 통합 거래 타임라인
재호출 비용 절감
Caffeine 캐시, 주간 피드백 재사용 전략
운영 대비
Actuator, Prometheus, log4jdbc, 배포 스크립트
확장 가능성
Kakao OAuth2 확장 포인트, 인프라 패키지 분리, 도메인별 서비스 분리
flowchart LR
A["입력"] --> B["식별"]
B --> C["검증"]
C --> D["저장"]
D --> E["집계"]
E --> F["피드백"]
F --> G["운영/관측"]
Loading
2-1. 흐름
회원가입과 로그인만 있는 서비스가 아니라 이메일 인증, 비밀번호 재설정, 프로필 변경까지 사용자 수명주기를 다룬다.
지출/수입만 있는 서비스가 아니라 월간 통합 거래내역, 카테고리별 목표, 저축 목표, 예정 결제까지 재무 문맥을 만든다.
집계 숫자만 보여주는 것이 아니라 Gemini 기반 주간 피드백까지 연결한다.
2-2. 운영을 생각한 요소
GlobalExceptionHandler로 예외를 한 군데서 정리한다.
JwtAuthFilter와 JwtUtil로 토큰 해석 책임을 분리한다.
Caffeine 캐시로 주차 비교 그래프 계산을 줄인다.
GET_LOCK 기반 named lock으로 커스텀 카테고리 동시 생성 경쟁 조건을 막는다.
@Scheduled 작업으로 피드백 라이프사이클을 관리한다.
Actuator, Prometheus, log4jdbc, Pinpoint-ready Dockerfile로 운영 가시성을 확보하려는 흔적이 남아 있다.
2-4. 실패 시나리오 제어 구조
flowchart TD
A["동시 요청"] --> A1["GET_LOCK 기반 카테고리 중복 생성 방지"]
B["반복 집계 호출"] --> B1["Caffeine 캐시 + 변경 시 Evict"]
C["오래된 AI 피드백"] --> C1["최신 생성일/수정일 비교 후 재생성"]
D["타 사용자 데이터 접근"] --> D1["JWT userId + 엔티티 소유권 검증"]
E["만료된 인증 코드"] --> E1["5분 만료 + 재발송 시 기존 코드 삭제"]
F["배포 잔존 프로세스"] --> F1["stop.sh -> start.sh 순차 배포"]
Loading
깨질 수 있는 지점
대응 방식
같은 커스텀 카테고리의 동시 생성
MySQL GET_LOCK으로 임계구역 보호
주차 비교 API의 반복 호출
@Cacheable("expendWeekCache")와 변경 시 @CacheEvict
거래가 바뀌었는데 예전 피드백이 남는 문제
최신 생성일/수정일과 피드백 생성일 비교 후 재생성
다른 사용자의 거래 상세 조회/수정/삭제
JWT에서 추출한 userId와 엔티티 소유자 비교
인증 코드 재사용/중복 누적
기존 코드 삭제 후 새 코드 저장, 만료 시간 5분 설정
배포 시 이전 프로세스 잔존
appspec.yml + stop.sh + start.sh로 정리 후 재기동
3. 시스템 전체 구조
3-1. 시스템 컨텍스트
flowchart LR
Client["Client<br/>Web / App"]
Api["WealthTracker API Server<br/>Spring Boot"]
Mysql["MySQL<br/>운영 데이터"]
Mail["Naver SMTP<br/>인증 코드 발송"]
Kakao["Kakao OAuth2<br/>소셜 사용자 정보"]
Gemini["Google Gemini<br/>주간 피드백 생성"]
Cache["Caffeine Cache<br/>주차별 지출 비교"]
Metrics["Actuator / Prometheus"]
Deploy["AWS CodeDeploy / EC2"]
Client --> Api
Api --> Mysql
Api --> Mail
Api --> Kakao
Api --> Gemini
Api --> Cache
Api --> Metrics
Deploy --> Api
Loading
3-2. 요청이 내부에서 지나가는 경로
flowchart LR
A["HTTP Request"] --> B["Controller"]
B --> C["Service"]
C --> D["Repository"]
D --> E["MySQL / External API"]
C --> F["JWT / Validation / Lock / Cache"]
E --> G["DTO 변환"]
G --> H["HTTP Response"]
Loading
3-3. 저장소와 외부 시스템 역할
구성요소
역할
MySQL
사용자, 인증 코드, 지출, 수입, 목표, 결제, 피드백 등 핵심 운영 데이터 저장
Caffeine Cache
주차별 지출 비교 API 결과 캐싱
Naver SMTP
회원가입/비밀번호 재설정 인증코드 발송
Kakao OAuth2
카카오 사용자 정보를 받아 JWT로 연결하는 확장 로그인 구조
Google Gemini
사용자의 이번 주 소비를 자연어 피드백으로 요약
Actuator / Prometheus
헬스체크와 메트릭 노출
Pinpoint-ready Dockerfile
APM 적용을 고려한 런타임 정의
3-4. 패키지 기준 시스템 분해
flowchart TB
A["com.WealthTracker.demo"] --> B["domain.auth"]
A --> C["domain.user"]
A --> D["domain.expend"]
A --> E["domain.income"]
A --> F["domain.expend_income"]
A --> G["domain.target"]
A --> H["domain.category"]
A --> I["domain.payment"]
A --> J["domain.feedback"]
A --> K["global"]
A --> L["infrastructure"]
Loading
3-5. 요청 1건에 함께 작동하는 방어선
flowchart LR
A["Client Request"] --> B["CORS / Security FilterChain"]
B --> C["JWT Parsing"]
C --> D["Controller"]
D --> E["DTO Validation"]
E --> F["Service"]
F --> G["User Lookup / Ownership Check"]
G --> H["Lock / Cache / Scheduler-aware Logic"]
H --> I["Repository"]
I --> J["MySQL / External API"]
J --> K["DTO Response"]
K --> L["GlobalExceptionHandler / Status Code"]
Loading
이 다이어그램의 포인트는 단순합니다.
요청은 Controller 하나만 통과하는 것이 아니라, 인증, 검증, 사용자 확인, 정합성 제어, 예외 처리까지 여러 방어선을 함께 통과합니다.
4. 기술 스택과 저장소 역할
4-1. 기술 스택
분류
기술
Language
Java 17
Framework
Spring Boot 3.3.4
Security
Spring Security, JWT, OAuth2 Client
ORM
Spring Data JPA
DB
MySQL
Cache
Spring Cache + Caffeine
Mail
Spring Mail
API Docs
Springdoc OpenAPI, Swagger UI
AI
Google Gemini REST 호출
Observability
Actuator, Prometheus, log4jdbc
Build
Gradle
Deploy
Docker, AWS CodeDeploy 스크립트
Test
JUnit5, Spring Boot Test
4-2. 왜 이 조합이 좋은가
관계형 정합성이 중요한 거래 데이터는 MySQL에 둔다.
반복 조회가 발생하는 주차별 지출 비교는 캐시로 줄인다.
인증은 세션이 아니라 JWT 중심으로 가져가 프론트 분리에 유리하게 만든다.
AI는 내부 비즈니스 로직과 분리된 REST 호출로 붙여 변경 가능성을 열어둔다.
운영 환경에서 헬스체크와 메트릭을 쉽게 붙일 수 있게 Actuator/Prometheus를 켠다.
flowchart LR
A["Controller"] --> B["Service"]
B --> C["Repository"]
B --> D["Utility / Lock / Cache"]
C --> E["MySQL"]
B --> F["SMTP / Gemini / OAuth2"]
Loading
5-3. 공통 관심사
영역
코드 기준 역할
SecurityConfig
Stateless 세션 정책, JWT 필터 체인 등록, OAuth2 UserService 연결, CORS 설정
JwtAuthFilter
Authorization 헤더에서 Bearer 토큰 파싱 후 SecurityContext 구성
JwtUtil
토큰 생성, userId/email/name claim 저장, 검증
GlobalExceptionHandler
CustomException, JWT 예외, 검증 예외 공통 응답 변환
CategoryExpendInitializer
기본 지출 카테고리 선주입
ExpendCategoryNamedLockFacadeImpl
커스텀 카테고리 생성 시 DB named lock 적용
FeedbackServiceImpl.cleanAndSaveFeedback
주 단위 피드백 정리 스케줄러
5-4. 응답/예외 처리 방식
성공 응답은 도메인 DTO 또는 ReturnCodeDTO(status, message) 형태를 사용합니다.
실패는 CustomException + ErrorCode 조합으로 처리합니다.
DTO 검증 실패는 필드별 에러 맵으로 반환합니다.
6. 상세 기능 설명
6-1. 인증 / 회원가입 / 비밀번호 재설정
관련 API
Method
Endpoint
설명
POST
/api/send-code
회원가입용 이메일 인증코드 발송
GET
/api/verify
이메일 인증코드 확인
POST
/api/resend-code
인증코드 재발송
POST
/api/signup
회원가입
POST
/api/reset-password
비밀번호 재설정 코드 발송
POST
/api/confirm-reset-password
재설정 코드 검증 후 비밀번호 변경
POST
/api/confirm-password
로그인 사용자의 현재 비밀번호 확인
6-1-1. 이메일 인증 기반 회원가입 흐름
sequenceDiagram
participant Client
participant Controller as AuthController
participant Service as SignupServiceImpl
participant Repo as VerificationCodeRepository
participant Mail as EmailServiceImpl
participant DB as MySQL
Client->>Controller: POST /api/send-code
Controller->>Service: createVerificationCodeAndSendEmail(email)
Service->>DB: user email 중복 조회
Service->>Repo: 기존 코드 삭제
Service->>Service: 6자리 인증코드 생성
Service->>Repo: verification_code 저장(5분 만료)
Service->>Mail: 이메일 발송
Controller-->>Client: 인증코드 발송 성공
Client->>Controller: GET /api/verify?email=...&code=...
Controller->>Service: verifyEmail(email, code)
Service->>Repo: email 기준 코드 조회
Service->>Service: 코드 일치 여부 / 만료 여부 확인
Controller-->>Client: 이메일 인증 성공
Client->>Controller: POST /api/signup
Controller->>Service: signupUser(dto)
Service->>Repo: email 기준 인증코드 존재 여부 확인
Service->>Service: 비밀번호 BCrypt 인코딩
Service->>DB: User 저장
Service->>Repo: 인증 코드 삭제
Controller-->>Client: 회원가입 성공
Loading
6-1-2. 코드 기준 핵심 포인트
인증코드는 VerificationCode 엔티티로 DB에 저장합니다.
만료 시간은 LocalDateTime.now().plusMinutes(5) 기준입니다.
중복 이메일은 회원가입 전에 차단합니다.
회원가입이 완료되면 인증코드는 삭제합니다.
비밀번호는 BCryptPasswordEncoder로 저장합니다.
6-1-3. 비밀번호 재설정 흐름
flowchart LR
A["POST /api/reset-password"] --> B["SignupServiceImpl.createPasswordResetCode"]
B --> C["User 존재 확인"]
C --> D["기존 인증코드 삭제"]
D --> E["새 6자리 코드 생성"]
E --> F["verification_codes 저장"]
F --> G["이메일 발송"]
G --> H["POST /api/confirm-reset-password"]
H --> I["코드 유효성 확인"]
I --> J["새 비밀번호와 기존 비밀번호 비교"]
J --> K["BCrypt 인코딩 후 User 저장"]
K --> L["사용한 코드 삭제"]
Loading
6-2. 로그인과 JWT
관련 API
Method
Endpoint
설명
POST
/api/login
이메일/비밀번호 로그인 후 JWT 발급
6-2-1. 로그인 흐름
sequenceDiagram
participant Client
participant Controller as LoginController
participant Service as LoginServiceImpl
participant DB as UserRepository
participant Jwt as JwtUtil
Client->>Controller: POST /api/login
Controller->>Service: login(email, password)
Service->>DB: email 기준 사용자 조회
Service->>Service: enabled 상태 확인
Service->>Service: BCrypt password 비교
Service-->>Controller: User 반환
Controller->>Jwt: createAccessToken(userId, email, name)
Jwt-->>Controller: JWT
Controller-->>Client: token 반환
Loading
6-2-2. 인증된 요청 처리 방식
sequenceDiagram
participant Client
participant Filter as JwtAuthFilter
participant Jwt as JwtUtil
participant UDS as CustomUserDetailsService
participant Repo as UserRepository
participant SC as SecurityContext
participant Controller
Client->>Filter: Authorization: Bearer ...
Filter->>Jwt: validationToken()
Jwt-->>Filter: 유효 여부
Filter->>Jwt: getUserId()
Filter->>UDS: loadUserByUsername(userId)
UDS->>Repo: findByUserId(userId)
Repo-->>UDS: User
UDS-->>Filter: UserDetails
Filter->>SC: Authentication 저장
SC-->>Controller: 현재 인증 컨텍스트 사용 가능
Loading
6-2-3. JWT에 담는 정보
subject: userId
claim.userId
claim.email
claim.name
즉, 단순 문자열 토큰이 아니라 이후 도메인 서비스가 바로 사용자 소유권을 검증할 수 있는 최소 정보가 들어 있습니다.
6-3. 프로필 조회 / 수정
관련 API
Method
Endpoint
설명
GET
/api/profile
현재 사용자 프로필 조회
PUT
/api/profile-update
이름 / 닉네임 수정
6-3-1. 동작 흐름
flowchart LR
A["Authorization Header"] --> B["JwtUtil.getUserId"]
B --> C["UserRepository.findById"]
C --> D["UserProfileResponseDTO 생성"]
D --> E["프로필 조회 응답"]
C --> F["변경 요청 DTO 비교"]
F --> G["변경된 필드만 반영"]
G --> H["User 저장"]
H --> I["프로필 수정 응답"]
Loading
6-3-2. 핵심 포인트
프로필 수정 시 기존 값과 달라진 필드만 반영합니다.
변경이 없으면 기존 프로필을 그대로 반환합니다.
사용자 식별은 항상 JWT 내부 userId 기준입니다.
6-4. 지출 관리
관련 API
Method
Endpoint
설명
POST
/api/expend
지출 기록 생성
GET
/api/expend/list?month=
월별 지출 목록
GET
/api/expend/recent
최근 5개 지출
GET
/api/expend/graph
이번 달 vs 저번 달 주차 비교
GET
/api/expend/{expendId}
지출 상세 조회
DELETE
/api/expend/delete/{expendId}
지출 삭제
PUT
/api/expend/update/{expendId}
지출 수정
GET
/api/expend/expendList
카테고리별 월간 요약
GET
/api/expend/expendList/day
최근 2주 일별 총지출
6-4-1. 지출 생성 흐름
sequenceDiagram
participant Client
participant Controller as ExpendController
participant Service as ExpendServiceImpl
participant Jwt as JwtUtil
participant Lock as ExpendCategoryNamedLockFacade
participant CategoryRepo as ExpendCategoryRepository
participant UserRepo as UserRepository
participant ExpendRepo as ExpendRepository
participant DB as MySQL
Client->>Controller: POST /api/expend
Controller->>Service: writeExpend(dto, token)
Service->>Jwt: validationToken / getUserId
Service->>Service: category / asset 문자열 검증
alt 기본 카테고리
Service->>CategoryRepo: findByCategoryName
else 사용자 정의 카테고리
Service->>Lock: getOrCreateCustomCategoryExpend
Lock->>DB: GET_LOCK(category, 10)
Lock->>CategoryRepo: findByCustomCategoryName or save
Lock->>DB: RELEASE_LOCK(category)
end
Service->>UserRepo: findByUserId
Service->>ExpendRepo: Expend 저장
Controller-->>Client: 기록 성공
Loading
6-4-3. 월간 / 주간 집계 흐름
flowchart TD
A["/api/expend/list?month="] --> B["월별 지출 raw 조회"]
B --> C["CategoryExpend 매핑"]
C --> D["ExpendResponseDTO 리스트 반환"]
E["/api/expend/graph"] --> F["userId 추출"]
F --> G["native query로 이번달/저번달 주차별 총액 계산"]
G --> H["ExpendWeekCompareDTO 반환"]
I["/api/expend/expendList"] --> J["카테고리별 이번달/저번달 금액 계산"]
J --> K["증감률 / UP-DOWN 계산"]
K --> L["카테고리별 최근 내역 2개 결합"]
M["/api/expend/expendList/day"] --> N["최근 2주 일별 총지출 조회"]
N --> O["빈 날짜는 0원으로 채움"]
O --> P["일별 그래프 DTO 반환"]
getAmountByMonth는 각 카테고리별로 이번 달/저번 달 지출을 비교해 UP, DOWN, SAME 메시지를 만듭니다.
getAmountByDate는 최근 14일을 기준으로 빈 날짜까지 채워 그래프 친화적인 응답을 만듭니다.
상세/수정/삭제는 모두 "이 지출이 로그인한 사용자의 것인지"를 검증합니다.
6-5. 수입 관리
관련 API
Method
Endpoint
설명
POST
/api/income
수입 기록 생성
GET
/api/income/list?month=
월별 수입 목록
GET
/api/income/recent
최근 5개 수입
GET
/api/income/{incomeId}
수입 상세 조회
DELETE
/api/income/delete/{incomeId}
수입 삭제
PUT
/api/income/update/{incomeId}
수입 수정
6-5-1. 수입 흐름
flowchart LR
A["POST /api/income"] --> B["JwtUtil로 userId 추출"]
B --> C["카테고리 문자열을 Category_Income으로 변환"]
C --> D["Asset 문자열 변환"]
D --> E["CategoryIncome 조회 또는 생성"]
E --> F["Income 엔티티 저장"]
G["GET /api/income/list"] --> H["월별 Income 조회"]
H --> I["CategoryIncome 매핑"]
I --> J["IncomeResponseDTO 반환"]
Loading
6-5-2. 핵심 포인트
수입은 enum 기반 기본 카테고리 중심으로 관리합니다.
저장 시 카테고리가 없으면 CategoryIncome을 생성해 재사용합니다.
지출과 마찬가지로 사용자 소유권 검증 후 상세/수정/삭제를 수행합니다.
6-6. 수입 + 지출 통합 거래 타임라인
관련 API
Method
Endpoint
설명
GET
/api/expend-income?month=
월별 통합 거래내역 조회
6-6-1. 동작 흐름
sequenceDiagram
participant Client
participant Controller as ExpendIncomeController
participant Service as ExpendIncomeServiceImpl
participant ExpendRepo
participant IncomeRepo
Client->>Controller: GET /api/expend-income?month=...
Controller->>Service: getExpendIncomeAllList(token, month)
Service->>ExpendRepo: 월별 지출 조회
Service->>IncomeRepo: 월별 수입 조회
Service->>Service: 하나의 리스트로 합치기
Service->>Service: 날짜 역순 정렬
Service->>Service: 타입별 DTO 매핑(수입/지출)
Controller-->>Client: 통합 타임라인 반환
Loading
6-6-2. 이 API의 의미
이 API는 단순히 두 테이블을 따로 반환하는 것이 아니라,
같은 월의 수입과 지출을 하나의 타임라인으로 합친다.
날짜 기준으로 정렬한다.
6-7. 저축 목표
관련 API
Method
Endpoint
설명
POST
/api/target/create
저축 목표 생성
PUT
/api/target/update/{targetId}
저축 목표 수정
DELETE
/api/target/delete/{targetId}
저축 목표 삭제
POST
/api/target/savings/{targetId}
특정 날짜에 목표 금액 저축
GET
/api/target/graph?month=
월별 목표/저축 그래프 데이터
6-7-1. 목표 생성 및 저축 누적 흐름
sequenceDiagram
participant Client
participant Controller as TargetController
participant Service as TargetServiceImpl
participant TargetRepo
participant DailyRepo as DailySavingRepository
participant DB as MySQL
Client->>Controller: POST /api/target/create
Controller->>Service: createTarget(dto, token)
Service->>TargetRepo: Target 저장
Controller-->>Client: targetId / targetAmount / savedAmount 반환
Client->>Controller: POST /api/target/savings/{targetId}
Controller->>Service: addDailySaving(targetId, dto, token)
Service->>TargetRepo: 사용자 소유 Target 조회
Service->>Service: DailySaving 생성
Service->>Service: target.addSaving(dailySaving)
Service->>DailyRepo: DailySaving 저장
Service->>TargetRepo: savedAmount 반영된 Target 저장
Controller-->>Client: 저축 성공
Loading
6-7-2. 저축 목표 그래프 흐름
flowchart LR
A["GET /api/target/graph?month="] --> B["TargetRepository.getAllSavedAmountByMonth"]
A --> C["TargetRepository.getAllTargetAmountByMonth"]
B --> D["TargetGraphDTO.nowAmount"]
C --> E["TargetGraphDTO.targetAmount"]
D --> F["그래프 응답"]
E --> F
Loading
6-7-3. 핵심 포인트
Target는 savedAmount를 aggregate처럼 관리합니다.
DailySaving은 목표의 일별 누적 내역을 별도 테이블로 보존합니다.
목표 금액과 실제 저축 금액을 월 단위로 바로 비교할 수 있게 API를 제공합니다.
6-8. 카테고리별 지출 목표
관련 API
Method
Endpoint
설명
POST
/api/category-target/create
카테고리 목표 생성
PUT
/api/category-target/update
카테고리 목표 수정
GET
/api/category-target/{category}
특정 카테고리 목표와 현재 지출 비교
6-8-1. 비교 흐름
flowchart TD
A["GET /api/category-target/{category}"] --> B["JWT에서 userId 추출"]
B --> C["CategoryTarget 조회"]
B --> D["ExpendRepository.thisMonthAmountByCategory"]
C --> E{"목표 존재 여부"}
E -- 없음 --> F["targetAmount=0, 현재 지출 안내 메시지"]
E -- 있음 --> G["목표금액 vs 현재지출 비교"]
G --> H["UP/DOWN 메시지 생성"]
F --> I["CategoryTargetResponseDTO"]
H --> I
Loading
6-8-2. 이 기능이 좋은 이유
단순 목표 CRUD가 아니라 "현재 얼마나 초과/절약 중인지"까지 한 번에 말해줍니다.
실제 지출 합계는 ExpendRepository.thisMonthAmountByCategory로 바로 계산합니다.
목표가 없는 경우도 0원 목표로 처리해 UX가 끊기지 않게 했습니다.
6-9. 예정 결제 관리
관련 API
Method
Endpoint
설명
POST
/api/payment
예정 결제 생성
GET
/api/payment/list
전체 예정 결제 조회
DELETE
/api/payment/delete/{paymentId}
예정 결제 삭제
PUT
/api/payment/update/{paymentId}
예정 결제 수정
GET
/api/payment/recent
최근 2개 예정 결제
GET
/api/payment/paymentList
이번 달 결제 요약
6-9-1. 결제 관리 흐름
sequenceDiagram
participant Client
participant Controller as PaymentController
participant Service as PaymentServiceImpl
participant UserRepo
participant PaymentRepo
Client->>Controller: POST /api/payment
Controller->>Service: writePayment(dto, token)
Service->>UserRepo: user 조회
Service->>Service: dueDate / lastPayment 비어있는지 검증
Service->>PaymentRepo: Payment 저장
Controller-->>Client: 기록 성공
Client->>Controller: GET /api/payment/paymentList
Controller->>Service: getAmountByMonth(token)
Service->>PaymentRepo: 이번달 / 저번달 금액 조회
Service->>PaymentRepo: 최근 2개 결제 조회
Service->>Service: 증감률 계산
Controller-->>Client: 결제 요약 응답
Loading
6-9-2. 결제 도메인에서 볼 포인트
dueDate, lastPayment를 별도 보유해 "다음 납부"와 "최근 납부"를 같이 관리합니다.
이번 달과 저번 달 결제액 비교 API를 따로 제공해 정기 지출 감시 기능으로 연결할 수 있습니다.
최근 2개의 예정 결제를 바로 반환해 홈 화면 위젯을 만들기 좋습니다.
6-10. 주간 AI 피드백
관련 API
Method
Endpoint
설명
GET
/api/feedback
이번 주 지출 기반 AI 피드백 생성 또는 재사용
6-10-1. 피드백 생성 흐름
sequenceDiagram
participant Client
participant Controller as FeedbackController
participant Service as FeedbackServiceImpl
participant ExpendRepo
participant FeedbackRepo
participant Gemini as Google Gemini
participant UserRepo
Client->>Controller: GET /api/feedback
Controller->>Service: sendFeedBack(token, key, url)
Service->>UserRepo: 현재 사용자 조회
Service->>ExpendRepo: 최신 지출 생성일 / 수정일 조회
Service->>FeedbackRepo: 이번 주 피드백 존재 여부 조회
alt 이번 주 피드백 있고 지출 변경 없음
Service-->>Controller: 기존 피드백 재사용
else 새 피드백 필요
Service->>ExpendRepo: 카테고리별 주간 지출 합 조회
Service->>Service: 프롬프트 생성
Service->>Gemini: REST 호출
Gemini-->>Service: 자연어 응답
Service->>FeedbackRepo: 새 피드백 저장
Service->>UserRepo: User.feedBack 연결
Service-->>Controller: 새 피드백 반환
end
Controller-->>Client: 자연어 피드백 응답
Loading
6-10-2. 캐시처럼 동작하는 DB 재사용 전략
flowchart TD
A["이번 주 피드백 요청"] --> B["이번 주 피드백 존재?"]
B -- 아니오 --> C["Gemini 호출"]
B -- 예 --> D["최신 지출 생성/수정 시각 확인"]
D --> E{"피드백 이후 거래 변경 있음?"}
E -- 예 --> C
E -- 아니오 --> F["기존 피드백 재사용"]
C --> G["feedback 테이블 저장"]
G --> H["응답 반환"]
F --> H
Loading
6-10-3. 이유
비싼 외부 AI 호출을 매번 하지 않고, 같은 주간 데이터면 재사용합니다.
단순 총액이 아니라 카테고리별 금액을 프롬프트에 포함해 더 맥락 있는 조언을 유도합니다.
사용자의 지출이 수정되면 피드백도 다시 만들어 데이터 정합성을 지킵니다.
6-10-4. 스케줄러
FeedbackServiceImpl.cleanAndSaveFeedback
매주 일요일 00:00 크론 실행
현재 주 시작 이전에 생성된 피드백 삭제
즉, 주간 피드백을 스냅샷처럼 유지하되 오래된 결과는 정리합니다.
6-11. Kakao OAuth2 연동 확장 포인트
6-11-1. 동작 흐름
flowchart LR
A["Kakao OAuth2 Login"] --> B["Spring Security oauth2Login"]
B --> C["KakaoOAuth2UserService.loadUser"]
C --> D["properties / kakao_account 파싱"]
D --> E["CustomUserInfoDTO 생성"]
E --> F["JwtUtil.createAccessToken"]
F --> G["HttpSession에 login_info / jwt_token 저장"]
Loading
7. API Docs
7-1. 인증 / 사용자
도메인
Method
Endpoint
Auth
POST
/api/send-code
Auth
GET
/api/verify
Auth
POST
/api/resend-code
Auth
POST
/api/signup
Auth
POST
/api/reset-password
Auth
POST
/api/confirm-reset-password
Auth
POST
/api/confirm-password
Login
POST
/api/login
User
GET
/api/profile
User
PUT
/api/profile-update
7-2. 지출 / 수입 / 통합 거래내역
도메인
Method
Endpoint
Expend
POST
/api/expend
Expend
GET
/api/expend/list
Expend
GET
/api/expend/recent
Expend
GET
/api/expend/graph
Expend
GET
/api/expend/{expendId}
Expend
DELETE
/api/expend/delete/{expendId}
Expend
PUT
/api/expend/update/{expendId}
Expend
GET
/api/expend/expendList
Expend
GET
/api/expend/expendList/day
Income
POST
/api/income
Income
GET
/api/income/list
Income
GET
/api/income/recent
Income
GET
/api/income/{incomeId}
Income
DELETE
/api/income/delete/{incomeId}
Income
PUT
/api/income/update/{incomeId}
ExpendIncome
GET
/api/expend-income
7-3. 목표 / 결제 / 피드백
도메인
Method
Endpoint
Target
POST
/api/target/create
Target
PUT
/api/target/update/{targetId}
Target
DELETE
/api/target/delete/{targetId}
Target
POST
/api/target/savings/{targetId}
Target
GET
/api/target/graph
CategoryTarget
POST
/api/category-target/create
CategoryTarget
PUT
/api/category-target/update
CategoryTarget
GET
/api/category-target/{category}
Payment
POST
/api/payment
Payment
GET
/api/payment/list
Payment
DELETE
/api/payment/delete/{paymentId}
Payment
PUT
/api/payment/update/{paymentId}
Payment
GET
/api/payment/recent
Payment
GET
/api/payment/paymentList
Feedback
GET
/api/feedback
8. 데이터 모델
8-1. 핵심 ERD
erDiagram
USER ||--o{ EXPEND : records
USER ||--o{ INCOME : records
USER ||--o{ TARGET : owns
USER ||--o{ CATEGORY_TARGET : owns
USER ||--o{ PAYMENT : owns
USER ||--o| FEEDBACK : latest_feedback
CATEGORY_EXPEND ||--o{ EXPEND : classifies
CATEGORY_INCOME ||--o{ INCOME : classifies
TARGET ||--o{ DAILY_SAVING : accumulates
USER {
bigint userId PK
string email
string password
string name
string nickName
boolean enabled
}
VERIFICATION_CODE {
bigint id PK
string email
string code
datetime expiryDate
}
CATEGORY_EXPEND {
bigint categoryId PK
string categoryName
string customCategoryName
}
EXPEND {
bigint expendId PK
bigint userId FK
bigint categoryId FK
bigint cost
datetime expendDate
string expendName
string asset
datetime createdAt
datetime updateDate
}
CATEGORY_INCOME {
bigint categoryId PK
string categoryName
}
INCOME {
bigint incomeId PK
bigint userId FK
bigint categoryId FK
bigint cost
datetime incomeDate
string incomeName
string asset
datetime createdAt
datetime updateDate
}
TARGET {
bigint targetId PK
bigint userId FK
int targetAmount
int savedAmount
date startDate
date endDate
}
DAILY_SAVING {
bigint savingId PK
bigint targetId FK
date date
int amount
}
CATEGORY_TARGET {
bigint categorytargetId PK
bigint userId FK
string category
bigint targetAmount
}
PAYMENT {
bigint paymentId PK
bigint userId FK
datetime dueDate
datetime lastPayment
bigint cost
string tradeName
string paymentDetail
datetime createdAt
}
FEEDBACK {
bigint feedbackId PK
text content
datetime createdAt
}
Loading
8-2. 엔티티별 의미
엔티티
의미
User
서비스 사용자. 인증 여부, 이름, 닉네임, 피드백 연결 보유
VerificationCode
회원가입/비밀번호 재설정용 6자리 인증코드 저장
Expend
지출 기록
CategoryExpend
기본 + 사용자 정의 지출 카테고리 마스터
Income
수입 기록
CategoryIncome
수입 카테고리 마스터
Target
저축 목표
DailySaving
목표에 대한 일별 저축 내역
CategoryTarget
카테고리별 월간 지출 목표
Payment
예정 결제 / 정기 납부성 지출 관리
FeedBack
이번 주 소비 요약 AI 피드백
8-3. 설계 포인트
User가 여러 재무 도메인의 기준축이 됩니다.
Expend는 CategoryExpend와 분리해 커스텀 카테고리 생성이 가능하게 했습니다.
Target과 DailySaving을 분리해 목표와 실행 이력을 동시에 보존합니다.
FeedBack은 사용자에게 매핑되지만, 생성 주기를 별도 스케줄로 정리합니다.
9. 운영 / 배포 / 관측
9-1. 배포 구조
flowchart LR
A["CI/CD 또는 배포 패키지"] --> B["CodeDeploy"]
B --> C["appspec.yml"]
C --> D["scripts/stop.sh"]
C --> E["scripts/start.sh"]
E --> F["build/libs/*.jar 복사"]
F --> G["spring-webapp.jar 실행"]
G --> H["EC2 애플리케이션 프로세스"]
Loading
10. 테스트와 품질 관리
flowchart LR
A["입력 검증"] --> B["DTO Validation"]
B --> C["CustomException / ErrorCode"]
C --> D["GlobalExceptionHandler"]
D --> E["일관된 오류 응답"]
F["성능/정합성"] --> G["Caffeine Cache"]
F --> H["Named Lock"]
F --> I["스케줄러"]