728x90

FCM 기능을 추가하기 위한 코드 변경을 하다보니 잔뜩 에러가 발생한다.


<provider
  android:name="android.support.v4.content.FileProvider" 에러가 표시하고 컴파일시 에러 메시지를 출력한다.


<provider
    android:name="androidx.core.content.FileProvider"
로 변경해준다.


AndroidX를 사용하려면 컴파일 SDK를 Android 9.0(API 레벨 28) 이상으로 설정하고, gradle.properties 파일에서 다음 두 Android Gradle 플러그인 플래그를 true로 설정해야 한다.

android.useAndroidX: true로 설정하면 Android 플러그인에서 지원 라이브러리 대신 적절한 AndroidX 라이브러리를 사용한다. 지정하지 않으면 플래그는 기본적으로 false이다.
android.enableJetifier: true로 설정하면 Android 플러그인에서 자동으로 기존 타사 라이브러리를 이전하여 바이너리를 다시 작성해 AndroidX를 사용한다. 지정하지 않으면 플래그는 기본적으로 false이다.


참조 : https://developer.android.com/jetpack/androidx?hl=ko

블로그 이미지

Link2Me

,
728x90

안드로이드 WebView 를 이용하여 MDB 기반 Web 화면을 읽어서 처리하는 코드이다.

LG G5폰(안드로이드 6.0)에서는 잘 동작한다.

삼성 갤럭시 S10(안드로이드 9.0)에서 실행을 시켰더니 로그인 자체부터 막힌다.

구글링해보니 HTTPS 통신이어야만 정상 동작된다고 나온다.

HTTP 통신에서 동작하도록 하는 방법은 아래와 같이 코드를 수정해주면 된다.


AndroidManifest.xml 파일 수정사항

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:theme="@style/AppTheme.NoActionBar">


/res/xml/network_security_config.xml 추가

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>


로 해주거나, 아래와 같이 한줄을 추가해주라고 나온다.

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"


일단 여기까지 하면 로그인까지는 문제없이 잘 동작되더라.

activity_main.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="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="5"
        android:orientation="vertical"
        >
        <WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="none"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:adjustViewBounds="true"
            android:scaleType="fitStart"
            />

    </LinearLayout>

</LinearLayout>


WebView 는 아래와 같이 처리하면 삼성 갤럭시 S10 에서도 이상없이 동작되는 걸 확인했다.

    private void myWebView(){
        String web_url = getIntent().getExtras().getString("url");
        try {
            url = URLDecoder.decode(web_url,"UTF-8"); // target
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.out.println("web url : " + web_url);

        mWebView = (WebView) findViewById(R.id.webview); // Layout 와의 연결

        mWebSettings = mWebView.getSettings(); // 세부 세팅 등록
        mWebSettings.setJavaScriptEnabled(true); // 자바스크립트 사용 허용
        mWebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        mWebSettings.setBuiltInZoomControls(false); // 화면 확대 축소 허용 여부
        mWebSettings.setDisplayZoomControls(false);
        mWebSettings.setSupportZoom(true); // 줌 사용 여부
        //mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); // 컨텐츠 사이즈 맞추기
        mWebSettings.setUserAgentString("Android");
        if(Build.VERSION.SDK_INT >= 16){
            mWebSettings.setAllowFileAccessFromFileURLs(true);
            mWebSettings.setAllowUniversalAccessFromFileURLs(true);
        }
        if(Build.VERSION.SDK_INT >= 21){
            mWebSettings.setMixedContentMode(mWebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
        mWebView.setWebChromeClient(new WebChromeClient());
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (url.startsWith("tel:")) {
                    Intent call_phone = new Intent(Intent.ACTION_CALL);
                    call_phone.setData(Uri.parse(url));
                    startActivity(call_phone); // 권한 설정은 Loing.java에서 처리했음
                } else if (url.startsWith("sms:")) {
                    Intent i = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));
                    startActivity(i);
                } else if (url.startsWith("intent:")) {
                    try {
                        Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
                        Intent existPackage = getPackageManager().getLaunchIntentForPackage(intent.getPackage());
                        if (existPackage != null) {
                            startActivity(intent);
                        } else {
                            Intent marketIntent = new Intent(Intent.ACTION_VIEW);
                            marketIntent.setData(Uri.parse("market://details?id=" + intent.getPackage()));
                            startActivity(marketIntent);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    view.loadUrl(url);
                }
                return true;
            }

        });

        mWebView.loadUrl(url);
    }


내가 사용했던 build.gradle, webview 등 파일 일부를 첨부한다.


WebView.zip



블로그 이미지

Link2Me

,
728x90

Class 를 만들어서 사용하면 여러모로 편하고 좋다.

본 Class 함수는 KIMSQ RB에서 사용하는 함수를 Class화해서 사용하고 있다.

그만큼 코딩을 편하면서도 깔끔하게 이해하게 해준다.

MDB(Meterial Design for Bootstrap) 든 일반 게시판이든 사용하는데는 다 사용 가능하다. MDB는 템플릿으로 깔끔하게 보여주기 때문에 디자인에 대한 고민을 덜어준다.


<?php

// 파일명 : dbDataClass.php

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

    // DB Query Cutom 함수
    function getDbData($table,$where,$column) {
        global $db;
        $sql ='select '.$column.' from '.$table.($where?' where '.$this->getSqlFilter($where):'');
        //echo $sql; // 디버깅 목적으로 필요할 때가 있다.
        $result = mysqli_query($db,$sql);
        $row = mysqli_fetch_array($result);
        return $row;
    }

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

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

    }

    //DB데이터 레코드 총 개수
    function getDbRows($table,$where){
        global $db;
        $sql = 'select count(*) from '.$table.($where?' where '.$this->getSqlFilter($where):'');
        $result = mysqli_query($db,$sql);
        if($result){
            $rows = mysqli_fetch_row($result);
            return $rows[0] ? $rows[0] : 0;
        } else {
            echo "Failed to connect to MySQL: " . mysqli_connect_error($db);
            exit(); // **this is missing**
        }
    }

    //DB삽입
    function getDbInsert($table,$key,$val){
        global $db;
        $sql ="insert into ".$table." (".$key.")values(".$val.")";
        //echo $sql;
        if(mysqli_query($db,$sql)){
            return 1;
        } else {
            return 0;
        }
    }

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

    //DB삭제
    function getDbDelete($table,$where)    {
        global $db;
        $sql ="delete from ".$table.($where?' where '.$this->getSqlFilter($where):'');
        if(mysqli_query($db,$sql)){
            return 1;
        } else {
            return 0;
        }
    }

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

    // 암호화
    function AES_Encode($plain_text){
        global $key;
        return base64_encode(openssl_encrypt($plain_text, "aes-256-cbc", $key, true, str_repeat(chr(0), 16)));
    }

    // 복호화
    function AES_Decode($base64_text){
        global $key;
        return openssl_decrypt(base64_decode($base64_text), "aes-256-cbc", $key, true, str_repeat(chr(0), 16));
    }

}//end dbClass

?>


블로그 이미지

Link2Me

,
728x90

JDK 1.5 이전에서는 여러 타입을 사용하는 대부분의 클래스나 메소드에서 인수나 반환값으로 Object 타입을 사용했다.
이 경우에는 반환된 Object 객체를 다시 원하는 타입으로 타입 변환해야 한다.

그리고 원하지 않는 자료형이 입력되었을 때의 오류를 컴파일 시점에서 잡아낼 수 없다.

(Object 클래스는 모든 클래스의 조상(부모)이므로 모든 타입이 들어갈 수 있다.)


자바에서 제네릭(Generic)이란 데이터의 타입(Data Type)을 일반화한다(Generalize)는 것을 의미한다.
제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법이다.
이렇게 컴파일 시에 미리 타입 검사(Type Check)를 수행하면 다음과 같은 장점을 가진다.


1. 프로그램 성능저하를 유발하는 캐스팅(강제 데이터타입 변환)을 제거한다.

   - 매번 데이터 타입을 강제로 캐스팅하면, 프로그램 성능 저하가 발생한다.

2. 코드절약 및 코드 재사용성을 증진시켜 유지보수를 편하게 한다.

   - Generic 이 없다면, 매번 필요한 데이터 타입에 맞는 새로운 클래스 또는 메소드를 작성해야 한다.

3. 컴파일시 타입오류를 체크하여, 사전에 엄격한 데이터타입 체크를 가능케한다.



예제 1 

package ex_Generic;
//Object 타입을 사용하면 모든 종류의 자바 객체를 저장할 수 있다는 장점은 있지만
//저장할 때 타입 변환이 발생하고, 읽어올 때도 타입 변환이 발생한다.
//이러한 캐스팅이 빈번해지면 전체 프로그램 성능에 좋지 못한 결과가 발생할 수 있다.
// 제네릭은 클래스를 설계할 때 구체적인 타입을 명시하지 않고,
// 타입 파라미터로 대체했다가, 실제 클래스가 사용될 때 구체적인 타입을 지정함으로써 타입 변환을 최소화한다.
public class Box<T> { // T라는 이름이 매개변수화된 자료형임을 나타냄.
    private T item; // T에 해당하는 자료형의 이름은 인스턴스를 생성하는 순간에 결정된다.
    // T는 상징적인 문자
   
    public Box() { // 생성자
       
    }
   
    public Box(T item) { // 매개변수가 있는 생성자
        this.item = item;
    }
   
    public void setData(T item) { // T[] arr
        this.item = item;
    }
    public T getData() {
        return item;
    }
}

package ex_Generic;

import java.util.ArrayList;
import java.util.List;

abstract class Animal {
    public void go() {
        System.out.println("Animal");
    }
}
class Dog extends Animal {
    public void go() {
        System.out.println("Dog");
    }
}
class Cat extends Animal {
    public void go() {
        System.out.println("Cat");
    }
}

public class Main {
    @SuppressWarnings("unchecked")
    public static <T extends Animal> void doAction(List<T> animals) {
        animals.add((T) new Cat());
        for (Animal animal: animals) {
            animal.go();
        }
    }
       
    public static void main(String[] args) {
        // 한정된 타입을 사용하도록 유도함으로써 타입 안전성 보장
        // 인스턴스 생성시 자료형을 지정한다. Integer
        Box<Integer> box1 = new Box<>();
        box1.setData(10);
        System.out.println(box1.getData());
       
        // 인스턴스 생성시 자료형을 지정한다. String
        Box<String> box2 = new Box<>();
        box2.setData("오랜지");
        System.out.println(box2.getData());
       
        // 컬렉션 프레임워크(Collections Framework)
        // Collection : 객체를 수집해서 저장하는 역할
        // Framework : 사용방법을 미리 정해 놓은 라이브러리
        // List, Set, Map 인터페이스 구현 클래스가 존재
        // List : ArrayList, Vector, LinkedList
        // Set : HashSet, TreeSet
        // Map : HashMap, Hashtable, TreeMap, Properties
        List<String> list = new ArrayList<String>();
        list.add("사과");
       
        String str = list.get(0); // Type 을 변환하지 않는다.
        System.out.println(str);
       
        List<Dog> animals = new ArrayList<Dog>();
        animals.add(new Dog());
        animals.add(new Dog());
        //animals.add(new Cat()); // Cat을 넣으려고 하면 컴파일 시점에 타입 체크를 해주기 때문에 에러 발생
        doAction(animals);
   
    }

}


여러 블로그 자료를 참조하고 동영상 강좌를 들으면서 정리하고 있는데 아직 완벽한 이해는 못한 상태다.

기본 개념은 이 자료로 이해는 된다. 하지만 깊이 있게 들어가는 사항은 좀 더 테스트를 하고 이해해야 한다.

'안드로이드 > Java 문법' 카테고리의 다른 글

자바 null 비교, String 비교, 정수 비교  (0) 2019.09.20
공백으로 문자열 분리  (0) 2019.09.11
Java 추상 클래스  (0) 2019.08.19
[Java] 다형성  (0) 2019.06.30
How to validate IP address with regular expression  (0) 2019.06.17
블로그 이미지

Link2Me

,
728x90

Eclipse 에서 PHP 개발 테스트를 하기 위해서 PHP Development Tools 를 설치했다.

구글 검색해서 4.1 버전 이미지를 보면서 설치를 했다.




검색어 php 를 입력하고 Go를 누르면 아래와 같이 PHP Development Tools 최신 버전이 나온다.

현재 그림은 이미 Installed 된 것이라 이렇게 보이지만 설치하지 않았으면 Install 이라고 나온다.


이후에 나온 그림은 캡처를 하지 않아서 게재할 수가 없다.

다른 블로그에 나온 이미지를 참조해도 되고 순서대로 진행하면 된다.

설치를 하고 나면 프로그램 재실행을 해준다.


이후에 PHP 파일에서 APM 과 연결하여 PHP 파일을 직접 실행하는 방법이다.


먼저 Preferences 를 눌러서 아래 화면이 나오면 PHP 항목을 선택한 다음에

Servers 를 선택하고 Default PHP Web Server 경로를 수정해줘야 한다.

APM(Apache + PHP + MySQL) 이 설치된 Autoset9 의 설치경로의 web root 경로(httpd.conf 에서 설정한 경로)를 지정해준다.



이제 PHP Project 를 생성할 때 아래와 같이 1번 항목이 보이게 된다.

2번 항목에는 이미 만들어놓은 PHP 경로를 적어주면 된다.


실제 파일에서 아래와 같이 한번만 실행하면 다음부터는 1번만 누르면 마지막 설정한 걸 실행한다.




사족을 달자면...

맥북 2010 프로에는 윈도우10 설치가 안되어 윈도우 8.1 을 설치해서 사용하고 있는데 Aptana 를 설치하면 에러가 발생하면서 설치가 안되어서 Eclipse Java 설치한 것에 PHP 모듈을 추가해 본 것이다.

여러가지 PHP 툴을 설치하지만 딱 마음에 드는게 별로 없다.

Eclipse PDT 를 사용해 보고 장점이 발견되면 추가로 적어둘려고 한다.


블로그 이미지

Link2Me

,
728x90

자바 강좌를 들으면서 실습용으로 Eclipse를 설치해서 사용중인데 기본 인코딩 모드가 UTF-8 이 아니었다.

PHP 모듈을 추가 설치하면서 PHP 파일을 읽어오는데 한글이 깨져 보여서 기본 인코딩 모드를 변경했다.



Visual Studio Code 에서 자바 실습하는 걸 해보려고 했지만 영~~ 아니다.

PHP 코드를 정렬하고자 Visual Studio Code 를 실행해서 해봤지만 확장자가 PHP 인것은 제대로 못하는 가 보다.

그래서 Eclipse 에서 PHP 모듈을 추가 설치했다.

블로그 이미지

Link2Me

,
728x90

Java는 다중상속을 지원하지 않는다. (C++은 다중 상속을 지원한다.)

두 조상으로부터 상속받는 멤버 중에서 멤버변수의 이름이 같거나 메서드의 선언부가 일치하고 구현 내용이 다르다면 이 두 조상으로부터 상속받는 자손클래스는 어느 조상의 것을 상속받게 되는 것인지 알 수 없다. 어느 한 쪽으로부터의 상속을 포기하던가, 이름이 충돌하지 않도록 조상클래스를 변경하는 수 밖에는 없다.
자바에서는 이러한 충돌문제를 해결하기 위해서 단일 상속만을 허용하고, 인터페이스를 이용해서 단일 상속의 단점을 보완하도록 하였다.

 

Java 인터페이스
- 인터페이스는 추상메서드, 상수 만을 가질 수 있으며, 이를 직접 구현하지 않는다.
- 인터페이스는 필드(변수)를 포함할 수 없다.
- 정의부분만 표현하고, 구현 부분은 상속받은 클래스에서 구현한다. (구현이라 함은 {}가 포함된 거)
- 클래스가 인터페이스를 가지는 경우 해당 인터페이스의 모든 멤버에 대한 구현(implementation)을 제공해야 한다.
- 여러개의 인터페이스를 부모로 둘 수 있다. (다중 상속 가능)
- 인터페이스로 객체를 생성할 수는 없다.
- 접근 제어자로 public 또는 default를 사용한다. (private 접근제한자는 사용할 수 없다.)
- 모든 멤버 변수는 public static final 이어야 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.

- 인터페이스의 구현은 extends가 아닌 implements를 사용한다.

- 인터페이스는 둘 이상을 동시에 구현 가능하다.(다중 상속 효과)

- 인터페이스간 상속이 가능한데 이때는 extends를 사용한다.

 

Android에서는 한 프로세스가 다른 프로세스의 메모리에 정상적으로 액세스할 수 없다.

 

interface 인터페이스이름 {
      public static final 타입 상수이름 = 값;
      public abstract 메서드이름(매개변수목록);
}



- interface 내에 선언된 변수는 public static final 로 선언되고 이를 생략할 수 있다.
- interface 내에 선언된 메소드는 public abstract 로 선언되고 이를 생략할 수 있다.
- interface 도 참조변수 선언 가능하고, 메소드 오버라이딩 원칙도 그대로 적용된다.

 

인터페이스에 정의된 모든 멤버 변수는 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일 시에 컴파일러가 자동으로 추가해준다.
 
 

예제

 

import java.util.Scanner;
 
interface IEmployee {
    // 멤버 앞에 접근제한자 생략 가능
    // 컴파일러에 의해 public abstract 가 붙음
    String getName();
    void setName(String n);
    int getCounter();
}
 
class Staff implements IEmployee {
 
    private String name;
    private int counter;
    public static int NOofEmployees;
 
    public Staff() { // 생성자
        counter = ++counter + NOofEmployees;
    }
 
    @Override
    public String getName() {
        return name;
    }
 
    @Override
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public int getCounter() {
        return counter;
    }
}
 
public class IEmployeeEx {
    private static Scanner kb;
 
    public static void main(String[] args) {
        String name;
        kb = new Scanner(System.in);
        System.out.print("직원 수 입력: ");
        Staff.NOofEmployees = Integer.parseInt(kb.next());
 
        Staff my = new Staff();
        System.out.print("신입 직원의 이름을 입력하세요: ");
        name = kb.next();
        my.setName(name);
 
        System.out.println("▷ 직원 정보 ◁");
        System.out.println(String.format("직원 번호 : %d, 직원이름 : %s", my.getCounter(),my.getName()));
 
    }
}
 
 

 

본 예제는 C# 강의를 들으면서 정리한 예제를 Java 로 구현한 것이다.

인터페이스에 대한 개념 이해가 부족해서 Android 에서 인터페이스 구현을 쉽게 못했는데 이제는 할 수 있을 거 같다.

C# 인터페이스 예제와 비교해서 보면 좀 더 이해가 빠를 수도 있다.

좀 더 심도있는 것은 구현해보면서 예제를 추가해볼 생각이다.

 

참고하면 좋은 자료

https://blog.naver.com/sensate1024/221232506713

 

블로그 이미지

Link2Me

,
728x90

Visual Studio 에서 자주 사용하는 단축키 정리


 라인 삭제

 CTRL + L

 라인 복사

 현재 커서가 위치한 줄에서 CTRL + C, 원하는 줄로 이동하여 CTRL + V

 코드 정렬

 CTRL + K + F

 선택 영역을 주석으로 처리

 CTRL + K, C

 선택 영역을 주석처리 제거

 CTRL + K, U

 대응하는 괄호 찾기(이동)

 CTRL + ]

 화면 글꼴 크게/작게 보기

 CTRL + 마우스 휠 누르면 화면 글꼴이 커지고 작아진다.


블로그 이미지

Link2Me

,
728x90

클래스의 멤버 변수를 private, public 으로 선언할 것인가에 따라 정보은닉, 정보공개를 고민하게 된다.

private 로 선언하면 getter 메소드, setter 메소드를 구현해야 한다.


C# 언어에서는 다양한 이유로 프로퍼티의 사용을 장려한다. 지금까지도 타입(type)에서 public 데이터 변수를 사용하거나, get/set류의 메서드를 직접 만들어서 사용하고 있다면 이제 그런 방법을 사용하지 않는 것이 좋다. 프로퍼티는 우리가 만든 타입의 값을 외부에서 접근할 수 있도록 하면서 동시에 객체지향에서 논의되는 캡슐화를 지원한다. 프로퍼티는 데이터 멤버처럼 접 근가능하면서 메서드의 형태로 구현되는 C# 언어의 요소이다.


프로퍼티(속성)를 사용하면 클래스가 구현 또는 코드를 숨기는 동시에 값을 가져오고 설정하는 방법을 공개적으로 노출할 수 있다.
- get 속성 접근자는 속성 값을 반환하고, set 접근자는 새 값을 할당하는데 사용한다.
- set 접근자의 value 키워드는 set 접근자가 할당하는 값을 정의하는데 사용한다.
- set 접근자만을 구현하면 쓰기 전용, get 접근자만을 구현하면 읽기 전용이다.
- value : set 접근자 사용 시 사용되는 암묵적 매개변수


자동구현 프로퍼티

using System;

namespace Property
{
    class Test
    {
        private int x;
        public int X   // 프로퍼티
        {
            get { return x; }
            set {
                x = value; // value : set 접근자 사용시 사용되는 암묵적 매개변수
            }
        }
    }

    class Person
    {
        public string Name { get; set; } // 자동 구현 프로퍼티
        public int Age { get; } // 읽기전용, 자동 구현 프로퍼티
        public int grade { get; } = 5; // 자동속성초기화
        public Person(string name, int age) // 생성자
        {
            this.Name = name;
            this.Age = age;
        }
        
        public override string ToString()
        {
            return $"name={Name}, age={this.Age}, grade={grade}";
        }
    }


    public class SaleItem
    {
        public string Name // 자동 구현 프로퍼티
        { get; set; }

        public decimal Price // 자동 구현 프로퍼티
        { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Test t1 = new Test();
            t1.X = 5000;              // 프로퍼티 set 접근자를 통한 값 지정
            Console.WriteLine(t1.X);  // 프로퍼티 get 접근자를 통한 출력

            Person a = new Person("Tom", 14);
            Console.WriteLine(a);

            var item = new SaleItem { Name = "구두", Price = 150000 };
            Console.WriteLine($"{item.Name}의 판매 가격은 {item.Price}");
        }
    }
}


Program.cs


'C# > 문법 및 기능' 카테고리의 다른 글

C# 인터페이스  (0) 2019.08.18
C# 구조체(Struct)  (0) 2019.08.14
C# 접근 제한자(Access Modifier)  (0) 2019.08.13
자바와 C#의 차이점  (0) 2016.12.27
C# NameValueCollection  (0) 2016.03.20
블로그 이미지

Link2Me

,
728x90

추상 클래스
추상 클래스(abstract class)란 하나 이상의 추상 메소드(abstract method)를 포함하는 클래스이다.
- 추상 메소드는 선언만 있고 구현이 없는 메서드이며 선언부에 'abstract' 라는 키워드를 붙인다.
- 추상 메소드가 포함되었다면 클래스도 추상 클래스이므로 클래스명 앞에도 'abstract' 키워드를 붙여야 한다.
- 추상 클래스는 추상 메서드를 포함하고 객체화 할 수 없다는 점만 제외하고 일반 클래스와 다르지 않으며 생성자, 멤버변수와 일반 메서드를 가질 수 있다.
- 추상 클래스는 다른 클래스들에게서 공통으로 가져야하는 메소드들의 원형을 정의하고 그것을 상속받아서 구현토록 하는데 사용된다.
- 해당 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야 한다.


public abstract class 클래스명 {
  필드
  생성자
  메서드
  추상메서드
}


package ex_abstract;

public abstract class Animal {
    // 추상 클래스(abstract class)란 하나 이상의 추상 메소드(abstract method)를 포함하는 클래스
    // 추상 메소드가 포함되었다면 클래스도 추상 클래스이므로 클래스명 앞에도 'abstract' 키워드를 붙여야 한다.
    public String aType;
    public void move() {
        System.out.println("동물이 어슬렁 어슬렁 움직인다.");
    }
    abstract void sound(); // 추상(abstract) 메서드는 선언만 있고 구현이 없는 메서드
}

package ex_abstract;

public class Dog extends Animal {
    public void move() {
        System.out.println("노곤하개");
    }
   
    @Override
    void sound() {

        // 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야한다.

        System.out.println("노곤하개 컹컹");
    }
}

package ex_abstract;

public class Cat extends Animal  {
   
    public Cat() {
        this.aType ="고양이과 동물";
    }

    @Override
    void sound() {
        // 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야한다.
        System.out.println("고양이 냐옹");
    }

}

package ex_abstract;

public class Main {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        dog.move(); // 오버라이드된 메소드 호출 ==> 노곤하개
        cat.move(); // 추상클래스 메소드 호출 ==> 동물이 어슬렁 어슬렁 거린다.
        dog.sound(); // 자식 클래스의 구현된 메소드 호출 ==> 노곤하개 컹컹
        cat.sound(); // 자식 클래스의 구현된 메소드 호출 ==> 고양이 냐옹
    }
}


ㅇ 부모 클래스의 생성자를 호출하려면 super 키워드를 사용해야 한다.

package ex_abstract;

public abstract class Animal {
    // 추상 클래스(abstract class)란 하나 이상의 추상 메소드(abstract method)를 포함하는 클래스
    // 추상 메소드가 포함되었다면 클래스도 추상 클래스이므로 클래스명 앞에도 'abstract' 키워드를 붙여야 한다.
    public String name;
   
    public Animal() {
        super();
    }
    public Animal(String name) {
        this.name = name;
    }
    public void move() {
        System.out.println("동물이 어슬렁 어슬렁 움직인다.");
    }
    abstract void sound(); // 추상(abstract) 메서드는 선언만 있고 구현이 없는 메서드
}

package ex_abstract;

public class Cat extends Animal  {
   
    public Cat(String name) {
        super(name); // 부모 클래스의 생성자를 호출하려면 super 키워드를 사용해야 한다.
    }

    @Override
    void sound() {
        // 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야한다.
        System.out.println("고양이 냐옹");
    }

}

package ex_abstract;

public class Dog extends Animal {
   
    public Dog() {
        super(); // 부모 클래스의 생성자를 호출하려면 super 키워드를 사용해야 한다.
    }

    public Dog(String name) {
        super(name); // 부모 클래스의 생성자를 호출하려면 super 키워드를 사용해야 한다.
    }

    public void move() {
        System.out.println("노곤하개");
    }
   
    @Override
    void sound() {
        // 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야한다.
        System.out.println("노곤하개 컹컹");
    }
}

package ex_abstract;

public class Main {

    public static void main(String[] args) {
        Dog dog = new Dog("송백구");
        Cat cat = new Cat("조나비");
        dog.move(); // 오버라이드된 메소드 호출
        cat.move(); // 추상클래스 메소드 호출
        dog.sound(); // 자식 클래스의 구현된 메소드 호출
        cat.sound(); // 자식 클래스의 구현된 메소드 호출
        System.out.println("고양이의 이름은 : " + cat.name);
        System.out.println("진돗개의 이름은 : " + dog.name);
    }
}


'안드로이드 > Java 문법' 카테고리의 다른 글

공백으로 문자열 분리  (0) 2019.09.11
Java 제네릭(Generic)  (0) 2019.08.27
[Java] 다형성  (0) 2019.06.30
How to validate IP address with regular expression  (0) 2019.06.17
자바 클래스 개념 이해 예제  (0) 2019.05.27
블로그 이미지

Link2Me

,
728x90

C# 인터페이스

- 인터페이스는 메서드, 속성(Property), 이벤트, 인덱서만을 가질 수 있으며, 이를 직접 구현하지 않는다.

- 인터페이스는 필드를 포함할 수 없다.

- 정의부분만 표현하고, 구현 부분은 상속받은 클래스에서 구현한다. (구현이라 함은 {}가 포함된 거)
- 인터페이스는 추상 멤버(abstract member)로만 구성된 추상 Base 클래스(abstract base class)와 개념적으로 유사하다.
- 클래스가 인터페이스를 가지는 경우 해당 인터페이스의 모든 멤버에 대한 구현(implementation)을 제공해야 한다.
- C# 클래스가 인터페이스를 갖는 경우 인터페이스의 모든 멤버에 대한 구현을 제공해야 한다.

- 여러개의 인터페이스를 부모로 둘 수 있다. (다중 상속 가능)
- 인터페이스로 객체를 생성할 수는 없다.

참고사항
public class Staff : IEmployee
를 보면 C#은 상속이 Class 인지, Interface 인지 구별없이 사용한다.
Java 는 Class 상속은 extends, Interface 구현은 implements 로 구별하고 있다.
그래서 C#에서는 인터페이스라는 걸 구별하기 위해 암묵적으로 대문자 I를 붙여주는 거 같다.

public class Staff : IEmployee

출처: https://link2me.tistory.com/1671 [소소한 일상 및 업무TIP 다루기]


예제 1

using System;

namespace Interface_01
{
    interface IEmployee
    {
        // 멤버 앞에 접근제한자 사용 안함
        string getName();
        void setName(string n);
        int getCounter();
    }

    public class Staff : IEmployee
    {
        // IEmployee 구현, 인터페이스라는 걸 구분하기 위해서 앞에 대문자 I를 붙인다.
        private String name;
        private int counter;
        public static int NOofEmployees;

        public string getName()
        {
            return name;
        }

        public void setName(string name)
        {
            this.name = name;
        }

        public int getCounter()
        {
            return counter;
        }

        public Staff() // 생성자
        {
            counter = ++counter + NOofEmployees;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string name;
            Console.Write("직원 수 입력: ");
            Staff.NOofEmployees = int.Parse(Console.ReadLine());

            Staff my = new Staff();
            Console.Write("신입 직원의 이름을 입력하세요: ");
            name = Console.ReadLine();
            my.setName(name);

            Console.WriteLine("▷ 직원 정보 ◁");
            Console.WriteLine("직원 번호 : {0}, 직원이름 : {1} : ", my.getCounter(),my.getName());
          
        }
    }
}


예제2 : 프로퍼티 속성을 이용한 인터페이스 구현

using System;

namespace Interface_02
{
    interface IEmployee
    {
        // 멤버 앞에 접근제한자 사용 안함
        string Name { get;set; } // 읽기, 쓰기 모두 가능한 프로퍼티
        int Counter { get; }  // 읽기 전용 프로퍼티
    }

    public class Staff : IEmployee
    {
        // IEmployee 구현, 인터페이스라는 걸 구분하기 위해서 앞에 대문자 I를 붙인다.
        private String name;
        private int counter;
        public static int NOofEmployees; // static 키워드를 사용해서 객체 생성없이 필드 사용

        public string Name  // 읽기, 쓰기 모두 가능한 프로퍼티
        {
            get { return name; }
            set { name = value; }
        }

        public int Counter  // 읽기 전용 프로퍼티
        {
            get { return counter; }
        }

        public Staff() // 생성자
        {
            counter = ++counter + NOofEmployees;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string name;
            Console.Write("직원 수 입력: ");
            Staff.NOofEmployees = int.Parse(Console.ReadLine());

            Staff my = new Staff();
            Console.Write("신입 직원의 이름을 입력하세요: ");
            name = Console.ReadLine();
            my.Name = name;

            Console.WriteLine("▷ 직원 정보 ◁");
            Console.WriteLine("직원 번호 : {0}, 직원이름 : {1} : ", my.Counter, my.Name);

        }
    }
}



Base()
this는 각각의 메모리를 구분하기위한 방법이며, 생성자를 호출하기 위한 방법으로 사용된다.
이외에 부모와 자식간의 메모리를 구분하기 위한 키워드가 바로 base키워드이다.
부모 클래스의 이름으로는 부모 클래스의 생성자를 호출할 수 없고 base 키워드를 사용해야 한다.

    public class Parent
    {
        private int i;

        public Parent(int i)
        {
            this.i = i;
        }
    }

    public class Child : Parent
    {
        private int j;

        public Child(int i, int j) : base(i)    // 부모 클래스의 생성자를 호출하려면 base 키워드를 사용해야 한다.
        {
            this.j = j;
        }
    }
 


Program.cs


'C# > 문법 및 기능' 카테고리의 다른 글

C# 프로퍼티(Property)  (0) 2019.08.19
C# 구조체(Struct)  (0) 2019.08.14
C# 접근 제한자(Access Modifier)  (0) 2019.08.13
자바와 C#의 차이점  (0) 2016.12.27
C# NameValueCollection  (0) 2016.03.20
블로그 이미지

Link2Me

,
728x90

구조체는 사용자 정의 Data Type 으로 int, double 등과 같은 기본 제공 변수 유형이 아닌
새로운 유형, 여러가지 유형을 사용하기 위해 사용자가 직접 만들어 사용한다.
- 구조체를 정의하려면 struct 문을 사용한다.
- C# 구조체에 메소드, 필드, 속성등을 가질 수 있다.
- C# 구조체에 기본(Default) 생성자를 정의할 수 없다.
- C# 구조체에 소멸자를 정의할 수 없다.
- 클래스는 힙에 생성되는 참조 유형이고 구조체는 스택에 생성되는 값 유형이다.
- 구조체는 상속을 지원하지 않는다.

- 클래스와 달리 구조체는 new 연산자를 사용하지 않고 인스턴스화할 수 있다.


using System;

namespace Struct_EX
{
    class BaseClass // 부모 클래스
    {
        public int a; // 클래스의 멤버를 접근 제한자로 지정하지 않으면 멤버의 접근 수준은 무조건 private로 자동 지정됨
        int b;

        private void A() { }
        protected void B() { }

        public void C() { }

        public void ParentMethod() // 부모클래스의 ParentMethod 메소드
        {
            A();
            B();
            C();
        }
    }
    class DerivedClass : BaseClass // DerivedClass (파생) 클래스는 BaseClass(부모클래스)를 상속받는다
    {
        public void ChildMethod()
        {
            //A(); // private 접근 지시어로 선언된 메소드 접근은 같은 클래스 안에서만 접근 가능
            B(); // protected 접근 지시어로 선언되었고 자식 클래스는 부모 클래스의 파생 클래스 이므로 접근 가능
            C();
        }

    }

    class Program
    {
        struct MyPoint // 클래스와 유사한 구문, 상속 불가, 인터페이스 구현 불가
        {
            public int x;
            public int y;

            public MyPoint(int x, int y) // 구조체의 생성자는 매개변수를 넣어 만들어야 한다.
            {
                this.x = x;
                this.y = y;
            }

            public override string ToString()
            {
                return string.Format("({0}, {1})", x, y);
            }
        }

        class PointC
        {
            public int x;
            public int y;
            public PointC(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        static void Main(string[] args)
        {
            BaseClass parent = new BaseClass(); // Baseclass(부모) 클래스로 새 객체를 생성한다.
            parent.a = 1; // 보호수준 때문에 접근 불가
            DerivedClass child = new DerivedClass(); // DerivedClass(자식) 클래스로 새 객체를 생성한다.
            parent.ParentMethod();
            child.ChildMethod();
            child.C();

            MyPoint p; // 클래스와 달리 구조체는 new 연산자를 사용하지 않고 인스턴스화할 수 있다.
            p.x = 55;
            p.y = 77;
            Console.WriteLine("구조체 : p.x = {0}, p.y = {1}", p.x, p.y);
            Console.WriteLine(p.ToString());

            MyPoint p1 = new MyPoint(11,22); //구조체 선언
            PointC p2 = new PointC(11,22);  //클래스 선언
            MyPoint p3 = p1; // 값에 의한 복사 (구조체는 스택에 생성되는 값 타입)
            PointC p4 = p2; // 참조에 의한 복사 (클래스는 힙에 생성되는 참조 타입으로 힙의 주소를 전달)
            Console.WriteLine("구조체 : p1.x = {0}, p1.y = {1}", p1.x, p1.y);
            Console.WriteLine("클래스 : p2.x = {0}, p2.y = {1}", p2.x, p2.y);

            p3.x = 33;
            p4.x = 44;
            Console.WriteLine("구조체 : 값에 의한 복사 p1.x = {0}, p3.x = {1}", p1.x, p3.x);

            // 구조체는 스택영역에 값이 직접 들어가서 깊은 복사가 된다.

            Console.WriteLine("클래스 : 참조에 의한 복사 p2.x = {0}, p4.x = {1}", p2.x, p4.x);
            // 클래스는 참조하는 주소가 복사되면서 스택영역의 변수가 같은 힙영역을 가리킨다.
        }
    }
}



'C# > 문법 및 기능' 카테고리의 다른 글

C# 프로퍼티(Property)  (0) 2019.08.19
C# 인터페이스  (0) 2019.08.18
C# 접근 제한자(Access Modifier)  (0) 2019.08.13
자바와 C#의 차이점  (0) 2016.12.27
C# NameValueCollection  (0) 2016.03.20
블로그 이미지

Link2Me

,
728x90

접근 제한자(Access Modifier) : 외부로부터 타입(class, struct, inteface, delegate )나 위 타입들의 멤버들(method, property, event, field)로의 접근을 제한할 때 사용한다.


클래스 멤버는 5가지의 접근 제한자를 (public, internal, private, protected, protected internal) 모두 가질 수 있지만, 구조체(struct) 멤버는 상속이 되지 않으므로 3가지의 접근 제한자만 (public, internal, private) 가질 수 있다.


접근 제한자

클래스 내부

외부 클래스

파생(자식) 클래스

프로젝트

 public





 protected


 


 

 internal



 

 private

 

 

 

 protected internal


 

 


어셈블리는 하나의 단일한 단위로 존재하는 .NET의 실행 가능한 프로그램 또는 실행 프로그램의 일부이며  실행 및 배포의 단위라고 할 수 있다. C# 응용 프로그램 작성의 결과로 생긴 .exe 파일, 클래스 라이브러리 작성의 결과인 DLL이 각각 하나의 어셈블리 이다.
하나의 단일한 어셈블리 안의 모든 코드는 하나의 단일한 단위로 빌드, 배포되며 버전 번호가 부여되는데 각 어셈블리는 다른 프로그램들이 사용 할 수 있는 pulic class, 속성, 메소드 등을 노출하고, private으로 선언된 것들은 모두 어셈블리 안에 은폐된다.


internal
동일한 어셈블리 내에 있는 다른 타입들이 접근할 수 있다.


protected
파생 클래스에서 이 클래스 멤버에 접근할 수 있다.

private
클래스 내부와 구조체 내에서만 멤버에 접근 가능하다.


using System;

namespace ClassModifier
{
    class BaseClass // 부모 클래스
    {
        int a; // 클래스의 멤버를 접근 제한자로 지정하지 않으면 멤버의 접근 수준은 무조건 private로 자동 지정됨
        int b;

        private void A() { }
        protected void B() { }

        public void C() { }

        public void ParentMethod() // 부모클래스의 ParentMethod 메소드
        {
            A();
            B();
            C();
        }
    }
    class DerivedClass : BaseClass // DerivedClass (파생) 클래스는 BaseClass(부모클래스)를 상속받는다
    {
        public void ChildMethod()
        {
            //A(); // private 접근 지시어로 선언된 메소드 접근은 같은 클래스 안에서만 접근 가능
            B(); // protected 접근 지시어로 선언되었고 자식 클래스는 부모 클래스의 파생 클래스 이므로 접근 가능
            C();
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            BaseClass parent = new BaseClass(); // Baseclass(부모) 클래스로 새 객체를 생성한다.
            parent.a = 1; // 보호수준 때문에 접근 불가
            DerivedClass child = new DerivedClass(); // DerivedClass(자식) 클래스로 새 객체를 생성한다.
            parent.ParentMethod();
            child.ChildMethod();
            child.C();
        }
    }
}



'C# > 문법 및 기능' 카테고리의 다른 글

C# 인터페이스  (0) 2019.08.18
C# 구조체(Struct)  (0) 2019.08.14
자바와 C#의 차이점  (0) 2016.12.27
C# NameValueCollection  (0) 2016.03.20
C# Dictionary 와 comboBox 연결  (0) 2016.02.08
블로그 이미지

Link2Me

,
728x90

C# 동영상 강좌를 듣기 위해 Visual Studio Community 2019 버전을 설치했다.

그런데 컴파일(CTRL + F5)을 해보면 "9664 프로세스가 0 코드로 인해 종료되었습니다."가 매번 나온다.


종료 상태가 0이면 프로그램이 정상 종료했다는 뜻이란다.


이 메시지를 없애는 방법을 찾아봤더니

문구를 없애는 방법으로 [도구] → [옵션] → [디버깅] → [일반] → [체크] 디버깅이 중지되면 자동으로 콘솔 닫기

를 해주면 된다고 나온다.





블로그 이미지

Link2Me

,
728x90

파일 삭제 처리하는 로직 구현이다.

<?php
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass;

if(!isset($_GET['idx']) || empty($_GET['idx'])){
    $rs=0;
} else {
    // 등록자 여부 체크
    $R = $d->getUidData('bbs_data',$_GET['idx']);
    if($R['userID'] == $_SESSION['userID'] || (isset($_SESSION['authID']) && ($_SESSION['authID']==1 || $_SESSION['authID']==2))){
        // 등록자 또는 관리자(관리자, 최고관리자) 인 경우에는 삭제 가능
        $rs=$d->getDbDelete('bbs_data', 'uid='.$_GET['idx'].'');
        $d->getDbDelete('bbs_comment', 'parentid='.$_GET['idx'].''); //댓글 같이 삭제됨
    } else {
        $rs = -2;
    }
}
echo $rs;
?>


블로그 이미지

Link2Me

,
728x90

게시판 기능을 구현하고 있는데, 코딩 구현보다 Layout 기능 익히는 게 더 힘들어서 적어둔다.

기능 구현이 끝나면 수정해야 할 부분(보안 문제 고려사항)

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


error_reporting(0); 로 변경해야 한다.

 

<?php

// 파일명 : bbsView.php
error_reporting(E_ALL);
ini_set("display_errors", 1);

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php'; // PC접속은 IP 접속 허용이 된 경우에만 접속 가능
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'bbsClass.php';
$c = new bbsClass();
$d = new DBDataClass();

$bid = isset($_GET['bid']) ? $_GET['bid']: '';
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;

$R = $d->getDbData('bbs_data', 'uid="'.$_GET['uid'].'"', '*');
$html = ($R['html'] == 1) ? 'HTML' : 'TEXT';

 

// 쿠키를 이용한 조회수 중복 방지
if(!empty($R['uid']) && empty($_COOKIE['bbs_data_'.$R['uid']])) {
    if(strcmp($_SESSION['userID'],$R['userID']) !== 0){ // 등록자 본인이 아니면
        $d->getDbUpdate('bbs_data','hit=hit+1','uid='.$R['uid']);
        setcookie('bbs_data_'.$R['uid'], TRUE, time() + (60 * 60 * 24), '/');
    }
}

?>
<table class="table table-bordered table-hover table-sm" cellspacing="0" width="100%">
    <tr>
        <td style="width:70px;">제목</td>
        <td class="text-left"><?php echo $R['subject']?></td>
    </tr>
    <tr>
        <td>내용</td>
        <td class="text-left"><?php echo $c->getContents($R['content'],$html);?></td>
    </tr>
</table>

<?php include_once $g['path_bbs'].'bbsComment.php';?>

 

<div class="table-responsive text-nowrap">
    <div class="float-left info">
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="BBSHome">목록</button>
    </div>
    <div class="float-right info">
        <?php if($R['userID'] == $_SESSION['userID'] || (isset($_SESSION['authID']) && $_SESSION['authID']==1 )):?>
        <a href="bbsWrite.php" class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" id="bbsModify" data-id="<?=$R['uid'];?>" curPage="<?=$curPage;?>">수정</a>
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="bbsDelete" data-id="<?=$R['uid'];?>" curPage="<?=$curPage;?>">삭제</button>
        <?php endif;?>
    </div>
</div> 

 

bbsComment.php ==> 댓글 달기 및 댓글 보기

<?php

// 파일명 : bbsComment.php

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

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass();

?>

<div class="form-group">
    <form name="commentForm" class="form-inline">
        <input type="hidden" name="mode" value="new" />
        <input type="hidden" name="parentid" value="<?php echo $_GET['uid'];?>" />
        <input type="hidden" name="userID" value="<?php echo $_SESSION['userID'];?>" />
        <input type="hidden" name="userNM" value="<?php echo $_SESSION['userNM'];?>" />
        <input type="hidden" name="p" value="<?=$curPage;?>" />

        <div class="input-group mb-3" style="width:100%;">
          <input type="text" name="comment" class="form-control" placeholder="댓글을 입력하세요" aria-label="comment"
            aria-describedby="comment_form">
          <div class="input-group-append">
            <button type="button" class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" id="comment_form">댓글</button>
          </div>
        </div>

    </form>
</div>

<div class="table-responsive text-nowrap">
<table class="table table-hover table-sm" cellspacing="0" width="100%">
    <tbody>
    <?php $COMM = $d->getDbArray('bbs_comment','parentid="'.$_GET['uid'].'"','*','uid DESC',0,1);?>
    <?php while($C = mysqli_fetch_array($COMM)):?>
        <tr id="<?php echo $C['uid'];?>">
            <td class="text-left"><?php echo $C['userID'];?></td>
            <td class="text-left"><?php echo $C['comment'];?></td>
            <td><?php echo substr($C['d_regis'],0,8);?></td>
            <td>
            <button class="btn btn-md m-0 z-depth-0 comment_del" type="button" title='삭제'>&times;</button>
            </td>
        </tr>
    <?php endwhile;?>
    </tbody>
</table>
</div>

<?php
// 파일명 : bbsCommentChk.php
if(!isset($_SESSION)){
    session_start();
}
//echo '<pre>';print_r($_POST);echo '</pre>';
//exit;
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST);
    require_once 'path.php';// root 폴더를 기준으로 상대경로 자동 구하기
    require_once $g['path_config'].'dbconnect.php';
    require_once $g['path_class'].'dbDataClass.php';
    $d = new DBDataClass(); // AES_Decode()

    $comment = trim($comment);
    date_default_timezone_set('Asia/Seoul');

    if($mode == 'new'){
        $d_regis = date('YmdHis');
        $access_ip=$_SERVER['REMOTE_ADDR'];
        $QKEY = "parentid,comment,d_regis,userID,userNM,ip";
        $QVAL = "'$parentid','$comment','$d_regis','$userID','$userNM','$access_ip'";
        $d->getDbInsert('bbs_comment',$QKEY,$QVAL);
        echo 1;
    } else {
        // 등록자 여부 체크
        $R = $d->getUidData('bbs_comment',$uid);
        if($R['userID'] == $_SESSION['userID']){
            $QSET="comment='".$comment."'";
            $QVAL="uid='".$uid."'";

            $d->getDbUpdate('bbs_data',$QSET,$QVAL);
            echo 2;
        } else {
            echo -2;
        }
    }
} else {
    echo -1;
}
?>

<?php
// 파일명 : bbsCommentDelete.php
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass;

if(!isset($_GET['idx']) || empty($_GET['idx'])){
    $rs=0;
} else {
    // 등록자 여부 체크
    $R = $d->getUidData('bbs_comment',$_GET['idx']);
    if($R['userID'] == $_SESSION['userID'] || (isset($_SESSION['authID']) && ($_SESSION['authID']==1 || $_SESSION['authID']==2))){
        // 등록자 또는 관리자(관리자, 최고관리자) 인 경우에는 삭제 가능
        $rs=$d->getDbDelete('bbs_comment', 'uid='.$_GET['idx'].'');
    } else {
        $rs = -2;
    }
}
echo $rs;
?>

 

jQuery 처리 부분은 https://link2me.tistory.com/1665 를 참조하면 된다.

 

블로그 이미지

Link2Me

,
728x90

게시판에서 등록한 글을 MySQL DB에 저장하는 로직 구현이다.

jQuery 와 연동하여 처리하므로 MySQL DB에 등록되지 전에 값이 제대로 잘 전달되는지 체크하는 걸 시도한다.

echo '<pre>';print_r($_POST);echo '</pre>';
exit;


POST 변수가 문제없이 잘 전달되었으면 이제 관련 코드를 아래와 같이 구현했다.

게시판을 완벽하게 처리하는 것은 아니고 1단에 대한 값만 등록하고, 게시글이 등록되었을 때 관리자에게 메일이 전달되도록 하는 걸 추가했다.


<?php
if(!isset($_SESSION)){
    session_start();
}
//echo '<pre>';print_r($_POST);echo '</pre>';
//exit;
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST);
    require_once 'path.php';// root 폴더를 기준으로 상대경로 자동 구하기
    require_once $g['path_config'].'dbconnect.php';
    require_once $g['path_class'].'dbDataClass.php';
    require_once $g['path_class'].'bbsClass.php';
    $d = new DBDataClass();
    $c = new bbsClass();

    if($mode == 'write'){
        $subject = trim($subject);
        $content = trim($content);
        $html = 0;
        $depth = 1;
        $notice = 0;
        date_default_timezone_set('Asia/Seoul');

        if($uid == 0){
            $d_regis = date('YmdHis');
            $access_ip=$_SERVER['REMOTE_ADDR'];
            $QKEY = "bbsid,subject,content,html,depth,notice,d_regis,userID,userNM,ip";
            $QVAL = "'$bid','$subject','$content',$html,$depth,$notice,'$d_regis','$userID','$userNM','$access_ip'";
            $d->getDbInsert('bbs_data',$QKEY,$QVAL);
            Mail2Admin($subject,$content);
            echo 1;
        } else {
            // 등록자 여부 체크
            $R = $d->getDbData('bbs_data','uid='.$uid,'*');
            if($R['userID'] === $_SESSION['userID']){ // 관리자도 수정은 불가
                $QSET="subject='".$subject."',";
                $QSET.="content='".$content."',";
                $QSET.="html='".$html."'";
                $QVAL="uid='".$uid."'";

                $d->getDbUpdate('bbs_data',$QSET,$QVAL);
                echo 2;
            } else {
                echo -2;
            }
        }
    } else if($mode == 'delete'){
        $d->getDbDelete('bbs_data',"uid='".$uid."'");
        echo 3; // 삭제
    }
} else {
    echo -1;
}

function Mail2Admin($subject,$message){
    global $c;
    $to = "jsk005@naver.com";
    $from = "webmaster@abc.com";
    $nameFrom = "AppMaster";
    $message = $c->getContents($message,'TEXT');

    $mailheaders = "Return-Path: $from\r\n";
    $mailheaders.= "From: $nameFrom <$from>\r\n";
    $mailheaders.= "Content-Type: text/html;charset=utf-8\r\n";
    $mailheaders.= "MIME-Version: 1.0\r\n";

    mail($to, $subject, $message, $mailheaders, $from);
}
?>

function getContents($str,$html){
    if ($html == 'HTML'){
        $str = htmlspecialchars_decode(stripslashes($str));
        $str = str_replace('<A href=','<a target="_blank" href=',$str);
        $str = str_replace('<a href=','<a target="_blank" href=',$str);
        $str = str_replace('<a target="_blank" href="#','<a href="#',$str);
        $str = str_replace(' target="_blank">','>',$str);
        $str = str_replace('< param','<param',$str);
        $str = str_replace("\t",'&nbsp;&nbsp;&nbsp;&nbsp;',$str);
        $str = str_replace('@IFRAME@','iframe',$str);

        $str = str_replace('imgOrignWin(this.src)=""','onclick="imgOrignWin(this.src);"',$str);
        $str = str_replace('imgorignwin(this.src)=""','onclick="imgOrignWin(this.src);"',$str);
        $_atkParam = array(';a=','&a=','?a=','m=admin','system=');
        foreach($_atkParam as $_prm){
            $str = str_replace($_prm,'',$str);
        }
    }
    else {
        $str = str_replace('<','&lt;',$str);
        $str = str_replace('>','&gt;',$str);
        $str = str_replace('&nbsp;','&amp;nbsp;',$str);
        $str = str_replace("\t",'&nbsp;&nbsp;&nbsp;&nbsp;',$str);
        $str = nl2br($str);
    }
    return $str;
}



블로그 이미지

Link2Me

,
728x90

MDB(Meterial Design for Bootstrap4) 게시판을 만드는 골격과 jQuery 내용이다.

Bootstrap4 기본 문법 사항은 https://www.w3schools.com/bootstrap4/default.asp 에 잘 나와 있다.

그리고 MDB 는 https://mdbootstrap.com/ 를 수시로 참조하면 템플릿 구성을 깔끔하게 만들 수 있다.

유료 템플릿을 구입하여 사용하고 있기 때문에 무료용으로 사용할 경우에는 동작 안되는 것이 있을 수 있으니 관련 Class를 수정해서 사용하면 될 것이다.


파일을 여러개로 나누어서 필요한 파일을 Load 하여 사용하는 방법으로 구현했다.

ㅇ 상대경로 자동 설정 : https://link2me.tistory.com/1197 참조

ㅇ MDB Layout 알아둘 사항 : https://link2me.tistory.com/1593 참조

ㅇ PHP Class 개념 이해 : https://link2me.tistory.com/1164 참조.

    PHP Class 함수는 스스로 작성해야 하며, 타 게시글을 찾다보면 이해하게 될 것으로 본다.

    게시판 구현에 필요한 대부분의 코드는 기록했지만 PHP Class 는 오픈하지 않았다.

    스스로 만들어야 할 영역이라고 보면 된다.

    DBDataClass.php 에 대한 사항은 https://link2me.tistory.com/1680 에 오픈했다.

ㅇ 페이징 처리 : https://link2me.tistory.com/1112 참조하되, bootstrap4 기반으로 Class 변경하는 건 직접 하시라.

    PHP Class 작성에 대한 일부 코드를 확인할 수 있다.


bbs.php 파일

<!DOCTYPE html>
<head>
<?php
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php'; // 세션 체크
require_once $g['path_config'].'config.php';
require_once $g['path_layout'].'default/_import.head.php';
require_once $g['path_root'].'deviceChk.php';
?>
</head>

<body class="fixed-sn mdb-skin">
<header>
    <!-- Navbar -->
    <nav class="navbar fixed-top navbar-toggleable-md navbar-expand-lg scrolling-navbar double-nav">
        <div class="breadcrumb-dn mr-auto">
            <p><?=$hostName;?></p>
        </div>
        <ul class="nav navbar-nav nav-flex-icons ml-auto">
            <?php require_once $g['path_menu'].'item/item_login.php' ?>
        </ul>
    </nav>
    <!-- /.Navbar -->
</header>

<main>
    <div class="container-fluid text-center">
        <div class="row">
            <div class="col-md-12">
                <div class="content" id="panel_content">
                <!-- 불러온 파일이 이곳에 위치되도록 jQuery 처리 -->
                </div>
                <div id="ajaxPath" data-path="<?=$g['path_menu'];?>"></div>
            </div>
        </div>
    </div>
</main>
<!--/Main layout-->

<!-- SCRIPTS -->
<?php require_once $g['path_layout'].'default/_import.tail.php';?>
<script>
// SideNav Initialization
$(".button-collapse").sideNav();
new WOW().init();

var uri = (uri != null)? uri : "bbsList.php";
var datapath =$("#ajaxPath").attr('data-path');
var bbsid = (bbsid != null)? bbsid : "toadmin";

BBSListTable(uri,bbsid,1,'','',0);

$('.memberShow').on('click',function(e){
    e.preventDefault(); // a 링크, submit 실행 방지
    url = $(this).attr('href');
    var item = $(this).text();
    if(url != '#'){
        $('#panel_content').load(url, function(){
            ModifyData();
        });
    }
});

function fixedEncodeURIComponent (str) {
    // encodeURIComponent 함수는 IE8 이상의 브라우저에서 모두 지원한다.
    return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
}

function BBSListTable(uri,bbsid,curPage,where,keyword,idx){
    $('#panel_content').load(uri+'?bid='+bbsid+'&p='+curPage+'&where='+where+'&keyword='+fixedEncodeURIComponent(keyword)+'&uid='+idx, function() {
        //e.preventDefault();
        var curPage = $('#paging .act a').text();
        $('#paging li').click(function(e) {
            e.preventDefault();
            switch($(this).text()){
                case '◁':
                    curPage=parseInt($(this).next().text()) - 1;
                    break;
                case '▷':
                    curPage=parseInt($(this).prev().text()) + 1;
                    break;
                default:
                    curPage = $(this).text();
                    break;
            }
            BBSListTable(uri,bbsid,curPage,where,keyword,0);
        });

        $('#BBSListTable tbody tr').mouseover(function() {
            $(this).children().css({
                'backgroundColor' : '#DCDCDC', 'cursor' : 'pointer'
            });
        }).mouseout(function() {
            $(this).children().css({
                'backgroundColor' : '#FFFFFF', 'cursor' : 'default'
            });
        }).click(function() {
            var idx = $(this).attr('id');
            var bbsid =$("#bbsid").attr('data-bbsid');
            uri = "bbsView.php";
            BBSListTable(uri,bbsid,curPage,where,keyword,idx);
        });

        $('#BBSSearch').click(function(e){
            BBSSearch(uri,bbsid,curPage,where,keyword);
        });

        $('#BBSSearchKeyword').on('keypress', function(event){
            var agent = navigator.userAgent.toLowerCase();
            var keycode = (event.keyCode ? event.keyCode : event.which);
            if(keycode == 13){ // 엔터키가 입력될 때까지는 어떤 것도 하지 않는다.
                event.preventDefault(); // 엔터키가 입력되면 현재 이벤트의 기본 동작을 중단한다.
                //event.stopPropagation(); // 현재 이벤트가 상위로 전파되지 않도록 중단한다.
                BBSSearch(uri,bbsid,curPage,where,keyword);
            }
        });

        $('#BBSHome').click(function(e){
            e.preventDefault();
            uri = "bbsList.php";
            BBSListTable(uri,bbsid,1,'','',0);
        });

        $('#bbsWrite').click(function(e){
            e.preventDefault();
            uri = $(this).attr('href');
            var bbsid =$("#bbsid").attr('data-bbsid');
            BBSListTable(uri,bbsid,curPage,where,keyword,0);
        });

        $('#bbsModify').click(function(e){
            e.preventDefault();
            uri = $(this).attr('href');
            var idx = $(this).attr('data-id');
            var page = $(this).attr('curPage');
            BBSListTable(uri,bbsid,page,where,keyword,idx);
        });

        $('#bbsRegister').click(function(){
            var subject = $('#subject');
            var content = $('textarea#content');
            var curPage = $('input[name=p]').val();

            if(subject.val() ==''){
                alert('제목을 입력하세요');
                subject.focus();
                return false;
            }

            if(content.val() ==''){
                alert('내용을 입력하세요');
                content.focus();
                return false;
            }

            $.ajax({
                url:datapath+'bbsWriteChk.php',
                type: 'POST',
                data: $("#bbswriteForm").serializeArray(),
                dataType:'text',
                success:function(msg){
                    if(msg == 1){
                        alert('등록했습니다.');
                        BBSListTable(uri,bbsid,curPage,where,keyword,idx);
                    } else if(msg == 2){
                        alert('수정했습니다.');
                        BBSListTable(uri,bbsid,curPage,where,keyword,idx);
                    } else if(msg==-2){
                        alert('수정권한이 없습니다.');
                    } else {
                        alert('데이터를 다시 한번 확인하세요\n'+msg);
                        return false;
                    }
                },
                error: function(jqXHR, textStatus, errorThrown){
                    alert("arjax error : " + textStatus + "\n" + errorThrown);
                }
            });

        });

        $('#bbsDelete').click(function(e){
            e.preventDefault();
            var idx = $(this).attr('data-id');
            var page = $(this).attr('curPage');
            BBSDelete(uri,bbsid,page,where,keyword,idx);
        });

        $('#comment_form').click(function(e){
            e.preventDefault();
            var comment = $("input[name=comment]");
            if(comment.val() ==''){
                alert('댓글을 입력하세요');
                comment.focus();
                return false;
            }
            var page = $("input[name=p]").val();
            var uid = $("input[name=parentid]").val();

            $.ajax({
                url:datapath+'bbsCommentChk.php',
                type: 'POST',
                data: {
                    mode:$("input[name=mode]").val(),
                    parentid:uid,
                    userID:$("input[name=userID]").val(),
                    userNM:$("input[name=userNM]").val(),
                    comment:$("input[name=comment]").val()
                    },
                dataType:'text',
                success:function(msg){
                    if(msg == 1){
                        alert('등록했습니다.');
                        uri = "bbsView.php";
                        BBSListTable(uri,bbsid,page,where,keyword,uid);
                    } else if(msg==-2){
                        alert('수정권한이 없습니다.');
                        return false;
                    } else {
                        alert('데이터를 다시 한번 확인하세요\n'+msg);
                        return false;
                    }
                },
                error: function(jqXHR, textStatus, errorThrown){
                    alert("arjax error : " + textStatus + "\n" + errorThrown);
                }
            });

        });

        $(".comment_del").click(function(){
            var idx = $(this).parent().parent().attr('id');
            var page = $("input[name=p]").val();
            CommnetDelete(uri,bbsid,page,where,keyword,idx);
        });

        $('#ToMainPage').click(function(){
            window.location.href = $(location).attr('protocol')+"//"+$(location).attr('host');
        });

    });
}

function BBSSearch(uri,bbsid,curPage,where,keyword){
    var where = $('[name=where]').val();
    var keyword = $('[name=keyword]').val();
    if(keyword.length == 0){
        alert('검색어를 입력하세요');
        $('input[name=keyword]').focus();
        return false;
    }
    BBSListTable(uri,bbsid,1,where,keyword,0);
}

function BBSDelete(uri,bbsid,curPage,where,keyword,idx){
    var verify = confirm('삭제하시겠습니까? \n 복구할 수 없습니다.');
    if (verify) {
        $.get('bbsDelete.php?idx='+idx, function(msg) {
            if (msg == 1) {
                alert('삭제되었습니다.');
                uri = "bbsList.php";
                BBSListTable(uri,bbsid,curPage,where,keyword,0);
            } else if(msg == -2){
                alert('삭제 권한이 없습니다.');
            } else {
                alert('삭제중 오류가 발생하였습니다.\n'+msg); // 디버깅 모드
                //alert('삭제중 오류가 발생하였습니다.'); // 운용 모드
            }
        });
    }
}

function CommnetDelete(uri,bbsid,curPage,where,keyword,idx){
    var verify = confirm('삭제하시겠습니까? \n 복구할 수 없습니다.');
    if (verify) {
        $.get('bbsCommentDelete.php?idx='+idx, function(msg) {
            if (msg == 1) {
                uri = "bbsView.php";
                var uid = $("input[name=parentid]").val();
                BBSListTable(uri,bbsid,curPage,where,keyword,uid);
            } else if(msg == -2){
                alert('삭제 권한이 없습니다.');
            } else {
                alert('삭제중 오류가 발생하였습니다.\n'+msg);
                //alert('삭제중 오류가 발생하였습니다.');
            }
        });
    }
}
</script>
</body>
</html>

<?php

// 파일명 : bbsList.php

error_reporting(0);
// 테이블 접속 처리
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'bbsClass.php';
require_once $g['path_class'].'adminClass.php';
$a = new adminClass();
$b = new bbsClass();
$d = new DBDataClass(); // AES_Decode()

$bbsid = isset($_GET['bid']) ? $_GET['bid'] :'toadmin';
$link_url = "bbs.php"; // 현재 실행중인 파일명 가져오기
$page = isset($_GET['page'])? trim($_GET['page']):1;//페이지 변수 설정
$rowsPage = ($mtype == 3) ? 5 : 5; // 한 화면에 표시되는 게시글 수
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$m = isset($_GET['m']) ? $_GET['m'] :'list';

$flddata ="*";// 화면에 출력할 칼럼 발췌
$where = isset($_GET['where']) ? $_GET['where']: '';
$keyword = isset($_GET['keyword']) ? $_GET['keyword']: '';
$xorderby= isset($xorderby) ? $xorderby : 'uid DESC'; // 없는 칼럼인지 꼭 체크하라.

$sqlque = 'display=1';
$uri =isset($_GET['uri'])? $_GET['uri']:'';

if($where && $keyword) {
    if($where == 'subject') $sqlque .= " and (subject LIKE '%".$keyword."%') ";
    if($where == 'userNM') $sqlque .= " and (userNM LIKE '%".$keyword."%') ";
}

$g['url_link']=($m?'m='.$m.'&amp;':'').($where?'where='.$where.'&amp;':'').($keyword?'keyword='.urlencode(stripslashes($keyword)).'&amp;':'');
$g['bbs_reset'] = $link_url.'?'.($m?'m='.$m.'&amp;':'');

$table ='bbs_data'; // 기본 접속 테이블과 동일구조 테이블 처리
$NUM = $d->getDbRows($table,$sqlque); // 전체 게시글수
$TPG = $b->getTotalPage($NUM,$rowsPage);
$rows= $d->getDbArray($table,$sqlque,$flddata,$xorderby,$rowsPage,$curPage);
$i = 0;
?>
<div class="table-responsive text-nowrap">
<div class="float-left">
    <?php if( $keyword ):?><strong>"<?php echo $keyword?>"</strong> 검색결과 : <?php endif?>
    <?php echo number_format($NUM)?>개 (<?php echo $curPage;?>/<?php echo $TPG;?>페이지)
</div>
<div class="float-right">
    <?php if(isset($_SESSION['userID'])):?>
    <a href="bbsWrite.php" class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 " id="bbsWrite">글쓰기</a>
    <?php endif?>
    <div id="bbsid" data-bbsid="<?php echo $bbsid; ?>"></div>
</div>

<table id="BBSListTable" class="table table-striped table-bordered table-hover table-sm" cellspacing="0" width="100%">
    <thead align='center'>
        <tr>
            <?php if($mtype==3):?><th scope="col">No</th><?php endif;?>
            <th scope="col">제목</th>
            <?php if($mtype==3):?><th scope="col">이름</th><?php endif;?>
            <th scope="col">날짜</th>
            <?php if($mtype==3):?><th scope="col">조회수</th><?php endif;?>
        </tr>
    </thead>
    <tbody>
        <?php if($NUM == 0):?>
        <tr>
            <td colspan="5">등록된 게시글이 없습니다.</td>
        </tr>
        <?php else:?>
        <?php foreach($rows as $R):?>
        <?php
            $no = $NUM - (($curPage - 1) * $rowsPage) - $i;
            $i++;
        ?>
        <tr id="<?php echo $R['uid']; ?>">
            <?php if($mtype==3):?><td><?php echo $no;?></td><?php endif;?>
            <td class="text-left"><?php echo $R['subject'];?></td>
            <?php if($mtype==3):?><td><?php echo $R['userNM'];?></td><?php endif;?>
            <td><?php echo substr($R['d_regis'],0,8);?></td>
            <?php if($mtype==3):?><td><?php echo $R['hit'];?></td><?php endif;?>
        </tr>
        <?php endforeach;?>
        <?php endif;?>
    </tbody>
</table>
<div class='form-group'>
    <form name="BBSForm" class="form-inline" action="<?php echo $link_url;?>">
        <input type="hidden" name="m" value="<?php echo $m;?>" />
        <input type="hidden" name="orderby" value="<?php echo $xorderby;?>" />

        <div class="input-group mb-3" style="width:80px;">
            <select name="where" class="browser-default custom-select">
                <option value="subject">제목</option>
                <option value="userNM">등록자</option>
            </select>
        </div>
        <div class="input-group mb-3" style="width:calc(100% - 80px);">
          <input type="text" name="keyword" class="form-control" id="BBSSearchKeyword">
          <div class="input-group-append">
            <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="BBSSearch">검색</button>
            <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="BBSHome">목록</button>
          </div>
        </div>
    </form>
</div>
<?php $b->PageLinkView($link_url,$NUM,$rowsPage,$curPage,$g['url_link']);?>
<button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0" id="ToMainPage" type="button">메인화면 전환</button>
</div>

<?php

// 파일명 : bbsView.php

error_reporting(0);
// error_reporting(E_ALL); 
//ini_set("display_errors", 1);

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'bbsClass.php';
$c = new bbsClass();
$d = new DBDataClass();

$bid = isset($_GET['bid']) ? $_GET['bid']: '';
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;

$R = $d->getDbData('bbs_data', 'uid='.$_GET['uid'], '*');
$html = ($R['html'] == 1) ? 'HTML' : 'TEXT';
?>
<table class="table table-bordered table-hover table-sm" cellspacing="0" width="100%">
    <tr>
        <td style="width:70px;">제목</td>
        <td class="text-left"><?php echo $R['subject']?></td>
    </tr>
    <tr>
        <td>내용</td>
        <td class="text-left"><?php echo $c->getContents($R['content'],$html);?></td>
    </tr>
</table>

<?php include_once $g['path_bbs'].'bbsComment.php';?>

<div class="table-responsive text-nowrap">
    <div class="float-left info">
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="BBSHome">목록</button>
    </div>
    <div class="float-right info">
        <?php if($R['userID'] == $_SESSION['userID']):?>
        <a href="bbsWrite.php" class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" id="bbsModify" data-id="<?=$R['uid'];?>" curPage="<?=$curPage;?>">수정</a>
        <?php endif;?>
        <?php if($R['userID'] == $_SESSION['userID'] || (isset($_SESSION['authID']) && $_SESSION['authID']==1) ):?>
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="bbsDelete" data-id="<?=$R['uid'];?>" curPage="<?=$curPage;?>">삭제</button>
        <?php endif;?>
    </div>
</div>

<?php

// 파일명 : bbsWrite.php

error_reporting(0);
// 테이블 접속 처리
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'bbsClass.php';
require_once $g['path_class'].'adminClass.php';
$a = new adminClass();
$b = new bbsClass();
$d = new DBDataClass(); // AES_Decode()

$bid = isset($_GET['bid']) ? $_GET['bid']: '';
$uid = isset($_GET['uid']) ? $_GET['uid']: '';
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$R=$d->getUidData('bbs_data',$uid);

?>
<div class="table-responsive text-nowrap">
<form id="bbswriteForm" method="post" class="text-center border border-light p-5">
    <table class="table table-striped table-bordered table-hover table-sm" cellspacing="0" width="100%">
        <input type="hidden" name="mode" value="write" />
        <input type="hidden" name="p" value="<?php echo $curPage;?>" />
        <input type="hidden" name="bid" value="<?php echo $bid;?>" />
        <input type="hidden" name="uid" value="<?php echo $R['uid'];?>" />
        <input type="hidden" name="userID" value="<?php echo $_SESSION['userID'];?>" />
        <input type="hidden" name="userNM" value="<?php echo $_SESSION['userNM'];?>" />

        <tr>
            <td>제목</td>
            <td><input type="text" name="subject" id="subject" class="form-control mb-4" placeholder="제목을 입력하세요" value="<?=$R['subject'];?>"></td>
        </tr>

        <tr>
            <td>내용</td>
            <td><textarea name="content" id="content" class="form-control mb-4" rows="8"  placeholder="내용을 입력하세요"><?=$R['content'];?></textarea></td>
        </tr>

        <tr>
            <td colspan="2">
            <button class="btn btn-info btn-block my-4" id="bbsRegister" type="submit">등록</button>
            </td>
        </tr>
    </table>
</form>
</div>


블로그 이미지

Link2Me

,
728x90

게시판 테이블 샘플이다.


CREATE TABLE IF NOT EXISTS `bbs_data` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `display` tinyint(2) NOT NULL DEFAULT '1',
  `bbsid` varchar(30) NOT NULL DEFAULT '',
  `level` tinyint(4) NOT NULL DEFAULT '1',
  `subject` varchar(200) NOT NULL DEFAULT '',
  `content` text NOT NULL,
  `html` tinyint(2) NOT NULL DEFAULT '0',
  `depth` tinyint(4) NOT NULL DEFAULT '0',
  `d_regis` varchar(14) NOT NULL DEFAULT '',
  `d_modify` varchar(14) NOT NULL DEFAULT '',
  `hit` int(11) NOT NULL DEFAULT '0',
  `hidden` tinyint(4) NOT NULL DEFAULT '0',
  `notice` tinyint(4) NOT NULL DEFAULT '0',
  `userID` varchar(60) NOT NULL,
  `userNM` varchar(50) NOT NULL,
  `ip` varchar(25) NOT NULL DEFAULT '',
  PRIMARY KEY (`uid`),
  KEY `bbsid` (`bbsid`),
  KEY `subject` (`subject`),
  KEY `userID` (`userID`),
  KEY `d_regis` (`d_regis`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;



댓글 게시판 테이블


CREATE TABLE IF NOT EXISTS `bbs_comment` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `parentid` int(11) NOT NULL DEFAULT '0',
  `comment` varchar(255) NOT NULL DEFAULT '',
  `d_regis` varchar(14) NOT NULL DEFAULT '',
  `userID` varchar(50) NOT NULL DEFAULT '',
  `userNM` varchar(50) NOT NULL,
  `ip` varchar(25) NOT NULL DEFAULT '',
  PRIMARY KEY (`uid`),
  KEY `parent_uid` (`parentid`),
  KEY `d_regis` (`d_regis`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


블로그 이미지

Link2Me

,