hehihoho3@gmail.com 07-27 f7e17ea config: 서비스 포트를 8877로 고정 UNIX

ITN-HUB#

2026 신유형 공공저작물 개방사업 · 3단계 법률검토 관리 시스템

신청기관 명부·담당자 관리부터 Mattermost 채널 자동 생성, 권리확인·권리처리 판정,
사업 현황 대시보드까지 한 곳에서 처리한다.

Spring Boot 3.3.5 Java 21 React 18 PostgreSQL 16 MyBatis Flyway Mattermost


목차#


무엇을 하는 시스템인가#

기존 Excel VBA 매크로(M06_Mattermost_기관목록, M07_Mattermost_채널일괄생성)로 하던 작업을
웹으로 옮긴 것이다. 47개 신청기관을 대상으로 다음 흐름을 처리한다.

 신청목록 엑셀        담당자            채널 2종         권리확인 / 권리처리       보고서
    업로드      →     배정      →     자동 생성    →      변호사 판정       →     제출
   기관 명부      신청기관·문정원     문정원·법률검토      엑셀 업로드 후          사업관리
                 아이티앤·변호사     + 안내글 핀 고정        건별 판정

⚠ 주의

이 애플리케이션은 실제 운영 Mattermost 서버(https://hub.iten.co.kr)에 실제 채널을 만든다.
처음 쓸 때는 실제 기관이 아니라 테스트 기관(예: 연번 999)으로 한 바퀴 돌려보고 시작할 것.

MATTERMOST_TOKEN시스템 관리자(system-admin) 권한 계정의 Personal Access Token이어야 한다.
팀 전체 범위에서 비공개 채널을 조회해야(레거시 채널 복구) 중복 생성을 막을 수 있기 때문이다.


화면 구성#

메뉴하는 일
DashboardKPI 7타일 · 단계별 칸반(카드 드래그로 단계 이동) · 최근 진행 기관
채널관리신청목록 시트 업로드 · 담당자 4역할 배정 · Mattermost 채널 2종 생성
기관관리기관 상세. 업무단계 스트립 + 7개 탭(채팅·자료·권리확인·권리처리·RE·타임라인·업무메모)
담당자관리담당자 등록·수정·삭제, 회원명단 엑셀 일괄 업로드
사업관리3단계 대상 기관 현황 · 신청 확인 연락 이력 · 기관별 최종 보고서
시스템관리채널 초기화(미리보기 → 확인문구 → 실행)

화면 곳곳의 버튼 옆 ? 아이콘을 누르면 실제 화면 캡처가 담긴 설명서가 열린다(19개 항목).


시작하기#

요구사항#

항목버전 / 비고
JDK21
PostgreSQL16
Node.js불필요 — mvn package가 v20.17.0을 내려받아 프런트를 함께 빌드한다
Docker백엔드 테스트에만 필요(Testcontainers)

빌드#

mvn -DskipTests package

frontend-maven-plugin이 React 앱을 빌드해 src/main/resources/static/에 넣고,
그 결과가 단일 jar 하나로 패키징된다. 프런트를 따로 빌드할 필요가 없다.

실행#

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은 없다.

프런트만 반복 개발할 때#

cd frontend && npm install && npm run dev   # Vite가 /api 를 :8877 로 프록시
mvn spring-boot:run                          # 백엔드는 따로 띄워 둔다

환경변수#

.env.example을 복사해 값을 채운다. 실제 값이 든 파일은 커밋하지 않는다.

필수 — 하나라도 비면 기동하지 않는다#

변수설명예시
DB_URLPostgreSQL JDBC URLjdbc:postgresql://192.168.0.60:5432/itnhub
DB_USERNAMEDB 계정itnhub
DB_PASSWORDDB 비밀번호
MATTERMOST_URLMattermost 베이스 URLhttps://hub.iten.co.kr
MATTERMOST_TOKENsystem-admin 권한 Personal Access Token
MATTERMOST_TEAM_ID채널을 만들 팀 ID
APP_ADMIN_USERNAME앱 로그인 계정admin
APP_ADMIN_PASSWORD앱 로그인 비밀번호(충분히 긴 무작위 값)

선택 — 기본값 있음#

변수기본값설명
MATTERMOST_TEAM_NAMEitn-hub채널 URL을 만들 때 쓰는 팀 이름
MATTERMOST_DEFAULT_USER_PASSWORDtest1234!자동 생성되는 담당자 계정의 초기 비밀번호

참고 — application.ymlspring.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 토큰을 함께 보낸다.


테스트#

mvn test                    # 백엔드 221건 — Testcontainers로 PostgreSQL을 띄우므로 Docker 필요
cd frontend && npm test     # 프런트 191건

배포#

Docker 기준 상세 절차는 deploy/README.md 에 있다.

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를 사내 저장소로 돌려야 한다.