Coding History

Spring article , member 기능 구현중

BlackBirdIT 2024. 8. 18. 17:37

로그인 로그아웃까지 다 했으니, 이제 modify부터 구현을 시작해보자.

우선 modify 페이지와 doModify 코드 수정.

    @RequestMapping("/usr/article/modify")
    public String showModify() {
        return "/usr/article/modify";
    }

    @RequestMapping("/usr/article/doModify")
    @ResponseBody
    public String doModify(HttpServletRequest req, int id, String title, String body) {

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

        Article article = articleService.getArticleById(id);

        if (article == null) {
            return Ut.jsReplace("F-1", Ut.f("%d번 게시글은 없습니다", id), " / ");
        }

        ResultData userCanModifyRd = articleService.userCanModify(rq.getLoginedMemberId(), article);

        if (userCanModifyRd.isFail()) {
            return Ut.jsHistoryBack(userCanModifyRd.getResultCode(), userCanModifyRd.getMsg());
        }

        if (userCanModifyRd.isSuccess()) {
            articleService.modifyArticle(id, title, body);
        }

        article = articleService.getArticleById(id);

        return Ut.jsReplace("S-1", Ut.f("%d번 게시글이 수정되었습니다.", id), "/usr/article/detail?id=" + id);
    }

수정을 위해서는 새로운 창이 필요하니 따로 경로를 지정해주고 기능은 기능만 하도록 바꿨다. 기존에 만들었던 login을 참고해서.

JSP.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<c:set var="pageTitle" value="게시물 수정하기"></c:set>
<%@ include file="../common/head.jspf"%>

<section class="modify-article-section">

    <form action="doModify" method="post">
        <input type="hidden" name="id" value="${article.id}" />

        <div class="form-group">
            <label for="title">제목</label>
            <input type="text" id="title" name="title" value="${article.title}" required />
        </div>

        <div class="form-group">
            <label for="body">내용</label>
            <textarea id="body" name="body" rows="10" required>${article.body}</textarea>
        </div>

        <div class="form-group">
            <input type="submit" value="수정하기" />
        </div>
    </form>

    <div class="actions">
        <a href="detail?id=${article.id}" class="btn">취소</a>
    </div>
</section>


</body>
</html>

CSS

/* 게시물 수정 스타일 */
.modify-article-section {
    width: 60%; 
    max-width: 1000px; 
    margin: 0 auto;
    padding: 20px;
    border: 1px solid #333;
    border-radius: 5px;
    background-color: #1e1e1e;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}

.modify-article-form {
    width: 80%;
}

.modify-article-section input[type="text"],
.modify-article-section textarea {
    width: 100%; 
    padding: 12px;
    background-color: #333;
    border: 1px solid #444;
    border-radius: 4px;
    color: #e0e0e0;
    font-size: 16px;
    box-sizing: border-box;
}

.modify-article-section .form-group {
    margin-bottom: 20px;
}

.modify-article-section input[type="submit"] {
    width: 100%; 
    padding: 12px;
    background-color: #2a9d8f;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
}

.modify-article-section input[type="submit"]:hover {
    background-color: #219080;
}

.actions {
    text-align: right;
    margin-top: 20px;
}

.actions .btn {
    padding: 10px 20px;
    background-color: #264653;
    color: #fff;
    border-radius: 4px;
    text-decoration: none;
    margin: 0 5px;
}

.actions .btn:hover {
    background-color: #1b3a4b;
}

해서 화면은 이렇게 나온다.

입력후, 수정하기를 클릭하니까 id 파라미터를 전달하지 않아서 오류가 뜬다.

그래서 코드를 다시 손봤다. 생각해보니까 화면을 보겠다고 modify에서 아무것도 구현한게 없었다.

    @RequestMapping("/usr/article/modify")
    public String showModify(Model model, int id) {
        Article article = articleService.getArticleById(id);

        if (article == null) {
            return "redirect:/usr/article/list";
        }

        model.addAttribute("article", article);
        return "usr/article/modify";
    }

Model과 id로 파라미터가 전달 될 수 있게 하고,

<section class="modify-article-section">

    <form action="doModify?id=${article.id}" method="post" class="modify-article-form">
        <input type="hidden" name="id" value="${article.id}" />

        <div class="form-group">
            <label for="title">제목</label>
            <input type="text" id="title" name="title" value="${article.title}" required />
        </div>

        <div class="form-group">
            <label for="body">내용</label>
            <textarea id="body" name="body" rows="10" required>${article.body}</textarea>
        </div>

        <div class="form-group">
            <input type="submit" value="수정하기" />
        </div>
    </form>

    <div class="actions">
        <a href="detail?id=${article.id}" class="btn">취소</a>
    </div>
</section>

여기서도 경로 지정에 article.id를 추가해줬다.

이제 게시물 수정하기를 클릭하면

원래 내용도 같이 불러오고,

1234 1234 로 수정을 하면,

알림창과 함께,

지정해준 detail 경로도 다시 돌아오게 만들었다. 수정도 잘 된 모습이다.

혹시 모르니까 DB상으로도 수정이 되었는가 확인해보자.

잘 됐다!

이제 write 기능 구현!!

write 관련 코드를 수정중에 가만 생각해보니까 글 작성 버튼이 없으면 접근을 못하니까 우선은 list에서 글 작성 버튼을 만들기로 했다.

저번의 수정 삭제 버튼과, 로그인 로그아웃 버튼이 바뀌는 것을 활용해서 로그인 하면 글 작성 버튼이 보이고, 로그인 상태가 아니라면 글 작성 버튼은 보이지 않도록 만드는게 좋을 것 같아서 그렇게 구현해 보았다.

<c:if test="${rq.isLogined()}">
    <div style="text-align: right; margin-bottom: 10px; margin-right: 20px;">
        <a href="write" class="btn">게시물 작성하기</a>
    </div>
</c:if>

코드는 이렇고 css 도 버튼 스타일로 따로 만들어서 처리했다. 우선 로그인 했을 때 보이는가 확인해보자.

잘 보이니까 로그아웃해보자.

로그아웃하면 글 작성 버튼이 사라진다.

일단 초석은 다졌으니, 본론으로 들어가서 코드를 보자.

    @RequestMapping("/usr/article/write")
    public String showWrite(Model model) {

        return "usr/article/write";
    }

    @RequestMapping("/usr/article/doWrite")
    @ResponseBody
    public String doWrite(HttpServletRequest req, String title, String body) {

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


        if (Ut.isEmptyOrNull(title)) {
            return Ut.jsHistoryBack("F-1", "제목을 입력해주세요");
        }
        if (Ut.isEmptyOrNull(body)) {
            return Ut.jsHistoryBack("F-2", "내용을 입력해주세요");
        }

        ResultData writeArticleRd = articleService.writeArticle(rq.getLoginedMemberId(), title, body);

        int id = (int) writeArticleRd.getData1();

        Article article = articleService.getArticleById(id);

        return Ut.jsReplace(writeArticleRd.getResultCode(), writeArticleRd.getMsg(), "생성된 게시글");
    }

일단은 이렇게 코드를 고쳤고

JSP 구성은 이렇게.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<c:set var="pageTitle" value="게시물 작성하기"></c:set>
<%@ include file="../common/head.jspf"%>

<section class="article-section">

    <form action="doWrite?id=${article.id}" method="post" class="article-form">
        <input type="hidden" name="id" value="${article.id}" />

        <div class="form-group">
            <label for="title">제목</label>
            <input type="text" id="title" name="title" value="${article.title}" required />
        </div>

        <div class="form-group">
            <label for="body">내용</label>
            <textarea id="body" name="body" rows="10" required>${article.body}</textarea>
        </div>

        <div class="form-group">
            <input type="submit" value="수정하기" />
        </div>
    </form>

    <div class="actions">
        <a href="detail?id=${article.id}" class="btn">취소</a>
    </div>
</section>


</body>
</html>

modify랑 흡사할테니 그냥 modify의 class도 죄다 저렇게 바꿨다.

기능이 잘 되는가 한번 보자.

기능은 잘 되는데,

경로지정이 잘못됐나보다. 고쳐보자.

아 여기 리턴 값을 제대로 안고쳤다.

    @RequestMapping("/usr/article/doWrite")
    @ResponseBody
    public String doWrite(HttpServletRequest req, String title, String body) {

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


        if (Ut.isEmptyOrNull(title)) {
            return Ut.jsHistoryBack("F-1", "제목을 입력해주세요");
        }
        if (Ut.isEmptyOrNull(body)) {
            return Ut.jsHistoryBack("F-2", "내용을 입력해주세요");
        }

        ResultData writeArticleRd = articleService.writeArticle(rq.getLoginedMemberId(), title, body);

        int id = (int) writeArticleRd.getData1();

        Article article = articleService.getArticleById(id);

        return Ut.jsReplace(writeArticleRd.getResultCode(), writeArticleRd.getMsg(),"/usr/article/detail?id=" + id);
        }

이제 다시 잘 되는가 확인해보자.

작성한 글로 잘 넘어간다. DB는 아까 확인했으니까 넘어가자.

다음으로는 회원가입 기능을 만들어보자. 일단 아까처럼 main페이지, 가 아니고 head에 로그인 상태가 아닐시에 login 버튼 옆에 join 버튼을 만들어보자.

                <c:if test="${!rq.isLogined()}">
                    <li><a class="mr-4" href="../member/login">LOGIN</a></li>
                    <li><a class="hover:underline" href="../member/join">JOIN</a></li>
                </c:if>
                <c:if test="${rq.isLogined()}">
                    <li><a onclick="if(confirm('로그아웃 하시겠습니까?') == false) return false;" class="mr-4"
                        href="../member/doLogout">LOGOUT</a></li>
                </c:if>

생각해보니까 그냥 다 만들어놔서 login 아래에 쓰기만 하면 된다.
mr-4도 수정해주고. 화면을 보면

로그인을 하면,

보이지 않는다.

    @RequestMapping("/usr/member/join")
    public String showJoin() {
        return "/usr/member/join";
    }

화면 구성하기전에 경로로 하나 만들어주고 JSP 생성.

일단 로그인 JSP를 가져와서 약간만 수정하고 화면이 잘 나오는지 확인해보자.

화면 구성을 하면서 css가 너무 복잡해지는 것 같아서 GPT한테 중복코드나 충돌하는 것들 다 정리해달라고 하고 login과 join의 스타일은 어차피 겹칠테니 서로를 통합시켰다.

그래서 join의 화면은 이렇게 보인다.

이제는 기능만 똑바로 구현되게끔 만들면 된다.

    @RequestMapping("/usr/member/join")
    public String showJoin() {
        return "/usr/member/join";
    }

    @RequestMapping("/usr/member/doJoin")
    @ResponseBody
    public String doJoin(HttpServletRequest req, String loginId, String loginPw,
            String name, String nickname, String cellphoneNum, String email) {
        Rq rq = (Rq) req.getAttribute("rq");

        if (Ut.isEmptyOrNull(loginId))
            return Ut.jsHistoryBack("F-1", Ut.f("아이디를 입력해주세요."));

        if (Ut.isEmptyOrNull(loginPw))
            return Ut.jsHistoryBack("F-2", Ut.f("비밀번호를 입력해주세요."));

        if (Ut.isEmptyOrNull(name))
            return Ut.jsHistoryBack("F-3", Ut.f("이름을 입력해주세요."));

        if (Ut.isEmptyOrNull(nickname))
            return Ut.jsHistoryBack("F-4", Ut.f("닉네임를 입력해주세요."));

        if (Ut.isEmptyOrNull(cellphoneNum))
            return Ut.jsHistoryBack("F-5", Ut.f("전화번호를 입력해주세요."));

        if (Ut.isEmptyOrNull(email))
            return Ut.jsHistoryBack("F-6", Ut.f("이메일을 입력해주세요."));

        ResultData doJoinRd = memberService.doJoin(loginId, loginPw, name, nickname, cellphoneNum, email);

        if (doJoinRd.isFail()) {
            return Ut.jsHistoryBack(doJoinRd.getResultCode(), doJoinRd.getMsg());
        }

        Member member = memberService.getMemberById((int) doJoinRd.getData1());

        return Ut.jsReplace("S-1", "회원가입이 완료되었습니다.", "/usr/member/login");
    }

우선은 이렇게 만들어줬고, 비밀번호 확인과 입력한 비밀번호가 일치하는지 하지 않는지에 대한 로직만 처리하면 된다.

JSP에서 처리하는 것이 편할 것 같아서 script태그를 써서 만들었다.

<section class="section">
    <form action="../member/doJoin" method="POST">
        <table class="form-table">
            <tbody>
                <tr>
                    <th>아이디</th>
                    <td><input name="loginId" autocomplete="off" type="text"
                        placeholder="아이디를 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>비밀번호</th>
                    <td><input name="loginPw" autocomplete="off" type="text"
                        placeholder="비밀번호를 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>비밀번호 확인</th>
                    <td><input name="loginPwConfirm" autocomplete="off" type="text"
                        placeholder="비밀번호를 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>이름</th>
                    <td><input name="name" autocomplete="off" type="text"
                        placeholder="이름을 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>닉네임</th>
                    <td><input name="nickname" autocomplete="off" type="text"
                        placeholder="닉네임을 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>전화번호</th>
                    <td><input name="cellphoneNum" autocomplete="off" type="text"
                        placeholder="전화번호를 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>이메일</th>
                    <td><input name="email" autocomplete="off" type="email"
                        placeholder="이메일을 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th></th>
                    <td><input type="submit" value="회원가입" /></td>

                </tr>
            </tbody>
        </table>
    </form>
</section>

<script>
    document.getElementById("joinForm").addEventListener("submit", function(event) {
        const pw = document.getElementById("loginPw").value;
        const pwConfirm = document.getElementById("loginPwConfirm").value;

        if (pw !== pwConfirm) {
            alert("비밀번호가 일치하지 않습니다. 다시 확인해주세요.");
            event.preventDefault();
        }
    });
</script>

작동확인을 해보자.

패스워드는 일부러 보이게 해뒀다.

회원가입 버튼을 누르면.

안되넹? 뭐가 문제인지 다시 확인하거나 아니면 타 사이트 처럼 입력이 처음부터 일치하지 않으면 막아두는 걸로 구현을 해볼까?

이건 내 능력으로 안될 것 같아서 gpt한테 상세하게 구현해달라고 부탁해서 만들었다.

우선은 테스트하면서 생긴 이상한 회원데이터를 삭제하고 다시 테스트데이터를 만들었고, 수정된 JSP 부터 보고 분석해보자.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<c:set var="pageTitle" value="회원가입"></c:set>
<%@ include file="../common/head.jspf"%>

<hr />

<section class="section">
    <form action="../member/doJoin" method="POST" onsubmit="return validateForm();">
        <table class="form-table">
            <tbody>
                <tr>
                    <th>아이디</th>
                    <td><input name="loginId" autocomplete="off" type="text"
                        placeholder="아이디를 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>비밀번호</th>
                    <td><input name="loginPw" id="loginPw" autocomplete="off"
                        type="password" placeholder="비밀번호를 입력해" required /></td>
                </tr>
                <tr>
                    <th>비밀번호 확인</th>
                    <td><input name="loginPwConfirm" id="loginPwConfirm"
                        autocomplete="off" type="password" placeholder="비밀번호를 다시 입력해"
                        required /></td>
                </tr>
                <tr>
                    <th>이름</th>
                    <td><input name="name" autocomplete="off" type="text"
                        placeholder="이름을 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>닉네임</th>
                    <td><input name="nickname" autocomplete="off" type="text"
                        placeholder="닉네임을 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>전화번호</th>
                    <td><input name="cellphoneNum" autocomplete="off" type="text"
                        placeholder="전화번호를 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th>이메일</th>
                    <td><input name="email" autocomplete="off" type="email"
                        placeholder="이메일을 입력해주세요" required /></td>
                </tr>
                <tr>
                    <th></th>
                    <td><input type="submit" value="회원가입" /></td>

                </tr>
            </tbody>
        </table>
    </form>
</section>

<script>
    const pwField = document.getElementById("loginPw");
    const pwConfirmField = document.getElementById("loginPwConfirm");
    const submitBtn = document.getElementById("submitBtn");

    function checkPasswords() {
        if (pwField.value === pwConfirmField.value) {
            pwField.style.borderColor = "green";
            pwConfirmField.style.borderColor = "green";
            submitBtn.disabled = false;
        } else {
            pwField.style.borderColor = "red";
            pwConfirmField.style.borderColor = "red";
            submitBtn.disabled = true;
        }
    }

    function validateForm() {
        if (pwField.value !== pwConfirmField.value) {
            alert("비밀번호가 일치하지 않습니다. 확인해주세요.");
            return false; // 폼 제출을 막습니다.
        }
        return true;
    }

    pwField.addEventListener("input", checkPasswords);
    pwConfirmField.addEventListener("input", checkPasswords);
</script>

</body>
</html>

여기서 우리가 주목해야될 것은 당연, from 태그에서 onsubmit="return validateForm();" 이 부분과 script 태그다.

보면 도큐먼트 어쩌구 되어있는데 이건 이거다.
document.getElementById는 자바스크립트에서 제공하는 표준 메서드이고. 이 메서드는 HTML 문서에서 특정 ID를 가진 요소를 선택하고, 그 요소를 나타내는 객체를 반환한다. 그래서 저렇게 사용되어있는 것이고 나머지는 우리가 하는 함수생성 후 구현해 둔 것이다. 찬찬히 살펴보면 뭘 하고 싶어서 만들었는지 이해할 수 있을 것이다.

그리고 다시 봐야될 것은 from 태그에서 onsubmit="return validateForm();"인데 서브밋을 할 때 이 함수가 작동하면서 비밀번호가 일치하는지 일치하지 않는지 판단하고 되돌아가게 하거나 승인한다.

회원가입이 은근 까다로워서 조금 헤멧다. 일단 구현이 제대로 다 되는지 확인해보자. 빈칸은 어차피 required속성으로 html 단계에서 막아버리기 때문에 java까지 넘길 수 없다.

그럼 우리가 확인할 것은 비번 일치하지 않을 때 색 변화와 일치하지 않을 때 validateForm함수가 잘 작동하는지, 모든 조건이 다 맞을 때 올바르게 db에 들어가는지 이다. test3으로 만들 것이고 처음엔 비번 일치하지 않게 만들어보겠다.

함수 잘 작동하고, 색도 일치하지 않으니까 빨간색으로 표시한다.

일치할 때는,

잘뜨고 회원가입을 누르면, 회원가입 완료되었습니다 알림까지 잘 뜬다.

로그인도 잘 되고, DB상으로도,


김영희씨가 잘 들어왔다.

글 작성까지 문제없이 된다!

일단 게시물은 여기까지 하겠다.