React 웹 프로그래밍 #2 : 엑셀 파일 웹서버 업로드

댓글수4 다음블로그 이동

프로그래밍

React 웹 프로그래밍 #2 : 엑셀 파일 웹서버 업로드

주넥
댓글수4


웹 서비스 개발에 큰 부분중 하나가 데이터베이스 DB를 이용한 데이터 관리이다.

최초에 Excel로 작성된 문서에서 데이터를 뽑아 웹서버내 DB에 보관할 필요가 있다.


아래 예제는 React상에서 웹 브라우저에서 올린 Excel 파일을 웹서버에서 JSON으로 변환하는 예제이다.


1. 서버 프로그래밍


우선 서버쪽에서 사용될 NPM 패키지는 multerxlsx2json 이다.

npm install --save multer

npm install --save xlsx2json


메인 서버 프로그램인  server/index.js 파일은 아래와 같다.

웹 브라우징에 사용되는 static file들이외에 데이터를 주고 받는 URI 는  api prefix를 붙여 http://localhost:3000/api/XXX/YYY 형태로 하고,

REST API 형태로 하고, 모든 처리를 server/routes 폴더 아래 프로그램 소스에서 처리한다.

// server/index.js

'use strict';


import express from 'express';

import morgan from 'morgan';

import bodyParser from 'body-parser';


import path from 'path';

import api from './routes';


const app = express();



// Setup logger

app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms'));

app.use(bodyParser.json());


// Serve static assets

app.use(express.static(path.resolve(__dirname, '..', 'build')));


app.use('/api',api);


// Always return the main index.html, so react-router render the route in the client

app.get('*', (req, res) => {

  res.sendFile(path.resolve(__dirname, '..', 'build', 'index.html'));

});


const PORT = process.env.PORT || 9000;


app.listen(PORT, () => {

  console.log(`App listening on port ${PORT}!`);

});



아래는 server/routes/index.js 프로그램 소스이다.

http://localhost:3000/api/assets/* 으로 시작되는 요청에 대한 처리는 assets.js 파일에서 처리한다.

import express from 'express';

import assets from './assets';


const router = express.Router();

router.use('/assets',assets);


export default router;


아래는 server/routes/assets.js 프로그램 소스이다.

http://localhost/api/assets/excel 로 POST되는 엑셀 파일을 JSON으로 변환하는 프로그램이다.

파일을 받는데는 multer 패키지가 이용되었고, 변환에는 xlsx2json 패키지가 사용되었다.

multer에 의해 전달되는 HTTP 패킷내에 userfile을 받아낸 후, xlsx2josn 에 의해서 JSON 자료구조로 변환된다. 

이렇게 변환된 JSON을 이용 DB에 저장하거나 하면 된다.

import express from 'express';

import multer from "multer";


const router = express.Router();

const upload = multer({dest : 'uploads/'});


import xlsx2json from 'xlsx2json';


/*

var assets = [

              {

                 "user" : "임원",

                 "id" : "2700693",

                 "name" : "프린터(삼성 ML-2580NK)",

                 "date" : "2012.04.18",

                 "location" : "홍길동",

                 "etc" : ""

              },

              {

                 "user" : "솔루션개발실장",

                 "id" : "2701186",

                 "name" : "모니터(LG 25MP58VQ)",

                 "date" : "2016.09.30",

                 "location" : "변사또",

                 "etc" : ""

              },

            ];

*/

var assets = [];

router.post('/excel', upload.single('userfile'), (req, res) => {  // upload.single('userfile') : http 패킷내 userfile 데이터 부분을 받아낸다.

    console.log(`Uploaded assets!`);

    console.log(req.file);

    res.send('Uploaded! : '+req.file);

    console.log(req.file);

    xlsx2json(req.file.path,

             {

                dataStartingRow: 2,  // xlsx 파일내 데이터 시작하는 열, 첫번째 열은 헤더

                mapping : {

                    'user' : 1,         // 1st ~ 6th column에 key를 할당

                    'id' : 2,

                    'name' : 3,

                    'date' : 4,

                    'location' : 5,

                    'etc' : 6

                }

             },

         (err, result) => {

             if (err) {

             }

             else {

                assets = result[0]; // the first sheet

                console.log(JSON.stringify(result)); // console에 변환 결과 JSON을 출력한다.

             }

         }

        );

});


router.get('/download', (req, res) => {

    console.log(`Request assets!`);

    res.json(assets);

});

export default router;                         


2. 클라이언트 프로그래밍


http://localhost:3000/test  로 test URL을 만들고, src/components/Test 폴더 아래 대응 클라이언트용 프로그램을 둔다.

클라이언트 메인 프로그램 src/index.js 는 아래와 같다.

import React from 'react';

import ReactDOM from 'react-dom';

import {BrowserRouter, Switch, Route, Link} from 'react-router-dom';


import './index.css';

import App from './components/App';

import About from './components/About';

import Test from './components/Test';

import NotFound from './components/NotFound';

import registerServiceWorker from './registerServiceWorker';


ReactDOM.render(

 <BrowserRouter>

  <Switch>

        <Route exact path="/" component={App} />

        <Route path="/about" component={About} />

        <Route path="/test" component={Test} />

        <Route component={NotFound} />

  </Switch>

 </BrowserRouter>,

  document.getElementById('root')

);


registerServiceWorker();



웹 UI frame work으로는 비교적 널리 쓰이고, 설명이 잘되어 있는 semantic UI 패키지를 이용한다.

npm install --save semantic-ui-react

npm install --save semantic-ui-css


아래는 http://localhost:3000/test 로 브라우징 되는 src/components/Test/index.js 파일이다.

import React, { Component } from 'react';

import 'semantic-ui-css/semantic.min.css';


import FileUpload from './FileUpload';

import './style.css';


var gAssets = [];


class Test extends React.Component {

  constructor(props) {

    super(props);

    this.state = {

       value : false,

    };

  }


  render() {

    return (

      <FileUpload/>

   );

  }

}


export default Test;


아래는 FileUpload.js 파일로 아래와 같이 렌더링된다.


UI 구성요소 전부를 Input, Label과 같은 Semantic UI 의 Element를 사용하면 좀 깔끔할 듯 한데, 

이런 저런 이유로 input이나 label등 HTML tag를 직접 섞어 사용하여 작성되었다.

Semantic original 웹사이트는 https://semantic-ui.com/ 이고,

React Semantic 웹사이트는 https://react.semantic-ui.com/introduction 이다.


주의할 점은 upload할 파일을 선택하는 input 에서 id를 "userfile" 로 

앞서 Server쪽 프로그래밍시 Multer에서 http 패킷내 받아낼 데이터를 지정할때 사용된 "userfile"과 동일해야 한다.

또한 기본 HTML UI형태가 아닌 Semantic UI 형태로 표시하기 위해서 파일 선택 input 의 style에  display 속성을 none으로 한다.

style={{display: 'none'}}

import React from 'react';

import PropTypes from 'prop-types';

import axios from 'axios';


import {Form, Label, Icon, Input, Button, Divider} from 'semantic-ui-react';

import 'semantic-ui-css/semantic.min.css';


class FileUpload extends React.Component {


  constructor(props) {

    super(props);

    this.state ={

      file:null

    }

    this.onFormSubmit = this.onFormSubmit.bind(this)

    this.onChange = this.onChange.bind(this)

    this.fileUpload = this.fileUpload.bind(this)

  }

  onFormSubmit(e){

    e.preventDefault() // Stop form submit

    if (this.state.file === null) {

        return;

    }

    this.uploadButton.ref.classList.add('loading'); // semantic ui 는 button에 loading icon 표시가능.

    this.fileUpload().then((response)=>{

      this.uploadButton.ref.classList.remove('loading'); // semantic ui 는 button에 loading icon 표시 다시 없앰.

      console.log(response.data);

      //alert(response.data);

    })

  }

  onChange(e) {

    this.setState({file:e.target.files[0]});

    this.refs._userfileName.value = e.target.files[0].name;

  }

  fileUpload(){

    const url = '/api/assets/excel'

    //var formData = new FormData(target);

    var formData = new FormData();

    formData.append('userfile',this.state.file);

    const config = {

        headers: {

            'content-type'  : 'multipart/form-data' // multer 사용시 multipart/form-data 형식이어야 한다.

        }

    }

    return axios.post(url, formData, config)

  }


  render() {

    return (

      <Form className="FileUpload" onSubmit={this.onFormSubmit}>

          <div class = "ui action input">

              <input placeholder='선택된파일...' type="text" ref="_userfileName" class="ui"/>

              <label for="userfile" class="ui icon button">

                 <Icon name="attach" />

              <input type="file" id="userfile"

                   accept=".xlsx, xls"

                   style={{display: "none"}} onChange={this.onChange}/>

              </label>

              <Button basic ref={(param)=>this.uploadButton = param} color="purple" type="submit">올리기</Button>

         </div>

     </Form>

   )

  }

}


export default FileUpload;


이제 클립ICON 버튼을 눌러 xlsx 파일 선택후 올리기를 누르면,

서버쪽 console에 변환된 JSON 결과가 제대로 출력되는지 확인한다.

파일을 서버로 보내는 중 올리기 버튼에 loading animation이 출력되도록 할수 있다.

이는 Semantic UI 에서 Button class에 'loading' 을 버튼이 눌렀을 때 add하고, 전송에 대한 응답을 받았을 때 다시 remove하면 된다.

이를 위해서 올리기 버튼에 대해서 ref 를 설정한다.

ref는 HTML DOM 의 id tag와 비슷한 역할을 하지만, ref는 일반 DOM 요소 뿐만 아니라 

컴포넌트 자체에 적용하여 컴포넌트의 내장된 메소드 및 변수를 사용 할 수 있습니다. 

ref={(param)=>this.uploadButton = param} 와 같이 다소 이해하기  어렵게 설정되는데, 

ref 설정시 함수가 호출되어, this.uploadButton 에 React 컴포넌트가 할당 되는 것이다.



맨위로

https://blog.daum.net/junek69/71

신고하기