diff --git a/HANDOFF.md b/HANDOFF.md index 8dbbb48..5d4ccff 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -191,20 +191,115 @@ 운영 DB 최신 상태: ga schema V73 적용 완료. -## 4. 잔여 작업 (P6) +## 3-11. P6 PoC — 외부 연동 어댑터 모듈 (V81~V84, 2026-05-24 라이브 적용) -P5-W2 / P5-W3(메뉴·권한 보강 V74 + 본론 V75~V78) 모두 완료. 잔여는 P6뿐. +P6 본 작업의 PoC가 완료되어 어댑터 추상화 + Mock 구현 + SDK 분기 + 운영 잔여 기능까지 들어감. +실제 KFTC/Toast SDK 통합 시 어댑터 구현체만 바꾸면 ga-api/ga-common 무변경. -### P6 — 외부 연동 (P5 이후) -| 항목 | 설명 | -|---|---| -| 펌뱅킹 실연동 | withdraw_request 결재 완료 → 은행 API 직접 호출 | -| SMS/카카오 발송 | user_notification 채널 실연동 | -| 모바일 셀프 조회 | 설계사 전용 모바일 앱 or PWA | +| 항목 | 마이그레이션 | 상태 | +|---|---|---| +| 분급 비율 회차(월) 단위 등록 지원 | V81 | 완료 | +| withdraw_request 펌뱅킹 추적 컬럼 (transfer_tx_id/transfer_status/transfer_at) | V82 | 완료 | +| WITHDRAW_REQUESTS EXECUTE 권한 보강 (오기 — 0건 적용) | V83 | 완료(no-op) | +| WITHDRAW 메뉴 EXECUTE 권한 (V83 보정본) | V84 | 완료 | + +핵심 산출물: +- **신규 모듈 `ga-external-adapter`** — 외부 SDK 의존성 격리, settings.gradle 등록 +- ga-common.adapter.bank: `BankTransferAdapter` 인터페이스 + Request/Response/Status +- ga-common.adapter.message: `MessageAdapter` 인터페이스 + Channel(SMS/KAKAO)/Request/Response +- `MockBankTransferAdapter` / `MockMessageAdapter` (로그 + 계좌·전화 마스킹, 기본 활성) +- `KftcBankTransferAdapter` / `ToastMessageAdapter` 스켈레톤 (UnsupportedOperationException, SDK 통합 시 구현) +- `@ConditionalOnProperty` 프로파일 분기: `app.adapter.bank=mock|kftc`, `app.adapter.message=mock|toast` +- `ApprovalService.handleApprovalCallback(WITHDRAW)` — 최종 승인 시 펌뱅킹 어댑터 호출 → 성공 시 APPROVED→SENT 전이 + transfer_tx_id 영속화, 실패 시 APPROVED 유지 + fail_reason 기록 +- `ApprovalService.sendStepNotification` — DB 알림 외 SMS 어댑터 호출 (UserMapper.selectFirstActiveContactByRoleCode 로 실제 결재자 phone 매핑, phone 미확보 시 SMS skip) +- `WithdrawBankRetryService` + `BankRetryController` (`POST /api/internal/bank-retry`) — 실패 transfer 재시도 배치 + `@EnableScheduling` + cron 옵션(기본 비활성) + `app.bank-retry.limit` +- `WithdrawRequestMapper.selectFailedTransfers` / `updateTransferResult` 추가 +- `WithdrawRequestVO/Resp` + `WithdrawRequestMapper.xml` + 화면 `WithdrawRequest.tsx` 에 transfer_tx_id/transfer_status/transfer_at 노출 + +설정값(application.yml): +```yaml +app: + adapter: + bank: ${ADAPTER_BANK:mock} # mock | kftc + message: ${ADAPTER_MESSAGE:mock} # mock | toast + bank-retry: + limit: ${BANK_RETRY_LIMIT:50} + cron: ${BANK_RETRY_CRON:0 0 0 1 1 ?} # 사실상 비활성. 운영 활성화 예 "0 */10 * * * *" +``` + +통합 검증 결과 (2026-05-24): +- 5모듈 컴파일 BUILD SUCCESSFUL (ga-common/ga-core/ga-external-adapter/ga-api/ga-batch) +- 운영 DB v82~v84 적용 완료, ga-api 8082 가동 +- POST /api/auth/login → 200 OK, accessToken 발급 +- GET /api/withdraw-requests → 200 OK, 2건 모두 status=SENT / transfer_status=ACCEPTED / transferTxId="MOCK-…" 영속화 +- POST /api/internal/bank-retry → 200 OK, data=0 (실패 건 없음 — 정상) +- GET /api/approvals/requests/mine → 200 OK, WITHDRAW APPROVED 이력 정상 +- GET /api/forecast-kpi → 200 OK +- GET /api/year-end-statements → 200 OK + +보정 사항: +- V83 작성 시 menu_code 오기로 0건 적용 → V84 로 정정 보강 + +## 4. 잔여 작업 (P6 실연동) + +P5 전체 + P6 PoC(어댑터 모듈 + Mock 구현 + SDK 분기 + 재시도 배치 + DB 추적 컬럼) 완료. +잔여는 **외부 스펙 확정 후 적용** 가능한 항목 + 모바일. + +### P6 실연동 +| 항목 | 설명 | 선행 조건 | +|---|---|---| +| KFTC 펌뱅킹 실 SDK 통합 | `KftcBankTransferAdapter` 본체 구현 + 인증·엔드포인트 주입 | KFTC API 키·계약·테스트망 | +| Toast(NHN) SMS 실 SDK 통합 | `ToastMessageAdapter` 본체 구현 | 계정·App Key·발신번호·템플릿 코드 | +| 카카오 알림톡 실 SDK 통합 | 동일 (채널만 KAKAO) | 비즈 채널·템플릿 승인 | +| 펌뱅킹 SETTLED 동기화 | ACCEPTED→SETTLED 폴링 스케줄 (queryStatus 사용) | 실 SDK 통합 이후 | +| 펌뱅킹/메시지 호출 감사 이력 | `external_call_log` 테이블 + 영속화 + 조회 화면 | 독립 진행 가능 | +| 모바일 셀프 조회 PWA | 설계사 전용. 정산 명세서/출금 신청/공지 등 | 화면 우선순위 협의 | + +## 3-12. P6 PoC — 외부연동 호출 감사 이력 (V85, 2026-05-24 라이브 적용) + +P6 PoC 의 운영성 보강. 펌뱅킹/SMS·카카오 어댑터 모든 호출이 `external_call_log` 테이블에 AOP 로 자동 영속화. 외부 SDK 통합 후에도 그대로 재사용. + +| 항목 | 위치 | 상태 | +|---|---|---| +| external_call_log 테이블 + 인덱스 + SYSTEM_EXTERNAL_CALL 메뉴/권한 | V85 | 완료 | +| ExternalCallLogVO / Resp / SearchParam | ga-core | 완료 | +| ExternalCallLogMapper(.java/.xml) — insertOne + selectList + selectById | ga-core | 완료 | +| ExternalCallLogService — recordCall @Transactional(REQUIRES_NEW) | ga-api | 완료 | +| ExternalCallLogController — GET /api/external-call-logs[/{id}] | ga-api | 완료 | +| ExternalCallLoggingAspect — BankTransferAdapter+/MessageAdapter+ 모든 메서드 @Around | ga-api/aop | 완료 | +| ExternalCallLogList.tsx + api/externalCallLog.ts + App.tsx 라우트 | ga-frontend | 완료 | + +핵심 동작: +- 어댑터 호출 전/후·예외 모두 `external_call_log` INSERT (호출자 트랜잭션과 분리 — REQUIRES_NEW) +- request/response 는 PII 마스킹된 JSON 한 줄로 요약 (계좌·전화 뒤 4자리, 이름 첫글자만) +- 영속화 자체가 실패해도 비즈니스 흐름은 절대 깨지 않음 (warn 로그만) +- `target_ref_type/id` 자동 추출: WITHDRAW(BankTransferRequest.withdrawRequestId), MessageRequest.refType/refId + +통합 검증 결과 (2026-05-24): +- Flyway: V85 자동 적용 → 운영 DB schema **v85** +- 5모듈 컴파일 BUILD SUCCESSFUL + ga-frontend TS 타입 검증 통과 +- 라이브 시나리오: 신규 withdraw#3 → 결재 1단계 + 2단계 APPROVE → 최종 승인 → 펌뱅킹 호출 + - withdraw#3 status=SENT, transferTxId=MOCK-…, transferStatus=ACCEPTED + - GET /api/external-call-logs → total=1, log#1: BANK/MockBankTransferAdapter/requestTransfer/success=true/durMs=3/target=WITHDRAW#3 + - 상세: requestSummary 의 accountNoMasked="****0123" — PII 마스킹 정상 (평문 비노출) + +## 4. 잔여 작업 (P6 실연동) + +P5 전체 + P6 PoC(어댑터 모듈 + Mock 구현 + SDK 분기 + 재시도 배치 + DB 추적 컬럼 + **호출 감사 이력**) 완료. +잔여는 **외부 스펙 확정 후 적용** 가능한 항목 + 모바일. + +### P6 실연동 +| 항목 | 설명 | 선행 조건 | +|---|---|---| +| KFTC 펌뱅킹 실 SDK 통합 | `KftcBankTransferAdapter` 본체 구현 + 인증·엔드포인트 주입 | KFTC API 키·계약·테스트망 | +| Toast(NHN) SMS 실 SDK 통합 | `ToastMessageAdapter` 본체 구현 | 계정·App Key·발신번호·템플릿 코드 | +| 카카오 알림톡 실 SDK 통합 | 동일 (채널만 KAKAO) | 비즈 채널·템플릿 승인 | +| 펌뱅킹 SETTLED 동기화 | ACCEPTED→SETTLED 폴링 스케줄 (queryStatus 사용) | 실 SDK 통합 이후 | +| 모바일 셀프 조회 PWA | 설계사 전용. 정산 명세서/출금 신청/공지 등 | 화면 우선순위 협의 | ### 운영 DB 마이그레이션 상태 -운영 DB `192.168.0.60:55432/trading_ai/ga` 현재 **V78 적용 완료** (2026-05-21). -V74~V78 Flyway 자동 적용 확인. ga-api 8082 가동 중. +운영 DB `192.168.0.60:55432/trading_ai/ga` 현재 **V85 적용 완료** (2026-05-24). +V79~V85 Flyway 자동 적용 확인. ga-api 8082 가동 중. 단위/통합 테스트는 SqlSessionFactory 환경 설정 문제로 아직 미작성 상태입니다. @@ -252,16 +347,21 @@ V74~V78 Flyway 자동 적용 확인. ga-api 8082 가동 중. - 스모크: 신규 4 + 회귀 4 = **GET 8/8 PASS**, 쓰기(regenerate/detect/draft) 4/4 PASS - 보정: YearEndStatementMapper.xml 의 `a.agent_code` 미존재 컬럼 참조 제거 (agent 테이블에 agent_code 없음) -## 5. 재개 절차 (다음 세션 — P6 라운드) +## 5. 재개 절차 (다음 세션 — P6 실연동 라운드) 1. **Claude Code 시작 시 cwd 확인**: `C:\Users\kyu\Desktop\ga_pro` (현재 세션과 동일) 2. **Agent Teams 활성화 확인**: `.claude/settings.json` 커밋되어 있음 3. **HANDOFF.md 읽기**: 본 문서 + 최근 git log로 컨텍스트 확보 -4. **운영 DB 상태**: **V78 적용 완료**. ga-api 8082 가동 중 (필요 시 `python verify_v74.py` / `smoke_v78.py` 재실행) -5. **권장 시작 지점**: P6 외부 연동 — 펌뱅킹 실연동 / SMS·카카오 발송 / 모바일 셀프 조회 -6. **사용자 결정 필요 항목**: +4. **운영 DB 상태**: **V85 적용 완료**. ga-api 8082 가동 중 (필요 시 `python verify_v74.py` / `smoke_v78.py` 재실행) +5. **권장 시작 지점 (외부 스펙 미정 시)**: + - 모바일 셀프 조회 PWA 스캐폴드 (화면 우선순위 협의) + - 펌뱅킹 SETTLED 동기화 폴러 (Mock 검증 → 실 SDK 통합 시 가치) + - 알림 채널 SMS phone 매핑 보강 (현재 user.phone 미설정 시 SMS skip — phone 등록 화면) +6. **사용자 결정 필요 항목 (P6 실연동 차단 사유)**: + - KFTC 펌뱅킹 API 계약·인증 정보 (실 SDK 통합 차단) + - NHN Toast / 카카오 비즈채널 계정 + 템플릿 코드 (실 메시지 통합 차단) + - 모바일 PWA 화면 우선순위 (잔여 27 빈 화면 중 어떤 것부터) - P2-1 마감 도메인의 마감 후 수정 차단 AOP 구현 방식 (전역 AOP vs Service-level 가드) - - P6 펌뱅킹/메시징 외부 연동처 실제 스펙 (현재 미정 → 착수 전 확인 필요) ## 6. PL 가이드 (다음 세션 유의사항) diff --git a/ga-api/src/main/java/com/ga/api/aop/ExternalCallLoggingAspect.java b/ga-api/src/main/java/com/ga/api/aop/ExternalCallLoggingAspect.java new file mode 100644 index 0000000..aceec60 --- /dev/null +++ b/ga-api/src/main/java/com/ga/api/aop/ExternalCallLoggingAspect.java @@ -0,0 +1,179 @@ +package com.ga.api.aop; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ga.api.service.external.ExternalCallLogService; +import com.ga.common.adapter.bank.BankTransferRequest; +import com.ga.common.adapter.bank.BankTransferResponse; +import com.ga.common.adapter.message.MessageRequest; +import com.ga.common.adapter.message.MessageResponse; +import com.ga.common.util.SecurityUtil; +import com.ga.core.vo.external.ExternalCallLogVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 외부 연동 어댑터 호출 감사 이력 자동 영속화 (V85 external_call_log). + * + *
대상: {@link com.ga.common.adapter.bank.BankTransferAdapter}, + * {@link com.ga.common.adapter.message.MessageAdapter} 모든 메서드. + * + *
정책: + *
{@link #recordCall} 은 AOP({@link com.ga.api.aop.ExternalCallLoggingAspect})에서 호출된다.
+ * 호출자 트랜잭션 롤백과 무관하게 영속화돼야 하므로 {@link Propagation#REQUIRES_NEW}.
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ExternalCallLogService {
+
+ private final ExternalCallLogMapper mapper;
+
+ /** AOP에서 호출. 호출자 트랜잭션과 분리(REQUIRES_NEW). */
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void recordCall(ExternalCallLogVO vo) {
+ mapper.insertOne(vo);
+ }
+
+ @Transactional(readOnly = true)
+ public PageResponse
+ {detail.requestSummary ?? '-'}
+
+
+ {detail.responseSummary ?? '-'}
+
+