일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 달력 라이브러리
- 마이페이지
- 배포
- Token
- ui탬플릿
- RCPS
- Typesciprt
- 빌드 및 배포
- MRC
- 로그인 로직
- react
- Update
- 로그인
- 공통메서드
- 파생상품평가
- stock option
- 밸류즈
- 캘린더 라이브러리
- 인증처리
- 관리자페이지
- userManagement
- 밸류즈 홈페이지
- 회원가입로직
- Styled Components
- Ajax
- 이미지 업로드
- jsonwebtoken
- register
- 스프링시큐리티
- mypage
- Today
- Total
I T H
[MySchedule project] 10. 마이페이지 로직(2) - 이미지(파일) 업로드 본문
이번 챕터는 마이페이지의 이미지 파일 업로드에 대해 다뤄보고자 한다.
마이페이지에서 사용자가 원하는 이미지를 등록할 수 있고, 이미지를 제거 할 수 있다.
Frontend
[ pages / Mypage / index.tsx ]
- 아래 코드를 추가해준다.
- "이미지 업로드 컴포넌트 UserImageFile"에 props로 myData의 userImage와 함수 handleUmageChange를 보내준다.
//이미지 업로드 함수
const handleImageChange = (updatedImage: string) => {
setMydata((prevState) => ({
...prevState,
userImage: updatedImage,
}));
};
<tr className=" border-t-[1px] h-[160px] w-full">
<th className="p-3 bg-neutral-100 sm:text-sm text-xs font-semibold sm:w-4/12 w-5/12">
이미지
</th>
<td className="py-3 w-8/12">
<UserImageFile
userImage={myData.userImage}
handleImageChange={handleImageChange}
/>
</td>
</tr>
[ pages / Mypage / UserImageFile.tsx ]
1. props로 userImage와 함수handleImageChange를 받아온다.
- userImage는 이미지파일의 이름이 들어있는 변수이고, handleImageChange함수는 새로운 이미지 파일을 state에 업데이트 시켜주도록 구현한 함수이다.
2. 이미지 업로드 버튼 클릭시 이벤트가 발생하고, 파일의 유효성 체크 검사를 실시한다.
- 파일이 없는 경우와, 파일 확장자 체크, 파일의 크기를 체크한다.
- 파일의 검사가 정상적으로 이루어 졌다면 파일을 formData에 담아서 axios를 통해 백앤드로 전달한다.
3. 파일의 삭제
- 실제 파일의 삭제 유무는 사용자가 회원수정 버튼을 최종 클릭시에 삭제되도록 구현하였다.
- 원래 등록된 이미지 파일이 있을 경우 삭제를 눌렀더라도 회원수정버튼을 최종적으로 누르지 않았다면 파일을 다시 불러 올 수 있게 구현하였다. 즉, 파일을 실수로 삭제했더라도 다시한번 회원수정버튼을 통해 파일삭제를 정확히 원하는지 사용자에게 인지시키기 위함이다.
import React, { ChangeEvent } from "react";
import { toast } from "react-toastify";
import axiosInstance from "../../utils/axios";
import "./UserImage.css";
//파라미터 타입들
interface ownProps {
userImage: string;
handleImageChange(updatedImage: string): void;
}
const UserImageFile = ({ userImage, handleImageChange }: ownProps) => {
const FILE_SIZE_MAX_LIMIT = 5 * 1024 * 1024; // 파일용량 : 5MB
//const SERVER_URL = process.env.REACT_APP_SERVER_URL;
//파일업로드 start
const fileUpload = async (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
const files = e.target.files as FileList;
// 파일이 없는 경우도 체크
// 파일업로드시 취소 버튼누를때 파일이 초기화 되기때문에 파일size를 불러올수없음
// 그래서 뒤에 있는 if문들의 조건이 에러뜸
if (files.length <= 0) {
return;
}
// 파일 용량 체크
if (files[0].size > FILE_SIZE_MAX_LIMIT) {
// handleImageChange("");
toast.info("파일업로드 용량은 5MB를 초과할 수 없습니다.");
return;
}
// 파일 확장자 체크
const fileForm = /(.*?)\.(jpg|jpeg|gif|bmp|png)$/;
if (!files[0].name.match(fileForm)) {
toast.info("jpg,jpeg,gif,bmp,png 파일 업로드만 가능합니다.");
// handleImageChange("");
return;
}
let formData = new FormData();
const config = {
headers: { "content-type": "multipart/form-data" },
};
formData.append("test", "111111");
formData.append("file", files[0]);
console.log(formData);
try {
const response = await axiosInstance.post(
"/mypage/image",
formData,
config
);
console.log(response.data.fileName);
handleImageChange(response.data.fileName);
} catch (error) {
console.error(error);
}
};
//파일업로드 end
//파일 삭제 -> 실제 삭제는 회원정보수정까지 눌러야 삭제되도록 처리
const handleImageDelete = () => {
handleImageChange("");
};
return (
<div className="">
<div
className="text-left border-[1px] h-[150px] w-7/12 rounded-md mx-2 tooltip"
onClick={() => handleImageDelete()}
>
{userImage && (
<img
className="w-full h-full object-cover rounded-md"
alt={userImage}
src={`${process.env.REACT_APP_SERVER_URL}/${userImage}`}
/>
)}
{userImage && (
<div className="tooltip-content">
<p>클릭시 이미지가 없어집니다.</p>
</div>
)}
</div>
<div className="my-8 mx-2 text-left">
<label
className="bg-blue-500 h-[50px] text-white sm:font-bold font-semibold sm:text-sm text-[11px] py-2 px-2 rounded-md hover:bg-blue-600 hover:cursor-pointer"
htmlFor="imgUpload"
>
이미지업로드
</label>
<input
onChange={fileUpload}
className="hidden"
type="file"
id="imgUpload"
/>
</div>
</div>
);
};
export default UserImageFile;
[ pages / Mypage / UserImage.css ]
- 파일의 삭제 이벤트가 실행되고, hover를 할 경우 뜨게하는 css부분인데 tooltip을 사용하였다.
/* .tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltip-content {
visibility: hidden;
border-radius: 5px;
margin-left: 2px;
width: 250px;
background-color: rgb(43, 43, 44);
padding: 0;
margin-top: 2px;
margin-bottom: 2px;
color: rgb(230, 230, 238);
text-align: center;
position: absolute;
z-index: 1;
}
.tooltip:hover .tooltip-content {
visibility: visible;
} */
.tooltip {
position: relative;
display: inline-block;
/* border-bottom: 1px dotted black; */
}
.tooltip .tooltip-content {
visibility: hidden;
width: 200px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 5px;
padding: 5px 0;
position: absolute;
z-index: 1;
top: 105%;
left: 35%;
margin-left: -60px;
}
.tooltip .tooltip-content::after {
content: "";
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent black transparent;
}
.tooltip:hover .tooltip-content {
visibility: visible;
}
Backend
[ routes / mypage.js ]
1. 파일업로드 코드 추가
- 전 챕터에서는 회원정보 수정까지만 다뤘으므로, 추가적으로 파일업로드 코드를 추가해준다.
- 파일업로드는 multer라이브러리를 사용해서 실제 파일을 uploads 폴더 안에 업로드 시키고, 파일 이름을 만들어 프론트로 보내준다.
2. 4가지의 경우의 수
- 이젠 이미지 업로드까지 합쳐지면 4가지의 경우의수가 나온다.
1) 사용자가 패스워드를 입력한 상태로 업데이트를 진행한 경우 + 이미지 업로드 한 경우
2) 사용자가 패스워드를 입력한 상태로 업데이트를 진행한 경우 + 이미지 업로드 안한 경우
3) 사용자가 패스워드를 입력하지 않은 상태로 업데이트를 진행한 경우 + 이미지 업로드 한 경우
4) 사용자가 패스워드를 입력하지 않은 상태로 업데이트를 진행한 경우 + 이미지 업로드 안한 경우
- 4가지의 경우의 수를 다 충족시키려면 코드가 많이 중복되기 때문에 공통 함수를 만들어서 관리하는 것이 가독성에 좋고 중복성을 줄일 수 있다. 그래서 이미지 유무에 대한 공통함수를 만들어서 상황에 맞게 호출시켰다.
const express = require("express");
const auth = require("../middleware/auth");
const User = require("../models/User");
const router = express.Router();
const bcrypt = require("bcryptjs");
const multer = require("multer");
const fsExtra = require("fs-extra");
// 파일업로드 start (multer를 사용한 )
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "uploads/"); //파일을 넣을 폴더이름
},
filename: function (req, file, cb) {
cb(null, `${Date.now()}_${file.originalname}`);
},
});
const upload = multer({ storage: storage }).single("file");
router.post("/image", auth, async (req, res, next) => {
upload(req, res, (err) => {
if (err) {
return req.statusCode(500).send(err);
}
return res.json({ fileName: res.req.file.filename });
});
});
// 파일업로드 end
//파일 한개 삭제 메서드
// const uploadRemove = (file_name) => {
// fs.unlinkSync("/uploads" + file_name);
// };
//회원정보 수정
router.post("/userDataUpdate", async (req, res, next) => {
try {
console.log(req.body);
let userPassword = "";
let commonData = {
userName: req.body.userName,
userEmail: req.body.userEmail,
userPhone: req.body.userPhone,
};
//이미지 유무 공통메서드
const userImageCommon = () => {
if (req.body.userImage !== "" && req.body.userImage !== null) {
//이미지가 있는경우
commonData.userImage = req.body.userImage;
} else {
commonData.userImage = "";
//실제파일 전체삭제 - 한개씩 삭제는 위에주석해놓음.
fsExtra.emptyDirSync("uploads/");
}
};
//패스워드를 입력한경우
if (req.body.userPassword !== "" && req.body.userPassword !== null) {
const salt = await bcrypt.genSalt(10); //salt생성
userPassword = await bcrypt.hash(req.body.userPassword, salt);
commonData.userPassword = userPassword;
userImageCommon();
} else {
console.log("test");
userImageCommon();
}
console.log(commonData);
const updateUser = await User.findOneAndUpdate(
{ userId: req.body.userId },
commonData,
{ new: true }
);
console.log(updateUser);
return res.json({
userId: updateUser.userId,
userName: updateUser.userName,
userPhone: updateUser.userPhone,
userEmail: updateUser.userEmail,
userImage: updateUser.userImage,
role: updateUser.role,
});
} catch (error) {
next(error);
}
});
module.exports = router;
[ 결과화면 ]





'React + Node.js' 카테고리의 다른 글
[MySchedule project] 12. 카카오 로그인 구현 (1) - Frontend (0) | 2024.02.12 |
---|---|
[MySchedule project] 11. 관리자 - 사용자관리 로직 mui x-data-grid와 mui modal 사용 (1) | 2024.02.07 |
[MySchedule project] 9. 마이페이지 로직(1) - 조회, 업데이트 (1) | 2024.02.01 |
[MySchedule project] 8. 인증에 의한 header, Route 로직 / 로그아웃 (2) | 2024.01.31 |
[MySchedule project] 7. JWT(json web token) / 로그인 로직(2) (2) | 2024.01.30 |