국비 코딩 풀스택 수업 19일차
요약정리
이미지는 서버에 따로 관리(고유키 code로 관리)
게시글 삭제시 서버에서 이미지 먼저 삭제후, 글 삭제
게시글에 판매자 정보(이름, 주소, 연락처)를 넣지 않음. 왜냐하면 판매자 정보가 변경되면
정보가 저장되는 곳이 2곳이 되기때문, 정보는 한곳에 모여있어야함.
//upload가 req를 건드려서 seller를 없애서
순서를 upload => auth.checkToken으로 함
router.post('/insert.json', upload.single("image"), auth.checkToken, async function(req, res, next)
코드 내부에 일일히 console.log로 찍어보면서 확인.
seller에 값이 안들어가는 것을 발견하고 순서변경하니깐 됬음
보통 판매자전용 홈페이지는 사이트내에서 안보이게 만듬
그래서 그냥 app.vue에 등록안해줌.
따로 SellerHome.vue 라는 홈페이지를 만들어서 관리해줬음.
물품관리 테이블 만들기
오전일과(판매자 모델, 이미지 모델 만들기)
exp > itemmodel.js
외래키, members에 있는 아이디만 가능
// npm i mongoose --save
var mongoose = require('mongoose');
// npm i mongoose-sequence --save, 물품번호
const AutoIncrement = require('mongoose-sequence')(mongoose);
var Schema = mongoose.Schema;
var itemSchema = new Schema({
_id : Number, //물품번호, 숫자, 기본키, 고유값, not null
seller : String, //외래키, members에 있는 아이디만 가능
name : { type:String, default:'' }, // 물품명, 문자
content : { type:String, default:'' }, // 물품설명, 문자
price : { type:Number, default:0 }, // 가격, 숫자
quantity : { type:Number, default:0 }, // 수량, 숫자
category : { type:String, default:'' }, // 분류, 문자 1000, 2000
regdate : { type:String, default:'' }, // 등록일, 문자
});
// 스키마 항목중에서 시퀀스 필요한 것 설정
itemSchema.plugin(AutoIncrement,
{id:"SEQ_ITEM_CODE", inc_field:'_id'});
// 몽고DB에 item컬렉션 생성됨.
module.exports = mongoose.model('item', itemSchema);
exp > itemimagemodel.js
// npm i mongoose --save
var mongoose = require('mongoose');
// npm i mongoose-sequence --save, 물품번호
const AutoIncrement = require('mongoose-sequence')(mongoose);
var Schema = mongoose.Schema;
var itemimageSchema = new Schema({
_id : Number, // 물품번호, 숫자, 기본키, 고유값, not null
code : Number, // 물품의 코드, 외래키, not null
filedata : { type:Buffer, default:null }, //파일데이터
filesize : { type:Number, default:0 }, // 파일의 크기 용량
filename : { type:String, default:'' }, // 파일 이름
filetype : { type:String, default:'' }, // 파일의 종류, jpeg, png 등..
idx : { type:Number, default:1 }, // 대표이미지 표시
regdate : { type:String, default:'' }, // 등록일, 문자
});
// 스키마 항목중에서 시퀀스 필요한 것 설정
itemimageSchema.plugin(AutoIncrement,
{id:"SEQ_ITEMIMAGE_NO", inc_field:'_id'});
// 몽고DB에 itemimage컬렉션 생성됨.
module.exports = mongoose.model('itemimage', itemimageSchema);
이미지 따로 관리(고유키 code로 이미지를 관리)
삭제시 이미지 먼저 삭제하고, 글을 삭제
외래키는 아이템에 있는것만 올려야함 아니면 불일치하기때문에(제약조건 위배)
app.vue item등록
라우트 아이템 작업
게시글에 판매자 정보를 안들고오고 기본키만 들고와야함
기본키만 들고와야함. 중요중요중요중요중요중요중요중요중요중요
왜냐하면 판매자의 정보가 바뀔 수 있기때문
그렇게 되면 두군데에 정보가 나뉘기때문
정보는 한곳에 모여있어야함.(한곳에 모여있고 필요한 곳은 키만 줘야함 키 = 판매자이름)
카피x 조회로 정보를 보여지게해야함
item.js(post insert, put update, get select)
var express = require('express');
var router = express.Router();
const multer = require('multer');
const upload = multer( {storage : multer.memoryStorage()} );
require('moment-timezone');
var moment = require('moment');
moment.tz.setDefault('Asia/Seoul');
var Item = require('../models/itemmodel');
// 외부에서 사용
const auth = require('../token/auth');
// 물품목록조회(페이지네이션, 검색기능 미포함)
// 127.0.0.1:3000/item/select.json
router.get('/select.json', auth.checkToken, async function(req, res, next) {
try {
// 1. 조회조건
const query = { seller : req.body.SID }
//2. 조회 하기
const result = await Item.find(query).sort({_id:-1});
if(result !== null){
return res.send( {status:200, result:result} );
}
return res.send( {status : 0} );
}
catch(e){
console.error(e);
return res.send({status:-1, result:e});
}
});
// 물품수정
// 127.0.0.1:3000/item/update.json
// {"code", "name", "content", "price", "quantity", "category"}
router.put('/update.json', auth.checkToken, async function(req, res, next) {
try {
// 1. 전달값 받기
const seller = req.body.SID;
// 2. 수정조건(물품코드와 판매자 정보가 둘 다 일치하는것)
// {$and : [ _id:3, seller:'a']}
// { _id:3, seller:'a' }
// 둘 다 같은 말이지만 위에껄로 써야 조건을 넣을 수 있음.
const query = {
_id: Number(req.body.code),
seller : seller
}
//3. 변경(결과를 읽어와서 변경만 바꾼다음 다시 저장)
const obj = await Item.findOne(query);
if(obj !== null){
//시간이 걸리는 것은 await을 넣음
obj.name = req.body.name;
obj.content = req.body.content;
obj.price = Number(req.body.price);
obj.quantity = Number(req.body.quantity);
obj.category = req.body.category;
//4. 저장하기
const result = await obj.save();
if(result !== null){
return res.send( {status:200} );
}
}
return res.send( {status : 0} );
}
catch(e){
console.error(e);
return res.send({status:-1, result:e});
}
});
// 물품등록
// AAA 과일, BBB: 생선, CCC:전자제품, DDD 농산물
// {
// "seller":'외래키', "name":'사과', "content":"내용"
// "price":120, "quantity":340, "category":"AAA"
// }
// 127.0.0.1:3000/item/insert.json
router.post('/insert.json', auth.checkToken,
async function(req, res, next) {
try {
// 1. 전달값 받기
const seller = req.body.SID; //판매자 정보 받기
// 2. 저장할 객체 생성
const obj = new Item();
obj.seller = seller;
obj.name = req.body.name;
obj.content = req.body.content;
obj.price = Number(req.body.price);
obj.quantity = Number(req.body.quantity);
obj.category = req.body.category;
obj.regdate = moment().format('YYYY-MM-DD HH:mm:ss');
// 3. DB에 저장
const result = await obj.save();
// 4. 결과에 따른 반환값
if(result !== null){
return res.send( {status:200} );
}
return res.send( {status : 0} );
}
catch(e){
console.error(e);
return res.send({status:-1, result:e});
}
});
module.exports = router;
post insert
판매자 이름 끌어오기는 auth.checkToken
obj에 바로 req.body기입하여 넣어줌(코드 줄이기)

// 물품등록
// AAA 과일, BBB: 생선, CCC:전자제품, DDD 농산물
// {
// "seller":'외래키', "name":'사과', "content":"내용"
// "price":120, "quantity":340, "category":"AAA"
// }
// 127.0.0.1:3000/item/insert.json
router.post('/insert.json', auth.checkToken,
async function(req, res, next) {
try {
// 1. 전달값 받기
const seller = req.body.SID; //판매자 정보 받기
// 2. 저장할 객체 생성
const obj = new Item();
obj.seller = seller;
obj.name = req.body.name;
obj.content = req.body.content;
obj.price = Number(req.body.price);
obj.quantity = Number(req.body.quantity);
obj.category = req.body.category;
obj.regdate = moment().format('YYYY-MM-DD HH:mm:ss');
// 3. DB에 저장
const result = await obj.save();
// 4. 결과에 따른 반환값
if(result !== null){
return res.send( {status:200} );
}
return res.send( {status : 0} );
}
catch(e){
console.error(e);
return res.send({status:-1, result:e});
}
});
member.js
post insert 회원가입에
role을 추가하여 seller, customer를 나눔(admin도 가능)
이전에 membermodel.js에서 role항목이 있어야함
(지금은 임시로 그냥 판매자로 설정해놓음)

put update
and개념
await개념
판매자 토큰이 일치하여야 실행되게 만듬
본인 게시물이 아닌 다른 코드(id)의 게시물을 수정하려할때
status는 0
if문으로 obj처리를 해줌
// 물품수정
// 127.0.0.1:3000/item/update.json
// {"code", "name", "content", "price", "quantity", "category"}
router.put('/update.json', auth.checkToken, async function(req, res, next) {
try {
// 1. 전달값 받기
const seller = req.body.SID;
// 2. 수정조건(물품코드와 판매자 정보가 둘 다 일치하는것)
// {$and : [ _id:3, seller:'a']}
// { _id:3, seller:'a' }
// 둘 다 같은 말이지만 위에껄로 써야 조건을 넣을 수 있음.
const query = {
_id: Number(req.body.code),
seller : seller
}
//3. 변경(결과를 읽어와서 변경만 바꾼다음 다시 저장)
const obj = await Item.findOne(query);
if(obj !== null){
//시간이 걸리는 것은 await을 넣음
obj.name = req.body.name;
obj.content = req.body.content;
obj.price = Number(req.body.price);
obj.quantity = Number(req.body.quantity);
obj.category = req.body.category;
//4. 저장하기
const result = await obj.save();
if(result !== null){
return res.send( {status:200} );
}
}
return res.send( {status : 0} );
}
catch(e){
console.error(e);
return res.send({status:-1, result:e});
}
});
get select
판매자 본인 것만 들고오게끔 만듬
포스트맨 판매자 토큰 입력시 해당 판매자의 글만 끌어옴
router.get('/select.json', auth.checkToken, async function(req, res, next) {
try {
// 1. 조회조건
const query = { seller : req.body.SID }
//2. 조회 하기
const result = await Item.find(query).sort({_id:-1});
if(result !== null){
return res.send( {status:200, result:result} );
}
return res.send( {status : 0} );
}
catch(e){
console.error(e);
return res.send({status:-1, result:e});
}
});
itemimage.js 만듬
var express = require('express');
var router = express.Router();
const multer = require('multer');
const upload = multer( {storage : multer.memoryStorage()} );
require('moment-timezone');
var moment = require('moment');
moment.tz.setDefault('Asia/Seoul');
// 외부에서 사용
const auth = require('../token/auth');
var Item = require('../models/itemmodel');
var ItemImage = require('../models/itemimagemodel');
// 127.0.0.1:3000/itemimage/insert.json
// 토큰 , 물품코드, 이미지
router.post('/insert.json', upload.single("image"),
auth.checkToken, async function(req, res, next) {
try {
// 1. 전달값 받기
const seller = req.body.SID;
const code = Number(req.body.code);
// 2. 전달받은 물품코드가 현재 로그인한 판매자의 것인지 확인
// 본인 것인지 검증하는 작업
const query = {
_id : code,
seller : seller
};
const obj = await Item.findOne(query);
if(obj !== null) { //조회 후 결과가 있으면
//등록가능 위치
const obj1 = new ItemImage();
obj1.code = code;
obj1.filedata = req.file.buffer;
obj1.filename = req.file.originalname;
obj1.filetype = req.file.mimetype;
obj1.filesize = req.file.size;
obj1.regdate = moment().format('YYYY-MM-DD HH:mm:ss');
const result = obj1.save();
if(result !== null){
return res.send( {status : 200} );
}
}
return res.send( {status : 0} );
}
catch(e){
console.error(e);
return res.send({status:-1, result:e});
}
});
module.exports = router;
app에 등록
본인것인지 검증을 하는 작업
그래서
var Item = require('../models/itemmodel');
필요
const query = { _id : code, seller : seller };
//upload가 req를 건드려서 seller를 없애서
순서를 upload => auth.checkToken으로 함
router.post('/insert.json', upload.single("image"), auth.checkToken, async function(req, res, next)
코드 내부에 일일히 console.log로 찍어보면서 확인.
seller에 값이 안들어가는 것을 발견하고 순서변경하니깐 됬음
오후일과@@@@@@@@@@@@@@@@@@@@
오전에 만든 모델의 약식
extensions> erd Editor설치 사진
prj.vuerd.json 만듬
언어 오라클
테이블 만들기
_id에 오른쪽 마우스 눌러서 고유키 만들기
item도 만듬
판매자 안만든 상태
relationship 사진 참고
목적 : 이걸 보고 코드를 짤 생각하는 것임.






vue로 넘어와서 프론트 작업
app.vue
<template>
<div>
<h3>판매자 홈</h3>
<router-link to="sellerjoin" v-if="!state.logged"><button>판매자등록</button></router-link>
<router-link to="sellerlogin" v-if="!state.logged"><button>판매자로그인</button></router-link>
<router-link to="sellerlogout" v-if="state.logged"><button>판매자로그아웃</button></router-link>
<router-link to="selleritem" v-if="state.logged"><button>물품관리</button></router-link>
</div>
</template>
<script>
import { computed, reactive } from '@vue/runtime-core';
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore();
const state = reactive({
logged : computed(()=> store.getters.getLogged)
});
return {state}
}
}
</script>
<style lang="scss" scoped>
</style>
판매자용 가입페이지 따로 만들기
메뉴에 판매자 가입메뉴x
따로 주소쳐서 들어가게끔만 해놓음
셀러 홈, 가입, 로그인, 로그아웃, 물품등록 만들음

vue.config.js 추가(/item) 뷰 프론트 cmd재구동
(이거 안해서 좀 해멨음.. 틀린건 없는데 뭐지?! 하면서 ㅋㅋㅋ)
실습으로 회원가입, 로그인, 로그아웃, 물품관리, 물품등록, 물품수정을 만들기로함
로그인, 로그아웃는 그냥 떙겨오면 되는거라 간단히 했고,
물품관리는 테이블을 만들어서 데이터 기입해줌,
물품등록까지는 시간이 되서 만들었고 등록에 성공했다.
내가한 것 : 로그인, 로그아웃, 물품등록
도움받은 것 : 물품관리
아직 못한 것 : 물품수정(handleData, handleUpdate)
SellerJoin.vue 회원가입
<template>
<div class="container">
{{state}}
<h3>회원가입</h3>
<div>
<label class="lbl">아이디</label>
<input type="text" v-model="state.id" @keyup="handleIDCheck" :ref="el => {form[0] = el}"/>
<label v-html="state.idcheck"></label>
</div>
<div>
<label class="lbl">암호</label>
<input type="password" v-model="state.pw" :ref="el => {form[1] = el}"/>
</div>
<div>
<label class="lbl">암호확인</label>
<input type="password" v-model="state.pw1" :ref="el => {form[2] = el}"/>
</div>
<div>
<label class="lbl">이름</label>
<input type="text" v-model="state.name" :ref="el => {form[3] = el}"/>
</div>
<div>
<label class="lbl">이메일</label>
<input type="text" v-model="state.email1" :ref="el => {form[4] = el}"/>
<label>@</label>
<select v-model="state.email2">
<option>naver.com</option>
<option>daum.net</option>
<option>gmail.com</option>
</select>
</div>
<div>
<label class="lbl">연락처</label>
<input type="text" style="width:50px" v-model="state.phone1" :ref="el => {form[5] = el}"/>
<label>-</label>
<input type="text" style="width:50px" v-model="state.phone2" :ref="el => {form[6] = el}"/>
<label>-</label>
<input type="text" style="width:50px" v-model="state.phone3" :ref="el => {form[7] = el}"/>
</div>
<div style="margin-top:10px">
<label class="lbl"></label>
<button @click="handleJoin">회원가입</button>
</div>
</div>
</template>
<script>
import { reactive, ref } from '@vue/reactivity'
import axios from 'axios';
import { useRouter } from 'vue-router';
export default {
setup () {
const router = useRouter();
const form = ref([]); // n개의 ref
// onMounted(()=>{
// id.value.focus();
// });
const state = reactive({
id : 'very',
pw : '1234',
pw1 : '1234',
name : '1234',
email1 : '1234',
email2 : 'daum.net',
phone1 : '010',
phone2 : '1234',
phone3 : '1234',
idcheck : '중복확인', // 표시용
idcode : 0, // 유효성검사용
});
const handleIDCheck = async() => {
state.idcode = 0;
if ( state.id.length > 3 ){
const url = `/member/idcheck.json?id=${state.id}`;
const headers = {"Content-Type":"application/json"};
const {data} = await axios.get(url, {headers})
console.log(data);
if(data.status === 200) {
if(data.result === 1){
state.idcheck = `<font color="red">사용불가</font>`
}
if(data.result === 0){
state.idcheck = `<font color="green">사용가능</font>`
state.idcode = 1;
}
}
else{state.idcheck = '중복확인';}
}
else{state.idcheck = '중복확인';}
};
const handleJoin = async() => {
//유효성 검사
if(state.id === ''){
alert('아이디를 입력하세요.')
form.value[0].focus();
return false;
}
if(state.idcode !== 1){
alert('아이디 중복확인하세요.')
form.value[0].focus();
return false;
}
if(state.pw === ''){
alert('암호를 입력하세요.')
form.value[1].focus();
return false;
}
if(state.pw1 === ''){
alert('암호확인을 입력하세요.')
form.value[2].focus();
return false;
}
if(state.name === ''){
alert('이름을 입력하세요.')
form.value[3].focus();
return false;
}
if(state.email1 === ''){
alert('이메일을 입력하세요.')
form.value[4].focus();
return false;
}
if(state.phone1 === ''){
alert('번호를 입력하세요.')
form.value[5].focus();
return false;
}
if(state.phone2 === ''){
alert('번호를 입력하세요.')
form.value[6].focus();
return false;
}
if(state.phone3 === ''){
alert('번호를 입력하세요.')
form.value[7].focus();
return false;
}
const url = `/member/insert.json`;
const headers = {"Content-Type":"application/json"};
const body = {
id : state.id,
pw : state.pw,
name : state.name,
email : state.email1 + '@' + state.email2,
phone : state.phone1 + '-' + state.phone2 + '-' + state.phone3,
role : "SELLER"
};
console.log(body);
const {data} = await axios.post(url, body,{headers});
console.log(data);
if(data.status === 200){
alert('회원가입 완료')
router.push({path:'/sellerhome'})
}
}
return {
form,
state,
handleIDCheck,
handleJoin
};
}
}
</script>
<style lang="css" scoped>
.container {
width:500px;
border: 1px solid #cccccc;
padding:20px;
}
.lbl {
display: inline-block;
width:90px;
}
</style>
SellerLogin.vue 로그인
<template>
<div class="container">
{{state}}
<h3>로그인</h3>
<div>
<label class="lbl">아이디</label>
<input type="text" v-model="id" :ref="el => {form[0] = el}"/>
<label v-html="state.idcheck"></label>
</div>
<div>
<label class="lbl">암호</label>
<input type="password" v-model="pw" :ref="el => {form[1] = el}"/>
</div>
<div style="margin-top:10px">
<label class="lbl"></label>
<button @click="handleLogin">로그인</button>
</div>
</div>
</template>
<script>
import { reactive, ref, toRefs } from '@vue/reactivity'
import axios from 'axios';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
export default {
setup () {
const router = useRouter();
const form = ref([]); // n개의 ref
const store = useStore();
// onMounted(()=>{
// id.value.focus();
// });
const state = reactive({
id : 'very3',
pw : 'bbbb',
});
const handleLogin = async() => {
//유효성 검사
if(state.id === ''){
alert('아이디를 입력하세요.')
form.value[0].focus();
return false;
}
if(state.pw === ''){
alert('암호를 입력하세요.')
form.value[1].focus();
return false;
}
const url = `/member/login.json`;
const headers = {"Content-Type":"application/json"};
const body = {
id : state.id,
pw : state.pw,
};
console.log(body);
const {data} = await axios.post(url, body,{headers});
console.log(data);
if(data.status === 200){
alert('판매자 로그인 완료')
sessionStorage.setItem("TOKEN", data.result);
store.commit('setLogged', true);
router.push({path:'/sellerhome'});
}
}
return {
form,
state,
...toRefs(state),
handleLogin
};
}
}
</script>
<style lang="css" scoped>
.container {
width:500px;
border: 1px solid #cccccc;
padding:20px;
}
.lbl {
display: inline-block;
width:90px;
}
</style>
SellerLogout.vue 로그아웃
<template>
<div>
</div>
</template>
<script>
import { onMounted } from '@vue/runtime-core'
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
export default {
setup () {
const router = useRouter();
const store = useStore();
onMounted(()=>{
if(confirm('로그아웃 할거요?')) {
sessionStorage.removeItem("TOKEN")
store.commit('setLogged', false);
}
router.push({path:'/sellerhome'});
});
return {}
}
}
</script>
<style lang="scss" scoped>
</style>
SellerItem.vue
<template>
<div>
<h3>물품관리</h3>
<router-link to="selleriteminsert">물품등록</router-link>
<table border="1">
<tr>
<th>번호</th>
<th>물품번호</th>
<th>이름</th>
<th>내용</th>
<th>가격</th>
<th>수량</th>
<th>분류</th>
<th>등록일</th>
<th>버튼</th>
</tr>
<tr v-for="(tmp, idx) of state.rows" :key="tmp">
<td>{{ idx + 1 }}</td>
<td>{{ tmp._id }}</td>
<td>{{ tmp.name }}</td>
<td>{{ tmp.content }}</td>
<td>{{ tmp.price }}</td>
<td>{{ tmp.quantity }}</td>
<td>{{ tmp.category }}</td>
<td>{{ tmp.regdate }}</td>
<td><button @click="handleUpdate()">수정</button><button>이미지등록</button></td>
</tr>
</table>
</div>
</template>
<script>
import { reactive } from '@vue/reactivity'
import { onMounted } from '@vue/runtime-core';
import axios from 'axios';
import { useRouter } from 'vue-router';
export default {
setup () {
const router = useRouter();
const state = reactive({
token : sessionStorage.getItem("TOKEN"),
rows : [],
});
onMounted(() => {
if(state.token === null) {
alert('로그인 되지 않았습니다.');
}
else{
handleData();
}
});
const handleData = async() => {
const url = `/item/select.json`;
const headers = {
"Content-Type":"application/json",
"token" : state.token
}
const { data } = await axios.get(url, {headers});
console.log(data);
if(data.status === 200){
state.rows = data.result;
}
}
const handleUpdate = () => {
router.push({path:'selleritemupdate'})
}
return {state, handleUpdate};
}
}
</script>
<style lang="css" scoped>
</style>
실습코드라서 아마 내일 많이 바뀔 예정임.
아래
SellerItemInsert.vue
SellerItemUpdate.vue는 내일 코드가 많이 바뀔 예정