117 lines
5.3 KiB
C#
117 lines
5.3 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace GameServer.Data
|
|
{
|
|
/// <summary>
|
|
/// 포트폴리오 전용 테이블(tb_portfolio_*)을 서버 기동 시점에 보장한다.
|
|
/// CREATE TABLE IF NOT EXISTS 를 사용하므로 몇 번을 실행해도 안전(멱등)하며,
|
|
/// DB 장애가 게임 서버 기동을 막지 않도록 어떤 예외도 밖으로 던지지 않는다.
|
|
/// </summary>
|
|
public static class PortfolioSchemaInitializer
|
|
{
|
|
// 카테고리 테이블
|
|
private const string CreateCategoryTableSql = @"
|
|
CREATE TABLE IF NOT EXISTS tb_portfolio_category (
|
|
category_no INT AUTO_INCREMENT PRIMARY KEY,
|
|
slug VARCHAR(64) NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
description VARCHAR(255) NULL,
|
|
accent VARCHAR(32) NULL,
|
|
sort_order INT NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uk_portfolio_category_slug (slug)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
|
|
|
// 게시물 테이블
|
|
private const string CreatePostTableSql = @"
|
|
CREATE TABLE IF NOT EXISTS tb_portfolio_post (
|
|
post_no INT AUTO_INCREMENT PRIMARY KEY,
|
|
category_no INT NOT NULL,
|
|
title VARCHAR(200) NOT NULL,
|
|
summary VARCHAR(500) NULL,
|
|
content_md MEDIUMTEXT NULL,
|
|
thumbnail_url VARCHAR(500) NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY ix_portfolio_post_category (category_no),
|
|
CONSTRAINT fk_portfolio_post_category FOREIGN KEY (category_no)
|
|
REFERENCES tb_portfolio_category (category_no) ON DELETE RESTRICT
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
|
|
|
// 게시물 이미지 테이블
|
|
private const string CreatePostImageTableSql = @"
|
|
CREATE TABLE IF NOT EXISTS tb_portfolio_post_image (
|
|
image_no INT AUTO_INCREMENT PRIMARY KEY,
|
|
post_no INT NOT NULL,
|
|
image_url VARCHAR(500) NOT NULL,
|
|
caption VARCHAR(255) NULL,
|
|
sort_order INT NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY ix_portfolio_post_image_post (post_no),
|
|
CONSTRAINT fk_portfolio_post_image_post FOREIGN KEY (post_no)
|
|
REFERENCES tb_portfolio_post (post_no) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
|
|
|
/// <summary>
|
|
/// 테이블 생성 + (비어 있을 때만) 기본 카테고리 4건 시딩.
|
|
/// 실패해도 예외를 던지지 않고 로그만 남긴다.
|
|
/// </summary>
|
|
public static async Task EnsureCreatedAsync(IServiceProvider services, ILogger logger)
|
|
{
|
|
try
|
|
{
|
|
using var scope = services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
|
|
// 문장 하나씩 실행 (MySql 커맨드는 다중 문장을 한 번에 처리하지 않는 경우가 있다)
|
|
await db.Database.ExecuteSqlRawAsync(CreateCategoryTableSql);
|
|
await db.Database.ExecuteSqlRawAsync(CreatePostTableSql);
|
|
await db.Database.ExecuteSqlRawAsync(CreatePostImageTableSql);
|
|
|
|
var categoryCount = await db.PortfolioCategories.CountAsync();
|
|
if (categoryCount == 0)
|
|
{
|
|
await SeedCategoriesAsync(db);
|
|
logger.LogInformation("포트폴리오 기본 카테고리 4건을 시딩했습니다.");
|
|
}
|
|
|
|
logger.LogInformation("포트폴리오 스키마 준비 완료 (카테고리 {Count}건).",
|
|
categoryCount == 0 ? 4 : categoryCount);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// 스키마 준비 실패가 게임 서버 기동을 막으면 안 되므로 여기서 삼킨다.
|
|
logger.LogError(ex, "포트폴리오 스키마 초기화에 실패했습니다. 포트폴리오 API가 동작하지 않을 수 있습니다.");
|
|
}
|
|
}
|
|
|
|
// 기본 카테고리 4건 (카테고리 테이블이 완전히 비어 있을 때만 호출된다)
|
|
private static async Task SeedCategoriesAsync(AppDbContext db)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
|
|
await InsertCategoryAsync(db, "game", "게임", "Unity 게임 프로젝트", "#7c5cff", 1, now);
|
|
await InsertCategoryAsync(db, "dev", "개발", "서버 · 툴 · 시스템", "#20d5c4", 2, now);
|
|
await InsertCategoryAsync(db, "art", "아트", "그래픽 · UI · 연출", "#ff5c9a", 3, now);
|
|
await InsertCategoryAsync(db, "log", "기록", "작업 일지와 회고", "#ffb74d", 4, now);
|
|
}
|
|
|
|
// 값은 반드시 파라미터로 전달한다 (문자열 결합 금지)
|
|
private static async Task InsertCategoryAsync(
|
|
AppDbContext db,
|
|
string slug,
|
|
string name,
|
|
string description,
|
|
string accent,
|
|
int sortOrder,
|
|
DateTime createdAt)
|
|
{
|
|
await db.Database.ExecuteSqlInterpolatedAsync(
|
|
$@"INSERT INTO tb_portfolio_category (slug, name, description, accent, sort_order, created_at)
|
|
VALUES ({slug}, {name}, {description}, {accent}, {sortOrder}, {createdAt})");
|
|
}
|
|
}
|
|
}
|