기본 콘텐츠로 건너뛰기

Pinned Post

부산 상어 '부캉이' 어디로 갔을까? 나흘째 모습 감춰…당국, 현장 모니터링 중단

NEWS · 부산 · 해양 생태 부산 상어 '부캉이' 어디로 갔을까? 나흘째 모습 감춰…당국, 현장 모니터링 중단 한 달 넘게 부산 북항 친수공원 인공 수로에 머물렀던 상어 부캉이가 바다로 나간 뒤 모습을 드러내지 않고 있다. 당국은 매일 진행하던 현장 관찰을 중단하고 시민 제보에 대응하는 방식으로 전환했다. 작성 기준일 : 2026년 10월 30일 원문 송고 : 2026년 10월 30일 15시 52분 출처 : 연합뉴스 이번 소식의 핵심 부캉이는 10월 21일 당국의 외해 유도 작업으로 인공 수로를 벗어났다. 10월 24일에는 국제여객터미널 앞바다에서 목격됐다. 10월 27일부터 당국의 모니터링에 포착되지 않고 있다. 국립수산과학원은 10월 30일부터 매일 진행하던 현장 모니터링을 중단했다. 앞으로는 시민 제보가 접수되면 비상 대기조가 현장에 출동한다. 1. 한 달 넘게 머물던 수로를 떠난 부캉이 부산 북항 친수공원 인공 수로에 한 달 넘게 머물렀던 상어 '부캉이'가 바다로 나간 뒤 좀처럼 모습을 드러내지 않고 있다. 부캉이는 10월 21일 당국의 외해 유도 작업을 통해 친수공원 수로 밖으로 이동했다. 이후 수로와 가까운 제1부두 앞바다에서 모습을 보였고, 10월 24일에는 기존 장소에서 약 2km 떨어진 국제여객터미널 앞바다에서도 목격됐다. 처음에는 북항 주변에서 이동하는 모습이 확인됐지만, 10월 27일부터는 당국의 모니터링에서도 더 이상 발견되지 않았다. 당국은 부캉이를 찾기 위해 제5부두 등으로 관찰 범위를 넓혔다. 그러나 추가 목격은 없었다. 27일에는 시민 제보도 접수됐지만, 이후에는 별다른 제보가 들어오지 않은 상황이다. 2. 10월 30일부터 현장 모니터링 방식 변경 국립수...

2편. PostgreSQL 연결과 ASP.NET Core CRUD 구현

실무 실습 가이드 · 기준일: 2026년 10월 30일

2편. PostgreSQL 연결과 ASP.NET Core CRUD 구현

PostgreSQL, EF Core, CRUD를 실습합니다.

1. 패키지 설치

프로젝트 폴더에서 EF Core와 PostgreSQL 공급자를 설치합니다. .NET 10과 호환되는 패키지 버전을 맞춥니다.

Set-Location D:\Dev\DockerCourse\TaskApi
dotnet add package Microsoft.OpenApi --version 2.13.0
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL --version 10.0.3
dotnet add package Microsoft.EntityFrameworkCore.Design --version 10.0.14
dotnet tool install --global dotnet-ef --version 10.0.14

EF CLI가 이미 있다면 마지막 명령 대신 dotnet tool update --global dotnet-ef --version 10.0.14를 실행합니다.

2. 모델과 DbContext

Models, Data 폴더를 만들고 파일을 추가합니다.

New-Item -ItemType Directory -Force Models,Data | Out-Null

Models/TaskItem.cs

namespace TaskApi.Models;
public sealed class TaskItem { public int Id { get; set; } public string Title { get; set; } = string.Empty; public bool IsCompleted { get; set; } }

Models/TaskRequests.cs

namespace TaskApi.Models;
public sealed class CreateTaskRequest { public string? Title { get; init; } }
public sealed class UpdateTaskRequest { public string? Title { get; init; } public bool IsCompleted { get; init; } }

Data/TaskDbContext.cs

using Microsoft.EntityFrameworkCore;
using TaskApi.Models;
namespace TaskApi.Data;
public sealed class TaskDbContext(DbContextOptions<TaskDbContext> options) : DbContext(options)
{
 public DbSet<TaskItem> Tasks => Set<TaskItem>();
 protected override void OnModelCreating(ModelBuilder m) {
  var e=m.Entity<TaskItem>(); e.ToTable("tasks"); e.HasKey(x=>x.Id);
  e.Property(x=>x.Title).HasMaxLength(200).IsRequired();
  e.Property(x=>x.IsCompleted).HasDefaultValue(false).IsRequired();
 }
}

3. CRUD API

기존 Program.cs를 교체합니다. 제목은 필수이며 200자 이하여야 합니다.

using Microsoft.EntityFrameworkCore;
using TaskApi.Data;
using TaskApi.Models;
var b=WebApplication.CreateBuilder(args);
b.Services.AddHealthChecks();
b.Services.AddDbContext<TaskDbContext>(o=>o.UseNpgsql(b.Configuration.GetConnectionString("DefaultConnection")!));
var app=b.Build();
app.MapGet("/",()=>Results.Ok(new {service="TaskApi",framework=".NET 10",status="running"}));
app.MapHealthChecks("/health");
app.MapGet("/api/tasks",async (TaskDbContext d)=>Results.Ok(await d.Tasks.AsNoTracking().OrderBy(x=>x.Id).ToListAsync()));
app.MapGet("/api/tasks/{id:int}",async (int id,TaskDbContext d)=>{var t=await d.Tasks.FindAsync(id);return t is null?Results.NotFound():Results.Ok(t);});
app.MapPost("/api/tasks",async (CreateTaskRequest r,TaskDbContext d)=>{var title=r.Title?.Trim();if(string.IsNullOrWhiteSpace(title)||title.Length>200)return Results.BadRequest(new{message="Title required; max 200 characters."});var t=new TaskItem{Title=title};d.Add(t);await d.SaveChangesAsync();return Results.Created($"/api/tasks/{t.Id}",t);});
app.MapPut("/api/tasks/{id:int}",async (int id,UpdateTaskRequest r,TaskDbContext d)=>{var t=await d.Tasks.FindAsync(id);if(t is null)return Results.NotFound();var title=r.Title?.Trim();if(string.IsNullOrWhiteSpace(title)||title.Length>200)return Results.BadRequest();t.Title=title;t.IsCompleted=r.IsCompleted;await d.SaveChangesAsync();return Results.Ok(t);});
app.MapDelete("/api/tasks/{id:int}",async (int id,TaskDbContext d)=>{var t=await d.Tasks.FindAsync(id);if(t is null)return Results.NotFound();d.Remove(t);await d.SaveChangesAsync();return Results.NoContent();});
app.Run();

4. 비밀 값과 Compose 설정

프로젝트 루트에 .env를 만듭니다. 아래 비밀번호는 로컬 학습용 예시이며 운영에 재사용하지 않습니다. .gitignore에는 .env와 backups/를 추가합니다.

POSTGRES_DB=taskdb
POSTGRES_USER=taskuser
POSTGRES_PASSWORD=TaskApi-local-Only-ChangeMe-2026!

compose.yaml

services:
  api:
    build: .
    ports: ["127.0.0.1:5080:8080"]
    environment:
      ASPNETCORE_ENVIRONMENT: Production
      ASPNETCORE_HTTP_PORTS: "8080"
      ConnectionStrings__DefaultConnection: "Host=db;Port=5432;Database=${POSTGRES_DB:?set .env};Username=${POSTGRES_USER:?set .env};Password=${POSTGRES_PASSWORD:?set .env}"
    depends_on:
      db: {condition: service_healthy}
    restart: unless-stopped
  db:
    image: postgres:18
    environment:
      POSTGRES_DB: ${POSTGRES_DB:?set .env}
      POSTGRES_USER: ${POSTGRES_USER:?set .env}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set .env}
    ports: ["127.0.0.1:5433:5432"]
    volumes: ["postgres_data:/var/lib/postgresql"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 10s
    restart: unless-stopped
volumes:
  postgres_data:

API는 db:5432, Windows 마이그레이션은 localhost:5433으로 연결합니다.

5. 마이그레이션과 API 테스트

DB를 실행하고 상태를 확인합니다.

docker compose config --services
docker compose up -d db
docker compose exec db pg_isready -U taskuser -d taskdb
docker compose exec db psql -U taskuser -d taskdb -c "SELECT current_database(), current_user;"

마이그레이션용 연결 문자열을 현재 PowerShell 세션에 설정합니다.

$e=@{}; Get-Content .env | ForEach-Object { if ($_ -match '^\s*([^#=]+)\s*=\s*(.*?)\s*$') { $e[$matches[1].Trim()]=$matches[2].Trim() } }
$env:ConnectionStrings__DefaultConnection="Host=localhost;Port=5433;Database=$($e['POSTGRES_DB']);Username=$($e['POSTGRES_USER']);Password=$($e['POSTGRES_PASSWORD'])"
dotnet build
dotnet ef migrations add InitialCreate
dotnet ef database update
docker compose exec db psql -U taskuser -d taskdb -c "\dt"

API를 다시 빌드하고 CRUD를 테스트합니다.

docker compose up --build -d api
curl.exe -i http://localhost:5080/health
curl.exe -i http://localhost:5080/api/tasks
$t=Invoke-RestMethod -Uri http://localhost:5080/api/tasks -Method Post -ContentType application/json -Body (@{title='Docker CRUD 실습'} | ConvertTo-Json -Compress)
$id=$t.id
Invoke-RestMethod "http://localhost:5080/api/tasks/$id"
Invoke-RestMethod -Uri "http://localhost:5080/api/tasks/$id" -Method Put -ContentType application/json -Body (@{title='수정 완료';isCompleted=$true} | ConvertTo-Json -Compress)
docker compose exec db psql -U taskuser -d taskdb -c 'SELECT "Id","Title","IsCompleted" FROM tasks ORDER BY "Id";'
curl.exe -i -X DELETE "http://localhost:5080/api/tasks/$id"

정상 결과: POST는 201, GET·PUT은 200, DELETE는 204가 정상입니다. DB 결과도 비교합니다.

댓글

이 블로그의 인기 게시물

ChatGpt 의 유료결제 영수증 및 청구서 다운로드

ChatGpt 의 유료결제 영수증 및 청구서 다운로드   https://chatgpt.com/  사이트에 접속하며 회원가입을 합니다.   ChatGpt 유료 결제를 위해서는 왼쪽 하단에 있는 " Team 워크스페이스 추가 "를 선택합니다.   https://chatgpt.com/     ChatGPT의 요금제는 세 가지로 나뉩니다:  Free ,  Plus , 그리고  Custom Plan (기업용 요금제). 각 요금제의 특징과 차이점은 다음과 같습니다. 1.  Free Plan (무료 플랜) 사용 모델:  GPT-3.5 제공 기능:  기본적인 질문 응답, 텍스트 생성 접속 가능성:  트래픽이 많을 때는 사용이 제한될 수 있으며, 응답 속도가 느릴 수 있습니다. 제한 사항:  최신 모델이나 고급 기능을 사용할 수 없고, 성능이나 속도 면에서 제한이 있습니다. 2.  Plus Plan (플러스 플랜) 월 요금:  $20 사용 모델:  GPT-4 장점:  더 빠른 응답 속도, 트래픽이 많을 때도 안정적인 접속 가능 차이점:  GPT-4의 향상된 성능으로 더 복잡한 질문이나 고급 작업에도 우수한 결과를 제공합니다. 접속 가능성:  트래픽이 많아도 안정적이며, 더 나은 응답 속도를 제공합니다. 3.  Custom Plan (맞춤형 플랜) 대상:  대규모 기업 또는 특정 요구사항이 있는 고객 요금:  고객의 요구에 따라 맞춤 설정 특징:  기업 맞춤형 기능과 성능을 제공하며, 보안, 데이터 정책, API 액세스와 같은 맞춤형 옵션을 포함할 수 있습니다. 차이점:  대규모 기업에 특화된 기능으로, 추가 지원과 고급 기능 제공이 가능합니다. 주요 차이점 모델 사용:  Free는 GPT-3.5,...

인천국제공항 제1여객터미널에서 일본으로 가는 출국 절차 안내 ✈️

인천공항 제1여객터미널 출국 절차 가이드 (탑승동 이동 및 셔틀트레인 완벽 정리) 공항 도착부터 체크인, 보안검색, 출국심사, 그리고 셔틀트레인을 타고 탑승동으로 이동하는 전체 출국 단계를 빠짐없이 안내합니다. 1. 공항 도착 시간 항공기 출발 3시간 전 제1여객터미널 3층 출국장 도착 권장 2. 탑승구 확인 101~132번 게이트는 셔틀트레인을 타고 탑승동 으로 이동 필수 3. 되돌아오기 불가 셔틀트레인은 편도 이동만 가능하므로 본동 면세품 인도 후 탑승 Step 1. 공항 도착 및 체크인 (3층 일반지역) 항공기 출발 3시간 전 공항 3층 출국장에 도착하는 것을 권장합니다. 셀프 체크인 & 자동 수하물 위탁(Self Bag-Drop): 키오스크에서 모바일·지류 탑승권을 발급받고 전용 카운터를 이용하면 대기시간을 크게 단축할 수 있습니다. 유인 카운터: 여권과 항공권(e-티켓)을 제시하여 좌석 배정 및 위탁 수하물을 처리합니다. Step 2. 출국 전 사전 준비 (환전 및 통신) 환전 수령: 사전 신청한 외화를 3층 또는 지하 1층 환전소·은행 영업점에서 수령합니다. 로밍 및 통신: 통신사 부스에서 데이터 로밍 확인 또는 신청한 USIM/eSIM/와이파이 단말기를 수령합니다. 기타 편의시설: 외투 보관 서비스, 약국, 여행자 보험 창구 이용은 보안검색장 진입 전 일반지역에서 마쳐야 합니다. Step 3. 출국장 진입 및 보안 검색 3층 출국장 게이트(2~5번)로 이동하여 탑승권과 여권을 확인받고 보안검색대로 이동합니다. 주의 (액체류 규정 안내) 기내 반입 액체류는 개별 용기당 100ml 이하, 1인당 총 1L 투명...

엑셀·워드와 호환되는 최신 WYSIWYG 웹 에디터 강추

최신 자료를 기반으로 엑셀과 워드 호환이 잘 되는 상위 10개의 WYSIWYG 웹에디터를 조사하겠습니다. 이 목록은 사용량, 기능, 개발자 선호도를 고려하여 선정되며, 각 웹에디터의 공식 웹사이트 링크도 함께 제공해드리겠습니다.  엑셀·워드와 호환되는 최신 WYSIWYG 웹 에디터 10선 1.  CKEditor 5 오픈 소스 기반의  CKEditor 5 는 높은 완성도의 WYSIWYG 웹 에디터로, Drupal 등 주요 CMS에서 기본 에디터로採용될 정도로 널리 쓰입니다 ( Drupal and CKEditor: a history of advanced content editing | CKEditor | CKEditor ). 풍부한 플러그인과 커스터마이징 기능을 제공하며,  Microsoft Word  문서를 댓글이나 변경 추적 내용까지 포함하여 불러오는 고급 호환 기능도 갖추고 있습니다 ( Wysiwyg Editors Statistics 2025 ). 또한 별도 플러그인을 통해 편집한 내용을 바로  .docx  워드 파일로 내보낼 수 있어 워드 호환성이 뛰어납니다 ( Export to Word | CKEditor 5 Documentation ). 개발자들은 CKEditor 5의 모듈식 설계와 활발한 커뮤니티 지원을 선호하며, 기업용 협업 편집 기능 등의 프리미엄 옵션도 제공됩니다. 2.  TinyMCE TinyMCE 는 오랜 역사와 함께 가장 널리 사용되는 웹 에디터 중 하나로, WordPress에서는 수년간 기본 편집기로採用되어 왔습니다. Joomla, Umbraco, Shopify 등 수많은 CMS에 내장되어 있으며, 전 세계 웹 콘텐츠의 약 40%가 TinyMCE로 작성·출판되고 있다고 합니다 ( Configure TinyMCE for your CMS to rival Wix and WordPress | TinyMCE ). 기본 기능만으로도 강력하지만,...