오늘은 만들어 둔 기능들 중 사진 업로드, 좋아요 기능을 포스팅 하려고 한다. 사진 업로드 할 때 이미지 파일만 받을 수 있도록 하는 처리를 안 해뒀는데(이외에 또 살펴 보면 더 이런 저런 문제가 있을 것이다.) 아직 디버깅 하는 날이 아니기 때문에 바뀐 코드는 다음 포스팅 때 수정된 코드로 올리려고 한다.
const CustomIcon = styled(FavoriteIcon)({
position: 'absolute',
top: '18vh',
left: '9vw',
width: '2vw',
height: '4vh',
color: '#F5382E',
cursor: 'pointer',
'&: hover': {
transform: 'scale(1.2)',
transitionDuration: '0.1s',
}
});
const UploadInput = styled(TextField)({
'& .css-9ddj71-MuiInputBase-root-MuiOutlinedInput-root' : {
fontFamily: 'Orbit !important',
}
});
const PhotoList = () => {
const [open, setOpen] = useState<any>(false);
const [data, setData] = useState<any>([]);
const [uploadFile, setUploadFile] = useState<any>('');
const [userId, setUserId] = useState<any>('');
useEffect(() =>{
setUserId(localStorage.getItem("userId"));
}, []);
const getData = async () => {
axios.get('/photo/list')
.then( payload => {
if(payload.data){
setData(payload.data);
}
})
.catch(e => toast.error("데이터를 불러올 수 없습니다."));
}
useEffect(() => {
getData();
}, [open]);
const likePhoto = async (req: any) => {
await axios.get("/photo/like/" + req)
.then(
payload => {
if(payload.status === 200) {
toast.success("응원이 전달되었습니다❤️");
}
}
)
.catch(e => toast.error("응원을 전달할 수 없습니다. 다시 시도해주세요"));
}
const onChangeImg = (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
if(e.target.files){
const uploadFile = e.target.files[0]
setUploadFile(uploadFile);
}
}
const uploadImage = async () => {
if(!uploadFile) toast.error("선택된 파일이 없습니다. 이미지를 업로드해주세요");
const formData = new FormData();
formData.append("file", uploadFile);
await axios({
method: 'post',
url: '/photo/upload/' + userId,
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
}
})
.then(payload => {
if(payload.status === 200) {
toast.success("사진이 업로드 되었습니다.");
setOpen(!open);
}
}
)
.catch( e => {
toast.error("업로드 할 수 없습니다. 다시 시도해주세요");
});
}
return <div className="main_contents">
<div className="meeting-area">
<div className="fix-text-area">
<h1 className="color-darkgray">오늘 플로깅에 참여한 분들의 인증샷이에요</h1>
<h3 className="color_grey">다른 사용자들의 줍깅을 응원해주세요, 무한으로 응원할 수도 있어요</h3>
</div>
<div className="add-meeting">
<h4 className="color-darkgray">인증샷 올리기</h4>
<AddCircleIcon sx={{ width: '2vw', height: '4vh', cursor: 'pointer', color: green[500]}}
onClick = {() => setOpen(!open)}
/>
</div>
<div className="info-unit-meeting h50vh dis-grid over">
{data?.map(
(unit: any, idx: any) => (
<div className="photo basic_sort" key={idx}>
<img className="photo-size" src={"/upload_image/" + unit.storedFileName} alt="image"/>
<CustomIcon onClick={() => likePhoto(unit.id)}/>
</div>
)
)}
</div>
</div>
{
open &&
<>
<div className="modal-back">
<div className="modal">
<div className="close-area">
<CloseIcon sx={{cursor: 'pointer'}} onClick={() => setOpen(false)} />
</div>
<div className="upload-area basic_sort">
<UploadInput type='file' name="upload_image" onChange={onChangeImg}
/>
</div>
<div className="close-area">
<AddCircleIcon
onClick = {uploadImage}
sx={{ width: '2vw', height: '4vh', cursor: 'pointer', color: green[500]}} />
</div>
</div>
</div>
</>
}
</div>
}
export default PhotoList;
@RestController
@RequestMapping("/photo")
@Slf4j
@RequiredArgsConstructor
public class PhotoController {
private final PhotoService photoService;
@PostMapping("/upload/{userId}")
public void uploadFile(@PathVariable("userId") String userId,
@RequestPart MultipartFile file) throws IOException {
log.info("multipartFile={}", file);
photoService.uploadFile(userId, file);
}
@GetMapping("/list")
public List<PhotoDto> photoList(){
return photoService.list();
}
@DeleteMapping("/delete/{photoNo}")
public void deletePhoto(@PathVariable("photoNo") Long photoNo,
@RequestParam("userId") String userId) {
photoService.delete(photoNo, userId);
}
@GetMapping("/like/{photoNo}")
public void likePhoto(@PathVariable("photoNo") Long photoNo){
photoService.like(photoNo);
}
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class PhotoService {
private final PhotoListRepository photoListRepository;
private final UserRepository userRepository;
private final String fileDir = "내가 지정한 파일 저장소 위치";
@Transactional
public void uploadFile(String userId, MultipartFile file){
User findUser = userRepository.findByUserStrId(userId);
if(findUser == null ) throw new UserNotFoundException("유저 정보가 존재하지 않습니다");
if(file.isEmpty()) throw new IllegalArgumentException("파일을 등록하지 않았습니다");
String newFileName = createStoreFileName(file.getOriginalFilename());
try{
Files.write(Path.of(fileDir + newFileName), file.getBytes());
}catch(Exception e){
e.printStackTrace();
}
PhotoList savePhoto = new PhotoList
(findUser,
newFileName,
fileDir + file.getOriginalFilename());
photoListRepository.save(savePhoto);
}
public List<PhotoDto> list() {
return photoListRepository.photoList().stream().map(
photo -> new PhotoDto(
photo.getId(), photo.getUser().getUserId(),
photo.getImage(),
photo.getStoredFilename(),
photo.getLikes(),
photo.getUploadDate()
)
).collect(Collectors.toList());
}
@Transactional
public void delete(Long photoNo, String userId) {
PhotoList photo = photoListRepository.findById(photoNo).orElseThrow(IllegalArgumentException::new);
User user = userRepository.findByUserStrId(userId);
if(user.getId() != photo.getUser().getId()) throw new IllegalArgumentException();
photoListRepository.delete(photo);
}
@Transactional
public void like(Long photoNo) {
photoListRepository.findById(photoNo).orElseThrow(IllegalArgumentException :: new);
photoListRepository.addCount(photoNo);
}
public Long mainCount() {
return photoListRepository.mainCount();
}
public List<PhotoDto> userPhotoList(String userId) {
User findUser = userRepository.findByUserStrId(userId);
if(findUser == null) {
throw new UserNotFoundException("유저 정보를 찾을 수 없습니다.");
}
return photoListRepository.userPhotoList(findUser.getId());
}
private String createStoreFileName(String originalFileName) {
String uuid = UUID.randomUUID().toString();
String ext = extractExt(originalFileName);
return uuid + "." + ext;
}
private String extractExt(String fileName) {
int index = fileName.lastIndexOf(".");
return fileName.substring(index +1);
}
public Long myPloggingCount(String userId) {
User findUser = userRepository.findByUserStrId(userId);
if(findUser == null) {
throw new UserNotFoundException("유저 정보를 찾을 수 없습니다.");
}
return photoListRepository.myPloggingCount(findUser.getId());
}
}
public interface PhotoListRepository extends JpaRepository<PhotoList, Long>, PhotoCustomRepository {
}
public interface PhotoCustomRepository {
void addCount(Long photoNo);
Long mainCount();
List<PhotoList> photoList();
List<PhotoDto> userPhotoList(Long userNo);
Long myPloggingCount(Long id);
}
@RequiredArgsConstructor
public class PhotoCustomRepositoryImpl implements PhotoCustomRepository{
private final JPAQueryFactory queryFactory;
@Override
public void addCount(Long photoNo) {
queryFactory
.update(photoList)
.set(photoList.likes ,photoList.likes.add(1)).where(photoList.id.eq(photoNo))
.execute();
}
public Long mainCount() {
DateTemplate<String> findData = Expressions.dateTemplate(
String.class,
"DATE_FORMAT({0}, {1})",
photoList.uploadDate,
ConstantImpl.create("%Y-%m-%d"));
return queryFactory
.selectFrom(photoList)
.where(
findData.eq(Expressions.currentDate().stringValue())
)
.fetchCount();
}
@Override
public Long myPloggingCount(Long id) {
return queryFactory.selectFrom(photoList).where(photoList.user.id.eq(id)).fetchCount();
}
@Override
public List<PhotoList> photoList() {
return queryFactory
.selectFrom(photoList)
.orderBy(photoList.uploadDate.desc())
.fetch();
}
@Override
public List<PhotoDto> userPhotoList(Long userNo) {
List<PhotoList> data = queryFactory
.selectFrom(photoList)
.orderBy(photoList.uploadDate.desc())
.where(photoList.user.id.eq(userNo))
.fetch();
return data.stream().map(
photo -> new PhotoDto(
photo.getId(), photo.getUser().getUserId(),
photo.getImage(),
photo.getStoredFilename(),
photo.getLikes(),
photo.getUploadDate()
)
).toList();
}
}