한국 우편번호, 도로명주소 및 지번주소 데이터를 관리하는 재사용 가능한 Go 패키지입니다.
- ✅ 도로명주소 & 지번주소 지원: 두 가지 주소 체계 모두 지원
- ✅ 재사용 가능: 다른 Go 서비스에서 import하여 사용
- ✅ REST API 제공: Gin 기반 HTTP API 서버 내장
- ✅ 레이어 분리: Repository, Service, Handler 패턴
- ✅ 고성능 검색: 우편번호 3자리 prefix 인덱스 최적화 (3-5배 빠름)
- ✅ 배치 처리: 대량 데이터 import 지원 (도로명/지번 모두)
- ✅ 표준화된 에러: 커스텀 에러 타입 제공
- ✅ 완벽한 테스트: 100+ 테스트로 검증된 안정성
go get github.com/epicsagas/korean-postalcode# CLI 도구 빌드 go build -o postalcode-migrate cmd/postalcode-migrate/main.go # 방법 1: .env 파일 사용 (권장) ./postalcode-migrate -cmd=up # 방법 2: DSN 직접 지정 ./postalcode-migrate -dsn="user:pass@tcp(localhost:3306)/dbname" -cmd=up # 상태 확인 ./postalcode-migrate -cmd=statusMigration 명령어:
up: 테이블 생성down: 테이블 삭제fresh: 테이블 재생성 (삭제 후 생성)status: 테이블 상태 및 데이터 개수 확인
DSN 설정:
-dsn플래그 사용 (우선순위 1).env파일 자동 로드 (우선순위 2)
# Shell 스크립트 사용 (권장) ./scripts/import.sh \ "user:pass@tcp(localhost:3306)/dbname" \ "data/postal_codes.txt" \ 1000import ( "github.com/epicsagas/korean-postalcode" postalcodeapi "github.com/epicsagas/korean-postalcode/pkg/postalcode" "gorm.io/driver/mysql" "gorm.io/gorm" ) // DB 연결 dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) // Repository & Service 생성 repo := postalcodeapi.NewRepository(db) service := postalcodeapi.NewService(repo) // 도로명주소 조회 roadResults, _ := service.GetByZipCode("01000") // 지번주소 조회 landResults, _ := service.GetLandByZipCode("25627")import ( "github.com/epicsagas/korean-postalcode" postalcodeapi "github.com/epicsagas/korean-postalcode/pkg/postalcode" ) // .env 파일에서 설정 로드 cfg, _ := postalcode.LoadConfig() // DB 연결 db, _ := gorm.Open(mysql.Open(cfg.Database.GetDSN()), &gorm.Config{}) // Repository & Service 생성 repo := postalcodeapi.NewRepository(db) service := postalcodeapi.NewService(repo)import ( postalcodeapi "github.com/epicsagas/korean-postalcode/pkg/postalcode" "your-project/internal/database" // 기존 프로젝트의 DB ) // 기존 프로젝트의 DB를 그대로 재사용! repo := postalcodeapi.NewRepository(database.DB) service := postalcodeapi.NewService(repo) // 사용 results, _ := service.GetByZipCode("01000")💡 핵심: *gorm.DB만 받으므로 어떤 프로젝트의 DB든 재사용 가능!
# 빌드 cd cmd/postalcode-api go build -o postalcode-api # .env 파일 설정 후 실행 ./postalcode-api # 또는 플래그로 직접 설정 ./postalcode-api -dsn "user:pass@tcp(localhost:3306)/dbname" -port 8080자동으로 제공되는 기능:
- ✅ 도로명주소 & 지번주소 API 엔드포인트
- ✅ Swagger UI 문서 (http://localhost:8080/swagger/index.html)
- ✅ CORS 지원
- ✅ Graceful shutdown
- ✅ Health check
- ✅ 자동 DB 마이그레이션
자세한 내용은 cmd/postalcode-api/README.md 참조
API 서버 실행 후 브라우저에서 접속:
http://localhost:8080/swagger/index.html Swagger 문서 재생성:
# swag CLI 설치 (최초 1회) go install github.com/swaggo/swag/cmd/swag@v1.8.12 # 문서 재생성 swag init -g cmd/postalcode-api/main.go -o docs/swagger --parseDependency --parseInternalimport ( "github.com/gin-gonic/gin" postalcodeapi "github.com/epicsagas/korean-postalcode/pkg/postalcode" ) service := postalcodeapi.NewService(repo) router := gin.Default() postalcodeapi.RegisterGinRoutes(service, router.Group("/api/v1/postal-codes")) router.Run(":8080")import ( "net/http" postalcodeapi "github.com/epicsagas/korean-postalcode/pkg/postalcode" ) service := postalcodeapi.NewService(repo) mux := http.NewServeMux() postalcodeapi.RegisterHTTPRoutes(service, mux, "/api/v1/postal-codes") http.ListenAndServe(":8080", mux)| Endpoint | Method | Description |
|---|---|---|
/road/zipcode/{code} | GET | 우편번호로 정확히 조회 (5자리) |
/road/prefix/{prefix} | GET | 우편번호 앞 3자리로 빠른 검색 (권장) |
/road/search | GET | 복합 검색 (시도, 시군구, 도로명) |
Example:
curl http://localhost:8080/api/v1/postal-codes/road/zipcode/01000 curl http://localhost:8080/api/v1/postal-codes/road/prefix/010 curl "http://localhost:8080/api/v1/postal-codes/road/search?sido_name=서울&limit=10"| Endpoint | Method | Description |
|---|---|---|
/land/zipcode/{code} | GET | 우편번호로 지번주소 조회 (5자리) |
/land/prefix/{prefix} | GET | 우편번호 앞 3자리로 빠른 검색 (권장) |
/land/search | GET | 복합 검색 (시도, 시군구, 읍면동, 리명) |
Example:
curl http://localhost:8080/api/v1/postal-codes/land/zipcode/25627 curl http://localhost:8080/api/v1/postal-codes/land/prefix/256 curl "http://localhost:8080/api/v1/postal-codes/land/search?sido_name=강원&eupmyeondong_name=강동면"먼저 우체국 사이트에서 최신 우편번호 데이터를 다운로드합니다:
다운로드 링크: 우체국 우편번호 서비스
다운로드 방법:
- 위 링크 접속
- "범위주소 DB" 다운로드 후 압축해제
파일 준비:
# 프로젝트의 data 디렉토리에 다운로드한 파일 복사 cp ~/Downloads/도로명주소*.txt data/road_address.txt cp ~/Downloads/지번주소*.txt data/land_address.txt💡 참고:
- 우체국 사이트의 파일명은 날짜별로 다를 수 있습니다 (예:
20251111_도로명주소.txt) - 파일 형식은 파이프(
|) 구분자를 사용하는 TXT 파일입니다 - 파일 크기가 클 수 있으므로 (수백 MB), 다운로드에 시간이 걸릴 수 있습니다
가장 쉬운 방법은 제공되는 shell 스크립트를 사용하는 것입니다:
# 도로명주소 데이터 import ./scripts/import.sh \ -file "data/road_address.txt" \ -type road \ -batch 1000 # 지번주소 데이터 import ./scripts/import.sh \ -file "data/land_address.txt" \ -type land \ -batch 1000 # DSN 직접 지정하는 경우 ./scripts/import.sh \ -dsn "user:pass@tcp(localhost:3306)/dbname" \ -file "data/road_address.txt" \ -type road \ -batch 1000스크립트 자동 기능:
- ✅ 파일 존재 확인
- ✅ 파일 정보 출력 (크기, 라인 수)
- ✅ 바이너리 자동 빌드
- ✅ 진행 상황 표시
- ✅ 성공/실패 결과 출력
수동으로 빌드하여 사용:
cd cmd/postalcode-import go build -o postalcode-import # 도로명주소 데이터 import (.env 파일 사용) ./postalcode-import \ -file "data/road_address.txt" \ -type road \ -batch 1000 # 지번주소 데이터 import (DSN 직접 지정) ./postalcode-import \ -dsn "user:pass@tcp(localhost:3306)/dbname" \ -file "data/land_address.txt" \ -type land \ -batch 1000플래그 설명:
-file: 데이터 파일 경로 (필수)-type: 데이터 타입 -road(도로명주소) 또는land(지번주소) (필수)-dsn: MySQL DSN (선택, 없으면 .env 파일 사용)-batch: 배치 처리 크기 (기본값: 1000)
import postalcodeapi "github.com/epicsagas/korean-postalcode/pkg/postalcode" importer := postalcodeapi.NewImporter(service) progressFn := func(current, total int) { fmt.Printf("Progress: %d/%d\n", current, total) } // 도로명주소 import (기존 데이터 자동 TRUNCATE) result, err := importer.ImportFromFile("road_data.txt", 1000, progressFn) // 지번주소 import (기존 데이터 자동 TRUNCATE) landResult, err := importer.ImportLandFromFile("land_data.txt", 1000, progressFn)💡 Import 동작:
- Import는 항상 기존 테이블 데이터를 TRUNCATE한 후 새 데이터를 삽입합니다
- 도로명주소(
ImportFromFile)와 지번주소(ImportLandFromFile)는 각각 독립적인 테이블을 사용합니다 - 부분 업데이트가 필요한 경우
service.Upsert()또는service.BatchUpsert()메서드를 사용하세요
import "github.com/epicsagas/korean-postalcode" // 도로명주소 및 지번주소 테이블 자동 생성 db.AutoMigrate(&postalcode.PostalCodeRoad{}, &postalcode.PostalCodeLand{})# 도로명주소 테이블 mysql -u user -p database < migrations/create_postal_code_roads.sql # 지번주소 테이블 mysql -u user -p database < migrations/create_postal_code_lands.sql31만건 데이터 기준:
| 검색 방법 | 실행시간 | 인덱스 |
|---|---|---|
zip_prefix = '010' | ~1-5ms | idx_zip_prefix ✅ |
zip_code LIKE '010%' | ~5-15ms | idx_zipcode |
zip_code = '01000' | ~1-3ms | idx_zipcode ✅ |
권장: 우편번호 앞 3자리 검색은 GetByZipPrefix() 사용
import ( "errors" "github.com/epicsagas/korean-postalcode" ) results, err := service.GetByZipCode("01000") if err != nil { switch { case errors.Is(err, postalcode.ErrNotFound): // Handle not found case errors.Is(err, postalcode.ErrInvalidZipCode): // Handle invalid format default: // Handle other errors } }- Swagger UI - 인터랙티브 API 문서 (서버 실행 필요)
- API.md - REST API 엔드포인트 완전 가이드
- USAGE.md - Repository/Service 사용 가이드
- INTEGRATION.md - 다른 프로젝트 통합 방법
- examples/ - 실행 가능한 코드 예제
korean-postalcode/ ├── config.go # 설정 관리 (공개 API) ├── errors.go # 표준화된 에러 (공개 API) ├── models.go # 데이터 모델 (공개 API) ├── internal/ # 비공개 구현 │ ├── repository/ # DB 접근 레이어 │ │ └── repository.go # Repository 구현 │ ├── service/ # 비즈니스 로직 │ │ └── service.go # Service 구현 │ ├── importer/ # 파일 Import 기능 │ │ └── importer.go # Importer 구현 │ └── http/ # HTTP API 핸들러 │ ├── handler.go # 표준 HTTP 핸들러 │ └── gin.go # Gin 핸들러 ├── pkg/ # 공개 래퍼 API │ └── postalcode/ # 편의 팩토리 함수 │ └── postalcode.go # NewRepository, NewService, etc. ├── cmd/ # CLI 도구 │ ├── postalcode-api/ # Gin API 서버 │ └── postalcode-import/ # 데이터 import 도구 ├── docs/ # 문서 │ ├── API.md # API 가이드 │ ├── USAGE.md # 사용 가이드 │ ├── INTEGRATION.md # 통합 가이드 │ └── swagger/ # Swagger 문서 │ ├── docs.go # Swagger 생성 파일 │ ├── swagger.json # Swagger JSON │ └── swagger.yaml # Swagger YAML ├── migrations/ # SQL 마이그레이션 │ ├── create_postal_code_roads.sql # 도로명주소 테이블 │ └── create_postal_code_lands.sql # 지번주소 테이블 ├── data/ # 데이터 파일 │ ├── 20251111_road_name.txt # 도로명주소 데이터 │ └── 20251111_land_rot.txt # 지번주소 데이터 ├── examples/ # 코드 예제 │ ├── basic/ # 기본 사용법 │ ├── api/ # REST API 서버 │ └── import/ # 데이터 import ├── scripts/ # Shell 스크립트 └── .env.example # 환경변수 예제 - internal/: 모든 구현 세부사항. 외부에서 직접 import 불가 (Go의 internal 패키지 규칙)
repository/: DB 접근 레이어service/: 비즈니스 로직importer/: 파일 import 기능http/: HTTP 핸들러 (Gin & 표준 HTTP)
- pkg/postalcode/: 공개 API 진입점. 팩토리 함수와 라우트 등록 함수 제공
- 루트 패키지: 공개 데이터 모델, 설정, 에러 타입만 노출
완전한 캡슐화: 사용자는 pkg/postalcode의 함수만 사용하며, 구현 세부사항은 완전히 숨겨집니다.
프로젝트는 100개 이상의 테스트로 완벽하게 검증되었습니다:
# 전체 테스트 실행 go test ./... # 상세 출력과 함께 실행 go test -v ./... # 커버리지 확인 go test -cover ./...tests/ ├── internal/ │ ├── repository/ │ │ └── repository_test.go # DB 접근 레이어 테스트 │ ├── service/ │ │ └── service_test.go # 비즈니스 로직 테스트 │ ├── importer/ │ │ └── importer_test.go # 파일 import 테스트 │ └── http/ │ ├── handler_test.go # 표준 HTTP 핸들러 테스트 │ └── gin_test.go # Gin 핸들러 테스트 ├── pkg/postalcode/ │ └── postalcode_test.go # 공개 API 테스트 ├── tests/ │ ├── integration_test.go # 통합 테스트 │ └── testdata/ # 테스트 데이터 │ ├── sample_road.txt # 도로명주소 샘플 │ └── sample_land.txt # 지번주소 샘플 └── examples/ └── example_test.go # 사용 예제 테스트 - ✅ Repository Layer: CRUD, 검색, 페이지네이션, 배치 작업
- ✅ Service Layer: 유효성 검증, 비즈니스 로직, 자동 zip prefix 추출
- ✅ HTTP Handlers: 모든 엔드포인트, HTTP 메서드, 쿼리 파라미터
- ✅ Importer: 파일 파싱, 에러 처리, 진행 상황 추적
- ✅ Integration: 전체 워크플로우, 에러 전파, 복잡한 검색
- ✅ Public API: 팩토리 함수, 라우트 등록, 인터페이스 호환성
모든 테스트는 in-memory SQLite를 사용하여 빠르고 격리된 환경에서 실행됩니다.
Issues와 Pull Requests를 환영합니다!
Apache License 2.0 - 자세한 내용은 LICENSE 파일을 참조하세요.
Copyright (c) 2024 epicsagas
Made with ❤️ for Korean Address Management