관리 메뉴

SIMPLE & UNIQUE

3회차_1강 : 등록페이지를 구현한다. (text, 이미지, 파일) 본문

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

3회차_1강 : 등록페이지를 구현한다. (text, 이미지, 파일)

착한코딩 2020. 1. 16. 00:38

3_1 목표 : 정합성 높은 데이터를 DB에 Insert 한다. 이미지 및 파일 업로드를 구현하고 흐름을 이해한다.

2회차 2강에서 text 데이터 삽입을 구현했다. 3회차 1강에서는 이미지와 파일을 node 경로에 업로드하고, 파일명만
text로 DB table에 저장할 것이다.

 

 

1. 등록페이지 react 파일을 수정한다.
AdminSoftwareView.js 파일에 아래 소스를 붙여넣는다.

state에 업로드 관련 변수가 추가되었고,
메뉴얼 파일과 메인/라벨 이미지를 선택하는 순간 node 서버의 파일 upload api를 호출하는 코드가 추가됐다.

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

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

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

    //업로드할 파일 세팅
    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('작업중 오류가 발생하였습니다.')
            }
        }).catch(error => {
            alert('작업중 오류가 발생하였습니다.')
        })
    }    

    //이미지 업로드
    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
                        $('#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
                        $('#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 () => {
        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
    }

    // 저장 버튼 클릭시 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'){
                        alert('Software Tools 등록이 완료되었습니다.')
                    }else if(type == "modify"){
                        alert('DSoftware Tools 수정이 완료되었습니다.')
                    }
                    // 저장 후 리스트페이지로 이동
                    window.location.href = 'http://localhost:3000/AdminSoftwareList';
                }else{
                    alert('작업중 오류가 발생하였습니다.')
                }  
            } catch (error) {
                alert('작업중 오류가 발생하였습니다.')
            }
        }//fnValidate end
    };

    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;

node 경로 C:\Users\ljung\OneDrive\문서\taling0102 에 uploads폴더를 생성하고, uploads폴더에 image폴더와 swmanual폴더를 추가해준다. 메뉴얼 파일은 swmanual폴더에, 이미지 파일은 image폴더에 업로드된다.

 

2. node 경로의 파일들을 수정하고, 업로드 파일이 저장될 경로를 생성한다.
node 경로 C:\Users\ljung\OneDrive\문서\taling0102의 server.js를 아래와 같이 수정한다.

 

  이미지, 파일과 같은 정적 파일을 제공할 때 Express의 기본 제공 미들웨어 함수인 express.static을 사용하면 편리하다.
정적 파일이 포함된 디렉토리의 이름을 express.static 미들웨어 함수에 전달하면, 해당 디렉토리에서 파일의 직접적인 제공받을 수 있다.

 

## 참고 ##

  미들웨어 : 구조 내에서 중간 처리를 위한 함수

var express = require('express');

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

var app = express();

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

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

//파일 업로드
app.use("/api/upload", fileuploadRouter);

//파일 업로드 경로 설정
app.use(express.static("./uploads"));

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

 

node 경로 C:\Users\ljung\OneDrive\문서\taling0102 에서
파일업로드에 필요한 패키지들을 설치해준다.


파일을 api 호출을 통해 react 페이지에서 node 서버로 이동시키려면 데이터 타입이 "multipart/form-data" 이어야 한다.

multer라는 패키지가 "multipart/form-data" 를 지원해준다.

moment는 업로드된 파일명 앞에 시간정보를 붙여서 새로운 파일명을 생성해 주기위한 목적으로 사용했다.

시간 포맷을 지원해주는 패키지이다.

npm install --save multer
npm install --save moment

C:\Users\ljung\OneDrive\문서\taling0102\routes 경로에서

UploadRout.js 파일을 만들고 아래 내용을 붙여넣는다.

var express = require('express');
var router = express.Router();
var upload = require('../modules/fileupload');
var multer = require('multer');

router.post("/", (req, res, next) => {
  // FormData의 경우 req로 부터 데이터를 얻을수 없다.
  // upload 핸들러(multer)를 통해서 데이터를 읽을 수 있다
  
  upload(req, res, function(err) {
      
    if (err instanceof multer.MulterError) {
      return next(err);
    } else if (err) {
      return next(err);
    }
    console.log('원본파일명 : ' + req.file.originalname)
    console.log('저장파일명 : ' + req.file.filename)
    console.log('크기 : ' + req.file.size)
    console.log('경로 : ' + req.file.location)
    return res.json({success:1, filename:req.file.filename});
  });
});

module.exports = router;

C:\Users\ljung\OneDrive\문서\taling0102\modules 경로에 fileupload.js 파일을 만들고 아래 내용을 붙여넣는다.

const multer = require('multer');
const moment = require('moment');

const storage = multer.diskStorage({
  destination: function(req, file, cb) {
      try {
        var m_typ = req.query.type;
        cb(null, m_typ);  // 파일이 저장되는 경로입니다.
      } catch (error) {
        console.log(error) 
      }
    },
    filename: function(req, file, cb) {
      cb(null, moment().format('YYYYMMDDHHmmss') + "_" + file.originalname);  // 저장되는 파일명
    }
});

const upload = multer({ storage: storage }).single("file");   // single : 하나의 파일업로드 할때

module.exports = upload;

node 경로 C:\Users\ljung\OneDrive\문서\taling0102에

파일과 이미지가 저장될 폴더를 생성해준다.

3. 실제 파일을 업로드 해보고, 등록페이지 form을 저장한다.

아래와 같이 text 데이터와 메뉴얼파일, 이미지파일을 등록해준다.

업로드 파일은 선택되는 순간, 미리보기 이미지가 보이며
node 서버 경로에실제 파일이 저장된 것을 확인한다.

등록페이지에서 저장버튼을 누르면 아래 쿼리 로그와 같이

업로드된 파일명이 저장된다.

 

 

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