일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 취미
- 개발자
- 보드게임점수
- 광명파티룸
- 보드게임점수계산
- 부천공간대여
- 옥길동파티룸
- 휴식
- 일
- 착한코딩
- MBTI
- 옥길동요거트
- 부천파티룸
- 그릭요거트
- 파티룸
- 구로파티룸
- 서울파티룸
- 존경하는위인
- 스컬킹점수계산
- 옥길요거트
- 옥길그릭요거트
- 옥길파티룸
- mysql
- 스페이스우일
- 해외여행
- 스컬킹
- 웹개발
- 코딩
- 가장존경하는인물
- 스컬킹점수
- Today
- Total
SIMPLE & UNIQUE
2-4강 : 파일, 이미지 업로드 구현 본문
2_4 목표 : 파일과 이미지를 node서버 경로에 업로드하고, 업로드 경로를 db에 삽입한다.
이전 강에서 text 데이터 삽입을 구현했다. 2회차 4강에서는 이미지와 파일을 node 경로에 업로드하는 api를 구현한다. 업로드가 완료되면 업로드 경로를 DB table에 삽입한다.
1. 등록페이지 react 파일을 수정한다.
SoftwareView.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 = {
before_swtcode: props.match.params.swtcode,
selectedFile: null,
}
}
componentDidMount () {
if(this.state.before_swtcode == 'register'){
$('.modifyclass').hide()
}else{
$('.saveclass').hide()
}
}
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) => {
if(this.Swt_toolname_checker === '') {
$('#is_Swt_toolname').addClass('border_validate_err');
alert('툴 이름을 다시 확인해주세요.')
return false;
}
$('#is_Swt_toolname').removeClass('border_validate_err');
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');
if(this.Giturl_checker === '') {
$('#is_Giturl').addClass('border_validate_err');
alert('Github URL을 다시 확인해주세요.')
return false;
}
$('#is_Giturl').removeClass('border_validate_err');
if(this.Comments_checker === '') {
$('#is_Comments').addClass('border_validate_err');
alert('설명을 다시 확인해주세요.')
return false;
}
$('#is_Comments').removeClass('border_validate_err');
if(this.Swt_function_checker === '') {
$('#is_Swt_function').addClass('border_validate_err');
alert('상세기능을 다시 확인해주세요.')
return false;
}
$('#is_Swt_function').removeClass('border_validate_err');
return true;
}
if(this.fnValidate()){
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 등록이 완료되었습니다.')
}
setTimeout(function() {
this.props.history.push('/SoftwareList');
}.bind(this),1500
);
}else{
alert('작업중 오류가 발생하였습니다.')
}
} catch (error) {
alert('작업중 오류가 발생하였습니다.')
}
}
};
handleFileInput(type, e){
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],
})
setTimeout(function() {
if(type =='manual'){
this.handlePostMenual()
}else{
this.handlePostImage(type)
}
}.bind(this),1
);
}
handlePostMenual(){
const formData = new FormData();
formData.append('file', this.state.selectedFile);
return axios.post("/api/upload?type=uploads/swmanual/", formData).then(res => {
this.setState({menualName : res.data.filename})
$('#is_MenualName').remove()
$('#upload_menual').prepend('<input id="is_MenualName" type="hidden"'
+'name="is_MenualName" value="/swmanual/'+this.state.menualName+'"}/>')
}).catch(error => {
alert('작업중 오류가 발생하였습니다.', error, 'error', '닫기')
})
}
handlePostImage(type){
const formData = new FormData();
formData.append('file', this.state.selectedFile);
return axios.post("/api/upload?type=uploads/image/", formData).then(res => {
if(type =='file'){
this.setState({fileName : res.data.filename})
$('#is_MainImg').remove()
$('#uploadimg').remove()
$('#upload_img').prepend('<img id="uploadimg" src="/image/'
+this.state.fileName+'"/>')
$('#upload_img').prepend('<input id="is_MainImg" type="hidden"'
+'name="is_MainImg" value="/image/'+this.state.fileName+'"}/>')
}else if(type =='file2'){
this.setState({fileName2 : res.data.filename})
$('#is_LabelImg').remove()
$('#uploadimg2').remove()
$('#upload_img2').prepend('<img id="uploadimg2" src="/image/'
+this.state.fileName2+'"/>')
$('#upload_img2').prepend('<input id="is_LabelImg" type="hidden"'
+'name="is_LabelImg" value="/image/'+this.state.fileName2+'"}/>')
}
}).catch(error => {
alert('작업중 오류가 발생하였습니다.')
})
}
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_Swtcode" type="hidden" name="is_Swtcode" />
<input id="is_Email" type="hidden" name="is_Email" value="guest" />
<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">
<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>
<div class="btn_confirm mt20" style={{"margin-bottom": "44px"}}>
<Link to={'/SoftwareList'} 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:\react200에 uploads폴더를 생성하고, uploads폴더에 image폴더와 swmanual폴더를 추가해준다. 메뉴얼 파일은 swmanual폴더에, 이미지 파일은 image폴더에 업로드된다.
2. node 경로의 파일들을 수정하고, 업로드 파일이 저장될 경로를 생성한다.
node 경로 C:\react200의 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);
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:\react200에서
파일 업로드에 필요한 패키지들을 설치해준다.
파일을 api 호출을 통해 react 페이지에서 node 서버로 이동시키려면 데이터 타입이 "multipart/form-data" 이어야 한다.
multer라는 패키지가 "multipart/form-data" 를 지원해준다.
moment는 업로드된 파일명 앞에 시간정보를 붙여서 새로운 파일명을 생성해 주기위한 목적으로 사용했다.
시간 포맷을 지원해주는 패키지이다.
npm install --save multer
npm install --save moment
C:\react200\routes 경로에서
UploadRout.js 파일을 만들고 아래 내용을 붙여넣는다.
var express = require('express');
var router = express.Router();
var upload = require('./fileupload');
var multer = require('multer');
router.post("/", (req, res, next) => {
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)
return res.json({filename:req.file.filename});
});
});
module.exports = router;
C:\react200\routes 경로에 fileupload.js 파일을 만들고 아래 내용을 붙여넣는다.
const multer = require('multer');
const moment = require('moment');
const storage = multer.diskStorage({
destination: function(req, file, cb) {
try {
var type = req.query.type;
cb(null, type);
} catch (error) {
console.log(error)
}
},
filename: function(req, file, cb) {
cb(null, moment().format('YYYYMMDDHHmmss') + "_" + file.originalname);
}
});
const upload = multer({ storage: storage }).single("file");
module.exports = upload;
node 경로 C:\react200에
파일과 이미지가 저장될 폴더를 생성해준다.
3. 실제 파일을 업로드 해보고, 등록페이지 form을 저장한다.
아래와 같이 text 데이터와 메뉴얼파일, 이미지파일을 등록해준다.
업로드 파일은 선택되는 순간, 미리보기 이미지가 보이며
node 서버 경로에실제 파일이 저장된 것을 확인한다.
등록페이지에서 저장버튼을 누르면 아래 쿼리 로그와 같이
업로드된 파일명이 저장된다.
https://taling.me/Talent/Detail/19341
강의 문의 : ljung5@naver.com
'탈잉 강의 자료 > 2020_비전공자도 가능한 웹 프로젝트' 카테고리의 다른 글
3-1강 : 삭제 기능 구현, ALERT 패키지 사용, GITHUB 사용 (0) | 2020.08.03 |
---|---|
2-5강 : 수정 페이지 구현, UPDATE쿼리 실행 (0) | 2020.08.02 |
2-3강 : 등록 페이지 구현, INSERT쿼리 실행 (0) | 2020.08.01 |
2-2강 : AWS RDS 서버 기타 설정 (0) | 2020.08.01 |
2-1강 : AWS 회원 가입, RDS 인스턴스 생성, DB 서버 연결 (0) | 2020.08.01 |