이미 여러장 하는건 올렸으니 설명없이
react
# Components
function AddSkill() {
const navigate = useNavigate();
const initialSkillFile: ISkillFile = {
uuid: null, // 기본키(범용적으로 유일한 값을 만들어주는 값)
fileTitle: "", // 제목
fileName: "",
fileUrl: "", // 파일 다운로드 URL
};
const [skillFile, setSkillFile] = useState<ISkillFile>(initialSkillFile);
// todo: 현재 선택한 파일을 저장할 배열변수
const [selectedFiles, setSelectedFiles] = useState<FileList>();
// todo : 함수 정의
// todo: input 태그에 수동 바인딩
const onChangeInput = (event: any) => {
const { name, value } = event.target; // 화면값
setSkillFile({ ...skillFile, [name]: value }); // 변수저장
};
// todo : 파일 선택상자에서 이미지 선택시 실행되는 함수
// 파일 선택상자 html 태그 : <input type="file" />
const selectFile = (event: any) => {
// 화면에서 이미지 선택시 저장된 객체 : event.target.files
// 변수명 as 타입명 : 개발자가 변수가 무조건 특정타입이라고 보증함
// (타입스크립트에서 체크 안함)
setSelectedFiles(event.target.files as FileList);
};
const save = () => {
// 선택된 이미지 파일 배열변수
// 변수명? = 옵셔널체이닝, 변수의 값이 null이면 undefined 바꾸어줌
let currentFile = selectedFiles?.[0]; // 첫번째 선택된 파일
SkillFileService.upload(skillFile, currentFile) // 저장 요청
.then((response: any) => {
navigate("/admin");
})
.catch((e: Error) => {
console.log(e);
});
};
return (
<>
<Header name="이곳은 기술 스택 추가 공간입니다." />
<div className="container mt-5" style={{ width: "500px" }}>
<div className="mb-3">
<label htmlFor="exampleFormControlInput1" className="form-label">
제목
</label>
<input
type="email"
onChange={onChangeInput}
className="form-control"
id="exampleFormControlInput1"
name="fileTitle"
/>
</div>
<hr />
<h4>💾이미지 업로드</h4>
<div className="input-group mb-3">
{/* upload 선택상자/버튼 start */}
<input
type="file"
className="form-control mb-3"
id="inputGroupFile02"
onChange={selectFile}
/>
{/* upload 선택상자/버튼 end */}
</div>
<div>
{/* 이미지를 업로드하지 않은 경우 메시지 출력 */}
{!selectedFiles && (
<div className="alert alert-warning wow fadeInUp" role="alert">
🚨이미지 업로드는 필수입니다.
</div>
)}
{/* <!-- 이미지내용 입력 박스 끝 --> */}
</div>
<button type="button" className="btn btn-success me-3" onClick={save}>
추가하기
</button>
<Link to={"/admin"} type="button" className="btn btn-light">
뒤로 가기
</Link>
</div>
</>
);
}
export default AddSkill;
# Service
// 저장함수
// uploadSkillFile : 제목 + 타이틀(내용) 속성 가진 객체
// fileDb : 실제 이미지(첨부파일)
// FormData 객체를 이용해서 백엔드로 전송
const upload = (
uploadSkillFile: ISkillFile,
currentFile: any
): Promise<any> => {
// FormData 객체 생성 : Map 자료구조와 유사(키, 값)
let formData = new FormData();
formData.append("fileTitle", uploadSkillFile.fileTitle);
formData.append("currentFile", currentFile); // 첨부파일
console.log(formData);
return http.post("/skill-file/upload", formData, {
headers: {
// headers : 문서종류
"Content-Type": "multipart/form-data", // 첨부파일 형태로 보낸다.
},
});
};
Springboot
# application.properties
# TODO : file upload size
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB
# Entity
@Entity
@Getter
@Table(name = "SKILL_FILE")
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Setter
@DynamicInsert
@DynamicUpdate
// soft delete
@Where(clause = "DELETE_YN = 'N'")
@SQLDelete(sql = "UPDATE SKILL_FILE SET DELETE_YN = 'Y', DELETE_TIME=NOW() WHERE UUID = ?")
public class SkillFile extends BaseTimeEntity {
@Id
private String uuid;
private String fileTitle;
private String fileName;
@Lob
private byte[] fileData; // 첨부파일(이진파일) -> DB에 BLOB 형태로 저장됨
private String fileUrl; // 파일 다운로드 url
}
# Controller
// 저장함수
@PostMapping("/skill-file/upload")
public ResponseEntity<Object> create( // 객체로 받을수 없어서 각각 변수로 받아와야 함
@RequestParam(defaultValue = "") String fileTitle, // 제목
@RequestParam MultipartFile currentFile // 첨부파일
) {
try {
skillFileSerivce.save(
null, // 기본키
fileTitle, // 제목
currentFile // 첨부파일
); // db 저장
return new ResponseEntity<>("업로드 성공", HttpStatus.OK);
} catch (Exception e) {
// DB 에러가 났을경우 : INTERNAL_SERVER_ERROR 프론트엔드로 전송
log.info(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
# Service
// todo : 저장(수정) 함수(업로드)
public SkillFile save(String uuid,
String fileTitle,
MultipartFile file // 첨부파일 객체 (MultipartFile)
) {
SkillFile skillFile2 = null;
try {
// 기본키 : uuid
if (uuid == null) {
// 저장 실행
// 1) DB에 이미지 저장
// 2) DB에 이미지를 다운로드 할 수 있는 url 저장 (다운로드 URL 만들기 필요)
// 3) 파일명(중복이 안되는) : uuid(기본키) 사용(유일값)
// TODO : 1) uuid 만들기
String tmpUuid = UUID.randomUUID() // UUID 랜덤 생성함수
.toString() // 문자열 변환
.replace("-", ""); // -를 빈문자열로 변환(편의상)
// TODO : 2) 다운로드 url 만들기
String fileDownloadUri = ServletUriComponentsBuilder
.fromCurrentContextPath() // 기본 경로 : localhost:8000
.path("/api/skill-file/") // 추가 경로 : 기본경로 + "/api/skill-file/"
.path(tmpUuid) // 기본경로 + 추가경로 + uuid 붙임
.toUriString(); // 문자열 변환
// 최종 url 예 : localhost:8000/api/advanced/skillFile/xxxxiiiii
// TODO : 3) 위의 정보를 SkillFile 객체에 저장 후 DB save 함수 실행
SkillFile skillFile = new SkillFile(
tmpUuid, // UUID
fileTitle, // 제목
file.getOriginalFilename(), // 실제 이미지 파일명 (예)course.jpg)
file.getBytes(), // 이미지 파일 크기 (100byte...)
fileDownloadUri); // 다운로드 url
skillFile2 = skillFileRepository.save(skillFile);
} else {
// 수정 실행
// TODO : 2) 다운로드 url 만들기
String fileDownloadUri = ServletUriComponentsBuilder
.fromCurrentContextPath() // 기본 경로 : localhost:8000
.path("/api/skill-file/") // 추가 경로 : 기본경로 + /api/advanced/skillFile/
.path(uuid) // 기본경로 + 추가경로 + 기존 uuid 붙임
.toUriString(); // 문자열 변환
// 최종 url 예 : localhost:8000/api/advanced/skillFile/xxxxiiiii
// TODO : 3) 위의 정보를 SkillFile 객체에 저장 후 DB save 함수 실행
SkillFile skillFile = new SkillFile(
uuid, // 기존 UUID
fileTitle, // 제목
file.getOriginalFilename(), // 실제 이미지 파일명 (예)course.jpg)
file.getBytes(), // 이미지 파일 크기 (100byte...)
fileDownloadUri); // 다운로드 url
skillFile2 = skillFileRepository.save(skillFile);
}
} catch (Exception e) {
log.debug(e.getMessage());
}
return skillFile2;
}
'SpringBoot' 카테고리의 다른 글
WebSocket - binary , text(참조) 타입 보내기 (1) | 2024.01.07 |
---|---|
Springboot 2.x 버젼 - QueryDSL 설정 (0) | 2023.12.23 |
Springboot - 파일 여러장을 저장해보자 (1) | 2023.12.21 |
SprinBoot - logback , log4jdbc 설정 (1) | 2023.12.17 |
Sprinboot 3.x버젼 - QueryDSL 설정과 간단하게 사용해보자 (1) | 2023.12.17 |