getAll(fileTitle,page,size){
return http.get(`/advanced/fileDb?fileTitle=${fileTitle}&page=${page}&size=${size}`)
}
fileTitle으로 검색이 가능하게 검색기능을 구현할 것이고, page와 size로 페이지 기능을 구현할 것이다.
data() {
return {
fileDb: [],
searchTitle : "",
page: 1, // 현재 페이지 번호
count: 4, // 전체 데이터 개수
pageSize: 3, // 한 페이지당 요소개수
pageSizes: [3, 6, 9], // 한 페이지당 요소개수 배열
};
},
업로드한 파일을 여러개 보여주기 위해 객체 배열을 준비했다.
searchTitle을 검색창에 걸어주어 제목으로 검색하면 백엔드로 넘어가게 해줄 것이다.
페이지 기능을 위해 페이지 관련 속성을 정의 해주었다.
methods: {
// 전체조회 함수
async retrieveFileDb(){
try {
let response = await FileDbService.getAll(this.searchTitle, this.page -1, this.pageSize);
const{fileDb, totalItems} = response.data;
this.fileDb = fileDb;
this.count = totalItems;
} catch (e) {
console.log(e);
}
},
// 삭제 함수
// deleteFileDb(uuid){},
// 페이지 공통함수
// select 박스 변경시 실행될 함수
pageSizeChange(){
this.page = 1; // 현재 페이지 번호 초기화
this.retrieveFileDb(); // 재조회 요청
},
},
mounted() {
this.retrieveFileDb();
},
앞서 진행했던 dept예제와 동일하므로 설명은 생략한다.
<template>
<div>
<div class="col-md-8 mt-5">
<!-- {/* 검색어 start */} -->
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Search by title" v-model="searchTitle"/>
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button"
@click="retrieveFileDb">
Search
</button>
</div>
</div>
<!-- {/* 검색어 end */} -->
</div>
<div class="col-md-12 mt-3">
<h4>FileDb List</h4>
<!-- {/* page start */} -->
<div class="mb-3">
Items per Page:
<select v-model="pageSize" @change="pageSizeChange">
<option v-for="(data, index) in pageSizes" :key="index" :value="data">{{ data }}</option>
</select>
</div>
<!-- b-pagination : 부트스트랩 - 페이지 번호 컨트롤 -->
<!-- total-rows : 전체 데이터 개수 -->
<!-- per-page : 1페이지 당 개수 -->
<!-- change : handlePageChange(), 페이지 번호 변경 시 실행되는 이벤트 -->
<b-pagination
v-model="page"
:total-rows="count"
:per-page="pageSize"
@click="retrieveFileDb"
></b-pagination>
<!-- {/* page end */} -->
<!-- {/* 쇼핑카트 이미지 start */} -->
<div class="row">
<div v-for="(data,index) in fileDb" :key="index" class="col-sm-6">
<div class="card">
<!-- 카드 이미지 -->
<img :src="data.fileUrl" class="card-img-top" alt="강의" />
<!-- 본문 : 제목 + 내용 -->
<div class="card-body">
<!-- 제목 -->
<h5 class="card-title">{{ data.fileTitle }}</h5>
<!-- 내용 -->
<p class="card-text">{{ data.fileContent }}</p>
<router-link :to="/fildDb/ + data.uuid"><span class="badge bg-warning">수정</span></router-link>
<a
style="
{
color: inherit;
}
"
class="ms-2"
@click="deleteFileDb(data.uuid)"
>
<span class="badge bg-danger">Delete</span>
</a>
</div>
</div>
</div>
</div>
<!-- {/* 쇼핑카트 이미지 end */} -->
</div>
</div>
</template>
이 부분도 언급할 새로운 부분이 없다.
@GetMapping("/fileDb")
public ResponseEntity<Object> findAll(// 프론트와 신호를 보낼 수 있게 해주는 타입이다. 어떤 자료형이 들어올지 모르니 Object로 해준다. 얘는 객체의 조상이라서 다 받아준다.
@RequestParam(defaultValue = "") String fileTitle,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size
) {
try {
Pageable pageable = PageRequest.of(page, size);
Page<FileDb> pageList = fileDbService.findAllByFileTitleContaining(fileTitle, pageable);
Map<String, Object> response = new HashMap<>();// jsp에서는 model을 이용해서 키와 밸류를 프론트로 보내주었는데, 이번에는 그렇게 하지 못하니 Map을 이용하여 키 밸류로 만들어 보내주자.
response.put("fileDb", pageList.getContent()); // 부서 정보
response.put("currentPage", pageList.getNumber()); // 현재페이지 번호
response.put("totalItems", pageList.getTotalElements()); // 전체데이터 개수
response.put("totalPages", pageList.getTotalPages()); // 전체 페이지수
// 1) pageList 값이 없으면 : dB 테이블 없음 => NO_CONTENT(203) 신호를 보낸다.(203)
// 2) pageList 값이 있으면 : 성공 => OK(200)
if (pageList.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}else {
return new ResponseEntity<>(response, HttpStatus.OK);
}
}catch (Exception e){
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); // 에러가 나면 이신호를 웹브라우저에 보내어 준다.
// 프로그램의 품질이 증가된다.
// 이걸해주면 에러가 나도 신호가 오기에 에러원인을 알 수있다.
// INTERNAL_SERVER_ERROR(500) 백엔드 서버 에러
}
}
전체 조회 기능은 지난 예제에서 크게 벗어난 것이 없고 변수이름만 조금씩 변경되었다.
파일 선택 상자는 files배열을 가지고 있다. 그래서 여러개의 파일이 업로드 되게 할 수 있다.
업로드 기능, 즉 저장 기능은 전체 조회 기능과 달리 service부터 달라진 점이 많다.
save(fileDb, image){
let formData = new FormData();
formData.append("fileTitle", fileDb.fileTitle);
formData.append("fileTitle", fileDb.fileContent);
formData.append("image", image);
return http.post("/advanced/fileDb/upload", formData, {"Content-Type": "multipart/form-data"})
/* json파일로 통신할 때는 항상 생략되지만 멀티파트 파일을 보낼때는 json파일이 아니므로 이렇게 보내야한다. */
}
fileDb객체와 이미지가 넘어가야하므로 변수에 넣어주었다.
파일은 문서형태가 multipart 또는 form-data로 전송이 되어야한다. 그래서 오로지 form태그나 formDate객체로 전송해야하는데 우리는 form을 사용하지 않으니 formData로 전송을 해보도록 하자.
FormData 객체를 불러와서 appand함수도 여기에 파일 데이터를 다 담아서 백엔드로 보내주면된다.
post함수로 보내줄 때 파일이 아닌 데이터는 json파일로 전송하기 때문에 파일 형식을 항상 생략했다. 하지만 multipart형식으로 보낼때는 형식을 "{"Content-Type": "multipart/form-data"}"이렇게 정의해줘야한다.
data() {
return {
currentImage: undefined, // 현재이미지
message: "", // 성공메세지 변수
fileDb: {
uuid: null,
fileTitle: "",
fileContent: "",
fileUrl: ""
}, // 파일 객체
}
},
프론트에서 받아서 백엔드로 전달할 이미지를 currentImage라는 변수로 걸어주었다.
저장이 완료되면 띄울 메세지를 변수로 만들어 주었다.
마지막으로 file 객체를 변수로 만들어 주었다.
methods: {
selectImage(){
//1) 파일 선택 상자에서 첫 번째로 선택한 이미지를 변수에 저장해야한다.
// ref="file"로 해놧으므로 file로 접근해야한다.
this.currentImage = this.$refs.file.file[0]; // 배열의 특징을 가지므로 file0을 해주어야한다. 아니면 배열에서 몇번째를 가져오는 지를 모름
this.message="";
},
async create(){
try {
let response = await FileDbService.save(this.fileDb, this.currentImage);
console.log(response);
this.message = response.data
} catch (e) {
console.log(e);
}
}
},
프론트에서 받은 이미지를 변수로 옮겨 닮아야한다. 사실 'v-model로 바로 받을 수 있지 않나? '라고 생각할 수 있지만, type이 file인 input에는 v-modle을 사용할 수 없다. 그래서 ref속성을 사용해야하는데 이 속성을 js에서는 $ref로 표기해야한다. 속성에 대한 자세한 얘기는 template에서 얘기해보자.
create()는 만들어진 fileDb정보들과 이미지를 백엔드로 넘기는 함수이다. 방법은 지금까지 많이 했으니 설명은 생략한다.
<div class="input-group mb-3">
<!-- {/* upload 선택상자/버튼 start */} -->
<input type="file" ref="file"/> <!-- type을 파일로 해주어야한다. -->
<!-- 파일 선택상자는 v-model이 안된다. 그래서 ref="변수명"을 해주어야한다.-->
<button
class="btn btn-outline-secondary"
type="button"
id="inputGroupFileAddon04"
@click="create"
>
Upload
</button>
</div>
다른 태그들은 전과 똑같아서 언급 할 것이 없지만, 앞서 말했던 파일 타입의 input태그에는 v-model을 사용할 수 없다는 점을 보자.
그럼 어떻게 해야할까? ref속성을 이용해 속성값으로 file을 주어야한다. 이것을 나중에 변수로 넣어주어야 하는데, js함수로 사용할때는 $refs.file.file[0]의 형태로 사용하면된다.
file은 배열형태로 여러개의 파일을 담을 수 있다. 즉 전송도 여러개의 파일을 할 수 있다. 그러므로 index로 보내줄 파일을 명시해줘야한다.
먼저 프론트에서 입력되 파일과 파일 정보들을 데이터 베이스에 저장하게 하는 함수를 만들어 보자.
@PostMapping("/fileDb/upload")
public ResponseEntity<Object> uploadFileDb(@RequestParam(defaultValue = "") String fileTitle,
@RequestParam(defaultValue = "") String fileContent,
@RequestParam MultipartFile image){
try {
fileDbService.save(null, fileTitle, fileContent, image);
return new ResponseEntity<>("업로드 성공", HttpStatus.OK);
}catch (Exception e){
return new ResponseEntity<>("업로드시 에러 발생",HttpStatus.INTERNAL_SERVER_ERROR);
}
}
json파일로 통신을 하기 위해 ResponseEntity타입으로 함수를 만들어준다.
프론트에서 전달되는 파일 제목, 내용, 파일을 RequstParam으로 받아준다. 이때 파일은 PathVariable형태로 받을수 없다. RequstParam으로만 주고 받는것으로 정해져있는듯 하다.
파일을 주고 받을때는 에러가 많이 나기때문에 예외처리를 해준다. fileDbService()함수를 이용해 파일 제목, 내용, 파일을 넣어주고 처음 들어올때 uuid는 존재하지 않기때문에 null을 넣어준다. uuid는 service함수에서 만들어서 넣어줄 것이다.
전체조회시 img태그에 src에 이미지 url을 입력하여 조회를 한다. 이때 이미지를 불러오는 함수이다.
@GetMapping("/fileDb/get/{uuid}")
public ResponseEntity<byte[]> findByIdDownloading(@PathVariable String uuid) {
FileDb fileDb = fileDbService.findById(uuid).get();
return ResponseEntity.ok()
// Todo : attachment: => attachment;
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileDb.getFileName() + "\"")
.body(fileDb.getFileData());
}
사진은 Byte객체 배열타입이다. 그래서 ResponseEntity함수안에 Byte객체를 넣어준다.
uuid를 인식해서 파일을 불러와야하므로 PathVariable를 이용해서 uuid를 변수로 받고, findById함수로 결과를 찾아준다.
여기서 얻은 결과의 getFileName와 getFileData를 각각 header와 body에 넣어서 ok신호와 같이 보내준다.
업로드에 성공하면 그림과 같이 "업로드 성공"이라는 메세지가 뜬다.
사진 파일이 저장이 잘 되었다.
11월만 검색해서 어떤 나고야 사진만 나오는지 확인해보자.
11월의 나고야만 잘 출력되는 것을 확인 할 수 있다.
이제 수정 기능을 만들어보자.
update(fileDb, image){
let formData = new FormData();
formData.append("fileTitle", fileDb.fileTitle);
formData.append("fileContent", fileDb.fileContent);
formData.append("image", image);
return http.put(`/advanced/fileDb/update/${fileDb.uuid}`,formData,{
headers: {
"Content-Type": "multipart/form-data",
},
});
get(uuid){
return http.get(`/advanced/fileDb/get/${uuid}`)
}
여기서도 파일을 전달하기에 formData로 전달을 해야한다. 업로드 기능과 거의 비슷하지만, 수정은 put함수를 사용해야하고, uuid를 url에 넣어줘야 한다는 차이가 있다.
uuid를 인식해서 해당 파일을 가지고 오는 함수이다. src에 사진의 url을 적어서 불러올때 실행되는 함수이다?(함수를 등록을 안했는데 어떻게 인식을해주고 실행되는 거지?)
1. uuid를 인식해줘야하기에 controller와 통신하는 url에 uuid를 넣어줬다. 객체도 전달받아야
data() {
return {
currentImage: undefined,
message: "", //성공메세지 변수
fileDb: {
uuid: this.$route.params.uuid,
fileTitle:"",
fileContent: "",
fileUrl:""
}
}
},
백엔드에 파일로 넘겨줄 currentImage변수를 만들어주었다.
message 수정이 성공되면 나타낼 메세지를 변수로 만들어주었다.
file정보를 담늦 변수들을 담아주었다.
uuid는 수정페이지 url에 있는 uuid를 가져와서 넣어주었다. $route.params를 통해 가져올 수 있다.
methods: {
async get(uuid){
try {
let response = await FileDbService.get(uuid);
console.log(response);
this.fileDb=response.data;
} catch (e) {
console.log(e);
}
},
selectImage(){
this.currentImage=this.$refs.file.files[0];
},
async update(){
try {
let response = await FileDbService.update(this.fileDb, this.currentImage );
console.log(response.data);
this.message = response.data
} catch (e) {
this.currentImage = "";
this.message ="";
console.log(e)
}
},
},
mounted() {
this.get(this.$route.params.uuid);
this.message='';
},
}
get함수로 상세조회를 해주어 수정을 할 input태그들에 수정 전의 값이 뜨게 만들 것이다.
selectImage함수로 파일이 수정되면 백엔드로 갈 이미지가 변경되게 만들었다.
update함수로 file 정보가 담긴 객체와 이미지를 변수로 받아 백엔드로 넘겨줄 것이다.
마지막으로 mounted함수로 페이지가 열리면 get함수를 통해 상세조회를 하고 message를 초기화 시킨다.
<!-- {/* 이미지내용 입력 박스 끝 */} -->
<div class="mb-3 col-md-12">
<img :src="fileDb.fileUrl" class="card-img-top" alt="강의" />
</div>
<!-- {/* upload 선택상자/버튼 start */} -->
<div class="input-group mb-3">
<!-- {/* upload 선택상자/버튼 start */} -->
<label class="btn btn-default p-0 mb-3">
<input type="file" ref="file" @change="selectImage"/>
</label>
<button class="btn btn-success mb-3" @click="update">Update</button>
</div>
<!-- {/* upload 선택상자/버튼 end */} -->
<!-- {/* upload 성공/실패 메세지 출력 시작 */} -->
<div v-if="message" class="alert alert-success" role="alert">
{{ message }}
</div>
이미지 태그의 src에 fileUrl을 걸어주어 수정 페이지에 들어가면 현제 사진이 나오게 했다.
파인선택상자의 사진이 변경되면 변경된 파일이 백엔드에 전송되는 변수로 바뀌게 만드는 함수인 selectImage를 걸어주었다.
Update버튼을 누르면 update함수가 실행되게 만들었다.
upload가 성공하면 message변수가 바뀌게 upload함수에 설정을 해놓아서 성공시 message가 뜨게 해주었다.
@GetMapping("/fileDb/get/{uuid}")
public ResponseEntity<Object> getFileDb(@PathVariable String uuid){
try {
Optional<FileDb> fileDb = fileDbService.findById(uuid);
if (fileDb == null) {
return new ResponseEntity<>("파일이 존재하지 않음", HttpStatus.NO_CONTENT);
}else {
return new ResponseEntity<>(fileDb, HttpStatus.OK);
}
}catch (Exception e){
return new ResponseEntity<>("문제발생", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
uuid를 통해 상세조회 함수를 만ㄷ르었다. 이것은 전의 것들과 똑같아서 설명은 생략한다.
@PutMapping("/fileDb/update/{uuid}")
public ResponseEntity<Object> updateFileDb(@PathVariable String uuid,
@RequestParam(defaultValue = "") String fileTitle,
@RequestParam(defaultValue = "") String fileContent,
@RequestParam MultipartFile image){
try {
FileDb fileDb = fileDbService.save(uuid, fileTitle, fileContent, image);
if (fileDb == null) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}else {
return new ResponseEntity<>("수정 성공", HttpStatus.OK);
}
}catch (Exception e){
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
이것도 save함수와 굉장히 비슷하다. 다른점은 uuid로 업데이트 할 객체를 선택해줘야한다는 것이다.
또한 uuid가 넣이 아니기에 uuid를 넣어주어야 한다는 것. 이둘 말고는 다를 것이 없다.
기존의 11월의 나고야 사진을 바꿔보자.
수정이 잘 된 것을 확인 할 수 있다.
마지막으로 삭제 기능을 만들어보자.
delete(uuid){
return http.delete(`/advanced/fileDb/delete/${uuid}`);
}
uuid로 객체를 인식해서 없애야하므로 uuid를 변수로 넣어주고 url에 넣어서 전달해준다.
async deleteFileDb(uuid){
console.log(uuid)
try {
let response = FileDbService.delete(uuid)
console.log(response);
this.retrieveFileDb();
} catch (e) {
console.log(e);
}
}
},
delete함수를 정의해주었다.
<a
style="
{
color: inherit;
}
"
class="ms-2"
@click="deleteFileDb(data.uuid)"
>
<span class="badge bg-danger">Delete</span>
</a>
delete버튼을 누르면 delete 함수가 실행되게 onclick을 걸어주었다.
6월 오사카를 삭제해보자
삭제가 잘 되었다.