728x90

CentOS 원격 서버로 파일 전송하기


# 원격 서버 SSH 접속포트가 1922인 경우
scp -p 1922  root@10.10.122.135:/root/backup/


# 원격 서버 SSH 접속포트가 22인 경우

scp qrcode.tar.gz root@10.10.122.133:/var/www/



블로그 이미지

Link2Me

,
728x90

자바와 코틀린의 기능을 표로 간략하게 비교하는 걸로 자주 사용하는 걸 추가해 두고자 한다.


기능

언어

설명

Intent

Java

  Intent intent = new Intent(getApplicationContext(), SubActivity.class);
  intent.putExtra("name","홍길동");
  startActivity(intent);

  Intent intent = getIntent(); /*데이터 수신*/
  String name = intent.getExtras().getString("name");
  int age = intent.getExtras().getInt("age");

kotlin

  val intent = Intent(this, secondActivity::class.java)
  intent.putExtra("phone",""010-0000-0000")
  startActivity(intent)

  val intent = getIntent()
  val phone =intent.getStringExtra("phone").toString()


기능

언어

설명

Context

Java

 Context mContext;
 mContext= LoginActivity.this;

kotlin

 lateinit var mContext: Context
 mContext = this@LoginActivity


기능

언어

설명

TAG

Java

 private final String TAG = this.getClass().getSimpleName();

kotlin

 private val TAG = this.javaClass.simpleName


블로그 이미지

Link2Me

,
728x90

Java static 메서드가 포함된 Class를 코틀린에서 직접 호출하니까 에러가 발생하고 제대로 동작이 안된다.


그래서 Android Studio 에서 기본 제공하는 방법으로 파일을 변환하면 Object 파일로 변환을 한다.

이렇게 변환한 파일을 다시 수작업으로 companion object 안에 메서드를 감싸는 방법으로 해주는 것이 좋더라.


자바 코드

public class Utils extends AppCompatActivity {

    public static void showAlert(Context context, String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    public static OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }

    public static boolean isValidmobileNO(String cellphoneNumber) {
        boolean returnValue = false;
        Log.i("cell", cellphoneNumber);
        String regex = "^\\s*(010|011|012|013|014|015|016|017|018|019)(-|\\)|\\s)*(\\d{3,4})(-|\\s)*(\\d{4})\\s*$";
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(cellphoneNumber);
        if (m.matches()) {
            returnValue = true;
        }
        return returnValue;
    }

    public static int getVersionCode(Context context) {
        PackageInfo packageInfo = null;
        try {
            packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        int versionCode = packageInfo.versionCode;
        return versionCode;
    }

}
 



코틀린 변환 코드

class Utils : AppCompatActivity() {

    companion object {
        fun showAlert(context: Context, title: String?, message: String?) {
            val builder = AlertDialog.Builder(context)
            builder.setTitle(title)
            builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK") { dialog, id -> dialog.dismiss() }
            val alert = builder.create()
            alert.show()
        }

        fun createOkHttpClient(): OkHttpClient {
            // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
            val builder = OkHttpClient.Builder()
            val interceptor = HttpLoggingInterceptor()
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
            builder.addInterceptor(interceptor)
            return builder.build()
        }

        fun isValidmobileNO(cellphoneNumber: String?): Boolean {
            var returnValue = false
            Log.i("cell", cellphoneNumber!!)
            val regex =
                "^\\s*(010|011|012|013|014|015|016|017|018|019)(-|\\)|\\s)*(\\d{3,4})(-|\\s)*(\\d{4})\\s*$"
            val p = Pattern.compile(regex)
            val m = p.matcher(cellphoneNumber)
            if (m.matches()) {
                returnValue = true
            }
            return returnValue
        }

        fun getVersionCode(context: Context): Int {
            var packageInfo: PackageInfo? = null
            try {
                packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
            } catch (e: PackageManager.NameNotFoundException) {
                e.printStackTrace()
            }
            val versionCode = packageInfo!!.versionCode
            return versionCode
        }

}


안드로이드 스튜디오가 기본 제공하는 변환을 이용하면, 약간 코틀린 문법에 맞게 변수를 조정해준다.

이렇게 하고 나서 코틀린에서 해당 메서드를 호출하면 정상으로 잘 동작한다.


블로그 이미지

Link2Me

,
728x90

LineaLayout 지식인 질의 예제


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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="horizontal"
        android:layout_weight="2">

        <View
            android:id="@+id/view1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#FF0000"
            android:layout_weight="1" />

        <View
            android:id="@+id/view2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#0000FF"
            android:layout_weight="1" />

        <View
            android:id="@+id/view3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#00FF00"
            android:layout_weight="1" />
    </LinearLayout>

    <View
        android:id="@+id/view4"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="#FFFF00"
        android:layout_weight="1"/>

    <View
        android:id="@+id/view5"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="#0000FF"
        android:layout_weight="1"/>

    <View
        android:id="@+id/view6"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="#00FF00"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="vertical"
        android:layout_marginTop="20dp"
        android:layout_weight="1">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="Button"
                android:textAllCaps="false"/>

            <ImageButton
                android:id="@+id/imageButton"
                android:layout_width="wrap_content"
                android:layout_height="50dp"
                android:layout_gravity="center"
                android:src="@drawable/ic_baseline_videocam_24" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="5dp">

            <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="20192019"
                android:textSize="32sp"
                android:textColor="#FF0000"/>

            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="24dp"
                android:text="홍길동"
                android:textSize="32sp"
                android:textColor="#0000FF"/>
        </LinearLayout>
    </LinearLayout>
</LinearLayout>
 



Vector Asset 추가하는 방법으로 이미지를 추가하면 된다.

'안드로이드 > Layout' 카테고리의 다른 글

하단 TabLayout + ViewPager2 Layout 예제  (0) 2020.12.30
하단 TabLayout + ViewPager Layout 예제  (0) 2020.11.01
LinearLayout 동적 생성  (0) 2020.10.01
SMS authentication with OTP Layout Example  (0) 2020.09.14
EditText DatePicker  (0) 2020.08.18
블로그 이미지

Link2Me

,
728x90

안드로이드 View Binding 기능을 어떤 걸로 테스트하면 좋을까 생각하다가 GitHub 에서 계산기를 받아서 그걸로 View Binding 테스트를 했다.


뷰 바인딩은 view와 상호작용하는 코드를 더욱 쉽게 작성할 수 있도록 도와주는 기능이다.


View Binding 을 하는 방법

1. 앱 build.gradle 추가사항

    viewBinding {
        enabled = true
    } 


2. Java code 수정사항

setContentView(R.layout.activity_main); 대신에

private ActivityMainBinding binding;

binding = ActivityMainBinding.inflate(getLayoutInflater()); // 1. inflate 호출하여 인스턴스 생성

View view = binding.getRoot(); // 2. getRoot()

setContentView(view); // 3. 화면상의 활성 뷰로 만든다.


binding.btnClear.setOnClickListener(v -> {
    binding.tvInput.setText("");
    binding.tvOutput.setText("");
});


3. XML 파일 수정사항은 없다.

    - XML 파일을 분리하여 호출하는 구조로는 binding 을 인식하지 못하더라. 그래서 1개의 파일로 작성했음.

    - findViewById 호출 대신에 직접 id값을 binding 한다.


<Button
    android:id="@+id/btnClear"
    android:layout_width="wrap_content"
    android:layout_height="100dp"
    android:layout_row="0"
    android:layout_column="0"
    android:backgroundTint="@color/colorWhiteGray"
    android:text="CL"
    android:textColor="@color/colorBlack"
    android:textSize="25sp" />


계산기 예제코드

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    viewBinding {
        enabled = true
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
    implementation 'com.google.android.material:material:1.2.1'
    // Rhino is an open-source implementation of JavaScript written entirely in Java.
    implementation group: 'org.mozilla', name: 'rhino', version: '1.7.13'

}


import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

import com.link2me.android.calculator.databinding.ActivityMainBinding;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;

public class MainActivity extends AppCompatActivity {
    String process;
    Boolean checkBracket = false;

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater()); // 1. inflate 호출하여 인스턴스 생성
        View view = binding.getRoot(); // 2. getRoot()
        setContentView(view); // 3. 화면상의 활성 뷰로 만든다.

        binding.btnClear.setOnClickListener(v -> {
            binding.tvInput.setText("");
            binding.tvOutput.setText("");
        });

        binding.btn0.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "0");
        });

        binding.btn1.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "1");
        });

        binding.btn2.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "2");
        });

        binding.btn3.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "3");
        });

        binding.btn4.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "4");
        });

        binding.btn5.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "5");
        });

        binding.btn6.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "6");
        });

        binding.btn7.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "7");
        });

        binding.btn8.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "8");
        });

        binding.btn9.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "9");
        });

        binding.btn0.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "0");
        });

        binding.btnAdd.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "+");
        });

        binding.btnMinus.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "-");
        });

        binding.btnMultiply.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "×");
        });

        binding.btnDivision.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "÷");
        });

        binding.btnDot.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + ".");
        });

        binding.btnPercent.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();
            binding.tvInput.setText(process + "%");
        });

        binding.btnEquals.setOnClickListener(v -> {
            process = binding.tvInput.getText().toString();

            process = process.replaceAll("×", "*");
            process = process.replaceAll("%",  "/100");
            process = process.replaceAll("÷","/");

            Context rhino = Context.enter();
            rhino.setOptimizationLevel(-1);

            String finalResult = "";

            try {
                Scriptable scriptable = rhino.initStandardObjects();
                finalResult = rhino.evaluateString(scriptable,process,"javascript",1,null).toString();
            }catch (Exception e){
                finalResult="0";
            }
            binding.tvOutput.setText(finalResult);
        });

        binding.btnBracket.setOnClickListener(v -> {
            if (checkBracket){
                process = binding.tvInput.getText().toString();
                binding.tvInput.setText(process + ")");
                checkBracket = false;
            }
            else{
                process = binding.tvInput.getText().toString();
                binding.tvInput.setText(process + "(");
                checkBracket = true;
            }
        });

    }
}


전체 코드는 https://github.com/jsk005/JavaProjects/tree/master/calculator 에 있다.

블로그 이미지

Link2Me

,
728x90

혈액순환개선제로 오메가3가 알고 복용하자.


혈관은 건강을 잇는 통로다. 우리 몸은 머리부터 발끝까지 크고 작은 혈관으로 촘촘하게 연결되어 있다.

심장에서 나온 혈액은 온 몸을 순환하면서 신체 곳곳에 산소와 영양분을 공급하고 세포의 대사과정에서 생긴 노폐물을 배출한다.

신체 건강을 위해서는 혈관이 막힘없이 잘 연결돼 있어야하고, 흐름도 원활해야 한다.


우리 몸은 내 몸에 맞는 음식을 섭취하면 혈관이 막히지 않고 건강하게 되지만, 내 몸에 맞지 않는 음식을 섭취하면 혈관내에 염증 생기고, 몸 여기저기에 이상 증상이 생긴다.

젊은 시절에는 잘 모르는 데, 나이를 먹어감에 따라 몸이 피곤하고 망가지는 현상이 발생할 수 있다.


현대인은 과다한 육류 섭취와 운동 부족 탓에 혈중 중성지질 수치가 증가하기 쉽다.
EPA는 중성지방이 간에서 합성되는 것을 억제하는 기능이 있어 동맥경화의 원인인 중성지방 수치를 줄여 혈행을 개선하는 효과가 있다. 혈압을 낮추고 맥박 수를 떨어뜨리는 효과도 있어 심혈관계 질환 위험을 낮추는 것으로 알려져 있다.


당뇨병 등을 만성질환을 앓고 있다면 고순도·고함량 오메가3 제제를 추천한다. 고순도·고함량 오메가3 건강식품이 심혈관질환 위험이 높은 당뇨병 환자의 심혈관질환 발생 위험을 25% 낮춘다는 대규모 연구(REDUCE-IT 연구)가 2019년 발표됐다. 해당 연구를 참고해 미국당뇨병학회는 당뇨병 환자 중에 심혈관질환 위험이 높은 환자는 4g의 고함량 오메가3지방산 제제 복용을 고려하도록 가이드라인에 추가했다. 구체적인 대상자는 당뇨병 환자 중에 이상지질혈증 치료제(스타틴)를 복용해도 중성지방이 135~499㎎/㎗로 높은 환자이다. 다만 오메가3제제는 고순도·고함량 제제를 먹어야 효과를 기대할 수 있다.


최근에는 오메가3는 불포화지방산으로 염증제거 효과때문에 많이 복용하고 있다.

대부분 오메가3 지방산이 풍부한 식품으로 참치, 고등어, 연어와 같은 생선과 해조류에 많다. 또한 호두, 들기름, 아마씨유 같은 식품에도 풍부하다. 생선, 즉 동물성 오메가3지방산은 DHA, EPA가 많다.

들기름, 호두, 아마씨유, 까놀라유 같은 식물성 식품에는 알파리놀렌산이 많이 포함되어 있다.

DHA, EPA는 두뇌 기능을 발달시키고 혈중 콜레스테롤을 저하시키는 반면, 알파리놀렌산은 우리 몸의 세포막을 이루는 필수 지방산 중 대부분을 차지해 세포 건강에 중요한 역할을 한다.

미국심장학회에서는 관상 심장질환이 있는 환자의 경우 하루에 EPA와 DHA를 합쳐 1000mg을 섭취하도록 권장하고 있으며 이러한 양은 연어 1토막, 고등어 5토막, 참치 7토막, 굴 1/3컵, 조개 2컵 등을 통해 섭취할 수 있다.


현재 오메가3지방산의 일일 권장량은 500~2000mg이다. 이는 생선을 일주일에 2번 이상 먹어야 충족되는 수치이다 보니 대부분 건강기능식품을 통해 섭취한다.

오메가3지방산은 혈전을 녹여 혈액순환을 원활하게 하는 효과가 있기 때문에 뇌졸중이나 수술 환자는 먹지 않는 편이 좋다. 특히 혈압약을 복용한다면 혈압 저하가 심하게 일어날 수 있으므로 주의한다. 오메가3를 과다하게 섭취할 경우 세포막의 지질이 산화 스트레스에 취약해질 수 있어, 항산화 비타민인 비타민E, 비타민C 등 항산화 영양소가 풍부한 식품성 기름, 채소, 과일과 함께 섭취하는 것이 좋다.

건강기능식품 속의 오메가3지방산은 기름이기 때문에 쉽게 기름이 공기나 물 같은 외부 물질과 접촉하면서 맛과 성분이 변할 수 있다(산패). 산패된 오메가3지방산 건강기능식품은 역할 정도의 비린내가 나고, 캡슐이 말랑거리면서 캡슐끼리 붙어 있는 특징을 보인다. 산패된 오메가3지방산은 아예 다른 성분이 되면서 인체 내에서 활성산소를 증가시켜 DNA와 세포 변형을 일으키는 발암물질로 작용한다.


오메가-3 지방산은 지방의 생성을 저해하고 지방의 분해를 도와줌으로써 혈액 중에 중성지방을 감소시킨다.
따라서 오메가-3 지방산을 적절히 섭취함으로써 이상지질혈증(혈액 중 필요이상으로 지방이 많은 상태)을 예방하는 데 도움이 된다.
시중에 유통되는오메가3는 정어리 추출 제품과 연어유 추출 제품이 있다.

어떤 건강식품이 내 몸에 맞는 걸까?

사람 체질에 따라 먹어야 할 식품이 다르므로 주의가 필요하다.






'건강 정보 > 건강관리' 카테고리의 다른 글

[체험기] 살이 찌는 원인과 당뇨  (0) 2021.06.13
블로그 이미지

Link2Me

,
728x90

Android 코드에서 실제 파일 존재 유무를 확인하는 방법으로 했더니 Main Thread UI에서 작업을 한다고 경고 메시지가 나온다.


    private class PhotoURLExists extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... params) {
            try {
                HttpURLConnection.setFollowRedirects(false);
                HttpURLConnection con =  (HttpURLConnection) new URL(params[0]).openConnection();
                con.setRequestMethod("HEAD");
                return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
            }
            catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    } 


위 코드로 확인을 할 수는 있으나, 제대로 처리하는 것이 아닌거 같아서 PHP에서 자료를 가져올 때 아예 체크하는 로직으로 대체하였다.


테이블 칼럼에는 파일명이 존재하는데, 실제로는 파일이 존재하지 않을 수가 있다.

이럴 경우 Android 코드에서 파일이 존재하는 줄 알고 가져오기를 하면 에러가 발생한다.

이걸 방지하는 방법은 PHP 코드에서 파일이 폴더에 존재하는지 검사하여 없으면 공백을 반환하도록 하는 것이 좋다.


<?php
if(!isset($_SESSION)) {
    session_start();
}
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // POST 전송으로 전달받은 값 처리
    if(!(isset($idx) && !empty($idx))) {
        echo 0;
        exit;
    }
    require_once 'phpclass/dbconnect.php';
    require_once 'phpclass/loginClass.php';
    $c = new LoginClass();
    $sql = "select idx,userNM,mobileNO,telNO,photo from Person";
    if(!empty($search)) {
        $where = "userNM LIKE '%".$search."%' or mobileNO LIKE '%".$search."%'";
    } else {
        $where = "";
    }

    if(strlen($where) > 0){
        $sql .= " where ".$where;
    }

    $R = array(); // 결과 담을 변수 생성
    $result = $c->putDbArray($sql);
    while($row = $result->fetch_assoc()) {
        if($row['photo'] == NULL) {
            $row['photo'] = "";
        } else {
            $path = "./photos/".$row['photo'];
            if(!file_exists($path)) {
                $row['photo'] = "";
            }
        }
        array_push($R, $row);
    }
    echo json_encode(array('result'=>$R)); //배열-문자열등을 json형식의 '문자열'로 변환
}
?>

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

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

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

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

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

    // 여기에 함수를 작성하면 된다.

    public function putDbArray($sql) {
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        $result = $stmt->get_result();
        return $result;
    }

    public function SearchFiltering($str){
        // 해킹 공격을 대비하기 위한 코드
        $str = preg_replace("/[\s\t\'\;\"\=\-]+/","", $str); // 공백이나 탭 제거, 특수문자 제거
        return $str;
    }

}


MySQLi 객체지향 방식으로 Class를 작성해보니, 함수화하기가 쉽지 않다.
변수를 받아서 처리하는 경우가 쉽지 않다.
PDO 방식으로 하는 것은 변수처리까지 원활하게 할 수 있다.


블로그 이미지

Link2Me

,
728x90

원하는 Server-Side 언어가 PHP, JSP, node.js 에 따라서 설치하는 사항이 조금씩 다르다.

CentOS7 에서 Apache Web서버와 PHP 7.3 을 설치하는 방법이다.

 

# 설치 확인
rpm -qa | grep httpd
rpm -qa | grep php
rpm -qa | grep MariaDB
rpm -qa | mysql

 

# PHP 설치여부 확인
rpm -qa | grep php
yum list installed | grep php

 

## 설치된 것 한꺼번에 지우기 (잘못 설치한 경우)
rpm -qa | grep httpd >list
rpm -qa | grep php >list

# vi list로 해서 삭제 안할 리스트는 선별 제거한다.
sudo yum -y remove $(awk '' <list)

 

PHP 설치

#Step 1 – Prerequsitis
yum -y install epel-release

#Step 2 - Repository 설치
yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm


#Step 3 - yum Utilities Package 설치
yum -y install yum-utils

#Step 4 – Install PHP and PHP-FPM
# Disable repo for PHP 5.4
yum-config-manager --disable remi-php54
yum-config-manager --enable remi-php73

yum -y install --enablerepo=remi-php73 httpd mod_ssl php php-zip php-fpm php-common php-opcache php-curl
yum -y install --enablerepo=remi-php73 php-devel php-gd php-imap php-ldap php-mysql php-mysqlnd php-pdo
yum -y install --enablerepo=remi-php73 php-odbc php-pear php-xml php-xmlrpc php-pecl-apc php-mbstring   
yum -y install --enablerepo=remi-php73 php-mcrypt php-soap php-tidy curl curl-devel php-libwww-perl
yum -y install --enablerepo=remi-php73 ImageMagick libxml2 libmxl2-devel mod_fcgid php-cli httpd-devel    
yum -y install --enablerepo=remi-php73 php-intl php-pspell wget php-openssl unzip mc git     

# PHP 설치버전 확인
php -v

 

# 설치된 PHP Package 확인
rpm -qa | grep php

 

## 보안설정
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/html
sudo chmod -R 755 /var/www/html

 

#Step 4 httpd.conf 파일 수정

vi /etc/httpd/conf/httpd.conf
ServerName localhost

# 불필요한 Methods 차단 설정 : method 허용은 GET, POST 만 허용
# Directory 태그를 사용하면 Directory 별로 모두 제한을 걸어줘야 하므로
# URL pattern 을 이용하여 일괄적으로 제한하는 방법
<Location />
   <LimitExcept GET POST>
       Order deny,allow
       Deny from all
   </LimitExcept>
</Location>

<Directory "/var/www/html">
    Options +FollowSymLinks -Indexes
    AllowOverride All
    Require all granted
    <LimitExcept GET POST>
      Order deny,allow
      Deny from all
    </LimitExcept>
</Directory>

<IfModule dir_module>
    DirectoryIndex index.html index.php
</IfModule>

# 서버 정보 노출 방지
ServerTokens Prod
ServerSignature Off 

# Trace Method 처리
TraceEnable off

ErrorDocument 400 /error.html
ErrorDocument 401 /error.html
ErrorDocument 402 /error.html
ErrorDocument 403 /error.html
ErrorDocument 404 /error.html
ErrorDocument 405 /error.html
ErrorDocument 408 /error.html
ErrorDocument 500 /error.html
ErrorDocument 501 /error.html
ErrorDocument 502 /error.html
ErrorDocument 503 /error.html

저장하고 나온다.

# Apache HTTPOXY 취약점 방지
echo "RequestHeader unset Proxy early" >> /etc/httpd/conf/httpd.conf

 

# Step 5 : Configure PHP-FPM

vi /etc/php.ini
cgi.fix_pathinfo=0
short_open_tag = On
post_max_size = 60M  ;// 8M 으로 되어 있었음.
upload_max_filesize = 50M ;// 기본 2M 으로 되어 있었음

;PHP 버전정보 노출 방지
expose_php=Off

date.timezone ="Asia/Seoul"

;PHP에서 세션은 일단 생성 된 뒤에, 가비지 콜렉터 관리로직에 의해 소멸된다.
session.gc_probability = 1
session.gc_divisor = 1
;이값이 100이면 1/100 즉 1%의 확률로 가비지콜렉션이 실행된다.
;1이면 정확하게 유효기간이 넘은 데이타가 삭제될 것이다.
session.gc_maxlifetime = 3600

;세션과 쿠키를 동시에 사용
session.use_cookies = 1

;PHP 시작과 동시에 세션 사용(1), 사용하지 않음(0)
session.auto_start = 0

# 저장하고 나온다.
# 반드시 추가구현해야 문제없이 동작한다.
cd /var/www/html/
vi .htaccess
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{THE_REQUEST} !^(POST|GET)\ /.*\ HTTP/1\.1$
RewriteRule .* - [R=405,L]

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ error.html?/$1 [QSA,PT,L]
</IfModule>

 

### 시간 동기화
yum -y install rdate

crontab -e
00 00  * * * /usr/bin/rdate -s time.bora.net && /sbin/clock -w

#크론탭 재시작
service crond restart

 

#crontab 설치 여부 확인
ps -ef | grep cron

# 서비스 활성화
systemctl enable httpd

# 서비스 시작
systemctl start httpd

systemctl stop httpd
systemctl restart httpd
systemctl status httpd

 

위 설치 스크립트에서 추가 설정을 하면 PHP-FPM 설정이 가능하다. 그 내용은 생략한다.

이것으로 윈도우10 에서 CentOS7 을 Virtual Box로 설치하여 Web서버를 운영하는 방법에 대한 설명을 마친다.

블로그 이미지

Link2Me

,
728x90

CentOS7 환경에서 MariaDB 를 yum 설치하는 방법이다.

root 권한에서 설치하는 것이라서 복사하여 그대로 붙여넣기 편하게 만들었다.

 

################################
##### MariaDB 10.3 버전 설치 #####
################################
 
vi /etc/yum.repos.d/MariaDB.repo
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.3/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
 
 
# 위 사항을 그대로 붙여넣기하고 저장하고 나온다.
 
# MariaDB Sever & Client 설치
yum -y install MariaDB-server MariaDB-client
 
# 서버 부팅시 자동 실행 설정
systemctl enable mariadb
 
# mariadb 시작 (둘 중 하나 실행)
systemctl start mariadb
service mariadb start
 
# mariadb 상태 확인(둘 중 하나 실행)
systemctl status mariadb
service mariadb status
 
# Maria DB 보안 설정하기
mysql_secure_installation
 
아무것도 입력하지 않고 엔터
Root 비밀번호 설정 : Y 입력 후 엔터
Root 비밀번호 입력
Anonymous User 삭제 : Y 입력후 엔터
원격 root 로그인 불가 설정 : Y 입력후 엔터
Test DB 삭제 : Y 입력후 엔터
변경사항 반영 : Y 입력 후 엔터
 
# MariaDB 버전 확인
mysql -uroot -p
use mysql
select version();
 
# 서버 설정 파일 변경
vi /etc/my.cnf.d/server.cnf
[mysqld]
init_connect="SET collation_connection = utf8_general_ci"  
init_connect="SET NAMES utf8"  
character-set-server = utf8
collation-server = utf8_general_ci
 
# Dump 파일에도 동일한 Character Set 적용
vi /etc/my.cnf.d/mysql-clients.cnf
[mysql]
default-character-set=utf8
[mysqldump]
default-character-set=utf8
 
# MariaDB 실행
systemctl restart mariadb
systemctl status mariadb
mysql -u root -p
status
quit 

 

 

# mariadb 상태 확인

 

 

# Maria DB 보안 설정하기
mysql_secure_installation

 

# 서버 설정 파일 변경
vi /etc/my.cnf.d/server.cnf

 

 

# Dump 파일에도 동일한 Character Set 적용
vi /etc/my.cnf.d/mysql-clients.cnf

 

 

 

 

설정확인

mysql -uroot -p
패스워드 입력

 

show variables like 'c%';

 

 

 

Apache 와 PHP 를 설치 방법 https://link2me.tistory.com/1890 참조

블로그 이미지

Link2Me

,
728x90

CentOS7 설치가 끝났으면 이제 Network 환경 설정을 static으로 변경해보자.


준비물

1. kitty 프로그램(SSH 접속 프로그램)

2. IP TIME 공유기

    - 공유기 없이 직접 초고속 인터넷에 접속된 경우에는 불필요.

    - 공유기는 Private IP 주소를 Public IP 주소로 변환하는 장치(NAT:Network Address Translation)


kitty 프로그램 다운로드

https://www.fosshub.com/KiTTY.html 에서 portable 버전 파일을 다운로드 한다.



다운로드 받은 포터블 파일을 적당한 위치에 복사/이동해서 실행시킨다.



글꼴 크기를 적절하게 조정하기 위해 8번을 클릭해서 처리한다.




자동 접속을 위해 root 비밀번호를 설정한다.






먼저 ip addr 를 실행한다.



vi /etc/sysconfig/network-scripts/ifcfg-enp0s3 를 하고 내용을 확인하면

DHCP 로 설정된 것을 확인할 수 있다.

DHCP 라는 것은 접속할 때마다 IP주소가 변경될 수 있다는 의미다.

IP주소가 변경되지 않도록 하려면 static으로 변경해야 한다.

공유기 뒷단의 Private IP address 부분이 변경되지 않도록 하기 위함이지, 초고속 인터넷 IP주소가 변경되지 않도록 한다는 의미가 아니다.

IP TIME 공유기를 상시 물려두면 거의 Public IP address가 변경되지 않을 수도 있다.



아래와 같이 BOOTPROTO=none 으로 변경하고 IP주소, Gateway 추가, DNS 를 추가하고 저장한다.

IPADDR=192.168.1.20
PREFIX=24
GATEWAY=192.168.1.100
DNS1=168.126.63.1
DNS2=168.126.63.2


# network 재시작
systemctl restart network


네트워크를 고정(static) IP주소로 변경했다.


이제 외부에서도 홈페이지 접속 및 SSH 접속을 위한 IP TIME 공유기를 설정하자.

IP TIME 공유기 URL 은 위에서 자동으로 설정한 Gateway IP 주소다.

admin 과 비밀번호를 입력하고 나면 아래 화면이 보일 것이다. 관리도구를 클릭한다.

포트포워딩 설정을 해준다.

홈페이지 접속을 위해서 http가 사용하는 포트 80 인데, 외부에서 접속할 때에는 8080 으로 접속하도록 포트를 지정했다. 기존에 사용하던 리눅스 전용서버가 있기 때문에 윈도우10에 설치한 CentOS 기반 Web서버 접속은 이렇게 하도록 설정했다.

외부에서 SSH 접속을 위한 포트번호도 추가했다.



SSH에 사용하는 root 패스워드는 강력하게 15자리 이상으로 하는 게 좋다.

보통 패스워드를 10자리 또는 8자리로 설정하는데 15자리 이상으로 설정하면 패스워드 알아내는 건 거의 어렵다고 볼 수 있기 때문이다.


방화벽 설정

기본 방화벽 설정은 SSH, HTTP(80), HTTPS(443), MariaDB(3306) 만 추가해주면 된다.

# 방화벽 설치
yum -y install firewalld

# 방화벽 확인
firewall-cmd --list-all

# http/https 및 port 방화벽 오픈
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --permanent --zone=public --add-port=3306/tcp

# 방화벽 적용 및 확인
firewall-cmd --reload
firewall-cmd --list-all




MariaDB 설치 방법 : https://link2me.tistory.com/1889 참조

블로그 이미지

Link2Me

,
728x90

윈도우 10에 Virtual Box를 설치하고 CentOS7 설치를 위한 준비가 끝났으면 이제 CentOS7 을 설치해보자.

 

 

 

 

 

 

 

※ 어댑터에 브리지 : Windows10 PC가 1개의 IP주소를 가지고, VirtualBox 도 1개의 IP주소를 별도로 갖는 방식

NAT : Windows 10 이 1개의 IP주소를 가지고 그 IP주소에 별도의 사설 IP주소를 할당하여 사용

https://link2me.tistory.com/2061 를 참조하면 NAT 방식으로 사용할 시 설정 방법이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

재부팅을 하고 나면 재시작을 하고 나서 다음과 같이 로그인 창이 나온다.

 

 

 

yum -y update 를 해주면 아래와 같이 실행되면 네트워크가 정상동작된다는 걸 확인할 수 있다.

 

 

여기까지가 CentOS7 기본 설치를 마친 단계이다.

 

Windows 10 에 CentOS7 네트워크 설정 - 4 https://link2me.tistory.com/1888

 

블로그 이미지

Link2Me

,
728x90

Virual Box 실행화면에서 몇가지 기본적인 사항을 설정하고 나서 CentOS7 설치를 위한 준비 단계를 살펴보자.


환경설정 → 일반 → 가상머신 폴더를 지정한다. 즉 CentOS7 설치할 폴더를 지정한다.


호스트 키 조합을 설정하는 것은 윈도우와 리눅스에서 마우스를 전환하기 위한 키라고 보면 된다.



이제 CentOS7 설치를 해보자.







공간 크기는 16GB로 설정했다. 8GB를 자동으로 잡아주더라.



여기까지 하고 나면 CentOS7 설치 파일을 지정하고 설치를 시작할 준비단계를 마친 것이다.


Windows 10 에 CentOS7 설치 하기 - 3 https://link2me.tistory.com/1887

블로그 이미지

Link2Me

,
728x90

윈도우10이 설치된 PC/노트북에 CentOS7 을 설치하는 방법이다.


준비물

1. CentOS7 ISO 파일

2. Virtual Box 파일

3. 설치공간 16GB : default 8GB 로 설정해도 된다.

4. IP TIME 공유기 : 사설 IP 여러개를 사용하면서 공인(Public) IP 로 변환하는 장치

    공유기가 있으면 Port Forwarding 기능을 이용하여 외부 접속이 가능하다.

    https://link2me.tistory.com/1449 참조



CentOS7 설치파일 다운로드 안내

1. CentOS7 ISO 파일 : https://www.centos.org/download/ 에서 파일을 다운로드 한다.

2번을 누르면 http://mirror.kakao.com/centos/7.8.2003/isos/x86_64/ 와 같은 파일 다운로드 경로로 변경된다.

토렌토 파일을 받아서 토렌토를 이용하여 파일 다운로드를 하거나 직접 다운로드를 한다.


다운받은 파일을 적당한 폴더에 이동시킨다.


Virtual Box 다운로드 및 설치

1. https://www.virtualbox.org/wiki/Downloads 에서 파일을 다운로드 한다.



2. 다운로드 받은 파일을 실행한다.


3. Virtual Box 설치 과정



적당한 폴더를 선택하고 설치해도 되고 ProgramFiles 에 default 설치를 해도 된다.






여기까지 하면 Virtual Box 설치는 완료되었다.


설치후 Virtual Box 실행화면



Windows 10 에 CentOS7 설치 환경 만들기 - 2  https://link2me.tistory.com/1886

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

Windows 10 에 CentOS7 설치 하기 - 3  (0) 2020.10.03
Windows 10 에 CentOS7 설치 환경 만들기 - 2  (0) 2020.10.03
CentOS7 SSH 포트 변경  (0) 2020.09.28
CentOS7 PHP-FPM yum 설치  (0) 2020.09.16
MariaDB 깨졌을 때 복구 및 백업  (0) 2020.09.02
블로그 이미지

Link2Me

,
728x90

LinearLayout 동적 생성 코드이다.


import android.graphics.Color;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LinearLayout myLinearLayout = new LinearLayout(this);
        myLinearLayout.setOrientation(LinearLayout.VERTICAL);

        LinearLayout.LayoutParams mParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);

        final EditText editText = new EditText(this);
        editText.setHint("여기에 입력하세요");
        mParams.setMargins(0,50,0,10);
        editText.setLayoutParams(mParams);
        myLinearLayout.addView(editText);

        DisplayMetrics dm = getResources().getDisplayMetrics();
        int size = Math.round(20 * dm.density);

        Button btn = new Button(this);
        btn.setText("버튼");
        btn.setBackgroundColor(Color.YELLOW);
        mParams.topMargin = size;
        btn.setLayoutParams(mParams);
        myLinearLayout.addView(btn);

        LinearLayout.LayoutParams tv_params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);

        final TextView textView = new TextView(this);
        textView.setText("텍스트뷰입니다.");
        textView.setTextColor(Color.MAGENTA);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,18);
        tv_params.gravity = Gravity.CENTER;
        tv_params.setMargins(0,50,0,0);
        textView.setLayoutParams(tv_params);
        myLinearLayout.addView(textView);

        setContentView(myLinearLayout);

        btn.setOnClickListener(view -> textView.setText(editText.getText().toString()));
    }

}


결과 화면


 


코드파일 및 결과 이미지

dynamicLayout.zip


'안드로이드 > Layout' 카테고리의 다른 글

하단 TabLayout + ViewPager Layout 예제  (0) 2020.11.01
LinearLayout 예제  (0) 2020.10.13
SMS authentication with OTP Layout Example  (0) 2020.09.14
EditText DatePicker  (0) 2020.08.18
Floating Action Button  (0) 2020.07.11
블로그 이미지

Link2Me

,