관리 메뉴

SIMPLE & UNIQUE

2회차_1강 : AWS RDS mysql 서버 연결 후, 데이터를 웹으로 가져온다. 본문

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

2회차_1강 : AWS RDS mysql 서버 연결 후, 데이터를 웹으로 가져온다.

착한코딩 2020. 1. 5. 23:02

2_1 목표 : 이미 테이블이 세팅되어 있는 mysql 서버에서 데이터를 select 하고, react 페이지에 리스트로 노출시킨다.

 

다음 2회차 2강에서 각자 AWS 계정을 만들고 RDS mysql 서버를 프리티어(1년무료) 버전으로 인스턴스를 생성한다. 그리고 테이블을 생성해 더미 데이터를 삽입할 것이다.

  2회차 1강에서는 이미 세팅이 완료된 작성자의 DB 서버에 접속해 실습한다.

 

 

App.js에서 1회차에서 SoftwareTool 관련 부분을 주석처리해 놨었는데, 아래와 같이 주석을 해제한다.

import React, { Component } from 'react';
import { Router, Route, Switch } from "react-router";

import Api_test from './Api_test'

// css
import '../css/new.css';
import '../css/owl.carousel.min.css';
import '../css/owl.theme.default.min.css';

// header
import HeaderAdmin from './Header/Header admin';

// footer
import Footer from './Footer/Footer';

// login
import LoginForm from './LoginForm';

// admin floatingPopulationList
import floatingPopulationList from './Floating_population/floatingPopulationList';

// admin softwareinfo
import AdminSoftwareList from './SoftwareToolsManage/AdminSoftwareList';
import AdminSoftwareView from './SoftwareToolsManage/AdminSoftwareView';

class App extends Component {
  constructor (props) {
    super(props);
    
    this.state = {
    }
}
  componentDidMount() {}
  render () {
    return (
      <div className="App">
          <HeaderAdmin/> 
          <Switch>
            {/* <Route exact path='/' component={Api_test} /> // root 경로일 경우 라우팅 */}
            <Route exact path='/' component={LoginForm} />
            <Route path='/Api_test' component={Api_test} />
            <Route path='/floatPopulationList' component={floatingPopulationList} />
            <Route path='/AdminSoftwareList' component={AdminSoftwareList} />
            <Route path='/AdminSoftwareView/:swtcode' component={AdminSoftwareView} />
          </Switch>
          <Footer 
            footer_address={this.props.footer_address} 
            footer_tel={this.props.footer_tel}  
            footer_email={this.props.footer_email} 
            footer_mobile={this.props.footer_mobile} 
          />
      </div>
    );
  }
}

App.defaultProps = {
  // footer value
  footer_address: '[34234] 서울특별시 강남구 삼성동 111-114',
  footer_tel: '02-1234-5678',
  footer_email: 'ljung5@naver.com',
  footer_mobile: '010-3288-3398',
};

export default App

 

 

1. SoftwareTools 파일을 세팅한다.

  react 경로 C:\Users\ljung\OneDrive\문서\taling0102\client\src\components에 SoftwareToolsManage폴더를 생성한다. 생성한 폴더안에 AdminSoftwareList.js와 AdminSoftwareView.js 파일을 만든다.

AdminSoftwareView에 아래 소스를 붙여넣는다.

import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import axios from "axios";

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

    // SW Tool 정보 호출
    callSwToolInfoApi = async () => {
        this.setState({ append_SwtoolInfo: this.SwToolInfoAppend() });
    }

    // SW Tool 정보 append
    SwToolInfoAppend = () => {
        let result = []
            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
    }

    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;

AdminSoftwareList 에 아래 소스를 붙여넣는다.

import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import axios from "axios";

class SoftwareList extends Component {
    constructor(props) {
        super(props);

        this.state = {
            responseSwtoolList: '',//swtool 리스트 response 변수
            append_SwtoolList: '', //swtool 리스트 append 변수
        }
    }

    componentDidMount() {
        // SW Tool 리스트 호출
        this.callSwToolListApi()
    }

    // SW Tool 리스트 호출
    callSwToolListApi = async () => {
        //SW Tool List 호출
        axios.post('/api/Swtool?type=list', {
        })
        .then( response => {
            try {
                this.setState({ responseSwtoolList: response });
                this.setState({ append_SwtoolList: this.SwToolListAppend() });
            } catch (error) {
                alert('작업중 오류가 발생하였습니다.');
            }
        })
        .catch( error => {alert('작업중 오류가 발생하였습니다.');return false;} );
    }

    // SW Tool 리스트 append
    SwToolListAppend = () => {
        let result = []
        var SwToolList = this.state.responseSwtoolList.data
        
        for(let i=0; i<SwToolList.json.length; i++){
            var data = SwToolList.json[i]

            var date = data.reg_date
            var year = date.substr(0,4)
            var month = date.substr(4,2)
            var day = date.substr(6,2)
            var reg_date = year +'.'+month+'.'+day

            result.push(
                <tr class="hidden_type">
                <td>{data.swt_toolname}</td>
                <td>{data.swt_function}</td>
                <td>{reg_date}</td>
                <td>
                    <Link to={'/AdminSoftwareView/'+data.swt_code} className="bt_c1 bt_c2 w50_b">수정</Link>
                    <a href="#n" class="bt_c1 w50_b" id={data.swt_code} toolname={data.swt_toolname} onClick={(e) => this.deleteSwtool(e)}>삭제</a>
                </td>
                </tr>
            )
        }
        return result
    }

    render () {
        return (
            <section class="sub_wrap" >
                <article class="s_cnt mp_pro_li ct1 mp_pro_li_admin">
                    <div class="li_top">
                        <h2 class="s_tit1">Software Tools 목록</h2>
                        <div class="li_top_sch af">
                        <Link to={'/AdminSoftwareView/register'} className="sch_bt2 wi_au">Tool 등록</Link>
                        </div>
                    </div>

                    <div class="list_cont list_cont_admin">
                        <table class="table_ty1 ad_tlist">
                            <tr>
                                <th>툴 이름</th>
                                <th>기능</th>
                                <th>등록일</th>
                                <th>기능</th>
                            </tr>
                        </table>	
                        <table class="table_ty2 ad_tlist">
                            {this.state.append_SwtoolList}
                        </table>
                    </div>
                </article>
            </section>
        );
    }
}

export default SoftwareList;

이때 axios.post('/api/Swtool?type=list' 에서 node api를 호출하는데, node코드에 관련 라우터가 없어서 오류가 발생할 것이다. 

 

2. node 코드를 수정하기전에 필요한 패키지들을 다운받는다.

cmd 또는 터미널을 열고 node경로 C:\Users\ljung\OneDrive\문서\taling0102에서 아래 명령어로 패키지들을 설치해 준다. mysql을 사용할 것이고, node 코드와 sql코드를 분리하기 위해 mybatis를 사용할 것이다. body-parser는 http 통신으로 request, response 데이터를 주고 받을 때, body에 있는 데이터를 파싱하기 위해 사용된다. 

//node 경로로 이동
CD C:\Users\ljung\OneDrive\문서\taling0102

npm install --save mysql
npm install --save mybatis-mapper
npm install --save body-parser

## 참고 ##

  mybatis는 자바 퍼시스턴트 프레임워크이다. persistent란 사용한 프로그램이 종료되어도 데이터는 남는다는 의미로, 쉽게 말해서 데이터베이스를 사용한다는 것이다. 

  자바에서 ORM(Object Relational Mapping)중 가장 많이 사용한다. 사용하는 가장 큰 이유는 비즈니스 소스와 sql 소스를 분리해서 개발할 수 있다는 점이다. 

  node도 마찬가지로 node 코드 사이에 sql을 깨워서 개발하는 방법보다는, 따로 분리시켜 개발 편의성과 유지보수 효율성을 높이기 위해 사용했다.

 

3. node 라우터, 모듈, 모델, db connection 관련 소스를 세팅한다.

node경로 C:\Users\ljung\OneDrive\문서\taling0102 에서 server.js 파일을 열고 아래 소스를 붙여넣는다.

swtool관련 호출을 라우팅하는 코드가 추가되었다.

var express = require('express');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var swtoolRouter = require("./routes/SwtoolRout");

var app = express();

app.use('/', indexRouter);
app.use('/users', usersRouter);

//sw Tool 조회
app.use("/api/Swtool", swtoolRouter);

const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));

node경로 C:\Users\ljung\OneDrive\문서\taling0102\routes 에 SwtoolRout.js 파일을 추가한다.

SwtoolRout.js 파일에 아래 소스를 붙여넣는다. 

var express = require('express');

var router = express.Router();
const bodyParser = require('body-parser');

router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));

var usersModule = require('../modules/SwtoolModule');

//post 호출
router.post('/', (req, res, next) => {
    router.use('/', usersModule);
    next('route')
});

module.exports = router;

node경로 C:\Users\ljung\OneDrive\문서\modules 에 SwtoolModule.js와 dbconnect_Module.js 파일을 추가한다.

SwtoolModule.js 파일에 아래 소스를 붙여넣는다. 

var express = require('express');
var router = express.Router();
const bodyParser = require('body-parser');

router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));

router.post('/', (req, res, next) => {
  var m_typ = req.query.type;
  if(m_typ == 'list'){
    //Swtool 리스트 조회
    try {
      // Mysql Api 모듈(CRUD)
      var dbconnect_Module = require('./dbconnect_Module');
  
      //Mysql 쿼리 호출정보 입력
      req.body.mapper = 'SwToolsMapper';//mybatis xml 파일명
      req.body.crud = 'select';//select, insert, update, delete 중에 입력
      req.body.mapper_id = 'selectSwToolsList';
      
      router.use('/', dbconnect_Module);
      next('route')
    } catch (error) {
      console.log("Module > dbconnect error : "+ error);      
    }
  }
});

module.exports = router;

dbconnect_Module.js 파일에 아래 소스를 붙여넣는다. 

## 참고 ##

  아래 코드에서 createPool()함수는 node서버와 mysql 서버 사이에 connection pool을 생성한 것이다. cp가 없다면 사용자마다 실행하는 쿼리마다 1개씩 커넥션이 생기고, 데이터 호출 후에 연결이 끊긴다. cp는 생성된 연결을 끊지 않고 유지시켜, 연결에 소모되는 자원을 줄일 수 있게 해준다. 

  connectionLimit : 66은 사용하고 있는 rds 서버 max connection수(동시접속허용수)가 66개 이기 때문에, 최대값으로 세팅했다. 

  서버가 켜지자마자 66개의 연결이 생기는 건 아니고, 커넥션이 생길 때마다 1개씩 증가해 그때까지 생성된 연결을 유지한다.  

var express = require("express");
var router = express.Router();
const mysql = require("mysql");
const bodyParser = require("body-parser");

router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));

// Connection Pool 세팅
const pool  = mysql.createPool({
  connectionLimit: 66,
  waitForConnections: true,
  host: "비밀",
  port: "3306",
  database: 'rtrod',
  user: "rtrod_user",
  password: "비밀",
});

// pool 동작 확인용 로깅 - 획득
pool.on('acquire', function (connection) {
  console.log('Connection %d acquired', connection.threadId);
});
// pool 동작 확인용 로깅 - 연결
pool.on('connection', function (connection) {
  console.log('SET SESSION auto_increment_increment=1');
});
// pool 동작 확인용 로깅 - 대기
pool.on('enqueue', function () {
  console.log('Waiting for available connection slot');
});
// pool 동작 확인용 로깅 - 해제
pool.on('release', function (connection) {
  console.log('Connection %d released', connection.threadId);
});

router.post("/", (req, res) => {
  const mybatisMapper = require("mybatis-mapper");

  var param = req.body;
  var m_typ = req.query.type;
  mybatisMapper.createMapper(["./models/" + param.mapper + ".xml"]);
  var time1 = new Date();
  console.log("## " + time1 + " ##");
  console.log("\n Called Mapper Name  = " + param.mapper);
  
  //질의문 형식
  var format = { language: "sql", indent: "  " };
  var query = mybatisMapper.getStatement(
    param.mapper,
    param.mapper_id,
    param,
    format
    );
    console.log("\n========= Node Mybatis Query Log Start =========");
    console.log(
      "* mapper namespce : " + param.mapper + "." + param.mapper_id + " *\n"
      );
      console.log(query + "\n");
      
  try {
    pool.getConnection(function(err,connection){
      if (err) throw err;
      connection.query(query, function(error, results) {
      //조회
      var time2 = new Date();
      console.log("## " + time2 + " ##");
      console.log("## RESULT DATA LIST ## : \n", results);
      string = JSON.stringify(results);
      var json = JSON.parse(string);
      
      console.log("========= Node Mybatis Query Log End =========\n");
      // 조회
      try {
        if (req.body.crud == "select") {
          //로그인 정보 확인
          if (param.mapper_id == "selectLoginCheck" && m_typ != "modinfo") {
            if (json[0] == undefined) {
              res.send(null);
            } else {
              bcrypt.compare(req.body.is_Password, json[0].userpassword, function(
                err,
                login_flag
              ) {
                if (login_flag == true) {
                  res.send({ json });
                } else {
                  res.send(null);
                }
              });
            }
          } else {
            res.send({ json });
          }
          //삽입
        } else if (req.body.crud == "insert") {
          res.send("succ");
          //수정
        } else if (req.body.crud == "update") {
          res.send("succ");
          //삭제
        } else if (req.body.crud == "delete") {
          res.send("succ");
        } else {
        }
      } catch (error) {
        // res.send("error");
      }
      connection.release();
  
      if (error) {
        console.log("db error************* : " + error);
        throw error
      }
    });
    })

  } catch (error) {
    console.log("pool error : "+error);
  }
});


module.exports = router;

node경로 C:\Users\ljung\OneDrive\문서\taling0102\models 에 SwToolsMapper.xml 파일을 추가한다.

SwToolsMapper.xml 파일에 아래 소스를 붙여넣는다. 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="SwToolsMapper">  
  <select id="selectSwToolsList">
      SELECT 
        swt_code
        , swt_toolname
        , swt_function
        , swt_imagepath
        , swt_big_imgpath
        , swt_comments
        , swt_demo_site
        , swt_manual_path
        , swt_github_url
        , reg_date
      FROM rtrod.rtrod_swtool
      ORDER BY update_date DESC
      <if test="startRow != null && startRow != ''">
        limit ${startRow}, ${endRow}
      </if>
  </select>
</mapper>

 yarn dev 명령어로 서버를 다시 시작하고, 헤더에서 software tools 관리를 선택하면 아래와 같은 화면이 나온다.

이때, cmd 또는 터미널에 관련 쿼리와 결과 리스트가 출력된다.

리스트페이지에서 Tool 등록 버튼을 누르면, 아래와 같이 등록페이지가 노출된다.

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

 

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

# 수업의 최종목표 1. Font-end(react) <> back-end(node) <> mysql 구조를 프레임워크화 한다. 2. select, update, delete, insert를 각각 1세트씩 구현한다.(이 세트를 확장해서 하고 싶은 것을 자유롭게 만드시면 됩니다.) 3. AWS EC2 서버에 접근하여 파일을 컨트롤 할 수 있다. 4. AWS RDS(mysql) 인스턴스를 생성하고, 필요한 테이블들을 관리할 수 있다. CRUD 이외에 일반적인

taling.me

 

Comments