--- name: data-skill description: 본 스킬은 대한민국 공공데이터포털(https://www.data.go.kr/)에서 제공하는 각종 공공데이터 API 를 사용하기 위한 설명입니다. 본 스킬은 공공데이터 개발 또는 공공 API 개발을 할 때 사용하면 됩니다. --- # 대한민국 공공데이터포털 API 스킬 ## 🔥 Center 프로젝트 글 생성/수정/관리 작업 시 필수 확인 > **⚠️ Center 프로젝트에서 글 관련 작업(생성, 수정, 삭제, 배치 생성 등)을 수행할 때는 반드시 다음 순서로 문서를 확인하세요:** > > 1. **[api-spec.md](../center-skill/references/api-spec.md)** - API 사용 가이드, 테스트용 토큰, curl 예시 > 2. **[create_post.sh](../center-skill/scripts/create_post.sh)** - 단일/배치 게시글 생성 스크립트 --- ## 1. 개요 **대한민국 공공데이터포털(data.go.kr)**은 중앙정부, 지자체, 공공기관이 보유한 데이터를 개방·제공하는 공식 플랫폼이다. ### 주요 특징 - **REST API 기반**: HTTP GET/POST 방식 호출 - **응답 형식**: JSON 또는 XML 선택 가능 - **인증 방식**: 서비스키(serviceKey) 기반 - **무료 제공**: 대부분의 API는 무료로 사용 가능 ## 2. 실제 API 응답 구조 **중요**: 공공데이터포털 API 문서와 실제 응답 구조가 다를 수 있다. 아래는 **실제 응답 구조**이다. ```json { "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를 터미널에서 직접 테스트할 수 있는 쉘 스크립트이다. #### 사용법 ```bash # 스크립트 실행 ./scripts/test-mofa-api.sh "" # 예시 ./scripts/test-mofa-api.sh "FAK7%2BJL3rqrFr7Wtn%2FxkKhW8hq1zDsite%2FxQdIwug4pDLD5bsqFJDKzroRXTkY8fm5LXMMMzIaTuvl%2F4iDtQ%2Bw%3D%3D" ``` #### 출력 내용 1. API 요청 정보 (엔드포인트, 파라미터) 2. HTTP 상태 코드 3. 결과 코드/메시지 4. 공지사항 목록 (제목, ID, 작성일) 5. 원본 JSON 응답 (처음 1500자) #### 주의사항 - API Key는 **반드시 URL 인코딩된 값**을 사용해야 한다 - 공공데이터포털에서 발급받은 키를 그대로 복사하면 이미 인코딩되어 있음 - 스크립트 실행 전 실행 권한 필요: `chmod +x scripts/test-mofa-api.sh` ## 4. 제공 API 레퍼런스 | API | 문서 | 설명 | |-----|------|------| | 외교부 공지사항 | [references/mofa-reminder.md](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](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 - 개별 공지사항 ```dart 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 응답 래퍼 ```dart class MofaNoticeResponse { final String resultCode; // "0" = 성공 final String resultMsg; // 결과 메시지 final int totalCount; // 전체 공지사항 수 final int numOfRows; // 요청한 개수 final int pageNo; // 페이지 번호 final List notices; // 공지사항 목록 final DateTime fetchedAt; // 조회 시간 bool get isSuccess => resultCode == '0'; // API 응답 파싱 (실제 구조에 맞게) factory MofaNoticeResponse.fromApiJson(Map 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 서비스이다. #### 핵심 개념 1. **싱글톤 패턴**: 앱 전체에서 하나의 인스턴스만 사용 2. **캐시 우선 전략**: 캐시 → API 호출 → 캐시 저장 3. **1시간 TTL**: 1시간마다 자동으로 캐시 만료 4. **이중 캐싱**: 메모리 + 파일 캐싱으로 성능 최적화 #### 사용법 ```dart 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; ``` #### 주요 구현 코드 ```dart 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 _mofaCache = FileCache( cacheName: 'mofa_notices', defaultTtl: _mofaCacheTtl, fromJson: MofaNoticeResponse.fromJson, toJson: (data) => data.toJson(), useMemoryCache: true, ); Future 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 연동 예시 ```dart class _NoticeScreenState extends State { bool _isLoading = true; MofaNoticeResponse? _response; @override void initState() { super.initState(); _loadNotices(); } Future _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. 개발 시 주의사항 1. **인증키 보안**: 인증키를 소스코드에 직접 하드코딩하지 말고 환경변수나 설정 파일로 관리 2. **URL Encode**: serviceKey는 반드시 URL Encode 처리 3. **응답 구조 확인**: 문서와 실제 응답 구조가 다를 수 있으므로 반드시 실제 응답 확인 4. **UTF-8 디코딩**: 한글 깨짐 방지를 위해 `utf8.decode(response.bodyBytes)` 사용 5. **HTML 엔티티 디코딩**: 제목/내용에 `'`, ` ` 등이 포함될 수 있음 6. **캐싱**: 동일한 데이터를 반복 조회할 경우 캐싱 적용 권장