서버사이드 랜더링 방식 사용
//쿼리메서드나, deleteById등은 한건씩 진행을 한다
//@Query 를 사용해서 update,delete 할 경우에 사용 Bulk 연선을 함
//그래서 트랜젝션을 복수개 할 것을 한번에 처리하기 때문에
//복수의 트랜잭션으 한번에 처리하기 위해 @Modifying을 사용
@Modifying
@Query("delete from Review r where r.member = :member")
void deleteByMember(Member member);
application.properties의 역할은 프로젝트의 초기화와 같은 역할을 한다
# App name
spring.application.name=ex6
# Server port
server.port=8080
# Context path
server.servlet.context-path=/ex6
# Restart WAS
spring.devtools.livereload.enabled=true
# Spring Datasource
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/db7
spring.datasource.username=db7
spring.datasource.password=1234
# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true
# Thymeleaf
spring.thymeleaf.cache=false
#File upload setting for Application
com.example.upload.path=c:\\upload
파일 업로드를 하기 위한 라이브러리를 추가해줌
#File upload setting for Application
com.example.upload.path=c:\\upload
용량이 크기 떄문에 @PostMapping
을 사용
HTTP POST 요청을 처리하기 위해 사용됩니다. Spring MVC에서 컨트롤러 메서드를 특정 URL 경로에 매핑하고, 해당 경로로 들어오는 POST 요청을 처리할 수 있도록 합니다. 주로 폼 데이터를 서버로 전송하거나, 서버에 자원을 생성할 때 사용됩니다
@RestController
는 해당 클래스가 RESTful 웹 서비스의 컨트롤러임을 나타냅니다.@RequestMapping("/api")
는 이 컨트롤러의 모든 요청이/api
경로로 시작됨을 지정합니다.@PostMapping("/create")
는/api/create
경로로 들어오는 POST 요청을create
메서드에 매핑합니다.@RequestBody
는 요청 본문(body)에 담긴 JSON이나 XML 데이터를 Java 객체로 변환하여 메서드의 매개변수로 전달합니다.
MultipartFile
은 파일 업로드를 처리하기 위해 제공되는 인터페이스입니다. MultipartFile
을 사용하면 클라이언트가 전송한 파일을 서버에서 손쉽게 다룰 수 있습니다. 여기서 MultipartFile
은 주로 Spring MVC나 Spring Boot에서 파일 업로드 기능을 구현할 때 사용됩니다.
기본 개념
- MultipartFile 인터페이스: 파일의 메타데이터(파일 이름, 콘텐츠 유형 등)와 파일 데이터(파일의 바이트 배열)를 다룰 수 있는 방법을 제공합니다.
- MultipartFile의 주요 메소드:
getName()
: 파일의 파라미터 이름을 반환합니다.getOriginalFilename()
: 업로드된 파일의 원래 이름을 반환합니다.getContentType()
: 파일의 MIME 타입을 반환합니다.getBytes()
: 파일의 내용을 바이트 배열로 반환합니다.getInputStream()
: 파일의 내용을InputStream
으로 반환합니다.transferTo(File dest)
: 파일을 지정된 위치로 저장합니다.
@RestController
@Log4j2
public class UploadController {
@PostMapping("/uploadAjax")
public void upload(MultipartFile[] uploadFiles){
}
}
파일 업로드 컨트롤러
//파일을 업로드
@PostMapping("/uploadAjax")
public void upload(MultipartFile[] uploadFiles){
for(MultipartFile multipartFile: uploadFiles){
String originalName = multipartFile.getOriginalFilename();
String fileName = originalName.substring(originalName.lastIndexOf("\\")+1);
log.info("fileName: "+fileName);
}
}
multiple
하나의 파일이 아닌 여러개의 파일을 올릴 수 있게 해줌
<input type="file" name="uploadFiles" multiple>
Axios는 JavaScript에서 HTTP 요청을 쉽게 수행할 수 있도록 도와주는 인기 있는 라이브러리입니다.
주로 웹 애플리케이션에서 클라이언트와 서버 간의 통신을 관리하는 데 사용됩니다.
이는 브라우저나 Node.js 환경에서 모두 사용할 수 있으며, 비동기적 HTTP 요청을 지원합니다.
axios 를 사용하는 방법
<scirpt src="http://unpkg.con/axios/dist/axios.min.js"></scirpt>
- Axios의 주요 역할과 기능
- HTTP 요청 처리:
- Axios는 GET, POST, PUT, DELETE 등 다양한 HTTP 메서드를 통해 서버와 데이터를 주고받을 수 있습니다. 예를 들어, 서버에서 데이터를 가져오거나(Form 데이터 전송, JSON 데이터 송수신 등) 서버에 데이터를 보낼 수 있습니다.
- Promise 기반:
- Axios는 Promise API를 기반으로 하여 비동기적으로 요청을 처리합니다. 이로 인해 요청 성공 시
.then()
메서드를, 실패 시.catch()
메서드를 사용하여 응답을 처리할 수 있습니다. 이는 비동기 작업의 결과를 쉽게 다룰 수 있도록 해줍니다.
- Axios는 Promise API를 기반으로 하여 비동기적으로 요청을 처리합니다. 이로 인해 요청 성공 시
- 자동 JSON 데이터 변환:
- Axios는 요청 시 보내는 데이터와 응답으로 받는 데이터를 자동으로 JSON으로 변환해줍니다. 이를 통해 데이터 직렬화 및 역직렬화 작업을 간단하게 처리할 수 있습니다.
- HTTP 요청/응답 인터셉터:
- Axios는 요청과 응답을 가로채서 공통 작업(예: 인증 헤더 추가, 로딩 상태 표시 등)을 처리할 수 있는 인터셉터(interceptors)를 제공합니다. 이를 통해 모든 요청이나 응답에 대해 공통 작업을 처리할 수 있습니다.
- 요청 취소:
- Axios는 요청을 취소할 수 있는 기능을 제공합니다. 이는 특정 조건에 따라 불필요한 요청을 취소하고, 자원 사용을 절약할 수 있습니다.
- 타임아웃 설정:
- Axios는 각 요청에 대해 타임아웃을 설정할 수 있습니다. 요청이 지정된 시간 내에 완료되지 않으면 요청이 자동으로 취소됩니다.
- 기본 URL 및 헤더 설정:
- Axios 인스턴스를 생성할 때 기본 URL 및 공통으로 사용될 헤더를 설정할 수 있습니다. 이를 통해 각 요청마다 반복되는 설정을 줄일 수 있습니다.
// Axios 라이브러리 불러오기 import axios from 'axios'; // GET 요청 예시 axios.get('https://api.example.com/data') .then(response => { console.log(response.data); // 요청 성공 시 처리 }) .catch(error => { console.error('Error:', error); // 요청 실패 시 처리 }); // POST 요청 예시 axios.post('https://api.example.com/data', { name: 'John Doe', age: 30 }) .then(response => { console.log(response.data); // 요청 성공 시 처리 }) .catch(error => { console.error('Error:', error); // 요청 실패 시 처리 });
- HTTP 요청 처리:
폼을 생성하는 방법
var formData = new FormData();
속성 선택자를 사용해서 inputfiles 안에 해당하는 속성의 타입
const inputFiles = document.querySelector("input[type='file']");
var files = inputFiles.files;
<script th:inline="javascript">
document.querySelector(".uploadBtn").onclick= function(){
var formData = new FormData();
const inputFiles = document.querySelector("input[type='file']");
var files = inputFiles.files;
for(let i=0; i<files.length; i++){
console.log(files[i])
formData.append("uploadFiles", files[i]);
}
const url = /*[[@{/uploadAjax}]]*/'url'
}
</script>
파일 여러개 선택하고 upload 하면 다음과 같이 console 안에 출력됨
window.onload
를 사용하는 이유
- 전체 페이지 로드 보장:
window.onload
는 페이지의 모든 콘텐츠가 로드된 후에만 실행됩니다. 따라서 이미지를 포함한 모든 리소스가 준비된 이후에 특정 작업을 수행할 수 있습니다.
- DOM 조작 시 안정성 보장:
- JavaScript를 사용하여 DOM(Document Object Model)을 조작할 때, DOM이 완전히 구성되기 전에 코드를 실행하면 오류가 발생할 수 있습니다.
window.onload
를 사용하면 이러한 오류를 방지할 수 있습니다.
- JavaScript를 사용하여 DOM(Document Object Model)을 조작할 때, DOM이 완전히 구성되기 전에 코드를 실행하면 오류가 발생할 수 있습니다.
- 초기화 작업 수행:
- 웹 애플리케이션의 초기화 작업(예: 초기 데이터 로드, 이벤트 리스너 등록 등)을 페이지가 완전히 로드된 후에 수행해야 하는 경우에 유용합니다.
<script th:inline="javascript">
window.onload = function(){
document.querySelector(".uploadBtn").onclick = function() {
var formData = new FormData();
const inputFiles = document.querySelector("input[type='file']");
var files = inputFiles.files;
for(let i=0;i<files.length;i++){
console.log(files[i])
formData.append("uploadFiles", files[i]);
}
const url = /*[[@{/uploadAjax}]]*/'url'
}
}
</script>
Spring Framework의 Thymeleaf 템플릿 엔진
const url = /*[[@{/uploadAjax}]]*/'url';
/*[[@{/uploadAjax}]]*/
:- 이 부분은 Thymeleaf에서 사용되는 표현식으로, 주석 안에 포함된 텍스트가 서버 측에서 처리됩니다.
@{...}
은 Thymeleaf의 URL 표기법으로, URL을 동적으로 생성하기 위해 사용됩니다.{}
안에 지정된 경로는 애플리케이션의 컨텍스트 경로를 포함하여 올바른 URL로 변환됩니다./uploadAjax
는 애플리케이션 내의 특정 경로를 나타내며, 이 경로에 해당하는 실제 URL이 생성됩니다.
'url'
:'url'
은 JavaScript의 문자열로, 실제 URL이 Thymeleaf에 의해 동적으로 생성된 후 대체될 문자열입니다.
동작 방식
- 서버 측에서 Thymeleaf 템플릿을 렌더링할 때,
/*[[@{/uploadAjax}]]*/
구문이 실행됩니다. - 이 구문은 현재 애플리케이션의 컨텍스트 경로와 결합하여
/uploadAjax
에 대한 완전한 URL을 생성합니다. - 생성된 URL은
'url'
이라는 기본 문자열을 대체합니다.
axios 파일을 업로드 하기 위해서는 다음 코드를 작성해야함
axios.post(url, formData, {
headers: {
"Content-Type" : "multipart/form-data",
"Access-Control-Allow-Origin" : "*",
"process-data" : false,
"content-type" : false,
}
})
.then(res => {console.log(res.data)})
.catch(err=> console.log("Error occourred: ",err))
src/main/resources/templates/uploadEx.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="file" name="uploadFiles" multiple>
<button class="uploadBtn">Upload</button>
<div class="uploadResult"></div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script th:inline="javascript">
window.onload = function(){
document.querySelector(".uploadBtn").onclick = function() {
var formData = new FormData();
const inputFiles = document.querySelector("input[type='file']");
var files = inputFiles.files;
for(let i=0;i<files.length;i++){
console.log(files[i])
formData.append("uploadFiles", files[i]);
}
const url = /*[[@{/uploadAjax}]]*/'url'
axios.post(url, formData, {
headers: {
"Content-Type" : "multipart/form-data",
"Access-Control-Allow-Origin" : "*",
"process-data" : false,
"content-type" : false,
}
})
.then(res => {console.log(res.data)})
.catch(err=> console.log("Error occourred: ",err))
}
}
</script>
</body>
</html>
@Value("${com.example.upload.path}")
@RestController
@Log4j2
public class UploadController {
@Value("${com.example.upload.path}")
private String uploadPath;
//파일을 업로드
@PostMapping("/uploadAjax")
public void upload(MultipartFile[] uploadFiles){
for(MultipartFile multipartFile: uploadFiles){
String originalName = multipartFile.getOriginalFilename();
String fileName = originalName.substring(originalName.lastIndexOf("\\")+1);
log.info("fileName: "+fileName);
}
}
}
//src/main/resources/application.properties
#File upload setting for Application
com.example.upload.path=c:\\upload
axios.post(url, formData, {
headers: {
"Content-Type" : "multipart/form-data",
"Access-Control-Allow-Origin" : "*",
"process-data" : false,
"content-type" : false,
}
})
.then(res => {console.log(res.data)})
.catch(err=> console.log("Error occourred: ",err))
upload 하면 axios가 서버에 데이터를 보내, 콘솔에서도 콘솔에서 확인 가능
fetch(url, {
method: 'POST',
body: formData,
dataType: 'json',
})
.then(res => res.json())
.then(json => {console.log(json);})
.catch(err => console.log("Error occourred: ",err))
파일을 업로드하고 저장하기 위해서 다음과 같이 코드 작성
@Value("${com.example.upload.path}")
private String uploadPath;
는 c:/upload 가 들어감
파일이 존재하진 않으면 폴더를 생성하고 있는경우 폴더를 생성하지 않게 만들어줌
private String makeFolder(){
String str = LocalDate.now().format(DateTimeFormatter.ofPattern("yyy/MM/dd"));
String folderPath = str.replace("/", File.separator);
File uploadPathFolder = new File(uploadPath, folderPath);
if(!uploadPathFolder.exists()){
uploadPathFolder.mkdirs(); //폴더 만듬
}
return folderPath;
}
@PostMapping("/uploadAjax")
public void upload(MultipartFile[] uploadFiles){
for(MultipartFile multipartFile: uploadFiles){
String originalName = multipartFile.getOriginalFilename();
String fileName = originalName.substring(originalName.lastIndexOf("\\")+1);
log.info("fileName: "+fileName);
//추가
String folderPath = makeFolder();
String uuid = UUID.randomUUID().toString();
String savaName = uploadPath+File.separator+folderPath+File.separator
+ uuid+"_"+fileName;
Path savePath = Paths.get(savaName);
try {
multipartFile.transferTo(savePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
이미지를 올려주고 그거에 대해서 db에 정보를 저장해주는 방법UploadResultDTO
생성
@Data
@AllArgsConstructor
public class UploadResultDTO implements Serializable {
private String fileName;
private String uuid;
private String folderPath;
public String getImageURL(){
try {
//파일명에 한글이 쓰이는경우 처리
return URLEncoder.encode(folderPath+"/"+uuid+"_"+fileName, "UTF-8");
} catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return "";
}
ResponseEntity
는 Spring Framework에서 제공하는 클래스로, HTTP 응답의 전체를 나타내는 데 사용됩니다. 이는 응답 상태 코드, 헤더, 바디 등을 모두 포함할 수 있는 컨테이너 역할을 합니다. Spring MVC에서 컨트롤러 메서드의 반환 타입으로 자주 사용되며, 클라이언트에게 더 많은 정보를 명확하고 유연하게 전달할 수 있게 해줍니다.
주요 기능과 특징
- 상태 코드(Status Code):
ResponseEntity
는 HTTP 상태 코드를 포함할 수 있습니다. 예를 들어, 성공적인 요청의 경우 200(OK), 리소스가 생성된 경우 201(Created), 클라이언트 오류인 경우 400(Bad Request) 등을 설정할 수 있습니다.
- 응답 헤더(Response Headers):
- 응답에 포함될 HTTP 헤더를 설정할 수 있습니다. 예를 들어,
Content-Type
,Cache-Control
,Set-Cookie
등의 헤더를 설정할 수 있습니다.
- 응답에 포함될 HTTP 헤더를 설정할 수 있습니다. 예를 들어,
- 응답 본문(Response Body):
- 응답의 본문 데이터를 포함할 수 있습니다. 이는 일반적으로 JSON 또는 XML 형태의 데이터로, 클라이언트가 필요로 하는 실제 정보를 담고 있습니다.
//src/main/java/com/example/ex6/controller/UploadController.java
@PostMapping("/uploadAjax")
public ResponseEntity<List<UploadResultDTO>> upload(MultipartFile[] uploadFiles){
List<UploadResultDTO> resultDTOList = new ArrayList<>();
....
String folderPath = makeFolder();
String uuid = UUID.randomUUID().toString();
String saveName = uploadPath + File.separator + folderPath + File.separator
+ uuid + "_" + fileName;
Path savePath = Paths.get(saveName);
try {
multipartFile.transferTo(savePath);
resultDTOList.add(new UploadResultDTO(fileName, uuid, folderPath));
} catch (IOException e) {
log.error(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(resultDTOList, HttpStatus.OK);
}
script th:inline="javascript">
window.onload = function(){
document.querySelector(".uploadBtn").onclick = function() {
var formData = new FormData();
const inputFiles = document.querySelector("input[type='file']");
var files = inputFiles.files;
for(let i=0;i<files.length;i++){
console.log(files[i])
formData.append("uploadFiles", files[i]);
}
const url = /*[[@{/uploadAjax}]]*/'url'
fetch(url, {
method: 'POST',
body: formData,
dataType: 'json',
})
.then(res => res.json())
.then(json => {console.log(json);showUploadedImages(json)})
.catch(err => console.log("Error occurred: ", err))
}
}
function showUploadedImages(arr) {
const uploadResultDiv = document.querySelector(".uploadResult")
let str = ""
for(let i=0;i<arr.length;i++){
str += `<img src="/display?fileName=${arr[i]}">`
}
uploadResultDiv.innerHTML = str;
}
</script>
function showUploadedImages(arr) {
const uploadResultDiv = document.querySelector(".uploadResult")
let str = ""
for(let i=0;i<arr.length;i++){
str += `<img src="/display?fileName=${arr[i]}">`
}
uploadResultDiv.innerHTML = str;
}
이미지가 바이너리 파일이라서 byte 타입으로 파일이름과 사이즈를 받아온다
@GetMapping("/display")
public ResponseEntity<byte[]> getImageFile(String fileName, String size) {
ResponseEntity<byte[]> result = null;
try {
String searchFilename = URLDecoder.decode(fileName, "UTF-8");
File file = new File(uploadPath + File.separator + searchFilename);
if (size != null && size.equals("1")) {
log.info(">>",file.getName());
file = new File(file.getParent(), file.getName().substring(2));
}
log.info("file: " + file);
HttpHeaders headers = new HttpHeaders();
// 파일의 확장자에 따라서 브라우저에 전송하는 MIME타입을 결정
headers.add("Content-Type", Files.probeContentType(file.toPath()));
result = new ResponseEntity<>(
FileCopyUtils.copyToByteArray(file), headers, HttpStatus.OK);
} catch (Exception e) {
log.error(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return result;
}
@PostMapping("/uploadAjax")
public ResponseEntity<List<UploadResultDTO>> upload(MultipartFile[] uploadFiles) {
List<UploadResultDTO> resultDTOList = new ArrayList<>();
for (MultipartFile multipartFile : uploadFiles) {
String originalName = multipartFile.getOriginalFilename();
String fileName = originalName.substring(originalName.lastIndexOf("\\") + 1);
log.info("fileName: " + fileName);
String folderPath = makeFolder();
String uuid = UUID.randomUUID().toString();
String saveName = uploadPath + File.separator + folderPath + File.separator
+ uuid + "_" + fileName;
Path savePath = Paths.get(saveName);
try {
multipartFile.transferTo(savePath); //원본파일 저장
//추가
String thumbnailSaveName = uploadPath+File.separator+folderPath+File.separator
+ "s_"+uuid+"_"+fileName;
File thumbnailFile = new File(thumbnailSaveName);
Thumbnailator.createThumbnail(savePath.toFile(), thumbnailFile,100,100);
resultDTOList.add(new UploadResultDTO(fileName, uuid, folderPath));
} catch (IOException e) {
log.error(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
return new ResponseEntity<>(resultDTOList, HttpStatus.OK);
}
삭제하기
@PostMapping("/removeFile")
public ResponseEntity<Boolean> removeFile(String fileName) {
String searchFilename = null;
try {
searchFilename = URLDecoder.decode(fileName, "UTF-8");
File file = new File(uploadPath + File.separator + searchFilename);
boolean result1 = file.delete();
File thumbnail =
new File(file.getPath() + File.separator + "s_" + searchFilename);
boolean result2 = thumbnail.delete();
return new ResponseEntity<>(
(result1 && result2) ? HttpStatus.OK : HttpStatus.INTERNAL_SERVER_ERROR
);
} catch (Exception e) {
log.error(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
영화에 대한 영화번호, 영화제목에 대한 정보를 전달 해주기 위해서 movieDTO 를 생성
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MovieDTO {
private Long mno;
private String title;
@Builder.Default //속성의 기본값으로 초기화
private List<MovieImageDTO> imageDTOList = new ArrayList<>();
private double avg;
private int reviewCnt;
private LocalDateTime regDate;
private LocalDateTime modDate;
}
@Builder.Default
생성시 속성의 기본값 초기화 ,@AllArgsConstructor
가 없으면 에러 발생
영화 이미지에 대한 정보를 전달하기 위한 MovieImageDTO 를 생성
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MovieImageDTO {
private String uuid;
private String imgName;
private String path;
public String getImageURL() {
try {
return URLEncoder.encode(path + "/" + uuid + "_" + imgName,
"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
public String getThumbnailURL() {
try {
return URLEncoder.encode(path + "/s_" + uuid + "_" + imgName, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
}
MovieService 구현하기
dto → entity / entity →dto 로 바꿔주는 함수 구현해줌
public interface MovieService {
Long register(MovieDTO movieDTO);
//
default Map<String, Object> dtoToEntity(MovieDTO movieDTO){
Map<String, Object> entityMap = new HashMap<>();
Movie movie = Movie.builder().mno(movieDTO.getMno())
.title(movieDTO.getTitle()).build();
entityMap.put("movie", movie);
List<MovieImageDTO> imageDTOList = movieDTO.getImageDTOList();
if (imageDTOList != null && imageDTOList.size() > 0) {
List<MovieImage> movieImageList = imageDTOList.stream().map(
new Function<MovieImageDTO, MovieImage>() {
@Override
public MovieImage apply(MovieImageDTO movieImageDTO) {
MovieImage movieImage = MovieImage.builder()
.path(movieImageDTO.getPath())
.imgName(movieImageDTO.getImgName())
.uuid(movieImageDTO.getUuid())
.movie(movie)
.build();
return movieImage;
}
}
).collect(Collectors.toList());
entityMap.put("movieImageList",movieImageList);
}
return entityMap;
}
default MovieDTO entityToDto(Movie movie, List<MovieImage> movieImageList,
Double avg , int reviewCnt){
MovieDTO movieDTO = MovieDTO.builder()
.mno(movie.getMno())
.title(movie.getTitle())
.regDate(movie.getRegDate())
.modDate(movie.getModDate())
.build();
List<MovieImageDTO> movieImageDTOList = movieImageList.stream().map(
new Function<MovieImage, MovieImageDTO>() {
@Override
public MovieImageDTO apply(MovieImage movieImage) {
MovieImageDTO movieImageDTO = MovieImageDTO.builder()
.imgName(movieImage.getImgName())
.path(movieImage.getPath())
.uuid(movieImage.getUuid())
.build();
return movieImageDTO;
}
}
).collect(Collectors.toList());
movieDTO.setAvg(avg);
movieDTO.setReviewCnt(reviewCnt);
return movieDTO;
};
}
파일사이즈 예외처리하기
<script th:inline="javascript">
function checkExtension(fileName, fileSize){
maxSize = 1024*1024*10
if(fileSize >= maxSize)
{
alert("파일 사이즈가 초과");
return false;
}
// https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_expressions
//const regex = new RegExp("(.*?)\.(exe|sh|zip|alz|tiff)$");
const regex = new RegExp("(.*?)\.(jpg|jpeg|png|gif|bmp|pdf)$",'i');
}
</script>
'PROGRAMING📚 > Spring📑' 카테고리의 다른 글
Spring / Spring boot (1) | 2024.09.26 |
---|---|
Spring[EX06.코드] - 데이터 , 이미지 등록(통합구현) (0) | 2024.09.25 |
CSRF - API 서버 (0) | 2024.09.25 |
JSP(JavaServer Pages) (1) | 2024.09.25 |
Spring : 구글 로그인 세션 만들기 (1) | 2024.09.25 |
댓글