Coding History

국비 지원 IT(웹앱개발) 취업반 강의 57일차 (Spring)

BlackBirdIT 2024. 8. 28. 09:03

좋아요 싫어요를 할 때, 로그인을 하지 않으면 작동하지 않도록 막아두었다.(인터셉터 사용)
하지만 디테일을 더 살리자면 로그인이 필요하다면 로그인 페이지로 사용자를 던져주는게 사용자의 경험을 높혀주는 일이 될 것이다. 그리고 로그인을 하면 다시 원래 보던 게시글로 이동시켜줘야한다. 이를 적용하기 위해서 어떻게 해야 좋을까.

나의 생각

일단 인터셉터에서 처리하던 것을 빼고 메서드 내에서 관리를 할 지, 아니면 인터셉터에 새로운 메서드를 만들어 적용시킬지 둘 중 하나를 선택해서 구현하는게 맞는 것 같다. 난 뭔갈 하면서 최종적인 목표를 생각하는 것이 약간 습관이라면 습관인데 코딩을 할 땐, 의식적으로 바로 앞의 문제부터 해결하자고 생각하면서 코드를 쓴다. 그래서 일단 닥친 문제.

문제 1.

인터셉터에서 처리하던 로그인하라는 안내가 코드를 이리저리 고치는 과정에서 작동을 제대로 하지 않는다. 따라서 나는 더 쉬운 방법을 택하려고 한다. 인터셉터에서 처리하던 코드는 각주 처리 했고, (등록을 하지 않는다는 뜻) 컨트롤러와 서비스내에서 처리하기로 결정했다.

서비스에서 if(loginedMemberId == 0) return -3; 해당 코드 추가로 로그인 되지 않았을 시 -3을 리턴하도록 했고 컨트롤러에서 이를 처리하면 되는데,
if(resultPoint == -3) return ResultData.from("F-1","로그인 후 사용","/usr/member/doLogin",resultPoint); 여기서 이걸 어떻게 로그인 페이지로 돌리느냐가 1차 문제다.
복붙하고 보니까 doLogin은 기능이다 그냥 login으로 고치자.

문제 2.

    @RequestMapping("/usr/article/doReaction")
    @ResponseBody
    public String doReaction(HttpServletRequest req , Model model, int id, String relTypeCode, int relId, int newPoint) {
        Rq rq = (Rq) req.getAttribute("rq");
        int loginedMemberId = rq.getLoginedMemberId();
        model.addAttribute("loginedMemberId", loginedMemberId);
        relTypeCode = "article";

        int resultPoint = reactionPointService.toggleReactionPoint(rq.getLoginedMemberId(), relTypeCode, relId, newPoint);        
        reactionPointService.updateArticleReactionPoints();

        ResultData loginRd = ResultData.from("","");

        if(resultPoint == -3) return Ut.jsReplace("F-A", "로그인 필요.", "/usr/member/login");
        // 최신의 좋아요/싫어요 수를 가져옴
        Article updatedArticle = articleService.getForPrintArticle(loginedMemberId, id);

        String status = resultPoint > 0 ? "liked" : "unliked";
        ResultData rd = ResultData.from("S-1", "리액션기능 실행완료.", "status", status);
        rd.setData2("reactedArticleId", id);

        Map<String, Object> reactionPoints = new HashMap<>();
        reactionPoints.put("goodReactionPoint", updatedArticle.getGoodReactionPoint());
        reactionPoints.put("badReactionPoint", updatedArticle.getBadReactionPoint());

        rd.setData2("reactionPoints", reactionPoints);

        return rd;
    }

일단 login 페이지로 유저를 보내기 위해서 할 수 있는게 무엇이 있을까를 생각해보면서 다른 메서드들은 어떻게 처리를 했던가 살펴보니, Ut에 만들어둔 jsReplace로 사용자를 이리저리 보냈다. 그래서 String으로 타입을 바꿔준 후 해당 코드로 리턴을 만들어주니, return rd; 여기서 문제다. 지금 JSP의 JS에서 좋아요나 싫어요 버튼을 눌렀을 때 rd로 데이터를 보내서 ajax를 활용해, 갱신하고 적용했었는데 지금 데이터를 보낼 수가 없다.

아! String으로 타입을 정하지 말고 Object로 해보자.

는 데이터를 이상하게 받아와서 그냥 다시 ResultData 타입으로 고치고 방법을 아예 바꿔봤다.

JS에서 접근해보기로.

    function reaction(point) {
        $.post('/usr/article/doReaction', {
            id: articleId,
            relTypeCode: relTypeCode,
            relId: relId,
            newPoint: point
        }, function(response) {
            console.log(response);

            if (response.resultCode && response.resultCode.startsWith("F-A")) {
                if (response.data1 && response.data1Name === "redirectUri") {
                    window.location.replace(response.data1);
                }
            } else if (response.resultCode && response.resultCode.startsWith("S-")) {
                updateReactionUI(point); // UI 업데이트
                $('#likeCounttt').text(response.data2.reactionPoints.goodReactionPoint);
                $('#disLikeCounttt').text(response.data2.reactionPoints.badReactionPoint);
            } else {
                alert(response.msg);
            }
        });
    }

startsWith("F-A") 를 사용해서 로그인 하지 않았을 경우를 추가해주고 또 다시 조건문을 걸어서 data1Name이 redirectUri 경우, window.location.replace(response.data1);으로 location을 지정해준다. 그럼 콘솔로그에 찍힌 데이터를 보면

data1: "/usr/member/login"
data1Name: "redirectUri"
data2: null
data2Name: null
fail: true
msg: "로그인 필요."
resultCode: "F-A"
success: false

여기 있는 데이터를 활용해서 if문으로 1차적으로 특정 상황을 거르고 해당 url로 이동할 수 있게 window.location.replace(response.data1);으로 처리했다.

나는 일단 이렇게 1차적으로 해결 했는데 강사님은 JS에서 로그인 유무를 NaN으로 판단했다.
이후에 로그인 페이지로 보내는 것은 나와 똑같이window.location.을 사용하셨다. 약간 차이점이 있다면
encodeURIComponent(window.location.href)를 사용했다.

그리고 하시면서 로그인후에 다시 원래 사용자가 보고 있던 페이지로 이동할 수 있는 코드를 보여주셨는데
로그인 JSP와 메서드를 약간 손 봐야했다.

우선은 수정된 JS부터 보자.

            if (response.resultCode && response.resultCode.startsWith("F-A")) {
                if (response.data1 && response.data1Name === "redirectUri") {
                    var currentUri = encodeURIComponent(window.location.href);
                    window.location.replace(response.data1 + '?afterLoginUri=' + currentUri);
                    return;
                }

currentUri에 현재 보고 있는 페이지를 저장시키고 난 이후에 로그인 페이지로 보내면서 ?afterLoginUri=를 덧붙여준다.

    @RequestMapping("/usr/member/doLogin")
    @ResponseBody
    public String doLogin(HttpServletRequest req, String loginId, String loginPw, String afterLoginUri) {

        rq = (Rq) req.getAttribute("rq");

        Member member = memberService.getMemberByLoginId(loginId);

        if (member == null) {
            return Ut.jsHistoryBack("F-3", Ut.f("%s는(은) 존재 하지않습니다.", loginId));
        }

        if (member.getLoginPw().equals(loginPw) == false) {
            return Ut.jsHistoryBack("F-4", Ut.f("비밀번호가 틀렸습니다."));
        }

        rq.login(member);

        if(afterLoginUri.length() > 0) return Ut.jsReplace("S-1", Ut.f("%s님 환영합니다", member.getNickname()), afterLoginUri);

        return Ut.jsReplace("S-1", Ut.f("%s님 환영합니다", member.getNickname()), " / ");
    }

String afterLoginUri를 받을 수 있게 하고, 이걸 로그인 페이지에서 받아올 수 있게 JSP from 태그 안에<input type="hidden" name="afterLoginUri" value=${param.afterLoginUri }>를 전송해주면 로그인 이후에 원래 사용자가 보던 페이지로 돌아올 수 있게 된다.

생각보다 그렇게 어려운 건 아니였는데 생각자체가 여기까지 뻗질 못하니 어려운게 맞는 것 같기도 하고..


이제는 댓글 기능이다.

  1. table 짜기
  2. 시나리오
  3. 테스트데이터 생성
  4. 테스트데이터 표시
  5. 댓글 작성 폼
  6. 댓글 작성 기능

순서대로 천천히 구현해보자.

댓글 기능 table에는 뭐가 필요할까?

일단 id는 기본으로 깔고 가겠지.
regDate 는 댓글 생성 날짜.
updateDate 는 댓글 수정 날짜.
memberId 는 댓글을 쓴 유저.
comentType 은 어느 게시판인지 구별.
comentId 는 어느 게시물인지 구별.
commentsBody 은 댓글의 내용.

해서 sql을 보면

CREATE TABLE comments (
    id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    regDate DATETIME NOT NULL,
    updateDate DATETIME NOT NULL,
    memberId INT(10) UNSIGNED NOT NULL,
    commentType CHAR(50) NOT NULL COMMENT '관련 데이터 타입 코드',
    commentId INT(10) NOT NULL COMMENT '관련 데이터 번호',
    commentsBody TEXT NOT NULL
);

일단은 이렇게 구성해봤다. testData를 생성해보자.

## 댓글 테스트 데이터 
INSERT INTO comments
SET regDate = NOW(),
updateDate = NOW(),
memberId = 3,
commentType = 'article',
commentId = 1,
commentsBody = 'hihihihihi';

INSERT INTO comments
SET regDate = NOW(),
updateDate = NOW(),
memberId = 1,
commentType = 'article',
commentId = 2,
commentsBody = 'hihihihihi';

INSERT INTO comments
SET regDate = NOW(),
updateDate = NOW(),
memberId = 2,
commentType = 'article',
commentId = 3,
commentsBody = 'hihihihihi';

이렇게 데스트데이터를 꾸렸다.

문제 없이 잘 들어간 모습이고. 이제는 detail 페이지에서 댓글이 보이게끔 만들어보자.
(와중에 강사님이 테이블을 만드셔서 변수명은 따라갔다. 데이터는 딱히 틀린게 없음.)

그래서 이걸 화면에 띄워주기 위해서는 일단 해당 데이터를 가져올 수가 있어야한다.

<div class="comments-section">
    <h3>Comments</h3>
    <c:forEach var="comment" items="${comments}">
        <div class="comment-item">
            <span class="comment-author">Member ID: ${comment.memberId}</span>
            <span class="comment-date">${comment.regDate}</span>
            <p class="comment-body">${comment.body}</p>
        </div>
    </c:forEach>
</div>

우선 html 설계를 하고,

Reply클래스에 겟셋터를 만들어주자.

@Data
public class Reply {
    private int id;
    private String regDate;
    private String updateDate;
    private int memberId;
    private String relTypeCode;
    private int relId;
    private String body;
}

detail 컨트롤러에서 댓글을 가져올 수 있게 만들어주고,

        // 댓글 데이터를 가져와서 모델에 추가
        List<Reply> comments = replyService.getReplyForArticle(id);
        model.addAttribute("comments", comments);
/////
@Service
public class ReplyService {

    @Autowired
    private ReplyRepository replytRepository;

    public List<Reply> getReplyForArticle(int articleId) {
        return replytRepository.findReplyByArticleId(articleId);
    }
}
////
@Mapper
public interface ReplyRepository {
    @Select("SELECT * FROM reply WHERE relTypeCode = 'article' AND relId = #{articleId} ORDER BY regDate DESC")
    List<Reply> findReplyByArticleId(int articleId);
}

순서대로 서비스, 리포지토리. 해서 새로고침해보면

이렇게 댓글 데이터를 가져올 수 있다.

작성 폼까지 만들기 위해서 html에는 from태그를 java에서는 reply 컨트롤러를 만들어줬다.

컨트롤러에서 @Controller를 빼먹어서 계속 Spring MVC 경로를 찾지 못한다는 말에 이것저것 다 해보다가 찾아서 수정했다.

그걸 수정하고 난 뒤에 댓글 작성이 잘 된다.


회원 번호인게 거슬려서 닉네임을 받을 수 있도록 vo에 writer 추가와 쿼리문도 수정해서 가져왔다.

다음 목표는 List에서 각 게시글의 댓글 개수를 표기하는 것과 댓글의 수정 및 삭제다.