만약 코드 테이블로 카테고리를 관리하는 것은
- 관련된 테이블은 조회를 할때마다 항상
join
으로 code용 테이블에 있는 데이터를 가져와야 한다.- 컴파일 단계에서 체크할 수 있는 것이 없다.
- 코드레벨에서 확인할 수 있는 것이 없다.
- 코드 테이블이 여러개로 분리된 경우 작성된 쿼리를 통해 여러 테이블을 다 찾아서 확인 해야한다.
@AuthenticationPrincipal
를 사용하여 현재 로그인한 사용자 정보를 가져온다.관리자가 회원 관리를 해야하기 때문에 User, Post, Comment 수정 및 삭제 권한이 있다.
AdminController.java
@Controller
@Slf4j
public class AdminController {
private final AdminUserService adminUserService;
private final AdminPostService adminPostService;
private final AdminCommentService adminCommentService;
public AdminController(AdminUserService adminUserService, AdminPostService adminPostService, AdminCommentService adminCommentService) {
this.adminUserService = adminUserService;
this.adminPostService = adminPostService;
this.adminCommentService = adminCommentService;
}
@RequestMapping("/admin")
private String main() {
return "admin/main";
}
@GetMapping("admin/users")
public String userList(Model model) {
List<User> users = adminUserService.findUsers();
model.addAttribute("users", users);
return "admin/users";
}
@GetMapping("admin/users/{userId}/edit")
public String userForm(@PathVariable("userId") Long userId, Model model) {
User user = (User) adminUserService.findOne(userId);
//UserAllDto form = new UserAllDto(id, username, nickname, profileImageSrc, role, major, part, studentId, team);
UserAllDto form = UserAllDto.builder()
.userId(user.getId())
.username(user.getName())
.nickname(user.getNickname())
.major(user.getMajor())
.part(user.getPart())
.studentId(user.getStudentId())
.role((user.getRole()))
.build();
model.addAttribute("form", form);
return "admin/updateUserForm";
}
}
AdminUserService
, AdminPostService
, AdminCommentService
와 의존관계에 있음@RequestMapping("/admin")
을 클래스 상단이 아닌 내부에 왜 썼을까..?userList(Model model)
: 요청을 서비스에 넘겨 DB에 저장된 모든 회원 조회 후 Model을 통해 데이터를 view로 넘겨준다.Builder
패턴을 최상단인 controller
에서 구현하면 보안상 좋지 않다. 왜냐면 DB 생명주기에 트랜잭션이랑 같이 DB조회하기 때문이다. -> service
나 dto
에서 해주는게 좋으며 controller
에서는 url 수신만 받는게 좋다.controller
는 service
에 위임만 하면 된다.BaseController.java
@ControllerAdvice
@RequiredArgsConstructor
public class BaseController {
private final EntityManager em;
@ModelAttribute("teams")
public List<Team> teams(Model model){
return em.createQuery("select t from Team t", Team.class).getResultList();
}
}
@ControllerAdvice
어노테이션으로 모든 controller에서 발생할 수 있는 예외 처리해준다.@ModelAttribute
어노테이션 사용으로 객체가 자동으로 Model 객체에 추가 후 view로 전달해준다.AdminCommentService.java
@Service
@Transactional
@RequiredArgsConstructor
public class AdminCommentService {
private final AdminCommentRepository adminCommentRepository;
// 특정 User가 작성한 댓글 조회 (by. userId)
@Transactional(readOnly = true)
public List<Comment> findCommentsByUserId(Long userId) { return adminCommentRepository.findByUserId(userId); }
// 댓글 전체 조회
@Transactional(readOnly = true)
public List<Comment> findComments() {
return adminCommentRepository.findAll();
}
// 댓글 1개 조회
@Transactional(readOnly = true)
public Comment findOne(Long commentId) {
return adminCommentRepository.findOne(commentId);
}
// 댓글 1개 삭제
@Transactional
public void deleteCommentOne(Long commentId) {
Comment findComment = adminCommentRepository.findOne(commentId);
adminCommentRepository.deleteComment(findComment);
}
// User가 작성한 댓글 전체 삭제
// 전체 댓글 삭제 아님
@Transactional
public void deleteCommentByUser(Long userId) {
List<Comment> findComments = adminCommentRepository.findByUserId(userId);
adminCommentRepository.deleteComments(findComments);
}
}
@Transactional
을 사용하여 영속 상태로 있고, 다른 것들을 수정해야 한다고 할때 해당 메소드 안에서 수정 가능하다.AdminCommentRepository.java
@Repository
@RequiredArgsConstructor
public class AdminCommentRepository {
private final EntityManager em;
// comment ID로 조회
public Comment findOne(Long id) { return em.find(Comment.class, id); }
// 모든 댓글 조회
public List<Comment> findAll() {
List<Comment> result = em.createQuery("select c from Comment c", Comment.class)
.getResultList();
return result;
}
// 작성자의 댓글 조회
public List<Comment> findByUserId(Long userId) {
List<Comment> result = em.createQuery("select distinct c from Comment c where c.author.id = :userId", Comment.class)
// JPQL 쿼리의 매개변수 값을 설정 userId 매개변수에 전달받은 userId값이 설정됨
.setParameter("userId", userId)
//결과를 리스트 형태로 반환 결과가 없으면 빈 리스트가 반환된다.
.getResultList();
return result;
}
// 댓글 1개 삭제
public void deleteComment(Comment comment) { em.remove(comment); }
// 댓글 N개 삭제
public void deleteComments(List<Comment> comments) {
for(Comment comment : comments) {
em.remove(comment);
}
}
}
CommunityRepository.java
@Repository
@RequiredArgsConstructor
public class CommunityRepository {
private final EntityManager em;
public List<AdminPost> findAll(String category){
return em.createQuery(
"select new Likelion.Recruiting.admin.AdminPost(p.id,p.title,u.name,p.createdTime,p.body,u.part) " +
" from Post p join p.author u" +
" where p.mainCategory = :main",AdminPost.class)
.setParameter("main",MainCategory.valueOf(category))
.getResultList();
}
}
CommentController.java
@CrossOrigin("*")
@RestController
@RequiredArgsConstructor
public class CommentController {
private final CommentService commentService;
private final CommentLikeService commentLikeService;
private final UserService userService;
private final PostRepository postRepository;
private final CommentRepository commentRepository;
private final CommentLikeRepository commentLikeRepository;
@GetMapping("/community/post/{postId}/comments")//게시글에 따른 댓글 & 대댓글 불러오기
public DataResponseDto getSimplePosts(@AuthenticationPrincipal CustomOauthUserImpl customOauthUser, @RequestHeader("HEADER") String header, @PathVariable("postId") Long postId) {
List<Comment> comments = commentRepository.findByPostId(postId);
String email = customOauthUser.getUser().getEmail();
User user = userService.findUser(email);// 옵셔널이므로 id없을시 예외처리할때 예외코드날아감 -->try catch쓰기
List<CommentDto> result = comments.stream()
.map(comment -> new CommentDto(comment,user))
.collect(Collectors.toList());
return new DataResponseDto(result.size(), result);
}
@PostMapping("/community/post/{postId}")//댓글 저장 api
public CreatePostResponseDto saveComment(@AuthenticationPrincipal CustomOauthUserImpl customOauthUser, @RequestBody CreateCommentRequestDto request, @PathVariable("postId") Long postId) {
Comment createdComment = Comment.builder()
.body(request.getBody())
.build();
String email = customOauthUser.getUser().getEmail();
User user = userService.findUser(email);
Post post = postRepository.findById(postId).get();//역시 예외처리는 예외코드로
Comment savedComment = commentService.createComment(createdComment,post,user);
return new CreatePostResponseDto(savedComment.getId(), "댓글 작성 성공");
}
}
CommentService
, CommentLikeService
, UserService
, PostRepository
, CommentRepository
, CommentLikeRepository
의존관계에 있다.@AuthenticationPrincipal
은 UserDetails
타입을 가지고 있다. -> UserDetails
타입을 구현한 PrincipalDetails
클래스를 받아 User object를 얻는다.SecurityContext
에는 Authentication
객체 타입만 들어갈 수 있다.Authentication
안에도 저장할 수 있는 객체 타입은 UserDetails
와 OAuth2User
이다.@AuthenticationPrincipal
을 통해 현재 로그인한 회원의 email 정보를 가져온다.CommentService.java
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CommentService {
private final CommentRepository commentRepository;
@Transactional
public Comment createComment(Comment comment, Post post, User user){
comment.setAuthor(user);
comment.setPost(post);
Comment createdComment = commentRepository.save(comment);
return createdComment;
}
@Transactional
public CreateResponseMessage updateComment(Long commentId, Long userId, CreateCommentRequestDto reqeust){
Comment comment = commentRepository.findById(commentId).get();
if(userId == comment.getAuthor().getId()){
comment.update(reqeust.getBody());
commentRepository.save(comment);
return new CreateResponseMessage((long)200, "업데이트 성공");
}
else return new CreateResponseMessage((long)403, "본인의 댓글이 아닙니다.");
}
@Transactional
public CreateResponseMessage deleteComment (Long commentId, Long userId){
Comment comment = commentRepository.findById(commentId).get();
if(userId.equals(comment.getAuthor().getId())) {
comment.delete();
if (comment.getIsDeleted() == true)
return new CreateResponseMessage((long) 200, "삭제 성공");
else return new CreateResponseMessage((long) 404, "이미 삭제된 댓글입니다.");
}
else return new CreateResponseMessage((long)403, "본인의 댓글이 아닙니다.");
}
public List<Comment> findUser_Comment(User user) {
return commentRepository.findAllDesc(user);
}
}
TokenService.java
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class TokenService {
private final UserRepository userRepository;
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenRepository refreshTokenRepository;
public User getUser(String token){
String email = jwtTokenProvider.getUserEmail(token);
User user = userRepository.findByEmail(email).get();
return user;
}
public NavbarDto login(Long uid){
// 해당 유저 찾기
User user = userRepository.findById(uid).get();
String email = user.getEmail();
Role role = user.getRole();
// JWT 만들기
String token = jwtTokenProvider.createToken(email, role);
return NavbarDto.builder()
.id(user.getId())
.name(user.getName())
.profileImage(user.getProfileImage())
.isJoined(user.isJoind())
.role(user.getRole())
.JWT(token)
.build();
}
public JWTDto makeJwt(Long uid){
// 해당 유저 찾기
User user = userRepository.findById(uid).get();
String email = user.getEmail();
Role role = user.getRole();
// JWT 만들기
String token = jwtTokenProvider.createToken(email, role);
return new JWTDto(token);
}
@Transactional
public String makeRt(Long uid){
// 해당 유저 찾기
User user = userRepository.findById(uid).get();
String email = user.getEmail();
Role role = user.getRole();
// Refresh Token 만들기
String RT = jwtTokenProvider.createRT(email, role);
// RT를 저장하기 위헤 객체로 만들기
RefreshToken refreshToken = RefreshToken.builder()
.user_id(user.getId())
.refreshToken(RT)
.build();
// RT DB에 저장하기
refreshTokenRepository.save(refreshToken);
return RT;
}
@Transactional
public void deleteRt(String rt){
RefreshToken refreshToken = refreshTokenRepository.findByRefreshToken(rt);
if(refreshToken != null){ // DB에 Refresh Token이 있는 경우
refreshTokenRepository.delete(refreshToken);
System.out.println("rt 삭제 완료!");
}
else { // DB에 Refresh Token이 없는 경우 == 이미 삭제 되었거나, 올바르지 않은 RT이거나
throw new RefreshException(ErrorCode.NOT_VALIDATE_RT.getErrorCode(), "refresh_token이 올바르지 않습니다.");
}
}
}
JwtTokenProvider.java
@Component
@RequiredArgsConstructor
public class JwtTokenProvider {
private final CustomOAuth2UserService customOAuth2UserService;
private final RefreshTokenRepository refreshTokenRepository;
private final UserRepository userRepository;
// JWT 토큰 유효시간 설정
// private Long tokenValidTime = 10 * 1000L; // 10초
private Long tokenValidTime = 60 * 60 * 1000L; // 1시간
// Refresh Token 유효시간 설정
// private Long RTValidTime = 10 * 1000L; // 1초
private Long RTValidTime = 14 * 24 * 60 * 60 * 1000L; // 2주
//---------------------- JWT 토큰 생성 ---------------------- //
public String createToken(String email, Role role) {
//payload 설정
//registered claims
Date now = new Date();
Claims claims = Jwts.claims()
.setSubject("access_token") //토큰제목
.setIssuedAt(now) //발행시간
.setExpiration(new Date(now.getTime() + tokenValidTime)); // 토큰 만료기한
//private claims
claims.put("email", email); // 정보는 key - value 쌍으로 저장.
claims.put("role", role);
return Jwts.builder()
.setHeaderParam("typ", "JWT") //헤더
.setClaims(claims) // 페이로드
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 서명. 사용할 암호화 알고리즘과 signature 에 들어갈 secretKey 세팅
.compact();
}
//---------------------- RT 토큰 생성 ---------------------- //
public String createRT(String email, Role role) {
//payload 설정
//registered claims
Date now = new Date();
Claims claims = Jwts.claims()
.setSubject("refresh_token") //토큰제목
.setIssuedAt(now) //발행시간
.setExpiration(new Date(now.getTime() + RTValidTime)); // 토큰 만료기한
claims.put("email", email); // 정보는 key - value 쌍으로 저장.
claims.put("role", role);
return Jwts.builder()
.setHeaderParam("typ", "refreshToken") //헤더
.setClaims(claims) // 페이로드
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 서명. 사용할 암호화 알고리즘과 signature 에 들어갈 secretKey 세팅
.compact();
}
}
@AuthenticationPrincipal
을 통해 현재 로그인 중인 유저 정보를 가져와서 Post, Comment, Reply에 대한 CRUD를 한다.