반응형

출처:https://gomcine.tistory.com/entry/PHP%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%ED%8C%8C%EC%8B%B1-%EA%B8%B0%EB%B2%95-%EC%9A%94%EC%95%BD


가끔 사이트 개발을 하거나 여러가지 데이터를 모으기 위해 웹페이지를 파싱해야할 경우가 있죠. 다양한 언어와 기법을 활용하여 웹페이지 파싱이 가능한데요. 저는 주로 PHP를 활용하여 웹페이지 스크랩하고 정규표현식을 통해 파싱 작업을 하고 있습니다. 그 내용을 간단하게 요약해 보도록 하죠.



파싱 대상 html 가져오기

1. echo file_get_contents(웹페이지 URL); => 서버 설정으로 막힌 경우가 많음.
2. curl (스누피라는 라이브러리 쓰면 좀 편함)
3. fsocketopen (최종 방법)


파싱할 때 주로 쓰는 함수 5가지

1. str_replace(".", "", $string); // . 삭제
2. $language = preg_replace( '/[^a-zA-Z_]/', '', $_REQUEST['language'] ); // 정규식으로 치환
3. preg_match_all('/[가-힣a-zA-Z0-9 ]*/i', $resultHtml, $find_words); => 그냥 preg_match 하면 1개만 가져옴
4. explode("-", "2017-09-20");
5. substr("테스트", 1, 2); => 스트
6. strip_tags("<b>테스트</b>")
7. print_r($array); => 배열 출력


정규표현식 상식

. => 모든 문자, .*? : 모든문자 최초매칭
greedy vs non-greedy (최대매칭 vs 최초매칭 => 조건이 만족되는 최초의 위치까지만 일치하는 것으로 간주 / ?를 사용함)
() : 그룹, [] : 범위


정규표현식 예시

preg_replace("/[#\&\+\-%@=\/\\\:;,\.'\"\^`~\_|\!\?\*$#<>()\[\]\{\}]/i", "", $title); // 특수문자 제거, 공백은 포함

/["[가-힣a-zA-Z0-9 ]*",/i

/\[[가-힣a-zA-Z0-9]*\]/
/[0-9,]+건/
/[0-9.]*[0-9.]/
/[0-9-]*[0-9-]/

/([0-9]{3}cm)/i
/([0-9]{2,3}kg)/i
/([A-Z]{1,2}형)/i

/^(to|cc|bcc|Reply-To)$/
/[^A-Za-z0-9!#$%&'*+\/=?^_`{|}~ -]/
/Location:\s([^\s]*)\s/
/(src|background)=["'](.*)["']/Ui
/^Get(\w+Service)$/I


결국 웹페이지 파싱 작업은 첫 번째로 웹페이지를 어떻게 스크랩할 것인가 그리고 두 번째로 가져온 html 데이터를 어떻게 파싱해서 데이터를 추출할 것인가에 달려 있는 것 같습니다. 첫 번째 부분은 어느 정도 정형화되어 있기 때문에 한 번만 잘 소스를 만들어두면 계속 재사용할 수 있으며 두 번째 부분은 웹페이지 구조를 얼마나 잘 분석하고 정규표현식을 잘 활용하여 데이터를 뽑아내는가에 달려 있는 것 같습니다.

반응형
반응형

출처: https://gomcine.tistory.com/entry/%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%B6%81-API-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EA%B0%9C%EB%B0%9C-%EB%B0%A9%EB%B2%95-%EC%A0%95%EB%A6%AC

 

페이스북 페이지를 운영하면서 포스팅 자동화 방법을 찾다가 직접 프로그램을 개발하기로 하였습니다. 그러면서 Google API와 Facebook API를 사용하게 되었는데요. 지난번 Google API 개발 방법 정리에 이어 이번에는 Facebook API 연동 및 개발 방법을 정리해봤습니다. 

 

1. 페이스북 앱 개발 과정 개요

2. 페이스북 API 종류

3. 페이스북 그래프 API와 SDK

4. 페이스북 개발자 계정 생성과 앱 등록

5. 사용자 엑세스 토큰 발급

6. 페이지/그룹 엑세스 토큰 발급

7. 페이스북 앱 검수

1. 페이스북 앱 개발 과정 개요

페이스북 API를 사용하려면 다음과 같은 과정을 거쳐야 합니다. 

 

페이스북 개발자 계정 생성 -> 앱 등록 및 앱 ID 생성 -> 엑세스 토큰 발급 -> 앱 개발 및 테스트 -> 앱 검수 -> 서비스 시작

2. 페이스북 API 종류

페이스북 API는 크게 두 종류로 나뉩니다. 바로 그래프 API와 마케팅 API 인데요. 그래프 API(=Graph API)는 Facebook 소셜 그래프에 있는 정보를 조회하거나 쓸 수 있는 API로 페이스북 개발을 한다면 주로 이 API를 사용하게 됩니다.

 

 

페이스북 마케팅 API는 페북 광고 캠페인이나 보고서 등에 이용하는 API로 주로 광고 회사나 마케팅 회사의 프로그램을 개발할 때 사용합니다.

 

3. 페이스북 그래프 API와 SDK

사용자 게시물이나 페이지, 그룹 등에 자동으로 포스팅하거나 게시글을 조회하려면 그래프 API를 사용해야 합니다. 페이스북에서는 그래프 API 조금 더 편리하게 사용할 수 있도록 플랫폼별 SDK 설치를 지원하고 있습니다.

 

 

페이스북에서 공식으로 지원하는 SDK로는 안드로이드, iOS, Javascript, React, Swift, PHP 등이 있습니다. 될 수 있으면 페북 공식 SDK로 개발하는 것을 추천합니다.

 

참고로 그래프 API는 2015년 4월 30일 버전 1.0에서 2.0으로 업그레이드 되었으며, 2019년 현재 최신 버전은 3.2입니다. 페북 API 개발을 할 때 꼭 버전을 확인한 후 개발해야 합니다.

 

4. 페이스북 개발자 계정 생성과 앱 등록

 

먼저 facebook for developer 사이트에 접속해서 페이스북 개발자 계정을 생성하고 로그인해야 합니다. 본인 페이스북 계정이 있다면 그 계정으로 로그인하면 됩니다. 

 

 

▼ 개발자 계정 로그인을 하면 앱 대시보드가 나옵니다. 여기서 내 앱을 클릭하고 새 앱 만들기를 눌러줍니다. 해당하는 정보를 입력하고 완료하면 다시 대시보드가 나옵니다. 여기서 왼쪽 메뉴 중 설정 -> 기본 설정을 누릅니다.

 

 

▼ 기본 설정에서는 앱 ID와 앱 시크릿 코드를 확인할 수 있습니다. 앱 도메인에는 아직 개발 중이기 때문에 localhost를 입력합니다. 추후에 앱 검수를 요청할 때는 실제 도메인을 입력해야 합니다. 비즈니스 사용은 개인용으로 사용한다면 내 비즈니스 지원을 선택합니다.

 

 

▼ 하단에 사이트 URL에는 사용자 로그인 요청 후 콜백을 받을 소스를 입력합니다. 여기서는 PHP SDK를 사용했기 때문에 callback php 파일의 위치를 지정했습니다. 각 언어별 플랫폼별 맞는 콜백 주소를 입력해줍니다. 

 

 

5. 사용자 엑세스 토큰 발급

▼ 다음으로 Facebook 로그인 과정을 거칩니다. 이 과정을 거치면 Facebook 로그인 메뉴 왼쪽에 초록색 체크 표시가 나타나고 사용자 엑세스 토큰을 발급 받게 됩니다. 로그인 설정을 위해 좌측 메뉴에서 Facebook 로그인 > 설정을 클릭합니다.

 

 

▼ 아래와 같이 세팅을 합니다. 유효한 OAuth 리디렉션 URI에는 앞선 콜백 URL을 입력합니다. 개발 중인 경우 localhost 리디렉션은 따로 추가하지 않아도 됩니다. 반대로 운영 단계에 들어가면 반드시 입력해주어야 합니다.

 

 

▼ 운영 단계에서 OAuth 리디렉션 URI를 추가했다면 하단에 있는 검사기를 통해 URI 리디렉션 유효성을 검사할 수 있습니다. 

 

 

▼ 간단하게 본인 계정으로 Facebook 로그인 단계를 거치기 위해 왼쪽 메뉴에서 Facebook 로그인 > 빠른 시작을 클릭합니다. 그리고 본인에게 맞는 서비스를 클릭합니다. (여기서는 웹 선택)

 

 

▼ 사이트 URL에 콜백 리디렉션 URL을 입력하고 save합니다. 이 빠른 시작은 javascript 용 SDK를 이용했습니다. 만약 다른 언어나 플랫폼이라면 다음 단계를 실행합니다.

 

 

▼ 빠른 시작으로 Facebook 로그인을 할 수 있지만 javascript 개발 시에만 유용합니다. 이 때는 그래프 API 탐색기를 통해 간단하게 로그인 과정을 거칠 수 있습니다.

 

 

▼ 사용자 또는 페이지에서 사용자 토큰을 누르고 사용자 액세스 토큰 받기를 클릭합니다.

 

 

▼ 권한 추가에서 필요한 권한을 추가합니다. 여기서는 일단 페이스북 페이지에 자동으로 포스팅하기 위한 목적이기 때문에 manage_pages와 publish_pages 권한이 기본적으로 필요합니다. (참고 : 페이스북 페이지 시작하기 문서)

 

 

▼ 이제 Get Access Token 버튼을 누르면 사용자 액세스 토큰이 생성됩니다. 이 토큰 정보와 app id, app secret을 코드에 넣습니다. 아래 코드는 PHP로 개발한 사례로 제 GitHub 레파지토리를 참고하길 바랍니다.

 

 

위 예제에서는 facebook_login.php와 facebook_login_callback.php 파일을 통해 새로운 사용자 엑세스 토큰을 받을 수도 있습니다.

 

 

 

하지만 이 예제는 간단하게 페이지 자동 포스팅을 구현하기 위한 예제로 실제 다른 사용자에게 서비스하려면 페이스북 로그인 과정을 그에 맞게 코딩하여야 합니다.

6. 페이지/그룹 엑세스 토큰 발급

▼ 이제 사용자 엑세스 토큰을 통해 페이지 엑세스 토큰을 발급 받는 방법을 살펴보도록 하겠습니다. (그룹 엑세스 토큰 발급도 페이지 엑세스 토큰 발급 방법과 거의 동일합니다. ) 먼저 관리하고 있는 각 페이지의 목록과 페이지 별 access token을 조회합니다.

 

 

페이지 정보를 조회하는 방법은 그래프 API 탐색기에서 me/accounts?fields=access_token, name, id 를 입력하고 제출을 누릅니다. 이 때 "사용자 또는 페이지" 메뉴에 "사용자 토큰"으로 선택되어 있는지 확인하세요.

 

우측 상단에 있는 제출 버튼을 누르면 각 페이지별로 ID와 이름, 그리고 access token이 발급됩니다. 이 토큰은 페이지에 엑세스할 수 있는 토큰으로 사용자 엑세스 토큰과는 다릅니다.

 

▼ 참고로 그래프 API 탐색기 아래를 보면 "코드 받기"라는 버튼이 있습니다. 이 버튼을 누르면 각 Request 에 대한 플랫폼별 소스 코드 snippet을 볼 수 있는데요. 앱 개발을 할 때 이 코드들을 활용하면 됩니다.

 

 

▼ 아래 소스와 같이 그래프 api를 통해 페이지에 포스팅 할 수 있습니다. 이 때 page id와 page access token을 파라미터로 같이 보냅니다.

 

7. 페이스북 앱 검수

▼ 페이스북 페이지에 자동으로 포스팅 되는 기능을 만들어도 페이스북 API 앱 검수를 통과하지 못하면 쓸 수 없습니다. 테스트할 때 페이지에 포스트가 올라가긴 하지만 이 포스트는 관리자 혼자만 볼 수 있고 홍보도 할 수 없습니다.

 

 

페이스북은 최근 개인정보 유출 사태 등을 겪으면서 페이스 북 API 제한과 앱 검수를 강화했습니다. 따라서 앱 검수에 2주 또는 한 달이 걸리기도 하는데요. 단순 조회를 위한 API 사용은 앱 검수 절차가 필요 없지만 페이지나 그룹에 글쓰기 등을 하는 경우는 앱 검수가 필요합니다. 또한 페이스북 api 사용자 정보를 이용하면 검수가 더 엄격해집니다.

 

 

 

앱 검수 시에는 실제 도메인이며 https 로 서비스 해야 하며, 페이스북 권한 요청에 대한 스크린캐스트 영상을 업로드 해야 합니다. 페이스북 앱 검수에 대한 자세한 내용은 위 영상과 아래 글들을 참고하시길 바랍니다.

 

페북 앱 검수 참고글

Instagram API 연동하기 - Medium Blog

HOW DO I SETUP A FACEBOOK APP, TO ALLOW ME TO POST AUTOMATICALLY TO FACEBOOK?

 

페이스북 API 개발 참고 문서

Graph API page reference

Short-Term Tokens and Long-Term Tokens

Getting started with the Facebook SDK for PHP

PHP Facebook SDK 시작하기

Page API 시작하기

반응형
반응형

출처: https://webnautes.tistory.com/829


안드로이드 앱이  PHP 프로그램을 매개로 하여 MySQL 데이터베이스 서버에 데이터를 JSON 형식으로 가져오는 간단한 예제입니다.




1. 웹브라우저로 PHP 동작  테스트


2. Android 앱에서 테스트


3. 코드 설명


4. 관련 포스팅


5. 참고




마지막 업데이트 2018. 12. 7




안드로이드 앱이 웹서버에 있는 PHP 파일 실행시키면, PHP 코드는 질의를 하여 MySQL 서버로부터 데이터를 가져옵니다.  

그리고 나서 데이터를 JSON 포맷으로 가공하여 안드로이드 앱으로 넘겨줍니다.

안드로이드 앱은 JSON 포맷의 데이터를 파싱하여 원하는 UI에 데이터를 보기 좋게 보여주게 됩니다.


JSON(제이슨, JavaScript Object Notation)은 사람이 읽을 수 있는 텍스트를 사용하여  데이터를 전달할 수 있도록 해주는 개방형 표준 포맷입니다.

데이터를 키(key)와 값(value)가 쌍을 이루는 형식을 변환하여 사용합니다.


다음 포스팅에 연결된 포스팅입니다.



Android PHP MySQL 예제 - 데이터베이스에 데이터 입력하기

http://webnautes.tistory.com/828




1. 웹브라우저로 PHP 동작  테스트

Android 앱으로 테스트를  진행하기 전에 다음 PHP 코드를  웹 브라우저로 간단한 테스트를 해보겠습니다.



1-1. 앞에서 진행했던 포스팅의 결과로 현재 testdb 데이터베이스의 person 테이블에  데이터가 저장되어 있는 상태입니다.



Android PHP MySQL 예제  - 데이터베이스에 데이터 입력하기

http://webnautes.tistory.com/828




다음 두 개의 파일이 필요합니다.


dbcon.php

MySQL 서버 접속을 위해 사용되는 코드입니다. 위에서 언급한 포스팅에 있습니다.


getjson.php

MySQL에 질의를 보내어 결과를 가져오고 JSON 포맷으로 변환하여 출력하는 역할을 합니다.


getjson.php


<?php

   error_reporting(E_ALL);
   ini_set('display_errors',1);

   include('dbcon.php');
       

   $stmt = $con->prepare('select * from person');
   $stmt->execute();

   if ($stmt->rowCount() > 0)
   {
       $data = array();

       while($row=$stmt->fetch(PDO::FETCH_ASSOC))
       {
           extract($row);
   
           array_push($data,
               array('id'=>$id,
               'name'=>$name,
               'country'=>$country
           ));
       }

       header('Content-Type: application/json; charset=utf8');
       $json = json_encode(array("webnautes"=>$data), JSON_PRETTY_PRINT+JSON_UNESCAPED_UNICODE);
       echo $json;
   }

?>




1-2. 윈도우라면 C:\wamp64\www\ 경로에 파일을 생성합니다.



1-3. 우분투라면 /var/www/html/ 경로에 파일을 생성합니다.



1-4. 웹브라우저에서 localhost/getjson.php 주소에 접속하면 캡쳐화면처럼 JSON 형식으로 데이터를 보여줍니다.





2. Android 앱에서 테스트


웹서버의 PHP 파일을 매개로하여  데이터베이스의 데이터를 가져오는 안드로이드 앱을 작성합니다.



2-1. 매니페스트 파일 AndroidManifest.xml의  manifest 태그 하위 항목으로 인터넷 접근 허용 퍼미션을 추가합니다.



<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.tistory.webnautes.phptest">

   <uses-permission android:name="android.permission.INTERNET" />
   
   <application
       android:allowBackup="true"




2-2. 레이아웃 파일 activity_main.xml을 아래 내용으로 대체합니다.



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context=".MainActivity"
   android:layout_margin="15dp"
   android:padding="10dp">

   <android.support.v7.widget.RecyclerView
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="6"
       android:layout_margin="5dp"
       android:padding="5dp"
       android:id="@+id/listView_main_list" />

   <Button
       android:layout_margin="2dp"
       android:id="@+id/button_main_all"
       android:layout_width="match_parent"
       android:layout_weight="2"
       android:layout_height="0dp"
       android:text="전체보기" />

   <TextView
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="4"
       android:layout_margin="5dp"
       android:padding="5dp"
       android:id="@+id/textView_main_result" />



</LinearLayout>



상단의 RecyclerView에는 데이터베이스로부터 가져온 JSON 형식의 데이터를 파싱해서 항목별로 보여줍니다.

하단의 TextView에는 JSON 형식의 데이터 또는 각종 에러를 보여주는 용도로 사용됩니다.




2-3. 레이아웃 파일 item_list.xml을 추가로 생성하여 아래 내용으로 바꿉니다.



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal"
   android:layout_margin="5dp"
   android:padding="5dp">

   <TextView
       android:id="@+id/textView_list_id"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_margin="5dp"
       android:padding="5dp" />


   <TextView
       android:id="@+id/textView_list_name"
       android:layout_width="100dp"
       android:layout_height="wrap_content"
       android:layout_margin="5dp"
       android:padding="5dp" />


   <TextView
       android:id="@+id/textView_list_country"
       android:layout_width="100dp"
       android:layout_height="wrap_content"
       android:layout_margin="5dp"
       android:padding="5dp" />

</LinearLayout>



RecyclerView의 한 줄을 여러 개의 열로 표현하기 위해 필요한 레이아웃입니다.

필요한 열 개수 만큼 TextView를 추가하고 보여주는 데이터 값에 맞게 크기를 지정해주면 됩니다.




2-4. RecyclerView를 사용하려면  build.gradle에 다음 노란줄을 추가해야 합니다.



dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'com.android.support:appcompat-v7:27.1.1'
   implementation 'com.android.support.constraint:constraint-layout:1.1.2'
   implementation 'com.android.support:recyclerview-v7:27.1.1'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}




2-5. 자바 파일 MainActivity.java을 아래 내용으로 바꿉니다.


다음 줄에 있는 IP 주소를 아파치 웹서버가 설치된  컴퓨터의 IP로 수정하세요.

private static String IP_ADDRESS = "IP주소";


안드로이드 에뮬레이터와 서버가 같은 컴퓨터에 동작하는 경우에는 다음 아이피를 입력합니다.

Android Studio의 에뮬레이터  - 10.0.2.2

GenyMotion - 192.168.56.1


package com.tistory.webnautes.phptest;

import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;


import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;


public class MainActivity extends AppCompatActivity {

   private static String IP_ADDRESS = "IP주소";
   private static String TAG = "phptest";

   private EditText mEditTextName;
   private EditText mEditTextCountry;
   private TextView mTextViewResult;
   private ArrayList<PersonalData> mArrayList;
   private UsersAdapter mAdapter;
   private RecyclerView mRecyclerView;
   private EditText mEditTextSearchKeyword;
   private String mJsonString;


   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       mTextViewResult = (TextView)findViewById(R.id.textView_main_result);
       mRecyclerView = (RecyclerView) findViewById(R.id.listView_main_list);
       mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

       mTextViewResult.setMovementMethod(new ScrollingMovementMethod());



       mArrayList = new ArrayList<>();

       mAdapter = new UsersAdapter(this, mArrayList);
       mRecyclerView.setAdapter(mAdapter);


       Button button_all = (Button) findViewById(R.id.button_main_all);
       button_all.setOnClickListener(new View.OnClickListener() {
           public void onClick(View v) {

               mArrayList.clear();
               mAdapter.notifyDataSetChanged();

               GetData task = new GetData();
               task.execute( "http://" + IP_ADDRESS + "/getjson.php", "");
           }
       });

   }


 
   private class GetData extends AsyncTask<String, Void, String>{

       ProgressDialog progressDialog;
       String errorString = null;

       @Override
       protected void onPreExecute() {
           super.onPreExecute();

           progressDialog = ProgressDialog.show(MainActivity.this,
                   "Please Wait", null, true, true);
       }


       @Override
       protected void onPostExecute(String result) {
           super.onPostExecute(result);

           progressDialog.dismiss();
           mTextViewResult.setText(result);
           Log.d(TAG, "response - " + result);

           if (result == null){

               mTextViewResult.setText(errorString);
           }
           else {

               mJsonString = result;
               showResult();
           }
       }


       @Override
       protected String doInBackground(String... params) {

           String serverURL = params[0];
           String postParameters = params[1];


           try {

               URL url = new URL(serverURL);
               HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();


               httpURLConnection.setReadTimeout(5000);
               httpURLConnection.setConnectTimeout(5000);
               httpURLConnection.setRequestMethod("POST");
               httpURLConnection.setDoInput(true);
               httpURLConnection.connect();


               OutputStream outputStream = httpURLConnection.getOutputStream();
               outputStream.write(postParameters.getBytes("UTF-8"));
               outputStream.flush();
               outputStream.close();


               int responseStatusCode = httpURLConnection.getResponseCode();
               Log.d(TAG, "response code - " + responseStatusCode);

               InputStream inputStream;
               if(responseStatusCode == HttpURLConnection.HTTP_OK) {
                   inputStream = httpURLConnection.getInputStream();
               }
               else{
                   inputStream = httpURLConnection.getErrorStream();
               }


               InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
               BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

               StringBuilder sb = new StringBuilder();
               String line;

               while((line = bufferedReader.readLine()) != null){
                   sb.append(line);
               }

               bufferedReader.close();

               return sb.toString().trim();


           } catch (Exception e) {

               Log.d(TAG, "GetData : Error ", e);
               errorString = e.toString();

               return null;
           }

       }
   }


   private void showResult(){

       String TAG_JSON="webnautes";
       String TAG_ID = "id";
       String TAG_NAME = "name";
       String TAG_COUNTRY ="country";


       try {
           JSONObject jsonObject = new JSONObject(mJsonString);
           JSONArray jsonArray = jsonObject.getJSONArray(TAG_JSON);

           for(int i=0;i<jsonArray.length();i++){

               JSONObject item = jsonArray.getJSONObject(i);

               String id = item.getString(TAG_ID);
               String name = item.getString(TAG_NAME);
               String country = item.getString(TAG_COUNTRY);

               PersonalData personalData = new PersonalData();

               personalData.setMember_id(id);
               personalData.setMember_name(name);
               personalData.setMember_country(country);

               mArrayList.add(personalData);
               mAdapter.notifyDataSetChanged();
           }



       } catch (JSONException e) {

           Log.d(TAG, "showResult : ", e);
       }

   }

}




2-6. PersonalData.java 파일을 추가합니다.


MainActivity와 UsersAdapter에서 사용하는 클래스로 각각 다음 용도로 사용합니다.

  • MainActivity- ArrayList에 데이터를 저장하기 위해 사용됩니다.

  • UsersAdapter - ArrayList에 있는 데이터를 RecyclerView에 보여줄 때 사용됩니다.


package com.tistory.webnautes.phptest;


public class PersonalData {
   private String member_id;
   private String member_name;
   private String member_country;

   public String getMember_id() {
       return member_id;
   }

   public String getMember_name() {
       return member_name;
   }

   public String getMember_country() {
       return member_country;
   }

   public void setMember_id(String member_id) {
       this.member_id = member_id;
   }

   public void setMember_name(String member_name) {
       this.member_name = member_name;
   }

   public void setMember_country(String member_address) {
       this.member_country = member_address;
   }
}




2-7. UsersAdapter.java 파일을 추가합니다.

ArrayList에 있는 PersonalData 타입의 데이터를 RecyclerView에 보여주는 작업을 합니다.


package com.tistory.webnautes.phptest;

import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;


public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.CustomViewHolder> {

   private ArrayList<PersonalData> mList = null;
   private Activity context = null;


   public UsersAdapter(Activity context, ArrayList<PersonalData> list) {
       this.context = context;
       this.mList = list;
   }

   class CustomViewHolder extends RecyclerView.ViewHolder {
       protected TextView id;
       protected TextView name;
       protected TextView country;


       public CustomViewHolder(View view) {
           super(view);
           this.id = (TextView) view.findViewById(R.id.textView_list_id);
           this.name = (TextView) view.findViewById(R.id.textView_list_name);
           this.country = (TextView) view.findViewById(R.id.textView_list_country);
       }
   }


   @Override
   public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
       View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_list, null);
       CustomViewHolder viewHolder = new CustomViewHolder(view);

       return viewHolder;
   }

   @Override
   public void onBindViewHolder(@NonNull CustomViewHolder viewholder, int position) {

       viewholder.id.setText(mList.get(position).getMember_id());
       viewholder.name.setText(mList.get(position).getMember_name());
       viewholder.country.setText(mList.get(position).getMember_country());
   }

   @Override
   public int getItemCount() {
       return (null != mList ? mList.size() : 0);
   }

}




2-8. 실행결과 입니다.


상단 RecyclerView는 MySQL에서 가져온 데이터를 파싱해서 리스트 형태로 출력합니다.

하단 TextView는 파싱 전  JSON 형식의 데이터 또는 에러를 출력합니다.





3. 코드  설명

1-1. PersonalData.java 파일에 사용할 데이터가 선언되어 있습니다.


public class PersonalData {
   private String member_id;
   private String member_name;
   private String member_country;

}



데이터 추가시 클래스의 데이터 저장 및 읽어올때 사용되는 멤버 메소드를 추가하는 방법은 다음 포스팅을 참고하세요.



Android RecyclerView 간단한 예제

https://webnautes.tistory.com/1214





1-2. UsersAdapter.java 파일에 데이터를 보여주기 위한 처리가 있습니다.



레이아웃 파일에 있는 UI 컴포넌트를 CustomViewHolder 클래스의 멤버변수와 연결합니다.

   class CustomViewHolder extends RecyclerView.ViewHolder {
       protected TextView id;
       protected TextView name;
       protected TextView country;


       public CustomViewHolder(View view) {
           super(view);
           this.id = (TextView) view.findViewById(R.id.textView_list_id);
           this.name = (TextView) view.findViewById(R.id.textView_list_name);
           this.country = (TextView) view.findViewById(R.id.textView_list_country);
       }
   }

onBindViewHolder 호출될때 CustomViewHolder에 데이터를 추가합니다.

   @Override
   public void onBindViewHolder(@NonNull CustomViewHolder viewholder, int position) {

       viewholder.id.setText(mList.get(position).getMember_id());
       viewholder.name.setText(mList.get(position).getMember_name());
       viewholder.country.setText(mList.get(position).getMember_country());
   }




3. 버튼을 클릭시 GetData AsyncTask가 실행됩니다.

안드로이드 코드에서 실행시킬 서버의 IP_ADDRESS와 PHP 파일 이름을 지정해줍니다.


Button button_all = (Button) findViewById(R.id.button_main_all);
button_all.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {

mArrayList.clear();
mAdapter.notifyDataSetChanged();

GetData task = new GetData();
task.execute( "http://" + IP_ADDRESS + "/getjson.php", "");
}
});


4. doInBackground 메소드에서 서버에 있는  PHP 파일을 실행시키고 응답을 저장하고 스트링으로 변환하여  리턴합니다.

@Override
  protected String doInBackground(String... params) {



다음 포스팅에서 설명한 부분이라 설명을 생략합니다.  



Android PHP MySQL 예제 - 데이터베이스에 데이터 입력하기

http://webnautes.tistory.com/828



5. 에러가 있는 경우 에러메시지를 보여주고 아니면 JSON을 파싱하여 화면에 보여주는 showResult 메소드를 호출합니다.


     @Override
protected void onPostExecute(String result) {
super.onPostExecute(result);

progressDialog.dismiss();
mTextViewResult.setText(result);
Log.d(TAG, "response - " + result);

if (result == null){

mTextViewResult.setText(errorString);
}
else {

mJsonString = result;
showResult();
}
}


6. 안드로이드 앱에서 받은 JSON 포맷의 데이터입니다.

간단한 규칙이 있는데 중괄호 {} 는 JSONObject  대괄호 [] JSONArray입니다.


{
   "webnautes": [
       {
           "id": "1",
           "name": "홍길동",
           "country": "조선"
       },
       {
           "id": "2",
           "name": "잔다르크",
           "country": "프랑스"
       }
   ]
}


         


7. 첫번째 괄호는 중괄호 {} 이므로 JSONObject입니다.


JSONObject jsonObject = new JSONObject(mJsonString);



8. jsonObject에서 TAG_JSON 키를 갖는 JSONArray를 가져옵니다.


JSONArray jsonArray = jsonObject.getJSONArray(TAG_JSON);


이제 다음같은 구조를 jsonArray가 갖고 있게 됩니다.

[
       {
           "id": "1",
           "name": "홍길동",
           "country": "조선"
       },
       {
           "id": "2",
           "name": "잔다르크",
           "country": "프랑스"
       }
   ]



9. JsonArray에는 JSONObject가 데이터 갯수만큼 포함되어 있습니다.

인덱스를 사용하여 JsonArray에서 JSONObject를 하나씩 가져옵니다.

for(int i=0;i<jsonArray.length();i++){
JSONObject item = jsonArray.getJSONObject(i);


10. JSONObject에서 키 id, name, country의 값을 가져옵니다.


String id = item.getString(TAG_ID);
String name = item.getString(TAG_NAME);
String country = item.getString(TAG_COUNTRY);


11. 데이터를 새로 생성한 PersonalData 클래스의 멤버변수에 입력하고 ArrayList에 추가합니다.

PersonalData personalData = new PersonalData();

personalData.setMember_id(id);
personalData.setMember_name(name);
personalData.setMember_country(country);

mArrayList.add(personalData);

12. 리스트에 데이터가 변경되었음을 알려줍니다. 화면에 추가된 데이터들이 보입니다.


mAdapter.notifyDataSetChanged();



4. 관련 포스팅



Android PHP MySQL 예제 - 데이터베이스에 데이터 입력하기

http://webnautes.tistory.com/828


Android PHP MySQL 예제 - 데이터베이스 질의(query) 결과 출력하기

https://webnautes.tistory.com/1159


Android PHP MySQL 예제  - 데이터베이스에 데이터 저장 및 JSON 형식으로 가져오는 예제 프로젝트

http://webnautes.tistory.com/1189  





5. 참고

[1] http://codeigniter-kr.org/bbs/view/tip?idx=8300


[2] http://www.simplifiedcoding.net/android-json-parsing-retrieve-from-mysql-database/

 

[3] http://www.simplifiedcoding.net/android-json-tutorial-to-get-data-from-mysql-database/

반응형
반응형

출처: https://jandh.tistory.com/16


아두이노와 ESP8266을 활용하여 센싱한 데이터의 데이터베이스 저장과 리트리빙

 

 

1.     사전 작업

 

WAMP 기반 웹서버를 Bitnami wordpress를 이용하여 구축한다. 물론 취향에 맞게 다른 AMP 스택을 설치할 수도 있다. 본 문서는 위의 Bitnami Wordpress를 기준으로 하여 설명한다.

Wordpress 설치 시에는 관리자id을 통상 admin으로 설정하는데, 해킹의 대상이 될 수 있으므로 가급적이면 다른 id로 설정하는 것을 권장한다.

 

Bitnami Wordpress를 설치한 후의 폴더 경로 및 구조는 아래와 같다.

 

 

<그림 1> Bitnami Wordpress 설치 폴더 구조

 

Wordpress는 아래와 같이 apps\wordpress 폴더에 설치가 되며, document root 는 htdocs이다.

 

<그림 2> Wordpress 설치 폴더

 

 

2.     센싱할 데이터 및 데이터베이스 테이블 정의

 

센싱하여 저장할 데이터는 센싱 시각, 온도 및 습도이며, mySql 기준으로 다음 [표.1]의 데이터 속성을 갖는다. DHT11 센서의 경우 온도, 습도를 정수형(TINYINT, SMALINT 등)으로도 선언 가능하겠으나, 확장성 등을 고려하여 FLOAT로 선언하였다.

여기서 sensDate는 편의상 실제로 측정한 시각이 아닌, 데이터베이스에 기록된 시각을 사용한다.

 

[표. 1] 데이터베이스 테이블 구조

Field

Data Type

Null

비고

sensDate

DATETIME

Not null

 

temperature

FLOAT

Not Null

 

humidity

FLOAT

Not null

 

 

 

3.     데이터베이스 생성

 

더 진행하기 전에 몇 가지 명칭을 아래와 같이 정의한다(실제 구현 시에는 다른 이름으로 설정하여 사용하여야 하며, 그렇지 않음으로 발생하는 손해는 사용자 개인 책임이다).

 

-       센싱한 데이터를 저장하기 위한 데이터베이스 이름 : abc_sens

-       데이터베이스의 테이블 이름 : sens_table

-       데이터베이스 사용자 : abc_user 

-       데이터베이스 사용자 비번 :  abc_1234

 

명령 프롬프트(cmd창)를 열고 <그림 1>의 mysql\bin 폴더로 이동하여 다음과 같이 root 권한으로 mysql을 실행한다. 이 때의 비번은 Wordpress 관리자 비번과 동일하다.

 

C:\Bit…\mysql\bin>mysql –u root -p

 

 

위에서 정의한 사용자 abc_user, 비번 abc_1234를 다음과 같이 생성한다.

 

mysql> create user ‘abc_user’@’localhost’ identified by ‘abc_1234’;

 

 

사용자를 생성하였으면 이번에는 데이터베이스를 생성한다.

 

mysql> create database abc_sens;

 

 

생성한 데이터베이스에 사용자가 액세스할 수 있는 권한을 아래와 같이 부여한다.

 

mysql> grant usage on *.* to 'abc_user'@'localhost';

 

아울러 사용할 데이터베이스인 abc_sens에 대한 모든 권한을 부여한다.

 

mysql> grant all on abc_sens.* to 'abc_user'@'localhost';

 

 

여기까지는 root 권한으로 mysql에서 작업을 한 것이다. exit 하고, 위에서 새로 생성한 사용자인 id는 abc_user, 비번은 abc_1234 로 mysql에 다시 접속한다.

 

>mysql –u abc_user –p (비번 입력 프롬프트가 나오면 비번인 abc_1234 입력)

 

이 상태에서 mysql> show databases; 명령을 입력하면 위에서 생성한 abc_sens가 나타난다. 이 데이터베이스를 사용한다는 선언을 mysql> use abc_sens; 입력으로 해준다.

 

 

 

이제 abc_sens 데이터베이스에 앞에서 정의한 테이블을 생성한다.

 

mysql> create table sens_table (sensDate DATETIME , temperature float, humidity float);

 

 

이상의 과정을 통하여 아래와 같이 테이블이 생성된 것을 확인할 수 있다.

 

mysql> describe sens_table;

 

 

이상을 확인하면 mysql을 빠져나온다.

 

 

4.     데이터 쓰기

 

4-1.      아두이노 ESP8266에서 웹서버로 데이터 보내기

 

아두이노에서 ESP8266의 AP 접속은 다음의 AT 커멘드를 순서대로 실행하여 완료한다. 여기서 AP_SSID와 AP_PWD는 ESP8266이 접속하고자 하는 AP의 SSID, password 이다. 각 AT 커멘드는 ESP8266 AT Instruction Set를 참조한다.

 

-       “AT+GMR\r\n”

-       “AT+CWMODE=1\r\n”

-       "AT+CWJAP=\"AP_SSID",\"AP_PWD\"\r\n"

 

위와 같이 AP에 접속한 후에는 다음의 명령을 실행한다.

 

-       "AT+CIPMUX=0\r\n"

 

 

데이터 센싱을 한 이후에, 그 센싱한 데이터를 웹서버로 전송하기 위하여 아두이노 프로그램에서 다음을 실행한다. 아래는 웹서버 IP주소가 192.168.5.4 이고, 웹서비스 포트가 80인 예이다.

 

-       "AT+CIPSTART=\"TCP\",\"192.168.5.4\",80\r\n"

 

이제 ESP8266의 AT커멘드로 웹서버로 전송할 데이터를 구성해본다. 앞에서 센싱한 온도가 20도, 습도가 30%라고 가정하면, AT+CIPSEND 명령으로 보낼 데이터는 다음과 같이 GET 방식을 이용하여 구성한다.

 

-       “GET /wordpress/abc-write.php?temprature=20&humidity=30\r\n”

 

여기서 /wordpress는 <그림. 2>의 htdocs가 위치한 경로명이고, abc-write.php는 htdocs에 위치한 데이터 저장을 위한 php 파일이다.

 

 

4-2.      데이터베이스에 저장하기

 

앞에서 웹서버가 수신한 데이터를 데이터베이스에 저장하기 위한 파일로 abc-write.php를 언급하였는데, 이 php 파일이 어떻게 코딩되는지 보기로 한다.

 

우선 데이터베이스를 액세스하기 위하여 다음의 각 변수를 지정한다. 각각의 변수에 해당하는 값들은 앞의 데이터베이스 생성에서 정의한 값들이다.

 

$servername = "localhost";

$username = "abc_user";

$password = "abc_1234";

$dbname = "abc_sens";

 

웹서버로 전송된 데이터는 parse_str() 함수로 파싱을 하고,

 

parse_str( html_entity_decode( $_SERVER['QUERY_STRING']) , $out);

 

temperature 인자가 있는 경우, mysqli() 함수를 이용하여 데이터베이스에 접속, insert into 명령으로 데이터를 기록하게 한다.

성공적으로 기록되면 리턴되는 값은 “Sensed data saved." 이고, 실패 시에는 "Error:”와 함께 에러 메시지를 리턴한다.

 

앞에서 설명했던 ESP8266을 통한 데이터 저장은 웹브라우저를 통해서도 확인이 되는데, 웹브라우저의 주소 창에 AT+CIPSEND로 전송될 데이터인 temprature=20&humidity=30 값을 이용하여, 다음과 같이 입력하면 그 결과를 확인할 수 있다.

 

-       http://192.168.5.4/wordpress/abc-write.php?temperature=20&humidity=30

 

<그림 3> 웹브라우저를 통한 데이터 전송

 

 

다음은 abc-write.php의 전체 코드이다.

 

<?php

  $servername = "localhost";

  $username = "abc_user";

  $password = "abc_1234";

  $dbname = "abc_sens";

  date_default_timezone_set('Asia/Seoul');

  $now = new DateTime();

  parse_str( html_entity_decode( $_SERVER['QUERY_STRING']) , $out);

  if ( array_key_exists( 'temperature', $out ) ) {

    // Create connection

    $conn = new mysqli($servername, $username, $password, $dbname);

    // Check connection

    if ($conn->connect_error) {

      die("Connection failed: " . $conn->connect_error);

    }

    $datenow = $now->format("Y-m-d H:i:s");

    $temp  = $out['temperature'];

    $humid = $out['humidity'];

    $sql = "INSERT INTO sens_table (sensDate , temperature, humidity) VALUES ('$datenow' , $temp, $humid)";

    if ($conn->query($sql) === TRUE) {

      echo "Sensed data saved.";

    } else {

      echo "Error: " . $sql . "<br>" . $conn->error;

    }

    $conn->close();

  }

?>

[Source Code] abc-write.php

 

 

4-3.      저장된 데이터 리트리빙하기

 

데이터베이스에 접근하기 이전에 wordpress 로그인 사용자를 확인하기 위하여

 

include_once("./wp-config.php");

$current_user = wp_get_current_user();

 

함수를 실행한다. 여기서는 로그인 하지 않은 경우에는 서비스를 거부하는 예로 코딩하였다. 즉, 현재 로그인 ID를 체크하여 로그인 사용자가 아니면 실행을 중단하도록 한다.

 

if( 0 == $current_user->ID )

 

로그인 사용자임이 확인되면, mysqli_connect() 함수를 이용하여 데이터베이스를 열고, mysqli_query() 함수를 이용하여 테이블의 데이터를 읽어 온다. 다음은 그 전체 소스코드이다.

 

<!DOCTYPE html><html>

<head>

<title>Sensed Data</title>

</head><body>

<center>

<H1>My Sensor Data</H1>

</center>

<?php

include_once("./wp-config.php");

$current_user = wp_get_current_user();

if( 0 == $current_user->ID ) {

        echo '<center>';

//      echo 'Login name is: ' . $current_user->display_name . '<br />';

        echo 'Login required... <br />';

        echo '</center>';

        exit();

}

echo "<center>";

echo "[" . $current_user->display_name . "() 로그인]<br />";

echo "</center>";

$con = mysqli_connect('localhost','abc_user','abc_1234','abc_sens');

// Check connection

if ($conn->connect_error) {

  die("Connection failed: " . $conn->connect_error);

}

//Selecting the data from table but with limit

$query = "SELECT * FROM sens_table order by sensDate desc LIMIT 0, 20";

$result = mysqli_query ($con, $query);

?>

<table align="center" border="0" cellpadding="3">

<tr><th>Send Date</th><th>Temperature(&deg;C)</th><th>Humidity(&#37;)</th></tr>

<?php

while ($row = mysqli_fetch_array($result)) {

?>

<tr align="center">

<td><?php echo $row['sensDate']; ?></td>

<td><?php echo $row['temperature']; ?></td>

<td><?php echo $row['humidity']; ?></td>

</tr>

<?php

};

?>

</table>

</body>

</html>

[Source Code] abc-read-text.php

 

 

다음의 그림은 위의 코드를 실행한 결과를 보여준다. 데이터가 많아서 페이지네이션이 필요한 경우에는 쿼리 문장에서 LIMIT 옵션을 이용하여 구현이 가능하다.

 

<그림 4> 데이터 리트리빙 결과

 

 

다음의 그림은 유사한 방법으로 Google Charts를 이용하여 그래픽으로 나타낸 것이다. 자세한 것을 아래의 소스코드 abc-read-chart.php를 참고한다.

 

<그림 5> Google Charts를 이용한 결과

 

<?php

// to check if user logged in

include_once("./wp-config.php");

$current_user = wp_get_current_user();

if( 0 == $current_user->ID ) {

        echo '<center>';

        echo '<h1>My Sensor Data</h1> <br />';

        echo '<p>Login required... <br /></p>';

        echo '</center>';

        exit();

}

// db initialization

$servername = "localhost";

$username = "abc_user";

$password = "abc_1234";

$dbname = "abc_sens";

date_default_timezone_set('Asia/Seoul');

// Check connection

$con = new mysqli($servername, $username, $password, $dbname);

if ($con->connect_error) {

  die("Connection failed: " . $con->connect_error);

}

?>

<!DOCTYPE HTML>

<html>

<head>

 <meta charset="utf-8">

 <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>

 <script type="text/javascript">

 google.charts.load('current', {packages:['corechart']});

 google.charts.setOnLoadCallback(drawChart);

 function drawChart() {

        var data = google.visualization.arrayToDataTable([

        ['Date', 'Temperature', 'Humidity'],

        <?php

        $query = "SELECT * From sens_table order by sensDate desc LIMIT 0, 200";

        $exec = mysqli_query($con,$query);

        while($row = mysqli_fetch_array($exec)){

               echo "['" . $row['sensDate'] . "'," . $row['temperature']. "," . $row['humidity']. "],";

        }

        ?>

        ]);

        var options = {

               title: 'Sensor Data',

               curveType: 'function',

               legend: {position: 'bottom'},

               hAxis: {direction: -1}

               }

               vAxis: {

                       viewWindow: {

                              min: -10

                       }

        };

        var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));

        chart.draw(data, options);

 }

 </script>

</head>

<body>

<center>

 <h1>My Sensor Data</h1>

 <div id="curve_chart" style="width: 1000px; height: 450px;"></div>

</center>

</body>

</html>

[Source Code] abc-read-chart.php

 

 

5.     결어

 

본 문서에서는 아두이노 보드와 ESP8266 WiFi모듈, 그리고 DHT11 온습도센서를 이용, 센싱한 온도와 습도를 네트워크로 전송하면 Bitnami Wordpress가 설치된 웹서버 데이터베이스에 데이터를 저장하고, 텍스트 모드로 출력하는 방법을 설명하였다. 아울러 구글 차트를 이용, 그래픽 모드로 리트리빙하는 방법도 설명하였다.


보안문제는 별도로 언급을 하지 않았으므로 본 문서를 참고로 하여 시스템을 시험 구축하고자 하는 사람은 보안에 대한 별도의 대책을 마련하여야 함에 유념하여야 한다.

 

[References]

 

[1] Bitnami Wordpress, https://bitnami.com/stack/wordpress

[2] ESP8266 AT Instruction Set Version 1.5.4, http://www.espressif.com/sites/default/files/4a-esp8266_at_instruction_set_en_v1.5.4_0.pdf

[3] Wordpress Code Reference, https://developer.wordpress.org/reference/

[4] PHP MySQL Improved Extension, http://php.net/manual/kr/book.mysqli.php

[5] Google Charts, https://developers.google.com/chart/

 



출처: https://jandh.tistory.com/16 [Inspiration]

반응형

+ Recent posts