프론트에서 백엔드로 데이터를 보내고자 할 때 우리는 보통 FormData를 활용해 백엔드에 데이터를 보낸다. 근데 FormData에는 File, text 두가지 형태만 들어갈 수 있다. 이때 리스트형태의 객체를 보내게 되면서 문제가 발생 하였다.
FormData에는 리스트 형태를 넣을 수 없어 json형태로 각각의 리스트 값을 키와 값으로 넣어서 보냈다. 그래서 이것을 spring boot에서는 우리가 보내는 방식에 맞게 저장해야한다.
interface CrewData {
crewName: string;
crewMembers: number[];
crewImg?: string | File | null;
}
const [friendList, setFriendList] = useState<
{ memberNickname: string; memberImg: string; memberSeq: number }[]
>([]);
const [crew, setCrew] = useState<number[]>([]);
const [crewName, setCrewName] = useState("");
const [crewImg, setCrewImg] = useState<File | null>(null);
<>
<form onSubmit={handleMakeCrew}>
<input
style={inputStyle}
value={crewName}
placeholder="그룹명을 적어주세요"
onChange={handleCrewName}
/>
<div>
{crewImg && (
<img
src={URL.createObjectURL(crewImg)}
alt="Crew"
width="100"
height="100"
style={{ marginTop: "20px" }}
/>
)}
<StyledButton
type="button"
width="25vw"
fontSize="10px"
onClick={handleCrewImgUpload}
background="#FFF6EC"
color="gray"
>
사진 업로드
</StyledButton>
<input
accept="image/*"
type="file"
hidden
id="crewUploadInput"
onChange={(e) => handleFileChange(e, setCrewImg)}
/>
<StyledButton fontSize="15px" type="submit">
생성하기
</StyledButton>
</div>
</form>
{friendList.map((friend, i) => {
return (
<CrewAdd
name={friend.memberNickname}
imgUrl={friend.memberImg}
key={friend.memberSeq}
onClick={() => toggleFriend(friend.memberSeq)}
selected={crew.includes(friend.memberSeq)}
/>
);
})}
</>
import React from "react";
import classes from "./CrewAdd.module.css";
import axios from "axios";
import ToggleButton from "@mui/material/ToggleButton";
interface FriendProps {
name: string;
key: number;
imgUrl: string | null;
onClick: () => void;
selected: boolean;
}
export const CrewAdd: React.FC<FriendProps> = ({
name,
key,
imgUrl,
onClick,
selected,
}) => {
return (
<>
<div className={classes.friendBox} onClick={onClick}>
<div className={classes.profileBox}>
{imgUrl ? (
<img
src={imgUrl}
alt="sds"
key={key}
className={classes.profileImg}
/>
) : (
<img
src="/images/profileImg.png"
alt="sds"
key={key}
className={classes.profileImg}
/>
)}
</div>
<h3>{name}</h3>
<div
style={{
backgroundColor: selected ? "orange" : "white",
borderRadius: "50%",
border: "1px solid black",
width: "5vw",
height: "5vw",
marginLeft: "30vw",
}}
></div>
</div>
</>
);
};
const handleCrewName = (e: ChangeEvent<HTMLInputElement>) => {
setCrewName(e.target.value);
};
const handleCrewImgUpload = () => {
const input = document.getElementById(
"crewUploadInput"
) as HTMLInputElement;
input?.click();
};
const handleFileChange = (
event: ChangeEvent<HTMLInputElement>,
setter: React.Dispatch<React.SetStateAction<File | null>>
) => {
const file = event.target.files?.[0];
if (file) {
setter(file);
}
};
formData.append("key값",value값);
formData.append("이마자 key값", 이미지파일);
const crewMembersJSON = JSON.stringify(
crew.map((memberSeq) => ({ memberSeq }))
);
formData.append("crewMembers", crewMembersJSON);
const handleMakeCrew = (e: React.FormEvent) => {
e.preventDefault(); // submit시 렌더링 막기
let crewData: CrewData | undefined;
const formData = new FormData(); //formData 생성
formData.append("crewName", crewName); // crewName 추가
if (crewImg) {
formData.append("crewImg", crewImg); // crewImg 추가
}
// 리스트를 JSON 형식으로 key,value값으로 저장하는 코드
const crewMembersJSON = JSON.stringify(
crew.map((memberSeq) => ({ memberSeq }))
);
formData.append("crewMembers", crewMembersJSON);
// formData를 post로 보내기
if (friendCount >= 1) {
axios
.post(`${process.env.REACT_APP_BASE_URL}/crew/regist`, formData, {
headers: {
"Content-Type": "multipart/form-data",
Authorization: `Bearer ${localStorage.getItem("accesstoken")}`,
},
})
.then((res) => {
navigate("/crew");
})
.catch((err) => console.log(err));
} else {
toast.error("최소 1명이상의 친구를 골라주세요.", {
position: "top-center",
autoClose: 1000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
});
}
};
ps) formData는 console.log(formData) 하면 값이 나오지 않는다.
아래의 방식으로 formData.get("key값")을 해주면된다.
console.log(formData.get("crewMembers"));
추가로 react-hook-form을 쓰면 formData를 사용할 필요없이 바로 보낼 수 있다.
Dto를 만들 때 리스트의 formData가 text,file 형태로만 보낼 수 있다는 것을 모르면 Dto에서 리스트의 타입을 List로 설정 할 수 있다.
List가 아니라 string으로 해줘야한다.
@Data
@NoArgsConstructor
public class CrewSignUpDto {
private String crewName;
private MultipartFile crewImg;
private String crewMembers;
@Builder
public CrewSignUpDto(String crewName, MultipartFile crewImg, String crewMembers) {
this.crewName = crewName;
this.crewImg = crewImg;
this.crewMembers = crewMembers;
}
}
@Transactional
public void registCrewforMember(CrewSignUpDto crewSignUpDto, String memberEmail)
throws IllegalArgumentException, EntityNotFoundException, JsonProcessingException {
if(crewSignUpDto.getCrewMembers().isEmpty()) {
throw new IllegalArgumentException("그룹은 최소 2명 이상이여야 합니다.");
}
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(crewSignUpDto.getCrewMembers());
List<Long> memberSeqs = new ArrayList<>(); // JsonNode를 순회하며 데이터 추출
for (JsonNode node : jsonNode) {
Long memberSeq = node.get("memberSeq").asLong();
memberSeqs.add(memberSeq);
}
Member member = memberRepository.findByEmail(memberEmail)
.orElseThrow(() -> new EntityNotFoundException("회원을 찾을 수 없습니다."));
String S3_fileName="";
if(crewSignUpDto.getCrewImg() != null){
S3_fileName = "crewImg/" + foodService.getRandomFileName();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(crewSignUpDto.getCrewImg().getContentType());
metadata.setContentLength(crewSignUpDto.getCrewImg().getSize());
try {
amazonS3Client.putObject(bucket, S3_fileName, crewSignUpDto.getCrewImg().getInputStream(), metadata);
}catch (Exception e) {
e.printStackTrace();
throw new IllegalStateException("이미지 저장 중 에러 발생");
}
}
// Crew 생성
Crew crew = Crew.builder()
.name(crewSignUpDto.getCrewName())
.img(S3_fileName)
.status("투표전")
.createdAt(LocalDateTime.now())
.lastModifiedAt(LocalDateTime.now())
.build();
crew = crewRepository.save(crew);
for (Long memberSeq:memberSeqs) {
CrewMember crewMember = CrewMember.builder()
.crew(crew)
.member(
Member.builder()
.memberSeq(memberSeq)
.build()
)
.checkVote(1)
.status(0)
.build();
crewMemberRepository.save(crewMember);
}
CrewMember crewMember = CrewMember.builder()
.crew(crew)
.member(member)
.status(1)
.checkVote(1)
.build();
crewMemberRepository.save(crewMember);
log.info("crew 생성 완료");
}
crewSignUpDto에서 crewMembers를 가져와서 JsonNode로 객체로 변환
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(crewSignUpDto.getCrewMembers());
List<Long> memberSeqs = new ArrayList<>();
// JsonNode를 순회하며 데이터 추출
for (JsonNode node : jsonNode) {
Long memberSeq = node.get("memberSeq").asLong();
memberSeqs.add(memberSeq);
}
Member member = memberRepository.findByEmail(memberEmail)
.orElseThrow(() -> new EntityNotFoundException("회원을 찾을 수 없습니다."));
S3_fileName : Amazon S3에 업로드할 파일의 경로 및 이름을 저장하는 빈 문자열
part1 : Amazon S3에 업로드될 이미지 파일의 경로와 파일 이름을 생성 이 경로는 "crewImg/"에 foodService.getRandomFileName() 메소드를 사용하여 무작위 파일 이름을 생성한 후 더함
part2 : Amazon S3 객체 메타데이터를 생성합니다. 이 메타데이터에는 업로드한 이미지 파일의 유형(contentType)과 크기(contentLength)가 설정
part3 : 이미지 파일의 contentType과 size 설정
part4 : amazonS3Client.putObject() 메소드를 사용하여 이미지 파일을 Amazon S3 버킷에 업로드하고, 업로드 중에 에러가 발생하면 예외를 처리하고 에러 메시지를 출력
String S3_fileName="";
if(crewSignUpDto.getCrewImg() != null){
// part1
S3_fileName = "crewImg/" + foodService.getRandomFileName();
// part2
ObjectMetadata metadata = new ObjectMetadata();
//part3
metadata.setContentType(crewSignUpDto.getCrewImg().getContentType());
metadata.setContentLength(crewSignUpDto.getCrewImg().getSize());
// part4
try {
amazonS3Client.putObject(bucket, S3_fileName, crewSignUpDto.getCrewImg().getInputStream(), metadata);
}catch (Exception e) {
e.printStackTrace();
throw new IllegalStateException("이미지 저장 중 에러 발생");
}
}
Crew crew = Crew.builder()
.name(crewSignUpDto.getCrewName())
.img(S3_fileName)
.status("투표전")
.createdAt(LocalDateTime.now())
.lastModifiedAt(LocalDateTime.now())
.build();
crew = crewRepository.save(crew);