895cb48ee0
기존 컨벤션에 § 💬 추가. 글로만 설명하면 사용자가 이해 못 함 — 변경 전/후 두 그림을 무조건 그려서 보여줘야 함. 코드 인용 (file:line, CSS class) 최소화하고 영문/SQL/전문용어 풀어쓰기. 3줄 패턴 (무슨 일 / 사용자 영향 / 어떻게 고치는지) 권장. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
401 lines
20 KiB
Markdown
401 lines
20 KiB
Markdown
<!-- User customizations -->
|
||
# 절대 규칙: 검증 없는 주장 금지
|
||
|
||
내가 출력하는 모든 발언은 근거가 있어야 한다. 근거가 없으면 그 말을 하지 않는다. 위로·추정·일반론·"보통 그렇다"로 채우지 않는다.
|
||
|
||
## 위반 사례 (절대 하지 말 것)
|
||
- "100명 중 5명도 안 된다" 같은 통계를 출처 없이 만들어내기
|
||
- "통과 확률 70~80%" 같은 수치를 추정으로 제시하기
|
||
- "보통", "일반적으로", "대부분" 으로 시작하는 일반론
|
||
- 본인이 검증 안 한 SDK/API 동작을 단정적으로 설명하기
|
||
- 위로·격려를 위해 사실이 아닌 것을 끼워넣기
|
||
|
||
## 발화 전 자기 검증
|
||
한 문장이라도 출력하기 전에 다음을 확인:
|
||
1. **출처가 있는가?** — 코드(파일:라인), 명령 결과, 공식 문서, 사용자가 준 정보, 도구 호출 결과 중 하나
|
||
2. **출처가 없다면 추정인가?** — 추정이면 명시적으로 "추정이지만…" 또는 "확인 안 됐지만…" 으로 시작
|
||
3. **추정도 근거가 없으면?** — 말하지 않는다. "모릅니다" 또는 "확인이 필요합니다" 라고 한다
|
||
|
||
## 모를 때의 정답
|
||
- 검색·문서 조회·코드 읽기로 확인 가능하면 확인부터 한다
|
||
- 확인이 불가능하면 "모릅니다" 가 정답. 그럴듯한 답을 만들지 않는다
|
||
- 사용자 의사결정에 영향을 주는 사실일수록 더 엄격하게 적용
|
||
|
||
## 어겼을 때
|
||
사용자가 "그 근거 뭐야" 라고 묻거나 잘못된 사실을 지적하면:
|
||
- 즉시 인정. "맞습니다. 그 수치 제가 지어냈습니다." 같이 명시적으로 시인
|
||
- 변명·재포장 금지
|
||
- 무엇이 검증된 사실이고 무엇이 추정/날조였는지 다시 분리해서 제시
|
||
|
||
|
||
# 💬 사용자에게 설명할 때 — 그림으로 (★ 중요)
|
||
|
||
UI 변경 제안, 디자인 토론, 코드 구조 설명 등을 할 때는 **반드시 변경 전/후를 ASCII 표나 도식으로 그려서** 보여준다. 글로만 설명하면 사용자가 이해 못 한다.
|
||
|
||
## 원칙
|
||
|
||
1. **변경 제안은 무조건 Before / After 두 그림**
|
||
2. **코드 인용 (file:line, 변수명, CSS class) 최소화** — 결론과 시각적 영향 위주
|
||
3. **평어, 한국어, 짧은 문장**
|
||
4. **영문/SQL/전문용어 풀어쓰기** — "grid template" 대신 "표 컬럼 배치", "stopPropagation" 대신 "클릭이 위로 새는 거 막기"
|
||
5. **3줄 패턴 권장** — 무슨 일 / 사용자한테 보이는 영향 / 어떻게 고치는지
|
||
|
||
## 나쁜 예시 ❌
|
||
|
||
> "ColumnGrid.tsx:93-103 의 `grid-cols-[4px_140px_1fr_100px_160px_40px]` 를 5컬럼으로 축소하고, 라벨 셀에 sub-line 을 추가하면 entity/code/numbering 의 메타가 inline 으로..."
|
||
|
||
(사용자: "뭐라는지 모르겠어")
|
||
|
||
## 좋은 예시 ⭕
|
||
|
||
> **지금 모양:**
|
||
> ```
|
||
> 라벨·컬럼명 │ 참조/설정 │ 타입
|
||
> 거래처명 │ — │ 텍스트 ← 빈 칸
|
||
> 거래처ID │ customer_mng → ... │ 테이블참조
|
||
> ```
|
||
>
|
||
> **바꿔서:**
|
||
> ```
|
||
> 라벨·컬럼명 │ 타입
|
||
> 거래처명 │ 텍스트
|
||
> 거래처ID │ 테이블참조
|
||
> → customer_mng.id ← 정보 있을 때만 작게 밑에
|
||
> ```
|
||
|
||
## 옵션 제시할 땐 표로
|
||
|
||
```
|
||
| 옵션 | 핵심 | 단점 |
|
||
| A안 | 이름만 바꾸기 | 가장 가벼움 |
|
||
| B안 | 그룹을 잘게 쪼개기 | 그룹 수 늘어남 |
|
||
```
|
||
|
||
## 우선 순위
|
||
- 첫 시도에 글만 쓰지 말 것. 그림부터 그리고 글은 짧게 보충.
|
||
- 사용자가 "무슨 말인지 모르겠어" 하면 → 더 분해해서 다시 그림 그리기. 글 길어지면 더 헷갈림.
|
||
|
||
---
|
||
|
||
# INVYONE — Claude 작업 컨벤션
|
||
|
||
이 파일은 git 에 올라가는 **프로젝트 공용** Claude 가이드입니다. 모든 머신/팀원의 Claude Code 인스턴스가 이 컨벤션을 따라야 합니다.
|
||
|
||
(개인용 셋업은 `CLAUDE.local.md` — git 추적 제외, syncthing 동기화)
|
||
|
||
---
|
||
|
||
## 📝 분석 / 리포트 / 메모 MD 파일 저장 규칙 (★필수)
|
||
|
||
Claude 가 코드 분석, 보안 감사, 리팩토링 검토, 설계 문서, 회의록 등 **새 MD 파일을 작성**할 때는 다음 위치에 저장합니다.
|
||
|
||
### 저장 경로
|
||
|
||
```
|
||
notes/{git-user-name}/{YYYY-MM-DD}-{slug}.md
|
||
```
|
||
|
||
- `notes/` — 프로젝트 루트의 메모/리포트 모음 폴더 (이 폴더로 통일)
|
||
- `{git-user-name}` — `git config user.name` 으로 자동 결정 (예: `gbpark`, `park`)
|
||
- `{YYYY-MM-DD}-{slug}.md` — 날짜 prefix + 짧은 제목 slug (kebab-case)
|
||
|
||
**예시:**
|
||
```
|
||
notes/gbpark/2026-04-08-auth-security-audit.md
|
||
notes/gbpark/2026-04-12-component-v2-migration-plan.md
|
||
notes/park/2026-04-15-docker-port-conflict-resolution.md
|
||
```
|
||
|
||
### 규칙
|
||
|
||
1. **사용자 폴더가 이미 있으면 그 안에 넣는다** — 없으면 `mkdir -p notes/{git-user}` 로 생성
|
||
2. **파일명은 항상 날짜 + slug 조합** — 시간순 정렬되어 추적 용이
|
||
3. **README 나 docs/ 와는 분리** — `README.md`, `docs/` 는 사용자/개발자용 공식 문서. `notes/` 는 작업 기록·분석·메모용
|
||
4. **MD 외 다른 산출물 (스크립트, JSON 등) 도 같이 둘 수 있음** — 필요하면 `notes/{git-user}/{slug}/` 식 하위 폴더 사용
|
||
5. **새 폴더/파일 작성 후엔 git add 권장** — syncthing 도 자동 동기화 (`notes/` 는 `.stignore-shared` 에 없음)
|
||
|
||
### 어디에 안 넣는가
|
||
|
||
- `_local/`, `_backup/`, `_pipeline/` — syncthing ignore. 머신 로컬용
|
||
- `docs/` — 공식 개발 문서. 작업 기록 아님
|
||
- 프로젝트 루트 직접 (`./STATUS.md`, `./PLAN.MD` 등) — 이미 기존에 있는 것 외에 새로 만들지 말 것
|
||
|
||
---
|
||
|
||
## 컨벤션이 적용되는 시나리오
|
||
|
||
| 사용자 요청 | 저장 위치 |
|
||
|---|---|
|
||
| "이 코드 분석해서 md 로 정리해줘" | `notes/{git-user}/{date}-{topic}.md` |
|
||
| "보안 감사 리포트 만들어줘" | `notes/{git-user}/{date}-security-audit.md` |
|
||
| "리팩토링 플랜 md 로 뽑아줘" | `notes/{git-user}/{date}-refactor-plan.md` |
|
||
| "회의 노트 정리해줘" | `notes/{git-user}/{date}-meeting-notes.md` |
|
||
| "마이그레이션 가이드 작성" | `notes/{git-user}/{date}-migration-guide.md` |
|
||
|
||
---
|
||
|
||
## Claude 사용 시 추가 주의사항
|
||
|
||
- **이 컨벤션은 사용자 명시 요청 없이도 자동 적용** — 사용자가 "md 만들어줘" 라고만 해도 위 경로에 저장
|
||
- **현재 git user 확인이 필요하면** `git config user.name` 실행
|
||
- **사용자 폴더가 처음이면** 만들면서 `.gitkeep` 정도만 두지 말고 바로 첫 노트 작성
|
||
|
||
---
|
||
|
||
## 🎨 공통 디자인 시스템 / CSS 참조 규칙 (★★★ 무조건 적용)
|
||
|
||
UI 작업(컴포넌트 작성, HTML 목업, 새 페이지/화면, 디자인 리빌딩, 스타일 수정 등)을 할 때는 **반드시** 아래 공통 CSS 파일들을 먼저 읽고 그 안의 토큰/클래스 컨벤션을 100% 따라야 합니다. 절대 새 색상/간격/라운드/그림자 값을 즉흥으로 만들지 말 것.
|
||
|
||
### "v5" 의 정체 (★ 헷갈리지 말 것)
|
||
|
||
**INVION v5 = 디자인 시안 5번째 = 최종 채택본. 현재 컨셉은 "Solid + Glow" (2026-04-21 개정)**
|
||
|
||
- 디자이너가 v1~v5 까지 5번 시안을 만들고 그 중 **v5 가 확정**되어 React 로 포팅됨
|
||
- 시안 원본 HTML (참고용, 여기엔 아직 glassmorphism 이 남아있음):
|
||
- `frontend/invion-layout-v5.html` (973줄, 풀 레이아웃 셸)
|
||
- `frontend/invion-preview-v5.html` (1049줄, 미리보기/모션 데모)
|
||
- 폐기된 시안: `frontend/invion-preview-v1~v4.html` (참고만, 적용 금지)
|
||
- **현재 적용 컨셉 (2026-04-21 개정)**:
|
||
- **로그인 페이지**: 우주(별/성운/별똥별/입자) 배경 + 글래스 카드 **유지**
|
||
- **메인 화면 이후 전부**: **반투명/blur/cosmic 배경 폐기**. 불투명 솔리드 카드 + primary-color 글로우 + 보라(`#6c5ce7`)/시안(`#00cec9`)/핑크(`#fd79a8`) 액센트
|
||
- v5 토큰이 옮겨진 곳: `frontend/styles/v5-layout.css`, `frontend/app/(auth)/login/login.css`
|
||
|
||
⚠️ **POP 디자이너의 "v5 그리드 시스템"** (`PopRenderer.tsx`, `pop-layout.ts` 등) 은 **별개 의미** — POP 화면 데이터 포맷의 5번째 버전. UI 디자인 v5 와 무관. 혼동 금지.
|
||
|
||
### 항상 먼저 읽어야 하는 파일
|
||
|
||
| 파일 | 역할 |
|
||
|---|---|
|
||
| `frontend/invion-layout-v5.html` | **v5 디자인 원본 (시안)** — 모든 v5 토큰/클래스의 진실의 원천. 포팅된 css 와 다르면 이게 정답 |
|
||
| `frontend/styles/v5-layout.css` | **INVION v5 React 포팅 메인** — `--v5-*` CSS 변수, 헤더/사이드바/탭/모달 등 모든 v5- 컴포넌트 클래스 정의. UI 작업 전 무조건 먼저 읽기 |
|
||
| `frontend/app/globals.css` | shadcn/Tailwind 토큰 (`--background`, `--primary`, `--foreground` 등 HSL), 다크모드 변수, 전역 reset |
|
||
| `frontend/app/(auth)/login/login.css` | **로그인 전용** 코스믹 배경(별/성운/입자) + 글래스 카드. 이 컨셉은 **로그인에만** 적용 — 메인 화면에 옮기지 말 것 |
|
||
| `frontend/components/layout/AppLayout.tsx` | v5 클래스가 실제로 어떻게 조립되는지 — 헤더/사이드바/탭/플라이아웃 사용 예 |
|
||
|
||
### 필수 준수 사항
|
||
|
||
1. **디자인 토큰은 무조건 변수 사용** — `--v5-primary`, `--v5-cyan`, `--v5-surface-solid`, `--v5-glow-sm/md/lg`, `hsl(var(--primary))` 등. 즉흥 hex/rgb 금지
|
||
2. **클래스명은 v5- 접두사 컨벤션 따르기** — 새 컴포넌트도 `.v5-card`, `.v5-btn`, `.v5-bdg` 처럼 같은 네이밍. shadcn 컴포넌트 사용 시 그대로 사용
|
||
3. **반투명/블러 금지 (★2026-04-21 신규)** — 메인 화면 이후 전 영역에서 `backdrop-filter: blur(...)`, `var(--v5-glass)`, `var(--v5-glass-strong)` 사용 **금지**. 카드/모달/사이드바/헤더 배경은 `var(--v5-surface-solid)` (라이트 `#ffffff` / 다크 `#11102a`) 를 쓰고, 테두리는 `border-border` 또는 `var(--v5-border)`. 예외: `frontend/app/(auth)/login/` 과 `frontend/styles/builder-ide.css` 는 별도 스코프라 기존 유지
|
||
4. **글로우는 유지** — 그림자는 검은 drop-shadow 대신 `var(--v5-glow-sm/md/lg)` (primary-color glow) 사용. 모달/강조 카드에 liberal 하게 사용 가능
|
||
5. **다크/라이트 모드는 둘 다 동작** — `.dark` 변형 잊지 말 것. 다크에서 `--v5-surface-solid` 는 `#11102a`, 라이트는 `#ffffff`. 별/입자/별똥별/성운은 **로그인에만** 존재, 메인은 평범한 단색 배경
|
||
6. **컴팩트 폰트 사이즈 유지** — v5 는 0.55~0.85rem 의 컴팩트 UI. 새로 만들 때도 같은 스케일 따를 것
|
||
7. **새 UI 패턴은 v5-layout.css 에 합치는 것을 기본 방향으로** — 일회성 inline `<style>` 보다 공통화 우선
|
||
|
||
### 작업 순서 (UI 작업 시 반드시)
|
||
|
||
```
|
||
1) frontend/styles/v5-layout.css 읽기
|
||
2) 필요하면 globals.css, login.css 도 함께 읽기
|
||
3) 기존 v5- 클래스 중 재사용 가능한 것 찾기
|
||
4) 모자라는 부분만 같은 토큰/네이밍으로 추가
|
||
5) 작업 결과를 사용자에게 보여줄 때 "어떤 v5 토큰/클래스를 따랐는지" 명시
|
||
```
|
||
|
||
### 예외
|
||
|
||
- 단발성 디버그/실험 페이지(`debug-*`, `test-*`)는 임시 스타일 허용
|
||
- `notes/` 안의 1회성 HTML 목업은 v5 토큰을 inline 으로 가져와 standalone 이어도 됨 (단 토큰 값은 반드시 v5-layout.css 와 동일해야 함)
|
||
|
||
이 규칙은 사용자가 명시 요청하지 않아도 모든 UI 작업에 자동 적용됩니다.
|
||
|
||
---
|
||
|
||
## 🔧 백엔드 코딩 규칙 — 스타일 (★★★ 절대 규칙)
|
||
|
||
### 아키텍처: 3레이어 (Mapper Interface 금지)
|
||
|
||
```
|
||
Controller (@RestController)
|
||
↓
|
||
Service (extends BaseService) — sqlSession 직접 사용
|
||
↓
|
||
XML (resources/mapper/[module].xml) — 소문자 파일명
|
||
```
|
||
|
||
- **@Mapper 인터페이스 생성 금지** — `sqlSession.selectList("namespace.queryId", params)` 직접 호출
|
||
- Service는 반드시 `BaseService` 상속, `@Slf4j`, `@Autowired CommonService`
|
||
|
||
### 데이터: Map<String, Object> — DTO/엔티티 클래스 금지
|
||
|
||
로우코드 ERP: 테이블/컬럼이 런타임에 결정됨. DTO 클래스 사전 생성 불가.
|
||
- 모든 파라미터: `Map<String, Object>`
|
||
- 모든 응답: `Map<String, Object>` 또는 `List<Map<String, Object>>`
|
||
- `ApiResponse`만 유일한 DTO
|
||
|
||
### 네이밍 컨벤션 (절대 규칙)
|
||
|
||
| 위치 | 컨벤션 | 예시 |
|
||
|---|---|---|
|
||
| **Java 코드** | camelCase | `getOrderList()`, `String companyCode` |
|
||
| **Map 키 (params.put, row.get)** | snake_case | `params.put("company_code", ...)`, `row.get("table_name")` |
|
||
| **#{파라미터}** | snake_case | `#{company_code}`, `#{table_name}` |
|
||
| **SQL (키워드/테이블/컬럼)** | UPPER_SNAKE | `SELECT COMPANY_CODE FROM TEMPLATES` |
|
||
| **SELECT 쉼표** | 앞에 | `, COLUMN_NAME` |
|
||
| **XML 파일명** | 소문자, Mapper 안 붙임 | `meta.xml`, `template.xml` |
|
||
| **XML namespace** | 파일명과 동일 | `namespace="meta"` |
|
||
| **OGNL test** | 바깥 작은따옴표 | `test='company_code != "*"'` |
|
||
|
||
### 메서드명 패턴
|
||
|
||
| 조작 | 패턴 | 예시 |
|
||
|---|---|---|
|
||
| 목록 | `get[Module]List` | `getTemplateList` |
|
||
| 카운트 | `get[Module]ListCnt` | `getTemplateListCnt` |
|
||
| 단건 | `get[Module]Info` | `getTemplateInfo` |
|
||
| 등록 | `insert[Module]` | `insertTemplate` |
|
||
| 수정 | `update[Module]` | `updateTemplate` |
|
||
| 삭제 | `delete[Module]` | `deleteTemplate` |
|
||
|
||
**List API는 반드시 Count 쿼리 동반** (`getXxxList` + `getXxxListCnt` = 한 세트)
|
||
|
||
### common 레이어 필수
|
||
|
||
```xml
|
||
<include refid="common.companyCodeFilter"/>
|
||
<include refid="common.dynamicOrderBy"/>
|
||
<include refid="common.pagination"/>
|
||
```
|
||
|
||
---
|
||
|
||
## 🌐 프론트엔드 데이터 타입 규칙 (★★★ 절대 규칙)
|
||
|
||
### Record<string, any> — 별도 인터페이스 정의 금지
|
||
|
||
백엔드가 `Map<String, Object>`이므로 프론트도 `Record<string, any>`.
|
||
|
||
```typescript
|
||
// ❌ 금지 — 불필요한 인터페이스 정의
|
||
interface TableInfo {
|
||
table_name: string;
|
||
column_count: number;
|
||
}
|
||
|
||
// ✅ 올바름
|
||
const tables: Record<string, any>[] = await getTableList();
|
||
```
|
||
|
||
### 유일한 예외: invyone-component.ts 규격 타입
|
||
|
||
`frontend/types/invyone-component.ts`에 확정된 타입만 예외:
|
||
- `FieldConfig`, `FieldType`, `FieldRef`
|
||
- `Component`, `ComponentType`, `Position`
|
||
- `DataPort`, `Connection`
|
||
- `Template`, `ViewConfig`
|
||
- 각 `ComponentTypeConfig` (TableConfig, FormConfig 등)
|
||
|
||
이것들은 시스템의 핵심 계약이므로 타입 유지. **그 외 전부 `Record<string, any>`.**
|
||
|
||
---
|
||
|
||
## 📐 INVYONE 로우코드 핵심 구조 (★ 모든 세션이 알아야 할 것)
|
||
|
||
### VEX → INVYONE 관계
|
||
|
||
INVYONE은 이미 운영 중인 로우코드 플랫폼 VEX(Node.js)의 2세대 리뉴얼(Java/Spring).
|
||
**핵심 원칙: 더 단순한 구조로 재설계하되, VEX 운영 기능은 전부 계승. 단순화 ≠ 기능 삭제.**
|
||
|
||
### FieldConfig 단일 규격
|
||
|
||
VEX의 ColumnConfig(354줄) + FilterConfig + FormField → **FieldConfig 하나(~30줄)로 통합**.
|
||
테이블/폼/검색 전부 같은 FieldConfig를 공유하며, 렌더러가 type을 보고 각자 다르게 렌더.
|
||
|
||
### 대시보드 = 메뉴
|
||
|
||
사이드바 메뉴 항목 = 대시보드. 별도 대시보드 UI가 아님.
|
||
`대시보드 생성("수주관리") → 사이드바 메뉴에 자동 등록 → 템플릿 카드 배치 → 화면 완성`
|
||
|
||
### 구현 순서
|
||
|
||
1. DB 메타 읽기 → FieldConfig 변환
|
||
2. 규격 기반 컴포넌트 (FcTable/FcForm/FcSearch)
|
||
3. 개발자 빌더 (수동 템플릿 구성)
|
||
4. 대시보드(=메뉴) 시스템
|
||
5. 제어 모드 (비즈니스 룰)
|
||
6. 자동생성/프리셋 (맨 마지막)
|
||
|
||
### 설계 문서 위치
|
||
|
||
| 문서 | 위치 |
|
||
|---|---|
|
||
| 컴포넌트 규격 v1.0 | `notes/gbpark/2026-04-08-invyone-component-spec.md` |
|
||
| 아키텍처 결정 | `notes/gbpark/2026-04-09-invyone-architecture.md` |
|
||
| 로우코드 플랫폼 SPEC | `notes/gbpark/2026-04-08-lowcode-platform-spec.md` |
|
||
| Phase 1~5 구현 설계 | `notes/gbpark/2026-04-10-phase{1~5}-*.md` |
|
||
| mockup (시각적 진실의 원천) | `notes/gbpark/2026-04-08-invyone-mockup/` |
|
||
| FieldConfig TS 타입 | `frontend/types/invyone-component.ts` |
|
||
|
||
---
|
||
|
||
## 🏢 멀티테넌시 · 서브도메인 라우팅 (★★★ 아키텍처 핵심)
|
||
|
||
INVYONE 은 **DB-per-tenant** 구조. 회사 하나 = 전용 PostgreSQL DB 하나 = 전용 서브도메인 하나.
|
||
`qnc.invyone.com` 접속 → `SubdomainResolverFilter` 가 Host 헤더 파싱 → `TenantRoutingDataSource` 가 `qnc_invyone` DB 로 자동 라우팅.
|
||
|
||
**전체 설명·플로우·배포 가이드는 반드시 읽을 것:**
|
||
- **`docs/MULTI_TENANCY_ARCHITECTURE.md`** ← 이거 하나만 읽으면 끝 (환경별 도메인 전략, CORS 패턴, 배포 체크리스트 포함)
|
||
|
||
### 절대 건드리면 안 되는 3가지
|
||
|
||
1. **테넌트 도메인에서 `NEXT_PUBLIC_API_URL=/api` 같은 Next rewrite 쓰지 말 것.**
|
||
Rewrite 가 Host 헤더를 변조해 `*.invyone.com` 서브도메인 파싱이 실패함.
|
||
→ `frontend/lib/api/client.ts` 의 "`.invyone.com` 이면 직접 `:8083/api`" 분기가 NEXT_PUBLIC_API_URL 체크보다 **앞에** 와야 함.
|
||
|
||
2. **CORS 는 `setAllowedOriginPatterns`** (`setAllowedOrigins` 아님). 와일드카드 매칭 필요.
|
||
**YAML 의 `[*]` 는 반드시 따옴표로 감쌀 것** (sequence 로 해석됨).
|
||
`CORS_ALLOWED_ORIGINS` 는 `.env` 에 있고 `.gitignore` 대상 → **서버마다 수동 세팅**.
|
||
|
||
3. **회사별 Hikari 풀은 반드시 `minIdle=0`.**
|
||
회사 N개 × minIdle=2 하면 Postgres `max_connections` 금방 초과.
|
||
`TenantDataSourceFactory.createTenant(...)` 만 사용할 것.
|
||
|
||
### 회사 생성 = API 호출 한 번
|
||
|
||
```
|
||
POST /api/admin/provisioning/companies
|
||
→ 6단계 자동 실행 (DB 생성 → pg_dump 스키마 복제 → 템플릿 데이터 → 관리자 → 메타 등록)
|
||
→ 13~15초 내 완료, 바로 서브도메인 접속 가능
|
||
```
|
||
필수 필드 4개만: `company_code`, `company_name`, `subdomain`, `db_prefix`.
|
||
나머지(사업자번호·대표자·이메일 등) 전부 optional.
|
||
|
||
### 환경별 도메인 — 요약
|
||
|
||
| 환경 | 접속 | 추가 설정 |
|
||
|---|---|---|
|
||
| **운영** | 실 `*.invyone.com` (+ 메인 `solution.invyone.com`) | **세팅 완료 (2026-04-24)** — Porkbun 와일드카드 DNS + Traefik v2.11 + Let's Encrypt DNS-01 와일드카드 TLS |
|
||
| **로컬 (직접 `docker up`)** | `*.localhost` 자동 | 0 (RFC 6761, Chrome/Firefox 기본 지원) |
|
||
| **원격 공유 개발서버** | `nip.io` or `hosts` 편집 | 경우 따라 |
|
||
|
||
### 새 환경 붙일 때 체크리스트
|
||
|
||
- [ ] `.env` 의 `CORS_ALLOWED_ORIGINS` 에 해당 도메인 패턴 포함 (`http://*.invyone.com:[*]` 등)
|
||
- [ ] DB 마이그레이션 `RUN_079/080/081.md` 운영 DB 에서 1회 실행
|
||
- [ ] `docker compose build --no-cache backend-spring` (`postgresql16-client` 포함)
|
||
- [ ] 프로덕션은 `tenant.provisioning.require-super-admin=true`
|
||
|
||
### 관련 코드 맵
|
||
|
||
```
|
||
backend-spring/src/main/java/com/erp/
|
||
├── tenant/ # 라우팅 (Filter / Holder / CompanyResolver / RoutingDataSource / Factory / DataSourceConfig / TenantController)
|
||
└── provisioning/ # 회사 생성 (Service / Registry / DatabaseCreator / SchemaCopier / DataCopier / AdminAccountCreator / Controller / StatsService)
|
||
|
||
backend-spring/src/main/resources/mapper/
|
||
├── tenant.xml # resolveDbNameBySubdomain
|
||
└── provisioning.xml # exists / insertCompanyWithTenant / listCompaniesForUi / updateDbStatus
|
||
|
||
frontend/
|
||
├── lib/api/provisioning.ts
|
||
├── lib/tenant/subdomain.ts # 호스트 파싱 + 예약어 제외
|
||
├── components/TenantGuard.tsx # 미등록 서브도메인 차단 (layout wrap)
|
||
├── components/admin/provisioning/ # 메인 화면 (accordion / stats strip / status dot / sparkline)
|
||
│ └── wizard/ # 4-step 마법사 (force_password_change 옵션 포함)
|
||
├── app/tenant-not-found/page.tsx # v5 solid+glow 에러 페이지
|
||
└── app/(main)/admin/sysMng/subdomainList/ # 회사 프로비저닝 페이지 엔트리
|
||
|
||
db/migrations/RUN_079/080/081_MIGRATION.md
|
||
notes/gbpark/2026-04-24-traefik-wildcard/ # 운영 Traefik 설정 산출물 (2026-04-24)
|
||
```
|