본문 바로가기
개발/Node JS

Node js, React 파일 관리 시스템 만들기 (12)

by 피로물든딸기 2021. 7. 18.
반응형

프로젝트 전체 링크

 

이전 - (11) AutoSizeInput에 파일 이름 연동하기

현재 - (12) axios post로 파일 저장하기

다음 - (13) shelljs를 이용하여 server의 파일 삭제하기

 

깃허브에서 코드 확인하기


먼저 America_2020_01.csv를 적당히 복사해서 파일 크기를 늘려보자.

 

2500line 정도면 500KB 정도 된다.

 

이 파일을 test.csv 라는 이름으로 2020/America에 저장해보자.


server에 fileSave.js를 아래와 같이 만든다.

 

fileName은 절대 경로로 보낸다. 

그리고 fileName에 저장할 content를 fileName에 저장한다.

//fileSave.js
const express = require("express");
const router = express.Router();
const fs = require("fs");

router.get("/", (req, res) => {
  let fileName = req.query.fileName;
  let content = req.query.file;
  
  fs.writeFile(fileName, content, "utf8", function (error) {
    console.log("write end");
  });
  
  res.send({ result: "success" });
});

module.exports = router;

 

MyTable.js에서 csvDownLoad를 참고하여 saveFile을 만든다.

csvDownLoad와 saveFile의 중복 코드는 따로 함수를 만들어서 중복을 줄여도 된다. 여기서는 그대로 복사한다.

const saveFile = () => {
  let rows = myTable.countRows();
  let cols = myTable.countCols();
  let tmpTables = myTable.getData(0, 0, rows - 1, cols - 1);
  let maxRow, maxCol;

  maxCol = 0;
  for (let r = 0; r < rows; r++) {
    for (let c = cols - 1; c >= 0; c--) {
      if (!(tmpTables[r][c] === "" || tmpTables[r][c] === null)) {
        maxCol = maxCol < c ? c : maxCol;
        break;
      }
    }
  }

  maxRow = 0;
  for (let c = 0; c < cols; c++) {
    for (let r = rows - 1; r >= 0; r--) {
      if (!(tmpTables[r][c] === "" || tmpTables[r][c] === null)) {
        maxRow = maxRow < r ? r : maxRow;
        break;
      }
    }
  }

  let parsing = myTable
    .getData(0, 0, maxRow, maxCol)
    .map((item) => item.map((cell) => getCell(cell)));
  let realTable = parsing.map((item) => item.join(",")).join("\n");

  let filePath = `${mnode.PATH}/2020/America`;
  let fileName = `${filePath}/test.csv`;

  fetch(`${mnode.MY_SERVER}/fileSave?fileName=${fileName}&file=${realTable}`)
  .then((response) => response.json())
  .then((data) => console.log(data));

  return;
};

test이기 때문에 절대 경로는 2020/America로 파일 이름은 test.csv로 하드코딩 하였다.

 

그리고 button을 추가한다.

<button onClick={saveFile}>SAVE</button>

 

America_2020_01.csv를 선택한 후, SAVE 버튼을 누르면 아래와 같은 에러가 나온다.

 

파일이 꽤 크기 때문에 get 방식으로 보내는 것은 한계가 있어 발생하는 에러다.

보통 이런 경우는 post 방식으로 파일을 보내서 해결할 수 있다.

 

fetch도 post를 사용할 수 있지만, 여기에서는 axios를 이용하여 보내자.

npm install axios

 

axios를 import하고 fetch 부분을 아래와 같이 바꾼다.

header에 "content-type" : "application/json"으로 선언한다.

//MyTable.js

...

import axios from "axios";

...

let filePath = `${mnode.PATH}/2020/America`;
let fileName = `${filePath}/test.csv`;

const config = {
  header: { "content-type": "application/json" },
};

axios
  .post(`${mnode.MY_SERVER}/fileSave?fileName=${fileName}`, { file: realTable }, config)
  .then((response) => console.log(response));

 

다시 server의 fileSave를 수정한다.

content를 body의 file로 받아오기 위해서는 bodyParser가 필요하다.

npm install body-parser

 

그리고 bodyParser의 용량을 1MB로 넉넉하게 잡아준다.

//fileSave.js
const express = require("express");
const router = express.Router();
const fs = require("fs");

const bodyParser = require("body-parser"); //npm install body-parser

router.use(bodyParser.json({ limit: "1MB" })); 

router.post("/", (req, res) => {
  let fileName = req.query.fileName;
  let content = req.body.file;

  fs.writeFile(fileName, content, "utf8", function (error) {
    console.log("write end");
  });

  res.send({ result: "success" });
});

module.exports = router;

 

2020/America에 test.csv가 그대로 저장된 것을 알 수 있다.


이제 필요한 경로에 파일을 저장하기 위해 MyTable에도 pathInfo를 추가한다.

따라서 MyTable.js에서 file → pathInfo.file로 변경해야 한다.

<MyTable csvFile={csvObject} pathInfo={{version, country, file}}/>

 

filePath, fileName은 pathInfo에 대해 변경하도록 한다.

이전에 pathInfo의 file로 fileName을 불러왔고, fileName은 useState를 이용해 value에 연동하였다.

let filePath = `${mnode.PATH}/${pathInfo.version}/${pathInfo.country}`;
let fileName = `${filePath}/${value}`;

 

이제 파일을 불러오고, 파일이름을 변경한 후, test.csv를 열어보자.

 

경로를 변경해도 잘 저장되는지 직접 확인해보자.


이제 file을 저장할 때, 이미 파일이 있는 경우, 덮어쓰기를 할지 체크하자.

fileList를 App.js에서 가지고 있으므로, MyTable에 넘긴다.

그리고 setFileList도 넘긴다.

<MyTable
  csvFile={csvObject}
  pathInfo={{ version, country, file }}
  fileList={fileList}
  setFileList={setFileList}
/>

 

saveFile의 가장 앞 부분에 아래의 코드를 추가하자. 

fileList를 for ~ of로 돌면서 같은 이름이 있다면, confirm 창이 나오도록 하자.

취소를 누르면 save 하지 않는다.

//MyTable.js

...

const MyTable = ({ csvFile, fileUploadFlag, pathInfo, fileList, setFileList }) => {

  ...

  const saveFile = () => {
    for(let name of fileList) {
      if(name === value) {
        let answer = window.confirm(`${name}이(가) 이미 있습니다.\n바꾸시겠습니까?`);
        if(answer === false) return;
      }
    }

 

마지막으로 saveFile의 axios 코드를 아래와 같이 수정한다.

response의 status가 200인 경우(OK), 새로 저장된 파일이 생긴다면 fileList를 갱신할 필요가 있다.

axios
  .post(`${mnode.MY_SERVER}/fileSave?fileName=${fileName}`, { file: realTable }, config)
  .then((response) => {
    if(response.status === 200) mnode.getFileList(filePath, "csv", setFileList);
  });

 

그리고 alert를 추가하여 성공여부를 알려주자.

alert(`${value} 파일이 ${pathInfo.version}/${pathInfo.country}에 저장되었습니다.`);

 

최종 코드는 아래와 같다.

 

Node js

//fileSave.js
const express = require("express");
const router = express.Router();
const fs = require("fs");

const bodyParser = require("body-parser"); //npm install body-parser

router.use(bodyParser.json({ limit: "1MB" })); 

router.post("/", (req, res) => {
  let fileName = req.query.fileName;
  let content = req.body.file;

  fs.writeFile(fileName, content, "utf8", function (error) {
    console.log("write end");
  });

  res.send({ result: "success" });
});

module.exports = router;

 

//server.js
const express = require('express');
const app = express();
const nodetest = require('./routes/nodetest');
const getFileFolderList = require('./routes/getFileFolderList');
const getFile = require('./routes/getFile');
const fileSave = require('./routes/fileSave');


const cors = require('cors');
app.use(cors()); //npm install cors --save

app.use('/nodetest', nodetest);
app.use('/getFileFolderList', getFileFolderList);
app.use('/getFile', getFile);
app.use('/fileSave', fileSave);


app.listen(3002, () => console.log('Node.js Server is running on port 3002...'));

 

React

//App.js
import React, { useEffect, useState } from "react";

import FileUpload from "./components/FileUpload";
import MyFileList from "./components/MyFileList";
import MyTable from "./components/MyTable";
import MyToggles from "./components/MyToggles";
import * as mnode from "./components/nodelibrary";

const csvObjectDefault = {
  HEIGHT: 0,
  WIDTH: 0,
  csv: [],
};

const nodeTest = () => {

  mnode.getFileFolderList(mnode.PATH, "csv");
  return;
}

const App = () => {
  const [csvObject, setCsvObject] = useState(csvObjectDefault);
  const [version, setVersion] = useState("");
  const [country, setCountry] = useState("");
  const [file, setFile] = useState("");
  const [fileList, setFileList] = useState([]);

  const getFileList = () => {
    if(version === "" || country === "") return;

    let path = `${mnode.PATH}/${version}/${country}`;
    mnode.getFileList(path, "csv", setFileList);
  }

  useEffect(getFileList, [version, country]);

  return (
    <div>
      <MyToggles
        version={version}
        setVersion={setVersion}
        country={country}
        setCountry={setCountry}
      />

      <hr style={{ borderColor: "grey" }} />

      <MyFileList fileList={fileList} setFile={setFile} />

      <button onClick={nodeTest}>서버 연결</button>
      <button onClick={() => console.log(csvObject)}>print csv</button>
      <div className="App">
        <FileUpload
          setCsvObject={setCsvObject}
          pathInfo={{ version, country, file }}
        />
        <MyTable
          csvFile={csvObject}
          pathInfo={{ version, country, file }}
          fileList={fileList}
          setFileList={setFileList}
        />
      </div>
    </div>
  );
};

export default App;

 

//MyTable.js
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import * as lib from "./library.js";
import * as mnode from "./nodelibrary";

import "handsontable/dist/handsontable.full.css";
import Handsontable from "handsontable";
import AutoSizeInput from "./AutoSizeInput";
import axios from "axios";

let myTable;
let currentRow, currentColumn;

const getCell = (cell) => {
  if (cell === null) return ``;
  return cell.includes(",") ? `"${cell}"` : `${cell}`;
};

const csvDownLoad = () => {
  let rows = myTable.countRows();
  let cols = myTable.countCols();
  let tmpTables = myTable.getData(0, 0, rows - 1, cols - 1);
  let maxRow, maxCol;

  maxCol = 0;
  for (let r = 0; r < rows; r++) {
    for (let c = cols - 1; c >= 0; c--) {
      if (!(tmpTables[r][c] === "" || tmpTables[r][c] === null)) {
        maxCol = maxCol < c ? c : maxCol;
        break;
      }
    }
  }

  maxRow = 0;
  for (let c = 0; c < cols; c++) {
    for (let r = rows - 1; r >= 0; r--) {
      if (!(tmpTables[r][c] === "" || tmpTables[r][c] === null)) {
        maxRow = maxRow < r ? r : maxRow;
        break;
      }
    }
  }

  let parsing = myTable
    .getData(0, 0, maxRow, maxCol)
    .map((item) => item.map((cell) => getCell(cell)));
  let realTable = parsing.map((item) => item.join(",")).join("\n");

  lib.downLoadCsv(realTable);

  return;
};

const MyTable = ({ csvFile, fileUploadFlag, pathInfo, fileList, setFileList }) => {
  const [displayIndex, setDisplayIndex] = useState("");
  const [displayCell, setDisplayCell] = useState("");
  const [value, setValue] = useState("");

  const saveFile = () => {
    for(let name of fileList) {
      if(name === value) {
        let answer = window.confirm(`${name}이(가) 이미 있습니다.\n바꾸시겠습니까?`);
        if(answer === false) return;
      }
    }

    let rows = myTable.countRows();
    let cols = myTable.countCols();
    let tmpTables = myTable.getData(0, 0, rows - 1, cols - 1);
    let maxRow, maxCol;

    maxCol = 0;
    for (let r = 0; r < rows; r++) {
      for (let c = cols - 1; c >= 0; c--) {
        if (!(tmpTables[r][c] === "" || tmpTables[r][c] === null)) {
          maxCol = maxCol < c ? c : maxCol;
          break;
        }
      }
    }

    maxRow = 0;
    for (let c = 0; c < cols; c++) {
      for (let r = rows - 1; r >= 0; r--) {
        if (!(tmpTables[r][c] === "" || tmpTables[r][c] === null)) {
          maxRow = maxRow < r ? r : maxRow;
          break;
        }
      }
    }

    let parsing = myTable
      .getData(0, 0, maxRow, maxCol)
      .map((item) => item.map((cell) => getCell(cell)));
    let realTable = parsing.map((item) => item.join(",")).join("\n");

    let filePath = `${mnode.PATH}/${pathInfo.version}/${pathInfo.country}`;
    let fileName = `${filePath}/${value}`;

    const config = {
      header: { "content-type": "application/json" },
    };

    axios
      .post(`${mnode.MY_SERVER}/fileSave?fileName=${fileName}`, { file: realTable }, config)
      .then((response) => {
        if(response.status === 200) mnode.getFileList(filePath, "csv", setFileList);
      });

    alert(`${value} 파일이 ${pathInfo.version}/${pathInfo.country}에 저장되었습니다.`);

    return;
  };

  const selectCell = () => {
    let selected = myTable.getSelectedLast();

    currentRow = selected[0];
    currentColumn = selected[1];

    if (currentRow < 0 || currentColumn < 0) return;

    setDisplayCell(myTable.getValue());
    setDisplayIndex(`${lib.rowToAlpha(currentColumn + 1)}${currentRow + 1}`);
  };

  const setValueCell = (e) => {
    if (currentRow < 0 || currentColumn < 0) return;

    setDisplayCell(e.target.value);
    myTable.setDataAtCell(currentRow, currentColumn, e.target.value);
  };

  const init = (csvFile) => {
    if (csvFile === undefined || csvFile.HEIGHT === 0) return;

    const container = document.getElementById("hot-app");

    if (myTable !== undefined) myTable.destroy();

    myTable = new Handsontable(container, {
      data: lib.makeTable(csvFile, 2, 3),
      colHeaders: true /* column header는 보이게 설정 */,
      rowHeaders: true /* row header 보이게 설정 */,
      colWidths: [60, 60, 60, 60, 60, 60, 60],
      wordWrap: false /* 줄 바꿈 x */,
      width: "50%",
      manualColumnResize: true /* column 사이즈 조절 */,
      manualRowResize: true /* row 사이즈 조절 */,
      manualColumnMove: true /* column move 허용 */,
      manualRowMove: true /* row move 허용 */,
      dropdownMenu: true /* dropdown 메뉴 설정 */,
      filters: true /* 필터 기능 on */,
      contextMenu: true /* cell 클릭 시 메뉴 설정 */,
      licenseKey: "non-commercial-and-evaluation",
      afterSelection: selectCell,
    });
  };

  useEffect(() => {
    init(csvFile);
  }, [csvFile]);

  useEffect(() => {
    setValue(pathInfo.file);
  }, [pathInfo.file]);

  return (
    <div>
      {fileUploadFlag && (
        <div>
          <button onClick={csvDownLoad}>DOWNLOAD</button>
          <button onClick={saveFile}>SAVE</button>
          <AutoSizeInput
            placeholder="파일 이름 입력"
            value={value}
            onChange={(e) => setValue(e.target.value)}
          />
          <div>
            <span>{displayIndex}</span>
            <input value={displayCell} onChange={setValueCell} />
          </div>
          <div id="hot-app"></div>
        </div>
      )}
    </div>
  );
};

function mapStateToProps(state, ownProps) {
  //console.log(state);
  return { fileUploadFlag: state };
}

export default connect(mapStateToProps)(MyTable);

이전 - (11) AutoSizeInput에 파일 이름 연동하기

다음 - (13) shelljs를 이용하여 server의 파일 삭제하기

반응형

댓글