728x90

네이버 지도 API 기능을 학습하려고 구글링하다보니 Gson 라이브러리를 사용한 코드가 있어서 이참에 Gson 라이브러리를 테스트하고 정리해둔다.


1. Gson 라이브러리 최신버전 구하는 곳 : https://github.com/google/gson

   에 가면 최신버전의 정보가 나온다.

   단순하게 toJson() and fromJson() 메소드로 Java 객체를 JSON으로 변환하고

   SON 을 Java 객체로 변환하는 라이브러리 를 목표로 한다고 나온다.


2. 안드로이드 스튜디오에서 Gson 라이브러리를 이용하려면

   앱 build.gradle 파일내 dependencies 에 코드 한줄 추가하면 된다.

   compile 'com.google.code.gson:gson:2.8.2'



FileList_Item Class

- 변수명을 서버 JSON 변수명과 동일하게 처리하라.



Gson 라이브러리를 사용하지 않고 서버 데이터를 가져와서 RecyclerView 에 보여주는 코드중에서 비교할 수 있는 것만 발췌했다.


Gson 라이브러리 사용 이전 코드


listViewAdapter = new ListViewAdapter(this,fileList); // Adapter 생성
audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅

코드 부분을 onCreate 에 추가하고 listViewAdapter.notifyDataSetChanged(); 를 하면 JSON Array 데이터가 RecyclerView 에 잘 보인다.


Gson 라이브러리 사용 코드



Gson gson = new Gson();
Type listType = new TypeToken<ArrayList<FileList_Item>>(){}.getType();
fileList = gson.fromJson(audiofiles.toString(), listType);


검색해보면 위 코드 3줄이면 해결된다고 모두 답변이 되어 있다. 그런데도 불구하고 RecyclerView 에 출력되지 않았다.


서버에서 가져온 JSON Array 데이터를 ArrayList 에 추가하는 코드가 매우 간단해진다는 건 알았다.

상위폴더 ArrayList 를 별도로 만들 필요가 없는 경우에는 매우 유용할 거 같다.

Stackoverflow 에 질문한 내용을 검색해보니 RecyclerView 에 데이터 보이지 않는다고 질의한 사항이 몇건 있었다.  System.out.println 으로 찍어보면 데이터가 ArrayList에 정상적으로 추가되어 있다. 그러나 화면에 보이지 않았다.


Android RecyclerView JSON Parsing using Retrofit 게시글을 보니까 ArrayList 데이터를 추가하고 난 후 바로 다음 줄에 아래 2줄을 적어준 걸 보고 시도했더니 폰 화면에 정상적으로 출력된다.

listViewAdapter = new ListViewAdapter(this,fileList); // Adapter 생성
audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅

Gson 라이브러리를 사용하면 JSON Array 데이터를 String으로 하나하나 분리할 필요없이 ArrayList에 바로 추가할 수 있어 코드가 심플해지는거 같다.

단, Class 에서 생성한 변수명과 서버(PHP)에서 json_encode로 생성한 변수명이 동일해야 하는거 같다.

서버에서 생성한 데이터가 JSON Object 가 아니라 JSON Array 면 코드가 더 심플해질 수 있는거 같다.


상위폴더를 안드로이드 Java 코드에서 생성한 것을 없애고, 서버 PHP 코드에서 구현하는 것으로 변경하고 테스트를 해보니 정상적으로 잘 처리된다.


코드 줄어든 것만 발췌하여 비교한다.

=== Gson 라이브러리 미사용 코드 ===

// 서버 정보를 파싱하기 위한 변수 선언
private static final String TAG_RESULTS="result";
private static final String TAG_fileNAME = "fileName";
private static final String TAG_filePATH ="filePath";
private static final String TAG_curPATH ="curPath";
private static final String TAG_isFile ="isFile";
JSONArray audiofiles = null;

protected void showList(String jsonData) {
    try {
        JSONObject jsonObj = new JSONObject(jsonData);
        audiofiles = jsonObj.getJSONArray(TAG_RESULTS);

        fileList.clear(); // 서버에서 가져온 데이터 초기화
        for(int i = 0; i < audiofiles.length(); i++){
            JSONObject c = audiofiles.getJSONObject(i);
            final String fileName = c.getString(TAG_fileNAME);
            final String filePath = c.getString(TAG_filePATH);
            final String curPath = c.getString(TAG_curPATH);
            final String isFile = c.getString(TAG_isFile);

            // 서버에서 가져온 데이터 저장
            FileAllList(fileName, filePath, curPath, isFile);
        }

        runOnUiThread(new Runnable() { // 화면에 반영하기 위하여 runOnUiThread()를 호출하여 실시간 갱신한다.
            @Override
            public void run() {
                // 갱신된 데이터 내역을 어댑터에 알려줌
                listViewAdapter.notifyDataSetChanged();
            }
        });
    } catch (JSONException e) {
        e.printStackTrace();
    }

}

// 파일 리스트 추가를 위한 메소드
public void FileAllList(String fileName, String filePath, String curPath, String isFile){
    FileList_Item item = new FileList_Item();
    item.setFileName(fileName);
    item.setFilePath(filePath);
    item.setCurPath(curPath);
    item.setIsFile(isFile);
    fileList.add(item);
}

=== Gson 라이브러리 사용한 코드 ====

 // 서버 정보를 파싱하기 위한 변수 선언
private static final String TAG_RESULTS="result";
JSONArray audiofiles = null;

protected void showList(String jsonData) {
    JSONObject jsonObj = null;
    try {
        jsonObj = new JSONObject(jsonData);
        audiofiles = jsonObj.getJSONArray(TAG_RESULTS);
    } catch (JSONException e) {
        e.printStackTrace();
    }

    fileList.clear(); // 서버에서 가져온 데이터 초기화
    Gson gson = new Gson();
    Type listType = new TypeToken<ArrayList<FileList_Item>>(){}.getType();
    fileList = gson.fromJson(audiofiles.toString(), listType);
    listViewAdapter = new ListViewAdapter(this,fileList); // Adapter 생성
    audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
}



정리 핵심

Gson gson = new Gson();
JSONArray jsonArray = new JSONArray(JSONdata);
Type listType = new TypeToken<ArrayList<FileList_Item>>(){}.getType();
ArrayList<FileList_Item> myFileList = gson.fromJson(jsonArray.toString(), listType);


블로그 이미지

Link2Me

,
728x90

PHP Connect to MySQL

- MySQL (procedural) : MySQL DB에서만 동작하며, PHP 7.0 이상에선 동작안됨. PHP 5.5에서 deprecated

- MySQLi (procedural) : MySQL DB에서만 동작한다.

- MySQLi (object-oriented) : MySQL DB에서만 동작한다.
- PDO(PHP Data Objects) :
Oracle, PostgreSQL, SQLite, MySQL 등 12가지의 DB에서 동작한다.


MySQLi 기반 Prepared Statements PHP 연동 방식이 SQL 인젝션에 강하다고 되어 있다.

그러면 절차지향 방식의 MySQLi 방식에서는 어떻게 보강하면 될까?

$userid = mysqli_real_escape_string($db, $userid);

와 같이 mysqli_real_escape_string 를 써주면 된다.

해킹 대비를 위해서는 INPUT 해킹 시도(SQL Injection), 출력 해킹 시도(Cross Site Scripting) 둘다 신경써야 한다.


기존 사용하던 MySQLi (procedural) 대신에 MySQLi (object-oriented) 로 연습을 해봤다.


https://www.androidhive.info/2012/01/android-login-and-registration-with-php-mysql-and-sqlite 에서 파일을 다운로드하여 좀 수정했다.


테스트 환경

- Windows10 기반 Autoset9 (PHP 5.6.0, MySQL 5.6.20) : 정상

- Windows10 기반 Autoset10(Apache 2.4.17, PHP 7.0, MariaDB 10.1.9 ) : 정상


리눅스 기반 PHP 5.6.30, MySQL 5.6.30 에서 테스트를 하다보니 일부 함수가 문제가 발생하는 걸 알았다.

mysqlnd 가 설치되지 않아서였더라.

phpize로 추가 설치하는 방법을 검색해봤는데 아직 해결책을 못찾았다.

정 안되면 APM(Apache + PHP + MySQL) 소스 설치를 다시 해볼 생각이다.


- SHA1 패스워드를 적용한 알고리즘이 좋더라. 그래서 이걸 수정/추가해서 코드를 보완

- 아이폰/안드로이드폰에서 접속시 폰의 고유장치번호를 인식하여 최초 등록 단말이 아닌 경우 로그인 불가처리

- 테이블명을 변경하고 칼럼을 추가하고 user 권한을 설정하는 걸 추가


테이블 구조

- phpMyAdmin SQL에서 아래코드를 붙여넣기 하면 자동으로 DB명과 테이블을 생성한다.

- DB명과 user 권한을 수정해서 이용하면 된다.


DB_Functions 클래스 및 DB 접속정보는 첨부파일 참조

- DB_Functions 클래스가 핵심 내용이다.

 <?php

class DB_Functions {
    private $conn;

    // 생성자
    function __construct() {
        require_once 'dbconnect.php';
        // DB 연결
        $db = new Db_Connect();
        $this->conn = $db->connect();
    }

    // 소멸자(destructor)
    function __destruct() {

    }

    // 회원 정보 신규 입력
    public function storeUser($userID, $name, $email, $password, $telNO, $mobileNO) {
        $hash = $this->hashSSHA($password);
        $encrypted_password = $hash['encrypted']; // encrypted password
        $salt = $hash['salt']; // salt

        $stmt = $this->conn->prepare("INSERT INTO members(userID, userNM, email, passwd, salt, telNO,mobileNO,created_at) VALUES(?, ?, ?, ?, ?, ?, ?, NOW())");
        $stmt->bind_param("sssssss", $userID, $name, $email, $encrypted_password, $salt, $telNO, $mobileNO);
        $result = $stmt->execute();
        $stmt->close();

        // check for successful store
        if ($result) {
            $stmt = $this->conn->prepare("SELECT * FROM members WHERE userID = ?");
            $stmt->bind_param("s", $userID);
            $stmt->execute();
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();

            return $user;
        } else {
            return false;
        }
    }

    // 로그인 체크
    public function getUser($userID, $password) {
        $stmt = $this->conn->prepare("SELECT * FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);

        if ($stmt->execute()) {
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();

            // verifying user password
            $salt = $user['salt'];
            $encrypted_password = $user['passwd'];
            $hash = $this->checkhashSSHA($salt, $password);
            // check for password equality
            if ($encrypted_password == $hash) {
                // user authentication details are correct
                return $user;
            }
        } else {
            return NULL;
        }
    }

    // 안드로이드/아이폰 로그인 체크
    public function LoginUserChk($userID,$password,$deviceID){
        if(empty($userID) || empty($password)){
            return 0;
        } else {
            $user = $this->getUser($userID, $password);
            if($user['idx']>0){
                // 장치 일련번호 체크
                if($user['phoneSE'] == NULL){
                    // 신규 장비번호 입력(최초 로그인)
                    $this->LoginUserEquipInput($userID,$deviceID);
                    return $user['idx'];
                } else {
                    if($user['phoneSE'] === $deviceID){
                        return 1; // 일련번호 일치
                    } else {
                        return -1; //일련번호 불일치
                    }
                }
            } else {
                return 0; //계정오류
            }
        }

    }

    // 장치번호 업데이트
    public function LoginUserEquipInput($userID,$deviceID){
        if(strlen($deviceID)>0 && is_numeric($deviceID)){ // 안드로이드폰
            $ostype = 2;
        } else if(strlen($deviceID)>30){ // 아이폰
            $ostype = 1;
        } else { // 기타
            $ostype = 0;
        }

        $sql='update members set phoneSE=?, OStype=? where userID=?';
        $stmt = $this->conn->prepare($sql);
        $stmt->bind_param("sss", $deviceID, $ostype, $userID);
        $status = $stmt->execute();
        if($status == true){
            return 1;
        } else {
            return 0;
        }
    }//end

    // 장치번호 초기화
    public function EquipReset($userID){
        $ostype = 0;
        $sql='update members set phoneSE=NULL, OStype=? where userID=?';
        $stmt = $this->conn->prepare($sql);
        $stmt->bind_param("ss", $ostype, $userID);
        $status = $stmt->execute();
        if($status == true){
            return 1;
        } else {
            return 0;
        }
    }//end

    // 회원 가입 여부 체크
    public function isUserExisted($userID) {
        $stmt = $this->conn->prepare("SELECT userID from members WHERE userID = ?");

        $stmt->bind_param("s", $userID);
        $stmt->execute();
        //$stmt->store_result();
        $result = $stmt->get_result();

        if ($result->num_rows > 0) {
            // user existed
            $stmt->free_result();
            $stmt->close();
            return true;
        } else {
            // user not existed
            $stmt->free_result();
            $stmt->close();
            return false;
        }
    }

    // 회원 정보 삭제
    public function deleteUser($userID){
        $stmt = $this->conn->prepare("delete FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);
        $stmt->execute();
        $stmt->close();
    }

    public function hashSSHA($password) {

        $salt = sha1(rand());
        $salt = substr($salt, 0, 10);
        $encrypted = base64_encode(sha1($password . $salt, true) . $salt);
        $hash = array("salt" => $salt, "encrypted" => $encrypted);
        return $hash;
    }

    public function checkhashSSHA($salt, $password) {
        $hash = base64_encode(sha1($password . $salt, true) . $salt);
        return $hash;
    }

}

?>


- user 권한 및 패스워드 설정을 다르게 한 경우에는 해당 파일을 수정해야 한다.


register.php

- 안드로이드에서 전송된 데이터라고 가정하고 값을 직접 입력하여 DB에 잘 저장되는지 테스트했다.


json 으로 넘길 데이터 형태는 개발자가 변경하고 싶은데로 변경하여 사용하면 된다.


login.php


등록된 폰과 다를 경우 관리자에게 문의하여 장치번호를 초기화하고 새로운 폰으로 다시 등록해야만 로그인이 가능해진다.


안드로이드 코드 부분은 다음에 기회되면 작성할 예정이다.

이미 기존에 로그인 처리하는 코드 예제가 있으므로 그걸 수정해서 활용하거나 PHP 코드를 일부 수정해서 사용해도 된다.


androidPHP_Login_and_Registration.zip



PDO 개념 및  사용법 : http://link2me.tistory.com/1332

PDO DB 연결방법 : http://link2me.tistory.com/1398

PDO MemberClass : http://link2me.tistory.com/1399

MVC 패턴 고려한 PDO Class 만들기 : http://link2me.tistory.com/1401

블로그 이미지

Link2Me

,