웹 서비스 개발에 큰 부분중 하나가 데이터베이스 DB를 이용한 데이터 관리이다.
최초에 Excel로 작성된 문서에서 데이터를 뽑아 웹서버내 DB에 보관할 필요가 있다.
아래 예제는 React상에서 웹 브라우저에서 올린 Excel 파일을 웹서버에서 JSON으로 변환하는 예제이다.
1. 서버 프로그래밍
우선 서버쪽에서 사용될 NPM 패키지는 multer와 xlsx2json 이다.
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);
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 컴포넌트가 할당 되는 것이다.