React Query String

React/React 2022. 6. 8. 00:05
728x90

http://localhost:3000/about?name=react&id=123 와 같은 URL 입력값에서 Query String을 추출하는 함수 예제이다.

React 강의를 듣고 그대로 타이핑을 하는데 동작이 안된다. 오래된 버전이라 그런가???

구글링을 해보고 그대로 실행해보는데도 역시 안되는 것들이 있다.

react-router-dom v6 에 맞게 구현해야 동작된다.

import React, {useState} from 'react';
import { useLocation } from 'react-router';
import qs from "query-string";  // npm install query-string
 
 
const About = () => {
    // In React Hooks:
    const searchParams = useLocation().search;
    console.log(searchParams);
    const query = qs.parse(searchParams);
    console.log(query);
 
    const name = new URLSearchParams(searchParams).get('name');
    const id = new URLSearchParams(searchParams).get('id');
    console.log({ name, id });
 
    return (
        <div>
            <h2>About 페이지입니다.</h2>
            {query.name && <p>name 은 {query.name} 입니다.</p>}
        </div>
    );
};
 
// https://everttimberg.io/blog/custom-react-hook-query-state/ 참고하자.
 
export default About;

 

비구조화 할당 문법을 통해 props 내부 값 추출하기

import {Route, Routes, useParams} from "react-router-dom";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import About from "./pages/About";
 
function App() {
    const { id } = useParams();
    return (
        <Routes>
            <Route path="/" element={<Home />}/>
            <Route path="/profile" element={<Profile/>}/>
            <Route path="/profile/:id" element={<Profile/>}/>
            <Route path="/about" element={<About name={"React"/>}/>
        </Routes>
    );
}
 
export default App;

 

props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며,

컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있다.

{props.name} 대신에 {name} 으로 내부 값을 바로 추출하는 방법 : const { name } = props;

import React, {useState} from 'react';
import { useLocation } from 'react-router';
import qs from "query-string";  // npm install query-string
 
 
const About = props => {
    // In React Hooks:
    const searchParams = useLocation().search;
    console.log(searchParams);
    const query = qs.parse(searchParams);
    console.log(query);
 
    //const name = new URLSearchParams(searchParams).get('name');
    //const id = new URLSearchParams(searchParams).get('id');
    //console.log({ name, id });
 
    const { name } = props; // 비구조화 할당 문법
 
    return (
        <div>
            <h2>{name} About 페이지입니다.</h2>
            {query.name && <p>name 은 {query.name} 입니다.</p>}
        </div>
    );
};
 
 
export default About;

리액트에는 두가지 종류의 state가 있다.

하나는 클래스형 컴포넌트가 지니고 있는 state 이고,

다른 하나는 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state이다.

 

 

'React > React' 카테고리의 다른 글

React-bootstrap Header.js 테스트  (0) 2022.08.21
React CORS error 해결 방법  (0) 2022.08.20
React Router v6  (0) 2022.06.07
React useState  (0) 2022.06.04
React 값 전달(부모 → 자식, 자식 → 부모)  (0) 2022.06.03
블로그 이미지

Link2Me

,

React Router v6

React/React 2022. 6. 7. 06:34
728x90

웹 애플리케이션에서 라우팅이라는 개념은 사용자가 요청한 URL에 따라 알맞는 페이지를 보여주는 것을 의미한다.

react-router-dom 버전 6 에서는 element 로 컴포넌트를 만들어야 동작하고, component 로 된 것은 에러 발생하고 동작되지 않는다.

V6 에서는 Switch 가 사라지고, Routes 로 대체되었다. 

Routes는 기존 Switch 처럼 경로 순서를 기준으로 선택하는 것이 아닌, 가장 일치하는 route 기반으로 선택하게 된다.

useHistory 가 사라지고, useNavigate 로 대체되었다.

- useNavigate로 기존에 useHistory의 기능을 전부 대체 가능하다.
- useHistory의 history는 객체였지만 useNavigate의 navigate는 함수다.
Route에 children, component가 사라지고, element 사용한다.

 

npm install react-router-dom // 라이브러리 설치

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom";
 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
 
reportWebVitals();

 

Route에 exact Prop 사라졌다. exact가 기본으로 되어있다.

기존 Route는 꼭 Switch 가 상위 요소로 없어도 되지만, v6의 Route는 Routes의 직속 자식이어야 한다.

import {Route, Routes, useParams} from "react-router-dom";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import About from "./pages/About";
 
function App() {
    const { id } = useParams();
    return (
        <Routes>
            <Route exact={true} path="/" element={<Home />}/>
            <Route path="/profile" element={<Profile/>}/>
            <Route path="/profile/:id" element={<Profile/>}/>
            <Route path="/about" element={<About/>}/>
        </Routes>
    );
}
 
export default App;

 

Link 는 컴포넌트, to 는 props

import React from 'react';
import {Link} from "react-router-dom";
 
const Home = () => {
    return (
        <div>
            <h1>Home 페이지입니다.</h1>
            <Link to={"/about?name=react"}>소개</Link>
        </div>
    );
};
 
export default Home;

 

Profile = (props) => 를 사용하지 말라.

http://localhost:3000/profile/4 를 해보면 결과를 확인할 수 있다.

import React from 'react';
import {useParams} from "react-router-dom";
 
const Profile = () => {
    // react-router-dom 버전 6부터는 element로 컴포넌트를 만들고
    // route props(match, history, location)을 받지 않는다.
    // useParams, useLocation, useHistory를 사용하여 route context에 접근한다.
    // let id = props.match.params.id 를 사용하면 에러 발생한다.
    const { id } = useParams();
    return (
        <div>
            <h1>Profile 페이지입니다.</h1>
            {id && <h3>ID: {id}</h3>}
        </div>
    );
};
 
export default Profile;

 

Query String 에 대한 사항은 다음 게시글을 읽어보면 된다.

https://link2me.tistory.com/2177

 

 

 

'React > React' 카테고리의 다른 글

React CORS error 해결 방법  (0) 2022.08.20
React Query String  (0) 2022.06.08
React useState  (0) 2022.06.04
React 값 전달(부모 → 자식, 자식 → 부모)  (0) 2022.06.03
React Event 처리하기  (0) 2022.05.29
블로그 이미지

Link2Me

,
728x90

https://link2me.tistory.com/1506 에 있는 파일 다운로드 및 실행 코드와 원리는 동일한 것도 포함되어 있으니 참고하면 도움된다.

 

전체 소스코드는 GitHUB 에 올려진 것을 참조하시라.

서버 코드와 관련된 URL, AES Key는 변경해서 올렸다.

https://github.com/jsk005/JavaProjects/tree/master/pdfviewer

 

GitHub - jsk005/JavaProjects: 자바 기능 테스트

자바 기능 테스트. Contribute to jsk005/JavaProjects development by creating an account on GitHub.

github.com

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.link2me.android.pdfviewer">
 
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission
        android:name="android.permission.REQUEST_INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true">
        <activity
            android:name=".ui.MainActivity"
            android:exported="false" />
        <activity
            android:name=".ui.SplashActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"
                tools:replace="android:resource" />
        </provider>
    </application>
 
</manifest>
 

 

 

 

package com.link2me.android.pdfviewer.ui;
 
import android.content.Context;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Toast;
 
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
 
import com.link2me.android.common.BackPressHandler;
import com.link2me.android.common.Utils;
import com.link2me.android.common.Value;
import com.link2me.android.pdfviewer.R;
import com.link2me.android.pdfviewer.adapter.BindPdfViewListAdapter;
import com.link2me.android.pdfviewer.databinding.ActivityMainBinding;
import com.link2me.android.pdfviewer.model.PdfResult;
import com.link2me.android.pdfviewer.model.Pdf_Item;
import com.link2me.android.pdfviewer.network.RetrofitAdapter;
import com.link2me.android.pdfviewer.network.RetrofitService;
import com.link2me.android.pdfviewer.network.RetrofitUrl;
 
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
 
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
 
public class MainActivity extends AppCompatActivity implements BindPdfViewListAdapter.OnItemClickListener {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;
 
    private ActivityMainBinding binding;
 
    private ArrayList<Pdf_Item> pdfItemList = new ArrayList<>();
    private BindPdfViewListAdapter mAdapter;
 
    DownloadPdfFromURL downloadApk;
    private File outputFile;
 
    private BackPressHandler backPressHandler;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
 
        backPressHandler = new BackPressHandler(this); // 뒤로 가기 버튼 이벤트
        mContext = MainActivity.this;
        initView();
    }
 
    private void initView() {
        createPdfList(); // 서버 데이터 가져오기
        buildRecyclerView();
    }
 
    private void createPdfList() {
        String keyword = Value.encryptAES(Value.URLkey());
        RetrofitService service = RetrofitAdapter.getClient().create(RetrofitService.class);
        Call<PdfResult> call = service.getPdfData(keyword,"");
        call.enqueue(new Callback<PdfResult>() {
            @Override
            public void onResponse(Call<PdfResult> call, Response<PdfResult> response) {
                if(response.body().getStatus().contains("success")){
                    pdfItemList.clear(); // 서버에서 가져온 데이터 초기화
 
                    for(Pdf_Item item: response.body().getPdfinfo()){
                        pdfItemList.add(item);
                    }
 
                    // runOnUiThread()를 호출하여 실시간 갱신한다.
                    runOnUiThread(() -> {
                        // 갱신된 데이터 내역을 어댑터에 알려줌
                        mAdapter.notifyDataSetChanged();
                    });
                } else {
                    Utils.showAlert(mContext,response.body().getStatus(),response.body().getMessage());
                }
            }
 
            @Override
            public void onFailure(Call<PdfResult> call, Throwable t) {
 
            }
        });
    }
 
    private void buildRecyclerView(){
        binding.pdfListview.setHasFixedSize(true);
        LinearLayoutManager manager = new LinearLayoutManager(mContext);
        mAdapter = new BindPdfViewListAdapter(mContext,pdfItemList); // 객체 생성
 
        DividerItemDecoration decoration = new DividerItemDecoration(mContext,manager.getOrientation());
        binding.pdfListview.addItemDecoration(decoration);
        binding.pdfListview.setLayoutManager(manager);
        binding.pdfListview.setAdapter(mAdapter);
 
        mAdapter.setOnItemSelectClickListener(this);
    }
 
    @Override
    public void onItemClicked(View view, Pdf_Item item, int position) {
//        Log.d(TAG, RetrofitUrl.BASE_URL+item.getPdfurl());
        Toast.makeText(getApplicationContext(), "잠시 기다리시면 "+item.getTitle()+" PDF 파일 열람이 가능합니다", Toast.LENGTH_LONG).show();
        String PDFUrl = RetrofitUrl.BASE_URL+item.getPdfurl();
        downloadPDF(PDFUrl);
    }
 
    private void downloadPDF(String fileUrl) {
        // 백그라운드 객체를 만들어 주어야 다운로드 취소가 제대로 동작됨
        downloadApk = new DownloadPdfFromURL();
        downloadApk.execute(fileUrl);
    }
 
    class DownloadPdfFromURL extends AsyncTask<String, Integer, String> {
 
        @Override
        protected String doInBackground(String... strings) {
            int count;
            int lenghtOfFile = 0;
            InputStream input = null;
            OutputStream fos = null;
 
            File filePath = new File(Environment.getExternalStorageDirectory() + "/download");
            outputFile = new File(filePath, "tempPDF.pdf");
            if (outputFile.exists()) { // 기존 파일 존재시 삭제하고 다운로드
                outputFile.delete();
            }
 
            try {
                URL url = new URL(strings[0]);
                URLConnection connection = url.openConnection();
                connection.connect();
 
                lenghtOfFile = connection.getContentLength(); // 파일 크기를 가져옴
 
                input = new BufferedInputStream(url.openStream());
                fos = new FileOutputStream(outputFile);
                byte data[] = new byte[1024];
                long total = 0;
 
                while ((count = input.read(data)) != -1) {
                    if (isCancelled()) {
                        input.close();
                    }
                    total = total + count;
                    if (lenghtOfFile > 0) { // 파일 총 크기가 0 보다 크면
                        publishProgress((int) (total * 100 / lenghtOfFile));
                    }
                    fos.write(data, 0, count); // 파일에 데이터를 기록
                }
 
                fos.flush();
 
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return null;
        }
 
        protected void onPostExecute(String result) {
            if (result == null) {
                // 미디어 스캐닝
                MediaScannerConnection.scanFile(getApplicationContext(), new String[]{outputFile.getAbsolutePath()}, nullnew MediaScannerConnection.OnScanCompletedListener() {
                    @Override
                    public void onScanCompleted(String s, Uri uri) {
 
                    }
                });
 
                // 다운로드한 파일 실행하여 업그레이드 진행하는 코드
                if (Build.VERSION.SDK_INT >= 24) {
                    openPDF(outputFile);
                } else {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    Uri apkUri = Uri.fromFile(outputFile);
                    intent.setDataAndType(apkUri, "application/pdf");
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    getApplicationContext().startActivity(intent);
                }
 
            } else {
                Toast.makeText(getApplicationContext(), "다운로드 에러", Toast.LENGTH_LONG).show();
            }
        }
    }
 
    void openPDF(File file) {
        Uri fileUri = FileProvider.getUriForFile(mContext, mContext.getApplicationContext().getPackageName() + ".fileprovider",file);
 
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(fileUri, "application/pdf");
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        try {
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    @Override
    public void onBackPressed() {
        backPressHandler.onBackPressed();
    }
}
 

 

 

'안드로이드 > Android 활용' 카테고리의 다른 글

Android APP(Java)에서 PHP Session 저장 및 사용  (0) 2023.12.23
WebView assets html 파일 읽기  (0) 2020.11.27
Android TextToSpeech  (0) 2020.09.30
Intent 이메일 전송하기  (0) 2020.09.08
Profile 이미지 처리  (0) 2020.09.01
블로그 이미지

Link2Me

,

React useState

React/React 2022. 6. 4. 00:05
728x90

Hook은 React 버전 16.8(2019년)부터 React 요소로 새로 추가되었다.

https://ko.reactjs.org/docs/hooks-effect.html 가 React 에서 기본 제공하는 내용 설명이다.

함수형 컴포넌트의 주요 단점은 state 와 라이프사이클 API 의 사용이 불가능하다는 점이었는데, 리액트 16.8 업데이트 이후 Hooks 기능이 도입되면서 해결되었다.

현재 리액트 공식 매뉴얼에서는, Class형 컴포넌트보다는 Function형 컴포넌트와 Hooks로 React 프로젝트를 만들기를 권장하고 있다.

 

App.js

import logo from './logo.svg';
import './App.css';
import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
 
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Example1 />
        <Example2 />
        <Example3 />
      </header>
    </div>
  );
}
 
export default App;

 

Example1.jsx

파일명을 생성하고 파일 안에서 rcc 를 입력하고 TAB 키를 누르면 자동완성된 기본 컴포넌트가 만들어진다.

클래스형 컴포넌트에서는 render함수가 꼭 있어야 한다.

import React, {Component} from 'react';
 
class Example1 extends Component {
    state = {count: 0};
 
    render() {
        const { count } = this.state;
        return (
            <div>
                <p>You clicked {count} times.</p>
                <button onClick={this.click}>Click me</button>
            </div>
        );
    }
 
    click = () => {
        this.setState({
            count: this.state.count + 1
        });
    };
}
 
export default Example1;

 

Example2.jsx

- useState 함수의 인자에는 상태의 초기값을 넣어 준다. 초기값은 숫자, 문자열, 객체, 배열 등

- 배열 비구조화 할당 const [count, setCount] 에서 첫번째 원소는 현재 상태이고, 두번째 원소는 상태를 바꾸어주는 함수이다. 이 함수를 Setter 함수라고 부른다.

- 하나의 useState 함수는 하나의 상태 값만 관리할 수 있다.

  컴포넌트에서 관리해야 할 상태가 여러 개라면 useState를 여러 번 사용하면 된다.

- state 는 컴포넌트 자체적으로 지닌 값으로 컴포넌트 내부에서 값을 업데이트할 수 있다.

import React from 'react';
 
const Example2 = () => {
    const [count, setCount] = React.useState(0);
 
    function click() {
        setCount(count + 1);
    }
 
    return (
        <div>
            <p>You clicked {count} times.</p>
            <button onClick={click}>Click me</button>
        </div>
    );
};
 
export default Example2;

 

Exampe3.jsx

// rsc 입력하고 Tab 키를 누른다.
import React from 'react';
 
const Example3 = () => {
    // useState : state를 대체할 수 있다.
    const [state, setState] = React.useState({count: 0});
 
    function click() {
        setState((state) => ({
            count: state.count + 1,
        }));
    }
 
    return (
        <div>
            <p>You clicked {state.count} times.</p>
            <button onClick={click}>Click me</button>
        </div>
    );
};
 
export default Example3;

 

 

이제 bootstrap4 를 적용해보자.

# Step1 – Install Bootstrap 4
npm install bootstrap --save

 

App.js

import logo from './logo.svg';
import './App.css';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
 
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Example1 />
        <Example2 />
        <Example3 />
      </header>
    </div>
  );
}
 
export default App;

 

 

Exampl1.jsx

import React, {Component} from 'react';
 
class Example1 extends Component {
    state = {count: 0};
 
    render() {
        const { count } = this.state;
        return (
            <div className="col-md-12 text-center">
                <p>You clicked {count} times.</p>
                <button className="btn btn-primary" onClick={this.click}>Click me</button>
            </div>
        );
    }
 
    click = () => {
        this.setState({
            count: this.state.count + 1
        });
    };
}
 
export default Example1;

 

 

 

'React > React' 카테고리의 다른 글

React Query String  (0) 2022.06.08
React Router v6  (0) 2022.06.07
React 값 전달(부모 → 자식, 자식 → 부모)  (0) 2022.06.03
React Event 처리하기  (0) 2022.05.29
React Component 만들기  (0) 2022.05.28
블로그 이미지

Link2Me

,
728x90

React Component 에서 값을 전달하는 방법이다.

자식 → 부모 데이터 전달

props : 상위 컴포넌트에서 하위 컴포넌트로 값을 전달하는 수단

- 하위 컴포넌트에서 상위 컴포넌트로 값을 전달할 수 없다.

- 하위 컴포넌트에서 상위 컴포넌트가 전달해준 값에 접근할 수 있게 해준다.

 

const [스테이트 값, 스테이트 변경 함수] = useState(스테이트 초기값);

import React, {useState} from 'react';
import Child from "./Child";
 
const Parent = () => {
    const [data, setData] = useState(0);
    // 1. 부모 컴포넌트에서 useState 를 통해 전달받은 데이터를 저장할 변수를 선언한다.
    // 2. 부모 컴포넌트에서 props로 함수를 넣어주면,
    //    자식 컴포넌트에서 그 함수를 이용해 값을 전달한다.
 
    const parentFnc = (data) => {
      setData(data);
    }
 
    return (
        <div>
            <div>부모 컴포넌트 값 : {data}</div>
            <Child number={data} getData={parentFnc} />
        </div>
    );
};
 
export default Parent;

 

Child 컴포넌트

Event Handling

- camelCase 로만 사용할 수 있다. onClick, onMouseEnter

- 이벤트에 연결된 자바스크립트 코드는 함수이다. onClick={함수}

- 실제 DOM 요소들에만 사용 가능하다.

import React from 'react';
 
const Child = props => {
    const onClick = () => {
      console.log(props.number);
      props.getData(props.number + 1);
    }
 
    return (
        <div>
            <button onClick={onClick}>+자식버튼</button>
        </div>
    );
};
 
export default Child;

Child 컴포넌트를 아래와 같이 수정 가능하다.

import React from 'react';
 
const Child = props => {
    const {number, getData} = props; // 비구조화 할당 문법
 
    const onClick = () => {
        console.log(number);
        getData(number + 1);
    };
 
    return (
        <div>
            <button onClick={onClick}>+자식버튼</button>
        </div>
    );
};
 
export default Child;

 

import Parent from "./components/Parent";
 
function App() {
  return (
    <div className="App">
      <Parent />
    </div>
  );
}
 
export default App;

 

부모 → 자식 데이터 전달

 

 

 

 

 

 

 

'React > React' 카테고리의 다른 글

React Query String  (0) 2022.06.08
React Router v6  (0) 2022.06.07
React useState  (0) 2022.06.04
React Event 처리하기  (0) 2022.05.29
React Component 만들기  (0) 2022.05.28
블로그 이미지

Link2Me

,
728x90

React 엘리먼트에서 이벤트를 처리하는 방식은 DOM 엘리먼트에서 이벤트를 처리 방식과 매우 유사하다.
- React의 이벤트는 소문자 대신 캐멀 케이스(camelCase)를 사용한다.
- JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달한다.

- DOM 요소에만 이벤트 설정이 가능하다. div, button, input, form, ul, li, span 등

- React Component 에는 이벤트 설정이 불가능하다.

 

Event Handler 예제

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <meta name="theme-color" content="#000000"/>
    <title>React App Sample</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
    class Component extends React.Component {
        constructor(props) {
            super(props);
            this.state = {count: 0};
            this.click = this.click.bind(this);
        }
 
        render() {
            return (
                <div>
                    <h3>{this.props.item}</h3>
                    <p>{this.state.count}</p>
                    <button onClick={this.click}>클릭</button>
                </div>
            );
        }
 
        click() {
            console.log("clicked");
            this.setState((state) => ({
                ...state,
                count: state.count + 1,
            }));
        }
    }
 
    // 사용
    ReactDOM.render(
        <Component item={"Front-End : React"}/>,
        document.querySelector('#root')
    );
</script>
</body>
</html>

 

여기에 버튼의 명칭이 ON, OFF 가 번갈아 가면서 나오도록 하는 걸 추가해보자.

<script type="text/babel">
    class Component extends React.Component {
        constructor(props) {
            super(props);
            this.state = {count: 0, isToggleOn: true};
            this.click = this.click.bind(this);
        }
 
        click() {
            console.log("clicked");
            this.setState((state) => ({
                ...state,
                count: state.count + 1,
                isToggleOn: !state.isToggleOn
            }));
        }
 
        render() {
            return (
                <div>
                    <h3>{this.props.item}</h3>
                    <p>{this.state.count}</p>
                    <button onClick={this.click}>{this.state.isToggleOn ? 'ON' : 'OFF'}</button>
                </div>
            );
        }
 
    }
 
    // 사용
    ReactDOM.render(
        <Component item={"React Event Handler"}/>,
        document.getElementById('root')
    );
</script>

 

 

 

 

 

 

 

 

'React > React' 카테고리의 다른 글

React Query String  (0) 2022.06.08
React Router v6  (0) 2022.06.07
React useState  (0) 2022.06.04
React 값 전달(부모 → 자식, 자식 → 부모)  (0) 2022.06.03
React Component 만들기  (0) 2022.05.28
블로그 이미지

Link2Me

,
728x90

https://ko.reactjs.org/docs/react-component.html 에 기본적인 React Component 사항을 확인한다.

 

 

컴포넌트는 소프트웨어의 재사용성을 높이고 유지보수를 용이하게 하기 위해 나온 기술이다.

컴포넌트는 두 가지 인스턴스 속성으로 props와 state를 가지고 있다.

props는 부모 컴포넌트가 자식 컴포넌트에게 주는 값이다.
어떠한 값을 컴포넌트에 전달해 줘야 할때 사용하며 할당된 후 컴포넌트 내부에서 값을 변경할 수 없다.
state는 컴포넌트 내부에서 선언하며 내부에서 값을 변경할 수 있다. 동적인 데이터를 다룰 땐 state를 사용한다.

둘(props, state)다 변경이 되면, render 가 다시 일어날 수 있다.

 

Class Component 예제1

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>React App Sample</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
  //console.log(React);
  //console.log(ReactDOM);
 
  // 리액트 컴포넌트는 리엑트로 만들어진 앱을 이루는 최소한의 단위
  // 컴포넌트는 데이터(props)를 입력받아 View(state) 상태에 따라 DOM Node를 출력하는 함수
  // 컴포넌트 이름은 항상 대문자로 시작해야 한다. 
// 소문자로 시작하는 컴포넌트는 DOM 태그로 취급하기 때문이다.
 
  // Class Component 정의
  class ClassComponent extends React.Component {
      // render() 메서드는 클래스 컴포넌트에서 반드시 구현돼야하는 유일한 메서드
      render() {
          return <div>Hello React world.</div>;
      }
  }
 
  // 사용
  ReactDOM.render(
      <ClassComponent />,
      document.querySelector('#root')
  );
</script>
</body>
</html>

 

Function Component 예제1

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>React App Sample</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
  // 컴포넌트 이름은 항상 대문자로 시작해야 한다. 
// 소문자로 시작하는 컴포넌트는 DOM 태그로 취급하기 때문이다.
 
  // Function Component 정의
  function FunctionComponent() {
      return <div>Hello, React world.</div>;
  }
 
  // 화살표 함수 컴포넌트 정의
 const FunctionComponent2 = () => <div>Hello, React world.</div>;
 
  // 사용
  ReactDOM.render(
      <FunctionComponent />,
      document.querySelector('#root')
  );
</script>
</body>
</html>

 

React createElement 로 컴포넌트 만들기 예제

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>React App Sample</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
  // 컴포넌트 이름은 항상 대문자로 시작해야 한다. 
  // 소문자로 시작하는 컴포넌트는 DOM 태그로 취급하기 때문이다.
 
  const Component = () => {
    return React.createElement('p'null, `type 이 "React 컴포넌트" 입니다.`);
    // React.createElement(태그, props, childeren)
  }
 
  // 사용
  ReactDOM.render(
      React.createElement(Component, nullnull),
      document.querySelector("#root")
  );
</script>
</body>
</html>

 

위 예제는 태그가 여러개 일 경우 처리하는데 상당히 어렵다.

https://babeljs.io/ 사이트에서 직접 입력해보면...

자동으로 React.createElement 를 생성해주는 걸 확인할 수 있다.

 

React 에서 사용하는 JSX 문법

- 최상위 요소가 하나여야 한다.

- 자식들을 바로 랜더링하고 싶으면, <>자식들</>를 사용한다.

- 자바스크립트 표현식을 사용하려면 {표현식}을 이용한다.

- if 문을 사용할 수 없기 때문에 삼항연산자 혹은 &&를 사용한다.

- style을 이용해 인라인 스타일링이 가능하다.

- class 대신 className을 사용해 class를 적용할 수 있다.

- 자식요소는 엄격하게 쌍으로 닫아야 하고, 자식요소가 없다면 열면서 닫아야 한다. <br />

 

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>React App Sample</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
  // 사용
  ReactDOM.render(
      <div>
          <div>
              <h2>Front-end Frameworks</h2>
              <ul>
                  <li>React</li>
                  <li>Angular</li>
                  <li>Vue.js</li>
              </ul>
          </div>
      </div>,
      document.querySelector("#root")
  );
</script>
</body>
</html>

 

함수형 컴포넌트로 만들어서 바꾸면

<script type="text/babel">
  function Component() {
      return (
          <div>
              <div>
                  <h2>Front-end Frameworks</h2>
                  <ul>
                      <li>React</li>
                      <li>Angular</li>
                      <li>Vue.js</li>
                  </ul>
              </div>
          </div>
      )
  }
 
  // 사용
  ReactDOM.render(
      <Component />,
      document.querySelector("#root")
  );
</script>

 

이제 값을 전달하는 컴포넌트를 구현하는 간단 예제이다.

<script type="text/babel">
  function Component(props) {
      return (
          <div>
              <div>
                  <h2>Front-end Frameworks</h2>
                  <ul>
                      <li>{props.item}</li>
                      <li>Angular</li>
                      <li>Vue.js</li>
                  </ul>
              </div>
          </div>
      )
  }
 
  // 사용
  ReactDOM.render(
      <Component item="리액트" />,
      document.querySelector("#root")
  );
</script>

 

class 컴포넌트 값 전달 예제

전달하는 값이 없으면 기본으로 지정된 값이 보이는 걸 확인할 수 있다.

<Component item="React" />  라고 값을 지정하면 지정된 값이 출력되는 걸 확인할 수 있다.

<script type="text/babel">
    class Component extends React.Component {
        render(){
            return (
                <div>
                    <div>
                        <ul>
                            <li>{this.props.item}</li>
                        </ul>
                    </div>
                </div>
            )
        }
 
        static defaultProps = {
            item : "Vue.js"
        };
    }
 
    // 사용
    ReactDOM.render(
        <Component />,
        document.querySelector('#root')
    );
</script>

 

state

<script type="text/babel">
    class Component extends React.Component {
        // state = {
        //     count: 0,
        // }
 
        constructor(props) {
            super(props);
            this.state = { count: 0}
        }
 
        render() {
            return (
                <div>
                    <ul>
                        <li>{this.props.item}</li>
                        <li>{this.state.count}</li>
                    </ul>
                </div>
            );
        }
 
        componentDidMount() {
            setTimeout(() => {
                this.setState({
                    count: this.state.count + 1,
                });
            }, 1000);
        }
 
        static defaultProps = {
            item: "React"
        };
    }
 
    // 사용
    ReactDOM.render(
        <Component/>,
        document.querySelector('#root')
    );
</script>

 

componentDidMount 처리를 다르게 하는 법

<script type="text/babel">
    class Component extends React.Component {
        constructor(props) {
            super(props);
            this.state = { count: 0}
        }
 
        render() {
            return (
                <div>
                    <ul>
                        <li>{this.props.item}</li>
                        <li>{this.state.count}</li>
                    </ul>
                </div>
            );
        }
 
        componentDidMount() {
            setTimeout(() => {
                this.setState((previousState)=>{
                    const newState = { count: previousState.count + 1}
                    return newState;
                });
            }, 1000);
        }
 
        static defaultProps = {
            item: "React"
        };
    }
 
    // 사용
    ReactDOM.render(
        <Component item={"Front-End : React"/>,
        document.querySelector('#root')
    );
</script>

 

 

 

 

 

'React > React' 카테고리의 다른 글

React Query String  (0) 2022.06.08
React Router v6  (0) 2022.06.07
React useState  (0) 2022.06.04
React 값 전달(부모 → 자식, 자식 → 부모)  (0) 2022.06.03
React Event 처리하기  (0) 2022.05.29
블로그 이미지

Link2Me

,
728x90

자바스크립트는 null과 undefined 타입을 제외하고 모든 것을 객체로 다룬다.
함수를 변수 or 다른 함수의 변수처럼 사용할 수 있다. 함수를 콜백함수로 사용할 경우, 함수의 이름만 넘겨주면 된다.
함수를 인자로 사용할 때 callback 처럼 () 를 붙일 필요가 없다는 것이다.

 

<script>
 
// 콜백(callback)
// 함수의 인수로 사용되는 함수
 
// setTimeout(함수, 시간)
 
function timeout(callback) {
    setTimeout(()=>{
        console.log('Callback function example');
        callback()
    }, 3000);
}
 
timeout(()=>{
    console.log('Done!');
});
 
</script>

 

 

<script>
 
let allUserData = [];
 
// 콘솔에 결과를 찍는 함수
function LogFunc(userData) {
    if ( typeof userData === "string") {
        console.log("string : " + userData);
    } else if ( typeof userData === "object") {
        for (var item in userData) {
            console.log(item + ": " + userData[item]);
        }
    }
}
 
// 두 개의 인자를 받아서 마지막에 콜백함수를 호출한다.
function getInput (options, callback) {
    allUserData.push (options);
    callback (options);
}
 
// getInput 함수를 호출할 때 , 우리는 LogFunc 함수의 이름을 인자로 넘긴다.
// LogFunc은 콜백함수가 되어 getInput 함수의 내부에서 동작 할 것이다.
getInput ({name:"배수지", items:"JavaScript"}, LogFunc);
getInput ("홍길동", LogFunc);
 
</script>

 

 

 

 

'React > morden javascript' 카테고리의 다른 글

자바스크립트 Object  (0) 2022.06.11
자바스크립트 호이스팅(Hoisting)  (0) 2022.06.10
Javascript this and Class  (0) 2022.06.09
javascript 화살표 함수(람다식)  (0) 2022.05.27
변수 유효범위(Variable Scope)  (0) 2022.05.27
블로그 이미지

Link2Me

,
728x90

ECMAScript6 (2015.6월)

자바스크립트에서 화살표 함수 사용법을 알아두자.

 

Node.js 생태계는 무척 잘 되어 있다. 즉 제공되는 기능을 적재적소에 잘 써먹기만 하면 대부분의 문제를 해결할 수 있다.

프론트엔드의 주요 역할은 HTML과 CSS를 이용한 퍼블리싱, 데이터 기반의 상호작용 가능한 UI 구현, 그리고 백엔드와의 HTTPS 통신 정도이다.

대부분의 복잡한 비즈니스 로직은 서버에 탑재된다. 서버(백엔드)는 다량의 데이터를 안전하게 보관하고 내부 동작 방식을 숨길 뿐만 아니라 여러 요청을 동시에 처리하는 데 최적화되어 있다.

 

화살표 함수에 대한 자세한 사항은 https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/Arrow_functions 를 참조하면 된다.

 

화살표 함수 표현(arrow function expression)은 전통적인 함수표현(function)의 간편한 대안이다.

기존의 함수 표현식에서 function 키워드를 삭제하고 인자를 받는 매개변수의 괄호()와 코드블록({}) 사이에 화살표(=>) 만 넣어주면 된다.

 

// () => { }   vs function() { }

 

let func = function(arg1, arg2, ...argN) {
  return expression;
};
 
// 축약버전 : 화살표 함수
let func = (arg1, arg2, ...argN) => expression

 

- 인수가 하나밖에 없다면 인수를 감싸는 괄호를 생략할 수 있다.

let double = n => n * 2;

 

- 인수가 하나도 없을 땐 괄호는 생략할 수 없다.

// 일반 함수
const foo = function () { console.log("foo") }; // foo
 
// 화살표 함수
const foo = () => console.log("foo"); // foo

 

- 본문이 여러 줄로 구성되었다면 중괄호를 사용해야 하고, 반드시 return 으로 결과를 반환해야 한다.

let sum = (a, b) => {  // 중괄호는 여러 줄로 구성되어 있음을 알려준다.
  let result = a + b;
  return result; // 반드시 return 지시자로 결과값을 반환해주어야 한다.
};
 
console.log(sum(34)); // 7
 
let sum = (a, b) => a + b; // 

 

<script>
 
let sum = (a, b) => a + b;
 
/* 위 화살표 함수는 아래 함수의 축약 버전이다.
 
let sum = function(a, b) {
  return a + b;
};
 
*/
</script>

 

 

<script>
// 매개변수 지정 방법
    () => { ... } // 매개변수가 없을 경우
     x => { ... } // 매개변수가 한 개인 경우, 소괄호를 생략할 수 있다.
(x, y) => { ... } // 매개변수가 여러 개인 경우, 소괄호를 생략할 수 없다.
 
// 함수 몸체 지정 방법
=> { return x * x }  // single line block
=> x * x   // 함수 몸체가 한줄 구문이면 중괄호를 생략할 수 있으며 암묵적으로 return된다.
 
() => { return { a: 1 }; }
() => ({ a: 1 })  // 위 표현과 동일하다. 객체 반환시 소괄호를 사용한다.
 
() => {           // multi line block.
  const x = 10;
  return x * x;
};
 
</script>
 

 

 

<script>
 
// ES5
var arr = [123];
var pow = arr.map(function (x) { // x는 요소값
    return x * x;
});
 
console.log(pow); // [ 1, 4, 9 ]


// ES6
const arr = [123];
const pow = arr.map(x => x * x);
 
console.log(pow); // [ 1, 4, 9 ]
 
</script>

 

'React > morden javascript' 카테고리의 다른 글

자바스크립트 Object  (0) 2022.06.11
자바스크립트 호이스팅(Hoisting)  (0) 2022.06.10
Javascript this and Class  (0) 2022.06.09
javascript 콜백 함수(callback function)  (0) 2022.05.27
변수 유효범위(Variable Scope)  (0) 2022.05.27
블로그 이미지

Link2Me

,
728x90

자바스크립트 코드로 동적으로 태그를 추가하는 예제이다.

 

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script defer src="./main.js"></script>
</head>
<body>
<h1>Hello world!</h1>
<ul></ul>
</body>
</html>

 

main.js

<script>
// for(시작조건;종료조건;변화조건) {}
 
const ulEl = document.querySelector('ul')
 
for(let i =0 ; i < 3; i++){
    const li = document.createElement('li')
    li.textContent = `list-${i+1}`
    li.addEventListener('click'function () {
        console.log(li.textContent)
    })
    ulEl.appendChild(li)
}
</script>

 

블로그 이미지

Link2Me

,
728x90

자바스크립트에서 객체나 함수는 모두 변수(variable)이다.

 

var 키워드로 선언된 변수는 함수 레벨 scope를 따름
    함수 레벨 Scope (Function-level scope)
        함수 내에서 선언된 지역 변수는 함수 내에서만 유효
        함수 외부에서는 참조 불가

let, const 키워드로 선언된 변수는 블록 레벨 scope를 따름
    블록 레벨 Scope (Block-level scope)
        코드 블록 내에서 선언된 변수는 코드 블록 내에서만 유효, 코드 블록 외부에서는 참조 불가
        코드 블록 내부에서 선언한 변수는 지역 변수

 

<script>
// 변수 유효범위(Variable Scope)
let a = 3
function name() {
    let a = 20  // let 은 블럭 레벨 범위안에서 유효
    console.log(a)
}
console.log(a)  // 결과 : 3
name()  // 결과 : 20
console.log(a) // 결과 3
</script>

 

 

<script>
// 변수 유효범위(Variable Scope)
 
function name() {
    if (true) {
        let a = 20  // let 은 블럭 레벨 범위안에서 유효
    }
    console.log(a) // 블럭 레벨 범위 밖에 a 변수
}
 
name()  // 결과 : ReferenceError: a is not defined
 
</script>

 

형변환(Type conversion)

Falsy(거짓과 같은 값) : false, '', null, undefined, 0, -0, NaN

Truthy(참과 같은 값) : true, 'false', 1, 2, -12, ....

 

NaN (Not a Number) → 1 + undefined

 

블로그 이미지

Link2Me

,
728x90

최신 버전의 node.js 를 PC에 설치한다.

 

npm install -g npm@latest

 

npx create-react-app 프로젝트명

npx create-react-app blog

 

 

 

cd blog

개발용 서버 띄우기 : npm start

 

 

 

 

'React > React TOOL&TIP' 카테고리의 다른 글

[vscode] React 코드 자동 완성 및 Auto Import 설정  (0) 2022.10.15
블로그 이미지

Link2Me

,
728x90

RecyclerView가 보여주는 ITEM VIEW 들을 만들어주는 adapter에서 사용하는 ViewHolder의 아이템뷰에 view binding을 적용하는 것이다.

 

테스트를 하다보니 RecyclerView 를 적용한 Adapter 와 Activity 모두 view binding 을 적용해야 이미지가 정상적으로 보이는 것을 확인할 수 있었다.

 

변경되는 사항

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 새로운 뷰를 만든다.
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.address_item,parent,false);
        ViewHolder viewHolder = new ViewHolder(itemView);
        return viewHolder;
    }
 
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        AddressItemBinding binding = AddressItemBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false);
        return new ViewHolder(binding);
    }

 

 

    public class ViewHolder extends RecyclerView.ViewHolder  {
        View layout;
        ImageView photoImg;
        TextView tv_name;
        TextView tv_mobileNO;
        TextView tv_telNO;
        ImageView call_btn;
        CheckBox cbSelect;
 
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            layout = itemView.findViewById(R.id.child_layout);
            photoImg = itemView.findViewById(R.id.profile_Image);
            tv_name = itemView.findViewById(R.id.child_name);
            tv_mobileNO = itemView.findViewById(R.id.child_mobileNO);
            tv_telNO = itemView.findViewById(R.id.child_telNO);
            call_btn = itemView.findViewById(R.id.child_Btn);
            cbSelect = itemView.findViewById(R.id.list_cell_checkbox);
        }
    }
 
    public class ViewHolder extends RecyclerView.ViewHolder  {
        AddressItemBinding itemBinding;
 
        public ViewHolder(@NonNull AddressItemBinding binding) {
            super(binding.getRoot());
            itemBinding = binding;
        }
 
        void bindItem(Address_Item item){
            if(!TextUtils.isEmpty(item.getPhoto())){
                Glide.with(mContext).load(item.getPhoto()).into(itemBinding.profileImage);
            }
            itemBinding.childName.setText(item.getUserNM());
            itemBinding.childMobileNO.setText(PhoneNumberUtils.formatNumber(item.getMobileNO()));
            itemBinding.childTelNO.setText(PhoneNumberUtils.formatNumber(item.getTelNO()));
 
            itemBinding.childBtn.setOnClickListener(v -> {
 
            });
            itemBinding.listCellCheckbox.setChecked(item.getCheckBoxState());
        }
    }
 

 

View Binding을 적용한 코드

 

package com.link2me.android.sample.adapter;
 
import static com.link2me.android.sample.main.AddrBindActivity.constraintLayout;
import static com.link2me.android.sample.main.AddrBindActivity.isCheckFlag;
 
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
 
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
 
import com.bumptech.glide.Glide;
import com.link2me.android.sample.R;
import com.link2me.android.sample.databinding.AddressItemBinding;
import com.link2me.android.sample.model.Address_Item;
import com.link2me.android.sample.network.RetrofitUrl;
 
import java.util.ArrayList;
 
public class BindListViewAdapter extends RecyclerView.Adapter<BindListViewAdapter.ViewHolder> {
    private final String TAG = this.getClass().getSimpleName();
    Context context;
    private ArrayList<Address_Item> rvItemList;
 
    // 인터페이스 선언 -------------------------------------------------------------
    private OnItemClickListener mListener;
 
    public interface OnItemClickListener {
        void onItemClicked(View view, Address_Item item, int position);
    }
 
    public void setOnItemSelectClickListener(OnItemClickListener listener){
        mListener = listener;
    }
    // 인터페이스 ----------------------------------------------------------------
 
    public BindListViewAdapter(Context context, ArrayList<Address_Item> items) {
        this.context = context;
        rvItemList = items;
    }
 
    public void selectAll(){ // checkbox 전체 선택
        for(int i=0;i < rvItemList.size();i++){
            rvItemList.get(i).setCheckBoxState(true);
        }
        notifyDataSetChanged();
    }
    public void unselectall(){ // checkbox 전체 해제
        for(int i=0;i < rvItemList.size();i++){
            rvItemList.get(i).setCheckBoxState(false);
        }
        notifyDataSetChanged();
    }
 
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 새로운 뷰를 만든다.
        AddressItemBinding binding = AddressItemBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false);
        return new ViewHolder(binding);
    }
 
    @Override
    public void onBindViewHolder(@NonNull BindListViewAdapter.ViewHolder holder, int position) {
        holder.bindItem(rvItemList.get(position), position);
    }
 
    @Override
    public int getItemCount() {
        return rvItemList.size();
    }
 
    public class ViewHolder extends RecyclerView.ViewHolder  {
        AddressItemBinding itemBinding;
 
        public ViewHolder(@NonNull AddressItemBinding binding) {
            super(binding.getRoot());
            itemBinding = binding;
        }
 
        void bindItem(Address_Item item, int position){
            String photoURL = RetrofitUrl.BASE_URL + "photos/" + item.getPhoto();
            if(photoURL.contains("null")){
                Glide.with(context).asBitmap().load(R.drawable.photo_base).into(itemBinding.profileImage);
            } else {
                Glide.with(context).asBitmap().load(photoURL).into(itemBinding.profileImage);
            }
            itemBinding.childName.setText(item.getUserNM());
            itemBinding.childMobileNO.setText(PhoneNumberUtils.formatNumber(item.getMobileNO()));
            itemBinding.childOfficeNO.setText(PhoneNumberUtils.formatNumber(item.getTelNO()));
 
            itemBinding.profileImage.setOnClickListener(view1 -> {
                if(mListener != null){
                    mListener.onItemClicked(view1,item,position);
                    // 클릭의 결과로 값을 전달
                    Log.e(TAG,"item clicked : "+position);
                }
            });
 
            final String[] items ={"휴대폰 전화걸기","사무실전화 걸기"};
            final AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("해당작업을 선택하세요");
            builder.setItems(items, (dialog, which) -> {
                Toast.makeText(context, items[which] + "선택했습니다.", Toast.LENGTH_SHORT).show();
                switch (which){
                    case 0:
                        if(item.getMobileNO().length() ==0){
                            Toast.makeText(context, "전화걸 휴대폰 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
 
                        AlertDialog dialog1 = new AlertDialog.Builder(context)
                                .setTitle(item.getUserNM())
                                .setMessage(PhoneNumberUtils.formatNumber(item.getMobileNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        (dialog23, which13) -> {
 
                                            Intent intent = new Intent(Intent.ACTION_CALL,
                                                    Uri.parse("tel:" + PhoneNumberUtils.formatNumber(item.getMobileNO())));
                                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                            context.startActivity(intent);
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        (dialog22, which12) -> dialog22.dismiss()).create();
                        dialog1.show();
                        break;
                    case 1:
                        if(item.getTelNO().length() ==0){
                            Toast.makeText(context, "전화걸 사무실 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog2 = new AlertDialog.Builder(context)
                                .setTitle(item.getUserNM())
                                .setMessage(PhoneNumberUtils.formatNumber(item.getTelNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        (dialog3, which1) -> {
 
                                            Intent intent = new Intent(Intent.ACTION_CALL,
                                                    Uri.parse("tel:" + PhoneNumberUtils.formatNumber(item.getTelNO())));
                                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                            context.startActivity(intent);
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        (dialog32, which14) -> dialog32.dismiss()).create();
                        dialog2.show();
                        break;
 
                }
            });
            builder.create();
 
            itemBinding.callBtn.setOnClickListener(v -> builder.show());
 
 
            if (isCheckFlag == false) {
                itemBinding.callBtn.setVisibility(View.VISIBLE);
                itemBinding.callBtn.setOnClickListener(view -> builder.show());
                itemBinding.listcellCheckbox.setVisibility(View.GONE);
                itemBinding.childLayout.setOnClickListener(view ->
                        Toast.makeText(context, "상세보기를 눌렀습니다 ===" + item.getIdx(), Toast.LENGTH_SHORT).show());
 
                itemBinding.childLayout.setOnLongClickListener(v -> {
                    isCheckFlag = true;
                    constraintLayout.setVisibility(View.VISIBLE);
                    notifyDataSetChanged();
                    return true;
                });
            } else {
                itemBinding.callBtn.setVisibility(View.GONE);
                //convertView.setClickable(false);
                itemBinding.listcellCheckbox.setVisibility(View.VISIBLE);
                itemBinding.listcellCheckbox.setTag(position); // This line is important.
 
                // 체크 박스 클릭하면 CheckBoxState 에 반영한다. setOnCheckedChangeListener 대신 사용
                itemBinding.listcellCheckbox.setOnClickListener(v -> {
                    if(rvItemList.get(position).getCheckBoxState() == true){
                        rvItemList.get(position).setCheckBoxState(false);
                        Log.d("checkbox","position : "+ position + " checkBoxState === "+ rvItemList.get(position).getCheckBoxState());
                    } else {
                        rvItemList.get(position).setCheckBoxState(true);
                        Log.d("checkbox","position : "+ position + " checkBoxState === "+ rvItemList.get(position).getCheckBoxState());
                    }
                });
 
                itemBinding.childLayout.setOnClickListener(v -> {
                    if(itemBinding.listcellCheckbox.isChecked() == false){
                        itemBinding.listcellCheckbox.setChecked(true);
                        rvItemList.get(position).setCheckBoxState(true);
                        //notifyDataSetChanged();
                        Log.d("checklist","position : "+ position + " checkBoxState === "+ rvItemList.get(position).getCheckBoxState());
                    } else {
                        itemBinding.listcellCheckbox.setChecked(false);
                        rvItemList.get(position).setCheckBoxState(false);
                        //notifyDataSetChanged();
                        Log.d("checklist","position : "+ position + " checkBoxState === "+ rvItemList.get(position).getCheckBoxState());
                    }
                });
            }
 
            // 재사용 문제 해결
            if(rvItemList.get(position).getCheckBoxState() == true){
                itemBinding.listcellCheckbox.setChecked(true);
                Log.d("ReUse","position : " + position + " checkBoxState === " + rvItemList.get(position).getCheckBoxState());
            } else {
                itemBinding.listcellCheckbox.setChecked(false);
                Log.d("ReUse","position : "+position + " checkBoxState ===" + rvItemList.get(position).getCheckBoxState());
            }
        }
    }
 
}
 
 
 
 
 

 

 

 

 

블로그 이미지

Link2Me

,

MySQL foreign key 예제

SQL 2022. 5. 16. 06:30
728x90

SQL 엔진에 따른 참조무결성 여부 확인 및 테이블 간에 참조 무결성 확인

##########################################################################
# 테이블 삭제 순서가 중요하다.
# DB 엔진을 MyISAM 으로 했을 경우와 InnoDB 로 했을 경우 차이점을 살펴본다.
 
DROP TABLE IF EXISTS buy, person;
CREATE TABLE person (
  userID varchar(20NOT NULL COMMENT '사용자ID',
  userNM varchar(30NOT NULL COMMENT '이름',
  mobileNO varchar(60NOT NULL COMMENT '휴대폰번호',
  reg_date timestamp NOT NULL DEFAULT current_timestamp() COMMENT '등록일자',
  display tinyint(2NOT NULL DEFAULT 1,
  PRIMARY KEY (userID)
ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
INSERT INTO person (userID, userNM, mobileNO) VALUES
('20221001''홍길동''010-1000-1001'),
('20221002''이순신''010-1000-1003'),
('20221003''강감찬''010-1000-1004'),
('20221004''신시아''010-1000-1005'),
('20221005''나현우''010-1000-1006'),
('20221006''홍석천''010-2000-1011'),
('20221007''김남일''010-2000-1012'),
('20221008''김남길''010-2000-1014'),
('20221009''박나래''010-2000-1015');
 
-- ALTER TABLE person ADD PRIMARY KEY (userID);
 
CREATE TABLE buy (
  id int(11NOT NULL AUTO_INCREMENT PRIMARY KEY,
  userID varchar(20NOT NULL,
  prod_name varchar(20NOT NULL,
  FOREIGN KEY (userID) REFERENCES person (userID)
)ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
INSERT INTO buy VALUES(NULL,'20221001','지갑');
INSERT INTO buy VALUES(NULL,'20221001','맥북');
INSERT INTO buy VALUES(NULL,'20221003','USB');
INSERT INTO buy VALUES(NULL,'20221004','가방');
INSERT INTO buy VALUES(NULL,'20221005','휴대폰');
INSERT INTO buy VALUES(NULL,'20221005','의류');
INSERT INTO buy VALUES(NULL,'20221005','운동화');
INSERT INTO buy VALUES(NULL,'20221006','노트북');
 
SELECT p.userID, p.userNM, b.prod_name 
FROM buy b INNER JOIN person p ON b.userID = p.userID;
 
UPDATE person SET userID='20221010' WHERE userID='20221001';
-- 에러가 발생하는지 확인한다.
 
##########################################################################
DROP TABLE IF EXISTS buy, person;
CREATE TABLE person (
  userID varchar(20NOT NULL COMMENT '사용자ID',
  userNM varchar(30NOT NULL COMMENT '이름',
  mobileNO varchar(60NOT NULL COMMENT '휴대폰번호',
  reg_date timestamp NOT NULL DEFAULT current_timestamp() COMMENT '등록일자',
  display tinyint(2NOT NULL DEFAULT 1,
  PRIMARY KEY (userID)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
INSERT INTO person (userID, userNM, mobileNO) VALUES
('20221001''홍길동''010-1000-1001'),
('20221002''이순신''010-1000-1003'),
('20221003''강감찬''010-1000-1004'),
('20221004''신시아''010-1000-1005'),
('20221005''나현우''010-1000-1006'),
('20221006''홍석천''010-2000-1011'),
('20221007''김남일''010-2000-1012'),
('20221008''김남길''010-2000-1014'),
('20221009''박나래''010-2000-1015');
 
-- ALTER TABLE person ADD PRIMARY KEY (userID);
 
CREATE TABLE buy (
  id int(11NOT NULL AUTO_INCREMENT PRIMARY KEY,
  userID varchar(20NOT NULL,
  prod_name varchar(20NOT NULL,
  FOREIGN KEY (userID) REFERENCES person (userID)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
INSERT INTO buy VALUES(NULL,'20221001','지갑');
INSERT INTO buy VALUES(NULL,'20221001','맥북');
INSERT INTO buy VALUES(NULL,'20221003','USB');
INSERT INTO buy VALUES(NULL,'20221004','가방');
INSERT INTO buy VALUES(NULL,'20221005','휴대폰');
INSERT INTO buy VALUES(NULL,'20221005','의류');
INSERT INTO buy VALUES(NULL,'20221005','운동화');
INSERT INTO buy VALUES(NULL,'20221006','노트북');
 
SELECT p.userID, p.userNM, b.prod_name 
FROM buy b INNER JOIN person p ON b.userID = p.userID;
 
UPDATE person SET userID='20221010' WHERE userID='20221001';
-- 에러가 발생하는지 확인한다.
 
DELETE FROM person WHERE userID='20221001';
-- 에러가 발생하는지 확인한다.
 
##########################################################################
-- 기준 테이블의 열이 변경되는 경우 : 회원 테이블의 userID 변경
-- alter table [추가할테이블명] add constraint [제약조건명] foreign key(컬럼명) 
  references [부모테이블명] (PK컬럼명) [ON DELETE CASCADE / ON UPDATE CASECADE];
 
DROP TABLE IF EXISTS buy, person;
CREATE TABLE person (
  userID varchar(20NOT NULL COMMENT '사용자ID',
  userNM varchar(30NOT NULL COMMENT '이름',
  mobileNO varchar(60NOT NULL COMMENT '휴대폰번호',
  reg_date timestamp NOT NULL DEFAULT current_timestamp() COMMENT '등록일자',
  display tinyint(2NOT NULL DEFAULT 1,
  PRIMARY KEY (userID)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
INSERT INTO person (userID, userNM, mobileNO) VALUES
('20221001''홍정민''010-1000-1001'),
('20221002''이순신''010-1000-1003'),
('20221003''강감찬''010-1000-1004'),
('20221004''신시아''010-1000-1005'),
('20221005''나현우''010-1000-1006'),
('20221006''홍석천''010-2000-1011'),
('20221007''김남일''010-2000-1012'),
('20221008''김남길''010-2000-1014'),
('20221009''박나래''010-2000-1015');
 
CREATE TABLE buy (
  id int(11NOT NULL AUTO_INCREMENT PRIMARY KEY,
  userID varchar(20NOT NULL,
  prod_name varchar(20NOT NULL
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
ALTER TABLE buy 
  ADD constraint fk_userID 
  FOREIGN KEY(userID) REFERENCES person (userID) 
  ON UPDATE CASCADE 
  ON DELETE CASCADE;
 
INSERT INTO buy VALUES(NULL,'20221001','지갑');
INSERT INTO buy VALUES(NULL,'20221001','맥북');
INSERT INTO buy VALUES(NULL,'20221003','USB');
INSERT INTO buy VALUES(NULL,'20221004','가방');
INSERT INTO buy VALUES(NULL,'20221005','휴대폰');
INSERT INTO buy VALUES(NULL,'20221005','의류');
INSERT INTO buy VALUES(NULL,'20221005','운동화');
INSERT INTO buy VALUES(NULL,'20221006','노트북');
 
SHOW INDEX FROM buy;
 
UPDATE person SET userID='20221010' WHERE userID='20221001';
 
DELETE FROM person WHERE userID='20221003';
 
-- foreign key 확인
select * from information_schema.table_constraints where table_name = 'buy';
 
-- foreign key 삭제
ALTER TABLE buy 
  DROP foreign key fk_userID;
 
-- foreign key 삭제 확인
select * from information_schema.table_constraints where table_name = 'buy';
 
use test;
SHOW INDEX FROM buy;
 
DELETE FROM person WHERE userID='20221004';
 
###############################################################################

 

 

블로그 이미지

Link2Me

,
728x90

CentOS 7 에서 PHP7.4 와 MariaDB를 설치하는 스크립트이다.

Apache httpd.conf 파일은 보안 설정과 php.ini 파일 보안설정이 고려되어 있다.

이런 스크립트 구하기는 쉽지 않을 것이다. Secure Coding 환경설정까지 고려된 스크립트라고 보면 된다.

 

 
##########################################################################
## CentOS 7 Package Update
# CentOS 7 업데이트
yum -y update
 
##########################################################################
# CentOS 7 방화벽 설정
yum -y install firewalld
 
# 방화벽 데몬 시작
systemctl start firewalld
 
# 서버 부팅 시 firewalld 데몬 자동 시작 설정
systemctl enable firewalld
 
firewall-cmd --permanent --add-service=http 
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-service=mysql
firewall-cmd --permanent --zone=public --add-port=3306/tcp
 
firewall-cmd --permanent --zone=public --add-port=8000/tcp
firewall-cmd --permanent --zone=public --add-port=8080/tcp
firewall-cmd --reload
firewall-cmd --list-all
 
#########################################################################
# Databse 접근을 위한 SELinux 설정 변경
# SELinux 상태 확인
sestatus
 
#SELinux httpd flag 확인 : 네트워크를 통해 Database에 연결할 수 있는 옵션이 꺼져 있음
getsebool -| grep httpd
 
# Database 접근을 위한 SELinx 설정 변경
setsebool -P httpd_can_network_connect_db 1
 
# SELinux 비활성화 하기
vi /etc/sysconfig/selinux
SELINUX=disabled
:wq 로 저장하고 나온다.
 
# 재부팅해야 SELinux 명령어 수정한 사항이 적용된다.
reboot 
 
# 임시 비활성화 방법
setenforce 0
 
################################
##### MariaDB 10.5 버전 설치 #####
################################
# MariaDB를 잘못 설치한 경우에는 기존 MariaDB에서 사용된 파일 삭제 필요
rm --/var/lib/mysql
 
rpm -qa | grep MariaDB
 
# 10.3 버전, 10.4버전도 동일한 형태이다.
cd /root
vi /etc/yum.repos.d/MariaDB.repo
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
 
:wq 로 저장하고 나온다.
 
sudo yum makecache fast
yum -y install mariadb-server mariadb-client
 
# mariadb 부팅 시 자동 시작 설정
systemctl enable mariadb
 
# mariadb 시작
systemctl start mariadb
 
# mariadb 상태 확인
service mariadb status
 
# Maria DB 보안 설정하기
# 거의 Y만 누르면 끝난다.
mariadb-secure-installation
 
mysql_secure_installation
# 알아낸 임시패스워드 입력하고 영문 소문자, 대문자, 숫자, 특수문자를 포함한 패스워드로 변경
 
# root 비밀번호 설정
 
# UTF-8 로 통신하기 위한 서버/클라이언트 설정
vi /etc/my.cnf.d/server.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
skip-character-set-client-handshake
 
#:wq (저장)하고 빠져나온다.
# DB서버와 접속을 할 때 클아이언트는 자신이 사용할 문자셋 설정하며, 그 설정을 사용하도록 되어있지만 
# skip-character-set-client-handshake 옵션을 사용할 경우 
# 클라이언트에서 설정한 문자셋을 무시하고 character_set_server값으로 설정된다.
 
vi /etc/my.cnf.d/mysql-clients.cnf
[mysql]
default-character-set=utf8mb4
[mysqldump]
default-character-set=utf8mb4
 
#:wq (저장)하고 빠져나온다.
 
# MariaDB 재시작
service mariadb restart
 
 
# yum으로 설치한 패키지 경로 알아내기
rpm -ql MariaDB-server.x86_64 | grep cnf
 
# mariadb 버전 확인
mariadb --version
 
# MariaDB 접속하여 적용된 사항 확인
mysql -u root -p
status
show variables like 'c%';
 
# DB 서버의 기본 문자셋으로서 설정 파일에 명시한 대로 utf8mb4 로 설정되어 있다.
 
 
#########################################
####### 실제 적용 예제 ######
#########################################
# https://link2me.tistory.com/431 참조하면 도움된다.
mysql -u root -p
-- DB 생성
drop database phpdb;
create database phpdb default character set utf8mb4 COLLATE = utf8mb4_unicode_ci;
 
-- 사용자 권한 부여
use mysql;
create user codefox@localhost identified by 'Wofullnder!#%';
grant all privileges on phpdb.* to codefox@localhost;
flush privileges;
 
-- 비밀번호 변경 및 권한 부여
grant all privileges on phpdb.* to codefox@localhost identified by 'Wofullnder!#%';
flush privileges;
quit
 
#############################################
# PHP 7.4 와 Apache 설치 
#############################################
## 현재 설치된 PHP 버전 확인
yum list php
 
yum -y install expat-devel
 
# 설치 확인 방법1
yum list installed | grep httpd
yum list installed | grep php
 
# 설치 확인 방법2
rpm -qa | grep httpd
rpm -qa | grep php
 
## httpd 설치된 것 한꺼번에 지우기 ==> 설치된 것이 없으면 생략
# vi list로 해서 삭제 안할 리스트는 선별 제거한다.
rpm -qa | grep httpd >list
sudo yum -y remove $(awk '{print $1}' <list)
 
# PHP 설치된 것 한꺼번에 지우기 ==> 설치된 것이 없으면 생략
# vi list로 해서 삭제 안할 리스트는 선별 제거한다.
rpm -qa | grep php >list
sudo yum -y remove $(awk '{print $1}' <list)
 
 
# Remi 저장소를 설치하고 활성화한다.
yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
 
# yum 저장소와 패키지를 관리
yum -y groupinstall "Development tools"
yum -y install epel-release yum-utils
 
# Disable repo for PHP 5.4 ==> yum 기본 설치된 PHP 5.4 버전 대신에 PHP 7.4 버전 설치
yum-config-manager --disable remi-php54
yum-config-manager --enable remi-php74
 
# Install PHP 7.4 on CentOS 7
yum install ---enablerepo=remi-php74 httpd httpd-devel
yum install ---enablerepo=remi-php74 php php-cli php-common php-devel php-ldap 
yum install ---enablerepo=remi-php74 mod_ssl php-mbstring php-mcrypt php-mysqlnd php-pdo 
yum install ---enablerepo=remi-php74 php-pgsql php-sqlite php-process php-snmp php-soap 
yum install ---enablerepo=remi-php74 php-libxml php-xml php-pear php-gd php-fpm php-dom 
yum install ---enablerepo=remi-php74 php-ssh2 php-xmlreader php-curl php-date php-exif 
yum install ---enablerepo=remi-php74 php-filter php-simplexml php-hash php-iconv php-imagick 
yum install ---enablerepo=remi-php74 php-json php-openssl php-pcre php-posix php-sockets 
yum install ---enablerepo=remi-php74 php-spl php-tokenizer php-zlib php-gmp php-Icinga php-intl 
yum install ---enablerepo=remi-php74 php-pecl-zip zip php-zip php-pspell 
yum install ---enablerepo=remi-php74 wget unzip mc git nmap telnet net-tools
 
# PHP 버전 확인
php -v
 
# Apache 버전 확인
httpd -v
 
# 서비스 활성화(재부팅시 auto start)
systemctl enable httpd
 
# 서비스 시작
systemctl start httpd
 
# 서비스 구동 상태 확인
systemctl status httpd
 
# 서비스 중지
systemctl stop httpd
# 서비스 재시작
systemctl restart httpd
 
# openssl 버전 확인
openssl version
 
###################################################
# Apache 환경 설정 
###################################################
# Web root 디렉토리 : /var/www/html 기본 설정인데 아래는 home 디렉토리 하단으로 수정했다.
# 보안 설정 부분이 같이 포함되어 있으니 아래와 같이 설정하면 도움된다.
# 더 자세한 사항은 구글링해서 찾아보시라.
## httpd.conf 파일 수정
vi /etc/httpd/conf/httpd.conf
ServerName localhost
 
DocumentRoot "/var/www/htdocs"
 
<Directory "/var/www">
    AllowOverride None
    Require all granted
</Directory>
 
<Directory "/var/www/htdocs">
    Options +FollowSymLinks -Indexes
    AllowOverride All
    Require all granted
    <LimitExcept GET POST>
      Order deny,allow
      Deny from all
    </LimitExcept>
</Directory>
 
<IfModule dir_module>
    DirectoryIndex index.php index.html 
</IfModule>
 
<IfModule mime_module>
    TypesConfig /etc/mime.types
    #AddType application/x-gzip .tgz
    #AddEncoding x-compress .Z
    #AddEncoding x-gzip .gz .tgz
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
    AddType application/x-httpd-php .php .html .do
    AddType application/x-httpd-php-source .phps
    AddType text/css .css
    AddType text/javascript .js
    #AddHandler cgi-script .cgi
    #AddHandler type-map var
    #AddOutputFilter INCLUDES .shtml
</IfModule>
 
ErrorDocument 400 /error.php
ErrorDocument 401 /error.php
ErrorDocument 402 /error.php
ErrorDocument 403 /error.php
ErrorDocument 404 /error.php
ErrorDocument 405 /error.php
ErrorDocument 408 /error.php
ErrorDocument 500 /error.php
ErrorDocument 501 /error.php
ErrorDocument 502 /error.php
ErrorDocument 503 /error.php
 
# 반드시 서버 정보 노출 방지 추가해야 함.
ServerTokens Prod
ServerSignature Off 
 
TraceEnable Off
 
# Apache HTTPOXY 취약점 방지
RequestHeader unset Proxy early
 
# 저장(:wq)하고 나온다.
 
##################################
# PHP.ini 환경 설정 수정사항
##################################
 
vi /etc/php.ini
short_open_tag = On
cgi.fix_pathinfo=0
 
;PHP 버전 정보 노출 방지(Hide PHP Version Number)
expose_php = Off
 
date.timezone ="Asia/Seoul"
 
;PHP에서 세션은 일단 생성 된 뒤에, 가비지 콜렉터 관리로직에 의해 소멸된다.
session.gc_probability = 1
;session.gc_divisor = 1000 으로 되어 있는 것을 1로 변경한다.
session.gc_divisor = 1
;이값이 100이면 1/100 즉 1%의 확률로 가비지콜렉션이 실행된다.
;1이면 정확하게 유효기간이 넘은 데이타가 삭제된다.
; 세션 시간은 1시간으로 설정한다.
session.gc_maxlifetime = 3600
 
post_max_size = 20M  ;// 8M 으로 되어 있었음.
upload_max_filesize = 18M ;// 기본 2M 으로 되어 있었음
 
 
# 저장(:wq)하고 나온다.
 
#####################################################################
# Web 서버 root 디렉토리 생성
mkdir -/var/www/htdocs
 
# 반드시 추가구현해야 문제없이 동작한다. error.php 파일 내용은 오픈하지 않는다.
# error.php 대신에 index.php 로 설정해도 무방하다.
# 해커가 없는 파일을 검색으로 찾을 때 비정상적으로 검색한다는 걸 알려주는 error.php 파일인데,
# 비정상적인 파일 검색 실행시 무조건 index.php 로 가도록 설정해도 되기 때문이다.
 
# Web 서버 root 디렉토리로 이동
cd /var/www/htdocs/
vi .htaccess
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ error.php?/$1 [QSA,PT,L]
</IfModule>
 
#:wq(저장)하고 빠져나온다.
 
## 보안설정
sudo chmod 640 /etc/httpd/conf/httpd.conf
sudo chown root:root /etc/httpd/conf/httpd.conf
sudo chmod 640 /etc/php.ini
sudo chown root:root /etc/php.ini
 
sudo chown -R apache:apache /var/www/htdocs
sudo chmod -755 /var/www/htdocs
 
# 방화벽 설정 확인
iptables -L
 
# Apache 재시작
systemctl restart httpd
 
# 만약 Web 서버가 동작하지 않는다면 에러 로그를 살펴봐야 한다.
cd /var/log/httpd/
 
 
# 열린포트 확인
yum -y install net-tools 
netstat -nltp
 
### 시간 동기화
yum -y install rdate
 
crontab -e
00 00  * * * /usr/bin/rdate -s time.bora.net && /sbin/clock -w
 
 
#####################################
# 최신버전 phpMyAdmin 설치
#####################################
# Web 서버 root 디렉토리로 이동
cd /var/www/htdocs
wget https://files.phpmyadmin.net/phpMyAdmin/5.1.3/phpMyAdmin-5.1.3-all-languages.zip
unzip phpMyAdmin-5.1.3-all-languages.zip
chown -R apache:apache phpMyAdmin-5.1.3-all-languages
mv phpMyAdmin-5.1.3-all-languages pmaaa
cd pmaaa
cp -rp config.sample.inc.php  config.inc.php
 
# 환경 설정
vi config.inc.php
 
$cfg['blowfish_secret'= 'qtdRoGmbcr]0s)r$9b_JUnoqtdRoGmbcr]0s)r$9b_JUnoGmbcrGmbcr]0s)r$9b{~Xz'// 임의의 값 설정
$cfg['Servers'][$i]['host'= 'localhost';
$cfg['Servers'][$i]['compress'= true;
$cfg['TempDir'= '/tmp';
 
# 저장(:wq)하고 빠져나온다.
 
# 주의사항
# phpMyAdmin 기능이 편리한 만큼 특정한 IP에서만 접속하도록 IPFiltering을 설정하는 걸 추가해줘야 안전하다.
# 설정 방법은 블로그 게시글을 찾아서 적용하기 바란다.
 
###############################
# composor 설치 
###############################
cd /var/www/htdocs
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
composer -V
# yes 입력
export COMPOSER_ALLOW_SUPERUSER=1
echo "export COMPOSER_ALLOW_SUPERUSER=1" >> ~/.bashrc
cat ~/.bashrc | grep export
 
# PHPSpredSheet 설치
cd /var/www/htdocs
composer require phpoffice/phpspreadsheet
 
# PHP Excel 예제는 본 블로그에 게시되어 있다.
 
###############################
# RSA 암호화 통신을 위한 설정
###############################
#리눅스 SSH 에서 RSA 공개키, 개인키 생성방법
mkdir -/home/rsa/key/
cd /home/rsa/key/
 
# Private Key 생성
openssl genrsa -out rsa_pri.pem 1024
 
# Public Key 생성
openssl rsa -pubout -in rsa_pri.pem -out rsa_pub.pem
 
##########################################################################
 
cd /var/www/htdocs/
vi info.php
<?php
phpinfo();
?>
 
#:wq(저장)하고 빠져나온다.
 
# Web 브라우저에서 동작여부 확인한다.
http://192.168.1.20/info.php
 
 
##########################################################################
# 패스워드 변경 (필요시)
mysql -uroot -p
update user set password=password('변경할비밀번호') where user='root';
flush privileges;
quit;
 
### 테이블 백업하기 ####
# 최초 단계에서는 실행하지 않는다.
mysqldump -uroot ---databases phpdb > phpdb.sql
mysqldump -uroot ---databases phpdb > phpdb.sql
 
### 테이블 구조만 백업하기 ####
# 최초 단계에서는 실행하지 않는다.
mysqldump -uroot ---no-data --databases phpdb > phpdb.sql
 
# 서버 접속 권한이 있어야 가능
# 개발용 노트북/맥북이 구로 사무실에서는 접속 가능
# 원격 서버에서 로컬 서버(현재 접속된 서버)로 파일 가져오기. 
scp root@192.168.1.20:/root/phpdb.sql ./
 
# DB 테이블 Upload. SQL 파일이 존재하는 경로에서 실행한다.
mysql -uroot -p
use phpdb;
source phpdb.sql; -- DB load
 
show tables;
drop table customer_bk;
 
-- 테이블 구조 확인
desc customer;
desc deviceM;
 
-- 테이블 칼럼 길이는 가능하면 너무 크지 않게 설정한다.
 
-- 데이터베이스 문자셋 변경
ALTER DATABASE {database} CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
ALTER DATABASE phpdb CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
 
-- 기존 테이블 문자셋 변경
ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
 
##########################################################################
 

 

 

'리눅스' 카테고리의 다른 글

CentOS 7 날짜 반복 증가 스크립트  (0) 2024.03.31
CentOS 5.8 APM 설치 과정  (0) 2022.05.14
VMWare Network 설정  (0) 2022.04.23
CentOS 7 Jenkins 설치  (0) 2022.03.25
Apache log Full  (0) 2022.02.22
블로그 이미지

Link2Me

,
728x90

오래된 CentOS 5.8 또는 CentOS 5.9 에 APM(Apache + PHP + MySQL) 설치 과정 스크립트이다.

이 스트립트가 잘 동작되는지 여부는 모른다.

몇년전에는 잘 동작되어 사용했던 스크립트이다.

 

# CentOS 5.8 인스톨 후 APM 설치과정
 
# 버전 확인
cat /etc/redhat-release
 
 
작업 서버
==== IP Setting ====
# 설정정보 확인
ifconfig
 
# DNS를 위한 네임서버를 지정 
cat /etc/resolv.conf   
cat /etc/hosts
 
# 네트워크 살았는지 죽었는지 테스트
ping 168.126.63.1
 
# 현재 MAC address 알아내기
cat /sys/class/net/*/address
 
== 설정 작업
cd /etc/sysconfig/network-scripts
# ll 하고 ifcfg-*** 파일을 확인한다.
# 네트워크 환경 설정에 맞게 변경한다.
vi /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=none
HWADDR=08:00:27:AE:E9:5A
ONBOOT=yes
NETMASK=255.255.255.0
IPADDR=192.168.1.20
GATEWAY=192.168.1.100
TYPE=Ethernet
DNS1=168.126.63.1
DNS2=168.126.63.2
 
service network restart
 
=================================
# httpd 데몬 확인
ps -ef | grep httpd
# MySQL 데몬 확인
ps -ef | grep mysql
 
# 아파치(Apache) 시작
/etc/init.d/httpd start
/etc/init.d/httpd stop
# MySQL 시작
/etc/init.d/mysqld start
 
=================================
# RPM이 설치 되어 있는지 확인
rpm -qa httpd php mysql
rpm -qa | grep http
rpm -qa | grep mysql
rpm -qa | grep php
 
# 기존 패키지 삭제
yum remove -y httpd mysql php
 
# 설치된 것이 제대로 제거되었는지 확인
rpm -qa httpd php mysql
rpm -qa | grep http
rpm -qa | grep mysql
rpm -qa | grep php
 
# 제거되지 않고 남아 있는 것이 있다면 rpm -e --nodeps (의존성에 개의치 않고 삭제) 를 붙여준다.
rpm ---nodeps jakarta-commons-httpclient-3.0-7jpp.4.el5_10
rpm ---nodeps php-cli-5.1.6-45.el5_11
rpm ---nodeps php-common-5.1.6-45.el5_11
 
전부 지워졌는지 확인하고 나서 APM 소스 설치에 들어간다.
 
# 모든 웹서버 데몬 죽이기
killall httpd
 
# 기존 패키지 stop
/etc/init.d/mysqld stop
 
# MySQL 데몬 확인
ps -ef | grep mysql
# 구동중인 프로세스 죽이기
kill -9 프로세스번호
 
=========================================================
# cmake 설치전에 필요 라이브러리 설치(경로위치 상관없음)
yum -y install zlib curl
yum -y install gcc gcc-cpp gcc-c++
yum -y install openssl openssl-devel
yum -y install libtermcap-devel ncurses-devel libc-client-devel bzip2-devel
yum -y install bison libpng-devel curl-devel libpng freetype 
yum -y install xml*
yum -y install gd*
yum -y install libxml*
 
# yum -y install php-devel phpize php-pear 
# 이걸 yum 설치하면 apache, php 가 다시 기본 설치된다.
 
=============================================
# 설치할 디렉토리 만들기
cd /usr/local/
mkdir APM
 
# cmake 설치
# 1. cmake 압축 풀고 설치 (기존 설치된 것 있으면 지우고 자동 재설치)
cd /home/htdocs/
rm -rf cmake-2.8.9/
tar -xzf cmake-2.8.9.tar.gz
cd cmake-2.8.9
./bootstrap
make && make install
 
======== MySQL 소스 설치 ==================
# 2. mysql 설치
cd /home/htdocs/
rm -rf mysql-5.5.27/
tar -xvzf mysql-5.5.27.tar.gz
cd mysql-5.5.27
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_EXTRA_CHARSETS=all -DMYSQL_DATADIR=/usr/local/mysql/data -DENABLED_LOCAL_INFILE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DMYSQL_UNIX_ADDR=/tmp/mysql.sock -DSYSCONFIGDIR=/etc -DMYSQL_TCP_PORT=3306
make && make install
 
=== PATH 추가 ===
cd
vi .bash_profile
# PATH 경로에 추가
PATH=$PATH:$HOME/bin:/usr/local/mysql/bin
저장후
source .bash_profile
 
cd /usr/local/mysql/
groupadd mysql
useradd -g mysql mysql
chown -R mysql:mysql /usr/local/mysql
# mysql 설치 디렉토리에 대한 mysql 권한 부여
 
# mysql 설정 파일 복사
cp support-files/my-medium.cnf /etc/my.cnf
 
# 기본 DB 생성
./scripts/mysql_install_db --user=mysql --datadir=/usr/local/mysql/data
 
 
# 시스템이 재부팅되어도 mysql 이 자동 실행되도록 설정
# 기존 설치된 파일이 있으면 overwrite 하면 됨
cp support-files/mysql.server /etc/init.d/mysqld
 
vi /etc/init.d/mysqld
datadir=/usr/local/mysql/data
로 변경하고 저장
 
# 등록이 잘 되었는지 확인
chkconfig --add mysqld
chkconfig --list | grep mysqld
 
# MySQL 서버와 프로그램을 연결해 줄 소켓을 링크 파일로 생성한다.
ln -/var/lib/mysql/mysql.sock /usr/local/mysql/mysql.socket
 
==== MySQL 구동
/etc/init.d/mysqld start
 
MYSQL root 암호 설정
mysql -uroot -p
password: 그냥 엔터키 (패스워드가 설정되지 않았을 때, 설정되어 있으면 접속 불가)
use mysql;
--update user set password=password('암호') where user='root';
update user set password=password('link2me03') where user='root';
flush privileges;
select host, user, password from user;
quit
 
=========== Apache 소스 설치 ===============
3. 아파치 설치
cd /home/htdocs/
rm -rf apr-1.4.6/
tar -xvzf apr-1.4.6.tar.gz
cd apr-1.4.6
./configure
make && make install
 
cd /home/htdocs/
rm -rf apr-util-1.4.1/
tar -xvzf apr-util-1.4.1.tar.gz
cd apr-util-1.4.1
./configure --with-apr=/usr/local/apr
make && make install
 
cd /home/htdocs/
rm -rf pcre-8.30/
tar -xvzf pcre-8.30.tar.gz
cd pcre-8.30
./configure
make && make install
 
# pcre-8.30 에러가 발생하는 부분(CentOS 6.6에서는 에러 메시지 없음)
 
cd /home/htdocs/
rm -rf httpd-2.4.3/
tar -xvzf httpd-2.4.3.tar.gz
cd httpd-2.4.3
./configure --prefix=/usr/local/apache --enable-mods-shared=all --enable-so --enable-rewrite-all
make && make install
 
# 컴파일 실수가 발생하면, 해당 디렉토리에서 바로 make distclean 으로 삭제한다.
# 그리고 나서 다시 실행한다.
 
cp /usr/local/apache/bin/apachectl /etc/init.d/httpd
 
# 리부팅시 아파치 프로세스를 자동으로 띄운다
cd /home/htdocs/
ln -/init.d/httpd /etc/rc0.d/K90httpd
ln -/init.d/httpd /etc/rc3.d/S89httpd
ln -/init.d/httpd /etc/rc5.d/S89httpd
 
vi /etc/init.d/httpd
둘째줄에서 i 를 눌러 Insert 모드로 변경한 다음에 아래 5줄을 복사하여 붙여넣기하여 삽입하고 저장
# chkconfig: 2345 90 90
# description: inir file for Apache server daemon
# processname: /usr/local/apache/bin/apachectl
# config: /usr/local/lib/httpd.conf
# pidfile: /usr/local/apache/logs/httpd.pid
 
cd /home/htdocs/httpd-2.4.3
chkconfig --add httpd
chkconfig --list httpd
chkconfig --level 2345 httpd on
 
vi /usr/local/lib/httpd.conf
User daemon 과 Group daemon 라인을 찾아서 daemon 대신에 nobody 로 변경
#ServerName www.example.com:80 아래줄에 추가
ServerName localhost
#처럼 해당 서버주소이름으로 변경. 도메인이 있으면 도메인으로 변경처리
 
# Options Indexes FollowSymLinks 에서 Indexes 제거해야 디렉토리 경로 보이는 것 방지됨
# 그리고 아래처럼 변경해줌
DocumentRoot "/usr/local/apache/htdocs"
<Directory "/usr/local/apache/htdocs">
    Options FollowSymLinks
    AllowOverride None
    Order deny,allow
    Allow from all
    Require all granted
</Directory>
 
아파치(Apache) 시작
/etc/init.d/httpd start
 
http://192.168.1.20/ --> Web browser 에서 It works! 나오면 성공적인 설치되었다는 표시
 
----It works 화면이 안될때 오류사항 해결 ------
/etc/sysconfig/iptables 에서 80포트 방화벽 해제
/etc/rc.d/init.d
 
 
======= PHP 소스 설치 =======
4. PHP 설치
cd /home/htdocs/
wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz
rm -rf libiconv-1.15/
tar -xvzf libiconv-1.15.tar.gz
cd libiconv-1.15
./configure --prefix=/usr/local/iconv
make && make install
 
cd /home/htdocs/
wget https://sourceforge.net/projects/mcrypt/files/Libmcrypt/2.5.8/libmcrypt-2.5.8.tar.gz
tar -xvzf libmcrypt-2.5.8.tar.gz
cd libmcrypt-2.5.8
./configure --prefix=/usr/local/
make && make install
 
 
# PHP 소스 설치시 에러가 발생하면 make clean 을 해줘야 한다.
 
cd /home/htdocs/
wget https://museum.php.net/php5/php-5.4.19.tar.gz
cd /home/htdocs/
rm -rf php-5.4.19/
tar -xvzf php-5.4.19.tar.gz
cd php-5.4.19
./configure --prefix=/usr/local/php \
 --with-config-file-path=/usr/local/apache/conf \
 --with-apxs2=/usr/local/apache/bin/apxs \
 --with-mysql=/usr/local/mysql \
 --with-iconv=/usr/local/iconv \
 --with-curl=/usr/lib \
 --enable-zip \
 --with-openssl \
 --with-mcrypt \
 --enable-mbstring \
 --with-gd \
 --with-mysqli=/usr/local/mysql/bin/mysql_config \
 --with-zlib \
 --with-freetype-dir=/usr/local/lib \
 --with-jpeg-dir=/usr/local/php  --with-mcrypt=/usr/local/
make
make install
 
 
## 위 명령 실행시 아래와 같은 오류가 난다면 해결책 한 후 위에 명령 다시 실행.
# configure: error: Please reinstall the libcurl distribution - easy.h should be in <curl-dir>/include/curl/
# 해결책 : # yum -y install curl* 또는 yum -y install curl & yum -y install curl-devel
 
# php.ini 샘플파일을 환경설정 디렉토리에 복사
cp php.ini-development /usr/local/lib/php.ini
 
# 아파치와 연동을위해 추가로 httpd.conf 3군데 수정
vi /usr/local/lib/httpd.conf
<IfModule dir_module>
DirectoryIndex index.html index.php   =>index.php추가
</IfModule>
 
<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common
 
    <IfModule logio_module>
      # You need to enable mod_logio.c to use %I and %O
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
    </IfModule>
 
    # 그림 이미지, css, js는 기록하지 않게 처리
    SetEnvIfNoCase Request_URI "\.(jpg|png|gif|css|ico|js|swf)$" notloglist
 
    #CustomLog "logs/access_log" common
    #CustomLog "logs/access_log" combined
    #CustomLog "|/아파치 설치 경로/bin/rotatelogs /로그를 저장할 경로/파일명 %Y%m%d%H 86400 +540" combined
    # 날짜별로 로그기록 남기기
    CustomLog "|/usr/local/apache/bin/rotatelogs /usr/local/apache/logs/access_log_%Y%m%d 86400 +540" combined env=!notloglist
</IfModule>
 
# 아래 두줄을 <IfModule mime_module>에 추가
    AddType application/x-httpd-php .php .html
    AddType application/x-httpd-php-source .phps
<IfModule mime_module>
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
    #PHP Enable
    AddType application/x-httpd-php .php .html
    AddType application/x-httpd-php-source .phps
</IfModule>
 
# lib, inc 등을 사용할 경우에는 반드시 AddType application/x-httpd-php .php .html .inc .lib .xml 처럼 설정해줘야 한다.
# 이 부분을 빠뜨리면 웹페이지 상에서 텍스트로 인식하여 파일의 내용이 그대로 노출된다.
# 파일 확장자가 htm 으로 끝나면 이 부분도 추가를 해줘야 한다.
# 코드가 뭔지 모르게 하고 싶다면 .do 를 사용하는 것도 방법 중의 하나이다.
 
 
###### php.ini 수정사항  ###########
vi /usr/local/lib/php.ini
시간설정 (앞에 ;지울것) 을 지우고 아래 내용 추가
date.timezone = "Asia/Seoul"
;<? 와 <?php 둘다 인식하게 함
short_open_tag = On
; PHP 가 받아들이는 POST data 최대 크기
;default 8M 로 되어 있음, upload_max_filesize 보다 크게 설정해야 함
post_max_size = 60M 
memory_limit = 128M
upload_max_filesize = 50M
 
; error_reporting 세팅
;개발환경
;error_reporting  =  E_ALL
; 실제 환경, 경고메시지 출력 안됨
error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
; 해킹의 먹이감이 되므로 평소에는 Off 로 해둔다. 에러가 있을 경우 디버깅 가능하도록 에러 메세지를 출력해준다.
display_errors = Off
; 평소에는 해제(Off)한다. 개발환경에서는 디버깅 가능하도록 On하면 에러 메세지를 출력해준다. 
display_startup_errors = Off
log_errors = On
; 에러 로그 파일 설정이 없으면 Apache 로그 파일에 기록된다.
; 퍼미션 설정해서 /home/user/log/php.log 과 같이 설정
;error_log = filename
 
; register_globals = On 으로 하면, PHP 스크립트의 변수 값을 임의로 변경할 수 있는 취약성이 있다.
; off 로 설정한 후, $_GET, $POST 를 사용해서 사용자가 전달한 값을 얻어야 한다.
register_globals = Off
 
;UTF-8 로 문자 인코딩 설정하기
;해당 부분을 찾아서 아래와 같이 수정
default_charset = "UTF-8"
[mbstring]
mbstring.language = Korean
mbstring.internal_encoding = UTF-8
mbstring.http_input = pass
mbstring.http_output = pass
mbstring.encoding_translation = Off
 
#########################################################
다른 방식으로 upload progress 추가
cd /usr/local/bin
./pecl install uploadprogress
 
vi /usr/local/lib/php.ini 파일에서 추가할 사항
extension=uploadprogress.so
 
# 이해 잘 안되는 부분
# 파일 생성 여부 확인
find / -name libphp5.so 
# modules/libphp5.so 설정 여부 확인해본다.
httpd.conf 파일내에 LoadModule php5_module        
 
chcon -t texrel_shlib_t 
# /usr/local/apache/modules/libphp5.so --> 모듈 올라오는 에러 잡는것
# 위 명령 에러날경우 SELINUX를 비활성화에서 활성화로 변경
cat /etc/selinux/config
# vi /etc/selinux/config
SELINUX=enforcing를 SELINUX=disabled로 변경 
 
5. 퍼미션 설정 및 점검
# 일반계정 유출 등에 의해 파일이 변조될 수 있으므로 확인하여 조치한다.
# Apache and PHP 환경설정 파일 퍼미션 변경
# httpd.conf 파일의 경로 찾기 
find / -name httpd.conf 
chmod 640 /usr/local/lib/httpd.conf
chown root:root /usr/local/lib/httpd.conf
chmod 640 /usr/local/lib/php.ini
chown root:root /usr/local/lib/php.ini
# Document Root 디렉토리 설정 확인 및 변경
# Document Root 디렉토리는 755, 파일은 644로 되어 있는지 확인하고 변경
cd /usr/local/apache/htdocs
# 가능하면 home/httpd/ 하위 디렉토리에 Document Root 가 설정되도록 변경(httpd.conf 파일내에서)
chown 작성자(nobody) /usr/local/apache/htdocs
chgrp 작성자그룹 /usr/local/apache/htdocs
chmod 755 /usr/local/apache/htdocs
 
 
DocumentRoot "/home/httpd"
<Directory "/home/httpd">
# 허용하지 않은 디렉토리를 액세스할 수 있으므로 가능하면 FollowSymLinks 를 삭제하는 것이 좋다.
#    Options IncludesNoExec FollowSymLinks
    Options IncludesNoExec
    AllowOverride None
    Order deny,allow
    Allow from all
    Require all granted
</Directory>
 
 
## httpd restart 
/etc/init.d/httpd stop
/etc/init.d/httpd start
/etc/init.d/mysqld start
 
## 정상적으로 설치되었는지 여부 확인
http://192.168.1.20/info.php
 
# phpinfo 가 정상적으로 보이면 APM 설치는 정상적으로 잘 되었다고 보면 된다.
 
========== DB 설정 =========================
1. MySQL 접속 및 사용자 권한 부여
mysql -u root -p
 
-- DB 생성
create database phpdb default character set utf8;
use mysql;
create user codefox@localhost identified by 'Wofullnder!#';
grant all privileges on phpdb.* to codefox@localhost;  -- 사용자 권한 부여
flush privileges;    -- // 변경된 내용을 메모리에 반영(권한 적용)
 
revoke all on phpdb.* from codefox@localhost;  -- 권한 회수
show grants for codefox@localhost;  -- 권한 확인
 
grant all privileges on phpdb.* to codefox@localhost;  -- 사용자 권한 부여
flush privileges;    -- // 변경된 내용을 메모리에 반영(권한 적용)
 
grant all privileges on phpdb.* to codefox@localhost identified by 'Wofullnder!#';
 
-- backup 받은 DB SQL 복구
show databases;
use phpdb;   -- DB를 선택하지 않으면 에러가 발생함
source codefox.sql 
 
 
# 게시물 내용 검색 (찾고자 하는 내용이 들어있는 파일 찾아내기)
cd /usr/local/apache/htdocs
find ./ -name "*.php" | xargs grep -"계정을 다시 확인"
find ./ -name "*.*" | xargs grep -"검색어를 입력"
 
# 서버 네트웍 IP 수정
   /etc/sysconfig/network-script/ifcfg-eth0 -> 서버 IP
   /etc/resolve.conf  -> 도메인 IP
 
# 소스 IP수정
   /usr/local/lib/httpd.conf
   /usr/local/apache/config.php
 
 
##############################################################################
# 환경 설정 
vi /usr/local/lib/httpd.conf
vi /usr/local/lib/php.ini
 
## httpd restart 
/etc/init.d/httpd stop
/etc/init.d/httpd start
/etc/init.d/mysqld start
 
netstat -an | grep "LISTEN "
 
 
<VirtualHost *:8080>
       DocumentRoot "/var/www/html"
       <Directory "/var/www/html"> 
          AllowOverride All 
       </Directory>
       ErrorLog /var/log/httpd/seogu-error-log
       CustomLog /var/log/httpd/seogu-acces-log common
</VirtualHost>
 
###########################################################################################
# 모듈 추가 설치
vi /etc/yum.repos.d/CentOS-Base.repo
https://kkckc.tistory.com/185 에서 파일을 받아서 내용을 전부 교체한다.
yum -y install mc
 
# 설치된 PHP 모듈 확인하기
# 먼저 whereis php 로 경로가 아래와 같은가 틀리는가 확인하고 수정해야 한다.
/usr/local/bin/php -m
 
 
# 오래된 CentOS 5.9 에서는 동작 안될 수도 있음
cd /home/htdocs/
wget https://sourceforge.net/projects/mcrypt/files/Libmcrypt/2.5.8/libmcrypt-2.5.8.tar.gz
 
cd /home/htdocs/
tar -xvzf libmcrypt-2.5.8.tar.gz
cd libmcrypt-2.5.8
./configure --prefix=/usr/local/
make && make install
 
cd /home/htdocs/php-5.4.6/ext/mcrypt
# phpize 파일을 실행한다.
/usr/local/bin/phpize
 
./configure --with-php-config=/usr/local/bin/php-config --with-mcrypt=/usr/local/
make
 
# cd modules
# ll 하면 mcrypt.so 파일이 생성된 것을 확인할 수 있다.
cd /usr/local/lib/php/extensions/ 
# ll 하여 extensions 디렉토리가 있는지 조사한다. 없으면 mkdir extensions 로 디렉토리를 생성한다.
mkdir -/usr/local/lib/php/extensions/
 
cd /home/htdocs/php-5.4.6/ext/mcrypt
cp -arp modules/mcrypt.so /usr/local/lib/php/extensions/
cd /usr/local/lib/php/extensions/
 
# php.ini 수정 사항
# phpinfo 파일에서 설치된 경로를 확인해야 한다.
# /usr/local/lib/php.ini 
# 검색 기능으로 /extension= 로 검색하여 추가한다.
cd /usr/local/lib/
vi /usr/local/lib/php.ini
extension=/usr/local/lib/php/extensions/mcrypt.so
 
# 아파치 재시작
# ps -ef | grep httpd
/usr/local/apache/bin/apachectl restart
 
# mbstring 모듈 추가
cd /home/htdocs/php-5.4.6/ext/mbstring
/usr/local/bin/phpize
./configure --with-php-config=/usr/local/bin/php-config --enable-mbstring
make
 
# ll ./modules/mbstring.so 파일이 생성되었는지 확인한다.
cd /home/htdocs/php-5.4.6/ext/mbstring
cp -arp modules/mbstring.so /usr/local/lib/php/extensions/
cd /usr/local/lib/php/extensions/
 
# php.ini 수정 사항
# 검색 기능으로 /extension= 로 검색하여 추가한다.
vi /usr/local/lib/php.ini
extension=/usr/local/lib/php/extensions/mbstring.so
 
# 아파치 재시작
/usr/local/apache/bin/apachectl restart
 
# 설치된 PHP 모듈 확인하기
/usr/local/bin/php -m
 
 

 

 

'리눅스' 카테고리의 다른 글

CentOS 7 날짜 반복 증가 스크립트  (0) 2024.03.31
CentOS 7 PHP 7.4 MariaDB 10.5 설치  (0) 2022.05.14
VMWare Network 설정  (0) 2022.04.23
CentOS 7 Jenkins 설치  (0) 2022.03.25
Apache log Full  (0) 2022.02.22
블로그 이미지

Link2Me

,
728x90

MySQL 샘플 데이터베이스 설치하는 방법이다.

 

https://www.mysqltutorial.org/mysql-sample-database.aspx

 

MySQL Sample Database

This page provides you with a MySQL sample database that helps you to practice with MySQL effectively and quickly. You can download the sample database and load it into your MySQL Server.

www.mysqltutorial.org

위 사이트에 접속하면 받을 수가 있는데 문제는 Import 하면 에러가 발생하더라.

전체적인 구조는 위와 같다.

 

Legacy Create 테이블 생성 방식(?)이라 그런지 몰라도 테이블 순서가 매우 중요하다.

sql 파일을 Editor로 열어보면, Foreign Key 가 설정되어 있고 employees 테이블을 참조하고 있다.

customer 테이블보다 먼저 employees 테이블이 먼저 Import 되어야 한다는 의미로 간주해야 한다.

CREATE TABLE `customers` (
  `customerNumber` int(11NOT NULL,
  `customerName` varchar(50NOT NULL,
  `contactLastName` varchar(50NOT NULL,
  `contactFirstName` varchar(50NOT NULL,
  `phone` varchar(50NOT NULL,
  `addressLine1` varchar(50NOT NULL,
  `addressLine2` varchar(50DEFAULT NULL,
  `city` varchar(50NOT NULL,
  `state` varchar(50DEFAULT NULL,
  `postalCode` varchar(15DEFAULT NULL,
  `country` varchar(50NOT NULL,
  `salesRepEmployeeNumber` int(11DEFAULT NULL,
  `creditLimit` decimal(10,2DEFAULT NULL,
  PRIMARY KEY (`customerNumber`),
  KEY `salesRepEmployeeNumber` (`salesRepEmployeeNumber`),
  CONSTRAINT `customers_ibfk_1` FOREIGN KEY (`salesRepEmployeeNumber`REFERENCES `employees` (`employeeNumber`)
ENGINE=InnoDB DEFAULT CHARSET=latin1;
 
 

 

이런 점을 고려하여 테이블 순서를 변경한 파일을 첨부한다.

mysqlsampledb.sql
0.20MB

 

위 파일을 Import 하면 정상적으로 테이블이 생성되고 데이터가 추가될 것이다.

 

DBWeaver TOOL 을 이용해서 접속해보자.

접속환경 : VirtualBox 기반 CentOS 7.9 MariaDB 10.5

DB IP address : 192.168.1.20 (사설 IP)

Access IP address : 192.168.1.25

 

-- 사용자 권한 부여
-- 비밀번호는 각자 수정하시라.
use mysql;
grant all privileges on cmodels.* to codefox@'192.168.1.25' identified by '비밀번호';
flush privileges;
 

 

 

 

 

'SQL' 카테고리의 다른 글

접속로그 통계 (신규, 중복 동시 처리)  (0) 2023.05.23
MySQL foreign key 예제  (0) 2022.05.16
MariaDB 멀티 인덱스(index) 설정  (0) 2022.04.05
MariaDB 대소문자 구분  (0) 2022.03.14
MySQL 중복 레코드 관리 방법  (0) 2022.03.02
블로그 이미지

Link2Me

,
728x90

https://mdbootstrap.com/docs/b4/jquery/javascript/charts/

 

Bootstrap Charts Guideline - examples & tutorial

Bootstrap charts are graphical representations of data. Charts come in different sizes and shapes: bar, line, pie, radar, polar and more.

mdbootstrap.com

기본적인 예제가 나와 있다.

이 예제를 가지고 DB와 연동하여 처리하는 방법이다.

 

 

<?php
error_reporting(0);
//*
ini_set("display_startup_errors"1);
ini_set("display_errors"1);
error_reporting(E_ALL);
// */
 
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php'// 세션 체크
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'statsClass.php';
$b = new statsClass();
 
$link_url = "Stats.php"// 현재 실행중인 파일명 가져오기
 
$ageData = $b->ageCnt_array();
 
?>
<p class="h4 mb-4">통계</p>
<div class="row">
    <div class="col-md-8 col-xl-8 col-lg-7">
        <div class="col-md-11">
            <canvas id="Trend1Chart"></canvas>
        </div>
        <div class="col-md-11  mb-4">
            
        </div>
        <div class="col-md-11">
            <canvas id="Trend3Chart"></canvas>
        </div>
    </div>
 
    <div class="col-md-4 col-xl-4 col-lg-5">
        <div class="card shadow mb-4">
            <div class="card-header py-3">
                <h6 class="m-0 font-weight-bold text-primary">연령대</h6>
            </div>
            <div class="card-body">
                <div class="chart-pie pt-4">
                    <canvas id="ageChart"></canvas>
                </div>
            </div>
        </div>
 
    </div>
</div>
 
<script>
  var ctxD = document.getElementById("ageChart").getContext('2d');
  var ageKey = <?php echo json_encode(array_keys($ageData));?>;
  var ageData = <?php echo json_encode(array_values($ageData));?>;
  var myLineChart = new Chart(ctxD, {
    type: 'doughnut',
    data: {
      labels: ageKey,
      datasets: [{
        data: ageData,
        backgroundColor: ["#F7464A""#46BFBD""#FDB45C""#949FB1""#4D5360"],
        hoverBackgroundColor: ["#FF5A5E""#5AD3D1""#FFC870""#A8B3C5""#616774"]
      }]
    },
    options: {
      responsive: true
    }
  });
</script>

 

PHP 배열을 Javascript 변수로 사용하는 방법이 핵심이다.

 

PHP에서 생성된 배열을 자바스크립트에서 사용하는 방법

<?php
$arr_php = array("a","b","c","d");
?>

위 값처럼 선언된 배열을 javascript에서 받아서 사용하려면?

var arr_js = <?php echo json_encode($arr_php)?>;

json_encode 해서 받으면 된다.

json_encode 함수를 사용하면 1차원 배열이든 2차원 배열이든 간단하게 자바스크립트 배열로 만들어 준다.

array_keys($array)  : 배열의 키값을 배열로 반환해주는 함수

array_values($array)  : 배열의 값을 배열로 반환해주는 함수
 
아래 statsClass 의 함수 구현한 것을 살펴보면 PHP 배열을 key, value 로 된 형태로 반환하는 것을 알 수 있다.
<?php
class statsClass {
    function ageCnt_array(){
        global $db;
        $sql = "select age, sum(Cnt) from stats group by age";
        $result = mysqli_query($db,$sql);
        $R=array();
        $TCNT = $this->ageTotalCnt();
        while($row = mysqli_fetch_row($result)){
            $row[0= $row[0].'('.$this->Percentage($row[1],$TCNT).'%)';
            $R[$row[0]] = $row[1];
        }
        return $R// 배열로 반환
    }
 
    function ageTotalCnt(){
        global $db;
        $sql = "select sum(Cnt) from stats";
        $result = mysqli_query($db,$sql);
        if($row = mysqli_fetch_row($result)){
            return $row[0];
        }
    }
 
    function Percentage($val,$TCNT){
        $percent = $val / $TCNT * 100;
        return round($percent,1);
    }
 
}
?>

 

 

블로그 이미지

Link2Me

,
728x90

strtotime()는 두 날짜를 UNIX 시간으로 변환하고 그로부터 초 수를 계산한다.

먼저 new DateTime()을 사용하여 날짜를 선언한다. 
그런 다음 첫 번째 날짜의 DateInterval() 오브젝트 diff()를 사용하여 차이의 정수 값을 가져오고 두 번째 날짜를 매개 변수로 전달한다. 년은 y 객체를 사용, 월은 m 객체, 일은 d 객체를 사용하면 된다.

<?php
$firstDate  = new DateTime("2022-01-01");
$secondDate = new DateTime("2022-04-24");
$intvl = $firstDate->diff($secondDate);
 
echo $intvl->y . " 년, " . $intvl->m." 월 and ".$intvl->d." 일";
 
// 날짜수를 계산
echo $intvl->days . " days ";
?>

 

DB와 연동하여 신청일자, 폐지일자 데이터를 가져와서 유형을 구분하고 DB 저장하기 위한 로직 예제이다.

<?php
 
$sql = "select * from customer ";
$result =  mysqli_query($db$sql);
while($R = mysqli_fetch_array($result)){
 
    $regdate = preg_replace("/[^0-9]/","",$R['reg_date']);
    $revodate  = preg_replace("/[^0-9]/","",$R['revo_date']);
    $datediff = getDateDiff($regdate$revodate);
 
}
mysqli_close($db); // 디비 접속 닫기
 
 
// 문자열 날짜를 입력받아 날짜 차이를 계산한다.
function getDateDiff($regDate$revoDate){
    date_default_timezone_set('Asia/Seoul');
    $date1 = new DateTime(date("Y-M-d", strtotime($regDate)));
    if($revoDate == '99991231'){
        $date2 = new DateTime(date("Y-M-d")); // 유지시 현재 날짜 반환
    } else {
        $date2 = new DateTime(date("Y-M-d", strtotime($revoDate)));
    }
 
    $intvl = $date2->diff($date1);
    $GapDate = $intvl->days;
    return DateType($GapDate);
}
 
// 날짜 타입 구분
function DateType($GapDate){
    
    if($GapDate >= 180){
        return "6개월";
    } else if($GapDate >= 90){
        return "3개월";
    } else if($GapDate >= 60){
        return "2개월";
    } else if($GapDate >= 30){
        return "1개월";
    } else if($GapDate >= 10){
        return "10일이상";
    } else {
        return "10일이내";
    }
}
 
?>

 

 

 

 

블로그 이미지

Link2Me

,

VMWare Network 설정

리눅스 2022. 4. 23. 20:49
728x90

VMWare Pro 버전을 설치하고 Network 설정하는 방법을 테스트하고 적어둔다.

처음에는 설정방법을 잘 몰라서 한참을 헤매다가 성공했다.

 

 

VMnet0 가 bridge 모드로 설정하는 옵션

 

반드시 Ethernet Controller 로 설정해야 한다.

 

설정이 잘 되어 있는지 확인하는 방법이다.

state UP 이라고 나와야 정상이다. state DOWN 이라고 되어 있으면 비정상이다.

 

브릿지 모드로 설정한다는 것은 IPTIME 과 같은 공유기 환경 내부에서 PC/맥북이 하나의 IP Address 를 할당받고, VMWare 가 같은 환경의 IP를 할당받는 것이다.

윈도우 환경에서 내 IP 주소 확인은 ipconfig 로 확인한다.

 

systemctl restart network 명령어를 입력해도 된다.

'리눅스' 카테고리의 다른 글

CentOS 7 PHP 7.4 MariaDB 10.5 설치  (0) 2022.05.14
CentOS 5.8 APM 설치 과정  (0) 2022.05.14
CentOS 7 Jenkins 설치  (0) 2022.03.25
Apache log Full  (0) 2022.02.22
CentOS 7 docker 설치 및 컨테이너 사용 방법  (2) 2021.12.31
블로그 이미지

Link2Me

,