๐ ์จ๋ผ์ธ ์๊ฒฉ์ฆ ๊ฒ์ฆ & ๋์งํธ ๋ฐฐ์ง ๋ฐ๊ธ ํ๋ซํผ - ๋ธ๋ก์ฒด์ธ ์คํ์ผ ์ธ์ฆ์ ๊ด๋ฆฌ
digital-badge-certification-platform
๋ฌธ์ ์ค๋ช
ํ๋ก์ ํธ ๊ฐ์
๊ธฐ์ ๊ณผ ๊ต์ก๊ธฐ๊ด์ด ์๋ฃ์์๊ฒ ๊ฒ์ฆ ๊ฐ๋ฅํ ๋์งํธ ์๊ฒฉ์ฆ์ ๋ฐ๊ธํ๊ณ , ์๋ฃ์์ ์์ ์ ์ธ์ฆ์๋ฅผ ํฌํธํด๋ฆฌ์ค์ฒ๋ผ ๊ด๋ฆฌํ๋ฉฐ ์ธ๋ถ์ ๊ณต์ ํ ์ ์๋ ํ์คํ ํ๋ซํผ์ ๋๋ค. Laravel 13.x + Blade + Livewire๋ก ๊ตฌํํ๋ฉฐ, QR ์ฝ๋ ๊ธฐ๋ฐ ์ง์ ํ์ธ, PDF ์๋ ์์ฑ, ๊ณต๊ฐ ํ๋กํ ํ์ด์ง, ์ด๋ฉ์ผ ์๋ ๋ฐ์ก ๋ฑ ์ค๋ฌด ์์ค์ ๊ธฐ๋ฅ์ ํฌํจํฉ๋๋ค.
ํต์ฌ ๊ธฐ๋ฅ
ํ์ ๊ธฐ๋ฅ
-
๊ด๋ฆฌ์ ๋์๋ณด๋ (๋ฐ๊ธ ๊ธฐ๊ด์ฉ)
- ์๊ฒฉ์ฆ ํ ํ๋ฆฟ ์์ฑ (๊ณผ์ ๋ช , ์ค๋ช , ๋์์ธ ์ปค์คํฐ๋ง์ด์ง)
- ์๋ฃ์ ์ผ๊ด ๋ฑ๋ก (CSV ์ ๋ก๋)
- ๊ฐ๋ณ/์ผ๊ด ์๊ฒฉ์ฆ ๋ฐ๊ธ
- ๋ฐ๊ธ ์ด๋ ฅ ์กฐํ ๋ฐ ํต๊ณ
-
์๊ฒฉ์ฆ ๋ฐ๊ธ ์์คํ
- ๊ณ ์ ์ธ์ฆ๋ฒํธ ์๋ ์์ฑ (UUID)
- QR ์ฝ๋ ์๋ ์ฝ์ (๊ณต๊ฐ ๊ฒ์ฆ URL ํฌํจ)
- PDF ์๋ ์์ฑ (Laravel DOMPDF/Snappy)
- ๋ฐ๊ธ ์ฆ์ ์ด๋ฉ์ผ ์๋ ๋ฐ์ก (Queue ์ฌ์ฉ)
-
์๋ฃ์ ๋ง์ดํ์ด์ง
- ๋ด๊ฐ ๋ฐ์ ์๊ฒฉ์ฆ ๋ชฉ๋ก (์ธ๋ค์ผ ๊ทธ๋ฆฌ๋)
- ์๊ฒฉ์ฆ ์์ธ ๋ณด๊ธฐ (๋ฉํ๋ฐ์ดํฐ: ๋ฐ๊ธ์ผ, ๋ฐ๊ธ๊ธฐ๊ด, ์ ํจ๊ธฐ๊ฐ)
- PDF ๋ค์ด๋ก๋
- ์์ ๋ฏธ๋์ด ๊ณต์ ๋งํฌ ์์ฑ
-
๊ณต๊ฐ ๊ฒ์ฆ ์์คํ
- QR ์ฝ๋ ์ค์บ โ ๊ณต๊ฐ ๊ฒ์ฆ ํ์ด์ง ์ด๋
- ์ธ์ฆ๋ฒํธ ์ ๋ ฅ์ผ๋ก ์๋ ๊ฒ์ฆ
- ๊ฒ์ฆ ๊ฒฐ๊ณผ: ๋ฐ๊ธ ์ ๋ณด, ์๋ฃ์ ์ด๋ฆ, ๊ณผ์ ๋ช , ๋ฐ๊ธ์ผ, ์ง์ ์ฌ๋ถ
- ์์กฐ ๋ฐฉ์ง: DB ํด์ ๋น๊ต
-
๊ณต๊ฐ ํ๋กํ ํ์ด์ง
- ์๋ฃ์๋ณ ๊ณ ์ URL (์:
/profile/{username}) - ์ทจ๋ํ ์๊ฒฉ์ฆ ๊ฐค๋ฌ๋ฆฌ
- ์๊ธฐ์๊ฐ & ์์ ๋งํฌ
- ํ๋กํ ๊ณต๊ฐ/๋น๊ณต๊ฐ ์ค์
- ์๋ฃ์๋ณ ๊ณ ์ URL (์:
๋ณด๋์ค ๊ธฐ๋ฅ
- ์๊ฒฉ์ฆ ๋ง๋ฃ์ผ ์๋ ์๋ฆผ (Laravel Scheduler)
- ๋ค๊ตญ์ด ์ง์ (ํ/์ ์๊ฒฉ์ฆ)
- ์๊ฒฉ์ฆ ํ ํ๋ฆฟ ๋ง์ผํ๋ ์ด์ค (๊ด๋ฆฌ์๊ฐ ๋ค์ํ ๋์์ธ ์ ํ)
- ๋ฐ๊ธ ํต๊ณ ๋์๋ณด๋ (Chart.js)
- ์๊ฒฉ์ฆ ์ฌ๋ฐ๊ธ ์์ฒญ ์์คํ
- OAuth ์์ ๋ก๊ทธ์ธ (Google/GitHub)
๊ธฐ์ ์คํ
- Backend: Laravel 13.x (Eloquent ORM, Queue, Scheduler, Mail)
- Frontend: Blade + Livewire 3.x (์ค์๊ฐ UI ์ ๋ฐ์ดํธ)
- Styling: Tailwind CSS 4.x
- Database: MySQL/PostgreSQL (๊ด๊ณํ ๋ฐ์ดํฐ ์ค๊ณ)
- PDF: Laravel Snappy (wkhtmltopdf) ๋๋ DOMPDF
- QR Code: SimpleSoftwareIO/simple-qrcode
- Storage: Laravel Storage (S3/๋ก์ปฌ)
- Queue: Redis (์ด๋ฉ์ผ ๋น๋๊ธฐ ๋ฐ์ก)
์์ ํ์ต ํฌ์ธํธ
- ๋ณต์กํ DB ์ค๊ณ (Organizations, Certificates, Templates, Users ๊ด๊ณ)
- Queue๋ฅผ ํ์ฉํ ๋น๋๊ธฐ ์์ (์ด๋ฉ์ผ, PDF ์์ฑ)
- ํ์ผ ์ ๋ก๋ & ์คํ ๋ฆฌ์ง ๊ด๋ฆฌ
- PDF ๋์ ์์ฑ (Blade ๋ทฐ๋ฅผ PDF๋ก ๋ณํ)
- QR ์ฝ๋ ์์ฑ ๋ฐ ๊ณต๊ฐ ๊ฒ์ฆ ๋ก์ง
- Laravel Livewire๋ฅผ ํ์ฉํ SPA ๊ฐ์ UX
- Middleware๋ฅผ ํตํ ๊ถํ ๊ด๋ฆฌ (๋ฐ๊ธ๊ธฐ๊ด/์๋ฃ์ ๋ถ๋ฆฌ)
- ์ด๋ฉ์ผ ํ ํ๋ฆฟ ์ปค์คํฐ๋ง์ด์ง
- Scheduler๋ฅผ ํ์ฉํ ์๋ํ (๋ง๋ฃ ์๋ฆผ)
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x ์ต์ ๋ฒ์ ์ฌ์ฉ ํ์
- โข Blade ํ ํ๋ฆฟ ์์ง ์ฌ์ฉ (React/Vue ๊ธ์ง)
- โข Livewire 3.x๋ก ๋์ UI ๊ตฌํ
- โข Tailwind CSS 4.x๋ก ์คํ์ผ๋ง
- โข MySQL ๋๋ PostgreSQL ์ฌ์ฉ (SQLite ๊ธ์ง, ํ๋ก๋์ ์์ค DB ํ์)
- โข Queue ์์คํ ํ์ ๊ตฌํ (Redis ๊ถ์ฅ)
- โข PDF ์์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํตํฉ (Snappy ๋๋ DOMPDF)
- โข QR ์ฝ๋ ์์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํตํฉ
- โข ํ์ผ ์ ๋ก๋๋ ๋ฐ๋์ Laravel Storage ์ฌ์ฉ
- โข ์ด๋ฉ์ผ ๋ฐ์ก์ Queue๋ก ๋น๋๊ธฐ ์ฒ๋ฆฌ
- โข ๊ณต๊ฐ ๊ฒ์ฆ ํ์ด์ง๋ ์ธ์ฆ ์์ด ์ ๊ทผ ๊ฐ๋ฅํด์ผ ํจ
- โข ๊ด๋ฆฌ์์ ์๋ฃ์ ๊ถํ ๋ถ๋ฆฌ (Middleware/Policy ์ฌ์ฉ)
- โข ๋ฐ์ํ ๋์์ธ ํ์ (๋ชจ๋ฐ์ผ ์นํ์ )
- โข ๋ณด์: CSRF ๋ณดํธ, SQL Injection ๋ฐฉ์ง, XSS ๋ฐฉ์ง
- โข ํ๋ก๋์ ๋ ๋: ์๋ฌ ํธ๋ค๋ง, ๋ก๊น , ํ๊ฒฝ๋ณ์ ๊ด๋ฆฌ
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir digital-badge-certification-platform && cd digital-badge-certification-platform
์ด ํด๋์์ ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค.
# ๋จ๊ณ 1: Laravel ํ๋ก์ ํธ ์ด๊ธฐ ์ค์ ๋ฐ ์ธ์ฆ ์์คํ
๊ตฌ์ถ
Laravel 13.x ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ณ , ๋ค์ ์๊ตฌ์ฌํญ์ ๋ง์ถฐ ์ด๊ธฐ ์ค์ ์ ์๋ฃํ์ธ์:
## 1. ํ๋ก์ ํธ ์์ฑ ๋ฐ ๊ธฐ๋ณธ ์ค์
- Laravel 13.x ์ต์ ๋ฒ์ ์ผ๋ก ํ๋ก์ ํธ ์์ฑ
- `.env` ํ์ผ ์ค์ (MySQL/PostgreSQL DB ์ฐ๊ฒฐ)
- Tailwind CSS 4.x ์ค์น ๋ฐ Vite ์ค์
- Livewire 3.x ์ค์น ๋ฐ ์ค์
## 2. ์ธ์ฆ ์์คํ
๊ตฌ์ถ
- Laravel Breeze (Blade + Livewire ์คํ) ์ค์น
- ์ฌ์ฉ์ ์ญํ ์์คํ
์ถ๊ฐ:
- `users` ํ
์ด๋ธ์ `role` ์ปฌ๋ผ ์ถ๊ฐ (enum: 'admin', 'student')
- ๊ด๋ฆฌ์๋ ์๊ฒฉ์ฆ ๋ฐ๊ธ ๊ธฐ๊ด ๋ด๋น์
- ํ์์ ์๊ฒฉ์ฆ ์๋ฃ์
- ํ์๊ฐ์
์ ๊ธฐ๋ณธ ์ญํ ์ 'student'๋ก ์ค์
- ์ญํ ๋ณ ์ ๊ทผ ์ ์ด๋ฅผ ์ํ Middleware ์์ฑ:
- `AdminMiddleware`: ๊ด๋ฆฌ์๋ง ์ ๊ทผ ๊ฐ๋ฅ
- `StudentMiddleware`: ํ์๋ง ์ ๊ทผ ๊ฐ๋ฅ
## 3. ๊ธฐ๋ณธ ๋ผ์ฐํธ ๊ตฌ์กฐ
- ๊ณต๊ฐ ๋ผ์ฐํธ: ํํ์ด์ง, ๋ก๊ทธ์ธ, ํ์๊ฐ์
, ์๊ฒฉ์ฆ ๊ฒ์ฆ ํ์ด์ง
- ๊ด๋ฆฌ์ ๋ผ์ฐํธ: `/admin/*` (AdminMiddleware ์ ์ฉ)
- ํ์ ๋ผ์ฐํธ: `/dashboard`, `/my-certificates` (auth ๋ฏธ๋ค์จ์ด)
## 4. ๋ ์ด์์ ๋ฐ ๋ค๋น๊ฒ์ด์
- Blade ๋ ์ด์์ ํ์ผ ์์ฑ (`layouts/app.blade.php`)
- Tailwind CSS๋ก ๊ธฐ๋ณธ ๋ค๋น๊ฒ์ด์
๋ฐ ๊ตฌํ:
- ๋ก๊ณ
- ๋ก๊ทธ์ธ/ํ์๊ฐ์
๋งํฌ (๋น๋ก๊ทธ์ธ ์)
- ์ญํ ๋ณ ๋ฉ๋ด (๋ก๊ทธ์ธ ์)
- ๊ด๋ฆฌ์: ๋์๋ณด๋, ํ
ํ๋ฆฟ ๊ด๋ฆฌ, ์๊ฒฉ์ฆ ๋ฐ๊ธ
- ํ์: ๋ด ์๊ฒฉ์ฆ, ํ๋กํ
- ๋ก๊ทธ์์ ๋ฒํผ
## 5. ํํ์ด์ง ๊ตฌํ
- ๋๋ฉ ํ์ด์ง (`welcome.blade.php`) ์์ฑ
- ์๋น์ค ์๊ฐ ์น์
- ์ฃผ์ ๊ธฐ๋ฅ ์นด๋ (์๊ฒฉ์ฆ ๋ฐ๊ธ, ๊ฒ์ฆ, ๊ณต๊ฐ ํ๋กํ)
- CTA ๋ฒํผ (ํ์๊ฐ์
, ์๊ฒฉ์ฆ ๊ฒ์ฆํ๊ธฐ)
์๋ฃ ํ `php artisan serve`๋ก ์๋ฒ๋ฅผ ์คํํ๊ณ , ํ์๊ฐ์
/๋ก๊ทธ์ธ์ด ์ ์ ์๋ํ๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x ํ๋ก์ ํธ๊ฐ ์์ฑ๋๊ณ , Livewire + Tailwind CSS๊ฐ ์ค์น๋๋ฉฐ, ์ญํ ๊ธฐ๋ฐ ์ธ์ฆ ์์คํ ์ด ๊ตฌ์ถ๋ฉ๋๋ค. ๊ด๋ฆฌ์์ ํ์์ ๊ตฌ๋ถํ๋ Middleware๊ฐ ์์ฑ๋๊ณ , ๊ธฐ๋ณธ ๋ ์ด์์๊ณผ ํํ์ด์ง๊ฐ ์์ฑ๋ฉ๋๋ค.
# ๋จ๊ณ 2: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ ๋ฐ ๋ง์ด๊ทธ๋ ์ด์ ์๊ฒฉ์ฆ ๋ฐ๊ธ ์์คํ ์ ํต์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ค๊ณํ๊ณ ๋ง์ด๊ทธ๋ ์ด์ ์ ์์ฑํ์ธ์. ## 1. Organizations ํ ์ด๋ธ (๋ฐ๊ธ ๊ธฐ๊ด) - `id`: Primary Key - `name`: ๊ธฐ๊ด๋ช (string, ํ์) - `logo`: ๋ก๊ณ ์ด๋ฏธ์ง ๊ฒฝ๋ก (string, nullable) - `description`: ๊ธฐ๊ด ์๊ฐ (text, nullable) - `created_at`, `updated_at` ## 2. Certificate_Templates ํ ์ด๋ธ (์๊ฒฉ์ฆ ํ ํ๋ฆฟ) - `id`: Primary Key - `organization_id`: Foreign Key (organizations) - `name`: ํ ํ๋ฆฟ๋ช (์: "์น ๊ฐ๋ฐ ๊ธฐ์ด ๊ณผ์ ") - `description`: ๊ณผ์ ์ค๋ช (text) - `design_config`: JSON ํ๋ (์์, ํฐํธ, ๋ ์ด์์ ์ค์ ) - `duration_months`: ์ ํจ ๊ธฐ๊ฐ (integer, nullable, ์: 12๊ฐ์) - `is_active`: ํ์ฑํ ์ฌ๋ถ (boolean, ๊ธฐ๋ณธ๊ฐ true) - `created_at`, `updated_at` ## 3. Certificates ํ ์ด๋ธ (๋ฐ๊ธ๋ ์๊ฒฉ์ฆ) - `id`: Primary Key - `certificate_number`: ๊ณ ์ ์ธ์ฆ๋ฒํธ (UUID, unique, indexed) - `template_id`: Foreign Key (certificate_templates) - `user_id`: Foreign Key (users, ์๋ฃ์) - `issued_at`: ๋ฐ๊ธ์ผ (timestamp) - `expires_at`: ๋ง๋ฃ์ผ (timestamp, nullable) - `pdf_path`: PDF ํ์ผ ๊ฒฝ๋ก (string) - `qr_code_path`: QR ์ฝ๋ ์ด๋ฏธ์ง ๊ฒฝ๋ก (string) - `verification_hash`: ๊ฒ์ฆ์ฉ ํด์ (string, ์์กฐ ๋ฐฉ์ง) - `status`: ์ํ (enum: 'active', 'expired', 'revoked') - `created_at`, `updated_at` ## 4. User_Profiles ํ ์ด๋ธ (์๋ฃ์ ๊ณต๊ฐ ํ๋กํ) - `id`: Primary Key - `user_id`: Foreign Key (users, unique) - `username`: ๊ณ ์ ์ฌ์ฉ์๋ช (string, unique, indexed) - `bio`: ์๊ธฐ์๊ฐ (text, nullable) - `avatar`: ํ๋กํ ์ฌ์ง ๊ฒฝ๋ก (string, nullable) - `linkedin_url`, `github_url`, `website_url`: ์์ ๋งํฌ (nullable) - `is_public`: ํ๋กํ ๊ณต๊ฐ ์ฌ๋ถ (boolean, ๊ธฐ๋ณธ๊ฐ false) - `created_at`, `updated_at` ## 5. Eloquent ๋ชจ๋ธ ์์ฑ - `Organization`, `CertificateTemplate`, `Certificate`, `UserProfile` ๋ชจ๋ธ ์์ฑ - ๊ด๊ณ ์ค์ : - Organization hasMany CertificateTemplates - CertificateTemplate hasMany Certificates - User hasMany Certificates - User hasOne UserProfile - Certificate belongsTo User, belongsTo CertificateTemplate ## 6. ์๋ ์์ฑ - ํ ์คํธ์ฉ ๊ด๋ฆฌ์ ๊ณ์ ์์ฑ (email: admin@example.com, role: admin) - ํ ์คํธ์ฉ ํ์ ๊ณ์ ์์ฑ (email: student@example.com, role: student) - ์ํ Organization 1๊ฐ ์์ฑ - ์ํ CertificateTemplate 2๊ฐ ์์ฑ ๋ง์ด๊ทธ๋ ์ด์ ์ ์คํํ๊ณ ์๋๋ฅผ ์คํํ์ฌ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Organizations, CertificateTemplates, Certificates, UserProfiles ํ ์ด๋ธ์ด ์์ฑ๋๊ณ , Eloquent ๋ชจ๋ธ๊ณผ ๊ด๊ณ๊ฐ ์ ์๋ฉ๋๋ค. ์๋๋ฅผ ํตํด ํ ์คํธ ๊ณ์ ๊ณผ ์ํ ๋ฐ์ดํฐ๊ฐ DB์ ์ฝ์ ๋ฉ๋๋ค.
# ๋จ๊ณ 3: ๊ด๋ฆฌ์ ๋์๋ณด๋ - ์๊ฒฉ์ฆ ํ
ํ๋ฆฟ ๊ด๋ฆฌ (Livewire)
๊ด๋ฆฌ์๊ฐ ์๊ฒฉ์ฆ ํ
ํ๋ฆฟ์ ์์ฑ, ์์ , ์ญ์ ํ ์ ์๋ CRUD ์์คํ
์ Livewire๋ก ๊ตฌํํ์ธ์.
## 1. ๊ด๋ฆฌ์ ๋์๋ณด๋ ๋ผ์ฐํธ ๋ฐ ์ปจํธ๋กค๋ฌ
- `/admin/dashboard`: ๊ด๋ฆฌ์ ํ (๋ฐ๊ธ ํต๊ณ ์์ฝ)
- `/admin/templates`: ํ
ํ๋ฆฟ ๋ชฉ๋ก
- `/admin/templates/create`: ํ
ํ๋ฆฟ ์์ฑ ํผ
- `/admin/templates/{id}/edit`: ํ
ํ๋ฆฟ ์์ ํผ
## 2. Livewire ์ปดํฌ๋ํธ: TemplateList
- ํ
ํ๋ฆฟ ๋ชฉ๋ก์ ํ
์ด๋ธ๋ก ํ์
- ๊ฒ์ ๊ธฐ๋ฅ (ํ
ํ๋ฆฟ๋ช
์ผ๋ก ์ค์๊ฐ ํํฐ๋ง)
- ์ ๋ ฌ ๊ธฐ๋ฅ (์์ฑ์ผ ๊ธฐ์ค)
- ํ์ฑํ/๋นํ์ฑํ ํ ๊ธ ๋ฒํผ
- ์์ /์ญ์ ๋ฒํผ
- ์ญ์ ์ ํ์ธ ๋ชจ๋ฌ (Livewire Wire:confirm ์ฌ์ฉ)
## 3. Livewire ์ปดํฌ๋ํธ: TemplateForm
- ํ
ํ๋ฆฟ ์์ฑ/์์ ํผ
- ์
๋ ฅ ํ๋:
- ํ
ํ๋ฆฟ๋ช
(required)
- ๊ณผ์ ์ค๋ช
(textarea, required)
- ์ ํจ ๊ธฐ๊ฐ (๊ฐ์ ์, nullable)
- ๋์์ธ ์ค์ (JSON, ์ผ๋จ ํ
์คํธ ํ๋๋ก ๊ฐ๋จํ ๊ตฌํ)
- ์ค์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ (Livewire ์ค์๊ฐ validation)
- ์ฑ๊ณต ์ ํ ์คํธ ๋ฉ์์ง ํ์
## 4. UI ๋์์ธ
- Tailwind CSS๋ก ๊น๋ํ ๊ด๋ฆฌ์ ํจ๋ ๋์์ธ
- ์ฌ์ด๋๋ฐ: ํ
ํ๋ฆฟ ๊ด๋ฆฌ, ์๊ฒฉ์ฆ ๋ฐ๊ธ, ํต๊ณ
- ๋ฉ์ธ ์์ญ: ์ปจํ
์ธ ํ์
- ๋ฐ์ํ ๋์์ธ (๋ชจ๋ฐ์ผ์์๋ ์ฌ์ด๋๋ฐ ํ ๊ธ)
## 5. ๊ถํ ํ์ธ
- AdminMiddleware๊ฐ ๋ชจ๋ `/admin/*` ๋ผ์ฐํธ์ ์ ์ฉ๋์ด ์๋์ง ํ์ธ
- ์ผ๋ฐ ์ฌ์ฉ์๊ฐ ์ ๊ทผ ์ 403 ์๋ฌ ๋๋ ํ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ
์๋ฃ ํ ๊ด๋ฆฌ์ ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธํ์ฌ ํ
ํ๋ฆฟ์ ์์ฑ/์์ /์ญ์ ํ ์ ์๋์ง ํ
์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ด๋ฆฌ์ ๋์๋ณด๋๊ฐ ๊ตฌ์ถ๋๊ณ , Livewire๋ฅผ ํ์ฉํ ํ ํ๋ฆฟ CRUD ์์คํ ์ด ์์ฑ๋ฉ๋๋ค. ์ค์๊ฐ ๊ฒ์, ์ ๋ ฌ, ํ ๊ธ, ์ญ์ ํ์ธ ๋ชจ๋ฌ์ด ์๋ํ๋ฉฐ, ๊น๋ํ UI๋ก ํ์๋ฉ๋๋ค.
# ๋จ๊ณ 4: ์๊ฒฉ์ฆ ๋ฐ๊ธ ์์คํ
- PDF ์์ฑ ๋ฐ QR ์ฝ๋ ํตํฉ
๊ด๋ฆฌ์๊ฐ ์๋ฃ์์๊ฒ ์๊ฒฉ์ฆ์ ๋ฐ๊ธํ๊ณ , PDF์ QR ์ฝ๋๋ฅผ ์๋ ์์ฑํ๋ ์์คํ
์ ๊ตฌํํ์ธ์.
## 1. ํจํค์ง ์ค์น
- PDF ์์ฑ: `composer require barryvdh/laravel-dompdf` ๋๋ `composer require barryvdh/laravel-snappy` (wkhtmltopdf ์ค์น ํ์)
- QR ์ฝ๋: `composer require simplesoftwareio/simple-qrcode`
## 2. ์๊ฒฉ์ฆ ๋ฐ๊ธ ํผ (Livewire)
- `/admin/certificates/issue`: ๋ฐ๊ธ ํ์ด์ง
- ์
๋ ฅ ํ๋:
- ํ
ํ๋ฆฟ ์ ํ (๋๋กญ๋ค์ด)
- ์๋ฃ์ ์ด๋ฉ์ผ ๋๋ ์ฌ์ฉ์ ๊ฒ์ (Livewire ์ค์๊ฐ ๊ฒ์)
- ๋ฐ๊ธ์ผ (๊ธฐ๋ณธ๊ฐ: ์ค๋)
- ๋ง๋ฃ์ผ (์๋ ๊ณ์ฐ: ๋ฐ๊ธ์ผ + ํ
ํ๋ฆฟ ์ ํจ๊ธฐ๊ฐ)
- ๋ฐ๊ธ ๋ฒํผ ํด๋ฆญ ์:
- Certificate ๋ ์ฝ๋ ์์ฑ (๊ณ ์ ์ธ์ฆ๋ฒํธ UUID ์์ฑ)
- PDF ์์ฑ (Blade ๋ทฐ๋ฅผ PDF๋ก ๋ณํ)
- QR ์ฝ๋ ์์ฑ (๊ฒ์ฆ URL ํฌํจ: `/verify/{certificate_number}`)
- ํ์ผ์ Storage์ ์ ์ฅ
- ๊ฒ์ฆ์ฉ ํด์ ์์ฑ (certificate_number + secret_key๋ฅผ SHA256 ํด์ฑ)
## 3. PDF ํ
ํ๋ฆฟ Blade ๋ทฐ
- `resources/views/certificates/pdf-template.blade.php` ์์ฑ
- ๋์์ธ:
- ์๋จ: ๋ฐ๊ธ ๊ธฐ๊ด ๋ก๊ณ ๋ฐ ์ด๋ฆ
- ์ค์: "Certificate of Completion" ์ ๋ชฉ
- ์๋ฃ์ ์ด๋ฆ (ํฌ๊ณ ๊ตต๊ฒ)
- ๊ณผ์ ๋ช
๋ฐ ์ค๋ช
- ๋ฐ๊ธ์ผ, ์ธ์ฆ๋ฒํธ
- ํ๋จ: QR ์ฝ๋ (์ฐ์ธก ํ๋จ)
- ์๋ช
๋ (๋ฐ๊ธ ๊ธฐ๊ด ๋ด๋น์ ์ด๋ฆ)
- Tailwind CSS ์ธ๋ผ์ธ ์คํ์ผ ์ฌ์ฉ (PDF์์๋ ์ธ๋ถ CSS ์ ํ์ )
## 4. QR ์ฝ๋ ์์ฑ
- QR ์ฝ๋์ ํฌํจํ URL: `https://yourdomain.com/verify/{certificate_number}`
- QR ์ฝ๋๋ฅผ PNG ์ด๋ฏธ์ง๋ก ์์ฑํ์ฌ Storage์ ์ ์ฅ
- PDF์ QR ์ฝ๋ ์ด๋ฏธ์ง ์ฝ์
## 5. ๋ฐ๊ธ ์ด๋ ฅ ํ์ด์ง
- `/admin/certificates`: ๋ฐ๊ธ๋ ์๊ฒฉ์ฆ ๋ชฉ๋ก
- ํ
์ด๋ธ: ์๋ฃ์ ์ด๋ฆ, ํ
ํ๋ฆฟ๋ช
, ๋ฐ๊ธ์ผ, ์ํ, PDF ๋ค์ด๋ก๋ ๋ฒํผ
- ๊ฒ์ ๋ฐ ํํฐ (Livewire)
์๋ฃ ํ ๊ด๋ฆฌ์ ๊ณ์ ์ผ๋ก ํ
์คํธ ์๊ฒฉ์ฆ์ ๋ฐ๊ธํ๊ณ , PDF์ QR ์ฝ๋๊ฐ ์ ์ ์์ฑ๋๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ด๋ฆฌ์๊ฐ ์๊ฒฉ์ฆ์ ๋ฐ๊ธํ๋ฉด PDF์ QR ์ฝ๋๊ฐ ์๋ ์์ฑ๋๊ณ Storage์ ์ ์ฅ๋ฉ๋๋ค. PDF์๋ ์๋ฃ์ ์ ๋ณด, ๊ณผ์ ๋ช , QR ์ฝ๋๊ฐ ํฌํจ๋๋ฉฐ, ๋ฐ๊ธ ์ด๋ ฅ ํ์ด์ง์์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค.
# ๋จ๊ณ 5: ๊ณต๊ฐ ์๊ฒฉ์ฆ ๊ฒ์ฆ ์์คํ
๋๊ตฌ๋ ์ธ์ฆ๋ฒํธ ๋๋ QR ์ฝ๋๋ฅผ ํตํด ์๊ฒฉ์ฆ์ ์ง์๋ฅผ ํ์ธํ ์ ์๋ ๊ณต๊ฐ ๊ฒ์ฆ ํ์ด์ง๋ฅผ ๊ตฌํํ์ธ์.
## 1. ๊ฒ์ฆ ํ์ด์ง ๋ผ์ฐํธ
- `/verify`: ๊ฒ์ฆ ํํ์ด์ง (์ธ์ฆ๋ฒํธ ์
๋ ฅ ํผ)
- `/verify/{certificate_number}`: QR ์ฝ๋ ์ค์บ ์ ์๋ ์ด๋ํ๋ ๊ฒ์ฆ ๊ฒฐ๊ณผ ํ์ด์ง
## 2. ๊ฒ์ฆ ํํ์ด์ง (`/verify`)
- Blade ๋ทฐ: `resources/views/verify/index.blade.php`
- ์ธ์ฆ๋ฒํธ ์
๋ ฅ ํผ (UUID ํ์)
- "๊ฒ์ฆํ๊ธฐ" ๋ฒํผ ํด๋ฆญ ์ `/verify/{certificate_number}`๋ก ์ด๋
- ๋์์ธ: ๊น๋ํ๊ณ ์ ๋ขฐ๊ฐ ์๋ UI (Tailwind CSS)
## 3. ๊ฒ์ฆ ๊ฒฐ๊ณผ ํ์ด์ง (`/verify/{certificate_number}`)
- ์ปจํธ๋กค๋ฌ: `VerificationController@show`
- ๋ก์ง:
- DB์์ certificate_number๋ก ์๊ฒฉ์ฆ ์กฐํ
- ๊ฒ์ฆ์ฉ ํด์ ๋น๊ต (์์กฐ ์ฌ๋ถ ํ์ธ)
- ์๊ฒฉ์ฆ์ด ์กด์ฌํ๊ณ ํด์๊ฐ ์ผ์นํ๋ฉด "์ ํจํ ์๊ฒฉ์ฆ"
- ์กด์ฌํ์ง ์๊ฑฐ๋ ํด์ ๋ถ์ผ์น ์ "์ ํจํ์ง ์์ ์๊ฒฉ์ฆ" ๊ฒฝ๊ณ
- ํ์ ์ ๋ณด:
- ์๋ฃ์ ์ด๋ฆ
- ๊ณผ์ ๋ช
๋ฐ ์ค๋ช
- ๋ฐ๊ธ ๊ธฐ๊ด
- ๋ฐ๊ธ์ผ ๋ฐ ๋ง๋ฃ์ผ
- ์ธ์ฆ๋ฒํธ
- ์ํ (์ ํจ/๋ง๋ฃ/์ทจ์)
- PDF ๋ค์ด๋ก๋ ๋ฒํผ (์ ํ์ฌํญ)
- ์ ํจํ ์๊ฒฉ์ฆ: ์ด๋ก์ ๋ฐฐ์ง + ์ฒดํฌ ์์ด์ฝ
- ์ ํจํ์ง ์์ ์๊ฒฉ์ฆ: ๋นจ๊ฐ์ ๊ฒฝ๊ณ + X ์์ด์ฝ
## 4. QR ์ฝ๋ ์ค์บ ํ
์คํธ
- ๋ฐ๊ธ๋ PDF์ QR ์ฝ๋๋ฅผ ์ค๋งํธํฐ์ผ๋ก ์ค์บ
- ์๋์ผ๋ก `/verify/{certificate_number}` ํ์ด์ง๋ก ์ด๋
- ๊ฒ์ฆ ๊ฒฐ๊ณผ ํ์ธ
## 5. SEO ๋ฐ ๊ณต์
- ๊ฒ์ฆ ๊ฒฐ๊ณผ ํ์ด์ง์ Open Graph ๋ฉํ ํ๊ทธ ์ถ๊ฐ
- ์์
๋ฏธ๋์ด ๊ณต์ ์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ต์ ํ
์๋ฃ ํ ํ
์คํธ ์๊ฒฉ์ฆ์ QR ์ฝ๋๋ฅผ ์ค์บํ๊ฑฐ๋ ์ธ์ฆ๋ฒํธ๋ฅผ ์
๋ ฅํ์ฌ ๊ฒ์ฆ์ด ์ ์ ์๋ํ๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ณต๊ฐ ๊ฒ์ฆ ํ์ด์ง๊ฐ ๊ตฌ์ถ๋๊ณ , ์ธ์ฆ๋ฒํธ ์ ๋ ฅ ๋๋ QR ์ฝ๋ ์ค์บ์ผ๋ก ์๊ฒฉ์ฆ์ ์ง์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ์ ํจํ ์๊ฒฉ์ฆ์ ์์ธ ์ ๋ณด๋ฅผ ํ์ํ๊ณ , ์ ํจํ์ง ์์ ๊ฒฝ์ฐ ๊ฒฝ๊ณ ๋ฉ์์ง๋ฅผ ํ์ํฉ๋๋ค.
# ๋จ๊ณ 6: ํ์ ๋ง์ดํ์ด์ง - ๋ด ์๊ฒฉ์ฆ ๊ด๋ฆฌ
์๋ฃ์์ด ์์ ์ด ๋ฐ์ ์๊ฒฉ์ฆ์ ํ์ธํ๊ณ ๋ค์ด๋ก๋ํ ์ ์๋ ๋ง์ดํ์ด์ง๋ฅผ ๊ตฌํํ์ธ์.
## 1. ํ์ ๋์๋ณด๋ ๋ผ์ฐํธ
- `/dashboard`: ํ์ ํ (์๊ฒฉ์ฆ ๊ฐ์ ์์ฝ)
- `/my-certificates`: ๋ด ์๊ฒฉ์ฆ ๋ชฉ๋ก
- `/my-certificates/{id}`: ์๊ฒฉ์ฆ ์์ธ ๋ณด๊ธฐ
## 2. ์๊ฒฉ์ฆ ๋ชฉ๋ก ํ์ด์ง (Livewire)
- Livewire ์ปดํฌ๋ํธ: `MyCertificates`
- ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ์๊ฒฉ์ฆ์ ๊ทธ๋ฆฌ๋ ํ์์ผ๋ก ํ์
- ๊ฐ ์นด๋:
- ๊ณผ์ ๋ช
- ๋ฐ๊ธ์ผ
- ์ํ ๋ฐฐ์ง (์ ํจ/๋ง๋ฃ)
- ์ธ๋ค์ผ (PDF ์ฒซ ํ์ด์ง ์ด๋ฏธ์ง ๋๋ ๊ธฐ๋ณธ ์ด๋ฏธ์ง)
- "์์ธ ๋ณด๊ธฐ" ๋ฒํผ
- ํํฐ ๊ธฐ๋ฅ: ์ํ๋ณ (์ ์ฒด/์ ํจ/๋ง๋ฃ)
- ๊ฒ์ ๊ธฐ๋ฅ: ๊ณผ์ ๋ช
์ผ๋ก ์ค์๊ฐ ํํฐ๋ง
## 3. ์๊ฒฉ์ฆ ์์ธ ํ์ด์ง
- Blade ๋ทฐ: `resources/views/certificates/show.blade.php`
- ํ์ ์ ๋ณด:
- ๊ณผ์ ๋ช
๋ฐ ์ค๋ช
- ๋ฐ๊ธ ๊ธฐ๊ด
- ๋ฐ๊ธ์ผ ๋ฐ ๋ง๋ฃ์ผ
- ์ธ์ฆ๋ฒํธ
- QR ์ฝ๋ ์ด๋ฏธ์ง
- ์ก์
๋ฒํผ:
- PDF ๋ค์ด๋ก๋
- ๊ณต๊ฐ ๊ฒ์ฆ ํ์ด์ง ๋งํฌ ๋ณต์ฌ
- ์์
๋ฏธ๋์ด ๊ณต์ (LinkedIn, Twitter)
## 4. PDF ๋ค์ด๋ก๋ ๊ธฐ๋ฅ
- ๋ผ์ฐํธ: `/certificates/{id}/download`
- ์ปจํธ๋กค๋ฌ: Storage์์ PDF ํ์ผ ์ฝ์ด์ ๋ค์ด๋ก๋ ์๋ต ๋ฐํ
- ๊ถํ ํ์ธ: ์์ ์ ์๊ฒฉ์ฆ๋ง ๋ค์ด๋ก๋ ๊ฐ๋ฅ (Policy ์ฌ์ฉ)
## 5. ์์
๊ณต์ ๋งํฌ ์์ฑ
- LinkedIn ๊ณต์ URL: `https://www.linkedin.com/sharing/share-offsite/?url={verification_url}`
- Twitter ๊ณต์ URL: `https://twitter.com/intent/tweet?text={text}&url={verification_url}`
- ๊ณต์ ํ
์คํธ: "์ ๋ {๊ณผ์ ๋ช
} ์๊ฒฉ์ฆ์ ์ทจ๋ํ์ต๋๋ค! ๐"
์๋ฃ ํ ํ์ ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธํ์ฌ ์๊ฒฉ์ฆ ๋ชฉ๋ก์ ํ์ธํ๊ณ PDF๋ฅผ ๋ค์ด๋ก๋ํ ์ ์๋์ง ํ
์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ํ์ ๋ง์ดํ์ด์ง๊ฐ ๊ตฌ์ถ๋๊ณ , ๋ฐ์ ์๊ฒฉ์ฆ์ ๊ทธ๋ฆฌ๋ ํ์์ผ๋ก ํ์ธํ ์ ์์ต๋๋ค. ์์ธ ํ์ด์ง์์ PDF ๋ค์ด๋ก๋, ๊ณต๊ฐ ๊ฒ์ฆ ๋งํฌ ๋ณต์ฌ, ์์ ๋ฏธ๋์ด ๊ณต์ ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
# ๋จ๊ณ 7: ์๋ฃ์ ๊ณต๊ฐ ํ๋กํ ํ์ด์ง
์๋ฃ์์ด ์์ ์ ์๊ฒฉ์ฆ์ ์ธ๋ถ์ ๊ณต๊ฐํ ์ ์๋ ํฌํธํด๋ฆฌ์ค ์คํ์ผ์ ํ๋กํ ํ์ด์ง๋ฅผ ๊ตฌํํ์ธ์.
## 1. ํ๋กํ ์ค์ ํ์ด์ง
- `/profile/edit`: ํ๋กํ ํธ์ง ํ์ด์ง (Livewire)
- ์
๋ ฅ ํ๋:
- ๊ณ ์ ์ฌ์ฉ์๋ช
(username, URL์ ์ฌ์ฉ, unique ๊ฒ์ฆ)
- ์๊ธฐ์๊ฐ (bio, textarea)
- ํ๋กํ ์ฌ์ง ์
๋ก๋ (Livewire File Upload)
- ์์
๋งํฌ (LinkedIn, GitHub, ์น์ฌ์ดํธ)
- ํ๋กํ ๊ณต๊ฐ/๋น๊ณต๊ฐ ํ ๊ธ
- ์ค์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ (์ฌ์ฉ์๋ช
์ค๋ณต ์ฒดํฌ)
- ํ๋กํ ์ฌ์ง์ Storage์ ์ ์ฅ
## 2. ๊ณต๊ฐ ํ๋กํ ํ์ด์ง
- ๋ผ์ฐํธ: `/profile/{username}` (์ธ์ฆ ๋ถํ์, ๋๊ตฌ๋ ์ ๊ทผ ๊ฐ๋ฅ)
- Blade ๋ทฐ: `resources/views/profiles/show.blade.php`
- ๋ ์ด์์:
- ์๋จ: ํ๋กํ ์ฌ์ง, ์ด๋ฆ, ์๊ธฐ์๊ฐ
- ์์
๋งํฌ ์์ด์ฝ (LinkedIn, GitHub, ์น์ฌ์ดํธ)
- ํ๋จ: ์ทจ๋ํ ์๊ฒฉ์ฆ ๊ฐค๋ฌ๋ฆฌ (๊ทธ๋ฆฌ๋)
- ์๊ฒฉ์ฆ ์นด๋:
- ๊ณผ์ ๋ช
- ๋ฐ๊ธ์ผ
- ๋ฐ๊ธ ๊ธฐ๊ด ๋ก๊ณ
- "๊ฒ์ฆํ๊ธฐ" ๋ฒํผ (ํด๋ฆญ ์ `/verify/{certificate_number}`๋ก ์ด๋)
- ํ๋กํ์ด ๋น๊ณต๊ฐ์ธ ๊ฒฝ์ฐ 404 ํ์ด์ง ํ์
## 3. ํ๋กํ ๊ณต์ ๊ธฐ๋ฅ
- ํ์ ๋์๋ณด๋์ "๋ด ํ๋กํ ๊ณต์ " ๋ฒํผ ์ถ๊ฐ
- ํด๋ฆญ ์ ๊ณต๊ฐ ํ๋กํ URL ๋ณต์ฌ (Clipboard API)
- ํ ์คํธ ๋ฉ์์ง: "ํ๋กํ ๋งํฌ๊ฐ ๋ณต์ฌ๋์์ต๋๋ค!"
## 4. SEO ์ต์ ํ
- ๊ณต๊ฐ ํ๋กํ ํ์ด์ง์ ๋ฉํ ํ๊ทธ ์ถ๊ฐ
- Open Graph ํ๊ทธ: ํ๋กํ ์ฌ์ง, ์ด๋ฆ, ์๊ธฐ์๊ฐ
- Twitter Card ํ๊ทธ
## 5. ๋์์ธ
- ๊น๋ํ๊ณ ์ ๋ฌธ์ ์ธ ํฌํธํด๋ฆฌ์ค ์คํ์ผ
- ๋ฐ์ํ ๋์์ธ (๋ชจ๋ฐ์ผ ์นํ์ )
- Tailwind CSS๋ก ๊ทธ๋ผ๋ฐ์ด์
๋ฐฐ๊ฒฝ, ์นด๋ ๊ทธ๋ฆผ์ ํจ๊ณผ ๋ฑ ํ์ฉ
์๋ฃ ํ ํ๋กํ์ ๊ณต๊ฐ๋ก ์ค์ ํ๊ณ `/profile/{username}` URL์ ๋ธ๋ผ์ฐ์ ์์ ์ด์ด ์ ์ ํ์๋๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ์๋ฃ์์ ๊ณต๊ฐ ํ๋กํ ํ์ด์ง๊ฐ ๊ตฌ์ถ๋๊ณ , ์๊ฒฉ์ฆ ๊ฐค๋ฌ๋ฆฌ๊ฐ ํฌํธํด๋ฆฌ์ค ์คํ์ผ๋ก ํ์๋ฉ๋๋ค. ํ๋กํ ํธ์ง ํ์ด์ง์์ ์ฌ์ฉ์๋ช , ์๊ธฐ์๊ฐ, ์์ ๋งํฌ๋ฅผ ์ค์ ํ ์ ์์ผ๋ฉฐ, ๊ณต๊ฐ/๋น๊ณต๊ฐ ์ค์ ์ด ์๋ํฉ๋๋ค.
# ๋จ๊ณ 8: ์ด๋ฉ์ผ ์๋ ๋ฐ์ก ์์คํ
(Queue)
์๊ฒฉ์ฆ ๋ฐ๊ธ ์ ์๋ฃ์์๊ฒ ์๋์ผ๋ก ์ด๋ฉ์ผ์ ๋ฐ์กํ๋ ๋น๋๊ธฐ ์์คํ
์ ๊ตฌํํ์ธ์.
## 1. Queue ์ค์
- `.env` ํ์ผ์์ `QUEUE_CONNECTION=database` ์ค์ (๋๋ Redis ์ฌ์ฉ)
- Queue ํ
์ด๋ธ ๋ง์ด๊ทธ๋ ์ด์
์์ฑ: `php artisan queue:table`
- ๋ง์ด๊ทธ๋ ์ด์
์คํ
## 2. Mailable ํด๋์ค ์์ฑ
- `php artisan make:mail CertificateIssued`
- ์ด๋ฉ์ผ ๋ด์ฉ:
- ์ ๋ชฉ: "์ถํํฉ๋๋ค! {๊ณผ์ ๋ช
} ์๊ฒฉ์ฆ์ด ๋ฐ๊ธ๋์์ต๋๋ค."
- ๋ณธ๋ฌธ:
- ์๋ฃ์ ์ด๋ฆ ์ธ์ฌ
- ๊ณผ์ ๋ช
๋ฐ ๋ฐ๊ธ์ผ
- ์ธ์ฆ๋ฒํธ
- PDF ๋ค์ด๋ก๋ ๋งํฌ (๋ง์ดํ์ด์ง)
- ๊ณต๊ฐ ๊ฒ์ฆ ๋งํฌ
- ๋ฐ๊ธ ๊ธฐ๊ด ์ฐ๋ฝ์ฒ
- Blade ์ด๋ฉ์ผ ํ
ํ๋ฆฟ ์์ฑ: `resources/views/emails/certificate-issued.blade.php`
- Tailwind CSS ์ธ๋ผ์ธ ์คํ์ผ๋ก ๋์์ธ (์ด๋ฉ์ผ ํด๋ผ์ด์ธํธ ํธํ)
## 3. Job ํด๋์ค ์์ฑ
- `php artisan make:job SendCertificateEmail`
- Job์์ Mailable ์ ์ก
- PDF ํ์ผ์ ์ด๋ฉ์ผ์ ์ฒจ๋ถ (์ ํ์ฌํญ)
## 4. ์๊ฒฉ์ฆ ๋ฐ๊ธ ์ Job ๋์คํจ์น
- `CertificateController`์ ๋ฐ๊ธ ๋ก์ง์์ `SendCertificateEmail::dispatch($certificate)` ์ถ๊ฐ
- Queue์ Job์ด ์ถ๊ฐ๋๋์ง ํ์ธ
## 5. Queue Worker ์คํ
- `php artisan queue:work` ๋ช
๋ น์ผ๋ก Queue Worker ์คํ
- Job์ด ์ฒ๋ฆฌ๋๊ณ ์ด๋ฉ์ผ์ด ๋ฐ์ก๋๋์ง ํ์ธ
- ๋ก๊ทธ์์ ์ด๋ฉ์ผ ๋ฐ์ก ํ์ธ (๊ฐ๋ฐ ํ๊ฒฝ์์๋ Mailtrap ์ฌ์ฉ ๊ถ์ฅ)
## 6. ์ด๋ฉ์ผ ๋ฐ์ก ์ด๋ ฅ
- `email_logs` ํ
์ด๋ธ ์์ฑ (์ ํ์ฌํญ)
- ๋ฐ์ก ์ฑ๊ณต/์คํจ ๊ธฐ๋ก
- ๊ด๋ฆฌ์ ๋์๋ณด๋์์ ์ด๋ฉ์ผ ๋ฐ์ก ์ด๋ ฅ ํ์ธ
## 7. ์๋ฌ ํธ๋ค๋ง
- Job ์คํจ ์ ์ฌ์๋ ์ค์ (์ต๋ 3ํ)
- ์คํจํ Job์ `failed_jobs` ํ
์ด๋ธ์ ๊ธฐ๋ก
์๋ฃ ํ ํ
์คํธ ์๊ฒฉ์ฆ์ ๋ฐ๊ธํ๊ณ , ์๋ฃ์ ์ด๋ฉ์ผ๋ก ์๋ ๋ฐ์ก๋๋์ง ํ์ธํ์ธ์. Mailtrap ๋๋ ์ค์ ์ด๋ฉ์ผ ๊ณ์ ์ผ๋ก ํ
์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ์๊ฒฉ์ฆ ๋ฐ๊ธ ์ Queue๋ฅผ ํตํด ๋น๋๊ธฐ๋ก ์ด๋ฉ์ผ์ด ๋ฐ์ก๋ฉ๋๋ค. ์ด๋ฉ์ผ์๋ ์ถํ ๋ฉ์์ง, ์ธ์ฆ๋ฒํธ, PDF ๋ค์ด๋ก๋ ๋งํฌ, ๊ฒ์ฆ ๋งํฌ๊ฐ ํฌํจ๋๋ฉฐ, Queue Worker๊ฐ ์ ์ ์๋ํฉ๋๋ค.
# ๋จ๊ณ 9: ์๊ฒฉ์ฆ ๋ง๋ฃ ์๋ฆผ ๋ฐ ํต๊ณ ๋์๋ณด๋
์๊ฒฉ์ฆ ๋ง๋ฃ์ผ์ด ๊ฐ๊น์์ง ์๋ฃ์์๊ฒ ์๋ ์๋ฆผ์ ๋ณด๋ด๊ณ , ๊ด๋ฆฌ์๊ฐ ๋ฐ๊ธ ํต๊ณ๋ฅผ ํ์ธํ ์ ์๋ ๋์๋ณด๋๋ฅผ ๊ตฌํํ์ธ์.
## 1. Laravel Scheduler ์ค์
- `app/Console/Kernel.php`์์ ์ค์ผ์ค ์ ์
- ๋งค์ผ ์์ ์ ๋ง๋ฃ ์์ ์๊ฒฉ์ฆ ํ์ธ (๋ง๋ฃ 7์ผ ์ )
- Artisan Command ์์ฑ: `php artisan make:command SendExpirationReminders`
- Command ๋ก์ง:
- ๋ง๋ฃ 7์ผ ์ ์ธ ์๊ฒฉ์ฆ ์กฐํ
- ๊ฐ ์๋ฃ์์๊ฒ ์๋ฆผ ์ด๋ฉ์ผ ๋ฐ์ก (Queue ์ฌ์ฉ)
## 2. ๋ง๋ฃ ์๋ฆผ Mailable
- `php artisan make:mail CertificateExpirationReminder`
- ์ด๋ฉ์ผ ๋ด์ฉ:
- ์ ๋ชฉ: "์๊ฒฉ์ฆ ๋ง๋ฃ ์๋ฆผ: {๊ณผ์ ๋ช
} ์๊ฒฉ์ฆ์ด 7์ผ ํ ๋ง๋ฃ๋ฉ๋๋ค."
- ๋ณธ๋ฌธ:
- ๋ง๋ฃ์ผ ์๋ด
- ์ฌ๋ฐ๊ธ ๋๋ ๊ฐฑ์ ์๋ด (ํ์ ์)
- ๋ฌธ์ ์ฐ๋ฝ์ฒ
## 3. Cron ์ค์
- ์๋ฒ์์ Laravel Scheduler ์คํ: `* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1`
- ๋ก์ปฌ ํ๊ฒฝ์์๋ `php artisan schedule:work`๋ก ํ
์คํธ
## 4. ๊ด๋ฆฌ์ ํต๊ณ ๋์๋ณด๋
- `/admin/dashboard`: ํต๊ณ ์์ฝ ํ์ด์ง
- ํ์ ์ ๋ณด:
- ์ด ๋ฐ๊ธ๋ ์๊ฒฉ์ฆ ์
- ํ์ฑ ์๊ฒฉ์ฆ ์
- ๋ง๋ฃ๋ ์๊ฒฉ์ฆ ์
- ์ต๊ทผ 7์ผ๊ฐ ๋ฐ๊ธ ์ถ์ด (Chart.js ๋ผ์ธ ์ฐจํธ)
- ํ
ํ๋ฆฟ๋ณ ๋ฐ๊ธ ํํฉ (Chart.js ํ์ด ์ฐจํธ)
- Livewire ์ปดํฌ๋ํธ๋ก ์ค์๊ฐ ์
๋ฐ์ดํธ
## 5. Chart.js ํตํฉ
- `npm install chart.js` ์ค์น
- Blade ๋ทฐ์์ Chart.js ์ฌ์ฉ
- ์ปจํธ๋กค๋ฌ์์ ํต๊ณ ๋ฐ์ดํฐ๋ฅผ JSON์ผ๋ก ์ ๋ฌ
- ๋ฐ์ํ ์ฐจํธ (๋ชจ๋ฐ์ผ ์ต์ ํ)
## 6. ํต๊ณ ๋ฐ์ดํฐ ์บ์ฑ
- ํต๊ณ ๋ฐ์ดํฐ๋ฅผ Redis์ ์บ์ฑ (1์๊ฐ)
- ์บ์ ๋ฌดํจํ: ์๊ฒฉ์ฆ ๋ฐ๊ธ ์
์๋ฃ ํ ๊ด๋ฆฌ์ ๋์๋ณด๋์์ ํต๊ณ ์ฐจํธ๊ฐ ์ ์ ํ์๋๋์ง ํ์ธํ๊ณ , Scheduler ๋ช
๋ น์ ์๋ ์คํํ์ฌ ๋ง๋ฃ ์๋ฆผ์ด ๋ฐ์ก๋๋์ง ํ
์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel Scheduler๋ฅผ ํตํด ๋งค์ผ ์๋์ผ๋ก ๋ง๋ฃ ์์ ์๊ฒฉ์ฆ์ ํ์ธํ๊ณ ์๋ฆผ ์ด๋ฉ์ผ์ ๋ฐ์กํฉ๋๋ค. ๊ด๋ฆฌ์ ๋์๋ณด๋์๋ ๋ฐ๊ธ ํต๊ณ๊ฐ Chart.js๋ก ์๊ฐํ๋๋ฉฐ, ์ค์๊ฐ ์ ๋ฐ์ดํธ๋ฉ๋๋ค.
# ๋จ๊ณ 10: ๋ณด์ ๊ฐํ, ํ
์คํธ, ํ๋ก๋์
๋ฐฐํฌ ์ค๋น
ํ๋ก์ ํธ์ ๋ณด์์ ๊ฐํํ๊ณ , ํ
์คํธ๋ฅผ ์์ฑํ๋ฉฐ, ํ๋ก๋์
ํ๊ฒฝ์ ๋ฐฐํฌํ ์ค๋น๋ฅผ ์๋ฃํ์ธ์.
## 1. ๋ณด์ ๊ฐํ
- **CSRF ๋ณดํธ**: ๋ชจ๋ ํผ์ `@csrf` ํ ํฐ ํ์ธ
- **XSS ๋ฐฉ์ง**: Blade์์ `{{ }}` ์ฌ์ฉ (์๋ ์ด์ค์ผ์ดํ)
- **SQL Injection ๋ฐฉ์ง**: Eloquent ORM ์ฌ์ฉ (Raw Query ๊ธ์ง)
- **ํ์ผ ์
๋ก๋ ๊ฒ์ฆ**:
- ํ์ฉ๋ MIME ํ์
๋ง ์
๋ก๋ (์ด๋ฏธ์ง: jpg, png)
- ํ์ผ ํฌ๊ธฐ ์ ํ (์ต๋ 2MB)
- **Rate Limiting**:
- ๊ฒ์ฆ ํ์ด์ง์ Rate Limit ์ ์ฉ (1๋ถ์ 10ํ)
- API ๋ผ์ฐํธ์ Throttle ๋ฏธ๋ค์จ์ด ์ ์ฉ
- **ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ**:
- `.env` ํ์ผ์ `.gitignore`์ ์ถ๊ฐ
- ๋ฏผ๊ฐํ ์ ๋ณด (API ํค, DB ๋น๋ฐ๋ฒํธ) ์ํธํ
## 2. Policy ๋ฐ Authorization
- `CertificatePolicy` ์์ฑ
- `view`, `download` ๋ฉ์๋ ์ ์ (์์ ์ ์๊ฒฉ์ฆ๋ง ์ ๊ทผ ๊ฐ๋ฅ)
- ์ปจํธ๋กค๋ฌ์์ `$this->authorize('view', $certificate)` ์ฌ์ฉ
## 3. ์๋ฌ ํธ๋ค๋ง ๋ฐ ๋ก๊น
- 404, 403, 500 ์๋ฌ ํ์ด์ง ์ปค์คํฐ๋ง์ด์ง
- `app/Exceptions/Handler.php`์์ ์๋ฌ ๋ก๊น
์ค์
- Sentry ๋๋ Bugsnag ์ฐ๋ (์ ํ์ฌํญ)
## 4. ํ
์คํธ ์์ฑ
- Feature ํ
์คํธ:
- ์๊ฒฉ์ฆ ๋ฐ๊ธ ํ
์คํธ
- ๊ฒ์ฆ ํ์ด์ง ํ
์คํธ
- ํ๋กํ ๊ณต๊ฐ/๋น๊ณต๊ฐ ํ
์คํธ
- Unit ํ
์คํธ:
- ๊ฒ์ฆ ํด์ ์์ฑ ๋ก์ง ํ
์คํธ
- UUID ์์ฑ ํ
์คํธ
- `php artisan test` ์คํํ์ฌ ๋ชจ๋ ํ
์คํธ ํต๊ณผ ํ์ธ
## 5. ์ฑ๋ฅ ์ต์ ํ
- **Eager Loading**: N+1 ์ฟผ๋ฆฌ ๋ฌธ์ ํด๊ฒฐ (`with()` ์ฌ์ฉ)
- **์ธ๋ฑ์ค ์ถ๊ฐ**: `certificate_number`, `username` ์ปฌ๋ผ์ ์ธ๋ฑ์ค
- **์บ์ฑ**: ํต๊ณ ๋ฐ์ดํฐ, ํ
ํ๋ฆฟ ๋ชฉ๋ก ์บ์ฑ
- **์ด๋ฏธ์ง ์ต์ ํ**: ์
๋ก๋๋ ์ด๋ฏธ์ง ์๋ ๋ฆฌ์ฌ์ด์ง (Intervention Image)
## 6. ํ๋ก๋์
๋ฐฐํฌ ์ค๋น
- `php artisan config:cache` (์ค์ ์บ์ฑ)
- `php artisan route:cache` (๋ผ์ฐํธ ์บ์ฑ)
- `php artisan view:cache` (๋ทฐ ์บ์ฑ)
- `.env` ํ์ผ์์ `APP_DEBUG=false`, `APP_ENV=production` ์ค์
- HTTPS ๊ฐ์ ์ค์ (AppServiceProvider์์ URL::forceScheme('https'))
- Queue Worker๋ฅผ Supervisor๋ก ๊ด๋ฆฌ
## 7. ๋ฌธ์ํ
- `README.md` ์์ฑ:
- ํ๋ก์ ํธ ์๊ฐ
- ์ค์น ๋ฐฉ๋ฒ
- ํ๊ฒฝ ๋ณ์ ์ค์
- ์คํ ๋ช
๋ น์ด
- ์คํฌ๋ฆฐ์ท
- API ๋ฌธ์ ์์ฑ (ํ์ ์)
์๋ฃ ํ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ต์ข
ํ
์คํธํ๊ณ , ํ๋ก๋์
ํ๊ฒฝ์ ๋ฐฐํฌํ ์ค๋น๊ฐ ์๋ฃ๋์๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ณด์์ด ๊ฐํ๋๊ณ , Policy๋ฅผ ํตํ ๊ถํ ๊ด๋ฆฌ๊ฐ ์ ์ฉ๋๋ฉฐ, ํ ์คํธ๊ฐ ์์ฑ๋์ด ๋ชจ๋ ํ ์คํธ๊ฐ ํต๊ณผํฉ๋๋ค. ์ฑ๋ฅ ์ต์ ํ๊ฐ ์๋ฃ๋๊ณ , ํ๋ก๋์ ๋ฐฐํฌ๋ฅผ ์ํ ์บ์ฑ ๋ฐ ํ๊ฒฝ ์ค์ ์ด ์๋ฃ๋ฉ๋๋ค. README.md ๋ฌธ์๊ฐ ์์ฑ๋์ด ํ๋ก์ ํธ๊ฐ ์์ฑ๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!