이 시스템은 Spring Boot를 기반으로 구축된 여행 및 일상 정보 공유 웹 애플리케이션입니다. 사용자는 게시글, 사진첩, 댓글 등을 작성하고 관리할 수 있으며, 관리자 기능을 통해 콘텐츠를 효율적으로 운영할 수 있습니다.
기술 스택
- 백엔드: Java 8+, Spring Boot, Spring MVC, Spring Data JPA
- 프론트엔드: HTML5, Thymeleaf, JavaScript, jQuery, AJAX
- 데이터베이스: MySQL 5.7 이상
- 빌드 도구: Apache Maven
- IDE: IntelliJ IDEA, Eclipse, STS 등 지원
핵심 기능
- 사용자 인증 및 세션 기반 로그인
- 여행 게시글 작성/수정/삭제 (썸네일 이미지 자동 생성)
- 카테고리(타입)별 게시글 분류 및 조회
- 사진첩(Atlas) 및 개별 사진(Picture) 관리
- 댓글 및 대댓글(Reply) 기능
- 검색 기능 (키워드 + 카테고리 조합)
- 조회수 기반 인기 게시글 목록
- 게시글 및 사진첩 페이징 처리
- 관리자 전용 페이지 (블로그, 카테고리, 사진첩 관리)
주요 컨트롤러 예시
블로그 관리 컨트롤러
@RestController
public class BlogController {
@Autowired
private BlogService blogService;
@Autowired
private TypeService typeService;
@PostMapping("/blogs")
public ResponseEntity<String> createBlog(
@RequestParam("tid") Integer typeId,
@RequestParam("title") String title,
@RequestParam("content") String content,
@RequestParam("flag") String flag,
@RequestParam(value = "image", required = false) MultipartFile image,
HttpServletRequest request) throws IOException {
User currentUser = (User) request.getSession().getAttribute("user");
if (currentUser == null) {
return ResponseEntity.status(401).body("Unauthorized");
}
Blog blog = new Blog();
blog.setType(typeService.findById(typeId));
blog.setTitle(title);
blog.setContent(content);
blog.setFlag(flag);
blog.setPublish("published".equals(flag));
blog.setUser(currentUser);
blog.setCreateDate(new Date());
blog.setUpdateDate(new Date());
blog.setViews(0);
blog.setNums(content.length());
Blog savedBlog = blogService.save(blog);
if (image != null && !image.isEmpty()) {
String imagePath = saveBlogImage(savedBlog.getId(), image, request);
blogService.updateFirstPicture(savedBlog.getId(), imagePath);
}
return ResponseEntity.ok("success");
}
private String saveBlogImage(Long blogId, MultipartFile image, HttpServletRequest req) throws IOException {
String uploadDir = req.getServletContext().getRealPath("/img/picture");
Path dirPath = Paths.get(uploadDir);
if (!Files.exists(dirPath)) {
Files.createDirectories(dirPath);
}
Path filePath = dirPath.resolve(blogId + ".jpg");
image.transferTo(filePath.toFile());
// JPG 변환 및 저장
BufferedImage originalImg = ImageIO.read(filePath.toFile());
BufferedImage jpgImg = new BufferedImage(originalImg.getWidth(), originalImg.getHeight(), BufferedImage.TYPE_INT_RGB);
jpgImg.createGraphics().drawImage(originalImg, 0, 0, null);
ImageIO.write(jpgImg, "jpg", filePath.toFile());
return filePath.toString();
}
}
댓글 및 대댓글 처리
@RestController
public class CommentController {
@Autowired
private CommentService commentService;
@Autowired
private ReplyService replyService;
@Autowired
private BlogService blogService;
@PostMapping("/comments/{blogId}")
public void addComment(@PathVariable Long blogId, HttpServletRequest request) {
Blog blog = blogService.findById(blogId);
Comment comment = new Comment();
comment.setBlog(blog);
comment.setContent(request.getParameter("content"));
comment.setNickname(request.getParameter("nickname"));
comment.setEmail(request.getParameter("email"));
comment.setCreateTime(new Date());
comment.setFlag(0);
commentService.save(comment);
}
@GetMapping("/comments/{blogId}")
public List<Comment> getCommentsWithReplies(@PathVariable Long blogId) {
List<Comment> comments = commentService.findByBlogId(blogId);
for (Comment c : comments) {
List<Reply> replies = replyService.findByBlogAndComment(blogId, c.getId());
c.setReplies(replies);
}
return comments;
}
}
사진 관리 컨트롤러
@RestController
public class PictureController {
@Autowired
private PictureService pictureService;
@Autowired
private AtlasService atlasService;
@PostMapping("/pictures")
public ResponseEntity<String> uploadPicture(
@RequestParam("aid") Long atlasId,
@RequestParam("title") String title,
@RequestParam("image") MultipartFile image,
HttpSession session,
HttpServletRequest request) {
User user = (User) session.getAttribute("user");
Atlas atlas = atlasService.findById(atlasId);
Picture picture = new Picture();
picture.setAtlas(atlas);
picture.setUser(user);
picture.setTitle(title);
picture.setCreateDate(new Date());
Picture savedPic = pictureService.save(picture);
try {
// 상세 이미지 저장
String detailPath = request.getServletContext().getRealPath("/img/pictureDetail");
saveImage(image, detailPath, savedPic.getId() + ".jpg");
// 썸네일 생성 및 저장
String thumbPath = request.getServletContext().getRealPath("/img/pictureThumbnail");
generateThumbnail(detailPath, thumbPath, savedPic.getId() + ".jpg");
} catch (IOException e) {
return ResponseEntity.status(500).body("Image upload failed");
}
return ResponseEntity.ok("success");
}
private void saveImage(MultipartFile file, String dir, String filename) throws IOException {
Path path = Paths.get(dir, filename);
if (!Files.exists(path.getParent())) {
Files.createDirectories(path.getParent());
}
file.transferTo(path.toFile());
}
private void generateThumbnail(String srcDir, String destDir, String filename) throws IOException {
// 간단한 썸네일 생성 로직 (실제 구현은 ImageUtil 클래스 활용)
BufferedImage original = ImageIO.read(Paths.get(srcDir, filename).toFile());
int targetWidth = 200;
int targetHeight = (int) ((double) original.getHeight() * targetWidth / original.getWidth());
BufferedImage thumb = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = thumb.createGraphics();
g.drawImage(original.getScaledInstance(targetWidth, targetHeight, Image.SCALE_SMOOTH), 0, 0, null);
g.dispose();
Path destPath = Paths.get(destDir, filename);
if (!Files.exists(destPath.getParent())) {
Files.createDirectories(destPath.getParent());
}
ImageIO.write(thumb, "jpg", destPath.toFile());
}
}
프론트엔드 통합
Thymeleaf 템플릿 엔진을 사용하여 서버 사이드 렌더링을 수행하며, AJAX를 통해 비동기 요청을 처리합니다. 예를 들어, 댓글 작성 시 폼 데이터를 POST로 전송하고, 성공 시 화면을 새로 고치지 않고 DOM을 동적으로 갱신합니다.
보안 및 세션 관리
간단한 세션 기반 인증을 사용합니다. 로그인 시 사용자 객체를 HttpSession에 저장하고, 민감한 API에서는 세션 존재 여부를 확인합니다.
@GetMapping("/check-login")
public ResponseEntity<Map<String, Object>> checkLogin(HttpSession session) {
Map<String, Object> response = new HashMap<>();
if (session.getAttribute("user") != null) {
response.put("status", "success");
} else {
response.put("status", "fail");
response.put("message", "Not logged in");
}
return ResponseEntity.ok(response);
}