본문 바로가기

Dot Programming/Spring Clone

[스프링 웹앱 프로젝트 #31] 닉네임 수정

31. 닉네임 수정

> 닉네임은 특정 패턴(^[ㄱ-ㅎ가-힣a-z0-9_-]{3,20}$)의 문자열만 지원함 
> 중복 닉네임 확인

 

이또한 반복작업...

 

NicknameForm과 Validator 생성

NicknameForm.java

@Data
public class NicknameForm {

    @NotBlank
    @Length(min =3, max =20)
    @Pattern(regexp = "^[ㄱ-ㅎ가-힣a-z0-9_-]{3,20}$")
    private String nickname;
}

 

 

NicknameValidator.java

@Component
@RequiredArgsConstructor
public class NicknameValidator implements Validator {

    private final AccountRepository accountRepository;

    @Override
    public boolean supports(Class<?> clazz) {
        return NicknameForm.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        NicknameForm nicknameForm = (NicknameForm) target;
        Account byNickname = accountRepository.findByNickname(nicknameForm.getNickname());
        if(byNickname != null){
            errors.rejectValue("nickname", "wrong.value", "입력하신 닉네임을 사용할 수 없습니다.");
        }
    }
}

 

 

Controller단에서 닉네임 변경 액션 처리

@Controller
@RequiredArgsConstructor
public class SettingsController {

    static final String SETTINGS_ACCOUNT_VIEW_NAME = "settings/account";
    static final String SETTINGS_ACCOUNT_URL = "/" + SETTINGS_ACCOUNT_VIEW_NAME;
    
    // ....
    
    private final NicknameValidator nicknameValidator;

 	// ...
    
    
    @InitBinder("nicknameForm")
    public void nicknameFormInitBinder(WebDataBinder webDataBinder) {
        webDataBinder.addValidators(nicknameValidator);
    }

	// ...

    /**
     * 닉네임설정
      */
    @GetMapping(SETTINGS_ACCOUNT_URL)
    public String updateAccountForm(@CurrentUser Account account, Model model) {
        model.addAttribute(account);
        model.addAttribute(modelMapper.map(account, NicknameForm.class));
        return SETTINGS_ACCOUNT_VIEW_NAME;
    }

    @PostMapping(SETTINGS_ACCOUNT_URL)
    public String updateAccount(@CurrentUser Account account, @Valid NicknameForm nicknameForm, Errors errors,
                                Model model, RedirectAttributes attributes) {
        if (errors.hasErrors()) {
            model.addAttribute(account);
            return SETTINGS_ACCOUNT_VIEW_NAME;
        }

        accountService.updateNickname(account, nicknameForm.getNickname());
        attributes.addFlashAttribute("message", "닉네임을 수정했습니다.");
        return "redirect:" + SETTINGS_ACCOUNT_URL;
    }
}

 

 

Service단에 영속성 유지되는 updateNickname()메소드 생성

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class AccountService implements UserDetailsService {

   //...

    public void updateNickname(Account account, String nickname) {
        account.setNickname(nickname);
        accountRepository.save(account);
        login(account);
    }
}

*닉네임 변경 후 로그인을 다시 해줘야지만 변경이 적용된다

 

 

Front View만들기

settings/account.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html :: head"></head>
<body class="bg-light">
    <nav th:replace="fragments.html :: main-nav"></nav>
    <div class="container">
        <div class="row mt-5 justify-content-center">
            <div class="col-2">
                <div th:replace="fragments.html :: settings-menu(currentMenu='account')"></div>
            </div>
            <div class="col-8">
                <div th:if="${message}" class="alert alert-info alert-dismissible fade show mt-3" role="alert">
                    <span th:text="${message}">완료</span>
                    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="row">
                    <h2 class="col-12">닉네임 변경</h2>
                </div>
                <div class="row">
                    <form class="needs-validation col-12" th:object="${nicknameForm}" th:action="@{/settings/account}" method="post" novalidate>
                        <div class="alert alert-warning" role="alert">
                            닉네임을 변경하면 프로필 페이지 링크도 바뀝니다!
                        </div>
                        <div class="form-group">
                            <input id="nickname" type="text" th:field="*{nickname}" class="form-control" aria-describedby="nicknameHelp" required>
                            <small id="nicknameHelp" class="form-text text-muted">
                                공백없이 문자와 숫자로만 3자 이상 20자 이내로 입력하세요. 가입후에 변경할 수 있습니다.
                            </small>
                            <small class="invalid-feedback">닉네임을 입력하세요.</small>
                            <small class="form-text text-danger" th:if="${#fields.hasErrors('nickname')}" th:errors="*{nickname}">nickname Error</small>
                        </div>
                        <div class="form-group">
                            <button class="btn btn-outline-primary" type="submit" aria-describedby="submitHelp">변경하기</button>
                        </div>
                    </form>
                </div>
                <div class="dropdown-divider"></div>
                <div class="row">
                    <div class="col-sm-12">
                        <h2 class="text-danger">계정 삭제</h2>
                        <div class="alert alert-danger" role="alert">
                            이 계정은 삭제할 수 없습니다.
                        </div>
                        <button class="btn btn-outline-danger disabled">계정 삭제</button>
                    </div>
                </div>
            </div>
        </div>

        <div th:replace="fragments.html :: footer"></div>
    </div>
    <script th:replace="fragments.html :: form-validation"></script>
</body>
</html> 

 

 

 

실행결과

 

기존 닉네임 loosie10

 

닉네임 변경 성공

 

닉네임 수정이 완료된 것을 볼 수있다. 닉네임이 수정되면서 프로필 기본 아이콘도 변경이 된다.

loosie99로 닉네임 변경 (아이콘도 바뀜)

 

 

 

마지막으로 테스트코드 작성!

@WithAccount("loosie")
    @DisplayName("닉네임 수정 폼")
    @Test
    void updateAccountForm() throws Exception {
        mockMvc.perform(get(SettingsController.SETTINGS_ACCOUNT_URL))
                .andExpect(status().isOk())
                .andExpect(model().attributeExists("account"))
                .andExpect(model().attributeExists("nicknameForm"));
    }

    @WithAccount("loosie")
    @DisplayName("닉네임 수정하기 - 입력값 정상")
    @Test
    void updateAccount_success() throws Exception {
        String newNickname = "loosi123";
        mockMvc.perform(post(SettingsController.SETTINGS_ACCOUNT_URL)
                .param("nickname", newNickname)
                .with(csrf()))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl(SettingsController.SETTINGS_ACCOUNT_URL))
                .andExpect(flash().attributeExists("message"));

        assertNotNull(accountRepository.findByNickname("loosi123"));
    }

    @WithAccount("loosie")
    @DisplayName("닉네임 수정하기 - 입력값 에러")
    @Test
    void updateAccount_failure() throws Exception {
        String newNickname = "¯\\_(ツ)_/¯";
        mockMvc.perform(post(SettingsController.SETTINGS_ACCOUNT_URL)
                .param("nickname", newNickname)
                .with(csrf()))
                .andExpect(status().isOk())
                .andExpect(view().name(SettingsController.SETTINGS_ACCOUNT_VIEW_NAME))
                .andExpect(model().hasErrors())
                .andExpect(model().attributeExists("account"))
                .andExpect(model().attributeExists("nicknameForm"));
    }

테스트 통과

 

 


참고

인프런 강의 - 스프링과 JPA 기반 웹 애플리케이션 개발