Skip to content

Commit

Permalink
[BE] dev 서버에 구현 사항을 배포한다. (#1082)
Browse files Browse the repository at this point in the history
  • Loading branch information
shin-jisong authored Jan 12, 2025
2 parents 0231b99 + 9bd267a commit 8205eca
Show file tree
Hide file tree
Showing 38 changed files with 1,986 additions and 361 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableJpaAuditing
@EnableAspectJAutoProxy
@EnableScheduling
@SpringBootApplication
public class Application {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,58 @@
package com.bang_ggood.article.controller;

import com.bang_ggood.article.dto.request.ArticleCreateRequest;
import com.bang_ggood.article.dto.request.ArticleUpdateRequest;
import com.bang_ggood.article.dto.response.ArticleResponse;
import com.bang_ggood.article.dto.response.ArticlesResponses;
import com.bang_ggood.article.service.ArticleService;
import com.bang_ggood.article.service.ArticleManageService;
import com.bang_ggood.auth.config.AdminPrincipal;
import com.bang_ggood.auth.config.AuthRequiredPrincipal;
import com.bang_ggood.user.domain.User;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.net.URI;

@RequiredArgsConstructor
@RestController
public class ArticleController {

private final ArticleService articleService;

public ArticleController(ArticleService articleService) {
this.articleService = articleService;
}
private final ArticleManageService articleManageService;

@PostMapping("/articles")
public ResponseEntity<Void> createArticle(@AdminPrincipal User user,
@Valid @RequestBody ArticleCreateRequest request) {
Long id = articleService.createArticle(request);
Long id = articleManageService.createArticle(request);
return ResponseEntity.created(URI.create("/article/" + id)).build();
}

@GetMapping("/articles/{id}")
public ResponseEntity<ArticleResponse> readArticle(@PathVariable("id") Long id) {
return ResponseEntity.ok(articleService.readArticle(id));
return ResponseEntity.ok(articleManageService.readArticle(id));
}

@GetMapping("/articles")
public ResponseEntity<ArticlesResponses> readArticles() {
return ResponseEntity.ok(articleService.readArticles());
return ResponseEntity.ok(articleManageService.readArticles());
}

@PutMapping("/articles/{id}")
public ResponseEntity<Void> updateArticle(@AdminPrincipal User user,
@PathVariable("id") Long id, @Valid @RequestBody ArticleUpdateRequest request) {
articleManageService.updateArticle(id, request);
return ResponseEntity.noContent().build();
}

@DeleteMapping("/articles/{id}")
public ResponseEntity<ArticleResponse> deleteArticle(@AdminPrincipal User user,
@PathVariable("id") Long id) {
articleService.deleteArticle(id);
articleManageService.deleteArticle(id);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,27 @@ public class Article extends BaseEntity {

private String thumbnail;

private Long viewCount;

public Article(String title, String content, String keyword, String summary, String thumbnail) {
this.title = title;
this.content = content;
this.keyword = keyword;
this.summary = summary;
this.thumbnail = thumbnail;
this.viewCount = 0L;
}

public void increaseViewCount() {
viewCount++;
}

public void change(Article article) {
this.title = article.title;
this.content = article.content;
this.keyword = article.keyword;
this.summary = article.summary;
this.thumbnail = article.thumbnail;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bang_ggood.article.dto.request;

import com.bang_ggood.article.domain.Article;
import jakarta.validation.constraints.NotBlank;

public record ArticleUpdateRequest(@NotBlank(message = "제목을 입력해야 합니다.") String title, String content, String keyword,
String summary, String thumbnail) {

public Article toEntity() {
return new Article(title, content, keyword, summary, thumbnail);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import java.time.LocalDateTime;

public record ArticleResponse(Long articleId, String title, String content, String keyword, String summary,
String thumbnail, LocalDateTime createdAt) {
String thumbnail, LocalDateTime createdAt, Long viewCount) {

public static ArticleResponse from(Article article) {
return new ArticleResponse(
Expand All @@ -14,7 +14,8 @@ public static ArticleResponse from(Article article) {
article.getKeyword(),
article.getSummary(),
article.getThumbnail(),
article.getCreatedAt()
article.getCreatedAt(),
article.getViewCount()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;

Expand All @@ -31,4 +32,11 @@ default Article getById(Long id) {
"SET a.deleted = true " +
"WHERE a.id = :id")
void deleteById(@Param("id") Long id);

@Transactional
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE Article a "
+ "SET a.viewCount = :viewCount "
+ "WHERE a.id = :id")
void updateViewCount(@Param("id") long id, @Param("viewCount") Long viewCount);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.bang_ggood.article.service;

import com.bang_ggood.article.domain.Article;
import com.bang_ggood.article.dto.request.ArticleCreateRequest;
import com.bang_ggood.article.dto.request.ArticleUpdateRequest;
import com.bang_ggood.article.dto.response.ArticleResponse;
import com.bang_ggood.article.dto.response.ArticlesResponse;
import com.bang_ggood.article.dto.response.ArticlesResponses;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@RequiredArgsConstructor
@Service
public class ArticleManageService {

private final ArticleService articleService;
private final ArticleViewService articleViewService;

@Transactional
public Long createArticle(ArticleCreateRequest request) {
Article article = request.toEntity();
return articleService.createArticle(article);
}

@Transactional(readOnly = true)
public ArticleResponse readArticle(Long id) {
Article article = articleService.readArticle(id);
articleViewService.increaseViewCount(id);
return ArticleResponse.from(article);
}

@Transactional(readOnly = true)
public ArticlesResponses readArticles() {
List<ArticlesResponse> articles = articleService.readArticles().stream()
.map(ArticlesResponse::from)
.toList();
return new ArticlesResponses(articles);
}

@Transactional
public void updateArticle(Long id, ArticleUpdateRequest request) {
articleViewService.syncViewCounts();
articleService.updateArticle(id, request.toEntity());
}

@Transactional
public void deleteArticle(Long id) {
articleViewService.syncViewCounts();
articleService.deleteArticle(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package com.bang_ggood.article.service;

import com.bang_ggood.article.domain.Article;
import com.bang_ggood.article.dto.request.ArticleCreateRequest;
import com.bang_ggood.article.dto.response.ArticleResponse;
import com.bang_ggood.article.dto.response.ArticlesResponse;
import com.bang_ggood.article.dto.response.ArticlesResponses;
import com.bang_ggood.article.repository.ArticleRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -22,25 +19,27 @@ public class ArticleService {
private final ArticleRepository articleRepository;

@Transactional
public long createArticle(ArticleCreateRequest request) {
Article article = request.toEntity();
public long createArticle(Article article) {
articleRepository.save(article);
return article.getId();
}

@Cacheable(cacheNames = ARTICLE, key = "#id")
@Transactional(readOnly = true)
public ArticleResponse readArticle(Long id) {
Article article = articleRepository.getById(id);
return ArticleResponse.from(article);
public Article readArticle(Long id) {
return articleRepository.getById(id);
}

@Transactional(readOnly = true)
public ArticlesResponses readArticles() {
List<ArticlesResponse> articles = articleRepository.findLatestArticles().stream()
.map(ArticlesResponse::from)
.toList();
return new ArticlesResponses(articles);
public List<Article> readArticles() {
return articleRepository.findLatestArticles();
}

@CachePut(cacheNames = ARTICLE, key = "#id")
@Transactional
public void updateArticle(Long id, Article updateArticle) {
Article article = articleRepository.getById(id);
article.change(updateArticle);
}

@CacheEvict(cacheNames = ARTICLE, key = "#id")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.bang_ggood.article.service;

import com.bang_ggood.article.domain.Article;
import com.bang_ggood.article.repository.ArticleRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import static com.bang_ggood.global.config.cache.CacheName.ARTICLE;

@RequiredArgsConstructor
@Service
public class ArticleViewService {

private final CacheManager cacheManager;
private final ArticleRepository articleRepository;
private final Set<Long> cacheArticleIds = new HashSet<>();

public void increaseViewCount(long id) {
Cache cache = cacheManager.getCache(ARTICLE);
if (cache != null) {
handleViewCount(id, cache);
}
}

private void handleViewCount(long id, Cache cache) {
Article article = cache.get(id, Article.class);
if (article != null) {
article.increaseViewCount();
cache.put(id, article);
cacheArticleIds.add(id);
}
}

@Scheduled(cron = "0 0 0 * * ?")
public void syncViewCounts() {
Cache cache = cacheManager.getCache(ARTICLE);
if (cache != null) {
syncAllArticleViewCounts(cache);
cache.clear();
cacheArticleIds.clear();
}
}

private void syncAllArticleViewCounts(Cache cache) {
cacheArticleIds.stream()
.map(id -> cache.get(id, Article.class))
.filter(Objects::nonNull)
.forEach(article -> articleRepository.updateViewCount(article.getId(), article.getViewCount()));
}
}
Loading

0 comments on commit 8205eca

Please sign in to comment.