사진을 업로드 했으니 경로를 통해서 가져와 렌더링 해줘야겠죠?
[ 전체코드 ]
GalleryController.java
package com.example.demo.controller;
import com.example.demo.dto.GalleryDTO;
import com.example.demo.entity.GalleryEntity;
import com.example.demo.service.GalleryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
@RestController
@RequestMapping("/api/v1/auth/y")
public class GalleryController {
@Autowired
GalleryService galleryService;
/**
* 기능 : 사진첩 작성 ( 훈련사 계정 )
* url : /createGallery
* request data : 날짜(글제목), 글 내용, 작성 날짜, 작성 시간
* response data : 사진첩 작성 성공
*/
@PostMapping("/createGallery")
public List<GalleryDTO> createGallery(@RequestPart (value = "file", required = false) List<MultipartFile> uploadFiles) {
return galleryService.createGallery(uploadFiles);
}
/**
기능 : 사진첩 목록
url : /gallery
request data :
response data : 사진첩 목록
*/
@GetMapping("/gallery")
public List<GalleryDTO> gallery() {
return galleryService.galleryList();
}
@GetMapping(value = "/galleryView", produces={MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE})
public ResponseEntity<byte[]> findById(@RequestParam("id") Long id) throws IOException {
// id에 해당하는 이미지 정보를 서비스로부터 가져옴
GalleryEntity galleryEntity = galleryService.findById(id);
// 파일 경로를 이용하여 이미지 파일을 읽기 위한 InputStream을 생성
InputStream inputStream = new FileInputStream(galleryEntity.getGallImg());
// InputStream에서 모든 바이트를 읽어와 byte 배열에 저장
byte[] bytes = inputStream.readAllBytes();
// 스트림을 닫아 리소스를 해제
inputStream.close();
// 이미지를 byte 배열로 변환하여 ResponseEntity에 담아서 반환
return new ResponseEntity<byte[]>(bytes, HttpStatus.OK);
}
}
[ 추가된 부분 ]
사진첩 목록을 가져오는 엔드포인트
@GetMapping("/gallery")
public List<GalleryDTO> gallery() {
return galleryService.galleryList();
}
특정 데이터를 요청 본문에 포함할 필요가 없기 때문에 요청 데이터는 없고, 응답 데이터는 List<GalleryDTO> 입니다.
요청이 들어오면 사진첩 목록을 가져오고, 그 목록을 클라이언트에게 반환합니다.
이미지를 가져오는 엔드포인트
@GetMapping(value = "/galleryView", produces={MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE})
public ResponseEntity<byte[]> findById(@RequestParam("id") Long id) throws IOException {
// id에 해당하는 이미지 정보를 서비스로부터 가져옴
GalleryEntity galleryEntity = galleryService.findById(id);
// 파일 경로를 이용하여 이미지 파일을 읽기 위한 InputStream을 생성
InputStream inputStream = new FileInputStream(galleryEntity.getGallImg());
// InputStream에서 모든 바이트를 읽어와 byte 배열에 저장
byte[] bytes = inputStream.readAllBytes();
// 스트림을 닫아 리소스를 해제
inputStream.close();
// 이미지를 byte 배열로 변환하여 ResponseEntity에 담아서 반환
return new ResponseEntity<byte[]>(bytes, HttpStatus.OK);
}
여기서 요청 데이터는 이미지의 id 입니다.
galleryService.findById(id) 메서드를 호출하여 ID에 해당하는 이미지 정보를 가져옵니다.
이미지 파일 경로를 사용하여 FileInputStream을 생성합니다.
FileInputStream에서 모든 바이트를 읽어와 byte[] 배열에 저장합니다.
그렇게 byte[] 배열로 변환한 이미지를 ResponseEntity에 담아 클라이언트에 반환합니다.
[ 전체코드 ]
GalleryService.java
package com.example.demo.service;
import com.example.demo.dto.GalleryDTO;
import com.example.demo.entity.GalleryEntity;
import com.example.demo.mapper.GalleryMapper;
import com.example.demo.repository.GalleryRepository;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Date;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Log4j2
public class GalleryService {
@Autowired
private GalleryRepository galleryRepository;
// 업로드 할 위치
@Value("${part.upload.path}")
private String uploadPath;
private String fileExtension;
private Path savePath;
// 사진첩 작성
public List<GalleryDTO> createGallery(List<MultipartFile> uploadFiles) {
List<GalleryDTO> galleryDTOList = new ArrayList<>();
for (MultipartFile uploadFile : uploadFiles) {
// 이미지 파일만 업로드
if (!Objects.requireNonNull(uploadFile.getContentType()).startsWith("image")) {
log.warn("this file is not an image type");
continue;
}
// 파일명: 업로드 된 파일의 원래 파일 이름을 추출
String originalName = uploadFile.getOriginalFilename();
// 마지막으로 온 "/"부분으로부터 +1 해준 부분부터 출력
// originalName이 "/path/to/file/example.txt"와 같은 문자열이라면, "example.txt" 부분만 (파일명만) 추출
String fileName = originalName.substring(originalName.lastIndexOf("//") + 1);
log.info("fileName: " + fileName);
// 파일 확장자
fileExtension = StringUtils.getFilenameExtension(originalName);
// 날짜 폴더 생성
String folderPath = makeFolder();
// UUID
String uuid = UUID.randomUUID().toString();
// 저장할 파일 이름 중간에 "_"를 이용하여 구분
String saveName = uploadPath + File.separator + folderPath + File.separator + uuid + "_" + fileName;
// Paths.get() 메서드는 특정 경로의 파일 정보를 가져옴 (경로 정의하기)
savePath = Paths.get(saveName);
try {
// transferTo(file) : uploadFile에 파일을 업로드 하는 메서드
uploadFile.transferTo(savePath);
galleryDTOList.add(new GalleryDTO(fileName, uuid, folderPath));
} catch (IOException e) {
e.printStackTrace();
}
}
GalleryEntity galleryEntity = GalleryEntity.builder()
.gallDate(Date.valueOf(LocalDate.now()))
.gallImg(String.valueOf(savePath))
.gallExtension(fileExtension)
.build();
galleryRepository.save(galleryEntity);
return galleryDTOList;
}
// 날짜 폴더 생성
public String makeFolder() {
//LocalDate를 문자열로 포멧
String str = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String folderPath = str.replace("/", File.separator);
// make folder --------
// File newFile= new File(dir,"파일명");
// -> 부모 디렉토리를 파라미터로 인스턴스 생성
File uploadPathFolder = new File(uploadPath, folderPath);
// 만약 uploadPathFolder가 존재하지않는다면 makeDirectory하라는 의미
// mkdir(): 디렉토리에 상위 디렉토리가 존재하지 않을경우에는 생성이 불가능한 함수
// mkdirs(): 디렉토리의 상위 디렉토리가 존재하지 않을 경우에는 상위 디렉토리까지 모두 생성하는 함수
if(!uploadPathFolder.exists()) {
boolean mkdirs = uploadPathFolder.mkdirs();
log.info("-------------------makeFolder------------------");
log.info("uploadPathFolder.exists(): "+uploadPathFolder.exists());
log.info("mkdirs: "+mkdirs);
}
return folderPath;
}
// 사진첩 목록 조회
// Mapstruct 사용 x
// public List<GalleryDTO> galleryList() {
// List<GalleryEntity> galleryEntities = galleryRepository.findAll();
// List<GalleryDTO> galleryDTOList = new ArrayList<>();
//
// for (GalleryEntity entity : galleryEntities) {
// GalleryDTO dto = new GalleryDTO();
//
// dto.setGallImg(entity.getGallImg());
// dto.setGallExtension(entity.getGallExtension());
//
// galleryDTOList.add(dto);
// }
//
// return galleryDTOList;
// }
// mapstruct 사용 o
public List<GalleryDTO> galleryList() {
List<GalleryEntity> galleryEntities = galleryRepository.findAll(); // 갤러리엔터티를 리스트에 저장
return galleryEntities.stream() // 스트림 사용
.map(GalleryMapper.instance::galleryToDTO) // Mapstruct 사용하여 갤러리 entity -> DTO로 변환
.collect(Collectors.toList()); // 변환된 갤러리 DTO들을 리스트로 수집하여 반환
}
// 해당 유저의 사진첩 상세조회
public GalleryEntity findById(Long id) {
return galleryRepository.findById(id).get();
}
}
[ 추가된 부분 ]
사진첩 목록 조회 ( Mapsturct를 사용안한 코드 )
// Mapstruct 사용 x
public List<GalleryDTO> galleryList() {
List<GalleryEntity> galleryEntities = galleryRepository.findAll();
List<GalleryDTO> galleryDTOList = new ArrayList<>();
for (GalleryEntity entity : galleryEntities) {
GalleryDTO dto = new GalleryDTO();
dto.setGallImg(entity.getGallImg());
dto.setGallExtension(entity.getGallExtension());
galleryDTOList.add(dto);
}
return galleryDTOList;
}
galleryRepository.findAll() 메서드를 호출하여 모든 GalleryEntity를 가져옵니다.
각 GalleryEntity를 GalleryDTO로 변환하여 galleryDTOList 리스트에 추가합니다.
최종적으로 galleryDTOList를 반환합니다.
하지만 Mapstruct를 학습했으니 써먹어야겠죠?
다음은 Mapstruct를 사용한 버전의 코드 입니다.
사진첩 목록 조회 ( Mapsturct를 사용안한 코드 )
// 사진첩 목록 조회 (MapStruct 사용 O)
public List<GalleryDTO> galleryList() {
// 갤러리 엔티티 리스트를 가져옴
List<GalleryEntity> galleryEntities = galleryRepository.findAll();
// 스트림을 사용하여 각 엔티티를 MapStruct를 사용해 DTO로 변환하고 리스트로 수집하여 반환
return galleryEntities.stream()
.map(GalleryMapper.instance::galleryToDTO) // MapStruct 매퍼를 사용하여 엔티티를 DTO로 변환
.collect(Collectors.toList()); // 변환된 DTO 리스트를 수집하여 반환
}
galleryRepository.findAll() 메서드를 호출하여 모든 GalleryEntity를 가져옵니다.
Java Stream API를 사용하여 각 GalleryEntity를 MapStruct 매퍼(GalleryMapper.instance::galleryToDTO)를 사용해 GalleryDTO로 변환합니다.
변환된 GalleryDTO들을 리스트로 수집하여 반환합니다.
[ 전체코드 ]
GalleryList.js
import React, { useEffect, useState } from "react";
import { useNavigate, Link } from 'react-router-dom';
import axios from "axios";
import 'bootstrap/dist/css/bootstrap.min.css';
import { Button, Card, Col, Container, Row } from 'react-bootstrap';
import { useCookies } from "react-cookie";
import AxiosInstance from "../api/AxiosInstance";
import '../css/GalleryList.css'
let GalleryList = (props) => {
let [cookies] = useCookies(['accessToken']);
let [dataList, setDataList] = useState([]);
let [imageMap, setImageMap] = useState({}); // 이미지 데이터를 저장할 상태 변수
let navigate = useNavigate();
useEffect(() => {
axios({
url: `http://localhost:8082/api/v1/auth/y/gallery`,
method: 'GET',
headers: {
'Authorization' : 'Bearer '+ cookies.accessToken
}
})
.then((res) => {
console.log(res.data)
if (res.status === 200) {
console.log("이미지 불러오기 성공")
setDataList(res.data); // 응답 데이터 설정
// 각 이미지 데이터를 요청하여 상태로 저장
res.data.forEach(data => {
requestImage(data.id).then(imageSrc => {
setImageMap(prevState => ({ ...prevState, [data.id]: imageSrc }));
});
});
}
})
.catch((error) => {
alert("이미지 불러오기 실패");
console.error("Error:", error);
});
}, []); // 의존성 배열을 빈 배열로 전달하여 최초 렌더링 시에만 실행되도록 설정
// 이미지 URL을 가져오는 requestImage 함수
let requestImage = async (imageId) => {
try {
const res = await axios({
url: `http://localhost:8082/api/v1/auth/y/galleryView?id=${imageId}`,
method: 'GET',
headers: {
'Authorization': 'Bearer ' + cookies.accessToken
},
responseType: 'arraybuffer' // 바이트 배열로 응답 받기
});
const base64 = btoa(
new Uint8Array(res.data)
.reduce((data, byte) => data + String.fromCharCode(byte), '')
);
const contentType = res.headers['content-type'];
return `data:${contentType};base64,${base64}`;
} catch (error) {
console.error("Error:", error);
}
};
let createGallery = () => {
navigate('/createGallery');
};
// 사진첩 삭제
let deleteGallery = (id) => {
axios({
url: `http://localhost:8082/api/v1/auth/y/gallery?id=${id}`,
method: 'DELETE',
headers: {
'Authorization' : 'Bearer '+ cookies.accessToken
}
})
.then((res) => {
console.log("갤러리 삭제 성공");
alert("해당 사진을 삭제하였습니다.")
// fetchData();
// 갤러리 목록 갱신
setDataList(res.data); // 응답 데이터 설정
})
.catch((error) => {
console.log("Error:", error);
alert("갤러리 삭제 실패!")
});
};
return (
<>
<h3>📷사진첩</h3>
<div className="container">
{dataList.length > 0 ? (
<div className="row justify-content-center">
{dataList.slice(0, 9).map((data, index) => (
<div key={index} className="col-md-4">
<div className="card">
{imageMap[data.id] ? (
<img className="card-img-top" src={imageMap[data.id]} alt="Gallery Image"/>
) : (
<div>Loading...</div>
)}
<div className="card-body">
<h5 className="card-title">{`${data.gall_date}`}</h5>
<p className="card-text">{`${data.id}`}</p>
<div className="button-group">
<Link to={`/galleryView/${data.id}`} className="btn btn-primary">상세보기</Link>
<button onClick={() => deleteGallery(data.id)} className="btn btn-danger">삭제하기</button>
</div>
</div>
</div>
</div>
))}
</div>
) : (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h3>아직 작성된 이미지가 없습니다🙄</h3>
</div>
)}
</div>
<div className="button-container">
<div className="button-wrapper">
<button className="creat-board-btn" onClick={createGallery}>📝작성하기</button>
</div>
</div>
</>
);
}
export default GalleryList;
[ 추가된 부분 ]
requestImage 함수
let requestImage = async (imageId) => {
try {
const res = await axios({
url: `http://localhost:8082/api/v1/auth/y/galleryView?id=${imageId}`,
method: 'GET',
headers: {
'Authorization': 'Bearer ' + cookies.accessToken
},
responseType: 'arraybuffer' // 바이트 배열로 응답 받기
});
const base64 = btoa(
new Uint8Array(res.data)
.reduce((data, byte) => data + String.fromCharCode(byte), '')
);
const contentType = res.headers['content-type'];
return `data:${contentType};base64,${base64}`;
} catch (error) {
console.error("Error:", error);
}
};
- 특정 이미지 ID를 받아 해당 이미지를 요청합니다.
- axios를 사용하여 GET 요청을 보내고, 응답 유형을 arraybuffer로 설정하여 바이트 배열로 데이터를 받습니다.
- 응답 데이터를 base64로 인코딩하여 데이터 URL을 생성합니다.
- 생성된 데이터 URL을 반환합니다.
useEffect 훅
useEffect(() => {
axios({
url: `http://localhost:8082/api/v1/auth/y/gallery`,
method: 'GET',
headers: {
'Authorization': 'Bearer ' + cookies.accessToken
}
})
.then((res) => {
console.log(res.data);
if (res.status === 200) {
console.log("이미지 불러오기 성공");
setDataList(res.data); // 응답 데이터를 상태로 설정
// 각 이미지 데이터를 요청하여 상태로 저장
res.data.forEach(data => {
requestImage(data.id).then(imageSrc => {
setImageMap(prevState => ({ ...prevState, [data.id]: imageSrc }));
});
});
}
})
.catch((error) => {
alert("이미지 불러오기 실패");
console.error("Error:", error);
});
}, []); // 의존성 배열을 빈 배열로 전달하여 최초 렌더링 시에만 실행되도록 설정
- useEffect 훅을 사용하여 컴포넌트가 처음 렌더링될 때 API 요청을 보냅니다.
- axios를 사용하여 GET 요청을 보내고, 헤더에 인증 토큰을 포함합니다.
- 요청이 성공하면 상태 변수 setDataList를 사용하여 데이터를 저장합니다.
- 응답 데이터의 각 항목에 대해 requestImage 함수를 호출하여 이미지를 가져오고, setImageMap 상태 업데이트 함수를 사용하여 이미지 맵을 갱신합니다.
💻 [ 결과 ]
'Project 댕린이집' 카테고리의 다른 글
[게시판] 게시글 작성 (0) | 2024.06.18 |
---|---|
[사진첩] 사진첩 상세보기 (이미지 렌더링) (0) | 2024.06.12 |
[사진첩] 업로드 한 파일 경로 저장 (0) | 2024.06.10 |
[사진첩] 업로드 파일(이미지) 미리보기 (0) | 2024.05.09 |
[사진첩] 저장경로와 파일명 지정 (0) | 2024.05.08 |