스터디/KAKAOCLOUDSCHOOL

[Node & MariaDB] 개발자 지망생 스터디 - 18일차

shineIT 2022. 11. 25. 20:54

17일차 실습 이어서..

 


 

더보기

Node.js에서 SQL을 이용해서 Select 구문을 실행했을 때 결과는 무조건 배열

Select 구문을 사용하게 되면 행과 열이 몇 개가 나올지 알 수 없음.

[{컬럼이름:값,컬럼이름:값...}, {}, {}]

 

SELECT count(*) from goods;

>> 30

results : [{count(*):30}]

results[0].count(*)

5. 데이터 일부분 가져오기

1) App.js 파일에 요청을 처리하는 메서드를 구현

//데이터 일부분 가져오기
//URL은 /item/list 
//파라미터는 pageno 1개 인데 없으면 1로 설정
app.get('/item/list', (req, res) => {
    //파라미터 읽어오기
    let pageno = req.query.pageno;
    if(pageno == undefined){
        pageno = 1;
    }
    console.log(pageno);
    //브라우저에서 테스트 - 콘솔 확인
    //localhost:9000/item/list
    //localhost:9000/item/list?pageno=3

    //item 테이블에서 itemid 를 가지고 내림차순 정렬해서 
    //페이지 단위로 데이터 가져오기
    //select * from item order by itemid desc limit 시작번호, 5
    //시작번호=(pageno-1)*5

    //파라미터는 무조건 문자열입니다.
    //파라미터를 가지고 산술연산을 할 때는 숫자로 변환을 수행
    
    //성공 과 실패 여부를 저장
    let result = true;
    //성공했을 때 데이터를 저장
    let list;
    //데이터 목록 가져오기
    connection.query(
        "select * from goods order by itemid desc limit ?, 5", 
        [(parseInt(pageno)-1)*5], (err, results, fields) => {
            if(err){
                console.log(err);
                result = false;
            }else{
                list = results;
                //console.log(list);
            }
            
            //goods 테이블의 전체 데이터 개수를 가져오기
            let cnt = 0;
            connection.query("select count(*) cnt from goods",
                [], (err, results, fields)=>{
                if(err){
                    //에러가 발생했을 때
                    console.log(err);
                    result = false;
                }else{
                    //정상적으로 구문이 실행되었을 때
                    //하나의 행만 리턴되므로 0 번째 데이터를 읽어내면 됩니다.
                    cnt = results[0].cnt;
                }

                //응답 생성해서 전송
                if(result === false){
                    res.json({"result":false});
                }else{
                    res.json({"result":true, "list":list, "count":cnt});
                }
        });
    });
});

 

 

2) index.html 파일에 페이지 단위 보기 구현

  • body부분에 페이지 단위 전체 데이터를 가져올 버튼을 생성
<a href="#" id="listbtn">페이지 단위 전체 데이터 가져오기</a><br/>
  • 스크립트 코드 추가
        let listbtn = document.getElementById("listbtn");
            //현재 페이지 번호를 저장할 변수를 선언
            let pageno = 1;
            listbtn.addEventListener("click", (e) => {
                let request = new XMLHttpRequest();
                request.open('GET', '/item/list?pageno=' + pageno);
                request.send('');
                request.addEventListener('load', () => {
                    //출력 영역을 초기화
                    content.innerHTML = '';
                    //데이터를 파싱
                    let data = JSON.parse(request.responseText);
                    if(data.result === true){
                        //데이터 개수 와 목록을 가져오기
                        let count = data.count;
                        let list = data.list;

                        //출력 내용 만들기
                        let display = "<div align='center' class='body'>";
                        display += "<h2>상품 목록</h2>";
                        display += "<table border='1' id='tbldata'>";
                        //전체 데이터 개수 출력
                        display += "<tr><td colspan='3' align='right'>";
                        display += "전체 데이터 개수:" + count + "개</td></tr>"
                        
                        //항목 헤더 출력
                        display += "<tr class='header'>";
                        display += "<th width='80'>ID</th>";
                        display += "<th width='320'>상품명</th>";
                        display += "<th width='100'>가격</th>";
                        display += "</tr>";

                        //데이터 목록 출력
                        for(item of list){
                            display += "<tr class='record'>";
                            display += "<td align='center'>" 
                                + item.itemid + "</td>";
                            display += "<td align='left'>" 
                                + item.itemname + "</td>";
                            display += "<td align='right'>" 
                                + item.price + " 원</td>";
                            display += "</tr>";
                        }
                        display += "</table></div>";

                        //더보기 구현
                        //현재 페이지가 마지막 페이지가 아닌 경우만 출력
                        if((pageno-1) * 5 < count){
                            display += "<table align='center'";
                            display += " width='500' id='tblbtn'>";
                            display += "<tr><td align='center' colspan='3'>";
                            display += "<span id='addbtn'><a href='#'>더보기</a></span></td>";
                            display += "</tr></table>";
                        }
                        content.innerHTML = display;

                        //더보기 버튼을 눌렀을 때 처리
                        let addbtn = document.getElementById("addbtn");

                        //addbtn이 존재하는 경우에만 수행
                        if(addbtn != undefined){
                            addbtn.addEventListener('click', (e) => {
                                pageno = pageno + 1;
                                let request = new XMLHttpRequest();
                                request.open('GET', '/item/list?pageno=' + pageno);
                                request.send('');
                                //전체 데이터 개수보다 더 많이 출력하면 더보기 영역을 삭제
                                if((pageno) * 5 >= data.count){
                                    pageno = pageno - 1;
                                    document.getElementById("tblbtn").remove();
                                }

                                //데이터를 가져오면
                                request.addEventListener('load', ()=>{
                                    let data = JSON.parse(request.responseText);
                                    let list = data.list;
                                    //데이터 테이블 출력
                                    const table = document.getElementById('tbldata');
                                    let display = "";
                                    for(item of list){
                                        display += "<tr class='record'>";
                                        display += "<td align='center'>" + item.itemid +
                                            "</td>";
                                        display += "<td align='left'>" + item.itemname +
                                            "</td>";
                                        display += "<td align='right'>" + item.price +
                                            " 원</td>";
                                        display += "</tr>";
                                    }
                                    table.innerHTML += display;
                                })
                            });
                        }


                    }else{
                        content.innerHTML = 
                            '<p>데이터를 가져오는데 실패</p>';
                    }
                });
            });

6. 데이터 상세보기

  • 데이터 한개의 정보를 전부 가져와서 출력
  • 테이블에서 데이터 1개를 가져오는 방법은 기본키나 unique 속성을 이용한 조회만 가능
  • 기본키나 unique 속성의 값을 받아서 서버에서 처리
  • itemid를 URL에 포함시켜 받아서 처리하는 구조로 구현

1)App.js 파일에 상세보기를 위한 코드를 추가

//상세보기 처리를 위한 코드
app.get('/item/detail/:itemid', (req, res) => {
    //파라미터 읽기
    let itemid = req.params.itemid;
    //itemid를 이용해서 1개의 데이터를 찾아오는 SQL을 실행
    connection.query("select * from goods where itemid=?",
     [itemid], (err, results, fields) => {
        if(err){
            console.log(err);
            res.json({"result":false});
        }else{
            console.log(results);
            res.json({"result":true, "item":results[0]});
        }
    });
});
  • 테스트는 localhost:9000/item/detail/번호

2)index.html 파일에서 상세보기를 위한 코드를 작성

  • itemname을 출력하는 코드를 수정 
display += "<td align='left'>" + "<a href='#' id='item" + item.itemid + "'>" + item.itemname + "</a></td>";
  • itemname을 클릭했을 때 동작할 스크립트 코드를 작성
      //itemname을 눌렀을 때 수행할 코드
            //링크 하나 하나 이벤트 처리를 하는 것은 자원 낭비
            //부모에 이벤트 처리 코드를 작성
            content.addEventListener('click', (e)=>{
                //클릭한 대상의 id가 item 으로 시작하는 경우만 동작
                if(e.target.id.startsWith('item')){
                    //클릭한 대상의 아이디에서 item을 제외한 부분 - itemid
                    let itemid = e.target.id.substring(4).trim();
                    //alert(itemid);
                    let request = new XMLHttpRequest();
                    request.open('GET', '/item/detail/' + itemid);
                    request.send('');
                    request.addEventListener('load', () => {
                        let data = JSON.parse(request.responseText);
                        if(data.result == true){
                            //데이터 가져오기
                            let item = data.item;
                            //출력 내용 생성
                            let display = "<div align='center' class='body'>";
                            display += '<h2>상세보기</h2>';
                            display += '<table>';
                            display += "<tr><td><img src='/img/" 
                                + item.pictureurl + "'/><td>";
                            
                            display += "<td align='center'><table>";
                            
                            display += "<tr height='50'><td width='80'>상품명</td>";
                            display += "<td width='160'>" + item.itemname + "</td>";
                            display += "<tr height='50'><td width='80'>가격</td>";
                            display += "<td width='160'>" + item.price + " 원</td>";
                            display += "<tr height='50'><td width='80'>비고</td>";
                            display += "<td width='160'>" + item.description + 
                                "</td></tr>";
                            
                            display += "</table></td></tr></table>";

                            
                            content.innerHTML = display;
                        }
                    });
                }
            });

7. 첨부 파일 (이미지) 다운로드

  • 웹 브라우저에서는 파일에 링크가 걸려있으면 자신이 출력할 수 있는 파일은 출력을 하고 출력할 수 없는 파일은 다운로드를 수행

1) 이미지 파일 출력하는 부분을 수정

display += "<tr><td><a href='/img/" + item.pictureurl + "'>"  + "<img src='/img/" + item.pictureurl + "'/></a><td>";

2)App.js 파일에 /img/이미지파일명 을 처리하는 코드를 추가

 

 

//이미지 다운로드 처리
app.get('/img/:pictureurl', (req, res) => {
    let pictureurl = req.params.pictureurl;
    //이미지 파일의 절대경로를 생성
    let file = 
    "C:\Users\Administrator\Documents\node\mariadb\public\img" 
        + "/" + pictureurl; 
    console.log(__dirname);
    //파일 이름을 가지고 타입을 생성
    let mimetype = mime.lookup(pictureurl);
    res.setHeader('Content-disposition', 
        'attachment; filename=' + pictureurl);
    res.setHeader('Content-type', mimetype);
    //파일의 내용을 읽어서 res에 전송
    let filestream = fs.createReadStream(file);
    filestream.pipe(res);
})

 

8. API 테스트를 위한 postman 프로그램 다운로드 및 설치

  • 홈페이지 또는 맥의 경우 Brew를 통해 설치 가능함 (설치 방법은 구글을 이용해 쉽게 찾을수 있음)

 

9. 데이터 삽입

  • 데이터 삽입은 삽입 화면을 먼저 출력하고 데이터를 입력받은 후 데이터를 서버에게 전송하면 처리
  • 데이터 삽입은 get 방식이 post 방식으로 처리
    • post 방식은 화면을 만들지 않고 웹브라우저에서 테스트가 불가능
    • 서버에 post 방식 요청을 만들고 테스트를 할 때는 별도의 프로그램을 설치해서 하게 됨

1)App.js 파일에 데이터 삽입을 위한 코드를 작성

  • 어떤 데이터를 받아야 하는지 결정

itemid: 가장 큰 itemid를 찾아서 +1
itemname, price, description, pictureurl(파일) 은 직접 입력
updatedate는 현재 날짜를 문자열로 입력

  • 삽입, 삭제, 갱신 작업이 발생하면 updatedate.txt 파일에 발생한 시간을 기록
    • 현재 데이터가 업데이트 된 시간을 알기 위해서 기록
  • App.js 파일에 현재 날짜를 문자열로 리턴하는 함수 와 현재 날짜 및 시간을 문자열로 리턴하는 함수 작성
더보기

1월부터 12월까지 날의 숫자를 배열로 생성

[0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

//현재 날짜를 문자열로 리턴하는 함수
//요즈음 등장하는 자바스크립트 라이브러리들의 샘플 예제는 
//특별한 경우가 아니면 function 을 사용하지 않습니다.
const getDate = () => {
    let date = new Date();
    let year = date.getFullYear();
    //월은 +1을 해야 우리가 사용하는 월이 됩니다.
    let month = date.getMonth() + 1;
    let day = date.getDate();
    month = month >= 10 ? month : '0' + month;
    day = day >= 10 ? day : '0' + day;
    return year + "-" + month + "-" + day;
}

//날짜 와 시간을 리턴하는 함수
const getTime = () => {
    let date = new Date();
    let hour = date.getHours();
    let minute = date.getMinutes();
    let second = date.getSeconds();

    hour >= 10 ? hour : '0' + hour;
    minute >= 10 ? minute : '0' + minute;
    second >= 10 ? second : '0' + second;

    return getDate() + " " 
        + hour + ":" + minute + ":" + second;
}
  • 삽입 요청을 처리하는 함수 추가
//데이터 삽입을 처리해주는 함수
app.post('/item/insert', upload.single('pictureurl'), 
    (req, res) => {
    //파라미터 읽어오기
    const itemname = req.body.itemname;
    const description = req.body.description;
    const price = req.body.price;

    //파일 이름 - 업로드하는 파일이 없으면 default.png
    let pictureurl;
    if(req.file){
        pictureurl = req.file.filename
    }else{
        pictureurl = 'default.jpg';
    }

    //가장 큰 itemid 찾기
    connection.query("select max(itemid) maxid from goods",
    [], (err, results, fields) => {
        let itemid;
        //최대값이 있으면 + 1 하고 없으면 1로 설정
        if(results.length > 0 ){
            itemid = results[0].maxid + 1;
        }else{
            itemid = 1;
        }

        //데이터 삽입
        connection.query("insert into goods(" + 
            "itemid, itemname, price, description," 
            + "pictureurl, updatedate) values(?, ?, ?, ?, ?, ?)",
            [itemid, itemname, price, description, pictureurl,
            getDate()], (err, results, fields) => {
            if(err){
                console.log(err);
                res.json({"result":false});
            }else{
                //현재 날짜 및 시간을 update.txt에 기록
                const writeStream = fs.createWriteStream('./update.txt');
                writeStream.write(getTime());
                writeStream.end();

                res.json({"result":true});
            }
        })
    });
})

2)index.html 파일에 삽입 요청을 위한 코드를 작성

  • 삽입 요청을 위한 DOM을 추가
<a href="#" id="insertbtn">데이터 삽입</a><br/>
  • DOM을 클릭했을 때 처리하는 코드를 script에 작성
            let insertbtn = document.getElementById("insertbtn");
            insertbtn.addEventListener('click', (e) => {
                //삽입화면 출력
                content.innerHTML = '';
                let html = 
                    `
                        <div>
                            <p></p>
                            <form id='insertform'
                            enctype='multipart/form-data'
                            method='post'>
                            아이템이름<input type='text'
                            name='itemname' id='itemname'/><br/>
                            가격<input type='text'
                            name='price' id='price'/><br/>
                            설명<input type='text'
                            name='description' id='description'/><br/>
                            이미지<input type='file'
                            name='pictureurl' id='pictureurl'/><br/>
                            <input type='submit' value='삽입'/>
                            </form></div>
                    `
                    content.innerHTML = html;

                    //폼안에서 삽입 버튼을 눌렀을 때 처리
                    let f = document.getElementById('insertform');
                    if(f != undefined){
                        f.addEventListener('submit', (e) => {
                            //기본 이벤트 제거
                            e.preventDefault();
                            //폼 데이터 찾아오기
                            const formData = new FormData(
                                document.getElementById("insertform"));
                            //폼 데이터를 전송
                            let request = new XMLHttpRequest();
                            request.open("POST", "/item/insert", true);
                            request.send(formData);
                            request.addEventListener('load', () => {
                                let data = JSON.parse(request.responseText);
                                if(data.result){
                                    //데이터 다시 불러오기
                                    document.getElementById("listbtn").click();
                                }else{
                                    alert("삽입 실패");
                                }
                            })
                        })
                    }
            });

 

10. 데이터 삭제

  • 기본키를 매개변수로 받아서 삭제를 합니다.
  • 삭제는 GET 이나 POST 별 상관 없습니다.

1)App.js 파일에 itemid를 받아서 데이터를 삭제하는 메서드를 구현

//데이터를 삭제하는 함수
app.post('/item/delete', (req, res) => {
    //post 방식으로 전송된 데이터 읽기
    let itemid = req.body.itemid;

    //itemid를 받아서 goods 테이블에서 삭제하기
    connection.query("delete from goods where itemid=?",
     [itemid], (err, results, fields)=>{
        if(err){
            console.log(err);
            res.json({"result":false});
        }else{
            //현재 날짜 및 시간을 update.txt에 기록
            const writeStream = fs.createWriteStream(
                './update.txt');
            writeStream.write(getTime());
            writeStream.end();

            res.json({"result":true});
        }
    });

});

2)index.html 파일에서 삭제를 구현

  • 상세보기 출력하는 부분에 삭제를 위한 DOM을 추가
//삭제를 위한 DOM을 추가
display += "<tr>" + "<td colspan='2' align='center' width='240'>"
+ "<a href='#' id='deletebtn'>데이터 삭제</a>" + "</td></tr>";
  • 상세보기를 출력하는 하단에 삭제를 위한 스크립트 코드를 추가
//데이터 삭제를 눌렀을 때 처리
let deletebtn = document.getElementById("deletebtn");
if(deletebtn != undefined){
	deletebtn.addEventListener('click', (e) => {
	//폼이 없는 경우의 POST 방식 파라미터 만들기
	let params = 'itemid=' + item.itemid;

	let request = new XMLHttpRequest();
	request.open('POST', '/item/delete');
	//폼이 아닌 경우는 form 형식으로 인코딩
	request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	request.send(params);
	request.addEventListener('load', () => {
		let data = JSON.parse(request.responseText);
		if(data.result){
			document.getElementById("listbtn").click();
			alert("삭제 성공");
		}else{
			alert("삭제 실패");
		}
	})
})

 

11. 데이터 수정

  • 현재 데이터를 수정할 수 있도록 화면에 출력하고 데이터를 입력받아서 수정
    • 텍스트는 입력받은 내용을 수정하면 되는데 이전 내용을 화면에 출력한 상태에서 내용을 고치지 않았어도 set으로 출력하면 되는데 파일은 input 에 설정할 수 없음

1) 수정 화면을 위한 html 파일을 생성 - public 디렉토리에 update.html

<form method="post" id="updateform" 
    enctype="multipart/form-data">
    아이템 아이디:<input type="text" name="itemid" id="itemid"/>
    <br/>
    아이템 이름:<input type="text" name="itemname" id="itemname"/>
    <br/>
    아이템 가격:<input type="text" name="price" id="price"/>
    <br/>
    설명:<input type="text" name="description" id="description"/>
    <br/>
    새로운 이미지:<input type="file" name="pictureurl" />
    <br/>
    이전 이미지<img width="100" height="100" id="picture">
    <br/>
    <!-- hidden 은 화면에 출력되지는 않지만 가지고 있어야 하는
    데이터가 있을 때 사용-->
    <input type="hidden" name="oldpictureurl" id="oldpictureurl"/>
    <br />
    <input type="submit" value="수정" />
</form>

2)App.js 파일에 수정의 get 요청이 오면 처리할 함수를 작성

//수정을 get으로 요청했을 때 - 수정 화면으로 이동
app.get('/item/update', (req, res) => {
    //public 디렉토리의 update.html을 읽어내서 리턴
    fs.readFile('./public/update.html', (err, data)=>{
        res.end(data);
    });
});

3)index.html 파일에 수정 화면으로 이동하기 위한 코드를 작성

  • 상세보기를 출력하는 부분에 데이터 수정 DOM을 추가(삭제를 추가한 부분의 위나 아래에 추가)
//수정을 위한 DOM을 추가
display += "<tr>" + "<td colspan='2' align='center' width='240'>"+ "<a href='#' id='updatebtn'>데이터 수정</a>" + "</td></tr>";
  • 수정 버튼을 눌렀을 때 처리를 위한 스크립트 코드를 추가
//데이터 수정을 눌렀을 때 처리
let updatebtn = document.getElementById("updatebtn");
if(updatebtn != undefined){
    updatebtn.addEventListener('click', (e) => {
        let request = new XMLHttpRequest();
        request.open('GET', '/item/update');
        request.send('');
        request.addEventListener('load', () => {
            let html = request.responseText;
            content.innerHTML = html;
            //수정은 수정하기 위한 원본 데이터를 화면에 출력
            document.getElementById("itemid").value = 
                item.itemid;
            document.getElementById("itemid").readOnly = 
                true;
            document.getElementById("itemname").value = 
                item.itemname;
            document.getElementById("price").value = 
                item.price;
            document.getElementById("description").value = 
                item.description;
            //원본의 이름을 숨김
            document.getElementById("oldpictureurl").value = 
                item.pictureurl;
            //원본을 다른 방식으로 출력
            document.getElementById("picture").src = 
                "/img/" + item.pictureurl;

            //수정 폼을 찾아오기
            let updateform = document.getElementById("updateform");
            if(updateform != undefined){
                //폼 안의 submit 버튼을 눌렀을 때
                updateform.addEventListener('submit', (e) => {
                    //기본 이벤트 제거
                    e.preventDefault();

                    //전송할 데이터 생성 - 폼 안에 입력한 데이터 생성
                    const formData = new FormData(updateform);

                    //서버에게 요청
                    let request = new XMLHttpRequest();
                    request.open("POST", "/item/update");
                    request.send(formData);

                    //응답을 받았을 때 처리
                    request.addEventListener('load', () => {
                        let data = JSON.parse(request.responseText);
                        if(data.result === true){
                            document.getElementById("listbtn").click();
                            alert("성공");
                        }else{
                            alert("실패");
                        }
                    });
                })
            }
        })
    })
}

4) App.js 파일에 실제 수정을 처리하는 코드를 작성

app.post('/item/update', upload.single('pictureurl'), (req, res) => {
   //파라미터 가져오기
   const itemid = req.body.itemid;
   const itemname = req.body.itemname;
   const price = req.body.price;
   const description = req.body.description;
   //예전 파일 이름
   const oldpictureurl = req.body.oldpictureurl;

   //수정할 파일 이름 만들기
   let pictureurl;
   //새로 선택한 파일이 있다면
   if(req.file){
        pictureurl = req.file.filename;
   }else{
        pictureurl = oldpictureurl;
   }
   //데이터베이스 작업
   connection.query("update goods set itemname=?, price=?," +  
    "description=?, pictureurl=?, updatedate=? where itemid=?",
     [itemname, price, description, pictureurl, getDate(),
    itemid], 
        (error, results, fields) => {
        //console.log(results);
        //console.log(fields);
        if(error){
            //에러가 발생한 경우
            console.log(error);
            res.json({"result": false});
        }else{
            //성공했을 때 처리
            const writeStream = 
                fs.createWriteStream("./update.txt");
            writeStream.write(getTime());
            writeStream.end();
            res.json({"result": true});
        }
   })
});