[Practice] 사용자 관련 API 만들기 (6)

2021. 4. 18. 23:44Spring/Practice

1. 문제

  • 회원 로그인 히스토리 기능을 구현하는 API

 

 

 

2. 풀이

- schema.sql

...

create table LOGS
(
    ID          BIGINT auto_increment primary key,
    TEXT        CLOB,
    REG_DATE    TIMESTAMP
);

- ApiLoginController.java

package com.example.jpa.sample.user.controller;

import com.example.jpa.sample.common.exception.BizException;
import com.example.jpa.sample.common.model.ResponseResult;
import com.example.jpa.sample.notice.model.ResponseError;
import com.example.jpa.sample.user.entity.User;
import com.example.jpa.sample.user.model.UserLogin;
import com.example.jpa.sample.user.model.UserLoginToken;
import com.example.jpa.sample.user.service.UserService;
import com.example.jpa.sample.util.JwtUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RequiredArgsConstructor
@RestController
public class ApiLoginController {

    private final UserService userService;

    // 문제 6
    @PostMapping("/api/login")
    public ResponseEntity<?> login(@RequestBody @Valid UserLogin userLogin, Errors errors) {

        if(errors.hasErrors()) {
            return ResponseResult.fail("입력값이 정확하지 않습니다.", ResponseError.of(errors.getAllErrors()));
        }

        User user = null;

        try {
            user = userService.login(userLogin);
        } catch(BizException e) {
            return ResponseResult.fail(e.getMessage());
        }

        UserLoginToken userLoginToken = JwtUtils.createToken(user);
        if(userLoginToken == null) {
            return ResponseResult.fail("JWT 생성에 실패하였습니다.");
        }

        return ResponseResult.success(userLoginToken);
    }
}

- UserService.java

package com.example.jpa.sample.user.service;

import com.example.jpa.sample.board.model.ServiceResult;
import com.example.jpa.sample.user.entity.User;
import com.example.jpa.sample.user.model.UserLogCount;
import com.example.jpa.sample.user.model.UserLogin;
import com.example.jpa.sample.user.model.UserNoticeCount;
import com.example.jpa.sample.user.model.UserSummary;

import java.util.List;

public interface UserService {

    ...

    User login(UserLogin userLogin);
}

- UserServiceImpl.java

package com.example.jpa.sample.user.service;

import com.example.jpa.sample.board.model.ServiceResult;
import com.example.jpa.sample.common.exception.BizException;
import com.example.jpa.sample.user.entity.User;
import com.example.jpa.sample.user.entity.UserInterest;
import com.example.jpa.sample.user.model.*;
import com.example.jpa.sample.user.repository.UserCustomRepository;
import com.example.jpa.sample.user.repository.UserInterestRepository;
import com.example.jpa.sample.user.repository.UserRepository;
import com.example.jpa.sample.util.PasswordUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@RequiredArgsConstructor
@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final UserCustomRepository userCustomRepository;
    private final UserInterestRepository userInterestRepository;

    ...

    @Override
    public User login(UserLogin userLogin) {

        Optional<User> optionalUser = userRepository.findByEmail(userLogin.getEmail());
        if(!optionalUser.isPresent()) {
            throw new BizException("회원 정보가 존재하지 않습니다.");
        }
        User user = optionalUser.get();

        if(PasswordUtils.equalPassword(userLogin.getPassword(), user.getPassword())) {
            throw new BizException("일치하는 정보가 없습니다."); // 정보 자체를 정확히 안알려주는 것도 보안의 일종
        }

        return user;
    }
}

- ApiLoginController.java

package com.example.jpa.sample.user.controller;

import com.example.jpa.sample.common.exception.BizException;
import com.example.jpa.sample.common.model.ResponseResult;
import com.example.jpa.sample.notice.model.ResponseError;
import com.example.jpa.sample.user.entity.User;
import com.example.jpa.sample.user.model.UserLogin;
import com.example.jpa.sample.user.model.UserLoginToken;
import com.example.jpa.sample.user.service.UserService;
import com.example.jpa.sample.util.JwtUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RequiredArgsConstructor
@RestController
public class ApiLoginController {

    private final UserService userService;

    // 문제 6
    @PostMapping("/api/login")
    public ResponseEntity<?> login(@RequestBody @Valid UserLogin userLogin, Errors errors) {

        if(errors.hasErrors()) {
            return ResponseResult.fail("입력값이 정확하지 않습니다.", ResponseError.of(errors.getAllErrors()));
        }

        User user = null;

        try {
            user = userService.login(userLogin);
        } catch(BizException e) {
            return ResponseResult.fail(e.getMessage());
        }

        UserLoginToken userLoginToken = JwtUtils.createToken(user);
        if(userLoginToken == null) {
            return ResponseResult.fail("JWT 생성에 실패하였습니다.");
        }

        return ResponseResult.success(userLoginToken);
    }
}

- MainApplication.java

package com.example.jpa.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@EnableAspectJAutoProxy // aop 동작 설정
@SpringBootApplication
public class SampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }

}

- Logs.java

package com.example.jpa.sample.logs.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Logs {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String text;

    @Column
    private LocalDateTime regDate;
}

- LogService.java

package com.example.jpa.sample.logs.service;

public interface LogService {

    void add(String text);
}

- LogServiceImpl.java

package com.example.jpa.sample.logs.service;

import com.example.jpa.sample.logs.entity.Logs;
import com.example.jpa.sample.logs.repository.LogsRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@RequiredArgsConstructor
@Service
public class LogServiceImpl implements LogService {

    private final LogsRepository logsRepository;

    @Override
    public void add(String text) {

        logsRepository.save(Logs.builder()
                .text(text)
                .regDate(LocalDateTime.now())
                .build());
    }
}

- LogsRepository.java

package com.example.jpa.sample.logs.repository;

import com.example.jpa.sample.logs.entity.Logs;
import org.springframework.data.jpa.repository.JpaRepository;

public interface LogsRepository extends JpaRepository<Logs, Long> {
}

- UserLogin.java (@ToString 어노테이션 추가)

package com.example.jpa.sample.user.model;

import lombok.*;

import javax.validation.constraints.NotBlank;

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class UserLogin {

    @NotBlank(message = "이메일 항목은 필수입니다.")
    private String email;

    @NotBlank(message = "비밀번호 항목은 필수입니다.")
    private String password;
}

- LoginLogger.java

package com.example.jpa.sample.common.aop;

import com.example.jpa.sample.logs.service.LogService;
import com.example.jpa.sample.user.entity.User;
import com.example.jpa.sample.user.model.UserLogin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j // 로그 찍어보는 용도로 사용
@RequiredArgsConstructor
@Aspect
@Component
public class LoginLogger {

    private final LogService logService;

    @Around("execution(* com.example.jpa.sample..*.*Service*.*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {

        log.info("#################################");
        log.info("서비스 호출 전");

        Object result = joinPoint.proceed();

//        joinPoint.getArgs() : 서비스 호출시 넘기는 파라미터
//        joinPoint.getSignature() : 메시지 정보 (ex. 메서드의 경우 getDeclaringTypeName(), getName() 사용)

        if("login".equals(joinPoint.getSignature().getName())) {

            StringBuilder sb = new StringBuilder();

            sb.append("\n");
            sb.append("함수명: " + joinPoint.getSignature().getDeclaringTypeName() + ", " + joinPoint.getSignature().getName());
            sb.append("\n");
            sb.append("매개변수: ");

            Object[] args = joinPoint.getArgs();
            if(args != null && args.length > 0) {
                for(Object o : args) {
                    if(o instanceof UserLogin) {
//                        String email = ((UserLogin)o).getEmail();
//                        String password = ((UserLogin)o).getPassword();
                        sb.append(((UserLogin)o).toString()); // @ToString

                        sb.append("리턴값: " + ((User)result).toString());
                    }
                }
                logService.add(sb.toString());
                log.info(sb.toString());
            }
        }

        log.info("#################################");
        log.info("서비스 호출 후");

        return result;
    }
}
728x90