본문 바로가기
국비학원/수업기록

국비 지원 개발자 과정_Day72

by 루팽 2023. 3. 13.

React-front에 추가설치

  yarn add react-quill

  yarn add quill-image-resize-module-react

 

<리액트와 pojo3 연결 - index.js>

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import AuthLogic from "./service/authLogic";
import firebaseApp from "./service/firebase";
import "react-quill/dist/quill.snow.css";
import "@fortawesome/fontawesome-free/js/all.js";
// import SampleApp from "./SampleApp";

// 공통코드: service -> authLogic.js -> import 외부 js 재사용 가능 -> export default 클래스명 -> module
// 브라우저에서 import하려면 반드시 babel이 필요함
const authLogic = new AuthLogic(firebaseApp); // 인스턴스화(파라미터가 올 수 있다)
// 왜 파라미터가 필요한가? - firebaseApp -> import firebaseApp from "./service/firebase"; -> export default firebaseApp
// authLogic.파이어베이스에서 제공하는 함수를 호출하겠다
// document.getElementById("root") -> index.html문서에서 DOM정보를 수집하는 것임
// const root = ReactDOM.createRoot(document.getElementById("root"));
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(
  <>
    <BrowserRouter>
      {/* App컴포넌트를 렌더링할 때 속성자리에 주소번지를 넘길 수 있다 - props */}
      {/* 태그와 JS 섞어쓰기 가능 - 중괄호{} */}
      <App authLogic={authLogic} />
      {/* <SampleApp /> */}
    </BrowserRouter>
  </>
);

 

<리액트와 pojo3 연결 - App.jsx>

import { Route, Routes } from "react-router-dom";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
//import DeptPage from './components/page/DeptPage';
import EmpPage from "./components/page/EmpPage";
import HomePage from "./components/page/HomePage";
import FireDeptPage from "./components/page/FireDeptPage";
import LoginPage from "./components/login/LoginPage";
import BoardPage from "./components/page/BoardPage";
import WorkoutPage from "./components/page/WorkoutPage";
import HackerNewsPage from "./components/page/HackerNewsPage";
import YoutubePage from "./components/page/YoutubePage";
import { useEffect, useState } from "react";
import BoardDetail from "./components/board/BoardDetail";
import BoardWriteFrom from "./components/board/BoardWriteFrom";

// index.js에서 브라우저 라우터로 감싸진 App태그 속성값으로 넘어온 주소번지를 받는다
const App = ({ authLogic }) => {
  console.log("App 호출");

  // 상수값 -> 나중에 axios(NodeJS, React-모듈화), fetch(바닐라-브라우저 지원)로 처리(둘 다 비동기 처리방식)
  const [items, setItems] = useState([
    { id: 1, name: "벤치프레스", count: 0 },
    { id: 2, name: "랫풀다운", count: 0 },
    { id: 3, name: "스쿼트", count: 0 },
  ]);

  /*
    첫번째 파라미터는 콜백함수 - 객체
    두번째 파라미터는 의존성 배열 - Dependency Array
    의존성 배열이 비어있으면 최초 App 컴포넌트가 렌더링될 때 딱 한번만 실행됨
    리렌더링이 되는 대상은 return안에 있는 코드들이다
    만약 items가 변경되면 그때는 리렌더링이 일어남
  */
  useEffect(() => {
    console.log("effect 호출");
  }, []);

  const handleIncrement = (item) => {
    const index = items.indexOf(item);
    items[index].count += 1;
    setItems([...items]);
  };

  const handleDecrement = (item) => {
    const index = items.indexOf(item);
    const count = items[index].count - 1;
    items[index].count = count < 0 ? 0 : count;
    setItems([...items]);
  };

  const handleDelete = (item) => {
    console.log(`handleDelete => ${item.name}`);
    const workouts = items.filter((workout) => workout.id != item.id);
    setItems([...workouts]);
  };

  const handleAdd = (name) => {
    console.log(`handleAdd => ${name}`);
    // AddForm화면에서 사용자가 입력한 운동이름을 받아온다
    // 세번째 파라미터는 0으로 초기화
    // 스프레드 연산자를 활용하여 기존 배열에 한 개의 객체를 추가하는 코드
    const workouts = [...items, { id: Date.now(), name, count: 0 }];
    // 상태 훅에 반영 - spread 연산자를 활용하여 새로운 주소번지가 채번되도록 처리해야함
    // 상태값이 변경되었다는 사실을 리액트에서 인지할 수 있다
    setItems([...workouts]);
  };

  // 사용자 정의 컴포넌트에서 return 다음에 오는 코드가 element의 집합(컴포넌트)
  // Router를 이용하면 SPA(Single Page Application)를 누릴 수 있다
  return (
    <>
      <Routes>
        {/* 아래같이 계속 authLogic 넘기는게 불편 -> 리덕스 배우면 상태관리가능! */}
        {/* exact쓰면 주소가 100% 일치할때만 이동되니 주의(*못씀!) */}
        <Route path="/" exact={true} element={<LoginPage authLogic={authLogic} />} />
        <Route path="/home/:userId" xact={true} element={<HomePage authLogic={authLogic} />} />
        <Route path="/board" exact={true} element={<BoardPage authLogic={authLogic} />} />
        <Route path="/board/write/*" element={<BoardWriteFrom />} />
        <Route path="/boarddetail/:bm_no" exact={true} element={<BoardDetail />} />
        <Route path="/workout" exact={true} element={ <WorkoutPage authLogic={authLogic}
              workouts={items}
              onIncrement={handleIncrement}
              onDecrement={handleDecrement}
              onDelete={handleDelete}
              onAdd={handleAdd} />} />
        <Route path="/hackernews" exact={true} element={<HackerNewsPage authLogic={authLogic} />} />
        <Route path="/youtube" exact={true} element={<YoutubePage authLogic={authLogic} />}/>
        <Route path="/dept/:id" exact={true} element={<FireDeptPage authLogic={authLogic} />} />
        {/* <Route path="/dept/:id" exact={true} element={<DeptPage authLogic={authLogic} />} /> */}
        <Route path="/emp" exact={true} element={<EmpPage authLogic={authLogic} />} />
      </Routes>
    </>
  );
};

export default App;

 

<리액트와 pojo3 연결 - BoardRow.jsx>

import React from "react";
import { Link } from "react-router-dom";

const BoardRow = ({board}) => {

  return (
    <>
      <tr>
        <td>{board.BM_NO}</td>
        <td><Link to={"/boarddetail/"+board.BM_NO} className="btn btn-primary">{board.BM_TITLE}</Link></td>
        <td>{board.BM_WRITER}</td>
      </tr>
    </>
  );
};

export default BoardRow;

 

<리액트와 pojo3 연결 - BoardPage.jsx>

import React, { useEffect, useState } from "react";
import { Button, Table } from "react-bootstrap";
import { useNavigate } from "react-router-dom";
import Bottom from "../include/Bottom";
import Header from "../include/Header";
import '../css/board.css'
import BoardRow from "../board/BoardRow";
import { boardListDB } from "../../service/dbLogic";

const BoardPage = ({ authLogic }) => {
  console.log('BoardPage App')
  const [boards, setBoards] = useState([]);

  // Single Page Application 컨벤션을 위한 훅
  const navigate = useNavigate();

  const onLogout = () => {
    console.log("BoardPage onLogout 호출");
    authLogic.logout();
  };

  // pojo3과 통신하는 useEffect -> CORS on으로 바꾸고 테스트
  useEffect (()=>{
    console.log('POJO3 서버와 통신 effect')
    // async를 써야 await 쓸 수 있음
    const boardList = async() => {
      const gubun = document.querySelector('#gubun').value
      const keyword = document.querySelector('#keyword').value
      const board = {
        gubun: gubun,
        keyword: keyword,
      }
      const res = await boardListDB(board)
      console.log(res.data)
      const list = []
      res.data.forEach((item) => {
        const obj = {
          BM_NO: item.BM_NO,
          BM_TITLE : item.BM_TITLE,
          BM_WRITER: item.BM_WRITER,
        }
        list.push(obj)
      })
      setBoards(list)
    }
    boardList()
  }, [setBoards]) // 빈배열이면 최초 한번만 실행, 여기선 조회 결과가 달라질 때마다 호출됨

  useEffect(() => {
    authLogic.onAuthChange((user) => {
      if (!user) {
        navigate("/");
      }
    });
  });

  // 조건 검색 구현
  const reactSearch = () => {
  }

  // 전체 조회 구현
  const boardList = () => {
  }

  return (
    <>
      <Header onLogout={onLogout} />
      <div className="container">
        <div className="page-header">
          <h2>공지관리<small>글목록</small></h2>
          <hr />
        </div>

        <div className="row">
          <div className="col-3">
            <select id="gubun" className="form-select" aria-label="분류선택">
              <option defaultValue>분류선택</option>
              <option value="bm_title">제목</option>
              <option value="bm_writer">작성자</option>
              <option value="bm_content">내용</option>
            </select>
          </div>
          <div className="col-6">
            <input type="text" id="keyword" className="form-control" placeholder="검색어를 입력하세요"
              aria-label="검색어를 입력하세요" aria-describedby="btn_search"/>
          </div>
          <div className="col-3">
            <Button variant="danger" id="btn_search" onClick={reactSearch}>검색</Button>
          </div>
        </div>

        <div className="board-list">
          <Table striped bordered hover>
            <thead>
              <tr>
                <th>#</th>
                <th>제목</th>
                <th>작성자</th>
              </tr>
            </thead>
            <tbody>
              {boards && boards.map((board, index) => (
                <BoardRow key={index} board={board} />
                ))}
            </tbody>
          </Table>
          <hr />
          <div className="boardlist-footer">
            <Button variant="warning" onClick={boardList}>
              전체조회
            </Button>&nbsp;
            <Button variant="success" onClick={() => {navigate(`/board/write`);}}>
              글쓰기
            </Button>
          </div>
        </div>
      </div>
      <Bottom />
    </>
  );
};

export default BoardPage;

 

<리액트와 pojo3 연결 - dbLogic.js>

import axios from "axios";

// pojo3과 통신해서 DB값 가져오는 boardListDB
export const boardListDB = (board) => {
  return new Promise((resolve, reject) => {
    try {
      const response = axios({
        method: "get",
        url: process.env.REACT_APP_CHAT_BANANA_IP + "board3/jsonBoardList.st3",
        params: board,
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

export const uploadImageDB = (file) => {
  console.log(file);
  return new Promise((resolve, reject) => {
    try {
      const response = axios({
        method: "post",
        url: process.env.REACT_APP_CHAT_BANANA_IP + "board3/imageUpload.st3",
        headers: {
          "Content-Type": "multipart/form-data",
        },
        processData: false,
        contentType: false,
        data: file,
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

export const boardInsertDB = (board) => {
  return new Promise((resolve, reject) => {
    try {
      const response = axios({
        method: "post",
        url: process.env.REACT_APP_CHAT_BANANA_IP + "board3/boardInsert.st3",
        headers: {
          "Content-Type": "multipart/form-data",
        },
        data: board,
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

/* rafce로 자동완성 단축키 - arrow function export default */

 

<리액트와 pojo3 연결 - BoardWriteForn.jsx>

import React, { useCallback, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { boardInsertDB, uploadImageDB } from '../../service/dbLogic'
import { BButton, ContainerDiv, FormDiv, HeaderDiv } from '../styles/FormStyle'
import QuillEditor from './QuillEditor'

const BoardWriteFrom = () => {
  const navigate = useNavigate()
  const[title, setTitle] = useState("")
  const[writer, setWriter] = useState("")
  const[pw, setPw] = useState("")
  const[content, setcontent] = useState("")
  const[bsfile, setBsFile] = useState("")
  const[bssize, setBsSize] = useState("")
  const[fileName, setFileName] = useState("")
  const[files, setFiles] = useState([])

  const quillRef = useRef()
  const fileRef = useRef()

  // 사용자가 입력한 값을 useState에 초기화하기
  const handleTitle = useCallback((e) => {
    setTitle(e)
  }, [])
  
  const handleWriter = useCallback((e) => {
    setWriter(e)
  }, [])

  const handlePW = useCallback((e) => {
    setPw(e)
  }, [])

  const handleContent = useCallback((e) => {
    setcontent(e)
  }, [])

  const boardInsert = async() => {
    const board = {
      bm_title: title,
      bm_content: content,
      bm_writer: writer,
      bm_pw: pw,
      bs_file: bsfile,
      bs_size: bssize,
    }
    const res = await boardInsertDB(board)
    console.log(res)
    // window.location.replace("/board")
    navigate("/board")
  }

  const handleClick = (event) => {
    event.preventDefault()
    document.querySelector("#file-input").click((event)=>{
      console.log(event.currentTarget.value);
    })
  }

  const handleChange = async (event) => {
    console.log('첨부파일 선택'+event.target.value);
    //console.log(fileRef.current.value);
    //fileRef에서 가져온 값중 파일명만 담기
    const str = fileRef.current.value.split('/').pop().split('\\').pop()
    setFileName(str)
    console.log(str);
    //선택한 파일을 url로 바꾸기 위해 서버로 전달할 폼데이터 만들기
    const formData = new FormData()
    const file = document.querySelector("#file-input").files[0]
    formData.append("application", file)
    const res = await uploadImageDB(formData)
    console.log(res.data)
    const fileinfo = res.data.split(',')
    console.log(fileinfo)
    setBsFile(fileinfo[0])
    setBsSize(fileinfo[1])
  }

  const handleFiles = () => {

  }

  return (
    <>
        <ContainerDiv>
        <HeaderDiv>
          <h3 style={{marginLeft:"10px"}}>공지사항 글작성</h3>
        </HeaderDiv>
        <FormDiv>
          <div style={{width:"100%", maxWidth:"2000px"}}>
            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h3>제목</h3> 
              <BButton onClick={()=>{boardInsert()}}>글쓰기</BButton>
            </div>
            <input id="dataset-title" type="text" maxLength="50" placeholder="제목을 입력하세요."
              style={{width:"100%",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handleTitle(e.target.value)}}/>
            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h3>작성자</h3> 
            </div>              
            <input id="dataset-writer" type="text" maxLength="50" placeholder="작성자를 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handleWriter(e.target.value)}}/>
            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h3>비밀번호</h3> 
            </div>              
            <input id="dataset-pw" type="text" maxLength="50" placeholder="비밀번호를 입력하세요."
              style={{width:"200px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} onChange={(e)=>{handlePW(e.target.value)}}/>
            
            <div style={{display: 'flex', justifyContent: 'space-between', marginBottom:'5px'}}>
              <h3>첨부파일</h3> 
            </div>                      
            <input id="file-input" ref={fileRef} type="file" maxLength="50" className="visuallyhidden" onChange={handleChange}/>            
            <button style={{height:'40px'}} onClick={handleClick}>파일선택</button>&nbsp;
            <input id="bs_file" name='bs_file' type="text" maxLength="50" value={fileName}
              style={{width:"600px",height:'40px' , border:'1px solid lightGray', marginBottom:'5px'}} />
            <h3>상세내용</h3>
            <hr style={{margin:'10px 0px 10px 0px'}}/>
            <QuillEditor value={content} handleContent={handleContent} quillRef={quillRef} files={files} handleFiles={handleFiles}/>
            {/* <BoardFileInsert files={files}/> */}
          </div>
        </FormDiv>
      </ContainerDiv>
    </>
  )
}

export default BoardWriteFrom

 

<리액트와 pojo3 연결 - QuillEditor.jsx>

import { useCallback, useEffect, useMemo } from 'react';
import ReactQuill, { Quill } from 'react-quill'; 
import ImageResize from 'quill-image-resize-module-react';
import { uploadImageDB } from '../../service/dbLogic';
//import 'react-quill/dist/quill.snow.css';
Quill.register('modules/ImageResize', ImageResize);


const QuillEditor = ({ value, handleContent, quillRef, files, handleFiles}) => {
    console.log(files);
    //const dispatch = useDispatch();
    const imageHandler = useCallback(() => {
        console.log(files);
        if(files.length > 2){
            return "이미지는 3장까지 업로드 가능합니다.";
        }
        const formData = new FormData(); // 이미지를 url로 바꾸기위해 서버로 전달할 폼데이터 만들기
        
        const input = document.createElement("input"); // input 태그를 동적으로 생성하기
        input.setAttribute("type", "file");
        input.setAttribute("accept", "image/*"); // 이미지 파일만 선택가능하도록 제한
        input.setAttribute("name", "image");
        input.click(); 
        
        // 파일 선택창에서 이미지를 선택하면 실행될 콜백 함수 등록
        input.onchange = async () => {
            const file = input.files[0];
            const fileType = file.name.split('.');
            console.log(fileType);
            if(!fileType[fileType.length-1].toUpperCase().match('JPG')&&
            !fileType[fileType.length-1].toUpperCase().match('PNG')&&
            !fileType[fileType.length-1].toUpperCase().match('JPEG')) {
                console.log("jpg png jpeg형식만 지원합니다.");
            }
            formData.append("image", file); // 위에서 만든 폼데이터에 이미지 추가
            for (let pair of formData.entries()) {
                console.log(pair[0], pair[1]); 
            }
            // 폼데이터를 서버에 넘겨 multer로 이미지 URL 받아오기
            const res = await uploadImageDB(formData);
            console.log(res.data); // 리턴받는 파일명
            const files = res.data.split(',')
            if (!res.data) {
                console.log("이미지 업로드에 실패하였습니다.");
            }
            const url = process.env.REACT_APP_CHAT_BANANA_IP+`board3/imageGet.st3?imageName=${files[0]}`;
            const quill = quillRef.current.getEditor();
            /* ReactQuill 노드에 대한 Ref가 있어야 메서드들을 호출할 수 있으므로
            useRef()로 ReactQuill에 ref를 걸어주자.
            getEditor() : 편집기를 지원하는 Quill 인스턴스를 반환함
            여기서 만든 인스턴스로 getText()와 같은 메서드를 사용할 수 있다.*/
            
            const range = quill.getSelection().index; 
            //getSelection()은 현재 선택된 범위를 리턴한다. 에디터가 포커싱되지 않았다면 null을 반환한다.
            
            if (typeof (range) !== "number") return;
            /*range는 0이 될 수도 있으므로 null만 생각하고 !range로 체크하면 잘못 작동할 수 있다.
            따라서 타입이 숫자이지 않을 경우를 체크해 리턴해주었다.*/
            
            quill.setSelection(range, 1);
            /* 사용자 선택을 지정된 범위로 설정하여 에디터에 포커싱할 수 있다. 
            위치 인덱스와 길이를 넣어주면 된다.*/
            
            quill.clipboard.dangerouslyPasteHTML(
                range,
                `<img src=${url} style="width: 100%; height: auto;" alt="image" />`);
                
                //handleFiles(res.data, `${Math.floor(file.size/(1024*1024)*10)/10}MB`);
            }   //주어진 인덱스에 HTML로 작성된 내용물을 에디터에 삽입한다.
            
            
        }, [quillRef, files]);
        
    const modules = useMemo(
        () => ({
        toolbar: { // 툴바에 넣을 기능들을 순서대로 나열하면 된다.
            container: [
                [{ 'header': [1, 2, 3, 4, 5, 6, false] }, { color: [] }, { 'align': [] }, { 'background': [] }],
                ["bold", "italic", "underline", "strike", "blockquote"],
                [
                    { list: "ordered" },
                    { list: "bullet" },
                    { indent: "-1" },
                    { indent: "+1" },
                ],
                ['clean'],
                ['link', "image"],
            ],
            handlers: { // 위에서 만든 이미지 핸들러 사용하도록 설정
                image: imageHandler,
            },
        },
        ImageResize: {
            modules: ['Resize']
        }
    }), [imageHandler])
    useEffect(()=>{
        console.log('QuillEditor useEffect');
    },[])
    const formats = [
        //'font',
        'header',
        'bold', 'italic', 'underline', 'strike', 'blockquote',
        'list', 'bullet', 'indent',
        'link', 'image',
        'align', 'color', 'background',        
    ]

    return (
        <div style={{height: "550px", display: "flex", justifyContent: "center", padding:"0px"}}>
            <ReactQuill 
                ref={quillRef}
                style={{height: "470px", width: "100%"}} 
                theme="snow" 
                placeholder= "본문 입력"
                modules={modules} 
                formats={formats}
                value={value} 
                onChange={(content, delta, source, editor) => {handleContent(editor.getHTML());}} />
        </div>
    )
}

export default QuillEditor

 

<리액트와 pojo3 연결 - BoardDetail.jsx>

import React, { useEffect, useState } from 'react'
import { json, useParams } from 'react-router-dom'
import { boardListDB } from '../../service/dbLogic'
import { ContainerDiv, FormDiv, HeaderDiv } from '../styles/FormStyle'
import BoardHeader from './BoardHeader'

const BoardDetail = () => {
  const {bm_no} = useParams()
  console.log(bm_no)

  const[pboard, setPBoard] = useState({
    bm_no: bm_no,
  })

   // 하나: 파일명(bs_file), 둘: 파일크기(bs_size)
  const[files, setFiles] = useState({

  })

  const[board, setBoard] = useState({
    BM_NO: 0,
    BM_TITLE: "",
    BM_WRITER: "",
    BM_CONTENT: "",
    BM_PW: "",
    BM_REG: "",
    BM_GROUP: 0,
    BM_POS: 0,
    BM_STEP: 0,
    BM_HIT: 0,
    BS_FILE: "",
    BS_SIZE: "",
  })

useEffect (() => {
  // 동기-기다림, 비동기- 기다리는동안 다른일 처리
  const boardDetail = async() => {
    const res = await boardListDB(pboard)
    console.log(res)
    const result = JSON.stringify(res.data)
    const jsonDoc = JSON.parse(result)
    setBoard({BM_NO: jsonDoc[0].BM_NO, BM_TITLE: jsonDoc[0].BM_TITLE, BM_WRITER: jsonDoc[0].BM_WRITER,
              BM_CONTENT: jsonDoc[0].BM_CONTENT, BM_PW: jsonDoc[0].BM_PW, BM_REG: jsonDoc[0].BM_REG, 
              BM_GROUP: jsonDoc[0].BM_GROUP, BM_POS: jsonDoc[0].BM_POS, BM_STEP: jsonDoc[0].BM_STEP, 
              BM_HIT: jsonDoc[0].BM_HIT 
    })
    // 첨부파일, quill editor 이미지 파일
    setFiles([jsonDoc[0].BS_FILE, jsonDoc[0].BS_SIZE])
  }
  boardDetail()
}, [pboard]) // 의존성 배열에는 제목에 해당되는 bm_no가 변경될 때마다 새로 실행됨
// 빈 배열이면 최초 App이 렌더링될 때(리턴에있는 컴포넌트가 렌더링될때) 딱 한 번만 실행됨
// 의존성 배열을 적지 않으면 변경될때마다 새로 읽는다 - 초기화된다(유지x)
// 주의사항: boards와 같은 n개 로우를 갖는 변수명을 사용X! - 무한루프(리렌더링) -> 대신 setBoards를 쓸것

  return (
    <ContainerDiv>
      <HeaderDiv>
        <h3 style={{marginLeft:"10px"}}>계층형 게시판</h3>
      </HeaderDiv>
      <FormDiv>
        <BoardHeader board={board}  bm_no={bm_no}/>
        <section style={{minHeight: '400px'}}>
          <div dangerouslySetInnerHTML={{__html:board.BM_CONTENT}}></div>
        </section>
        <hr style={{height:"2px"}}/>
      </FormDiv>
    </ContainerDiv>
  )
}

export default BoardDetail

 

<리액트와 pojo3 연결 - BoardHeader.jsx>

import React from 'react'
import { useNavigate } from 'react-router-dom'
import { BButton } from '../styles/FormStyle'

const BoardHeader = ({board}) => {
  const navigate = useNavigate()

  const boardDelete = () => {

  }

  return (
    <div>
      <div style={{display: 'flex', flexDirection: 'column', width: '100%'}}>
        <div style={{display: 'flex', justifyContent:"space-between"}}>
          <div style={{overflow: "auto"}}>
            <span style={{marginBottom:'15px', fontSize: "30px", display:"block"}}>
              {board.BM_TITLE}
            </span>
          </div>
          {
            <div style={{display: 'flex', justifyContent: 'flex-end'}}>
              <BButton style={{margin:'0px 10px 0px 10px'}} onClick={()=>{navigate(`/board/update?bm_no=${board.BM_NO}`)}}>
                수정
              </BButton>
              <BButton onClick={()=>{boardDelete()}}>
                삭제
              </BButton>
            </div>
          }
        </div>
        <div style={{display: 'flex', justifyContent: 'space-between', fontSize: '14px'}}>
          <div style={{display: 'flex', flexDirection: 'column'}}>
            <span>작성자 : {board.BM_WRITER}</span>
            <span>작성일 : {board.BM_REG}</span>
          </div>
          <div style={{display: 'flex', flexDirection: 'column', marginRight:'10px'}}>
            <div style={{display: 'flex'}}>
              <span style={{marginRight:'5px'}}>조회수 :</span>
              <div style={{display: 'flex', justifyContent: 'flex-end', width:'30px'}}>{board.BM_HIT}</div>
            </div>
          </div>
        </div>
      </div>
      <hr style={{height: '2px'}}/>
    </div>
  )
}

export default BoardHeader

<리액트와 pojo3 연결 - ActionSupport.java>

package com.pojo.step3;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import com.pojo.step2.Board2Controller;

public class ActionSupport extends HttpServlet {
	Logger logger = Logger.getLogger(ActionSupport.class);

	protected void doService(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("doService 호출");
		String uri = req.getRequestURI();
		logger.info(uri); // /board3/boardList.st3가 찍힘

		String context = req.getContextPath();
		logger.info(context); // "/" -> server.xml에 들어있음

		String command = uri.substring(context.length() + 1);
		System.out.println(command); // board3/boardList.st3

		int end = command.lastIndexOf(".");
		System.out.println(end); // 16(board3의 경우)

		command = command.substring(0, end);
		System.out.println(command); // board3/boardList

		String upmu[] = null; // upmu[0]=업무명(폴더명), upmu[1]=요청기능이름(메소드명)
		upmu = command.split("/"); // /기준으로 문자열 잘라서 upmu 배열에 담기
		logger.info(upmu[0] + ", " + upmu[1]);
		// upmu req에 담기
		req.setAttribute("upmu", upmu);
		Object obj = ""; // null이 맞지만 String이 들어온다는 전제로 ""

		try {
			obj = HandlerMapping.getController(upmu, req, res);
		} catch (Exception e) {
			logger.info("Exception: " + e.toString());
		}

		// 페이지 이동처리 공통코드
		// obj 형식 예시 -> redirect:XXX.jsp or forward:XXX.jsp
		if (obj != null) {
			String pageMove[] = null;
			ModelAndView mav = null;

			// obj가 String인 경우 -> webapp
			if (obj instanceof String) {
				// obj에 :이 포함된 경우
				if (((String) obj).contains(":")) {
					logger.info(": 포함되어 있음");
					pageMove = obj.toString().split(":");
				}
				// objdp /가 포함된 경우
				else if (((String) obj).contains("/")) {
					logger.info("/ 포함되어 있음");
					pageMove = obj.toString().split("/");
				}
				// obj에 :과 /이 포함되지 않은 경우
				else {
					// spring boot -> @RestController 사용
					// spring4 -> ResponseBody 사용(@RestController 미지원)
					logger.info(":과 / 포함되어 있지 않음"); // 마임타입 text/plain
					pageMove = new String[1];
					pageMove[0] = obj.toString();
					logger.info(obj.toString());
				}
			}
			
			// obj가 ModelAndView인 경우 -> WEB-INF
			else if (obj instanceof ModelAndView) {
				mav = (ModelAndView) obj;
				pageMove = new String[2];
				pageMove[0] = ""; // forward가 들어있으면 안됨 -> 있으면 webapp로 향함
				pageMove[1] = mav.getViewName();
			}
			
			logger.info("Object가 String, ModelAndView일 경우가 끝난 지점");
			// pageMove가 null이 아닐 경우 각 방식으로 페이지 이동처리
			if (pageMove != null && pageMove.length == 2) {
				// pageMove[0] = redirect or forward or ""
				// pageMove[1] = XXX
				new ViewResolver(req, res, pageMove);
			}
			else if (pageMove != null && pageMove.length == 1) {
				res.setContentType("text/plainl;charset=UTF-8");
				PrintWriter out = res.getWriter();
				out.print(pageMove[0]);
			}
		} // end of 페이지 이동처리 공통코드
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("doGet호출");
		doService(req, res);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("doPost호출");
		doService(req, res);
	}
}

 

<리액트와 pojo3 연결 - HandlerMapping.java>

package com.pojo.step3;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

public class HandlerMapping {
	static Logger logger = Logger.getLogger(HandlerMapping.class);
	
	/***************************************
	 * @param upmu[](upmu[0]=업무명,폴더명 | upmu[1]=메소드명, 기능명, 페이지이름)
	 * @param request -> 1-1, 1-2와는 다르게 인터페이스를 implements하지 않는다
	 * @param response -> HttpServlet
	 * Q. 어디서 받아오는가?
	 * @return Object
	 * 테스트 -> http://localhost:9000/board3/boardList.st3
	 ***************************************/
	
	public static Object getController(String[] upmu, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info(upmu[0]+", "+upmu[1]);
		Controller3 controller = null;
		String path = null;
		Object obj = null;
		ModelAndView mav = null;
		
		// 게시판 구현
		if("board3".equals(upmu[0])) {
			controller = new Board3Controller();
			
			// 게시글 전체 목록 -> html 화면 출력(text/html)
			if("boardList".equals(upmu[1])) {
				obj = controller.boardList(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				else if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// json 게시글 전체 목록 -> json 화면 출력(application/json)
			else if("jsonBoardList".equals(upmu[1])) {
				obj = controller.jsonBoardList(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				else if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 글 입력 - 새글쓰기와 댓글쓰기
			else if("boardInsert".equals(upmu[1])) {
				obj = controller.boardInsert(req, res);
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 이미지 업로드 - 리액트 quill editor 이미지 추가
			else if("imageUpload".equals(upmu[1])) {
				obj = controller.imageUpload(req, res);
				logger.info("imageUpload 호출 => " + obj instanceof String);
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			// 리액트 -
			else if("imageGet".equals(upmu[1])) {
				obj = controller.imageGet(req, res);
				logger.info("imageGet 호출 => " + obj instanceof String);
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 글 수정 - 첨부파일 수정 유무 고려하기
			else if("boardUpdate".equals(upmu[1])) {
				obj = controller.boardUpdate(req, res);
			// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 글 삭제 - 첨부파일 삭제 유무 고려하기
			else if("boardDelete".equals(upmu[1])) {
				obj = controller.boardDelete(req, res);
			// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 글 상세보기
			else if("boardDetail".equals(upmu[1])) {
				obj = controller.boardDetail(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				else if(obj instanceof String) {
					return (String)obj;
				}
			}
		} // end of 게시판 구현
		
		// 인증 관리 구현
		else if("auth".equals(upmu[0])) {
		}
		
		// 회원 관리 구현
		else if("member".equals(upmu[0])) {
		}
		
		// 주문 관리 구현
		else if("order".equals(upmu[0])) {
		}
		
		return obj;
	}
}

 

<리액트와 pojo3 연결 - Controller3.java>

package com.pojo.step3;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// javascript기반의 UI API
// 리액트는 바닐라스크립트 + ES6(주요이슈-spread, module, arrow),ES7 + html 섞어쓰기
public interface Controller3 {
	public Object jsonBoardList(HttpServletRequest req, HttpServletResponse res);
	public Object boardList(HttpServletRequest req, HttpServletResponse res);
	public Object boardDetail(HttpServletRequest req, HttpServletResponse res);
	public Object imageUpload(HttpServletRequest req, HttpServletResponse res);
	public Object imageGet(HttpServletRequest req, HttpServletResponse res);
	public Object boardInsert(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
	public Object boardUpdate(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
	public Object boardDelete(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
}

 

<리액트와 pojo3 연결 - Board3Controller.java>

package com.pojo.step3;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;
import com.util.HashMapBinder;

public class Board3Controller implements Controller3 {
	Logger logger = Logger.getLogger(Board3Controller.class);
	Board3Logic boardLogic = new Board3Logic();

	// 메소드
	@Override
	public ModelAndView boardList(HttpServletRequest req, HttpServletResponse res) {
		logger.info("boardList 호출");
		List<Map<String, Object>> bList = null;
		// 사용자가 조건 검색을 원하는 경우 - 조건 값을 전달할 객체 생성함
		// MyBatis에서는 동적쿼리를 지원하므로 하나로 2가지 경우 사용 가능함
		Map<String, Object> pMap = new HashMap<>();
		HashMapBinder hmb = new HashMapBinder(req);
		hmb.bind(pMap);
		bList = boardLogic.boardList(pMap);
		ModelAndView mav = new ModelAndView(req);
		mav.setViewName("board3/boardList");
		mav.addObject("bList", bList);
		return mav; // 리턴이 mav면 webapp/WEB-INF/views/board3 아래의 jsp
	}

	@Override
	public Object jsonBoardList(HttpServletRequest req, HttpServletResponse res) {
		logger.info("jsonBoardList호출");
		List<Map<String, Object>> bList = null;
		Map<String, Object> pMap = new HashMap<>();
		HashMapBinder hmb = new HashMapBinder(req);
		hmb.bind(pMap);
		bList = boardLogic.boardList(pMap);
		// 오라클 연동 후에 조회 결과가 bList에 담겨있음
		// forward할 때 그 주소번지를 저장해둠 - 화면(jsonBoardList.jsp)에서 접근함 - 키값이 중요함
		req.setAttribute("bList", bList);
		return "forward:board3/jsonBoardList"; // 리턴이 String이면 webapp/board3 아래의 jsp
	}

	@Override
	public Object boardDetail(HttpServletRequest req, HttpServletResponse res) {
		logger.info("boardDetail호출");
		List<Map<String, Object>> bList = null;
		// 전체 조회에 대한 sql문 재사용 가능함 - 1건 조회 경우
		// 하지만 재사용성(상세보기 조회수 업데이트)를 위해 Logic의 메소드 나눔
		Map<String, Object> pMap = new HashMap<>();
		HashMapBinder hmb = new HashMapBinder(req);
		hmb.bind(pMap);
		bList = boardLogic.boardDetailList(pMap);
		logger.info(bList);
		req.setAttribute("bList", bList);
		return "forward:board3/boardDetail";
	}

	/*
	 * INSERT INTO board_master_t(bm_no, bm_title, bm_writer, bm_content, bm_reg,
	 * bm_hit) VALUES(seq_board_no.nextval, #{bm_title}, #{bm_writer},
	 * to_char(sysdate, 'YYYY-MM-DD') , 0)
	 * 
	 * 화면에서 받아올 값 - bm_title, bm_writer, bm_content 그렇지 않은 경우 - bm_reg
	 */
	@Override
	public Object boardInsert(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("boardInsert호출");
		int result = 0;
		// 폼태그 안에 사용자가 입력한 정보(bm_writer, bm_title, bm_content...)를 받아온다
		// req.getParameter("bm_writer")
		// req.getParameter("bm_title")
		// req.getParameter("bm_content")
		// -> 계속 반복해야하기에 HashMapBinder클래스 공통코드 생성
		Map<String, Object> pMap = new HashMap<>();
		logger.info("before ==> " + pMap);
		HashMapBinder hmb = new HashMapBinder(req);
		hmb.multiBind(pMap);
		logger.info("after ==> " + pMap);
		result = boardLogic.boardInsert(pMap);
		String path = "";
		if (result == 1) {
			path = "redirect:/board3/boardList.st3";
		} else {
			path = "redirect:/board3/boardInsertFail";
			res.sendRedirect(path);
		}
		return path;
	}

	@Override
	public Object boardUpdate(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("boardUpdate호출");
		int result = 0;
		Map<String, Object> pMap = new HashMap<>();
		HashMapBinder hmb = new HashMapBinder(req);
		hmb.bind(pMap);
		logger.info(pMap);
		// result값의 변화를 주는 코드(0->1)
		result = boardLogic.boardUpdate(pMap);
		String path = "";
		if (result == 1) {
			path = "redirect:/board3/boardList.st3";
		} else {
			path = "redirect:/board3/boardUpdateFail";
			res.sendRedirect(path);
		}
		return path;
	}

	@Override
	public Object boardDelete(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("boardDelete호출");
		int result = 0;
		Map<String, Object> pMap = new HashMap<>();
		HashMapBinder hmb = new HashMapBinder(req);
		hmb.bind(pMap);
		result = boardLogic.boardDelete(pMap);
		String path = "";
		if (result == 1) {
			path = "redirect:/board3/boardList.st3";
		} else {
			path = "redirect:/board3/boardUpdateFail";
			res.sendRedirect(path);
		}
		return path;
	}

	// quill editor에서 이미지 선택하면 업로드처리 - 물리적인 위치 - 톰캣서버 - chat_banana - webapp - pds
	// 첨부파일 업로드 API는 cos.jar 사용 - maven repo
	@Override
	public Object imageUpload(HttpServletRequest req, HttpServletResponse res) {
		logger.info("imageUpload 호출 성공");
		// 첨부파일 처리에 필요한 변수 선언
		// get방식 - header에 담김 - query string
		// post방식 - encType속성 - request.getParameter("") 사용자가 입력한 값을 읽을 수 없음 
		MultipartRequest multi = null; // post이면서 첨부파일이 있는 형태인 경우 이 클래스가 반드시 필요함
		String realFolder = "D:\\workspace_java\\chat_banana\\src\\main\\webapp\\pds";
		// 첨부파일의 한글처리
		String encType = "utf-8";
		// 첨부파일의 크기
		int maxSize = 50 * 1024 * 1024; // 5MB
		try {
			// 인스턴스화하기 - 인스턴스화가 성공하자마자 pds폴더에 추가됨
			// @param1 - req 요청 - body에 담김(post방식) - 단위테스트x
			// @param2 - 실제 파일이 있는 물리적인 위치
			// @param3 - 첨부파일의 최대 크기값
			// @param4 - 한글 인코딩 설정값
			// @param5 - 같은 이름이 있을 경우 관찰하고 그에대한 대응값 반환
			multi = new MultipartRequest(req, realFolder, maxSize, encType, new DefaultFileRenamePolicy());
			// 거의 즉시 업로드됨 - 파일 크기가 크면 지연상태에 빠짐 - dead lock로 상태 이어지지 않도록 조심!
		} catch (Exception e) {
			logger.info("Exception : " + e.toString());
		}
		// String filename = boardLogic.imageUpload(multi, realFolder);
		Map<String, Object> rMap = boardLogic.imageUpload(multi, realFolder);
		logger.info(rMap);
		// Gson g = new Gson();
		// String temp = g.toJson(rMap);
		// logger.info(temp);
		// logger.info(g);
		String temp = "";
		temp = rMap.get("bs_file").toString() + "," + rMap.get("bs_size").toString();
		logger.info(temp);
		
		return temp;
	}

	// process.env.REACT_APP_CHAT_BANANA_IP+`board3/imageGet.st3?imageName=${res.data}`
	@Override
	public Object imageGet(HttpServletRequest req, HttpServletResponse res) {
		String b_file = req.getParameter("imageName");
		logger.info("imageGet 호출 성공===>" + b_file);
		String filePath = "D:\\workspace_java\\chat_banana\\src\\main\\webapp\\pds"; // 절대경로.
		String fname = b_file;
		logger.info("b_file: 8->euc" + b_file);
		// File은 내용까지 복제되는 것은 아니고 파일명만 객체화해주는 클래스이다
		File file = new File(filePath, b_file.trim());
		String mimeType = req.getServletContext().getMimeType(file.toString());
		// 마임타입이 널이면 아래의 속성값으로 마임타입을 설정
		// -> 브라우저는 해석이 가능한 마임타입은 페이지 로딩 처리,
		// 		해석이 불가능한 마임타입은 다운로드함
		// 강제로 다운로드 처리를 위한 마임타입 변경
		if (mimeType == null) {
			res.setContentType("application/octet-stream");
		}
		String downName = null;
		FileInputStream fis = null;
		ServletOutputStream sos = null;
		try {
			if (req.getHeader("user-agent").indexOf("MSIE") == -1) {
				downName = new String(b_file.getBytes("UTF-8"), "8859_1");
			} else {
				downName = new String(b_file.getBytes("EUC-KR"), "8859_1");
			}
			res.setHeader("Content-Disposition", "attachment;filename=" + downName);
			// 위에서 생성된 파일 문자열 객체를 가지고 파일생성에 필요한 객체의 파라미터 넘김
			fis = new FileInputStream(file);
			sos = res.getOutputStream();
			// 파일 내용을 담을 byte배열을 생성
			byte b[] = new byte[1024 * 10];
			int data = 0;
			while ((data = (fis.read(b, 0, b.length))) != -1) {
				// 파일에서 읽은 내용을 가지고 실제 파일에 쓰기 처리함
				sos.write(b, 0, data);
			}
			// 처리한 내용이 버퍼에 있는데 이것을 모두 처리요청하기
			// 내보내고 버퍼를 비운다 - 버퍼는 크기가 작음(휘발성)
			sos.flush();
		} catch (Exception e) {
			logger.info(e.toString());
		} finally {
			try {
				if (sos != null)
					sos.close();
				if (fis != null)
					fis.close();
			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
		// byte[] fileArray = boardLogic.imageDownload(imageName);
		// logger.info(fileArray.length);
		return null;
	}// end of imageGet

	/**
	 * 이미지 다운로드
	 * 
	 * @param req
	 * @param res
	 */
	public void imageDownload(HttpServletRequest req, HttpServletResponse res) {
		logger.info("imageDownload 호출 성공");
		String b_file = req.getParameter("imageName");
		String filePath = "D:\\workspace_java\\chat_banana\\src\\main\\webapp\\pds"; // 절대경로.
		String fname = b_file;
		logger.info("b_file: 8->euc" + b_file);
		File file = new File(filePath, b_file.trim());
		String mimeType = req.getServletContext().getMimeType(file.toString());
		if (mimeType == null) {
			res.setContentType("application/octet-stream");
		}
		String downName = null;
		FileInputStream fis = null;
		ServletOutputStream sos = null;
		try {
			if (req.getHeader("user-agent").indexOf("MSIE") == -1) {
				downName = new String(b_file.getBytes("UTF-8"), "8859_1");
			} else {
				downName = new String(b_file.getBytes("EUC-KR"), "8859_1");
			}
			res.setHeader("Content-Disposition", "attachment;filename=" + downName);
			fis = new FileInputStream(file);
			sos = res.getOutputStream();
			byte b[] = new byte[1024 * 10];
			int data = 0;
			while ((data = (fis.read(b, 0, b.length))) != -1) {
				sos.write(b, 0, data);
			}
			sos.flush();
		} catch (Exception e) {
			logger.info(e.toString());
		} finally {
			try {
				if (sos != null)
					sos.close();
				if (fis != null)
					fis.close();
			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
	}// end of imageDownload
}

 

<리액트와 pojo3 연결 - HashMapBinder.java>

package com.util;

import java.io.File;
import java.util.Enumeration;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

// 스프링부트에선 RequestParam이 대신해줌, Model, ModelMap
// 사용자가 입력한 값을 Map에 담아준다
// 담을 Map은 컨트롤계층에서 bind메소드호출시 파라미터를 이용해서 원본 주소번지를 받아온다
// 그리고 그 안에 담는다
public class HashMapBinder {
	Logger logger = Logger.getLogger(HashMapBinder.class);
	// 표준 요청 객체
	HttpServletRequest req = null; // 전역변수
	// cos.jar에서 제공하는 요청객체임 - 첨부파일로 post이면서 encType 속성이 적용된 경우 사용할것
	MultipartRequest multi = null;
	// 첨부파일의 업로드 물리적인 경로
	String realFolder = null;
	// 첨부파일 한글처리
	String encType = "UTF-8";
	// 첨부파일 최대크기
	int maxSize = 5 * 1024 * 1024;

	public HashMapBinder() {
	}

	// 생성자 파라미터(지역변수)에 요청객체가 필요한 이유는?
	// -> 생성자의 1역할: 전역변수의 초기화
	public HashMapBinder(HttpServletRequest req) {
		this.req = req;
		realFolder = "D:\\workspace_java\\chat_banana\\src\\main\\webapp\\pds";
	}

	public void multiBind(Map<String, Object> pMap) {
		logger.info("multiBind 호출");
		// 컨트롤 계층에서 생성한 맵 객체 비우기
		pMap.clear();
		try {
			multi = new MultipartRequest(req, realFolder, maxSize, encType, new DefaultFileRenamePolicy());
		} catch (Exception e) {
			logger.info(e.toString());//발생한 예외 이름 출력하기
		}
		Enumeration<String> en = multi.getParameterNames();
		System.out.println("multiBind en => " + en);
		while (en.hasMoreElements()) {
			String key = en.nextElement();
			pMap.put(key, multi.getParameter(key));
			System.out.println("multiBind key => " + key + "  |  multi.getParameter(key) => " + multi.getParameter(key));
		}
		// 첨부파일에 대한 정보 받아오기
		Enumeration<String> files = multi.getFileNames(); // n개만큼 처리가능
		if(files != null) {
			//업로드 대상 파일을 객체로 만듦
			File file = null; // 내용까지 복제되는건 아니다 - 파일명에 대해서만 객체화
			while(files.hasMoreElements()) {
				String fName = files.nextElement();
				String fileName = multi.getFilesystemName(fName);
				pMap.put("bs_file", fileName);
				if(fileName != null && fileName.length() > 1) {
					file = new File(realFolder + "\\" + fileName);
				}
				logger.info(file);
			} // end of while
			// 첨부파일 크기를 담을 수 있는 변수 선언
			double size = 0;
			if(file != null) {
				size = file.length(); // 파일크기를 byte단위로 담아줌
				size = size/1024.0; // byte -> kbyte단위로 바꾸기
				pMap.put("bs_size", size);
			}
		} // end of if
	}

	// 어떤 페이지 어떤 상황에서 공통코드를 재사용 가능하게 할 것인가?
	// 각 업무별 요청 클래스에서 주입받을 객체를 정해서 메소드의 파라미터로 전달받음
	// 전달받은 주소번지 원본에 값을 담아준다
	public void bind(Map<String, Object> pMap) {
		logger.info("bind 호출");
		pMap.clear();
		Enumeration<String> en = req.getParameterNames();
		System.out.println("bind en => " + en);
		while (en.hasMoreElements()) {
			String key = en.nextElement();
			pMap.put(key, req.getParameter(key));
			System.out.println("bind key => " + key + "  |  bind req.getParameter(key) => " + req.getParameter(key));
		}
	}
}

.

<리액트와 pojo3 연결 - Board3Logic.java>

package com.pojo.step3;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.oreilly.servlet.MultipartRequest;

public class Board3Logic {
	Logger logger = Logger.getLogger(Board3Logic.class);
	Board3Dao boardDao = new Board3Dao();

	/**
	 * 글 목록 출력
	 * 
	 * @param pMap
	 * @return
	 */
	public List<Map<String, Object>> boardList(Map<String, Object> pMap) {
		logger.info("boardList 호출" + pMap);
		List<Map<String, Object>> bList = null;
		bList = boardDao.boardList(pMap);

		return bList;
	}

	/**
	 * 글 상세보기
	 * 
	 * @param pMap
	 * @return
	 */
	public List<Map<String, Object>> boardDetailList(Map<String, Object> pMap) {
		logger.info("boardDetailList 호출" + pMap);
		List<Map<String, Object>> bList = null;
		bList = boardDao.boardList(pMap);
		int bm_no = Integer.parseInt(pMap.get("bm_no").toString());
		boardDao.hitCount(bm_no);
		return bList;
	}

	/**
	 * 새 글쓰기
	 * 
	 * @param pMap
	 * @return
	 */
	public int boardInsert(Map<String, Object> pMap) {
		logger.info("boardInsert 호출" + pMap);
		int result = 0;
		int bm_no = 0;
		int bm_group = 0;
		bm_no = boardDao.getBNo();
		pMap.put("bm_no", bm_no);
		// Map안에서 꺼낸다는 건 화면에서 넘어온 값이라는 뜻
		if (pMap.get("bm_group") != null) {
			bm_group = Integer.parseInt(pMap.get("bm_group").toString());
		}

		// 댓글쓰기인 경우
		if (bm_group > 0) {
			logger.info("댓글쓰기 로직 호출");
			boardDao.bStepUpdate(pMap);
			pMap.put("bm_pos", Integer.parseInt(pMap.get("bm_pos").toString()) + 1);
			pMap.put("bm_step", Integer.parseInt(pMap.get("bm_step").toString()) + 1);
		}

		// 새글쓰기인 경우 -> 그룹번호 채번 포함
		else {
			logger.info("새글쓰기 로직 호출 => " + bm_group);
			bm_group = boardDao.getBGroup();
			pMap.put("bm_group", bm_group);
			pMap.put("bm_pos", 0);
			pMap.put("bm_step", 0);
		}
		result = boardDao.boardInsert(pMap);

		// 첨부파일이 존재할 경우
		if (pMap.get("bs_file") != null && pMap.get("bs_file").toString().length() > 1) {
			pMap.put("bm_no", bm_no);
			// 현재 첨부파일은 하나만 담는 것으로 가정하고 처리함
			pMap.put("bs_seq", 1);
			int result2 = 0;
			result2 = boardDao.boardSInsert(pMap);
			logger.info(result2); // 1이면 등록 성공(첨부파일 추가 성공)
		}
		return result;
	}

	/**
	 * 글 수정
	 * 
	 * @param pMap
	 * @return
	 */
	public int boardUpdate(Map<String, Object> pMap) {
		logger.info("boardUpdate 호출" + pMap);
		int result = 0;
		result = boardDao.boardMUpdate(pMap);
		return result;
	}

	/**
	 * 글 삭제
	 * 
	 * @param pMap
	 * @return
	 */
	public int boardDelete(Map<String, Object> pMap) {
		logger.info("boardDelete 호출" + pMap);
		int result = 0;
		result = boardDao.boardMDelete(pMap);
		return result;
	}

	/**
	 * 이미지 업로드
	 * 
	 * @param multi
	 * @param realFolder
	 * @return
	 */
	// 컨트롤 계층에서 multi 주소번치를 넘겨준 이유는
	// 실제 파일명과 파일 크기를 Map에 담기 위해서임
	// 반환값으로는 파일명, 파일크기
	// pageMove[0]에만 값이 들어가고 [1]은 null인 상태 - ArrayIndexOutofBoundException
	public Map<String, Object> imageUpload(MultipartRequest multi, String realFolder) {
		Map<String, Object> pMap = new HashMap<>();
		logger.info("image:" + multi);
		String filename = null;
		String fullPath = null;
		// 첨부파일에 대한 정보를 받아오는 코드 추가
		Enumeration<String> files = multi.getFileNames();
		logger.info("files : " + files);
		// 첨부파일이 존재하나?
		if (files != null) {
			File file = null;
			while (files.hasMoreElements()) {
				String fname = files.nextElement();
				logger.info("fname:" + fname);// bs_file
				filename = multi.getFilesystemName(fname);
				logger.info("filename:" + filename);// 첨부파일이름
				pMap.put("bs_file", filename);
				// file객체 생성하기 -> 왜냐하면 첨부파일의 크기를 알고 싶어요....
				// file = multi.getFile(filename);
				if (filename != null && filename.length() > 1) {
					file = new File(realFolder + "\\" + filename);
				}
				logger.info("file:" + file);
			} // end of while
				// 첨부파일의 크기를 담을 수 있는 변수
			double size = 0;
			if (file != null) {
				size = file.length();// 파일 크기가 저장 5.2MB
				logger.info("size:" + size);
				size = size / (1024);
				logger.info("size/1024:" + size);
				pMap.put("bs_size", size);
			}
			// int result = boardSDao.boardSInsert(pMap);
		} ////// end of if
			// return filename;
		return pMap;
	}

	/**
	 * 이미지 다운로드
	 * 
	 * @param imageName
	 * @return
	 */
	public byte[] imageDownload(String imageName) {
		String fname = null;
		try {
			fname = URLDecoder.decode(imageName, "UTF-8");
			logger.info(fname);
		} catch (UnsupportedEncodingException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		// out.print("b_file: 8->euc"+b_file);
		// out.print("<br>");
		String filePath = "D:\\workspace_java\\chat_banana\\src\\main\\webapp\\pds"; // 절대경로.
		// 가져온 파일이름을 객체화 시켜줌. - 파일이 있는 물리적인 경로가 필요함.
		File file = new File(filePath, fname.trim());

		// 해당 파일을 읽어오는 객체 생성해 줌. - 이 때 파일명을 객체화 한 주소번지가 필요함.
		FileInputStream fis = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();

		try {
			fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}

		int readCount = 0;
		byte[] buffer = new byte[1024];
		byte[] fileArray = null;

		try {
			while ((readCount = fis.read(buffer)) != -1) {
				baos.write(buffer, 0, readCount);
			}
			fileArray = baos.toByteArray();
			fis.close();
			baos.close();
		} catch (IOException e) {
			throw new RuntimeException("File Error");
		}
		return fileArray;
	}
}

댓글