728x90

<?php

$num = 254; // 55 ~ 255 사이인지 검사방법
if(preg_match("/(^5[5-9]$|^[6-9]{1}[0-9]{1}$|^[1-2]{1}[0-4]{1}[0-9]{1}$|^25[0-5]$)/",$num)){
    echo $num.'<br />';
} else {
    echo 'The number is out of range.<br />';
}

?>


^ : 문자열의 시작
$ : 문자열의 끝
[] : 대괄호 사이에 존재하는 문자들중 하나에 일치
{} : 반복횟수
| :  OR

55 ~ 99 : 55 ~ 59, 60 ~ 99 로 분리해서 접근하면 ^5[5-9]$|^[6-9]{1}[0-9]{1}$
100 ~ 249 : 백의 자리는 1~2, 십의 자리는 0 ~ 4, 1의 자리는 0~9  ==> ^[1-2]{1}[0-4]{1}[0-9]{1}$
250 ~ 255 : ^25[0-5]$


$num = 48; // 1 ~ 50 사이인지 검사
if(preg_match("/(^[1-9]{1}$|^[1-4]{1}[0-9]{1}$|^50$)/m",$num)){
    echo $num.'<br />';
} else {
    echo 'The number is out of range<br />';
}


블로그 이미지

Link2Me

,
728x90

정규표현식은 1970년대에 등장한 오래된 기술이다.

유닉스나 리눅스 시스템을 다루는 사람이라면 더더욱 잘 알아야 한다.

정규표현식을 처음에 접하면 너무 생소해서 어렵게만 느껴진다.

 

정규표현식을 제대로 다룰 줄 모르다보니 구글링 검색만 많이 하게 되는데 조금이라도 이해하고자 적어본다.

정규표현식은 텍스트에서 원하는 패턴을 찾는 도구다.

 

 ? : 0 또는 하나     {0,1} 와 같다.
 + : 1개 이상       {1,} 와 같다.
 * : 0 개 이상        {0,} 과 같다.

 [] : []안에 있는 문자열 중 한문자. 정규표현식에서 대괄호는 일반 문자가 아닌 메타문자로 인식한다.
 {} : {} 바로 앞에 있는 문자열(또는 문자)의 개수
 () : ()안에 있는 문자들의 그룹화. ( : 참조그룹 시작, ) : 참조그룹 끝.
  | : OR 연산자 기능



 w : 영문자, 숫자, _, 기타 스크립트 문자
 W : 영문자, 숫자, _, 기타 스크립트 문자를 제외한 문자

 s : 공백
 S : 공백을 제외한 모든 문자
 D : 숫자가 아닌 모든 문자
 d : 숫자
 i : 대소문자 무시
 정규식으로 한글의 범위는 \xa1-\xfe 로 표현
 ^ : 문자열의 시작 

 $ : 문자열의 마지막. 행의 끝.
 . : 문자열중 임의의 한문자(개행문자 제외)
a{2}b : aab 를 가진 문자열을 사용할 수 있다. 즉 (2) 는 {} 앞에 있는 문자 a 의 개수가 2개인 것을 의미
 
() : 찾는 패턴의 전체 또는 일부분을 괄호를 사용해서 그룹으로 묶으면 그 내용이 임시로 메모리에 저장된다.

 

사용할 수 있는 문자열을 지정하는 정규 표현식의 문자는 [] 이다.

[-.]? : 하이픈 또는 점이 0번 또는 1번 나온다는 의미다.

특정 문자열을 사용할 수 없도록 지정하고 싶다면, 지정하는 대괄호 [] 사이에 삿갓(^) 표시를 입력하면 된다.

[^123] 과 같이 사용하면 1,2,3 숫자는 문자열로 사용할 수 없다.

[] 안의 두 문자 사이에 하이픈(-)을 사용하게 되면 두 문자 사이의 범위(From - To)를 의미한다.

[0-9] : 0 ~ 9 까지의 숫자를 찾는다.

[5-9] : 5 ~ 9 사이의 숫자를 찾는다.

\d : 0 ~9 까지의 숫자중 하나

 

\D : 숫자가 아닌 문자를 모두 찾는다.

[^0-9] : 숫자가 아닌 문자를 모두 찾는다.

 

바로 앞 문자열의 반복을 의미하는 *,+,? 문자

.* : 임의의 문자를 0개 이상 찾아라.

정규 표현식의 Dot(.) 메타 문자는 줄바꿈 문자인 \n를 제외한 모든 문자와 매치됨을 의미한다.

 

[^\n]* 과 같이 사용하면 개행문자(\n)가 아닌 문자를 0개 이상 찾아라.

 

정규 표현식에서 특수문자를 사용하기 위한 역슬래시(\)

실제 사용해야 할 특수문자가 있을 때는 그 문자 앞에 역슬래시(\)로 해당 문자열을 이스케이프해야 한다.

 

^[_0-9a-zA-Z-] : ^ 는 문자열의 시작을 알리는 것이다.
처음 문자열 시작을 0 에서 9 까지의 숫자나 소문자 a 에서 소문자 z, 대문자 A 에서 대문자 Z 까지 사용

/^[a-z]+$/i : 대소문자 구분없이 알파벳으로 이루어진 문자열

 

\ : 이스케이프 문자. 여러 메타 문자들을 이스케이프하여 그대로 사용할 수 있게 한다.
    *, ?, +, [, { ,(, ), }, ], ^, $, |, \, . 등

 

 

$idx = "192.168.1.1가나다aacc";
$idx = preg_replace("/[^0-9\.]/", "", $idx); // 숫자와 컴마 이외 제거

 

 

$str = "https://www.abc.com/abc.php?mode=n&p=1&keyword='love'";
// $str 의 내용중 mode까지의 문자열 저장
$str = preg_replace('/(mode)(.*)/',"$1",$str);
echo $str.'<br/>';

$str = '<th scope="col">에러코드</th>';
$str = preg_replace("/<\/?(td|tr|tbody)[^>]*>/i", "", $str);
// [^>]* 는 > 가 아닌 문자를 0개 이상 찾아라.
echo $str.'<br/>';

 

preg_match($pattern, $str, $match);
$pattern : 정규식 패턴, $str : 추출할 문장, $match : 추출된 값을 배열에 저장

preg_match_all("패턴", "추출할 문장", "결과 배열");

 

preg_match - 첫번째 매치가 일어나면 실행을 중지한다. (즉, 최대 매치횟수는 1회)
preg_match_all - 전체 매치된 횟수를 반환하며, 오류시 FALSE를 반환한다.

 

<?php

$str = "부산광역시 연제구 (연산 1동) 158-77";
//preg_match("/\(\S+\)/",$str,$matches);
//preg_match("/\([^\s]+\)/",$str,$matches); // \S 는 [^\s] 와 같다.
preg_match("/\(.*\)/",$str,$matches); // 괄호 시작과 종료안에서 .* 임의의 문자를 0개 이상 찾는다.

echo $matches[0]."<br />";
echo str_replace(array('(',')'),"",$matches[0]).'<br />';

##########################################
// http://www.naver.com
// https? : http 또는 https
// /^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/

$mobileNO = "010-1234-7777";
$mobileNO = preg_replace("/\D/", "", $mobileNO); // 숫자가 아닌 모든 문자는 제거하라.

$mobileNO = preg_replace("/(^01[0|1|6|7|8|9]{1})([0-9]{3,4})([0-9]{4})$/", "\\1-\\2-\\3", $mobileNO);

echo $mobileNO.'<br />';

// [] : []안에 있는 문자열 중 한문자

// () : ()안에 있는 문자들의 그룹화

// {} : {} 바로 앞에 있는 문자열(또는 문자)의 개수

// ^01 : 시작을 01로 시작하고 0,1,6,7,8,9 중 1개의 글자를 그룹화하라.

// ([0-9]{3,4}) : 숫자 0~9 중 3개 또는 4개를 그룹화하라.

// ([0-9]{4}) : 숫자 0~9 중 4개를 그룹화하라.

 

 

이미지 다운로드시 상위 폴더로 이동하기 위한 ../ 정보가 포함되어 있는 걸 방지

$str ="../../image.png";
$str=preg_replace("/\.{2}\//","",$str);
echo $str.'<br/>';

 

$url ="http://www.naver.com/index.php";
preg_match("/^(https?:\/\/)?([^\/]+)/i",$url,$matches);
// ^ 시작이 http:// 또는 https:// 로 시작하는 문자열이 ? (0 또는 1개)

// ?는 바로 앞에 있는 괄호의 반복()을 의미한다.

// ? : 0개 또는 1개
// + : 1개 이상
// [^\/] : /가 아닐 때까지 +(1개 이상) 즉, /를 만날때까지의 값

echo '<pre>';print_r($matches);echo '</pre>';

 

공백이 2칸 이상일 때 1칸으로 변경

$str ="I'm a office    worker.";
$str=preg_replace("/\s{2,}/"," ",$str);
// 연속된 공백을 1개의 공백으로 변경

echo $str.'<br/>';

// \s{2,} : 공백이 2개 이상이면

 

$str ="주석이전 내용; // 주석 처리할 내용";
$str = preg_replace('/\/\/([^\n]+)/', "", $str); // (.*) 과 ([^\n]+) 는 같다.
// //부터 시작하여 개행문자까지 모두 공백으로 처리하라.
echo $str.'<br />';

 

$str ="<!--홍길동-->";
preg_match("/--([^--]+)/",$str,$matches);

// -- 부터 시작해서 --를 만날때까지의 값 추출

echo '<pre>';print_r($matches);echo '</pre>';

 

SQL 인젝션 공격 시도 방지

SQL Injection은 웹 어플리케이션에서 DB에 Query시 입력된 데이터의 유효성 검증을 하지 않아,
개발자가 의도하지 않는 동적 쿼리(Dynamic Query) 를 생성하여 DB정보를 열람하거나 조작할 수 있는 보안 취약점이다.

$userID = "admin";
$password = "password' OR 1='1";
$sql = "SELECT * FROM members WHERE userID='{$userID}' AND passwd='{$password}'";
echo $sql.'<br/>'; // 이걸 DB 에서 실행해보면 패스워드 무력화가 된다는 걸 알 수 있다.
$sql=preg_replace("/\-/","",$sql); // 주석 제거
$sql=preg_replace("/\s{1,}1\=(.*)+/","",$sql); // 공백이후 1=1이 있을 경우 제거
$sql=preg_replace("/\s{1,}(or|and|null|where|limit)/i"," ",$sql); // 공백이후 or, and 등이 있을 경우 제거
echo $sql.'<br/>';

 

여기서 한가지 더 테스트를 해봤다.

$userID = "admin";
$password = mysqli_real_escape_string($db,"password' OR 1='1 --");
$sql = "SELECT * FROM members WHERE userID='{$userID}' AND passwd='{$password}'";
echo $sql.'<br/>';

 

결과 :
SELECT * FROM members WHERE userID='admin' AND passwd='password\' OR 1=\'1 --'

 

' 앞에 역슬레쉬(\)를 붙여서 동작이 제대로 안되도록 하는 거라는 걸 확인할 수 있다.

 

$userID = mysqli_real_escape_string($db,"<script>documnet.cookie();</script>admin");

을 해보면 "admin"만 출력한다는 걸 확인할 수 있다.

 

패스워드나 아이디 입력한 걸

$password = "password' OR 1='1 --";

$password = preg_replace("/[\s\t\'\;\"\=\-]+/","", $password); // 공백이나 탭 제거, 특수문자 제거

으로 하면 공백 자체를 입력하지 못하게 하여 SQL Injection 공격을 방지할 수 있다.

 

정규식을 아이디/패스워드 입력용, 검색용 등 각각 별도로 함수를 만들어서 사용하는 게 좋을 것이다.

 

 

$str = "<script>documnet.cookie();</script>admin";
$str = preg_replace("/\<script[^\>]*\>(.*?)\<\/script\>/", "", $str); // 스크립트 제거
echo $str.'<br/>';

$str ="<!-- HTML -->주석";
$str = preg_replace("/\<\!\-{2}[^\>](.*?)\-{2}\>/", "", $str); // HTML 주석 제거
echo $str.'<br/>';

 

?>

 

플래그(flags)

 

정규 표현식 리터럴을 작성할 때 플래그를 사용하여 기본 검색 설정을 변경할 수 있다.

플래그(flag)

설명
i 검색 패턴을 비교할 때 대소문자를 구분하지 않도록 설정함.
g 검색 패턴을 비교할 때 일치하는 모든 부분을 선택하도록 설정함.
m Multi line을 표현하며 대상 문자열이 다중 라인의 문자열인 경우에도 검색하는 것을 의미한다.
y 대상 문자열의 현재 위치부터 비교를 시작하도록 설정함.
u 대상 문자열이 UTF-8로 인코딩된 것으로 설정함.

 

블로그 이미지

Link2Me

,
728x90

<?php
class DBController {
    private $host = 'localhost';
    private $database = 'test';
    private $userid = 'root';
    private $password = 'autoset';
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    public function __construct() {
        $this->db = $this->connectDB();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    function __destruct(){
        mysqli_close($this->connectDB());
        //mysqli_close($this->db);
    }

    private function connectDB() {
        $dbconn = mysqli_connect($this->host, $this->userid, $this->password, $this->database);
        mysqli_set_charset($dbconn, "utf8"); // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
        if (mysqli_connect_errno()) {
           printf("Connect failed: %s\n", mysqli_connect_error());
           exit();
        } else {
          return $dbconn;
        }
    }

}//end dbClass
?>

<?php
class LoginClass extends DBController {
// class 자식클래스 extends 부모클래스
    // 회원 가입 여부 체크
    public function isUserExisted($u) {
        if(!isset($u) || empty($u)) {
            return '-1';
        } else {
            $sql ="SELECT count(userID) from members WHERE userID='".$u."'";
            $result = mysqli_query($this->db, $sql);
            if($row = mysqli_fetch_array($result)){
                return $row[0]; // 미가입이면 0 반환, 가입이면 1 반환
            } else {
                return -1;
            }
        }
    }
}//end class LoginClass
?>


PHP 함수를 만들어서 사용하는 경우 개수를 체크하기 위해 count(*)를 사용한다.
이 경우 반환값은 정수일까? String(문자열)일까?

아래 코드를 직접 실행해보면 결과가 나온다.
변수 타입 확인은 getttype 함수 또는 var_dump 함수로 확인할 수 있다.
<?php
$userID ='jsk005@naver.com';
include_once 'dbController.php';
require_once 'loginClass.php';
$c = new LoginClass();
$rs = $c->isUserExisted($userID);

if($rs === 1){
    echo 'Variable Type is Integer<br />';
} else if($rs == '1'){
    echo '
Variable Type is string <br />';
    echo gettype($rs);
    //var_dump($rs);
}
?>

결과는 정수 타입이 아니라 String 이다.


블로그 이미지

Link2Me

,
728x90

MySQLi 객체지향 접속방식으로 코드를 작성한 회원가입과 로그인 예제를 만들었다.

모두 꼼꼼하게 테스트하여 잘 동작함을 확인했다.


안전한 비밀번호 작성 규칙

- 영문자, 숫자, 특수문자 중 2가지 종류를 조합할 때에는 10자리 이상 / 3가지 종류를 조합할 때에는 8자리 이상으로 설정

- 비밀번호는 최소 6개월에 한번씩 변경

- 누구나 쉽게 알 수 있는 비밀번호 사용은 자제


<?php
// DB 정보 입력
define("DB_HOST", "localhost");
define("DB_DATABASE", "test");
define("DB_USER", "root");
define("DB_PASSWORD", "autoset");
?>

<?php
class DBController {
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    // 생성자
    function __construct() {
        $this->db = $this->connectDB();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    // 소멸자(destructor)
    function __destruct() {
        mysqli_close($this->connectDB());
    }

    private function connectDB() {
        require_once 'config.php';
        // MySQLi 객체지향 DB 연결
        $conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
        return $conn; // return database handler
    }

}
?>

<?php
class LoginClass extends DBController {
    // class 자식클래스 extends 부모클래스
    // override : 부모 클래스와 자식 클래스가 같은 메소드를 정의했을 경우 자식 클래스가 우선시된다.

    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;
    }

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

        $stmt = $this->db->prepare("INSERT INTO members(userID, userNM, passwd, salt, regdate) VALUES(?, ?, ?, ?, ?)");
        $stmt->bind_param("sssss", $userID, $userNM, $encrypted_password, $salt,$regdate);
        $result = $stmt->execute();
        $stmt->close();

        // check for successful store
        if ($result) {
            $user = $this->getUser($userID, $password);
            //echo '<pre>';print_r($user);echo '</pre>';
            $rs = $this->storeUserDetail($user['uid'],$userNM,$userID,$mobileNO);
            if($rs == 1){
                return $user;
            } else {
                return -1;
            }
        } else {
            return -1; // 0
        }
    }

    // 회원 세부 정보 입력
    public function storeUserDetail($relateduid,$userNM,$email,$mobileNO){
        $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
        $sql ="INSERT INTO member_data(relateduid,userNM,email,mobileNO) VALUES(?, ?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $stmt->bind_param("isss", $relateduid, $userNM, $email, $mobileNO);
        $result = $stmt->execute();
        $stmt->close();
        if($result){
            return 1;
        } else {
            return 0;
        }
    }


    // 로그인 체크
    public function getUser($userID, $password) {
        $stmt = $this->db->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['uid']>0){ // 가입자 정보가 있다면
                // 장치 일련번호 체크
                if($user['phoneSE'] == NULL){
                    // 신규 장치번호 입력(최초 로그인)
                    $this->LoginUserEquipInput($userID,$deviceID);
                    return 1;
                } 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->db->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->db->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->db->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->db->prepare("delete FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);
        $stmt->execute();
        $stmt->close();
    }

    // 인증이 성공한 상태에서 직접 접속을 시도하는 외부 접속으로부터 보호
    function checkReferer($url) {
        $referer = $_SERVER['HTTP_REFERER'];
        $res = strpos($referer,$url);
        return $res == 0 ? false : true;
    }
}
?>

 <?php
if (!isset($_SESSION)) {
    session_start();
}

// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    //echo '<pre>';print_r($_POST);echo '</pre>'; // 전송받은 배열을 확인하고 싶을 때 사용
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'config/dbconnect.php';
        require_once 'config/loginClass.php';
        $c = new LoginClass();
        if ($c->isUserExisted($userID) === true) {
            echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
        } else {
            $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
            // 회원 등록
            $user = $c->storeUser($userID, $userNM, $password, $mobileNO);
            if($user) {// 회원 등록 성공
                $_SESSION['userID'] = $user['userID'];
                $_SESSION['userNM'] = $user['userNM'];
                $_SESSION['admin'] = $user['admin'];
                echo json_encode(array('result' => '1'));
            } else {
                // 회원 등록 실패
                echo json_encode(array('result' => '-1'));
            }
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
}
?>

 <?php
// 파일을 직접 실행하는 비정상적 동작을 방지 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'config/dbconnect.php';
        require_once 'config/loginClass.php';
        $c = new LoginClass();
        $user = $c->getUser($userID, $password);
        //echo '<pre>'; print_r($user);echo '</pre>';
        if ($user != false) {
            $_SESSION['userID'] = $user['userID'];
            $_SESSION['userNM'] = $user['userNM'];
            $_SESSION['admin'] = $user['admin'];
            echo json_encode(array('result' => '1'));
        } else {
            echo json_encode(array('result' => '0'));
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
} else { // 비정상적인 접속인 경우
    echo 0; // a.loginChk.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>


직접 실행해 볼 수 있는 테이블 구조 및 관련 파일을 모두 첨부한다.

안드로이드에서 로그인하는 코드 샘플도 포함되어 있다.


mysqlI_login.zip



블로그 이미지

Link2Me

,
728x90

KIA 성적이 전년도 우승팀 답지 않게 중위권에서 놀고 있는 이유는 뭘까?



올해 투수력이 작년만큼 강하지 못하고 경쟁팀들은 전력보강 및 전력분석을 통해 KIA 투수의 약점을 공략하고 있다.

구위가 떨어져서일까?

김민식 포수는 타력보강을 위해 우승후에 캠프에 자청하고 가서 타격을 보강했다.

김민식은 작년도만큼 절심함도 없는데다가 투수리드가 타격보다 훨씬 더 중요하다는 걸 간과하고 있다.

가장 중요한 것은 2017년도 KIA가 SK와 4:4 트레이드를 통해서 약점이었던 포수를 보강하고 최형우가 4번 타자의 몫을 톡톡히 해주면서 시너지가 급상승했다.

그러면서 중위권에서 1위로 분위기가 급반전되면서 우승을 차지했다.

올해 헥터 투수도 난타를 많이 당했다. 하지만 한승택과 호흡을 맞춘 경기에서는 점수도 적게 주고 투구수도 훨씬 더 적게 주면서 안정적인 모습을 보여줬다.

타격이 좀 약한 한승택을 2군으로 보내고, 백용환을 올리는데 김기태 감독은 투수리드의 가치가 얼마나 큰 지를 잘 모르는 거 같다.

투수리드 능력이 좋은 포수를 기용하는 것이 공격형 포수보다 훨씬 더 중요하다.

NC는 김태군 포수가 군입대를 하면서 포수가 엄청 약해지면서 NC가 꼴찌를 하고 있다.

투수리드가 읽혀서 대량실점을 많이 하게 했던 정범모 포수를 NC가 트레이드 하여 경기에 임하지만 좋아지기는 했어도 아직 점수를 주는 편이다.


KIA가 반등하는 것은 투수리드 능력이 좋은 한승택을 더 자주 기용하는 것이라고 본다.

김민식 포수는 타격 잘하려고 노력할 시간에 상대 타자분석하고 투수리드를 어떻게 하면 더 잘할 것인지 연구해야 한다.

김민식 포수와 호흡을 맞추면 공을 던지는 투구수가 한승택에 비해 더 많다.

박경완 포수처럼 최고의 야전사령관이 되려면 끊임없이 노력해야 한다.

포수 능력이 좋은 팀이 상위권이라는 건 두산을 보면 알 수 있지 않는가.


김기태 감독은 선수 기용을 본인이 트레이드하거나 영입했던 선수 중심이 아니라 객관적인 관점에서 기용해야 한다.

연봉이 높은 선수라도 못하면 기용하지 말고 열심히 하는 선수에게 기회를 줘야 한다.

윤석민 3닝 혹시 마무리해서 안좋은 결과를 만들었으면 양현종 투구수 관리도 해주고 선수를 아끼는 모습을 보여줘야 하는데 참 아쉽다..

공평한 기회를 준다는 느낌이 들도록 선수를 기용하길 ...

'스포츠' 카테고리의 다른 글

넥센 야구가 더 성장하려면  (0) 2014.11.23
심창민 데드볼을 지켜보며.... 데드볼과 선수멘탈  (0) 2013.06.07
김상현 트레이드  (0) 2013.05.07
블로그 이미지

Link2Me

,
728x90

폴더안에 있는 파일들의 파일명을 배열(array)로 저장하고 출력해보자.

basename($_FILES['b_file']['name']); // basename() 메소드는 경로를 제외하고 파일명만 추출

pathinfo($filename, PATHINFO_FILENAME); //확장자를 제외한 파일명
pathinfo($filename, PATHINFO_EXTENSION); // 파일 확장자 구하기


<?php
$dir ='./photos'; // 폴더명 지정

$fileList = getCurrentFileList($dir);
echo count($fileList).'개 검색됨<br />';
foreach($fileList as $f){
    echo $f.'<br />';
}

function getCurrentFileList($dir){
    $valid_formats = array("jpg","png");
    $handle = opendir($dir); // 디렉토리의 핸들을 얻어옴
    // 지정한 디렉토리에 있는 디렉토리와 파일들의 이름을 배열로 읽어들임
    $R = array(); // 결과 담을 변수 생성
    while ($filename = readdir($handle)) {
        if($filename == '.' || $filename == '..') continue;
        $filepath = $dir.'/'.$filename;
        if(is_file($filepath)){ // 파일인 경우에만
            $getExt = pathinfo($filename, PATHINFO_EXTENSION); // 파일 확장자 구하기
            if(in_array($getExt, $valid_formats)){
                array_push($R,basename($filename,'.jpg')); // 파일이름만 선택하여 배열에 넣는다.
            }
        }
    }
    closedir($handle);
    sort($R); // 가나다순으로 정렬하기 위해
    return $R;
}
?>


블로그 이미지

Link2Me

,
728x90

PHP 에서 배열의 결과를 json 으로 반환하는 코드다.

네이버지식인에 간혹 화면에 결과가 없다고 질의가 올라오는 코드라서 아주 간단하게 적어본다.

json_encode 는 UTF-8로 된 것만 인식한다고 이해하면 된다.

DB, 서버와의 통신, 파일 인코딩 모드를 모두 UTF-8로 기본 설정해야 한다.


아래 코드를 파일로 첨부한다. 친절하게 테스트해보라고 테이블 구조까지 포함했다.


JSON.zip



<?php
extract($_GET);
$userID ="jsk005@naver.com"; // $_GET 배열로 넘어온 데이터라고 가정한다.

include_once 'dbconnect.php';

$sql = "SELECT * FROM members WHERE userID='".$userID."'";

$result = mysqli_query($dbconn,$sql) or die(mysqli_error($dbconn));
$R = array();
while($row = mysqli_fetch_array($result)){
array_push($R, array("userID"=>$row['userID'], "userNM"=>$row['userNM'], "OSType"=>$row['OStype']));
}

//echo '<pre>';print_r($R);echo '</pre>'; // 데이터가 제대로 들어있는지 확인 용도

echo json_encode(array("result"=>$R)); // 출력은 파일 Encoding 모드가 UTF-8 이어야 한다.
mysqli_close($dbconn);
?>

<?php
$dbconn = isConnectDb(); // DB 연결 함수

function isConnectDb(){
    $dbhost = 'localhost';
    $database = 'test';
    $dbuser = 'root';
    $dbpass = 'autoset';
    $dbport = "3306";
    $conn = mysqli_connect($dbhost,$dbuser,$dbpass,$database,$dbport);
    mysqli_set_charset($conn, "utf8");  // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
    if (mysqli_connect_errno()) {
        echo "Failed to connect to MySQL: " . mysqli_connect_error();
        exit;
    } else {
        return $conn;
    }
}
?>



블로그 이미지

Link2Me

,
728x90

http://brackets.io/ 에서 설치 파일을 다운로드 한다.

Adobe에서 제공하는 브라켓은 오픈 소스로 만들어진 텍스트 편집기로 웹 개발에 필요한 기능을 포함하고 있는 웹 개발 도구다.

확장기능을 설치하면 편리하게 사용할 수 있는 text editor 인거 같다.

브라켓 에디터를 설치한 이유는 NodeJS 코드 연습을 위한 목적이다.


일반적인 기능 설명은 https://opentutorials.org/course/2418/13455 생활코딩 사이트의 유투브 강좌에 잘 설명되어 있다.




설치 폴더는 Autoset9 에 설치되도록 변경했다.


프로그램을 실행하여 보기 --> 테마를 선택하여 [테마 설정]에서 아래와 같이 글꼴 크기를 조정한다.


실시간 미리보기를 누르면 크롬브라우저가 자동으로 연동되어 코드를 수정하면 실시간으로 크롬브라우저에서 변경된 사항을 확인할 수 있다.


확장기능 설치 : 파일 -> 확장기능 관리자

- Emmet : 편리한 자동완성 기능

- Custom Work : 파일을 탭 형식으로 보여주기

- Number Tabs

- Brackets Outline List  : 한 파일에 함수들을 한번에 볼 수 있는 플러그인

- NodeJS integration : 브라켓에서 노드 프로그램 실행하기



NodeJS 확장기능을 설치하고 실행하면 이런 에러메시지가 나온다.



파일을 선택하지 않아서 생기는 현상이다.



파일을 선택하고 실행버튼을 누르면 결과가 나온다.


실행할 파일을 제거하는 방법은



파일을 제거 또는 추가해주면 리스트에 보인다.

블로그 이미지

Link2Me

,
728x90

코드는 Morden PHP 라고 보기에는 좀 약할 수 있다. 하지만 참고하면 도움이 될 코드가 엄청 많다.

회원가입과 로그인, 최신 패스워드 권장 적용방식, 중복체크가 가능하고 방식은 Ajax 방식으로 처리한다.

jQuery, CSS 코드, PHP Class 함수는 안드로이드폰 로그인과의 연동부분까지 고려되어 있으니 입문 개발자에게는 훌륭한 샘플이 될 것이다.

메인 화면 Layout 구성은 초간단으로 기능 동작 중심이다. 회원가입과 로그인만 제대로 되는지 체크하는 용도라고 보면 된다.


== 회원가입 폼 ===

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    // 회원가입처리
    $('#join-submit').click(function(e){
        e.preventDefault();
        var userNM = $("input[name='userNM']");
        if( userNM.val() =='') {
            alert("성명을 입력하세요");
            userNM.focus();
            return false;
        }

        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        var mobileNO = $("input[name='mobileNO']");
        if(mobileNO.val() ==''){
            alert('휴대폰 번호를 입력하세요');
            mobileNO.focus();
            return false;
        } else if(!/^[0-9]{10,11}$/.test(mobileNO.val())){
            alert("휴대폰 번호는 숫자만 10~11자리 입력하세요.");
            return false;
        } else if(!/^(01[016789]{1}|02|0[3-9]{1}[0-9]{1})([0-9]{3,4})([0-9]{4})$/.test(mobileNO.val())){
            alert("유효하지 않은 전화번호 입니다.");
            return false;
        }

        var password = $("input[name='Password']");
        var repassword = $("input[name='rePassword']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        if(password.val().search(/\s/) != -1){
            alert("비밀번호는 공백없이 입력해주세요.");
            return false;
        }
        if(!/^[a-zA-Z0-9!@#$%^&*()?_~]{8,20}$/.test(password.val())){
            alert("비밀번호는 숫자, 영문, 특수문자(!@$%^&*?_~ 만 허용) 조합으로 8~20자리를 사용해야 합니다.");
            return false;
        }
        // 영문, 숫자, 특수문자 2종 이상 혼용
        var chk=0;
        if(password.val().search(/[0-9]/g) != -1 ) chk ++;
        if(password.val().search(/[a-z]/ig)  != -1 ) chk ++;
        if(password.val().search(/[!@#$%^&*()?_~]/g) != -1) chk ++;
        if(chk < 2){
            alert("비밀번호는 숫자, 영문, 특수문자를 두가지이상 혼용하여야 합니다.");
            return false;
        }
        // email이 아닌 userID 인 경우에는 체크하면 유용. email은 특수 허용문자에서 걸러진다.
        /*
        if(password.val().search(userID.val())>-1){
            alert("userID가 포함된 비밀번호는 사용할 수 없습니다.");
            return false;
        }
        */
        if(repassword.val() =='') {
            alert("비밀번호를 다시 한번 더 입력하세요!");
            repassword.focus();
            return false;
        }
        if(password.val()!== repassword.val()){
            alert('입력한 두 개의 비밀번호가 일치하지 않습니다');
            return false;
        }

        //var loginpath =$("#ajaxPath").attr('data-path');
        $.ajax({
            url: 'a.register.php',
            type: 'POST',
            data: {
                userNM:userNM.val(),
                userID:email.val(),
                password:password.val(),
                mobileNO:mobileNO.val()
            },
            dataType: "json", // json, text
            success: function (response) {
                //alert(response); //text 로 하고 a.register.php 에서 print_r을 사용하면 넘어간 데이터를 확인 가능
                if(response.result == 1){
                    alert('가입 완료');
                    location.replace('index.php'); // 화면 갱신
                } else if(response.result == 0){
                    alert('이미 가입된 아이디입니다');
                } else if(response.result == -2){
                    alert('입력 데이터를 확인하세요');
                } else {
                    //alert('등록중에 에러가 발생했습니다' + response);
                    alert('등록중에 에러가 발생했습니다');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });

    });

    // userID(e-mail) 가입여부 검사
    $("#checkid").click(function(e){
        e.preventDefault();
        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        $.ajax({
            url: 'a.joinChk.php',
            type: 'POST',
            data: {userID:email.val()},
            dataType: "json",
            success: function (msg) {
                //alert(msg); // 확인하고 싶으면 dataType: "text" 로 변경한 후 확인 가능
                if(msg.result == 1){
                    alert('사용 가능합니다');
                } else if(msg.result == 0){
                     alert('이미 가입된 아이디입니다');
                    email.val('');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });

});
</script>
</head>
<body>
<div class="container">
<h2>회원가입 Form 예제</h2>
<form method="post" action="a.register.php">
    <table>
        <tr>
            <td style='width:100px'>이름</td>
            <td><input type="text" size=37 name="userNM" value=""></td>
        </tr>
        <tr>
            <td>E-Mail</td>
            <td>
                <input type="text" size=25 name="email" value="">
                <input type="button" id="checkid" value="중복체크">
            </td>
        </tr>
        <tr>
            <td>휴대폰번호</td>
            <td><input type="text" size=37 name="mobileNO" value=""></td>
        </tr>
        <tr>
            <td>비밀번호</td>
            <td><input type="password" size=37 name="Password"></td>
        </tr>
        <tr>
            <td>비밀번호(확인)</td>
            <td><input type="password" size=37 name="rePassword"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'>
                <input type="button" id="join-submit" value="회원가입">
            </td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 === 회원가입 a.register.php ===

<?php
if (!isset($_SESSION)) {
    session_start();
}

// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    //echo '<pre>';print_r($_POST);echo '</pre>'; // 전송받은 배열을 확인하고 싶을 때 사용
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'dbController.php';
        require_once 'loginClass.php';
        $c = new LoginClass();
        if ($c->isUserExisted($userID)==1) {
            echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
        } else {
            $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
            // 회원 등록
            $user = $c->storeUser($userID, $userNM, $password, $mobileNO);
            if ($user) {// 회원 등록 성공
                $_SESSION['userID'] = $user['userID'];
                $_SESSION['admin'] = $user['admin'];
                echo json_encode(array('result' => '1'));
            } else {
                // 회원 등록 실패
                echo json_encode(array('result' => '-1'));
            }
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
}
?>

 === 로그인 폼 ===

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    $('#login-submit').click(function(e){
        var userID = $("input[name='userID']");
        if( userID.val() =='') {
            alert("아이디를 입력하세요");
            userID.focus();
            return false;
        }

        var password = $("input[name='password']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        $.ajax({
            url: 'a.loginChk.php',
            type: 'POST',
            data: {userID:userID.val(), password:password.val()},
            dataType: "json",
            success: function (response) {
                if(response.result == 1){
                    //alert('로그인 성공');
                    location.href='index.php';
                } else if(response.result == -2){
                     alert('입력한 값에 문제가 있습니다');
                } else {
                    alert('로그인 실패');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });
});
</script>
</head>
<body>
<div class="container">
<h2>로그인 Form 예제</h2>
<form method="post" action="a.loginChk.php">
    <table>
        <tr>
            <td style="font-size:14px">로그인 ID</td>
            <td><input type="text" name="userID" value=""></td>
        </tr>
        <tr>
            <td style="font-size:14px">패스워드</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan='2' align=left>
                <input type="button" id="login-submit" value="로그인">
            </td>
        </tr>
        <tr>
            <td colspan='2'><p>Don't have an account? <a href="joinForm.php">Sign up now</a>.</p></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 === 로그인 체크 ===

<?php
// 파일을 직접 실행하는 비정상적 동작을 방지 하기 위한 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'dbController.php';
        require_once 'loginClass.php';
        $c = new LoginClass();
        $user = $c->getUser($userID, $password);
        if ($user != false) {
            $_SESSION['userID'] = $user['userID'];
            $_SESSION['userNM'] = $user['userNM'];
            $_SESSION['admin'] = $user['admin'];
            echo json_encode(array('result' => '1'));
        } else {
            echo json_encode(array('result' => '0'));
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
} else { // 비정상적인 접속인 경우
    echo 0; // a.loginChk.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>

 == 가입여부 체크 ===

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.
    include_once 'dbController.php';
    require_once 'loginClass.php';
    $d = new DBController();
    $c = new LoginClass();
    $rs = $c->isUserExisted($userID);
    if($rs == '0'){
        echo '{"result":"1"}';
    } else {
        echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
    }
}
?>


주요코드는 위에 열거하였고 나머지 코드에 대해서는 첨부파일을 참조하면 된다.

첨부파일에는 SQL 테이블 구조까지 포함되어 있고, 100% 동작되는지 여부를 확인한 코드다.

login.zip


인터넷에 올려진 코드가 잘 동작하지 않는 것은 변수명을 살짝 바꾸거나, 한두개 핵심적인 것을 빼먹어서 그런 경우가 참 많다. 코드가 제대로 동작 안되는 것은 본문에 있는 내용과 어떤 부분이 약간 다른지 확인하면 해결될 문제인데, 일단 시도를 안해보는 거 같다. 첨부한 예제는 오래된 책에 나온 구닥다리 PHP 와는 비교되지 않는 좋은 샘플 예제다.

무조건 실행해서 결과가 OK 되는 걸 원하는 건 초보자로서 당연할 수 있다.

하지만 개념 이해를 위한 공부를 해야 내 것이 된다.

login.zip 파일과 login_2.zip 파일 내용을 비교하면서 뭐가 틀려서 안되었던 걸까 하고 이해를 해야 한다.

이해를 하기 위해서는 파일 비교하는 방법을 알아야 한다.

https://link2me.tistory.com/1327 에 AcroDiff.exe 라는 파일이 있다.

최근에 개발자들이 Visual Studio Code 를 선호하여 AcroEdit 라는 툴이 있는지 모를 수 있지만, 이 에디터에 포함된 AcroDiff.exe 파일은 파일의 내용 비교에는 최고의 툴이다.

이걸 이용해서 파일을 서로 비교해보면 어디가 달라져 있고, 무엇 때문에 질문을 했는지 이해가 될 것이다.

login_2.zip


블로그 이미지

Link2Me

,
728x90

회원가입폼과 기가입여부를 체크하는 기능이 포함된 Form 예제다.

jQuery, Ajax 를 사용하여 입력 필드 체크와 휴대폰번호 및 비밀번호 유효성 검사, 이미 가입된 ID인지 여부를 체크하는 기능, 회원가입 자료 저장, 로그인 처리 기능이 포함되어 있다.

첨부파일에는 테이블 구조 SQL 까지 포함되어 있으므로 윈도우 기반 Autoset9 에서 직접 테스트해볼 수 있다.


간략하게 사용법 순서를 적어보면....

1. 테이블 구조 SQL 문을 autoset9 에서 테이블 생성한다.

    members 테이블과 member_data 테이블 두개의 테이블로 나누었고 PHP에서 JOIN 으로 데이터를 처리한다.

 CREATE TABLE IF NOT EXISTS `members` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `userID` varchar(50) NOT NULL DEFAULT '',
  `userNM` varchar(20) NOT NULL,
  `passwd` varchar(80) NOT NULL,
  `salt` varchar(10) NOT NULL,
  `admin` tinyint(2) NOT NULL DEFAULT '0',
  `phoneSE` varchar(80) DEFAULT NULL,
  `OStype` tinyint(2) NOT NULL DEFAULT '0',
  `regdate` char(14) NOT NULL DEFAULT '',
  `curdate` char(14) DEFAULT NULL COMMENT '최근접속',
  PRIMARY KEY (`uid`),
  UNIQUE KEY `userID` (`userID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

 CREATE TABLE IF NOT EXISTS `member_data` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `relateduid` int(11) NOT NULL DEFAULT '0',
  `level` int(11) NOT NULL DEFAULT '0',
  `userNM` varchar(60) NOT NULL COMMENT '이름',
  `email` varchar(60) NOT NULL COMMENT '메일',
  `mobileNO` varchar(20) NOT NULL,
  `officeNO` varchar(20) DEFAULT NULL,
  `homeNO` varchar(20) DEFAULT NULL,
  `home_addr1` varchar(100) DEFAULT NULL,
  `home_addr2` varchar(50) DEFAULT NULL,
  `office_addr1` varchar(100) DEFAULT NULL,
  `office_addr2` varchar(50) DEFAULT NULL,
  `sex` tinyint(2) NOT NULL DEFAULT '0',
  `point` int(11) NOT NULL DEFAULT '0',
  `smart` tinyint(2) NOT NULL DEFAULT '0',
  `modifydate` char(14) NOT NULL DEFAULT '',
  PRIMARY KEY (`uid`),
  KEY `relateduid` (`relateduid`),
  KEY `mobileNO` (`mobileNO`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


2. 회원가입 폼

    - E-Mail 을 userID 로 사용하고 가입여부를 체크하는 중복체크 jQuery가 포함되어 있다.

    - E-Mail 주소 유효성 체크 기능이 포함되어 있다.

    - 휴대폰 번호 유효성 체크 기능이 포함되어 있다.

    - 비밀번호는 영문, 숫자, 특수문자 등을 혼용하여 가입하도록 했다.



3. 회원가입 처리 로직은 직접 구현해보도록 하여 입력값이 넘어오는지 여부만 체크하는 걸 주석처리했다.

    회원가입 결과를 확인할 수 있도록 내용을 수정 보완했다.

4. LoginForm.php 에서는 로그인 화면을 구현했다.

    로그인 체크 jQuery 와 Ajax 처리하는 걸 추가했다.

5. 회원가입/로그인이 성공하면 index.php 에서 SESSION을 검사하여 main.php 파일로 이동하도록 했다.


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    // 회원가입처리
    $("form").submit(function(){
        var userNM = $("input[name='userNM']");
        if( userNM.val() =='') {
            alert("성명을 입력하세요");
            userNM.focus();
            return false;
        }

        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        var mobileNO = $("input[name='mobileNO']");
        if(mobileNO.val() ==''){
            alert('휴대폰 번호를 입력하세요');
            mobileNO.focus();
            return false;
        } else if(!/^[0-9]{10,11}$/.test(mobileNO.val())){
            alert("휴대폰 번호는 숫자만 10~11자리 입력하세요.");
            return false;
        } else if(!/^(01[016789]{1}|02|0[3-9]{1}[0-9]{1})([0-9]{3,4})([0-9]{4})$/.test(mobileNO.val())){
            alert("유효하지 않은 전화번호 입니다.");
            return false;
        }

        var password = $("input[name='Password']");
        var repassword = $("input[name='rePassword']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        if(password.val().search(/\s/) != -1){
            alert("비밀번호는 공백없이 입력해주세요.");
            return false;
        }
        if(!/^[a-zA-Z0-9!@#$%^&*()?_~]{8,20}$/.test(password.val())){
            alert("비밀번호는 숫자, 영문, 특수문자(!@$%^&*?_~ 만 허용) 조합으로 8~20자리를 사용해야 합니다.");
            return false;
        }
        // 영문, 숫자, 특수문자 2종 이상 혼용
        var chk=0;
        if(password.val().search(/[0-9]/g) != -1 ) chk ++;
        if(password.val().search(/[a-z]/ig)  != -1 ) chk ++;
        if(password.val().search(/[!@#$%^&*()?_~]/g) != -1) chk ++;
        if(chk < 2){
            alert("비밀번호는 숫자, 영문, 특수문자를 두가지이상 혼용하여야 합니다.");
            return false;
        }
        // email이 아닌 userID 인 경우에는 체크하면 유용. email은 특수 허용문자에서 걸러진다.
        /*
        if(password.val().search(userID.val())>-1){
            alert("userID가 포함된 비밀번호는 사용할 수 없습니다.");
            return false;
        }
        */
        if(repassword.val() =='') {
            alert("비밀번호를 다시 한번 더 입력하세요!");
            repassword.focus();
            return false;
        }
        if(password.val()!== repassword.val()){
            alert('입력한 두 개의 비밀번호가 일치하지 않습니다');
            return false;
        }

    });

    // userID(e-mail) 가입여부 검사
    $("#checkid").click(function(e){
        e.preventDefault();
        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        $.ajax({
            url: 'a.joinChk.php',
            type: 'POST',
            data: {userID:email.val()},
            dataType: "json",
            success: function (msg) {
                //alert(msg); // 확인하고 싶으면 dataType: "text" 로 변경한 후 확인 가능
                if(msg.result == 1){
                    alert('사용 가능합니다');
                } else if(msg.result == 0){
                     alert('이미 가입된 아이디입니다');
                    email.val('');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });

});
</script>
</head>
<body>
<div class="container">
<h2>회원가입 Form 예제</h2>
<form method="post" action="a.register.php">
    <table>
        <tr>
            <td style='width:100px'>이름</td>
            <td><input type="text" size=37 name="userNM" value=""></td>
        </tr>
        <tr>
            <td>E-Mail</td>
            <td>
                <input type="text" size=25 name="email" value="">
                <input type="button" id="checkid" value="중복체크">
            </td>
        </tr>
        <tr>
            <td>휴대폰번호</td>
            <td><input type="text" size=37 name="mobileNO" value=""></td>
        </tr>
        <tr>
            <td>비밀번호</td>
            <td><input type="password" size=37 name="Password"></td>
        </tr>
        <tr>
            <td>비밀번호(확인)</td>
            <td><input type="password" size=37 name="rePassword"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'><input type="submit" value="회원가입"></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 body {    
    font-family: 나눔바른고딕, NanumBarunGothic, 맑은고딕, "Malgun Gothic", 돋움, Dotum, "Apple SD Gothic Neo", sans-serif;
    font-size: 12px;
    color: rgb(33, 33, 33);
    letter-spacing: 0.03em;
}

table {
    width: 600px;
}

tr {
    height: 50px;
}

input[type=text], input[type=password] {
    padding: 5px 10px; /* 상하 우좌 */
    margin: 3px 0; /* 상하 우좌 */
    font-family: inherit; /* 폰트 상속 */
    border: 1px solid #ccc; /* 999가 약간 더 진한 색 */
    font-size:14pt;
    box-sizing: border-box;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    -webkit-transition: width 0.4s ease-in-out;
    transition: width 0.4s ease-in-out;
}

input[type=text]:focus, input[type=password]:focus {
    border: 2px solid #555;
}

input[type=submit],input[type=button] {
    margin-top: 10px;
    width:100px;
    height:40px;
    line-height: 22px;
    padding: 5px, 10px; /* 상하 우좌 */
    background: #E6E6E6;
    color: #000000;
    font-size: 15px;
    font-weight: normal;
    letter-spacing: 1px;
    border: none;
    cursor: pointer;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

input[type=submit]:hover {
    background-color: #FFBF00;
}

 <?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.
    include_once 'dbController.php'; // http://link2me.tistory.com/1478 참조
    require_once 'loginClass.php';
    $c = new LoginClass();
    $rs = $c->isUserExisted($userID);
    if($rs == '0'){
        echo '{"result":"1"}';
    } else {
        echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
    }
}
?>

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    echo '<pre>';print_r($_POST);echo '</pre>';
}
?>


LoginClass 함수 만든 예제다.

<?php
class LoginClass extends DBController {
    // 회원 가입 여부 체크
    public function isUserExisted($u) {
        if(!isset($u) || empty($u)) {
            return '-1';
        } else {
            $sql ="SELECT count(userID) from members WHERE userID='".$u."'";
            $result = mysqli_query($this->db, $sql);
            if($row = mysqli_fetch_array($result)){
                return $row[0]; // 미가입이면 0 반환, 가입이면 1 반환
            } else {
                return -1;
            }
        }
    }

}//end class LoginClass

?> 


직접 테스트해보고 싶은 개발자의 타이핑 노고를 덜어주고자 테스트한 파일을 첨부한다.

Last Update : 2019.04.12 첨부파일 내용 보강(초보자를 위한 배려)


ex02.zip


https://link2me.tistory.com/1481 읽어보고 첨부된 예제를 받아서 분석하고 이해하면 많은 도움이 될 것이다.


도움되신 분은 공감 팍팍 눌러주세요.

블로그 이미지

Link2Me

,
728x90

PHP 에서 내용을 다른 파일로 전달하는 방법으로 가장 기본 예제가 로그인 예제다.

네이버 지식인에 올라오는 공통적인 질의사항에 대한 개념이해를 돕기 위함이다.

HTML5 기반 jQuery 입력 필드 체크, 모바일 환경을 고려했다.

<meta name="viewport" content="width=device-width, initial-scale=1"> 이 부분이 모바일 환경을 고려한 부분이다.

bootstrap 폼 예제는 아니다. 기본 Form 에 대한 이해를 하고 나면 bootstrap Form으로 변경하는 것도 쉽다.

Form 을 작성하려면 <form> 태그를 사용한다.

<form> 태그는 두가지 주요 속성을 가진다.

- action 속성 : 어디에 데이터를 보낼지의 URL을 지정

- method 속성 : 어떻게 데이터를 전송할지의 방법을 지정. method 속성값에는 POST와 GET의 두가지 방법이 있다.


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form1.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    $("form").submit(function(){
        var loginID = $("input[name='loginID']").val();
        var loginPW = $("input[name='loginPW']").val();

        if( loginID =='') {
            alert("아이디를 입력하세요");
            $("input[name='loginID']").focus();
            return false;
        }

        if(loginPW =='') {
            alert("패스워드를 입력하세요!");
            $("input[name='loginPW']").focus();
            return false;
        }
    });
});
</script>
</head>
<body>
<div class="container">
<h2>Form 예제1</h2>
<form method="post" action="form.action.php">
    <table>
        <tr>
            <td>로그인 ID</td>
            <td><input type="text" name="loginID" value=""></td>
        </tr>
        <tr>
            <td>패스워드</td>
            <td><input type="password" name="loginPW"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'><input type="submit" value="전송"></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 body {    
    font-family: 나눔바른고딕, NanumBarunGothic, 맑은고딕, "Malgun Gothic", 돋움, Dotum, "Apple SD Gothic Neo", sans-serif;
    font-size: 12px;
    color: rgb(33, 33, 33);
    letter-spacing: 0.03em;
}

table {
    width: 300px;
}

tr {
    height: 50px;
}

input[type=text], input[type=password] {
    width: 100%;
    padding: 5px 10px; /* 상하 우좌 */
    margin: 3px 0; /* 상하 우좌 */
    font-family: inherit; /* 폰트 상속 */
    border: 1px solid #ccc; /* 999가 약간 더 진한 색 */
    font-size:14pt;
    box-sizing: border-box;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    -webkit-transition: width 0.4s ease-in-out;
    transition: width 0.4s ease-in-out;
}

input[type=text]:focus, input[type=password]:focus {
    border: 2px solid #555;
}

input[type=submit] {
    margin-top: 10px;
    width:100px;
    height:40px;
    line-height: 22px;
    padding: 5px, 10px; /* 상하 우좌 */
    background: #E6E6E6;
    color: #000000;
    font-size: 15px;
    font-weight: normal;
    letter-spacing: 1px;
    border: none;
    cursor: pointer;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

input[type=submit]:hover {
    background-color: #FFBF00;
}

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // POST 배열로 넘어온 값 확인 목적
    echo '<pre>';print_r($_POST);echo '</pre>';
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.

    $ref = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER']:'';
    // 만약 사용자가 이전 페이지의 링크를 거치지 않고 브라우저에 URL을 직접 입력하여 접속한 경우는 ''
} else { // 비정상적인 접속인 경우
    echo 0; // form.action.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>


클라이언트에서 Javascript 로 검증하고 있으면 서버에서 재검증할 필요가 없다고 생각할 수도 있지만 이것은 크게 잘못된 생각이다. 공격자는 모든 요청을 자유롭게 서버에 전송할 수 있으므로, Javascript에서의 검증을 쉽게 빠져나갈 수 있다.


만약 사용자가 이전 페이지의 링크를 거지치 않고 직접 접속한 경우에는 브라우저에 URL을 직접 입력하여 접속한 경우에는 참조원 정보뿐만아니라 $_SERVER['HTTP_REFERER'] 도 없다.


테스트한 파일을 첨부한다.

form-01.zip


블로그 이미지

Link2Me

,
728x90

PHP 에서 MySQL 접속 dbController Class 만드는 코드다.

절차지향방식의 생성자, 소멸자 만드는 방법과 class MySQLDbClass extends DBController { } 예시다.


사용법

require_once '../phpclass/dbController.php';

$d = new DBController();


<?php
class DBController {
    private $host = 'localhost';
    private $database = 'survey';
    private $userid = 'root';
    private $password = 'autoset';
    protected $db;

    public function __construct() {
        $this->db = $this->connectDB();
    }

    function __destruct(){
        mysqli_close($this->connectDB());
        //mysqli_close($this->db);
    }

    private function connectDB() {
        $dbconn = mysqli_connect($this->host, $this->userid, $this->password, $this->database);
        mysqli_set_charset($dbconn, "utf8"); // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
        if (mysqli_connect_errno()) {
           printf("Connect failed: %s\n", mysqli_connect_error());
           exit();
        } else {
          return $dbconn;
        }
    }

    //DB-UID데이터
    function getUidData($table,$uid){
        return $this->getDbData($table,'uid='.(int)$uid,'*');
    }

    // DB Query Cutom 함수
    function getDbData($table,$where,$column) {
        $result = mysqli_query($this->db,'select '.$column.' from '.$table.($where?' where '.$this->getSqlFilter($where):''));
        $row = mysqli_fetch_array($result);
        return $row;
    }

    // DB Query result 함수
    function getDbresult($table,$where,$column) {
        $result = mysqli_query($this->db,'select '.$column.' from '.$table.($where?' where '.$this->getSqlFilter($where):''));
        return $result;
    }

    //DB데이터 ARRAY -> 테이블에 출력할 데이터 배열
    function getDbArray($table,$where,$flddata,$orderby,$rowsPage,$curPage){
        $sql = 'select '.$flddata.' from '.$table.($where?' where '.$this->getSqlFilter($where):'').($orderby?' order by '.$orderby:'').($rowsPage?' limit '.(($curPage-1)*$rowsPage).', '.$rowsPage:'');
        if($result = mysqli_query($this->db,$sql)){
            return $result;
        }
    }

    //DB데이터 레코드 총 개수
    function getDbRows($table,$where){
        $sql = 'select count(*) from '.$table.($where?' where '.$this->getSqlFilter($where):'');
        if($result = mysqli_query($this->db,$sql)){
            $rows = mysqli_fetch_row($result);
            return $rows[0] ? $rows[0] : 0;
        }
    }

    //DB삽입
    function getDbInsert($table,$key,$val){
        mysqli_query($this->db,"insert into ".$table." (".$key.")values(".$val.")");
    }

    //DB업데이트
    function getDbUpdate($table,$set,$where){
        mysqli_query('set names utf8');
        mysqli_query('set sql_mode=\'\'');
        mysqli_query($this->db,"update ".$table." set ".$set.($where?' where '.$this->getSqlFilter($where):''));
    }

    //DB삭제
    function getDbDelete($table,$where)    {
        mysqli_query($this->db,"delete from ".$table.($where?' where '.$this->getSqlFilter($where):''));
    }

    //SQL필터링
    function getSqlFilter($sql){
        return $sql;
    }

}//end dbClass

?>


만약 2개의 파일로 나누어서 만들었다면 첨부파일처럼 코드를 작성하면 된다.

dbClass.zip


블로그 이미지

Link2Me

,
728x90

접속한 단말을 구분하고 IP주소를 확인하는 코드다.

echo $_SERVER['HTTP_USER_AGENT'].'<br />';

이 한줄로 접속한 단말의 정보를 알 수 있다. 직접 확인해보라.

스마트폰 단말에서 접속이 글자가 너무 작아지는 걸 방지하기 위해서 헤더정보를 포함했다.


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex,nofollow"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<?php
echo $_SERVER['HTTP_USER_AGENT'].'<br />';
if(strpos($_SERVER['HTTP_USER_AGENT'],"iPhone")>0){
    echo '<br />'."접속단말 : 아이폰";
} else if(strpos($_SERVER['HTTP_USER_AGENT'],"Android")>0) {
    echo '<br />'."접속단말 : 안드로이드";
}

$ipAddress = getClientIP();
echo '<br />';
echo '<br />'.$ipAddress;

    function getClientIP() {
        if (isset($_SERVER)) {
            if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
                return $_SERVER["HTTP_X_FORWARDED_FOR"];

            if (isset($_SERVER["HTTP_CLIENT_IP"]))
                return $_SERVER["HTTP_CLIENT_IP"];

            return $_SERVER["REMOTE_ADDR"];
        }

        if (getenv('HTTP_X_FORWARDED_FOR'))
            return getenv('HTTP_X_FORWARDED_FOR');

        if (getenv('HTTP_CLIENT_IP'))
            return getenv('HTTP_CLIENT_IP');

        return getenv('REMOTE_ADDR');
    }

?>
</body>
</html>


접속단말이 Android 인 경우 Android 버전 정보를 알고 싶다면 어떻게 하면 될까?

if(strstr($_SERVER['HTTP_USER_AGENT'], 'Android')){
    preg_match('/Android (\d+(?:\.\d+)+)[;)]/', $_SERVER['HTTP_USER_AGENT'], $matches);
    echo $matches[1].'<br />';
}
로 하면 버전 정보를 추출할 수 있다.


'Web 프로그램 > PHP 응용 및 활용' 카테고리의 다른 글

PHP 달력 만들기 소스  (0) 2018.10.30
날짜 선택  (0) 2018.10.26
PHP 글자수 줄임 표시  (0) 2018.06.09
PHP to JSP  (0) 2018.04.28
PHP Form 다중 체크박스 값 전달 및 POST  (0) 2018.04.02
블로그 이미지

Link2Me

,
728x90

Apache AccessLog 를 분석하다보니 이런 패턴이 보인다. (시도한 IP주소는 삭제했음)



[01/Jun/2018:06:46:41 +0900] "GET /phpmyadmin/index.php HTTP/1.1" 404 218
[01/Jun/2018:06:46:41 +0900] "GET /phpMyAdmin/index.php HTTP/1.1" 404 218
[01/Jun/2018:06:46:41 +0900] "GET /pmd/index.php HTTP/1.1" 404 211
[01/Jun/2018:06:46:41 +0900] "GET /pma/index.php HTTP/1.1" 200 1
[01/Jun/2018:06:46:43 +0900] "GET /pma/index.php HTTP/1.1" 200 1
[01/Jun/2018:06:46:43 +0900] "GET /PMA/index.php HTTP/1.1" 404 211
[01/Jun/2018:06:46:43 +0900] "GET /PMA2/index.php HTTP/1.1" 404 212
[01/Jun/2018:06:46:43 +0900] "GET /pmamy/index.php HTTP/1.1" 404 213
[01/Jun/2018:06:46:44 +0900] "GET /pmamy2/index.php HTTP/1.1" 404 214
[01/Jun/2018:06:46:44 +0900] "GET /mysql/index.php HTTP/1.1" 404 213
[01/Jun/2018:06:46:44 +0900] "GET /admin/index.php HTTP/1.1" 404 213
[01/Jun/2018:06:46:44 +0900] "GET /db/index.php HTTP/1.1" 404 210
[01/Jun/2018:06:46:44 +0900] "GET /dbadmin/index.php HTTP/1.1" 404 215
[01/Jun/2018:06:46:44 +0900] "GET /web/phpMyAdmin/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /admin/pma/index.php HTTP/1.1" 404 217
[01/Jun/2018:06:46:44 +0900] "GET /admin/PMA/index.php HTTP/1.1" 404 217
[01/Jun/2018:06:46:44 +0900] "GET /admin/mysql/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /admin/mysql2/index.php HTTP/1.1" 404 220
[01/Jun/2018:06:46:44 +0900] "GET /admin/phpmyadmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /admin/phpMyAdmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /admin/phpmyadmin2/index.php HTTP/1.1" 404 225
[01/Jun/2018:06:46:44 +0900] "GET /mysqladmin/index.php HTTP/1.1" 404 218
[01/Jun/2018:06:46:44 +0900] "GET /mysql-admin/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /phpadmin/index.php HTTP/1.1" 404 216
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin0/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin1/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin2/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /myadmin/index.php HTTP/1.1" 404 215
[01/Jun/2018:06:46:44 +0900] "GET /myadmin2/index.php HTTP/1.1" 404 216
[01/Jun/2018:06:46:44 +0900] "GET /xampp/phpmyadmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /phpMyadmin_bak/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /www/phpMyAdmin/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /tools/phpMyAdmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin-old/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /phpMyAdminold/index.php HTTP/1.1" 404 221
[01/Jun/2018:06:46:44 +0900] "GET /phpMyAdmin.old/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /pma-old/index.php HTTP/1.1" 404 215
[01/Jun/2018:06:46:44 +0900] "GET /claroline/phpMyAdmin/index.php HTTP/1.1" 404 228
[01/Jun/2018:06:46:44 +0900] "GET /typo3/phpmyadmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /phpma/index.php HTTP/1.1" 404 213
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin/phpmyadmin/index.php HTTP/1.1" 404 229
[01/Jun/2018:06:46:44 +0900] "GET /phpMyAdmin/phpMyAdmin/index.php HTTP/1.1" 404 229



404는 파일이 존재하지 않는다는 의미이고 200은 파일은 존재한다는 의미다.

phpMyAdmin 사용을 매우 주의해야겠다는 생각이 들었다.

특정한 IP에서만 접속 가능하도록 ipFiltering을 걸어두었기에 brute force 공격(무차별 root 패스워드 대입공격)은 불가능하다.

하지만 ipFiltering 을 걸어두지 않고 MySQL 패스워드를 간단하게 사용하는 사이트가 있다면 사이트가 털릴 가능성이 매우 높다.

이런 로그를 잘 살펴보지 않으면 무슨 문제가 발생하고 있는지 조차 모를 수 있다.

1. MySQL root 패스워드는 복잡하게 생성해야 한다.

2. 특정 IP 에서만 접속 허용하는 ipFiltering.php 를 만들어서 incluce_once 시켜서 접속 IP를 확인하게 만든다.


블로그 이미지

Link2Me

,
728x90

해태와 KIA가 다른 점이 무얼까?

해태는 김응용 감독의 카리스마 아래 일사분란하게 움직이고 군기가 바짝 든 그런 팀이었다.

KIA는 연봉 많이 주고 헝그리 정신이 필요없는 데다가 요즈음에는 선배한테 얻어맞는 시대가 아니라 그런지 독기를 품고 야구하는 모습이 별로 없는거 처럼 보인다.


187cm, 92kg 이면 신체조건은 정말 좋은 투수인데 경기를 리드하지 못하고 얻어터지는 걸 반복하는 새가슴 투수라는게 안타깝다.



퓨처스리그에서 잘 던져서 올려도 1군 선수들한테는 줄창 얻어터진다.

공을 마음먹은 대로 뿌리지도 못하더라.


타자들은 수준급 외국인 투수 영입으로 나날이 발전하고 있다.

1군에서 뛰고 있는 선수는 KBO에서 가장 우수한 선수들이 뛰는 곳이다. 이런 선수들을 상대하면서도 절대로 기죽지 않고 자신의 기량을 발휘할 수 있어야 하는데 그게 잘 안되는거 같아 안타깝다.

급박한 상황에서도 흔들림없는 멘탈을 가져야 하는데 홍건희는 몇년을 지켜봐도 그게 안된다.


세계 양궁의 절대강자가 된 양궁선수들은 번지점프, 공동묘지 달리기, 특수훈련 등 각종 담력 훈련이 대표팀의 기본 훈련이라고 기사에 나온다.


홍건희 투수에게 필요한 것은 날마다 투구 연습하는 것도 중요하겠지만 절대 흔들리지 않는 담력 훈련이 필요한 거 같다. 국내 공동묘지 훈련이건 외국 번지점프, 귀신 나오는 집 등 담력 테스트에 좋은 훈련을 하는게 필요한 거 아닐까?


신인 시절 양현종을 선발투수로 조련한 칸베 토시오(75·일본) 전 KIA 투수코치다.
"2008, 2009년에 하루도 쉬지 않고 매일 밤 훈련하고 캠프에서는 (하루) 350개 이상의 공을 던졌다"며 "그런 대단한 노력이 있었기에 특급 투수로 성장했다"고 본다.


투수도 스스로 타자 강약점 분석하고, 본인이 자신있는 주무기 개발하고, 배짱있는 투구를 할 수 있기를 기대해본다.




블로그 이미지

Link2Me

,
728x90

PHP 테이블에서 글자수가 너무 길어서 2줄로 보이거나 테이블 구조가 이상하게 보이는 증상이 있어서 찾아봤더니 mb_strimwidth 함수를 이용하면 편하게 글자수 줄임이 가능하다.


mb_strimwidth ( string $str , int $start , int $width [, string $trimmarker [, string $encoding ]] )

mb_strimwidth('텍스트','시작위치','끝위치',"끝에붙일말","언어코드");


사용 예제


mb_strimwidth("Hello World", 0, 10, "...");


echo "<td class='bl'>".mb_strimwidth($row['positionNM'],'0','18','...','utf-8')."</td>";


표의 셀에서 글자수가 길어서 글줄임 ... 으로 표시하기도 하지만

echo "<td class='bl' style='font-size:0.8vw'>".$row['userNM']."</td>";

와 같이 style='font-size:0.8vw' 를 사용하면 글자 크기를 줄여서 보여줄 수 있다.


https://stackoverflow.com/questions/16056591/font-scaling-based-on-width-of-container
에 나온 내용을 참고했다.
The values are:
    vw (% of the viewport width)
    vh (% of the viewport height)
    vi (1% of the viewport size in the direction of the root element's inline axis)
    vb (1% of the viewport size in the direction of the root element's block axis)
    vmin (the smaller of vw or vh)
    vmax (the larger or vw or vh)

1 v* is equal to 1% of the initial containing block.


나중에 살펴보려고 검색한 링크를 적어둔다.

Responsive fonts - auto-scaling root font-size

https://codepen.io/2kool2/pen/ZpLYYo


'Web 프로그램 > PHP 응용 및 활용' 카테고리의 다른 글

날짜 선택  (0) 2018.10.26
접속단말 및 IP주소  (0) 2018.06.11
PHP to JSP  (0) 2018.04.28
PHP Form 다중 체크박스 값 전달 및 POST  (0) 2018.04.02
PHP 배열 합집합, 교집합, 차집합, 대칭차집합  (0) 2018.04.01
블로그 이미지

Link2Me

,
728x90

반응형웹은 다양한 디바이스(PC, 노트북, 테블릿, 스마트폰 등)에 최적화된 UI를 제공하게 할 수 있다.
웹사이트의 레이아웃을 만들 때 방문자가 사용하는 모니터의 화면 해상도를 고려해야 한다.
너무 크게 가로폭을 만들면 작은 해상도의 모니터로 접속했을 때 가로 스크롤이 생겨 컨텐츠를 보는 게 불편하기 때문이다.
해상도에 따라 가로폭이나 배치를 변경하여 가독성을 높인다.

이러한 작업을 할 수 있게 해주는 것이 @media 이다. media 쿼리는 장치 특성에 따라 브라우저가 해석해야 할 CSS 코드를 분기 처리하는 규칙이다. 보통 해상도 또는 해상력을 판별하여 분기 처리한다.


CSS3 @media 쿼리는 IE6~8 브라우저에서는 지원되지 않는다. 그래서 Javascript로 구현된 respond.js를 이용해서 이 문제를 해결해야한다. respond.min.js 라이브러리를 사용하면 IE6~8 브라우저도 media 쿼리를 해석할 수 있다.

html5shiv.js 파일을 문서 head 요소 내부에 삽입하면 IE6~8 브라우저도 HTML5 요소를 화면에 표시하게 된다.

하지만 html5shiv.js 파일을 추가해도 새로운 요소들 때문에 화면이 깨지는 증상을 발견할 수 있다. 이유는 display:block 으로 표시를 해야 할 블럭 요소들이 display:inline 으로 랜더링 되기 때문이다.

header,footer,section,article,aside,nav,hgroup,details,menu,figure,figcaption{
  display:block
}

인라인 형태로 표시되던 새로운 HTML5 요소를 블럭 요소로 변경해 준다. 이제 IE 6~8 브라우저도 HTML5 요소를 화면에 제대로 표시하게 된다.

최근에는 IE6~8 사용 거의 안하므로 이런 건 무시하자.


고정으로 보일 영역과 가변으로 보일 영역을 지정한다.
고정 영역에서도 padding 처리, 글꼴 크기 등을 조절하여 디바이스 기기에 따라 변하게 작성한다.


A 영역에는 공용으로 사용할 기본 CSS를 모두 적어준다.

B/C/D/E 영역에는 기본 사항과 공통 요소 값을 다르게 적어주면 해당 해상도에 따라 덮어쓰기가 된다.


  A

/* ########### 599px 이하 ########### */
@media (max-width: 599px) {
  B
}

/* ########### 600px 이상 ~ 767px 이하 ########### */
@media (min-width: 600px) and (max-width: 767px) {
  C
}

/* ########### 768px 이상 ########### */
@media ( min-width: 768px ) {
  D
}

/* ########### 1040px 이상 ########### */
@media ( min-width: 1024px ) {
  E
}



Layout 구성하는 예제를 작성해보자.

가장 먼저 전체 Layout 에서 헤더 중심으로 파일을 살펴본다.

<!doctype html>
<html lang="en">
<head>
<title>반응형 웹 예제</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./css/main.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>

</body>
</html>


<meta name="viewport" content="width=device-width, initial-scale=1">

webkit 계열 iOS, Android 모바일 브라우저에서 이코드를 해석한다.


jQuery 를 사용하기 위해 https://www.w3schools.com/ 를 참조하여 Header 를 구성하면 편하다.


<link rel="stylesheet" href="./css/main.css">

이제 main.css 파일 내용은 @media 를 사용해서 작성할 것이다.


<body> </body> 사이의 구성할 Layout 은 <div></div> 를 사용한다.

div 태그는 기본 속성이 세로로 하나씩 추가되므로 container 에 가로로 정렬되도록 하려면 별도 처리를 해야 한다.


<body>
<div class="header"></div>
<div class="container">
    <div class="sidemenu"></div>
    <div class="content"></div>
</div>
<div class="footer"></div>
</body>


div 태그 : float 속성으로 가로 정렬 한다.

/* ########### 768px 이상 ########### */
@media ( min-width: 768px ) {
    .container:after {content:"";display:both;clear:both;}
    .sidemenu { float:left; width:160px;}
    .content {float:none; width:auto; margin-left:160px;}
    /* sidemenu 와 content 의 왼쪽 오른쪽의 간격 */
    .sidemenu    {
        padding-right: 10px;
        -moz-box-sizing: border-box;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
    }
    .footer { 

        clear:both; /* float 속성 적용한 것을 해제하겠다. */

        width:100%;

    }
}

<div class="header"></div>

에는 상단 가로정렬 메뉴를 만들어보자.

<div class="header">
    <nav class="topmenu">
        <ul>
            <li><a href="#">Home</a></li>
            <li><a href="#">메뉴1</a></li>
            <li><a href="#">메뉴2</a></li>
            <li><a href="#">메뉴3</a></li>
            <li><a href="#">메뉴4</a></li>
            <li><a href="#">메뉴5</a></li>
        </ul>
    </nav>
</div>


위와 같은 코드만 추가하면 메뉴는 세로 정렬로 추가된다.

가로 정렬로 추가되도록 하려면 CSS 코드를 추가해줘야 한다.

/* 메뉴(가로 정렬) */
.topmenu ul {    margin: 0; padding: 0; list-style: none; }
.topmenu li a    {
    display: block;
    padding: 7px 10px;
    color: #000000;
    font-size: 14px;
    text-decoration: none;
}
.topmenu li a:hover {background-color: #F7D358;}
.topmenu ul:after    {content: ""; display: block; clear: both;}
.topmenu li {float: left; width: auto;}


header를 바의 형태로 디자인 하려면

.header    {background-color: #58D3F7;}



사이드 메뉴 만들기

    <div class="sidemenu">
        <ul>
            <li><a href="#">Home</a></li>
            <li><a href="#">Side 메뉴1</a></li>
            <li><a href="#">Side 메뉴2</a></li>
            <li><a href="#">Side 메뉴3</a></li>
            <li><a href="#">Side 메뉴4</a></li>
            <li><a href="#">Side 메뉴5</a></li>
        </ul>
    </div> 

 /* 사이드 메뉴(sidemenu) */
.sidemenu ul {margin: 0; padding: 0; list-style: none;}
.sidemenu li a    {
    display: block;
    padding: 5px;
    color: #000000;
    font-size: 14px;
    text-decoration: none;
}
.sidemenu li a:hover {background-color: #eeeeee;}


content 부분

    <div class="content">
        <article class="story">
            <h2>본문 제목</h2>
            <p><img src="img/img_sample.png" alt=""></p>
            <p>본문 내용을 여기에 작성한다.</p>
            <p>김남길과 김아중이 연기한 명불허전은 조선시대 침술대가 허임을 소재로......</p>
        </article>
    </div>

 /* 본문 기사 */
.story h1    {margin-top: 0;    margin-bottom: 10px;font-size: 26px;}
.story p    {margin-top: 0;    margin-bottom: 10px;}
.story img    {max-width: 100%; height: auto}


연습한 Layout 샘플 파일을 첨부한다.

구글에서 "반응형 css 예제" 로 검색하면 참고할 수 있는 자료가 많이 나온다.

div 화면 구성하는 것을 많이 다뤄보면 도움된다.

블로그 이미지

Link2Me

,