관리 메뉴

SIMPLE & UNIQUE

4회차_2강 : EC2 서버에 소스파일을 전송 후, 클라우드 서버를 구동한다. 본문

탈잉 강의 자료/react.js(프론트) + node.js(백앤드) 개발에서 배포까지

4회차_2강 : EC2 서버에 소스파일을 전송 후, 클라우드 서버를 구동한다.

착한코딩 2020. 1. 27. 01:24

4_2 목표 : 서버경로에 node를 설치하고, SFTP로 소스파일을 전송해 서버를 구동한다. 그리고 외부 접속 테스트를 한다.

 

1. ec2 서버에 업로드할 소스파일을 tar 파일로 압축한다.

  /node_modules, /client/node_modules 폴더를 제외한 파일들을 새폴더에 복사하고, tar 파일로 압축한다.

2. Sftp를 이용해 tar 파일을 전송한다.

  mobaXterm을 열어 Sftp를 연결하고, 소스파일들을 위치시킬 폴더를 생성한다.

새로 생성한 파일에 tar 파일을 드래그해 이동시킨다.  

3. SSH로 서버 터미널에 접속해 tar파일이 있는 경로로 진입해 압축을 푼다.

  터미널에 접속되면 아래 명령어들을 순서대로 실행한다.

// root 권한으로 전환
sudo su -
// tar 파일 압축을 풀 위치로 이동
cd /home/ubuntu/taling_200127
// tar 파일 압축해제
tar -xvf tarling_200127.tar

4. node.js와 npm을 설치한다.

  아래 명령어로 ubuntu 서버에 node.js와 npm을 설치한다.

// apt-get 업데이트
sudo apt-get update
// node.js 설치
sudo apt-get install -y nodejs
// npm 설치
apt install npm 

 

5. node 경로로 진입해,  package.json 파일에 작성되어 있는 패키지들을 설치한다.

  node 경로에서 아래 순서대로 명령어를 실행한다.

// node 경로로 접근
cd /home/ubuntu/taling_200127/taling0127
// pakage.json 파일에 작성된 패키지 리스트 설치
npm install

 

Sftp 창을 새로고침해보면 node 경로에, node_modules 폴더가 생성된 것을 확인할 수 있다.

 

6. react 경로(/client)로 진입해, package.json 파일에 작성되어 있는 패키지들을 설치한다.

  react경로에서 아래 순서대로 명령어를 실행한다.

// react 경로로 접근
cd /home/ubuntu/taling_200127/taling0127/client
// yarn 설치
npm install –g yarn
// pakage.json 파일에 작성된 패키지 리스트 설치
yarn install

//서버시간 변경
sudo rm /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime
//서버시간 확인
date

yarn install로 설치되지 않는다면, npm install을 한다. 이것도 안되면, 아래 명령어로 npm을 최신화한 다음 npm install을 실행한다.

npm install -g npm@latest
sudo npm install

 

Sftp 창을 새로고침해보면 react 경로에, node_modules 폴더가 생성된 것을 확인할 수 있다.

7. 로컬에서와 동일하게 yarn dev 명령어로 react, node 서버를 동시에 구동한다.

  먼저 react 경로의 package.json에서 로컬주소로 되어있는 proxy IP를 EC2서버 IP로 바꿔준다.

// react 경로로 접근
cd /home/ubuntu/taling_200127/taling0127/client
// proxy ip 수정을 위해, vi 명령어로 에디터를 실행한다.
vi package.json

아래와 같이 에디터가 실행되면 i > enter 로 수정모드로 변경하고

커서를 이동해 맨 아래 proxy 부분을 아래와 같이 수정한다.

esc > :wq 로 수정된 사항을 저장한다.

 

node 경로에서 yarn dev 명령어로 서버를 구동한다.

Software tools 목록에 진입하고, 로컬서버와 동일하게 아래와 같은 페이지가 노출되는지 확인한다.

 

8. Tool 등록/수정페이지에서 [저장] 버튼을 눌렀을 때, 리스트 페이지로 redirect 되는 부분을 수정한다.

  AdminSoftwareView.js 파일을 보면 저장이 성공적으로 완료되면,

로컬 주소인 http://localhost:3000/AdminSoftwareList로 페이지 이동을 시킨다.

                if(body == "succ"){
                    if(type == 'save'){
                        this.sweetalertSucc('Software Tools 등록이 완료되었습니다.', false)
                    }else if(type == "modify"){
                        this.sweetalertSucc('Software Tools 수정이 완료되었습니다.', false)
                    }
                    // 저장 후 리스트페이지로 이동
                    var thiss=this
                    setTimeout(function() {
                        // datasource 정보 호출
                        window.location.href = 'http://localhost:3000/AdminSoftwareList';
                        }.bind(this),1500
                    );
                }

EC2 서버에서는 IP주소가 로컬과 다르기 때문에 아래 코드처럼 수정해줘야 한다.

## 참고 ##

  Route는 컴포넌트에 기본적으로 match, history, location 이라는 것을 넘겨준다. 이때 histroy.push(‘/react 경로’) 함수에 react 경로를 넣어주면 해당 url로 새로고침 없이 이동시켜준다.

 

                if(body == "succ"){
                    if(type == 'save'){
                        this.sweetalertSucc('Software Tools 등록이 완료되었습니다.', false)
                    }else if(type == "modify"){
                        this.sweetalertSucc('Software Tools 수정이 완료되었습니다.', false)
                    }
                    // 저장 후 리스트페이지로 이동
                    setTimeout(function() {
                        // datasource 정보 호출
                        this.props.history.push('/AdminSoftwareList');
                        }.bind(this),1500
                    );
                }

 AdminSoftwareView.js 전체 코드

더보기
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import axios from "axios";
import $ from 'jquery';
import Swal from 'sweetalert2'

class SoftwareView extends Component {
    constructor(props) {
        super(props);
        this.state = {
            responseSwtoolInfo: '',//swtool 정보 response 변수
            append_SwtoolInfo: '', //swtool 정보 append 변수

            before_swtcode: props.match.params.swtcode, //swtool 정보 swtool 코드
            swtcode: '', //swtool 저장 swtool 코드
            swt_toolname: '', //swtool 정보 sw툴 명
            swt_demo_site: '',//swtool 정보 데모사이트
            swt_github_url: '',//swtool 정보 깃허브 주소
            swt_comments: '',//swtool 정보 설명
            swt_function: '',//swtool 정보 기능

            //파일, 이미지 업로드
            file: '',//메인 이미지 미리보기용 path
            file2: '',//라벨 이미지 미리보기용 path
            fileName: '',//메인 이미지명
            fileName2: '',//라벨 이미지명
            menualName: '',//메뉴얼명
            selectedFile: null, //업로드 대상 파일
        }
    }
    componentDidMount () {
        // SW Tool 정보 호출
        this.callSwToolInfoApi()
        if(this.state.before_swtcode == 'register'){
            $('.modifyclass').hide()
        }else{
            $('.saveclass').hide()
        }
    }

    //업로드할 파일 세팅
    handleFileInput(type, e){
        var id = e.target.id
        if(type =='file'){
            $('#imagefile').val(e.target.files[0].name)
        }else if(type =='file2'){
            $('#imagefile2').val(e.target.files[0].name)
        }else if(type =='manual'){
            $('#manualfile').val(e.target.files[0].name)
        }
        this.setState({
          selectedFile : e.target.files[0],
        })

        if(type =='manual'){
            setTimeout(function() {
                this.handlePostMenual(type, id ,e)
            }.bind(this),1
            );
        }else{
            setTimeout(function() {
                this.handlePostImage(type, id ,e)
            }.bind(this),1
            );
        }
    }

    //메뉴얼 업로드
    handlePostMenual(type, id, e){
        const formData = new FormData();
        formData.append('file', this.state.selectedFile);
    
        return axios.post("/api/upload?type=uploads/swmanual/", formData).then(res => {
            try {

                this.state.menualName = res.data.filename
                
                $('#upload_menual').prepend('<input id="is_MenualName" type="hidden" name="is_MenualName" value="'+this.state.menualName+'"}/>')
            } catch (error) {
                alert('작업중 오류가 발생하였습니다.', error, 'error', '닫기')
            }
        }).catch(error => {
            alert('작업중 오류가 발생하였습니다.', error, 'error', '닫기')
        })
    }    

    //이미지 업로드
    handlePostImage(type, id, e){
        const formData = new FormData();
        formData.append('file', this.state.selectedFile);
    
        return axios.post("/api/upload?type=uploads/image/", formData).then(res => {
            try {
                setTimeout(function() {
                    if(type =='file'){
                        this.state.file = '/image/'+res.data.filename
                        this.state.fileName = res.data.filename
                        $('#uploadimg').show()
                        $('#is_MainImg').remove()
                        $('#uploadimg').remove()
                        $('#upload_img').prepend('<img id="uploadimg" src="'+this.state.file+'"/>')
                        $('#upload_img').prepend('<input id="is_MainImg" type="hidden" name="is_MainImg" value="'+this.state.fileName+'"}/>')
                        
                    }else if(type =='file2'){
                        this.state.file2 = '/image/'+res.data.filename
                        this.state.fileName2 = res.data.filename
                        $('#uploadimg2').show()
                        $('#is_LabelImg').remove()
                        $('#uploadimg2').remove()
                        $('#upload_img2').prepend('<img id="uploadimg2" src="'+this.state.file2+'"/>')
                        $('#upload_img2').prepend('<input id="is_LabelImg" type="hidden" name="is_LabelImg" value="'+this.state.fileName2+'"}/>')
                    }
                }.bind(this),1000
                );

            } catch (error) {
                alert('작업중 오류가 발생하였습니다.')            
            }
        }).catch(error => {
            alert('작업중 오류가 발생하였습니다.')            
        })
    }


    // SW Tool 정보 호출
    callSwToolInfoApi = async () => {
        //SW Tool List 호출
        axios.post('/api/Swtool?type=info', {
            is_Swtcode: this.state.before_swtcode,
        })
        .then( response => {
            try {
                this.setState({ responseSwtoolInfo: response });
                this.setState({ append_SwtoolInfo: this.SwToolInfoAppend() });

                $('#is_Swt_toolname').val(this.state.swt_toolname)
                $('#is_Swt_demo_site').val(this.state.swt_demo_site)
                $('#is_Giturl').val(this.state.swt_github_url)
                $('#is_Comments').val(this.state.swt_comments)
                $('#is_Swt_function').val(this.state.swt_function)
                
                $('#upload_img').prepend('<img id="uploadimg" src="'+this.state.file+'"/>')
                $('#upload_img').prepend('<input id="is_MainImg" type="hidden" name="is_MainImg" value="'+this.state.fileName+'"}/>')
                
                $('#upload_img2').prepend('<img id="uploadimg2" src="'+this.state.file2+'"/>')
                $('#upload_img2').prepend('<input id="is_LabelImg" type="hidden" name="is_LabelImg" value="'+this.state.fileName2+'"}/>')
                
                $('#imagefile').val(this.state.fileName)
                $('#imagefile2').val(this.state.fileName2)
                $('#manualfile').val(this.state.menualName)

                if($('#uploadimg').attr('src').indexOf("null") > -1){
                    $('#uploadimg').hide()
                }
                if($('#uploadimg2').attr('src').indexOf("null") > -1){
                    $('#uploadimg2').hide()
                }
            } catch (error) {
                alert('작업중 오류가 발생하였습니다.')
            }
        })
        .catch( error => {alert('작업중 오류가 발생하였습니다.');return false;} );
    }

    // SW Tool 정보 append
    SwToolInfoAppend = () => {
        let result = []
        var SwToolInfo = this.state.responseSwtoolInfo.data
        
        if(this.state.before_swtcode != 'register'){
            var data = SwToolInfo.json[0]

            this.state.swt_toolname = data.swt_toolname
            this.state.swt_demo_site = data.swt_demo_site
            this.state.swt_github_url = data.swt_github_url
            this.state.swt_comments = data.swt_comments
            this.state.swt_function = data.swt_function

            this.state.file = '/image/'+data.swt_big_imgpath
            this.state.fileName = data.swt_big_imgpath
            $('#imagefile').val(data.swt_big_imgpath)
            
            this.state.file2 = '/image/'+data.swt_imagepath
            this.state.fileName2 = data.swt_imagepath
            $('#imagefile2').val(data.swt_imagepath)
                
            this.state.menualName = data.swt_manual_path
        }
            result.push(
                <table class="table_ty1">
                    <tr>
                        <th>
                            <label for="is_Swt_toolname">툴 이름<span class="red">(*)</span></label>
                        </th>
                        <td>
                            <input type="text" name="is_Swt_toolname" id="is_Swt_toolname" class="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                            <label for="is_Swt_demo_site">데모 URL<span class="red">(*)</span></label>
                        </th>
                        <td>
                            <input type="text" name="is_Swt_demo_site" id="is_Swt_demo_site" class="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                            <label for="is_Giturl">Github URL<span class="red">(*)</span></label>
                        </th>
                        <td>
                            <input type="text" name="is_Giturl" id="is_Giturl" class="" />
                        </td>
                    </tr>
                    <tr>
                        <th>
                            <label for="is_Comments">설명<span class="red">(*)</span></label>
                        </th>
                        <td>
                            <textarea name="is_Comments" id="is_Comments" rows="" cols=""></textarea>
                        </td>
                    </tr>
                    <tr class="div_tb_tr fileb">
                        <th>
                            메뉴얼 파일 #1
                        </th>
                        <td class="fileBox fileBox_w1">
                            <label for="uploadBtn1" class="btn_file">파일선택</label>
                            <input type="text" id="manualfile" class="fileName fileName1" readonly="readonly" placeholder="선택된 파일 없음"/>
                            <input type="file" id="uploadBtn1" class="uploadBtn uploadBtn1" onChange={e => this.handleFileInput('manual',e)}/>	
                            <div id="upload_menual">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            메인 이미지
                        </th>
                        <td className="fileBox fileBox1">
                            <label htmlFor='imageSelect' className="btn_file">파일선택</label>
                            <input type="text" id="imagefile" className="fileName fileName1" readOnly="readonly" placeholder="선택된 파일 없음"/>
                            <input type="file" id="imageSelect" className="uploadBtn uploadBtn1" onChange={e => this.handleFileInput('file',e)}/>
                            <div id="upload_img">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            라벨 이미지
                        </th>
                        <td className="fileBox fileBox2">
                            <label htmlFor='imageSelect2' className="btn_file">파일선택</label>
                            <input type="text" id="imagefile2" className="fileName fileName1" readOnly="readonly" placeholder="선택된 파일 없음"/>
                            <input type="file" id="imageSelect2" className="uploadBtn uploadBtn1" onChange={e => this.handleFileInput('file2',e)}/>
                            <div id="upload_img2">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            <label for="is_Swt_function">상세 기능<span class="red">(*)</span></label>
                        </th>
                        <td>
                            <textarea name="is_Swt_function" id="is_Swt_function" rows="" cols=""></textarea>
                        </td>
                    </tr>
                </table>
            )
        return result
    }

    // 저장 버튼 클릭시 validate check
    submitClick = async (type, e) => {

        this.Swt_toolname_checker = $('#is_Swt_toolname').val();
        this.Swt_demo_site_checker = $('#is_Swt_demo_site').val();
        this.Giturl_checker = $('#is_Giturl').val();
        this.Comments_checker = $('#is_Comments').val();
        this.Swt_function_checker = $('#is_Swt_function').val();

        this.fnValidate = (e) => {

            // ## Swt_toolname check start 
            if(this.Swt_toolname_checker === '') {
                $('#is_Swt_toolname').addClass('border_validate_err');
                alert('툴 이름을 다시 확인해주세요.')
                return false;
            }
            $('#is_Swt_toolname').removeClass('border_validate_err');

            // ## Swt_demo_site check start 
            if(this.Swt_demo_site_checker === '') {
                $('#is_Swt_demo_site').addClass('border_validate_err');
                alert('데모 URL을 다시 확인해주세요.')
                return false;
            }
            $('#is_Swt_demo_site').removeClass('border_validate_err');

            // ## Giturl check start 
            if(this.Giturl_checker === '') {
                $('#is_Giturl').addClass('border_validate_err');
                alert('Github URL을 다시 확인해주세요.')
                return false;
            }
            $('#is_Giturl').removeClass('border_validate_err');

            // ## Comments check start 
            if(this.Comments_checker === '') {
                $('#is_Comments').addClass('border_validate_err');
                alert('설명을 다시 확인해주세요.')
                return false;
            }
            $('#is_Comments').removeClass('border_validate_err');

            // ## Swt_function check start 
            if(this.Swt_function_checker === '') {
                $('#is_Swt_function').addClass('border_validate_err');
                alert('상세기능을 다시 확인해주세요.')
                return false;
            }
            $('#is_Swt_function').removeClass('border_validate_err');

            var date = new Date()
            var y_str = date.getFullYear().toString();

            var month = date.getMonth()+1
            var m_str = month.toString();

            var day = date.getDate()
            var d_str = day.toString();

            var hour = date.getHours()
            var min = date.getMinutes()
            var sec = date.getSeconds()

            // 프로젝트 코드생성
            this.state.swtcode = 'USW'+y_str+m_str+d_str+hour+min+sec
            
            $('#is_Swtcode').val(this.state.swtcode)

            return true;
        }

        //유효성 체크
        if(this.fnValidate()){
            //software Tools 저장
            //form type To Json
            var jsonstr = $("form[name='frm']").serialize();
            //특수문자 깨짐 해결
            jsonstr = decodeURIComponent(jsonstr);
            var Json_form = JSON.stringify(jsonstr).replace(/\"/gi,'')
            Json_form = "{\"" +Json_form.replace(/\&/g,'\",\"').replace(/=/gi,'\":"')+"\"}";
        
            try {
                const response = await fetch('/api/Swtool?type='+type, {
                    method: 'POST',
                    headers: {
                    'Content-Type': 'application/json',
                    },
                    //한글 디코딩
                    body: Json_form,
                });
                const body = await response.text();
                if(body == "succ"){
                    if(type == 'save'){
                        this.sweetalertSucc('Software Tools 등록이 완료되었습니다.', false)
                    }else if(type == "modify"){
                        this.sweetalertSucc('Software Tools 수정이 완료되었습니다.', false)
                    }
                    // 저장 후 리스트페이지로 이동
                    setTimeout(function() {
                        this.props.history.push('/AdminSoftwareList');
                        }.bind(this),1500
                    );
                }else{
                    alert('작업중 오류가 발생하였습니다.')
                }  
            } catch (error) {
                alert('작업중 오류가 발생하였습니다.')
            }
        }//fnValidate end
    };

    //alert 기본 함수
    sweetalert = (title, contents, icon, confirmButtonText) => {
        Swal.fire({
            title: title,
            text: contents,
            icon: icon,
            confirmButtonText: confirmButtonText
            })
    }

    //alert 성공 함수
    sweetalertSucc = (title, showConfirmButton) => {
        Swal.fire({
            position: 'bottom-end',
            icon: 'success',
            title: title,
            showConfirmButton: showConfirmButton,
            timer: 1000
        })
    }

    render () {
        return (
            <section class="sub_wrap">
                <article class="s_cnt mp_pro_li ct1">
                    <div class="li_top">
                        <h2 class="s_tit1">Software Tools 등록/수정</h2>
                    </div>
                    <div class="bo_w re1_wrap re1_wrap_writer">
                        <form name="frm" id="frm" action="" onsubmit="" method="post" >
                            <input id="is_Email" type="hidden" name="is_Email" value={this.state.admin_userid} />
                            <input id="is_Swtcode" type="hidden" name="is_Swtcode" value={this.state.swtcode} />
                            <input id="is_beforeSwtcode" type="hidden" name="is_beforeSwtcode" value={this.state.before_swtcode} />
                            
                            <article class="res_w">
                                <p class="ment" style={{"text-align": "right"}}>
                                    <span class="red">(*)</span>표시는 필수입력사항 입니다.
                                </p>
                                <div class="tb_outline">
                                    {this.state.append_SwtoolInfo}

                                    <div class="btn_confirm mt20" style={{"margin-bottom": "44px"}}>
                                        <Link to={'/AdminSoftwareList'} className="bt_ty bt_ty1 cancel_ty1">취소</Link>
                                        <a href="javascript:" className="bt_ty bt_ty2 submit_ty1 saveclass" onClick={(e) => this.submitClick('save', e)}>저장</a>
                                        <a href="javascript:" className="bt_ty bt_ty2 submit_ty1 modifyclass" onClick={(e) => this.submitClick('modify', e)}>수정</a>
                                    </div>

                                </div>
                            </article>
                        </form>	
                    </div> 
                </article>
            </section>
        );
    }
}

export default SoftwareView;

 

https://taling.me/Talent/Detail/19341

 

[2월/주말] REACT, NODE, MYSQL, AWS 개발부터 배포까지/ 따라하면 완성되는 웹프로젝트. | 탈잉

✔ 수업 목표 ⊙ Font-end(react), back-end(node), mysql 구조를 프레임워크화 한다. ⊙ SELECT, UPDATE, DELETE, INSERT를 각각 1세트씩 구현한다. (CRUD 세트를 참고해서 만들고 싶은 기능을 혼자 개발할 수 있어요) ⊙ AWS EC2 인스턴스를 생성하고, 서버에 접근하여 파일을 컨트롤 할 수 있다. ⊙ AWS RDS(mysql) 인스턴스를 생성하고, 필요한 테이블들을 관리할 수 있다. ✔ 제 수업만의

taling.me

 

Comments