# ITN-HUB > **2026 신유형 공공저작물 개방사업 · 3단계 법률검토 관리 시스템** > > 신청기관 명부·담당자 관리부터 Mattermost 채널 자동 생성, 권리확인·권리처리 판정, > 사업 현황 대시보드까지 한 곳에서 처리한다. `Spring Boot 3.3.5` `Java 21` `React 18` `PostgreSQL 16` `MyBatis` `Flyway` `Mattermost` --- ## 목차 - [무엇을 하는 시스템인가](#무엇을-하는-시스템인가) - [화면 구성](#화면-구성) - [시작하기](#시작하기) - [환경변수](#환경변수) - [업무 흐름](#업무-흐름) - [핵심 규칙](#핵심-규칙) - [프로젝트 구조](#프로젝트-구조) - [API](#api) - [테스트](#테스트) - [배포](#배포) --- ## 무엇을 하는 시스템인가 기존 Excel VBA 매크로(`M06_Mattermost_기관목록`, `M07_Mattermost_채널일괄생성`)로 하던 작업을 웹으로 옮긴 것이다. 47개 신청기관을 대상으로 다음 흐름을 처리한다. ``` 신청목록 엑셀 담당자 채널 2종 권리확인 / 권리처리 보고서 업로드 → 배정 → 자동 생성 → 변호사 판정 → 제출 기관 명부 신청기관·문정원 문정원·법률검토 엑셀 업로드 후 사업관리 아이티앤·변호사 + 안내글 핀 고정 건별 판정 ``` > **⚠ 주의** > > 이 애플리케이션은 **실제 운영 Mattermost 서버(`https://hub.iten.co.kr`)에 실제 채널을 만든다.** > 처음 쓸 때는 실제 기관이 아니라 테스트 기관(예: 연번 `999`)으로 한 바퀴 돌려보고 시작할 것. > > `MATTERMOST_TOKEN`은 **시스템 관리자(system-admin) 권한** 계정의 Personal Access Token이어야 한다. > 팀 전체 범위에서 비공개 채널을 조회해야(레거시 채널 복구) 중복 생성을 막을 수 있기 때문이다. --- ## 화면 구성 | 메뉴 | 하는 일 | | :-- | :-- | | **Dashboard** | KPI 7타일 · 단계별 칸반(카드 드래그로 단계 이동) · 최근 진행 기관 | | **채널관리** | 신청목록 시트 업로드 · 담당자 4역할 배정 · Mattermost 채널 2종 생성 | | **기관관리** | 기관 상세. 업무단계 스트립 + 7개 탭(채팅·자료·권리확인·권리처리·RE·타임라인·업무메모) | | **담당자관리** | 담당자 등록·수정·삭제, 회원명단 엑셀 일괄 업로드 | | **사업관리** | 3단계 대상 기관 현황 · 신청 확인 연락 이력 · 기관별 최종 보고서 | | **시스템관리** | 채널 초기화(미리보기 → 확인문구 → 실행) | 화면 곳곳의 버튼 옆 **`?` 아이콘**을 누르면 실제 화면 캡처가 담긴 설명서가 열린다(19개 항목). --- ## 시작하기 ### 요구사항 | 항목 | 버전 / 비고 | | :-- | :-- | | JDK | **21** | | PostgreSQL | **16** | | Node.js | 불필요 — `mvn package`가 v20.17.0을 내려받아 프런트를 함께 빌드한다 | | Docker | 백엔드 테스트에만 필요(Testcontainers) | ### 빌드 ```bash mvn -DskipTests package ``` `frontend-maven-plugin`이 React 앱을 빌드해 `src/main/resources/static/`에 넣고, 그 결과가 **단일 jar 하나**로 패키징된다. 프런트를 따로 빌드할 필요가 없다. ### 실행 ```bash export DB_URL=jdbc:postgresql://localhost:5432/itnhub export DB_USERNAME=itnhub export DB_PASSWORD=... export MATTERMOST_URL=https://hub.iten.co.kr export MATTERMOST_TOKEN=... export MATTERMOST_TEAM_ID=... export APP_ADMIN_USERNAME=admin export APP_ADMIN_PASSWORD=... java -jar target/itnhub-0.1.0.jar ``` 기동하면 Flyway가 `itnhub` 스키마와 테이블(V1~V12)을 **자동으로 만든다.** 수동 DDL은 없다. ### 프런트만 반복 개발할 때 ```bash cd frontend && npm install && npm run dev # Vite가 /api 를 :8877 로 프록시 mvn spring-boot:run # 백엔드는 따로 띄워 둔다 ``` --- ## 환경변수 `.env.example`을 복사해 값을 채운다. **실제 값이 든 파일은 커밋하지 않는다.** ### 필수 — 하나라도 비면 기동하지 않는다 | 변수 | 설명 | 예시 | | :-- | :-- | :-- | | `DB_URL` | PostgreSQL JDBC URL | `jdbc:postgresql://192.168.0.60:5432/itnhub` | | `DB_USERNAME` | DB 계정 | `itnhub` | | `DB_PASSWORD` | DB 비밀번호 | | | `MATTERMOST_URL` | Mattermost 베이스 URL | `https://hub.iten.co.kr` | | `MATTERMOST_TOKEN` | **system-admin 권한** Personal Access Token | | | `MATTERMOST_TEAM_ID` | 채널을 만들 팀 ID | | | `APP_ADMIN_USERNAME` | 앱 로그인 계정 | `admin` | | `APP_ADMIN_PASSWORD` | 앱 로그인 비밀번호(충분히 긴 무작위 값) | | ### 선택 — 기본값 있음 | 변수 | 기본값 | 설명 | | :-- | :-- | :-- | | `MATTERMOST_TEAM_NAME` | `itn-hub` | 채널 URL을 만들 때 쓰는 팀 이름 | | `MATTERMOST_DEFAULT_USER_PASSWORD` | `test1234!` | 자동 생성되는 담당자 계정의 초기 비밀번호 | > **참고 —** `application.yml`의 `spring.datasource.hikari.schema: itnhub` 는 **지우면 안 된다.** > `spring.flyway.schemas`는 Flyway 전용 커넥션에만 적용돼서, 이 설정이 없으면 런타임 커넥션이 > `public` 스키마를 보고 `relation "organization" does not exist` 로 죽는다. --- ## 업무 흐름 ### 진행단계 12단계 ``` ① 신청 → ② 목록접수 → ③ 예비검토 → ④ 변호사 배당 → ⑤ 법률검토(권리확인) → ⑥ RE:확인 → ⑦ 법률검토(권리확인) 완료 → ⑧ 법률검토(권리처리) → ⑨ RE:처리 → ⑩ 법률검토(권리처리) 완료 → ⑪ 법률검토 최종완료 → ⑫ 보고서 작성 ``` - 채널을 만들면 **①신청이 자동으로 시작**된다. 그 뒤로는 **전부 수동**이다 — 자료를 올리거나 게시글을 써도 단계가 저절로 넘어가지 않는다(요청자 확정 사항). - 단계 변경 이력은 전부 **타임라인 탭**에 남는다. ### 최초 사용 순서 1. PostgreSQL을 준비하고 환경변수를 채운다 → 기동하면 스키마가 만들어진다 2. 브라우저로 접속해 관리자 계정으로 로그인 3. **담당자관리** 에서 담당자를 등록한다(회원명단 엑셀 일괄 업로드 가능) 4. **채널관리** 에서 신청목록 시트를 올려 기관 명부를 만들고, 기관마다 담당자 4역할을 배정한다 5. **채널 생성** — 채널 2종 + 담당자 계정 + 안내글이 한 번에 준비된다 6. **기관관리 › 권리확인/권리처리** 탭에서 조사 엑셀을 올리고 건별로 판정한다 7. **사업관리** 에서 연락 이력을 남기고 최종 보고서를 올린다 --- ## 핵심 규칙 수정할 때 깨뜨리면 안 되는 약속들이다. | 규칙 | 내용 | | :-- | :-- | | **채널은 항상 2개** | 기관마다 `NNN_기관명 (문정원)` · `NNN_기관명 (법률검토)` 가 쌍으로 만들어진다. 게시글·자료도 늘 양쪽에 함께 반영된다. 한쪽만 처리하는 코드는 버그다. | | **재실행 안전** | 채널 생성·시드 업로드·채널 초기화 모두 여러 번 실행해도 안전하다. 이미 처리된 것은 건너뛰고 없는 것만 만든다. 새 로직에서도 이 원칙을 지킨다. | | **채널 ⇔ 단계** | 채널이 있으면 진행단계가 반드시 있고, 단계가 없으면 채널도 없다(V11이 이 불변식을 맞춘다). 대시보드 칸반의 **단계 미지정** 컬럼은 **채널 미생성 기관만** 담는다. | | **엑셀 재업로드** | 같은 파일을 다시 올려도 **웹에서 고친 값(`web_edited_at`)은 보존**되고 조사원 영역만 덮어쓴다. 병합 기준은 순번이 아니라 **파일의 행 순서**다(원본에 순번 중복이 있다). | | **제3자 권리처리** | 권리 일부 보유·권리 미보유는 `권리처리 추진` / `권리처리 추진 미희망` 택일, 초상권 포함은 `개방불가`까지 3택. 추진이면 공공누리 유형이 `보류`로, 미희망이면 기존 유형이 자동 선택되고 비고에 사유가 적힌다. | | **작성자 자동 기록** | 업무메모·연락 이력의 작성자는 화면에서 고르지 않는다. 로그인 사용자를 서버가 기록하고, 수정해도 최초 작성자·작성시각은 유지된다. | | **연번은 문자열** | 기관 코드는 `연번1_연번2`(`001_00`) 형식이다. 숫자로 바꾸면 앞자리 0이 사라져 매칭이 전부 깨진다. | --- ## 프로젝트 구조 ``` itnhub/ ├─ src/main/java/kr/itn/itnhub/ │ ├─ org/ 기관 명부·배정 ├─ review/ 권리확인 │ ├─ contact/ 담당자 ├─ process/ 권리처리 │ ├─ provision/ 채널 생성(멱등) ├─ stage/ 진행단계·이력 │ ├─ mattermost/ Mattermost API 클라이언트 ├─ timeline/ 타임라인 집계 │ ├─ feed/ 채널 게시글·파일 ├─ memo/ 업무메모 │ ├─ dashboard/ 대시보드 집계 ├─ project/ 사업관리 │ ├─ seed/ 신청목록 시트 시드 └─ system/ 채널 초기화 │ └─ config/ 보안·설정 │ ├─ src/main/resources/ │ ├─ db/migration/ Flyway V1~V12 │ ├─ mapper/ MyBatis XML │ └─ application.yml │ ├─ frontend/src/ │ ├─ components/ 화면 19개 (Dashboard, OrgOverview, ReviewBoard …) │ ├─ help/ 버튼 옆 「?」 도움말 — 본문과 캡처가 여기 한 곳에 있다 │ ├─ api/client.ts 모든 서버 호출이 지나는 단일 창구 │ └─ stages.ts 12단계 라벨 정본 │ ├─ deploy/ 176 서버 배포 정의 (compose · env 템플릿 · 절차서) └─ Dockerfile 프런트까지 함께 빌드하는 멀티스테이지 ``` --- ## API | 영역 | 엔드포인트 | | :-- | :-- | | 기관 | `GET /api/orgs` · `PUT /api/orgs/{id}/assignments` · `POST /api/orgs/{id}/channels` | | 진행단계 | `PUT /api/orgs/{id}/stage` · `GET /api/orgs/{id}/stage-history` | | 채팅·자료 | `/api/orgs/{id}/posts` `messages` `notice` `files` · `GET /api/files/{fileId}` | | 권리확인 | `/api/orgs/{id}/review` `import` `download` · `PUT …/review/bulk` | | 권리처리 | `/api/orgs/{id}/process` `import` `download` · `PUT …/process/bulk` | | 대시보드 | `GET /api/dashboard` — KPI·칸반·최근 진행 기관을 한 번에 | | 사업관리 | `GET /api/projects` · `/api/orgs/{id}/contact-logs` · `/api/orgs/{id}/reports` | | 업무메모 | `GET` `POST` `PUT` `DELETE /api/orgs/{id}/memos` | | 타임라인 | `GET /api/orgs/{id}/timeline` | | 시스템 | `GET /api/system/reset/preview` · `POST /api/system/reset/channels` | 전 구간 로그인이 필요하고(`/api/auth/login` 제외), 변경 요청은 CSRF 토큰을 함께 보낸다. --- ## 테스트 ```bash mvn test # 백엔드 221건 — Testcontainers로 PostgreSQL을 띄우므로 Docker 필요 cd frontend && npm test # 프런트 191건 ``` --- ## 배포 Docker 기준 상세 절차는 **[`deploy/README.md`](deploy/README.md)** 에 있다. ```bash docker build -t itnhub:latest . sudo cp deploy/itnhub.env.example /etc/itnhub/itnhub.env # 값 채우기 cd /opt/itnhub && sudo docker compose up -d ``` | | | | :-- | :-- | | 산출물 | **단일 jar** — React가 그 안에 있어 서버에 Node도 웹서버도 필요 없다 | | 영속 볼륨 | **불필요** — 업로드 파일은 DB(`org_report`)와 Mattermost에 저장된다 | | DB | 컨테이너로 띄우지 않고 기존 PostgreSQL을 그대로 쓴다 | | 시간대 | 컨테이너를 `Asia/Seoul`로 고정 — UTC면 도달 일시·D+n이 9시간 어긋난다 | | 헬스체크 | `GET /login` 200 (actuator 미포함) | > **⚠ 빌드 머신에는 인터넷이 필요하다**(Node·npm·Maven을 내려받는다). 실행 서버는 필요 없다. > 사내망이 막혀 있으면 Maven 미러와 `nodeDownloadRoot`를 사내 저장소로 돌려야 한다.