diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 87e64e7..02d9e60 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -72,7 +72,7 @@ jobs: # run: docker push haechansomg/simple-board2:was02 - name: Deploy to server - uses: appleboy/ssh-action@v1.0.0 + uses: appleboy/ssh-action@v1.0.3 id: deploy env: COMPOSE: "/home/ubuntu/compose/docker-compose.yml" @@ -85,5 +85,4 @@ jobs: script: | sudo docker-compose -f $COMPOSE down --rmi all sudo docker pull haechansomg/simple-board:was01 - sudo docker-compose -f $COMPOSE up -d - sudo docker image prune -f \ No newline at end of file + sudo docker-compose -f $COMPOSE up -d \ No newline at end of file diff --git a/build.gradle b/build.gradle index cd7673e..7cc1544 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-quartz:2.7.5' implementation 'org.apache.commons:commons-collections4:4.0' implementation group: 'com.github.javafaker', name: 'javafaker', version: '1.0.2' + implementation 'org.springframework.boot:spring-boot-starter-mail' testImplementation 'org.assertj:assertj-core:3.24.2' testImplementation("org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.2") } diff --git a/docker-compose.yml b/docker-compose.yml index 2e8b663..c2e84d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,6 +32,6 @@ services: # - 9121:9121 # environment: # REDIS_ADDR: "redis:6379" - links: - - redis +# links: +# - redis # - prometheus \ No newline at end of file diff --git a/src/main/java/squad/board/BoardApplication.java b/src/main/java/squad/board/BoardApplication.java index 77e9d96..217b6bc 100644 --- a/src/main/java/squad/board/BoardApplication.java +++ b/src/main/java/squad/board/BoardApplication.java @@ -1,6 +1,5 @@ package squad.board; -import org.quartz.*; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; diff --git a/src/main/java/squad/board/apicontroller/BoardApiController.java b/src/main/java/squad/board/apicontroller/BoardApiController.java index 0086701..9a54324 100644 --- a/src/main/java/squad/board/apicontroller/BoardApiController.java +++ b/src/main/java/squad/board/apicontroller/BoardApiController.java @@ -1,7 +1,9 @@ package squad.board.apicontroller; +import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import squad.board.aop.BoardWriterAuth; @@ -22,10 +24,19 @@ public class BoardApiController { @PostMapping(value = "/boards") public CommonIdResponse saveBoard( @SessionAttribute Long memberId, - @Valid @RequestBody CreateBoardRequest createBoard) { + @Valid @RequestBody CreateBoardRequest createBoard) throws JsonProcessingException { return boardService.createBoard(memberId, createBoard); } +// @PostMapping(value = "/boards", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, +// MediaType.APPLICATION_JSON_VALUE}) +// public CommonIdResponse saveBoard( +// @SessionAttribute Long memberId, +// @RequestPart CreateBoardRequest createBoard, +// @RequestPart("images") MultipartFile[] images) { +// return boardService.createBoard(memberId, createBoard, images); +// } + // 이미지 S3 전송 @PostMapping(value = "/boards/img") public ImageInfoResponse saveImg( diff --git a/src/main/java/squad/board/config/AsyncThreadPoolConfig.java b/src/main/java/squad/board/config/AsyncThreadPoolConfig.java new file mode 100644 index 0000000..8cb99bb --- /dev/null +++ b/src/main/java/squad/board/config/AsyncThreadPoolConfig.java @@ -0,0 +1,20 @@ +package squad.board.config; + +import java.util.concurrent.Executor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +@EnableAsync +public class AsyncThreadPoolConfig { + + @Bean + public Executor asyncThreadTaskExecutor() { + ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); + threadPoolTaskExecutor.setCorePoolSize(1); + threadPoolTaskExecutor.setMaxPoolSize(1); + return threadPoolTaskExecutor; + } +} diff --git a/src/main/java/squad/board/config/MailConfig.java b/src/main/java/squad/board/config/MailConfig.java new file mode 100644 index 0000000..8b9e288 --- /dev/null +++ b/src/main/java/squad/board/config/MailConfig.java @@ -0,0 +1,15 @@ +package squad.board.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +@Configuration +public class MailConfig { + + @Bean + public JavaMailSender javaMailSender() { + return new JavaMailSenderImpl(); + } +} diff --git a/src/main/java/squad/board/config/RedisConfig.java b/src/main/java/squad/board/config/RedisConfig.java index a1d9791..6fba2a4 100644 --- a/src/main/java/squad/board/config/RedisConfig.java +++ b/src/main/java/squad/board/config/RedisConfig.java @@ -27,6 +27,17 @@ public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(redisConfiguration); } + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + + return redisTemplate; + } + @Bean public RedisConnectionFactory redisConnectionFactoryToken() { RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(); @@ -36,14 +47,6 @@ public RedisConnectionFactory redisConnectionFactoryToken() { return new LettuceConnectionFactory(redisConfiguration); } - @Bean(name = "redisTemplate") - public RedisTemplate redisTemplate() { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(redisConnectionFactory()); - redisTemplate.setDefaultSerializer(new StringRedisSerializer()); - return redisTemplate; - } - @Bean(name = "redisTemplateToken") public RedisTemplate redisTemplateToken() { RedisTemplate redisTemplate = new RedisTemplate<>(); diff --git a/src/main/java/squad/board/config/TaskSchedulerConfig.java b/src/main/java/squad/board/config/TaskSchedulerConfig.java new file mode 100644 index 0000000..814b241 --- /dev/null +++ b/src/main/java/squad/board/config/TaskSchedulerConfig.java @@ -0,0 +1,22 @@ +package squad.board.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +@Configuration +public class TaskSchedulerConfig implements SchedulingConfigurer { + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskScheduler()); + } + + @Bean + public Executor taskScheduler() { + return Executors.newScheduledThreadPool(2); + } +} \ No newline at end of file diff --git a/src/main/java/squad/board/exception/ObjectMapperException.java b/src/main/java/squad/board/exception/ObjectMapperException.java new file mode 100644 index 0000000..464a0ba --- /dev/null +++ b/src/main/java/squad/board/exception/ObjectMapperException.java @@ -0,0 +1,4 @@ +package squad.board.exception; + +public class ObjectMapperException extends IllegalStateException { +} diff --git a/src/main/java/squad/board/service/BoardService.java b/src/main/java/squad/board/service/BoardService.java index 19147f4..d6bd904 100644 --- a/src/main/java/squad/board/service/BoardService.java +++ b/src/main/java/squad/board/service/BoardService.java @@ -1,5 +1,6 @@ package squad.board.service; +import com.fasterxml.jackson.core.JsonProcessingException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; @@ -21,6 +22,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -28,35 +30,31 @@ @Slf4j public class BoardService { + private static final long MAX_IMAGE_SIZE = 5000000L; + private static final int MAX_IMAGE_NAME_SIZE = 100; + private static final String IMAGE_EXTENSION_EXTRACT_REGEX = "(.png|.jpg|.jpeg)$"; + private static final String TEMP_FOLDER_NAME = "tmp"; private final BoardMapper boardMapper; private final CommentMapper commentMapper; private final ImageMapper imageMapper; private final S3Service s3Service; - private static final long MAX_IMAGE_SIZE = 5000000L; - private static final int MAX_IMAGE_NAME_SIZE = 100; - private static final String TEMP_FOLDER_NAME = "tmp"; - private static final String ORIGINAL_FOLDER_NAME = "original"; - private static final String IMAGE_EXTENSION_EXTRACT_REGEX = "(.png|.jpg|.jpeg)$"; - - public CommonIdResponse createBoard(Long memberId, CreateBoardRequest createBoard) { + private final S3MessageQueue messageQueue; + private final S3DeadLetterQueue deadLetterQueue; + + public CommonIdResponse createBoard(Long memberId, CreateBoardRequest createBoard) throws JsonProcessingException { // 게시글 저장 Board board = createBoard.toEntity(memberId); boardMapper.save(board); // 이미지 정보 저장 if (createBoard.isImageExist()) { - saveImageInfo(board.getBoardId(), createBoard.getImageInfo()); + imageMapper.save(createBoard.getImageInfo(), board.getBoardId()); + deadLetterQueue.pushAll(createBoard.getImageInfo().stream() + .map(ImageInfoRequest::getImageUUID) + .toList()); } return new CommonIdResponse(board.getBoardId()); } - private void saveImageInfo(Long boardId, List imageInfoRequests) { - imageMapper.save(imageInfoRequests, boardId); - for (ImageInfoRequest request : imageInfoRequests) { - // tmp 폴더의 이미지를 original 폴더로 이동 - s3Service.moveImageToOriginal(request.getImageUUID(), TEMP_FOLDER_NAME, ORIGINAL_FOLDER_NAME); - } - } - public ImageInfoResponse saveImageToS3(MultipartFile image) { imageValidation(image); String uuid = s3Service.saveFile(image, TEMP_FOLDER_NAME); @@ -66,7 +64,8 @@ public ImageInfoResponse saveImageToS3(MultipartFile image) { private void imageValidation(MultipartFile image) { String imageName = image.getOriginalFilename(); - if (!imageName.substring(imageName.lastIndexOf(".")).matches(IMAGE_EXTENSION_EXTRACT_REGEX)) { + if (!imageName.substring(imageName.lastIndexOf(".")) + .matches(IMAGE_EXTENSION_EXTRACT_REGEX)) { throw new ImageException(ImageStatus.INVALID_IMAGE_EXTENSION); } // 이미지 파일명 길이 제한 @@ -81,20 +80,28 @@ private void imageValidation(MultipartFile image) { } @Transactional(readOnly = true) - public ContentListResponse findBoards(Long size, Long requestPage, Long memberId) { - if (memberId == null) { + public ContentListResponse findBoards(Long size, Long requestPage, + Long memberId) { + if (memberId==null) { throw new BoardException(BoardStatus.INVALID_MEMBER_ID); } Long offset = calcOffset(requestPage, size); - Pagination boardPaging = new Pagination(requestPage, boardMapper.countBoards(memberId), size); - return new ContentListResponse<>(boardMapper.findAllWithNickName(size, offset, memberId), boardPaging); + Pagination boardPaging = new Pagination(requestPage, boardMapper.countBoards(memberId), + size); + return new ContentListResponse<>(boardMapper.findAllWithNickName(size, offset, memberId), + boardPaging); + } + + private Long calcOffset(Long page, Long size) { + return (page - 1) * size; } @Transactional(readOnly = true) public ContentListResponse findBoards(Long size, Long requestPage) { Long offset = calcOffset(requestPage, size); Pagination boardPaging = new Pagination(requestPage, boardMapper.countBoards(null), size); - return new ContentListResponse<>(boardMapper.findAllWithNickName(size, offset, null), boardPaging); + return new ContentListResponse<>(boardMapper.findAllWithNickName(size, offset, null), + boardPaging); } @Transactional(readOnly = true) @@ -118,7 +125,7 @@ public CommonIdResponse updateBoard(Long boardId, BoardUpdateRequest updateBoard List imageInfoRequests = updateBoard.getImageInfoList(); // DB에 이미지 정보 저장 if (CollectionUtils.isNotEmpty(imageInfoRequests)) { - saveImageInfo(boardId, imageInfoRequests); + imageMapper.save(imageInfoRequests, boardId); } // 기존 이미지 정보 List savedImageUuid = imageMapper.findImageUuid(boardId); @@ -139,14 +146,14 @@ private void deleteImageInfo(List deleteRequestImageUuid) { } @Transactional(readOnly = true) - public ContentListResponse searchBoard(String keyWord, Long size, Long requestPage, String searchType) { + public ContentListResponse searchBoard(String keyWord, Long size, + Long requestPage, String searchType) { Long offset = calcOffset(requestPage, size); - Pagination boardPaging = new Pagination(requestPage, boardMapper.countByKeyWord(keyWord, searchType), size); - List byKeyWord = boardMapper.findByKeyWord(keyWord, size, offset, searchType); - return new ContentListResponse<>(boardMapper.findByKeyWord(keyWord, size, offset, searchType), boardPaging); - } - - private Long calcOffset(Long page, Long size) { - return (page - 1) * size; + Pagination boardPaging = new Pagination(requestPage, + boardMapper.countByKeyWord(keyWord, searchType), size); + List byKeyWord = boardMapper.findByKeyWord(keyWord, size, offset, + searchType); + return new ContentListResponse<>( + boardMapper.findByKeyWord(keyWord, size, offset, searchType), boardPaging); } } diff --git a/src/main/java/squad/board/service/S3Consumer.java b/src/main/java/squad/board/service/S3Consumer.java new file mode 100644 index 0000000..4c0c624 --- /dev/null +++ b/src/main/java/squad/board/service/S3Consumer.java @@ -0,0 +1,46 @@ +package squad.board.service; + +import com.amazonaws.AmazonServiceException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +@RequiredArgsConstructor +@Slf4j +public class S3Consumer { + + private final S3MessageQueue messageQueue; + private final S3DeadLetterQueue deadLetterQueue; + private final S3Service s3Service; + private final S3MailSender s3MailSender; + private final String MAIL_CONTENT = "The dead letter queue has exceeded its maximum allowable size."; + +// @Scheduled(fixedDelay = 1000) +// public void normalConsume() { +// while (!messageQueue.isEmpty()) { +// String uuid = messageQueue.pop(); +// try { +// s3Service.moveImageToOriginal(uuid); +// } catch (AmazonServiceException e) { +// deadLetterQueue.push(uuid); +// } +// } +// } + + @Scheduled(fixedDelay = 2000) + public void deadLetterConsume() { + if (deadLetterQueue.isRateLimit()) { + s3MailSender.send(MAIL_CONTENT); + } + List uuids = new ArrayList<>(); + while (!deadLetterQueue.isEmpty()) { + uuids.add(deadLetterQueue.pop()); + } + messageQueue.pushAll(uuids); + } +} diff --git a/src/main/java/squad/board/service/S3DeadLetterQueue.java b/src/main/java/squad/board/service/S3DeadLetterQueue.java new file mode 100644 index 0000000..b9155f3 --- /dev/null +++ b/src/main/java/squad/board/service/S3DeadLetterQueue.java @@ -0,0 +1,34 @@ +package squad.board.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class S3DeadLetterQueue { + + private static final String MESSAGE_QUEUE = "s3_dead_letter"; + private static final int EMPTY = 0; + private static final int LIMIT = 3; + private final RedisTemplate redisTemplate; + + public void pushAll(List imageUUID) { + for (String uuid : imageUUID) + redisTemplate.opsForList().leftPushAll(MESSAGE_QUEUE, uuid); + } + + public String pop() { + return (String) redisTemplate.opsForList().rightPop(MESSAGE_QUEUE); + } + + public boolean isRateLimit() { + return redisTemplate.opsForList().size(MESSAGE_QUEUE) > LIMIT; + } + + public boolean isEmpty() { + return redisTemplate.opsForList().size(MESSAGE_QUEUE)==EMPTY; + } +} diff --git a/src/main/java/squad/board/service/S3MailSender.java b/src/main/java/squad/board/service/S3MailSender.java new file mode 100644 index 0000000..5b4d6a7 --- /dev/null +++ b/src/main/java/squad/board/service/S3MailSender.java @@ -0,0 +1,22 @@ +package squad.board.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class S3MailSender { + private final JavaMailSender javaMailSender; + private static final String ADMIN_EMAIL = "bukak2019@naver.com"; + private static final String TITLE = "S3 Move Action Failed"; + + public void send(final String content) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo(ADMIN_EMAIL); + message.setSubject(TITLE); + message.setText(content); + javaMailSender.send(message); + } +} diff --git a/src/main/java/squad/board/service/S3MessageQueue.java b/src/main/java/squad/board/service/S3MessageQueue.java new file mode 100644 index 0000000..7c3ae6e --- /dev/null +++ b/src/main/java/squad/board/service/S3MessageQueue.java @@ -0,0 +1,36 @@ +package squad.board.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class S3MessageQueue { + + private static final String MESSAGE_QUEUE = "s3queue"; + private final RedisTemplate redisTemplate; + private final S3MailSender s3FailMailSender; + + + public void pushAll(List uuids) { + for (String uuid : uuids) { + redisTemplate.opsForList().leftPush(MESSAGE_QUEUE, uuid); + } + } + + public void push(String imageUUID) { + redisTemplate.opsForList().leftPush(MESSAGE_QUEUE, imageUUID); + } + + public String pop() { + return (String) redisTemplate.opsForList().rightPop(MESSAGE_QUEUE); + } + + public boolean isEmpty() { + return redisTemplate.opsForList().size(MESSAGE_QUEUE)==0; + } + +} diff --git a/src/main/java/squad/board/service/S3Service.java b/src/main/java/squad/board/service/S3Service.java index cad257a..614cfef 100644 --- a/src/main/java/squad/board/service/S3Service.java +++ b/src/main/java/squad/board/service/S3Service.java @@ -1,27 +1,26 @@ package squad.board.service; import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.*; +import com.amazonaws.services.s3.model.ObjectMetadata; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; -import software.amazon.awssdk.services.s3.model.Delete; -import software.amazon.awssdk.services.s3.model.ObjectIdentifier; import java.io.IOException; -import java.util.List; -import java.util.ListIterator; import java.util.UUID; @RequiredArgsConstructor @Component @Slf4j public class S3Service { - private final AmazonS3 s3Client; + private static final String TEMP_FOLDER_NAME = "tmp"; + private static final String ORIGINAL_FOLDER_NAME = "original"; + private final AmazonS3 s3Client; + private final S3MessageQueue messageQueue; @Value("${cloud.aws.s3.bucket}") private String bucket; @@ -33,16 +32,18 @@ public String saveFile(MultipartFile multipartFile, String folderName) { metadata.setContentLength(multipartFile.getSize()); metadata.setContentType(multipartFile.getContentType()); try { - s3Client.putObject(bucket, folderName + "/" + uuid, multipartFile.getInputStream(), metadata); + s3Client.putObject(bucket, folderName + "/" + uuid, multipartFile.getInputStream(), + metadata); } catch (IOException e) { log.error("Image Upload Failed"); } return uuid; } - public void moveImageToOriginal(String imageUUID, String from, String to) { - s3Client.copyObject(bucket, from + "/" + imageUUID, bucket, to + "/" + imageUUID); - s3Client.deleteObject(bucket, from + "/" + imageUUID); + @Async + public void moveImageToOriginal(String uuid) { + s3Client.copyObject(bucket, TEMP_FOLDER_NAME + "/" + uuid, bucket, ORIGINAL_FOLDER_NAME + "/" + uuid); + s3Client.deleteObject(bucket, TEMP_FOLDER_NAME + "/" + uuid); } // 이미지 소스 반환 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9f96606..3d7808a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,3 @@ spring: profiles: - active: prod + active: dev diff --git a/src/main/resources/static/js/board/createBoard.js b/src/main/resources/static/js/board/createBoard.js index 301a2ae..2372aba 100644 --- a/src/main/resources/static/js/board/createBoard.js +++ b/src/main/resources/static/js/board/createBoard.js @@ -4,65 +4,119 @@ const contentInput = document.getElementById("editor"); const writeBtn = document.getElementById("write-btn"); const imageSelector = document.getElementById('img-selector'); let imageInfoList = []; +// const formData = new FormData(); + +// writeBtn.addEventListener("click", () => { +// +// while (contentInput.firstChild) { +// contentInput.removeChild(contentInput.firstChild); +// } +// +// const title = titleInput.value; +// const content = contentInput.innerHTML; +// const data = { +// "title": title, +// "content": content, +// "imageInfo": imageInfoList +// } +// formData.append('createBoard', +// new Blob([JSON.stringify(data)], {type: 'application/json'})); +// +// axios.post(`${homeUrl}/api/boards`, formData, { +// headers: { +// 'Content-Type': 'multipart/form-data' +// } +// }).then(response => { +// const statusCode = response.status; +// const data = response.data; +// // 게시글 작성 성공 +// if (statusCode === 200) { +// window.location.href = `/boards/${data.id}`; +// } +// }).catch(error => { +// const data = error.response.data; +// // 필드 에러 +// if (data.code === 500) { +// alert(data.fieldErrorMessage) +// } +// }) +// }) writeBtn.addEventListener("click", () => { - const title = titleInput.value; - const content = contentInput.innerHTML; - const data = { - "title": title, - "content": content, - "imageInfo": imageInfoList + const title = titleInput.value; + const content = contentInput.innerHTML; + const data = { + "title": title, + "content": content, + "imageInfo": imageInfoList + } + axios.post(`${homeUrl}/api/boards`, data + ).then(response => { + const statusCode = response.status; + const data = response.data; + // 게시글 작성 성공 + if (statusCode === 200) { + window.location.href = `/boards/${data.id}`; } - axios.post(`${homeUrl}/api/boards`, data - ).then(response => { - const statusCode = response.status; - const data = response.data; - // 게시글 작성 성공 - if (statusCode === 200) { - window.location.href = `/boards/${data.id}`; - } - }).catch(error => { - const data = error.response.data; - // 필드 에러 - if (data.code === 500) { - alert(data.fieldErrorMessage) - } - }) + }).catch(error => { + const data = error.response.data; + // 필드 에러 + if (data.code === 500) { + alert(data.fieldErrorMessage) + } + }) }) // 이미지 첨부기능 const btnImage = document.getElementById('btn-image'); btnImage.addEventListener('click', function () { - imageSelector.click(); + imageSelector.click(); }); +// imageSelector.addEventListener('change', function (e) { +// const files = e.target.files; +// formData.append("images", files[0]); +// const reader = new FileReader(); +// reader.onload = function (event) { +// // 이미지 태그를 생성하고 src 속성을 설정 +// const img = document.createElement('img'); +// img.src = event.target.result; +// img.style.maxWidth = '200px'; // 이미지 크기를 조절할 수 있습니다 +// img.style.maxHeight = '200px'; +// +// // contentInput 요소에 이미지 태그를 추가 +// contentInput.appendChild(img); +// } +// reader.readAsDataURL(files[0]); +// }); + imageSelector.addEventListener('change', function (e) { - const files = e.target.files; - const formData = new FormData(); - let imgSrc; - formData.append("image", files[0]); - axios.post(`${homeUrl}/api/boards/img`, - formData - , { - headers: { - "Content-Type": "multipart/form-data" - } + const files = e.target.files; + const formData = new FormData(); + let imgSrc; + formData.append("image", files[0]); + axios.post(`${homeUrl}/api/boards/img`, + formData + , { + headers: { + "Content-Type": "multipart/form-data" } - ).then(response => { - imgSrc = response.data.imageSrc; - imageInfoList.push( - { - imageUUID: response.data.imageUUID, - imageSize: response.data.imageSize, - imageOriginalName: response.data.imageOriginalName - }); - let img = document.createElement("img"); - img.src = imgSrc; - img.style.width = '600px'; - contentInput.appendChild(img); - }).catch(error => { - const data = error.response.data; - alert(data.message); - }) + } + ).then(response => { + imgSrc = response.data.imageSrc; + imageInfoList.push( + { + imageUUID: response.data.imageUUID, + imageSize: response.data.imageSize, + imageOriginalName: response.data.imageOriginalName + }); + let img = document.createElement("img"); + img.src = imgSrc; + img.style.width = '200px'; + contentInput.appendChild(img); + }).catch(error => { + const data = error.response.data; + alert(data.message); + }) }); diff --git a/src/main/resources/static/js/board/url.js b/src/main/resources/static/js/board/url.js index 833ca6a..a15e4eb 100644 --- a/src/main/resources/static/js/board/url.js +++ b/src/main/resources/static/js/board/url.js @@ -1,2 +1,2 @@ -const homeUrl = 'http://13.125.17.239:80' -// const homeUrl = 'http://localhost:8080' \ No newline at end of file +// const homeUrl = 'http://13.125.17.239:80' +const homeUrl = 'http://localhost:8080' \ No newline at end of file diff --git a/src/main/resources/templates/board/createBoard.html b/src/main/resources/templates/board/createBoard.html index aad7ea7..8a91d12 100644 --- a/src/main/resources/templates/board/createBoard.html +++ b/src/main/resources/templates/board/createBoard.html @@ -1,79 +1,79 @@ - + - - - Simple Board - - + + + Simple Board + +
- +
-
+
- -
- -
- - - - - - - -
-
-
- + +
+ +
+ + + + + + + +
+
+
+ -
- -
+
+
+
- +