# 대시보드 → n8n → Slack 정기 리포트 연동 설계

작성일: 2026-06-23

## 1. 목적 / 범위

문자ON 관리자 대시보드(`/cmm/main/mainPage.do`)의 주요 운영 지표를 **매일(또는 매주) 정해진 시각에 Slack 채널로 요약 발송**한다.

- 연동 방식: 앱에 **신규 JSON API 엔드포인트**를 추가하고, **n8n이 스케줄로 호출**하여 Slack으로 게시(pull 방식).
- 포함 지표(1차): **매출(일/월), 회원 현황, 문자 발송 현황**.
- 인증: **공유 토큰**(헤더) 단독. (회사 내부망 사용 전제로 IP 허용목록은 제외)
- 비범위(추후): 발신번호/환불 대기 지표, 실시간 이벤트 알림, IP 허용목록.

## 2. 현황 (재사용 자산)

`itn.let.main.web.EgovMainController`가 대시보드 데이터를 아래 서비스 메서드로 조회하고 있으며, **그대로 재사용**한다(신규 SQL 없음).

- 매출: `MjonPayService.selectPayDayChartDashboard`, `selectPayMonthChartDashboard`, `selectPayYearChart`
- 회원: `EgovConectStatsService.selectMberTotStats`, `selectMberCmpHstStatsDashboard`, `selectTodaySttCountString`
- 문자: `MjonMsgService.selectAgentSmsCountStatMainList`, `selectMemDayChartDashboard`

## 3. 엔드포인트 계약 (Contract)

**요청**
```
GET /api/dashboard/summary.do
Header: X-DASHBOARD-TOKEN: <공유 토큰>   (필수)
Query : ?date=YYYY-MM-DD                 (선택, 미지정 시 오늘)
```

**응답** — 앱은 Slack 포맷이 아닌 **순수 도메인 JSON**만 반환(Slack 표현은 n8n 담당, 앱-Slack 결합도 제거).
```jsonc
{
  "isSuccess": true,
  "msg": "",
  "baseDate": "2026-06-23",
  "sales": {                              // 매출(VAT 포함, 단위: 원, 숫자)
    "todayAmt": 1234500,                  // 당일 매출액
    "dayChart":   [ {"date":"06-20","amt":900000}, ... ],   // 최근 4일
    "monthChart": [ {"month":"6월","amt":25000000}, ... ],  // 최근 4개월
    "yearAmtSum": 310000000               // 연간 누적
  },
  "members": {                            // 회원 현황
    "userNorCnt": 0, "userComCnt": 0,     // 개인/기업 정상회원수
    "mberNorTot": 0,                      // 정상회원 합계
    "mberBlockTot": 0, "mberSecessTot": 0,// 정지/탈퇴 누적
    "cmpApply": 0, "cmpApprove": 0, "cmpReject": 0, // 기업회원 신청/승인/반려
    "todayExpireCnt": 0, "todayStopCnt": 0          // 금일 탈퇴/정지
  },
  "sms": {                                // 문자 발송 현황(전송사별)
    "agents": [
      {"agentCode":"ITN019","succCnt":12000,"failCnt":30,"succRate":99}, ...
    ],
    "todaySignupCnt": 0                   // 일 회원가입자수(참고)
  }
}
```

- 원본 서비스가 문자열로 반환하는 금액/건수는 서비스 계층에서 **숫자로 정규화**한다.
- 천단위 콤마/원화 표기 등 **표현 포맷은 n8n**에서 처리한다(앱은 raw 숫자만 제공).
- 누락 지표는 1차 범위에서 제외하며, 향후 `sales`/`members`/`sms` 블록에 필드를 추가하는 식으로 확장한다.

## 4. 인증 / 보호

- `globals.properties`에 `Globals.dashboard.api.token` 추가, 환경별 `globals_*.properties`에서 override.
- 신규 인터셉터 `DashboardApiAuthInterceptor`를 **`/api/dashboard/**` 경로에만** 매핑(`egov-interceptor-servlet.xml`).
  - 요청 헤더 `X-DASHBOARD-TOKEN` 값이 설정 토큰과 불일치/누락 → **401** JSON(`{"isSuccess":false,"msg":"unauthorized"}`)을 직접 작성하고 컨트롤러 진입 차단.
- 엔드포인트가 `*.do`이므로 Spring Security의 DB 기반 URL 권한(`context-security.xml`)에 걸릴 수 있다 → `/api/dashboard/**`를 `security="none"`으로 예외 추가(토큰으로 자체 보호, 세션 불필요).
- 토큰 값은 응답/로그에 노출 금지.

## 5. Java 컴포넌트 구성 (4종 패턴)

신규 모듈 `itn.let.mjo.dashboardapi`:

| 파일 | 역할 |
|---|---|
| `web/DashboardApiController.java` | **`@RestController`**. `GET /api/dashboard/summary.do` → `getDashboardSummary(...)`가 `DashboardSummaryVO`를 직접 반환(Jackson 직렬화, jsonView 미사용). 얇게 유지 |
| `service/DashboardSummaryService.java` | 인터페이스 |
| `service/DashboardSummaryVO.java` | 응답 DTO(sales/members/sms 중첩 + isSuccess/msg/baseDate). Jackson 직렬화용 getter 포함 |
| `service/impl/DashboardSummaryServiceImpl.java` | `@Service`. 기존 `mjonPayService`·`conectStatsService`·`mjonMsgService`를 주입해 조합·숫자 정규화 |
| `web/DashboardApiAuthInterceptor.java` | 토큰 헤더 검증 인터셉터 |

설계 근거 / 규칙:
- **DAO 신규 없음** — 기존 검증된 서비스 메서드만 호출.
- `@RestController`는 `@Controller`를 메타포함 → 디스패처 컨텍스트의 `@Controller` 스캔에 잡힘. Jackson 컨버터는 `<mvc:annotation-driven/>` + `jackson-databind` 의존성 + 기존 `@ResponseBody` 선례로 동작 검증됨.
- 빈 스캔 분리 유지: 컨트롤러/인터셉터=디스패처 컨텍스트, 서비스=루트 컨텍스트.
- 관리자 로그 AOP가 `itn..*Controller.select*`에 걸리므로, 메서드명을 `getDashboardSummary`로(=`select*` 아님) 지어 불필요한 잡로그를 피한다.
- 지표 블록별 try/catch — 개별 지표 실패가 전체 응답을 막지 않도록 하고 실패 블록은 null/0으로 둔다(기존 대시보드 Ajax 관례).

## 6. n8n 워크플로우 / Slack

```
[Schedule Trigger]   매일 09:00 (cron)
       ↓
[HTTP Request]       GET https://<admin-host>/api/dashboard/summary.do
                     Header X-DASHBOARD-TOKEN = {{ $env.MJON_DASH_TOKEN }}
       ↓
[Code (Function)]    도메인 JSON → Slack Block Kit 변환(콤마/원화 포맷 여기서)
       ↓
[Slack node]         지정 채널 게시(OAuth 또는 Incoming Webhook)
```

- Slack 메시지: 제목(`📊 문자ON 일일 리포트 — {baseDate}`) + 매출/회원/문자 섹션을 Block Kit `section` 블록으로 구성.
- 토큰은 n8n 환경변수 `MJON_DASH_TOKEN`에 보관(워크플로우 JSON에 평문 미포함).
- 산출물: import 가능한 `n8n-workflow.json` 예시 + Code 노드 포맷 스니펫.

## 7. 에러처리 / 테스트 / 배포

- 앱 서비스: 지표 블록별 try/catch, 전역 실패 시 `isSuccess:false` + `msg`. 인증 실패는 인터셉터에서 401 JSON.
- 테스트: 본 저장소는 단위테스트 인프라가 없음(surefire `skipTests=true`) → **수동 검증**.
  - `curl -H "X-DASHBOARD-TOKEN: <token>" http://localhost/api/dashboard/summary.do` (토큰 유/무 케이스 모두).
  - n8n 워크플로우 Execute → Slack 채널 수신 확인.
- 설정: `globals.properties`에 `Globals.dashboard.api.token` 추가, 환경별 override. 운영 토큰은 별도 공유.
- 배포: WAR 재빌드 후 WAS 반영. 신규 엔드포인트는 추가 전용이라 기존 기능 영향 없음.

## 8. 결정 사항 요약

- 연동: pull(신규 JSON API + n8n 스케줄)  ✅
- 집계 위치: 신규 Service(접근안 A)  ✅
- 컨트롤러: `@RestController`  ✅
- 인증: 공유 토큰 단독(IP 허용목록 제외)  ✅
- 지표: 매출(일/월) / 회원 현황 / 문자 발송 현황  ✅
