[클론 코딩] 네이버 카페 - 카페 회원정보 수정

2024. 5. 26. 17:50Project/Naver Cafe

  카페 회원이 자신의 정보를 수정할 수 있는 기능을 구현한다. 회원정보 수정을 요청하면 수정할 정보를 입력할 수 있는 수정 양식이 사용자에게 전달되고 사용자가 확인(적용)을 누르면 해당 정보가 DB에 반영되는 기능이 될 것이다.


CafeMember

package CloneCoding.NaverCafe.domain.cafeMember;

import static CloneCoding.NaverCafe.domain.cafeMember.enums.CafeMemberPosition.*;

@Entity
@Table(name = "CAFE_MEMBER", uniqueConstraints = {
        @UniqueConstraint(
                name = "ACCOUNT_ID_UNIQUE",
                columnNames = {"CAFE_ID", "ACCOUNT_ID"}
        ),
        @UniqueConstraint(
                name = "USERNAME_UNIQUE",
                columnNames = {"CAFE_ID", "NICKNAME"}
        )})
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class CafeMember {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Column(name = "ACCOUNT_ID")
    private String accountId;

    @Column(name = "NICKNAME")
    private String nickname;

    @Column(name = "PROFILE_IMAGE")
    @Builder.Default
    private String profileImage = "default_image";

    @Column(name = "DESCRIPTION")
    @Builder.Default
    private String description = "자기소개를 입력해주세요";

    @Column(name = "GENDER_AGE_OPEN")
    @Builder.Default
    private boolean genderAgeOpen = true;

    @Column(name = "MY_BLOG_OPEN")
    @Builder.Default
    private boolean myBlogOpen = false;

    @Column(name = "POPULAR_MEMBER_PUSH")
    @Builder.Default
    private boolean popularMemberPush = true;

    @Column(name = "GENDER")
    private String gender;

    @Column(name = "BIRTHDAY")
    private LocalDate birthday;

    @Column(name = "POSITION")
    @Builder.Default
    private String position = CAFE_MEMBER.name();

    @Column(name = "GRADE")
    @Builder.Default
    private String grade = "-";

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CAFE_ID")
    private Cafe cafeId;

    public void update(RequestUpdateCafeMember request) {

        this.profileImage = request.getProfileImage();

        if (!request.getNickname().isEmpty()) {
            this.nickname = request.getNickname();
        }

        this.description = request.getDescription();
        this.genderAgeOpen = request.isOpenSetting();
        this.myBlogOpen = request.isMyBlogOpen();
        this.popularMemberPush = request.isPopularMemberPush();

    }

}

 

  카페 회원 엔티티 클래스이다. 카페 회원을 DB에 저장할 때 필요한 정보들을 가지고 있다. 특이사항으로는 해당 클래스와 매핑된 테이블은 CAFE 테이블과 ManyToOne 연관관계를 가지는데 덕분에 카페 별로 계정ID, 별명에 대해 중복을 방지하려면 복합키를 사용해 유니크 설정을해 줄 필요가 있었다. @Table의 uniqueConstraints에 @UniqueConstraint으로 유니크키(들)을 설정해준다. 결과적으로 cafeId와 accountId(or nickname) 둘 모두 같은 레코드가 있다면 DB 반영을 막고 없다면 반영하는 식의 유니크 설정이 적용되었다.

  • update() : CafeMember 객체를 전달받은 request 정보로 수정하는 메서드

ResponseUpdateForm

package CloneCoding.NaverCafe.domain.cafeMember.dto;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResponseUpdateForm {

    private String profileImage;

    private String nickname;

    private String description;

    private boolean genderAgeOpen;

    private boolean myBlogOpen;

    private boolean popularMemberPush;

}

 

  사용자가 회원정보를 양식을 통해 수정할 때 제공될 데이터를 전달하는 DTO이다. 기존 카페 회원의 프로필 이미지, 별명, 자기소개, 각종 설정을 전달한다. 사용자는 수정하기 전 자신의 현재 정보를 확인할 수 있을 것이다.


RequestUpdateCafeMember

package CloneCoding.NaverCafe.domain.cafeMember.dto;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class RequestUpdateCafeMember {

    @NotNull
    private String profileImage;

    @NotNull
    @Size(min = 2, max = 20, message = "별명은 2 ~ 20자로 제한됩니다.")
    @Pattern(regexp = "^[a-zA-Z0-9]+", message = "별명은 영소문자, 영대문자, 숫자만 입력 가능합니다.")
    private String nickname;

    @NotNull
    private String description;

    @NotNull
    private boolean openSetting;

    @NotNull
    private boolean myBlogOpen;

    @NotNull
    private boolean popularMemberPush;

}

 

  사용자는 수정양식에 따라 데이터를 입력해 서버에 수정을 요청할 텐데 그때 사용자의 요청 데이터를 전달하는 DTO이다. 모든 필드는 null 값을 허용하지 않으며 그 중 nickname은 제약조건을 갖는다.


CafeMemberController

package CloneCoding.NaverCafe.domain.cafeMember.controller;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/{cafe_url}")
public class CafeMemberController {

    private final CafeMemberService cafeMemberService;

    @GetMapping("/update")
    public ResponseUpdateForm getUpdateForm(@PathVariable("cafe_url") String url,
                                            @RequestHeader("Authorization") String token) {
        log.info("카페 회원정보 수정 양식 요청");
        return cafeMemberService.createUpdateForm(url, token);
    }

    @PutMapping("/update")
    public String updateCafeMember(@PathVariable("cafe_url") String url,
                                   @RequestBody @Valid RequestUpdateCafeMember request,
                                   @RequestHeader("Authorization") String token) {
        log.info("카페 회원정보 수정 요청");
        return cafeMemberService.updateCafeMember(url, request, token);
    }

}

 

  • getUpdateForm() : cafeMemberService의 createUpdateForm() 메서드를 호출하고 결과를 반환
  • updateCafeMember() : cafeMemberService의 updateCafeMember() 메서드를 호출하고 결과를 반환

CafeMemberServiceImpl

package CloneCoding.NaverCafe.domain.cafeMember.service;

@Service
@RequiredArgsConstructor
public class CafeMemberServiceImpl implements CafeMemberService {

    private final CafeMemberRepository cafeMemberRepository;
    private final CafeRepository cafeRepository;
    private final MemberRepository memberRepository;
    private final AesUtil aesUtil;

    @Override
    public ResponseUpdateForm createUpdateForm(String url, String token) {

        Cafe findCafe = checkCafe(url);
        CafeMember findCafeMember = checkCafeMember(findCafe, token);

        return ResponseUpdateForm.builder()
                .profileImage(findCafeMember.getProfileImage())
                .nickname(findCafeMember.getNickname())
                .description(findCafeMember.getDescription())
                .genderAgeOpen(findCafeMember.isGenderAgeOpen())
                .myBlogOpen(findCafeMember.isMyBlogOpen())
                .popularMemberPush(findCafeMember.isPopularMemberPush())
                .build();
    }

    @Override
    public String updateCafeMember(String url, RequestUpdateCafeMember request, String token) {

        Cafe findCafe = checkCafe(url);
        CafeMember findCafeMember = checkCafeMember(findCafe, token);

        findCafeMember.update(request);

        cafeMemberRepository.save(findCafeMember);

        return UPDATE_CAFE_MEMBER_INFO.getMessage();

    }

    private Cafe checkCafe(String url) {
        return cafeRepository.findByUrl(url);
    }

    private CafeMember checkCafeMember(Cafe cafe, String token) {
        String accountId = aesUtil.aesDecode(token);
        return cafeMemberRepository.findByAccountId(cafe, accountId);
    }

}

 

  • createUpdateForm() : 회원정보 수정을 요청한 회원의 정보로 ResponseUpdateForm 객체를 생성해 반환
  • updateCafeMember() : 회원이 요청한 정보로 기존 회원의 정보를 수정, 엔티티 저장 후에 결과 메시지 반환
  • checkCafe() : findByUrl() 메서드를 호출해 결과 값을 반환
  • chekcCafeMember() : 토큰을 복호화해 얻은 계정ID로 카페회원을 조회 후, 조회한 CafeMember 객체 반환

QueryCafeMemberRepository

package CloneCoding.NaverCafe.domain.cafeMember.repository;

@Repository
@RequiredArgsConstructor
public class QueryCafeMemberRepositoryImpl implements QueryCafeMemberRepository {

    private final JPAQueryFactory query;

    @Override
    public CafeMember findByAccountId(Cafe cafe, String accountId) {
        return Optional.ofNullable(query
                        .selectFrom(cafeMember)
                        .where(cafeMember.cafeId.eq(cafe), cafeMember.accountId.eq(accountId))
                        .fetchOne())
                .orElseThrow(() -> new NoSuchElementException("해당 계정 정보를 가진 카페 회원이 존재하지 않습니다."));
    }

}

 

  • findByAccountId() : accountId를 가지고 CAFE_MEMBER 테이블에서 일치하는 데이터를 조회 후 반환

API TEST

API TEST - 카페 회원정보 수정 양식 호출

 

  카페 회원정보 수정 요청시 사용자에게 전달될 현재 회원의 정보이다. 사용자는 이를 확인하고 수정할 내용을 입력할 것이다.

 

API TEST - 카페 회원정보 수정
API TEST - 카페 회원정보 수정 후 양식 호출

 

  사용자가 입력한 정보로 회원정보를 수정한 결과이다. 수정 이후 완료 메시지가 정상 출력되며, 다시 한 번 수정 양식을 호출하니 변경된 정보가 전달되는 것을 확인할 수 있다.