- 포트 배치: 5001로 통합 (5002 제거) - 배포 절차: 단일 Web 앱 빌드로 단순화 - 서비스: taxbaik만 관리 (taxbaik-admin 제거) - Nginx: /taxbaik 블록 하나로 통합 - 파일 구조: Web/Components/Admin으로 명시 - 인증: JWT + localStorage 패턴 문서화 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,42 +11,60 @@
|
||||
|
||||
## 2. 아키텍처
|
||||
|
||||
### 2.1 프로젝트 구조
|
||||
### 2.1 프로젝트 구조 (통합)
|
||||
|
||||
**단일 앱 구조** (소규모 프로젝트 최적화):
|
||||
|
||||
```
|
||||
TaxBaik.Domain 클래스 라이브러리 (엔티티, 인터페이스, enum — 외부 의존성 없음)
|
||||
TaxBaik.Domain 클래스 라이브러리 (엔티티, 인터페이스, enum)
|
||||
TaxBaik.Infrastructure 클래스 라이브러리 (Dapper repository, DB 마이그레이션)
|
||||
TaxBaik.Application 클래스 라이브러리 (서비스, DTO, 비즈니스 로직)
|
||||
TaxBaik.Web ASP.NET Core 앱 (Razor Pages SSR, port 5001)
|
||||
TaxBaik.Admin ASP.NET Core 앱 (Blazor Server, port 5002)
|
||||
TaxBaik.Web ASP.NET Core 앱 (포트 5001)
|
||||
├─ Pages/ Razor Pages (공개 홈페이지, 블로그, 문의폼)
|
||||
├─ Components/
|
||||
│ ├─ (Web pages)
|
||||
│ └─ Admin/ Blazor Server (관리자 백오피스)
|
||||
│ ├─ Pages/
|
||||
│ ├─ Layout/
|
||||
│ └─ App.razor
|
||||
└─ Services/ 인증, 블로그, 문의 등
|
||||
```
|
||||
|
||||
**경로:**
|
||||
- 홈페이지: `/taxbaik` (Razor Pages)
|
||||
- 관리자: `/taxbaik/admin` (Blazor)
|
||||
- 로그인: `/taxbaik/admin/login`
|
||||
|
||||
### 2.2 계층 책임
|
||||
- **Domain**: 비즈니스 규칙, 엔티티 정의
|
||||
- **Infrastructure**: DB 접근, Dapper 구현체, 마이그레이션 실행
|
||||
- **Application**: 서비스, DTO 매핑, 비즈니스 워크플로우
|
||||
- **Web**: 공개 사이트 (SEO 최적화, Razor Pages SSR)
|
||||
- **Admin**: 관리자 백오피스 (실시간 UI, Blazor Server)
|
||||
- **Web (Pages/)**: 공개 홈페이지 (SEO 최적화, Razor Pages SSR)
|
||||
- **Web (Components/Admin)**: 관리자 백오피스 (실시간 UI, Blazor Server)
|
||||
- **Web (Services/)**: 인증(JWT), 블로그, 문의 관리 등
|
||||
|
||||
### 2.3 기술 결정 이유
|
||||
|
||||
**왜 Razor Pages (Web)인가?**
|
||||
- Razor Pages는 서버에서 HTML을 렌더링 → Google, Naver가 즉시 크롤 가능
|
||||
- Blazor Server는 초기 응답이 shell HTML → SEO 불리 (블로그는 구글 검색이 핵심)
|
||||
**왜 Razor Pages (공개 사이트)인가?**
|
||||
- 서버에서 HTML을 렌더링 → Google, Naver가 즉시 크롤 가능
|
||||
- Blazor는 초기 응답이 shell HTML → SEO 불리 (블로그는 검색 유입이 핵심)
|
||||
|
||||
**왜 Blazor Server (Admin)인가?**
|
||||
**왜 Blazor Server (관리자)인가?**
|
||||
- 관리자는 SEO 불필요 → WebSocket으로 실시간 UI 업데이트 가능
|
||||
- 기존 QuantEngine 패턴과 일치
|
||||
- 복잡한 관리 UI를 쉽게 구현
|
||||
|
||||
**왜 단일 앱 (통합 Web)인가?**
|
||||
- 소규모 프로젝트 → 분리의 이점 < 개발 복잡도
|
||||
- **개발**: 터미널 1개, 포트 1개 (5001)
|
||||
- **배포**: 앱 1개, DB 마이그레이션 1회
|
||||
- **유지보수**: 모든 비즈니스 로직 한 곳 (Application)
|
||||
- **장점**: 기존 분리 구조의 모든 기능 + 간단한 개발 경험
|
||||
|
||||
**왜 Dapper인가?**
|
||||
- QuantEngine에서 이미 사용 중 (팀 지식)
|
||||
- 팀 기존 지식 (QuantEngine에서 사용)
|
||||
- 복잡한 조인, 페이징, 성능 제어 용이
|
||||
- EF Core 대비 SQL 완전 제어 가능
|
||||
|
||||
**왜 두 개의 ASP.NET Core 앱인가?**
|
||||
- Web: 정적 콘텐츠 + Razor Pages
|
||||
- Admin: 동적 Blazor + WebSocket
|
||||
- 미들웨어 파이프라인 다름 → 독립 배포로 한쪽 문제가 다른 쪽에 영향 안 함
|
||||
|
||||
---
|
||||
|
||||
## 3. 로컬 개발 환경 설정
|
||||
@@ -88,24 +106,27 @@ psql -h localhost -U taxbaik -d taxbaikdb -c "\dt"
|
||||
dotnet run -p TaxBaik.Web
|
||||
```
|
||||
|
||||
#### 단계 3: 개발 워크플로우
|
||||
#### 단계 3: 개발 워크플로우 (단일 앱 통합)
|
||||
|
||||
```bash
|
||||
# 터미널 1: SSH 터널 유지
|
||||
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
|
||||
|
||||
# 터미널 2: Web 사이트 (Razor Pages)
|
||||
# 터미널 2: 통합 Web 앱 (Razor Pages + Blazor)
|
||||
cd TaxBaik.Web
|
||||
dotnet run
|
||||
# 접속: http://localhost:5001/taxbaik
|
||||
|
||||
# 터미널 3: Admin 앱 (Blazor)
|
||||
cd TaxBaik.Admin
|
||||
dotnet run
|
||||
# 접속: https://localhost:5002
|
||||
# 로그인: admin / admin123
|
||||
# 접속:
|
||||
# - 홈페이지: http://localhost:5001/taxbaik
|
||||
# - 관리자: http://localhost:5001/taxbaik/admin/login
|
||||
# - 로그인: admin / admin123
|
||||
```
|
||||
|
||||
**장점**:
|
||||
- ✅ 한 개의 포트 (5001)
|
||||
- ✅ 한 개의 터미널에서 실행
|
||||
- ✅ 한 번의 DB 마이그레이션
|
||||
- ✅ 모든 기능 유지 (JWT 인증, Blazor UI, Razor Pages SEO)
|
||||
|
||||
### 3.2 appsettings.json (로컬)
|
||||
|
||||
```json
|
||||
@@ -173,8 +194,7 @@ ssh kjh2064@178.104.200.7
|
||||
3000 : Gitea Web (localhost만, proxy via /를 통해)
|
||||
2222 : Gitea SSH (공개)
|
||||
5000 : QuantEngine Blazor (localhost, proxy via /quant/)
|
||||
5001 : TaxBaik.Web (localhost, proxy via /taxbaik)
|
||||
5002 : TaxBaik.Admin (localhost, proxy via /taxbaik/admin)
|
||||
5001 : TaxBaik.Web (공개 사이트 + 관리자 통합, localhost, proxy via /taxbaik)
|
||||
5432 : PostgreSQL (localhost 바인드)
|
||||
```
|
||||
|
||||
@@ -182,11 +202,10 @@ ssh kjh2064@178.104.200.7
|
||||
|
||||
**핵심 전략**: .NET Core shadow copy로 배포 중 무중단 실행
|
||||
|
||||
1. **로컬 빌드**:
|
||||
1. **로컬 빌드** (단일 앱 통합):
|
||||
```bash
|
||||
dotnet clean TaxBaik.sln
|
||||
dotnet publish TaxBaik.Web -c Release -o ./publish/web
|
||||
dotnet publish TaxBaik.Admin -c Release -o ./publish/admin
|
||||
dotnet publish TaxBaik.Web -c Release -o ./publish
|
||||
```
|
||||
|
||||
2. **CI/CD 배포** (Gitea Actions):
|
||||
@@ -213,7 +232,6 @@ ssh kjh2064@178.104.200.7
|
||||
```bash
|
||||
# 최근 5개 배포만 유지
|
||||
ls -dt ~/deployments/taxbaik_* | tail -n +6 | xargs -r rm -rf
|
||||
ls -dt ~/deployments/taxbaik_admin_* | tail -n +6 | xargs -r rm -rf
|
||||
```
|
||||
|
||||
**systemd 서비스 graceful shutdown 설정**:
|
||||
@@ -225,19 +243,16 @@ KillMode=mixed # SIGTERM → 30초 대기 → SIGKILL
|
||||
|
||||
### 3.4 서비스 파일 위치
|
||||
```
|
||||
/etc/systemd/system/taxbaik.service ← Web
|
||||
/etc/systemd/system/taxbaik-admin.service ← Admin
|
||||
/etc/systemd/system/taxbaik.service ← 통합 Web 앱 (공개 사이트 + 관리자)
|
||||
```
|
||||
|
||||
### 5.5 배포 디렉토리 구조 (서버)
|
||||
```
|
||||
/home/kjh2064/
|
||||
├── taxbaik_active → ./deployments/taxbaik_20260626_150000/
|
||||
├── taxbaik_admin_active → ./deployments/taxbaik_admin_20260626_150000/
|
||||
└── deployments/
|
||||
├── taxbaik_20260626_150000/ (Web publish 출력)
|
||||
├── taxbaik_20260626_150000/ (통합 Web publish 출력)
|
||||
├── taxbaik_20260626_140000/ (이전 버전)
|
||||
├── taxbaik_admin_20260626_150000/ (Admin publish 출력)
|
||||
└── ...
|
||||
```
|
||||
|
||||
@@ -253,27 +268,17 @@ KillMode=mixed # SIGTERM → 30초 대기 → SIGKILL
|
||||
location /taxbaik {
|
||||
proxy_pass http://127.0.0.1:5001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection keep-alive;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
|
||||
location /taxbaik/admin {
|
||||
proxy_pass http://127.0.0.1:5002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
```
|
||||
|
||||
**더 구체적인 경로가 우선**: `/taxbaik` 블록이 `/` Gitea 블록보다 먼저 매칭됨.
|
||||
**참고**: 단일 `/taxbaik` 블록이 공개 사이트와 관리자 (Blazor WebSocket)를 모두 처리합니다.
|
||||
|
||||
---
|
||||
|
||||
@@ -341,7 +346,7 @@ public async Task<BlogPost?> GetBySlugAsync(string slug, CancellationToken ct)
|
||||
- 비동기 메서드: **Async** 접미사 (GetBySlugAsync)
|
||||
- 비공개 메서드: **Async 접미사 생략 가능**
|
||||
|
||||
### 6.2 파일 구조
|
||||
### 6.2 파일 구조 (통합 Web 앱)
|
||||
```
|
||||
Domain/
|
||||
Entities/BlogPost.cs
|
||||
@@ -359,12 +364,17 @@ Application/
|
||||
|
||||
Web/
|
||||
Pages/Blog/Index.cshtml
|
||||
Pages/Blog/Index.cshtml.cs ← PageModel
|
||||
Pages/Blog/Index.cshtml.cs ← PageModel (공개 사이트)
|
||||
Components/
|
||||
Admin/
|
||||
Pages/Blog/BlogList.razor ← Blazor 관리자 페이지
|
||||
Layout/MainLayout.razor
|
||||
App.razor
|
||||
Services/
|
||||
AuthService.cs ← JWT 인증
|
||||
CustomAuthenticationStateProvider.cs
|
||||
LocalStorageService.cs
|
||||
wwwroot/css/site.css
|
||||
|
||||
Admin/
|
||||
Components/Pages/Blog/BlogList.razor
|
||||
Services/AdminAuthStateProvider.cs
|
||||
```
|
||||
|
||||
### 6.3 모든 UI는 한국어
|
||||
@@ -437,41 +447,60 @@ catch
|
||||
|
||||
---
|
||||
|
||||
## 8. Blazor Admin 패턴
|
||||
## 8. Blazor Admin 패턴 (통합 Web 앱)
|
||||
|
||||
### 8.1 PathBase
|
||||
Admin은 `/taxbaik/admin/` 경로에서 실행:
|
||||
전체 앱은 `/taxbaik/` 경로에서 실행:
|
||||
|
||||
```csharp
|
||||
// Program.cs
|
||||
app.UsePathBase("/taxbaik/admin");
|
||||
app.UsePathBase("/taxbaik");
|
||||
```
|
||||
|
||||
`@page` 지시문의 경로는 이 기본값에 상대적. 예:
|
||||
```razor
|
||||
@page "/login" ← 실제 URL: /taxbaik/admin/login
|
||||
@page "/blog" ← 실제 URL: /taxbaik/admin/blog
|
||||
@page "/admin/login" ← 실제 URL: /taxbaik/admin/login
|
||||
@page "/admin/blog" ← 실제 URL: /taxbaik/admin/blog
|
||||
@page "/blog" ← 실제 URL: /taxbaik/blog (Razor Pages)
|
||||
```
|
||||
|
||||
### 8.2 Cookie 인증
|
||||
### 8.2 JWT 인증 (LocalStorage + Bearer Token)
|
||||
```csharp
|
||||
// Program.cs
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(opts => opts.LoginPath = "/login");
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
builder.Services.AddScoped<AuthService>();
|
||||
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider>(
|
||||
sp => sp.GetRequiredService<CustomAuthenticationStateProvider>());
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
builder.Services.AddAuthorizationCore();
|
||||
```
|
||||
|
||||
### 8.3 모든 페이지에 [Authorize] 추가
|
||||
토큰은 localStorage에 저장되며, `CustomAuthenticationStateProvider`가 자동으로 복원:
|
||||
|
||||
```csharp
|
||||
// CustomAuthenticationStateProvider.cs
|
||||
public async Task LoginAsync(string token)
|
||||
{
|
||||
await _localStorage.SetItemAsStringAsync("authToken", token);
|
||||
StateHasChanged(); // Blazor 상태 갱신
|
||||
}
|
||||
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
await _localStorage.RemoveItemAsync("authToken");
|
||||
StateHasChanged();
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 모든 Admin 페이지에 [Authorize] 추가
|
||||
```razor
|
||||
@* _Imports.razor *@
|
||||
@* Components/Admin/_Imports.razor *@
|
||||
@attribute [Authorize]
|
||||
```
|
||||
|
||||
Login 페이지만 [AllowAnonymous]:
|
||||
Admin 로그인 페이지만 [AllowAnonymous]:
|
||||
```razor
|
||||
@page "/login"
|
||||
@page "/admin/login"
|
||||
@attribute [AllowAnonymous]
|
||||
```
|
||||
|
||||
@@ -606,16 +635,15 @@ ssh kjh2064@178.104.200.7
|
||||
# DB 확인
|
||||
psql -U kjh2064 -d taxbaikdb -c "\dt"
|
||||
|
||||
# 서비스 상태
|
||||
systemctl status taxbaik taxbaik-admin
|
||||
# 서비스 상태 (통합 Web 앱만)
|
||||
systemctl status taxbaik
|
||||
|
||||
# 엔드포인트 확인
|
||||
curl http://127.0.0.1:5001/health
|
||||
curl http://127.0.0.1:5002/health
|
||||
curl http://127.0.0.1:5001/taxbaik
|
||||
|
||||
# Nginx 라우팅 확인
|
||||
curl http://127.0.0.1/taxbaik
|
||||
curl http://127.0.0.1/taxbaik/admin
|
||||
curl http://127.0.0.1/taxbaik/admin/login
|
||||
```
|
||||
|
||||
### E2E 테스트
|
||||
|
||||
Reference in New Issue
Block a user