1283 lines
87 KiB
PHP
1283 lines
87 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '백엔드 개발')
|
|
|
|
@push('styles')
|
|
<style>
|
|
.academy-img-hover {
|
|
transition: box-shadow 0.2s ease;
|
|
}
|
|
.academy-img-hover:hover {
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
.academy-img-wrap {
|
|
overflow: hidden;
|
|
border-radius: 0.75rem;
|
|
}
|
|
|
|
#hover-preview {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 45;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
backdrop-filter: blur(3px);
|
|
pointer-events: none;
|
|
}
|
|
#hover-preview.is-active {
|
|
display: flex;
|
|
pointer-events: none;
|
|
}
|
|
#hover-preview img {
|
|
max-height: 80vh;
|
|
max-width: 85vw;
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
|
|
opacity: 0;
|
|
transform: scale(0.3);
|
|
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
}
|
|
#hover-preview.is-active img {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
#hover-preview .hover-caption {
|
|
position: absolute;
|
|
bottom: 2rem;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
color: rgba(255,255,255,0.85);
|
|
font-size: 0.8rem;
|
|
background: rgba(0,0,0,0.5);
|
|
padding: 0.4rem 1rem;
|
|
border-radius: 2rem;
|
|
white-space: nowrap;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease 0.15s;
|
|
}
|
|
#hover-preview.is-active .hover-caption {
|
|
opacity: 1;
|
|
}
|
|
|
|
#lightbox {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 50;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
#lightbox.is-open {
|
|
display: flex;
|
|
}
|
|
#lightbox img {
|
|
max-height: 90vh;
|
|
max-width: 90vw;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.help-balloon-trigger {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
background: #fff7ed;
|
|
color: #c2410c;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
border: 1.5px solid #fdba74;
|
|
vertical-align: middle;
|
|
margin-left: 4px;
|
|
transition: all 0.15s ease;
|
|
position: relative;
|
|
user-select: none;
|
|
}
|
|
.help-balloon-trigger:hover {
|
|
background: #c2410c;
|
|
color: white;
|
|
border-color: #9a3412;
|
|
}
|
|
.help-balloon {
|
|
display: none;
|
|
position: absolute;
|
|
bottom: calc(100% + 10px);
|
|
left: 50%;
|
|
transform: translateX(-50%) scale(0.95);
|
|
background: #1e293b;
|
|
color: #f1f5f9;
|
|
font-size: 12px;
|
|
font-weight: 400;
|
|
line-height: 1.6;
|
|
padding: 10px 14px;
|
|
border-radius: 10px;
|
|
max-width: 300px;
|
|
min-width: 200px;
|
|
width: max-content;
|
|
white-space: normal;
|
|
word-break: keep-all;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
|
z-index: 40;
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
pointer-events: none;
|
|
}
|
|
.help-balloon::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
border: 7px solid transparent;
|
|
border-top-color: #1e293b;
|
|
}
|
|
.help-balloon-trigger.is-active .help-balloon {
|
|
display: block;
|
|
opacity: 1;
|
|
transform: translateX(-50%) scale(1);
|
|
pointer-events: auto;
|
|
}
|
|
.help-balloon-trigger.is-active .help-balloon.balloon-right {
|
|
left: auto;
|
|
right: -10px;
|
|
transform: scale(1);
|
|
}
|
|
.help-balloon-trigger.is-active .help-balloon.balloon-right::after {
|
|
left: auto;
|
|
right: 14px;
|
|
transform: none;
|
|
}
|
|
|
|
.code-block {
|
|
background: #1e1e2e;
|
|
color: #a6e3a1;
|
|
border-radius: 0.75rem;
|
|
padding: 1rem 1.25rem;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
font-size: 0.75rem;
|
|
line-height: 1.6;
|
|
overflow-x: auto;
|
|
white-space: pre;
|
|
}
|
|
.code-block .code-comment { color: #6c7086; }
|
|
.code-block .code-tag { color: #89b4fa; }
|
|
.code-block .code-attr { color: #f9e2af; }
|
|
.code-block .code-string { color: #a6e3a1; }
|
|
.code-block .code-keyword { color: #cba6f7; }
|
|
.code-block .code-func { color: #89dceb; }
|
|
.code-block .code-number { color: #fab387; }
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<div class="max-w-6xl mx-auto">
|
|
|
|
<!-- 히어로 배너 -->
|
|
<div class="rounded-2xl overflow-hidden mb-8 shadow-lg" style="background: linear-gradient(135deg, #431407 0%, #0f172a 100%);">
|
|
<div class="flex items-center" style="flex-wrap: wrap;">
|
|
<div style="flex: 1 1 300px; padding: 2rem 2.5rem;">
|
|
<div class="flex items-center gap-2 text-sm mb-2" style="color: #fb923c;">
|
|
<span>아카데미</span>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
|
<span style="color: #fdba74;">백엔드 개발</span>
|
|
</div>
|
|
<h1 class="text-3xl font-bold mb-2" style="color: #ffffff;">백엔드 개발</h1>
|
|
<p class="text-sm" style="color: #cbd5e1;">서버, 데이터베이스, API부터 배포까지 — 비개발자도 이해하는 백엔드 가이드</p>
|
|
</div>
|
|
<div class="shrink-0" style="width: 240px; padding: 1.5rem;">
|
|
<div class="overflow-hidden rounded-xl">
|
|
<img src="{{ asset('images/academy/backend-dev/1.svg') }}" alt="3계층 아키텍처"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-8">
|
|
<!-- 좌측 고정 목차 (TOC) -->
|
|
<nav class="hidden lg:block shrink-0" style="width: 220px;">
|
|
<div class="sticky top-24">
|
|
<div class="bg-orange-50 border border-orange-200 rounded-xl p-5">
|
|
<h2 class="font-semibold text-orange-800 mb-3 flex items-center gap-2 text-sm">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
|
목차
|
|
</h2>
|
|
<div class="space-y-0.5 text-xs">
|
|
<a href="#what-is-backend" class="block text-orange-700 hover:text-orange-900 py-1 font-medium">1. 백엔드란?</a>
|
|
<a href="#frontend-vs-backend" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">프론트 vs 백엔드</a>
|
|
<a href="#backend-roles" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">백엔드의 역할</a>
|
|
|
|
<a href="#server-client" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">2. 서버와 클라이언트</a>
|
|
<a href="#http-cycle" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">HTTP 요청/응답</a>
|
|
<a href="#url-structure" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">URL 구조</a>
|
|
|
|
<a href="#database" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">3. 데이터베이스</a>
|
|
<a href="#rdb-nosql" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">RDB vs NoSQL</a>
|
|
<a href="#sql-crud" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">SQL CRUD</a>
|
|
|
|
<a href="#laravel" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">4. Laravel</a>
|
|
<a href="#mvc-pattern" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">MVC 패턴</a>
|
|
<a href="#eloquent-orm" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">Eloquent ORM</a>
|
|
|
|
<a href="#api-design" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">5. API 설계</a>
|
|
<a href="#rest-principles" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">REST 원칙</a>
|
|
<a href="#api-auth" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">API 인증</a>
|
|
|
|
<a href="#auth-security" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">6. 인증과 보안</a>
|
|
<a href="#session-vs-token" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">세션 vs 토큰</a>
|
|
<a href="#security-threats" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">보안 위협</a>
|
|
|
|
<a href="#business-logic" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">7. 비즈니스 로직</a>
|
|
<a href="#service-layer" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">서비스 레이어</a>
|
|
<a href="#transaction" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">트랜잭션</a>
|
|
|
|
<a href="#queue-async" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">8. 큐와 비동기</a>
|
|
<a href="#sync-vs-async" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">동기 vs 비동기</a>
|
|
<a href="#queue-worker" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">큐/워커</a>
|
|
|
|
<a href="#caching-performance" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">9. 캐싱과 성능</a>
|
|
<a href="#cache-basics" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">캐시 원리</a>
|
|
<a href="#n-plus-one" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">N+1 문제</a>
|
|
|
|
<a href="#deployment" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">10. 배포와 운영</a>
|
|
<a href="#environments" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">환경 분리</a>
|
|
<a href="#deploy-monitor" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">Docker 배포</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- 우측 콘텐츠 -->
|
|
<div class="flex-1 min-w-0 space-y-10">
|
|
|
|
<!-- 모바일 목차 -->
|
|
<div class="lg:hidden bg-orange-50 border border-orange-200 rounded-xl p-4 mb-6">
|
|
<details>
|
|
<summary class="font-semibold text-orange-800 text-sm cursor-pointer flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
|
목차 보기
|
|
</summary>
|
|
<nav class="mt-3 space-y-1 text-sm">
|
|
<a href="#what-is-backend" class="block text-orange-700 hover:text-orange-900 py-1">1. 백엔드란?</a>
|
|
<a href="#server-client" class="block text-orange-700 hover:text-orange-900 py-1">2. 서버와 클라이언트</a>
|
|
<a href="#database" class="block text-orange-700 hover:text-orange-900 py-1">3. 데이터베이스 기초</a>
|
|
<a href="#laravel" class="block text-orange-700 hover:text-orange-900 py-1">4. Laravel 프레임워크</a>
|
|
<a href="#api-design" class="block text-orange-700 hover:text-orange-900 py-1">5. API 설계</a>
|
|
<a href="#auth-security" class="block text-orange-700 hover:text-orange-900 py-1">6. 인증과 보안</a>
|
|
<a href="#business-logic" class="block text-orange-700 hover:text-orange-900 py-1">7. 비즈니스 로직</a>
|
|
<a href="#queue-async" class="block text-orange-700 hover:text-orange-900 py-1">8. 큐와 비동기 처리</a>
|
|
<a href="#caching-performance" class="block text-orange-700 hover:text-orange-900 py-1">9. 캐싱과 성능 최적화</a>
|
|
<a href="#deployment" class="block text-orange-700 hover:text-orange-900 py-1">10. 배포와 운영</a>
|
|
</nav>
|
|
</details>
|
|
</div>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 1. 백엔드란? --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="what-is-backend" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">1</span>
|
|
백엔드란?
|
|
</h2>
|
|
|
|
<div id="frontend-vs-backend" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
프론트엔드 vs 백엔드
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">3계층(3-Tier) 아키텍처란 프레젠테이션(화면), 애플리케이션(로직), 데이터(DB)를 분리하는 설계 방식이다. 각 계층은 독립적으로 수정·확장할 수 있다.</span></span>
|
|
</h3>
|
|
<div class="bg-orange-50 rounded-lg p-5 border border-orange-100 mb-4">
|
|
<p class="text-sm text-orange-900 leading-relaxed">
|
|
<strong>백엔드(Back-end)</strong>란 사용자 눈에 보이지 않는 서버 측 영역이다.
|
|
데이터를 저장하고, 비즈니스 규칙을 처리하고, 보안을 관리하고, API를 통해 프론트엔드에 데이터를 전달하는 역할을 한다.
|
|
</p>
|
|
</div>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 공장의 생산라인</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
백엔드는 <strong>공장의 생산라인</strong>이다. 고객은 완성된 제품(화면)만 보지만,
|
|
그 뒤에서는 원자재(데이터) 입고, 가공(비즈니스 로직), 품질검사(유효성 검증), 출하(API 응답)가 이루어진다.
|
|
프론트엔드가 매장(쇼룸)이라면, 백엔드는 그 뒤의 공장 전체다.
|
|
</p>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200 text-center">
|
|
<div class="mb-2">
|
|
<svg class="w-8 h-8 mx-auto text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>
|
|
</div>
|
|
<p class="font-bold text-orange-800 mb-1">프론트엔드 (매장)</p>
|
|
<p class="text-orange-700">사용자가 보는 화면<br>HTML, CSS, JavaScript<br>버튼, 폼, 메뉴, 테이블</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-4 border text-center">
|
|
<div class="mb-2">
|
|
<svg class="w-8 h-8 mx-auto text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2" /></svg>
|
|
</div>
|
|
<p class="font-bold text-gray-800 mb-1">백엔드 (공장)</p>
|
|
<p class="text-gray-600">서버에서 처리하는 로직<br>PHP, Laravel, MySQL<br>데이터 저장, 인증, API</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="backend-roles" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
백엔드의 4가지 역할
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
|
|
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
|
|
<p class="font-bold text-blue-800 mb-1">1. 데이터 저장/관리</p>
|
|
<p class="text-blue-700">주문, 고객, 제품 정보를 데이터베이스에 저장하고 필요할 때 꺼내온다.</p>
|
|
</div>
|
|
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
|
<p class="font-bold text-green-800 mb-1">2. 비즈니스 로직 처리</p>
|
|
<p class="text-green-700">견적 계산, 재고 차감, 할인 적용 등 업무 규칙을 코드로 구현한다.</p>
|
|
</div>
|
|
<div class="bg-purple-50 rounded-lg p-4 border border-purple-200">
|
|
<p class="font-bold text-purple-800 mb-1">3. 보안/인증</p>
|
|
<p class="text-purple-700">로그인, 권한 확인, 비밀번호 암호화 등 보안을 책임진다.</p>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200">
|
|
<p class="font-bold text-orange-800 mb-1">4. API 제공</p>
|
|
<p class="text-orange-700">프론트엔드(앱, 웹)에 데이터를 전달하는 창구를 만들어 제공한다.</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM 시스템의 백엔드:</strong>
|
|
SAM은 API 서버(Laravel)가 백엔드를 담당한다. MNG(관리자 웹)와 React 앱이 API를 통해 데이터를 받고,
|
|
MySQL 데이터베이스에 주문·견적·재고 등 모든 데이터가 저장된다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/1.svg') }}" alt="3계층 아키텍처 다이어그램"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">3계층 아키텍처 — 브라우저, API 서버, 데이터베이스</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 2. 서버와 클라이언트 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="server-client" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">2</span>
|
|
서버와 클라이언트
|
|
</h2>
|
|
|
|
<div id="http-cycle" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
HTTP 요청과 응답
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">HTTP는 HyperText Transfer Protocol. 웹에서 데이터를 주고받는 약속(규약)이다. 브라우저가 서버에 "이것 주세요"라고 요청하면, 서버가 "여기 있습니다"라고 응답한다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 편지 주고받기</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
클라이언트(브라우저)가 서버에 <strong>편지(요청)</strong>를 보낸다. 편지 봉투에는 받는 주소(URL), 요청 종류(GET/POST), 내용이 들어 있다.
|
|
서버는 편지를 읽고 처리한 뒤, <strong>답장(응답)</strong>을 보낸다. 답장에는 상태 코드(200 성공, 404 없음)와 데이터가 들어 있다.
|
|
</p>
|
|
</div>
|
|
<div class="overflow-x-auto mb-4">
|
|
<table class="w-full text-sm border-collapse">
|
|
<thead>
|
|
<tr class="border-b-2 border-gray-200 bg-gray-50">
|
|
<th class="text-left py-2 px-3 font-medium text-gray-600">HTTP 메서드</th>
|
|
<th class="text-left py-2 px-3 font-medium text-gray-600">역할</th>
|
|
<th class="text-left py-2 px-3 font-medium text-gray-600">비유</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="text-xs text-gray-700">
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 px-3 font-mono font-bold text-green-600">GET</td>
|
|
<td class="py-2 px-3">데이터 조회</td>
|
|
<td class="py-2 px-3">서류 열람 요청</td>
|
|
</tr>
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 px-3 font-mono font-bold text-blue-600">POST</td>
|
|
<td class="py-2 px-3">데이터 생성</td>
|
|
<td class="py-2 px-3">새 주문서 제출</td>
|
|
</tr>
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 px-3 font-mono font-bold text-orange-600">PUT</td>
|
|
<td class="py-2 px-3">데이터 전체 수정</td>
|
|
<td class="py-2 px-3">주문서 전체 다시 작성</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="py-2 px-3 font-mono font-bold text-red-600">DELETE</td>
|
|
<td class="py-2 px-3">데이터 삭제</td>
|
|
<td class="py-2 px-3">주문 취소</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="url-structure" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
URL 구조와 HTTPS
|
|
</h3>
|
|
<div class="code-block mb-4"><span class="code-comment">// URL 구조 분해</span>
|
|
<span class="code-string">https</span>://<span class="code-func">api.sam-erp.com</span>/<span class="code-keyword">api/v1</span>/<span class="code-attr">orders</span>/<span class="code-number">123</span>?<span class="code-tag">status=active</span>
|
|
|
|
<span class="code-comment">// https → 프로토콜 (암호화 통신)</span>
|
|
<span class="code-comment">// api.sam-erp.com → 도메인 (서버 주소)</span>
|
|
<span class="code-comment">// /api/v1 → API 접두사 + 버전</span>
|
|
<span class="code-comment">// /orders → 자원 (Resource)</span>
|
|
<span class="code-comment">// /123 → 특정 자원의 ID</span>
|
|
<span class="code-comment">// ?status=active → 쿼리 파라미터 (필터)</span></div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 API 통신:</strong>
|
|
SAM은 HTTPS로 암호화된 통신을 사용한다. API 서버 주소는 별도 도메인으로 운영되며,
|
|
MNG와 React 앱이 REST API를 통해 데이터를 주고받는다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/2.svg') }}" alt="요청-응답 사이클"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">요청-응답 사이클 — 클라이언트와 서버의 대화</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 3. 데이터베이스 기초 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="database" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">3</span>
|
|
데이터베이스 기초
|
|
</h2>
|
|
|
|
<div id="rdb-nosql" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
RDB vs NoSQL
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">RDB(관계형 데이터베이스)는 엑셀처럼 행과 열로 데이터를 저장한다. NoSQL은 JSON 문서, 키-값 쌍 등 유연한 형태로 저장한다. SAM은 MySQL(RDB)을 사용한다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 엑셀 파일</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
관계형 데이터베이스는 <strong>잘 정리된 엑셀 파일</strong>과 같다.
|
|
각 시트(테이블)에 제목 행(컬럼)이 있고, 각 행(레코드)에 데이터가 들어간다.
|
|
시트 간에 "주문번호"같은 공통 항목으로 연결(관계)할 수 있다.
|
|
</p>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
|
|
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
|
|
<p class="font-bold text-blue-800 mb-2">RDB (관계형)</p>
|
|
<ul class="text-blue-700 space-y-1">
|
|
<li>• 행과 열로 구성된 테이블</li>
|
|
<li>• SQL 언어로 데이터 조작</li>
|
|
<li>• 테이블 간 관계(FK) 설정</li>
|
|
<li>• 예: MySQL, PostgreSQL</li>
|
|
</ul>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-4 border">
|
|
<p class="font-bold text-gray-800 mb-2">NoSQL (비관계형)</p>
|
|
<ul class="text-gray-600 space-y-1">
|
|
<li>• JSON 형태로 유연하게 저장</li>
|
|
<li>• 대량 데이터에 강함</li>
|
|
<li>• 스키마 없이 자유로운 구조</li>
|
|
<li>• 예: MongoDB, Redis</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-gray-700 mb-3">
|
|
데이터베이스의 핵심 개념 세 가지: <strong>PK(Primary Key)</strong>는 각 행을 고유하게 식별하는 번호(사원번호),
|
|
<strong>FK(Foreign Key)</strong>는 다른 테이블을 참조하는 연결고리(부서코드),
|
|
<strong>인덱스</strong>는 빠른 검색을 위한 색인이다.
|
|
</p>
|
|
</div>
|
|
|
|
<div id="sql-crud" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
SQL CRUD와 인덱스
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">인덱스는 특정 컬럼의 값과 위치를 미리 정리해둔 목록이다. 책 뒷면의 색인처럼, 전체를 훑지 않고 원하는 데이터를 빠르게 찾을 수 있다.</span></span>
|
|
</h3>
|
|
<div class="code-block mb-4"><span class="code-comment">-- CREATE: 데이터 생성</span>
|
|
<span class="code-keyword">INSERT INTO</span> <span class="code-func">orders</span> (customer_name, total) <span class="code-keyword">VALUES</span> (<span class="code-string">'주일산업'</span>, <span class="code-number">1500000</span>);
|
|
|
|
<span class="code-comment">-- READ: 데이터 조회</span>
|
|
<span class="code-keyword">SELECT</span> * <span class="code-keyword">FROM</span> <span class="code-func">orders</span> <span class="code-keyword">WHERE</span> status = <span class="code-string">'active'</span>;
|
|
|
|
<span class="code-comment">-- UPDATE: 데이터 수정</span>
|
|
<span class="code-keyword">UPDATE</span> <span class="code-func">orders</span> <span class="code-keyword">SET</span> status = <span class="code-string">'shipped'</span> <span class="code-keyword">WHERE</span> id = <span class="code-number">123</span>;
|
|
|
|
<span class="code-comment">-- DELETE: 데이터 삭제</span>
|
|
<span class="code-keyword">DELETE FROM</span> <span class="code-func">orders</span> <span class="code-keyword">WHERE</span> id = <span class="code-number">123</span>;</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 데이터베이스:</strong>
|
|
SAM은 MySQL 8.0을 사용하며, <code>tenant_id</code> 컬럼으로 회사(테넌트)별 데이터를 격리한다.
|
|
주문, 견적, 품목, 사용자 등 모든 데이터가 관계형 테이블에 저장된다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/3.svg') }}" alt="ERD 테이블 관계도"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">ERD 테이블 관계 — 주문, 주문항목, 품목</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 4. Laravel 프레임워크 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="laravel" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">4</span>
|
|
Laravel 프레임워크
|
|
</h2>
|
|
|
|
<div id="mvc-pattern" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
MVC 패턴
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">미들웨어는 요청이 컨트롤러에 도달하기 전에 거치는 관문이다. 로그인 확인, 권한 검사, 요청 로깅 등을 처리한다. 공장의 보안 검색대와 같다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 공장 부서 분리</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
<strong>Model(모델)</strong>은 자재 창고 — 데이터를 보관하고 꺼내준다.
|
|
<strong>View(뷰)</strong>는 쇼룸 — 완성품을 예쁘게 진열한다.
|
|
<strong>Controller(컨트롤러)</strong>는 공장장 — 주문을 받아 창고에서 자재를 꺼내고, 가공해서, 쇼룸에 진열하도록 지시한다.
|
|
</p>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs mb-4">
|
|
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200 text-center">
|
|
<p class="font-bold text-blue-800 mb-1">Model (모델)</p>
|
|
<p class="text-blue-700">데이터 = DB 테이블<br>데이터 조회·저장·수정</p>
|
|
</div>
|
|
<div class="bg-green-50 rounded-lg p-4 border border-green-200 text-center">
|
|
<p class="font-bold text-green-800 mb-1">View (뷰)</p>
|
|
<p class="text-green-700">화면 = HTML 템플릿<br>데이터를 사용자에게 표시</p>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200 text-center">
|
|
<p class="font-bold text-orange-800 mb-1">Controller (컨트롤러)</p>
|
|
<p class="text-orange-700">중재자 = 비즈니스 로직<br>요청 처리, 모델·뷰 연결</p>
|
|
</div>
|
|
</div>
|
|
<div class="code-block mb-4"><span class="code-comment">// routes/web.php — URL과 컨트롤러 연결</span>
|
|
<span class="code-keyword">Route</span>::<span class="code-func">get</span>(<span class="code-string">'/orders'</span>, [<span class="code-tag">OrderController</span>::class, <span class="code-string">'index'</span>]);
|
|
|
|
<span class="code-comment">// Controller — 요청을 처리하는 중재자</span>
|
|
<span class="code-keyword">class</span> <span class="code-tag">OrderController</span> {
|
|
<span class="code-keyword">public function</span> <span class="code-func">index</span>() {
|
|
$orders = <span class="code-tag">Order</span>::<span class="code-func">where</span>(<span class="code-string">'status'</span>, <span class="code-string">'active'</span>)-><span class="code-func">get</span>();
|
|
<span class="code-keyword">return</span> <span class="code-func">view</span>(<span class="code-string">'orders.index'</span>, <span class="code-func">compact</span>(<span class="code-string">'orders'</span>));
|
|
}
|
|
}</div>
|
|
</div>
|
|
|
|
<div id="eloquent-orm" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
Eloquent ORM과 Artisan
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">ORM(Object-Relational Mapping)은 SQL을 직접 쓰지 않고 PHP 코드로 데이터베이스를 다루는 기술이다. 통역사처럼 PHP 언어를 SQL로 자동 변환해준다.</span></span>
|
|
</h3>
|
|
<div class="code-block mb-4"><span class="code-comment">// SQL 직접 작성</span>
|
|
<span class="code-keyword">SELECT</span> * <span class="code-keyword">FROM</span> orders <span class="code-keyword">WHERE</span> status = <span class="code-string">'active'</span> <span class="code-keyword">ORDER BY</span> created_at <span class="code-keyword">DESC</span>;
|
|
|
|
<span class="code-comment">// Eloquent ORM — 같은 결과를 PHP로</span>
|
|
$orders = <span class="code-tag">Order</span>::<span class="code-func">where</span>(<span class="code-string">'status'</span>, <span class="code-string">'active'</span>)
|
|
-><span class="code-func">orderBy</span>(<span class="code-string">'created_at'</span>, <span class="code-string">'desc'</span>)
|
|
-><span class="code-func">get</span>();</div>
|
|
<div class="bg-gray-50 rounded-lg p-4 border text-xs text-gray-700 mb-4">
|
|
<p class="font-semibold text-gray-800 mb-2">Artisan 주요 명령어</p>
|
|
<div class="space-y-1 font-mono">
|
|
<p><span class="text-orange-600">php artisan migrate</span> — DB 테이블 생성/수정</p>
|
|
<p><span class="text-orange-600">php artisan make:controller</span> — 컨트롤러 생성</p>
|
|
<p><span class="text-orange-600">php artisan make:model</span> — 모델 생성</p>
|
|
<p><span class="text-orange-600">php artisan cache:clear</span> — 캐시 초기화</p>
|
|
<p><span class="text-orange-600">php artisan tinker</span> — 대화형 PHP 콘솔</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 Laravel:</strong>
|
|
SAM은 Laravel 11 + PHP 8.3을 사용한다. MNG와 API 모두 Laravel 프레임워크 기반이며,
|
|
Eloquent ORM으로 MySQL 데이터를 다루고, Artisan 명령어로 마이그레이션·캐시 관리를 한다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/4.svg') }}" alt="MVC 흐름도"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">MVC 흐름도 — Route → Controller → Model → View</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 5. API 설계 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="api-design" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">5</span>
|
|
API 설계
|
|
</h2>
|
|
|
|
<div id="rest-principles" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
REST 원칙과 JSON
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">REST는 URL로 자원(Resource)을 표현하고 HTTP 메서드로 행위를 나타내는 설계 방식이다. /orders는 주문 자원, GET은 조회, POST는 생성 행위를 의미한다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 주문 창구</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
API는 <strong>주문 창구</strong>다. 메뉴판(API 문서)을 보고 정해진 양식(JSON)으로 주문(요청)하면,
|
|
주방(서버)에서 요리(처리)해서 음식(데이터)을 내준다.
|
|
아무렇게나 주문하면 안 되고, 정해진 형식을 따라야 한다.
|
|
</p>
|
|
</div>
|
|
<div class="overflow-x-auto mb-4">
|
|
<table class="w-full text-sm border-collapse">
|
|
<thead>
|
|
<tr class="border-b-2 border-gray-200 bg-gray-50">
|
|
<th class="text-left py-2 px-3 font-medium text-gray-600">메서드</th>
|
|
<th class="text-left py-2 px-3 font-medium text-gray-600">URL</th>
|
|
<th class="text-left py-2 px-3 font-medium text-gray-600">동작</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="text-xs text-gray-700">
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 px-3 font-mono font-bold text-green-600">GET</td>
|
|
<td class="py-2 px-3 font-mono">/api/v1/orders</td>
|
|
<td class="py-2 px-3">주문 목록 조회</td>
|
|
</tr>
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 px-3 font-mono font-bold text-green-600">GET</td>
|
|
<td class="py-2 px-3 font-mono">/api/v1/orders/123</td>
|
|
<td class="py-2 px-3">주문 #123 상세 조회</td>
|
|
</tr>
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 px-3 font-mono font-bold text-blue-600">POST</td>
|
|
<td class="py-2 px-3 font-mono">/api/v1/orders</td>
|
|
<td class="py-2 px-3">새 주문 생성</td>
|
|
</tr>
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 px-3 font-mono font-bold text-orange-600">PUT</td>
|
|
<td class="py-2 px-3 font-mono">/api/v1/orders/123</td>
|
|
<td class="py-2 px-3">주문 #123 수정</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="py-2 px-3 font-mono font-bold text-red-600">DELETE</td>
|
|
<td class="py-2 px-3 font-mono">/api/v1/orders/123</td>
|
|
<td class="py-2 px-3">주문 #123 삭제</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="code-block mb-4"><span class="code-comment">// JSON 응답 예시</span>
|
|
{
|
|
<span class="code-attr">"data"</span>: {
|
|
<span class="code-attr">"id"</span>: <span class="code-number">123</span>,
|
|
<span class="code-attr">"customer"</span>: <span class="code-string">"주일산업"</span>,
|
|
<span class="code-attr">"total"</span>: <span class="code-number">1500000</span>,
|
|
<span class="code-attr">"status"</span>: <span class="code-string">"active"</span>,
|
|
<span class="code-attr">"items"</span>: [
|
|
{ <span class="code-attr">"name"</span>: <span class="code-string">"방화셔터 A형"</span>, <span class="code-attr">"qty"</span>: <span class="code-number">5</span> }
|
|
]
|
|
}
|
|
}</div>
|
|
</div>
|
|
|
|
<div id="api-auth" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
API 인증과 버전 관리
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">Sanctum은 Laravel 공식 인증 패키지다. 모바일 앱과 SPA를 위한 토큰 발급, API 인증을 간단하게 구현한다. 회원증을 발급받아 매번 보여주는 것과 같다.</span></span>
|
|
</h3>
|
|
<div class="code-block mb-4"><span class="code-comment">// Bearer Token 인증 — 회원증 제시</span>
|
|
<span class="code-keyword">GET</span> /api/v1/orders
|
|
<span class="code-tag">Authorization</span>: <span class="code-string">Bearer eyJhbGciOiJIUzI1NiIs...</span>
|
|
|
|
<span class="code-comment">// 버전 관리 — API가 바뀌어도 기존 앱이 동작</span>
|
|
/api/<span class="code-func">v1</span>/orders <span class="code-comment">← 현재 버전</span>
|
|
/api/<span class="code-func">v2</span>/orders <span class="code-comment">← 새 버전 (구조 변경)</span></div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 API 인증:</strong>
|
|
SAM API는 <code>/api/v1/</code> 접두사를 사용하고, Laravel Sanctum으로 토큰 인증을 처리한다.
|
|
React 앱에서 로그인하면 토큰을 발급받아 이후 모든 API 요청에 포함한다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/5.svg') }}" alt="REST API 구조"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">REST API 구조 — URL 분해와 JSON 응답</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 6. 인증과 보안 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="auth-security" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">6</span>
|
|
인증과 보안
|
|
</h2>
|
|
|
|
<div id="session-vs-token" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
세션 vs 토큰
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">bcrypt는 비밀번호를 되돌릴 수 없는 형태로 변환(해싱)하는 알고리즘이다. 같은 비밀번호라도 매번 다른 해시값을 생성하여 보안성이 높다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 도장카드 vs 출입배지</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
<strong>세션</strong>은 <strong>도장카드</strong>다. 가게(서버)에 내 도장카드를 보관해두고, 올 때마다 번호표(세션ID)만 보여준다. 가게가 기록을 관리한다.
|
|
<strong>토큰</strong>은 <strong>출입배지</strong>다. 내가 배지(JWT)를 가지고 다니며, 입장할 때마다 배지를 보여준다. 건물(서버)은 배지 진위만 확인한다.
|
|
</p>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
|
|
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
|
|
<p class="font-bold text-blue-800 mb-2">세션 (Session)</p>
|
|
<ul class="text-blue-700 space-y-1">
|
|
<li>• 서버에 상태 저장 (Stateful)</li>
|
|
<li>• 쿠키로 세션 ID 전달</li>
|
|
<li>• 서버 메모리/DB 사용</li>
|
|
<li>• SAM MNG에서 사용</li>
|
|
</ul>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200">
|
|
<p class="font-bold text-orange-800 mb-2">토큰 (Token)</p>
|
|
<ul class="text-orange-700 space-y-1">
|
|
<li>• 서버에 상태 미저장 (Stateless)</li>
|
|
<li>• Authorization 헤더로 전달</li>
|
|
<li>• 확장성이 좋음 (서버 무상태)</li>
|
|
<li>• SAM API에서 사용 (Sanctum)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="security-threats" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
보안 위협과 방어
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
|
|
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-2">SQL Injection</p>
|
|
<p class="text-red-700 mb-2">사용자 입력에 악의적 SQL을 삽입하는 공격</p>
|
|
<p class="text-green-700">방어: 파라미터 바인딩 (Eloquent 자동 처리)</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-2">CSRF</p>
|
|
<p class="text-red-700 mb-2">사용자 모르게 악의적 요청을 보내는 공격</p>
|
|
<p class="text-green-700">방어: CSRF 토큰 검증 (Laravel 자동 처리)</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 보안:</strong>
|
|
SAM MNG는 세션 기반 인증 + CSRF 토큰으로 보안을 처리하고,
|
|
API 서버는 Sanctum 토큰 인증을 사용한다. 비밀번호는 bcrypt로 해싱하여 저장한다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/6.svg') }}" alt="세션 vs 토큰 비교"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">인증 비교 — 세션 방식 vs 토큰 방식</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 7. 비즈니스 로직 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="business-logic" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">7</span>
|
|
비즈니스 로직
|
|
</h2>
|
|
|
|
<div id="service-layer" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
서비스 레이어와 유효성 검증
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">서비스 레이어는 컨트롤러에서 비즈니스 로직을 분리한 클래스다. 컨트롤러가 "무엇을 할지" 결정하면, 서비스가 "어떻게 할지" 실행한다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 회사 규정</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
비즈니스 로직은 <strong>회사 규정</strong>이다. "견적 금액이 100만원 이상이면 팀장 승인 필요",
|
|
"재고가 0이면 주문 불가" 같은 규칙을 코드로 구현한 것이다.
|
|
규정이 바뀌면 서비스 클래스만 수정하면 된다.
|
|
</p>
|
|
</div>
|
|
<div class="code-block mb-4"><span class="code-comment">// Validation — 입력값 검증</span>
|
|
$request-><span class="code-func">validate</span>([
|
|
<span class="code-string">'customer_name'</span> => <span class="code-string">'required|max:100'</span>,
|
|
<span class="code-string">'email'</span> => <span class="code-string">'required|email'</span>,
|
|
<span class="code-string">'total'</span> => <span class="code-string">'required|numeric|min:0'</span>,
|
|
]);
|
|
|
|
<span class="code-comment">// Event/Listener — 이벤트 기반 처리</span>
|
|
<span class="code-comment">// 주문 생성 → 이벤트 발생 → 리스너가 알림 발송</span>
|
|
<span class="code-keyword">event</span>(<span class="code-keyword">new</span> <span class="code-tag">OrderCreated</span>($order));
|
|
<span class="code-comment">// → SendOrderNotification 리스너가 자동 실행</span></div>
|
|
</div>
|
|
|
|
<div id="transaction" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
트랜잭션
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">트랜잭션은 여러 DB 작업을 하나의 단위로 묶는 것이다. 모두 성공하면 확정(commit), 하나라도 실패하면 전부 취소(rollback)한다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 은행 이체</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
A 계좌에서 10만원을 빼고 B 계좌에 10만원을 넣는 이체를 생각해보자.
|
|
A에서 빼기는 성공했는데 B에 넣기가 실패하면? 돈이 사라진다!
|
|
<strong>트랜잭션</strong>은 두 작업을 하나로 묶어 "둘 다 성공하거나, 둘 다 취소"하도록 보장한다.
|
|
</p>
|
|
</div>
|
|
<div class="code-block mb-4"><span class="code-comment">// Laravel 트랜잭션 예시</span>
|
|
<span class="code-tag">DB</span>::<span class="code-func">transaction</span>(<span class="code-keyword">function</span> () {
|
|
<span class="code-comment">// 1. A 계좌에서 출금</span>
|
|
$accountA-><span class="code-func">decrement</span>(<span class="code-string">'balance'</span>, <span class="code-number">100000</span>);
|
|
|
|
<span class="code-comment">// 2. B 계좌에 입금</span>
|
|
$accountB-><span class="code-func">increment</span>(<span class="code-string">'balance'</span>, <span class="code-number">100000</span>);
|
|
|
|
<span class="code-comment">// 둘 다 성공 → 자동 commit</span>
|
|
<span class="code-comment">// 하나라도 실패 → 자동 rollback</span>
|
|
});</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 비즈니스 로직:</strong>
|
|
SAM은 견적 계산, 주문 처리, 재고 관리 등의 비즈니스 로직을 서비스 클래스에서 처리한다.
|
|
주문 생성 시 재고 차감, 이력 기록 등을 트랜잭션으로 묶어 데이터 무결성을 보장한다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/7.svg') }}" alt="트랜잭션 다이어그램"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">트랜잭션 — 성공(commit)과 실패(rollback)</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 8. 큐와 비동기 처리 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="queue-async" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">8</span>
|
|
큐와 비동기 처리
|
|
</h2>
|
|
|
|
<div id="sync-vs-async" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
동기 vs 비동기
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">큐는 선입선출(FIFO) 방식의 작업 대기열이다. 은행 대기표처럼 먼저 들어온 작업부터 순서대로 처리한다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 대기표 시스템</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
<strong>동기 처리</strong>는 은행 창구에서 줄 서는 것 — 앞 사람이 끝날 때까지 기다려야 한다.
|
|
<strong>비동기 처리</strong>는 대기표를 받는 것 — 번호표를 받고 자기 볼일을 보다가, 호출되면 처리한다.
|
|
이메일 100통을 보낼 때, 동기면 30초 기다려야 하지만 비동기(큐)면 즉시 "발송 예약됨"이 표시된다.
|
|
</p>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
|
|
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-2">동기 처리 (Synchronous)</p>
|
|
<p class="text-red-700">순서대로 하나씩 처리<br>앞 작업 끝나야 다음 시작<br>사용자가 기다려야 함</p>
|
|
</div>
|
|
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
|
<p class="font-bold text-green-800 mb-2">비동기 처리 (Asynchronous)</p>
|
|
<p class="text-green-700">큐에 넣고 즉시 응답<br>워커가 백그라운드에서 처리<br>사용자는 기다리지 않음</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="queue-worker" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
큐/워커와 스케줄러
|
|
</h3>
|
|
<div class="code-block mb-4"><span class="code-comment">// Job 클래스 — 큐에 넣을 작업 단위</span>
|
|
<span class="code-keyword">class</span> <span class="code-tag">SendAlimtalk</span> <span class="code-keyword">implements</span> <span class="code-func">ShouldQueue</span> {
|
|
<span class="code-keyword">public function</span> <span class="code-func">handle</span>() {
|
|
<span class="code-comment">// 알림톡 발송 로직</span>
|
|
<span class="code-tag">AlimtalkService</span>::<span class="code-func">send</span>($this->phone, $this->message);
|
|
}
|
|
}
|
|
|
|
<span class="code-comment">// 큐에 작업 넣기</span>
|
|
<span class="code-tag">SendAlimtalk</span>::<span class="code-func">dispatch</span>($phone, $message);
|
|
<span class="code-comment">// → 즉시 응답, 워커가 나중에 처리</span>
|
|
|
|
<span class="code-comment">// 스케줄러 — 매일 새벽 2시에 백업</span>
|
|
$schedule-><span class="code-func">command</span>(<span class="code-string">'backup:run'</span>)-><span class="code-func">dailyAt</span>(<span class="code-string">'02:00'</span>);</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 큐 활용:</strong>
|
|
SAM은 알림톡 발송, PDF 생성, 이메일 발송 등을 큐로 비동기 처리한다.
|
|
Docker 컨테이너 안에서 Queue Worker가 상시 실행되며, Scheduler로 정기 작업을 자동 실행한다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/8.svg') }}" alt="큐 처리 흐름"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">큐 처리 — 요청 → 큐 → 워커 → 완료</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 9. 캐싱과 성능 최적화 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="caching-performance" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">9</span>
|
|
캐싱과 성능 최적화
|
|
</h2>
|
|
|
|
<div id="cache-basics" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
캐시의 원리
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">캐시는 자주 사용하는 데이터를 빠른 저장소에 임시 보관하는 기법이다. DB에서 매번 조회하는 대신 캐시에서 바로 꺼내면 응답 속도가 수십 배 빨라진다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 책상 위 필기도구</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
자주 쓰는 볼펜과 메모지는 <strong>책상 위(캐시)</strong>에 놓아둔다. 필요할 때 바로 꺼내 쓸 수 있다.
|
|
가끔 쓰는 도구는 <strong>서랍(데이터베이스)</strong>에 보관한다. 서랍을 열고 찾는 데 시간이 더 걸린다.
|
|
캐시는 이처럼 자주 쓰는 데이터를 가까이 두어 빠르게 접근하는 기법이다.
|
|
</p>
|
|
</div>
|
|
<div class="code-block mb-4"><span class="code-comment">// Laravel 캐시 사용</span>
|
|
<span class="code-comment">// 캐시에 있으면 바로 반환, 없으면 DB에서 조회 후 캐시에 저장</span>
|
|
$orders = <span class="code-tag">Cache</span>::<span class="code-func">remember</span>(<span class="code-string">'active_orders'</span>, <span class="code-number">3600</span>, <span class="code-keyword">function</span> () {
|
|
<span class="code-keyword">return</span> <span class="code-tag">Order</span>::<span class="code-func">where</span>(<span class="code-string">'status'</span>, <span class="code-string">'active'</span>)-><span class="code-func">get</span>();
|
|
});
|
|
<span class="code-comment">// 3600 = 1시간(초) 동안 캐시 유지</span></div>
|
|
</div>
|
|
|
|
<div id="n-plus-one" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
N+1 문제와 Eager Loading
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">N+1 문제는 1번의 쿼리로 목록을 가져온 뒤, 각 항목마다 추가 쿼리를 실행하는 것이다. 100개 주문을 조회하면 101번의 쿼리가 발생한다.</span></span>
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
|
|
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-2">N+1 문제 (느림)</p>
|
|
<div class="font-mono text-red-700 space-y-0.5">
|
|
<p>1. SELECT * FROM orders</p>
|
|
<p>2. SELECT * FROM items WHERE order_id=1</p>
|
|
<p>3. SELECT * FROM items WHERE order_id=2</p>
|
|
<p>... (N번 반복)</p>
|
|
<p class="mt-1 font-sans font-bold">총 101번 쿼리!</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
|
<p class="font-bold text-green-800 mb-2">Eager Loading (빠름)</p>
|
|
<div class="font-mono text-green-700 space-y-0.5">
|
|
<p>1. SELECT * FROM orders</p>
|
|
<p>2. SELECT * FROM items WHERE order_id IN (1,2,...)</p>
|
|
<p class="mt-1 font-sans font-bold">총 2번 쿼리!</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="code-block mb-4"><span class="code-comment">// N+1 문제 발생</span>
|
|
$orders = <span class="code-tag">Order</span>::<span class="code-func">all</span>(); <span class="code-comment">// 1번 쿼리</span>
|
|
<span class="code-keyword">foreach</span> ($orders <span class="code-keyword">as</span> $order) {
|
|
$order->items; <span class="code-comment">// N번 추가 쿼리!</span>
|
|
}
|
|
|
|
<span class="code-comment">// Eager Loading으로 해결</span>
|
|
$orders = <span class="code-tag">Order</span>::<span class="code-func">with</span>(<span class="code-string">'items'</span>)-><span class="code-func">get</span>(); <span class="code-comment">// 2번 쿼리로 끝!</span></div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 성능 최적화:</strong>
|
|
SAM은 config, route 캐싱으로 응답 속도를 최적화하고,
|
|
Eager Loading으로 N+1 문제를 방지한다. DB 인덱스를 적절히 설정하여 대량 데이터 조회 성능을 확보한다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/9.svg') }}" alt="캐시 계층 다이어그램"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">캐시 계층 — 캐시 히트와 캐시 미스</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 10. 배포와 운영 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="deployment" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">10</span>
|
|
배포와 운영
|
|
</h2>
|
|
|
|
<div id="environments" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
환경 분리
|
|
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">.env 파일은 DB 접속 정보, API 키 등 환경마다 달라지는 설정을 모아둔 파일이다. 절대 Git에 커밋하면 안 된다.</span></span>
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 시험생산 → 품질검사 → 출하</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
공장에서 신제품을 바로 출하하지 않는다.
|
|
<strong>시험생산(Local)</strong>에서 만들어보고, <strong>품질검사(Staging)</strong>를 통과하면,
|
|
비로소 <strong>출하(Production)</strong>한다.
|
|
소프트웨어도 마찬가지로 환경을 분리하여 안전하게 배포한다.
|
|
</p>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs mb-4">
|
|
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200 text-center">
|
|
<p class="font-bold text-blue-800 mb-1">Local (개발)</p>
|
|
<p class="text-blue-700">개발자 PC<br>자유롭게 실험<br>Docker 컨테이너</p>
|
|
</div>
|
|
<div class="bg-yellow-50 rounded-lg p-4 border border-yellow-200 text-center">
|
|
<p class="font-bold text-yellow-800 mb-1">Staging (테스트)</p>
|
|
<p class="text-yellow-700">운영과 동일한 환경<br>최종 검증<br>실 데이터 없음</p>
|
|
</div>
|
|
<div class="bg-green-50 rounded-lg p-4 border border-green-200 text-center">
|
|
<p class="font-bold text-green-800 mb-1">Production (운영)</p>
|
|
<p class="text-green-700">실제 서비스<br>실 사용자 접속<br>장애 시 즉시 대응</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="deploy-monitor" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
|
|
Docker 배포와 모니터링
|
|
</h3>
|
|
<div class="code-block mb-4"><span class="code-comment"># SAM 배포 절차 (간략)</span>
|
|
<span class="code-comment"># 1. 코드 작성 & 커밋</span>
|
|
<span class="code-func">git add</span> . && <span class="code-func">git commit</span> -m <span class="code-string">"feat: 새 기능 추가"</span>
|
|
|
|
<span class="code-comment"># 2. 코드 푸시</span>
|
|
<span class="code-func">git push</span> origin develop
|
|
|
|
<span class="code-comment"># 3. 서버에서 코드 받기</span>
|
|
<span class="code-func">git pull</span> origin develop
|
|
|
|
<span class="code-comment"># 4. 의존성 설치 & 마이그레이션</span>
|
|
<span class="code-func">composer install</span>
|
|
<span class="code-func">php artisan migrate</span>
|
|
<span class="code-func">php artisan config:clear</span></div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
|
|
<div class="bg-gray-50 rounded-lg p-4 border">
|
|
<p class="font-bold text-gray-800 mb-2">Git 브랜치 전략</p>
|
|
<ul class="text-gray-600 space-y-1">
|
|
<li>• <code>develop</code> — 개발 진행 브랜치</li>
|
|
<li>• <code>main</code> — 운영 배포 브랜치</li>
|
|
<li>• <code>feature/*</code> — 기능 개발 브랜치</li>
|
|
<li>• <code>hotfix/*</code> — 긴급 수정 브랜치</li>
|
|
</ul>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-4 border">
|
|
<p class="font-bold text-gray-800 mb-2">로그와 모니터링</p>
|
|
<ul class="text-gray-600 space-y-1">
|
|
<li>• <code>storage/logs/laravel.log</code> — 앱 로그</li>
|
|
<li>• <code>nginx error.log</code> — 웹서버 로그</li>
|
|
<li>• <code>php artisan queue:work</code> — 큐 상태</li>
|
|
<li>• <code>docker ps</code> — 컨테이너 상태</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
|
|
<p class="text-xs text-orange-800 leading-relaxed">
|
|
<strong>SAM의 배포:</strong>
|
|
SAM은 Docker Compose로 로컬 개발 환경(MNG, API, MySQL, Nginx)을 구성하고,
|
|
운영 서버에는 직접 배포한다. develop 브랜치에서 개발을 진행하며,
|
|
코드 포맷팅(Pint), 캐시 클리어, 마이그레이션을 거쳐 배포한다.
|
|
</p>
|
|
</div>
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/backend-dev/10.svg') }}" alt="배포 파이프라인"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 320px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">배포 파이프라인 — 코드 → Git → 빌드 → 서버</p>
|
|
|
|
<!-- 핵심 정리 -->
|
|
<div class="mt-6 bg-orange-50 rounded-lg p-5 border border-orange-100">
|
|
<h4 class="font-semibold text-orange-800 mb-2 text-sm">핵심 정리</h4>
|
|
<p class="text-xs text-orange-700 leading-relaxed">
|
|
백엔드는 <strong>"사용자 눈에 보이지 않는 서버의 세계"</strong>다.
|
|
HTTP로 요청을 받아, 데이터베이스에서 데이터를 꺼내고, 비즈니스 로직을 처리하여 API로 응답한다.
|
|
Laravel 프레임워크가 MVC 패턴, ORM, 미들웨어, 큐, 캐시 등 핵심 기능을 제공하고,
|
|
세션·토큰으로 인증, 트랜잭션으로 데이터 무결성, Eager Loading으로 성능을 보장한다.
|
|
Docker로 개발 환경을 통일하고, Git으로 버전을 관리하여 안전하게 배포한다.
|
|
SAM은 이 모든 백엔드 기술을 활용하여 ERP/MES 통합 시스템을 운영하고 있다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- hover 프리뷰 오버레이 -->
|
|
<div id="hover-preview">
|
|
<img id="hover-preview-img" src="" alt="">
|
|
<span class="hover-caption" id="hover-preview-caption"></span>
|
|
</div>
|
|
|
|
<!-- 클릭 라이트박스 -->
|
|
<div id="lightbox" onclick="closeLightbox()">
|
|
<button onclick="closeLightbox()" style="position:absolute; top:1rem; right:1rem; background:none; border:none; cursor:pointer; color:rgba(255,255,255,0.8); padding:0.5rem;">
|
|
<svg style="width:2rem; height:2rem;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
|
|
</button>
|
|
<img id="lightbox-img" onclick="event.stopPropagation()">
|
|
</div>
|
|
<script>
|
|
(function() {
|
|
var preview = document.getElementById('hover-preview');
|
|
var previewImg = document.getElementById('hover-preview-img');
|
|
var previewCaption = document.getElementById('hover-preview-caption');
|
|
var hoverTimer = null;
|
|
var isPreviewActive = false;
|
|
var HOVER_DELAY = 350;
|
|
|
|
document.querySelectorAll('.academy-img-hover').forEach(function(img) {
|
|
img.addEventListener('mouseenter', function() {
|
|
var el = this;
|
|
hoverTimer = setTimeout(function() {
|
|
showPreview(el);
|
|
}, HOVER_DELAY);
|
|
});
|
|
|
|
img.addEventListener('mouseleave', function() {
|
|
clearTimeout(hoverTimer);
|
|
if (isPreviewActive) {
|
|
hidePreview();
|
|
}
|
|
});
|
|
});
|
|
|
|
function showPreview(el) {
|
|
previewImg.src = el.src;
|
|
previewImg.alt = el.alt || '';
|
|
var caption = el.alt || '';
|
|
var nextP = el.parentElement && el.parentElement.querySelector('p');
|
|
if (nextP) caption = nextP.textContent;
|
|
previewCaption.textContent = caption;
|
|
|
|
preview.classList.add('is-active');
|
|
isPreviewActive = true;
|
|
}
|
|
|
|
function hidePreview() {
|
|
preview.classList.remove('is-active');
|
|
isPreviewActive = false;
|
|
}
|
|
|
|
window.openLightbox = function(el) {
|
|
var lb = document.getElementById('lightbox');
|
|
var img = document.getElementById('lightbox-img');
|
|
img.src = el.src;
|
|
img.alt = el.alt;
|
|
lb.classList.add('is-open');
|
|
document.body.style.overflow = 'hidden';
|
|
hidePreview();
|
|
};
|
|
|
|
window.closeLightbox = function() {
|
|
var lb = document.getElementById('lightbox');
|
|
lb.classList.remove('is-open');
|
|
document.body.style.overflow = '';
|
|
};
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
if (isPreviewActive) hidePreview();
|
|
closeLightbox();
|
|
}
|
|
});
|
|
|
|
// 도움말 풍선 토글
|
|
window.toggleBalloon = function(el) {
|
|
var wasActive = el.classList.contains('is-active');
|
|
// 모든 열린 풍선 닫기
|
|
document.querySelectorAll('.help-balloon-trigger.is-active').forEach(function(t) {
|
|
t.classList.remove('is-active');
|
|
});
|
|
// 클릭한 풍선만 토글
|
|
if (!wasActive) {
|
|
el.classList.add('is-active');
|
|
}
|
|
};
|
|
|
|
// 풍선 외부 클릭 시 닫기
|
|
document.addEventListener('click', function(e) {
|
|
if (!e.target.closest('.help-balloon-trigger')) {
|
|
document.querySelectorAll('.help-balloon-trigger.is-active').forEach(function(t) {
|
|
t.classList.remove('is-active');
|
|
});
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
@include('components.academy-glossary', ['domain' => 'backend-dev'])
|
|
@endsection
|