| name | data-skill |
| description | 본 스킬은 대한민국 공공데이터포털(https://www.data.go.kr/)에서 제공하는 각종 공공데이터 API 를 사용하기 위한 설명입니다. 본 스킬은 공공데이터 개발 또는 공공 API 개발을 할 때 사용하면 됩니다. |
대한민국 공공데이터포털 API 스킬
1. 개요
**대한민국 공공데이터포털(data.go.kr)**은 중앙정부, 지자체, 공공기관이 보유한 데이터를 개방·제공하는 공식 플랫폼이다.
주요 특징
- REST API 기반: HTTP GET/POST 방식 호출
- 응답 형식: JSON 또는 XML 선택 가능
- 인증 방식: 서비스키(serviceKey) 기반
- 무료 제공: 대부분의 API는 무료로 사용 가능
2. 실제 API 응답 구조
중요: 공공데이터포털 API 문서와 실제 응답 구조가 다를 수 있다. 아래는 실제 응답 구조이다.
{
"response": {
"header": {
"resultCode": "0",
"resultMsg": "정상"
},
"body": {
"dataType": "JSON",
"items": {
"item": [
{ /* 실제 데이터 항목 */ }
]
},
"numOfRows": 10,
"pageNo": 1,
"totalCount": 225
}
}
}
핵심 포인트
| 항목 | 문서 (잘못됨) | 실제 응답 (올바름) |
|---|---|---|
| 결과 코드 | resultCode (int) |
response.header.resultCode (string) |
| 데이터 배열 | data[] |
response.body.items.item[] |
| 총 개수 | totalCount |
response.body.totalCount |
에러 코드
| 코드 | 메시지 | 설명 |
|---|---|---|
0 |
OK | 정상 |
-1 |
시스템 내부 오류 | 서버 에러 |
-2 |
파라미터 부적합 | 요청 파라미터 오류 |
-4 |
등록되지 않은 인증키 | 인증키 미등록 |
-10 |
트래픽 초과 | 일일 호출 횟수 초과 |
-401 |
유효하지 않은 인증키 | 인증키 오류 |
3. 제공 스크립트
3.1 외교부 공지사항 API 테스트 (scripts/test-mofa-api.sh)
외교부 공지사항 API를 터미널에서 직접 테스트할 수 있는 쉘 스크립트이다.
사용법
# 스크립트 실행
./scripts/test-mofa-api.sh "<URL_ENCODED_API_KEY>"
# 예시
./scripts/test-mofa-api.sh "FAK7%2BJL3rqrFr7Wtn%2FxkKhW8hq1zDsite%2FxQdIwug4pDLD5bsqFJDKzroRXTkY8fm5LXMMMzIaTuvl%2F4iDtQ%2Bw%3D%3D"
출력 내용
- API 요청 정보 (엔드포인트, 파라미터)
- HTTP 상태 코드
- 결과 코드/메시지
- 공지사항 목록 (제목, ID, 작성일)
- 원본 JSON 응답 (처음 1500자)
주의사항
- API Key는 반드시 URL 인코딩된 값을 사용해야 한다
- 공공데이터포털에서 발급받은 키를 그대로 복사하면 이미 인코딩되어 있음
- 스크립트 실행 전 실행 권한 필요:
chmod +x scripts/test-mofa-api.sh
4. 제공 API 레퍼런스
| API | 문서 | 설명 |
|---|---|---|
| 외교부 공지사항 | references/mofa-reminder.md | 출국 전 참고 공지사항 목록 조회 |
4.1 외교부 공지사항 API 빠른 참조
- 서비스 URL:
http://apis.data.go.kr/1262000/NoticeService2/getNoticeList2 - HTTP Method: GET
- 응답 형식: JSON/XML
- 상세 문서: references/mofa-reminder.md
5. Flutter 연동 가이드
Flutter 앱에서 공공데이터 API를 사용하는 방법이다. 본 프로젝트의 lib/services/data/ 디렉토리에 구현되어 있다.
5.1 파일 구조
lib/services/data/
├── data.service.dart # API 호출 및 캐싱 서비스 (싱글톤)
└── mofa_notice.model.dart # 응답 데이터 모델
5.2 모델 클래스 (mofa_notice.model.dart)
MofaNotice - 개별 공지사항
class MofaNotice {
final String id; // 공지사항 ID (예: "ATC0000000048200")
final String title; // 제목 (HTML 엔티티 포함)
final String content; // 내용 (txt_origin_cn)
final String writtenDate; // 작성일 (예: "2025-12-15")
final String fileUrl; // 첨부파일 URL (없으면 빈 문자열)
// HTML 엔티티 디코딩된 제목/내용 반환
String get decodedTitle => _decodeHtmlEntities(title);
String get decodedContent => _decodeHtmlEntities(content);
}
MofaNoticeResponse - API 응답 래퍼
class MofaNoticeResponse {
final String resultCode; // "0" = 성공
final String resultMsg; // 결과 메시지
final int totalCount; // 전체 공지사항 수
final int numOfRows; // 요청한 개수
final int pageNo; // 페이지 번호
final List<MofaNotice> notices; // 공지사항 목록
final DateTime fetchedAt; // 조회 시간
bool get isSuccess => resultCode == '0';
// API 응답 파싱 (실제 구조에 맞게)
factory MofaNoticeResponse.fromApiJson(Map<String, dynamic> json) {
final response = json['response'];
final header = response['header'];
final body = response['body'];
final items = body['items']['item'] as List;
// ...
}
}
5.3 서비스 클래스 (data.service.dart)
싱글톤 패턴으로 구현된 API 서비스이다.
핵심 개념
- 싱글톤 패턴: 앱 전체에서 하나의 인스턴스만 사용
- 캐시 우선 전략: 캐시 → API 호출 → 캐시 저장
- 1시간 TTL: 1시간마다 자동으로 캐시 만료
- 이중 캐싱: 메모리 + 파일 캐싱으로 성능 최적화
사용법
import 'package:philgo/services/data/data.service.dart';
// 1. 공지사항 로드 (캐시 우선)
final response = await DataService.instance.loadMofaNotices();
// 2. 성공 여부 확인
if (response.isSuccess) {
// 3. 공지사항 목록 접근
for (final notice in response.notices) {
print('제목: ${notice.decodedTitle}');
print('날짜: ${notice.writtenDate}');
print('내용: ${notice.decodedContent}');
}
}
// 4. 캐시 강제 초기화 (새로고침)
await DataService.instance.clearMofaCache();
// 5. 캐시 남은 시간 확인
final remaining = DataService.instance.mofaCacheRemainingTime;
주요 구현 코드
class DataService {
static DataService? _instance;
static DataService get instance => _instance ??= DataService._();
// API Key (이미 URL 인코딩됨)
static const String apiKey = AppConfig.dataApiKey;
// 1시간 캐시 TTL
static const Duration _mofaCacheTtl = Duration(hours: 1);
// FileCache 사용 (메모리 + 파일 이중 캐싱)
late final FileCache<MofaNoticeResponse> _mofaCache = FileCache(
cacheName: 'mofa_notices',
defaultTtl: _mofaCacheTtl,
fromJson: MofaNoticeResponse.fromJson,
toJson: (data) => data.toJson(),
useMemoryCache: true,
);
Future<MofaNoticeResponse> loadMofaNotices() async {
// 1. 캐시 확인
final cached = await _mofaCache.get('mofa_notices');
if (cached != null) return cached;
// 2. API 호출
final response = await http.get(Uri.parse(
'$_mofaApiBaseUrl?serviceKey=$apiKey&returnType=JSON&numOfRows=5&pageNo=1'
));
// 3. UTF-8 디코딩 (한글 깨짐 방지)
final json = jsonDecode(utf8.decode(response.bodyBytes));
// 4. 실제 응답 구조에 맞게 파싱
final data = MofaNoticeResponse.fromApiJson(json);
// 5. 캐시 저장
await _mofaCache.set('mofa_notices', data);
return data;
}
}
5.4 UI 연동 예시
class _NoticeScreenState extends State<NoticeScreen> {
bool _isLoading = true;
MofaNoticeResponse? _response;
@override
void initState() {
super.initState();
_loadNotices();
}
Future<void> _loadNotices() async {
final response = await DataService.instance.loadMofaNotices();
setState(() {
_response = response;
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return CircularProgressIndicator();
}
if (!_response!.isSuccess) {
return Text('에러: ${_response!.resultMsg}');
}
return ListView.builder(
itemCount: _response!.notices.length,
itemBuilder: (context, index) {
final notice = _response!.notices[index];
return ListTile(
title: Text(notice.decodedTitle),
subtitle: Text(notice.writtenDate),
onTap: () => _showDetail(notice),
);
},
);
}
}
6. 개발 시 주의사항
- 인증키 보안: 인증키를 소스코드에 직접 하드코딩하지 말고 환경변수나 설정 파일로 관리
- URL Encode: serviceKey는 반드시 URL Encode 처리
- 응답 구조 확인: 문서와 실제 응답 구조가 다를 수 있으므로 반드시 실제 응답 확인
- UTF-8 디코딩: 한글 깨짐 방지를 위해
utf8.decode(response.bodyBytes)사용 - HTML 엔티티 디코딩: 제목/내용에
', 등이 포함될 수 있음 - 캐싱: 동일한 데이터를 반복 조회할 경우 캐싱 적용 권장