히스토리 기능 구현
Q1) 회원 로그인, 히스토리 기능을 구현하는 API를 작성해 보세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| /**
* 로그인 구현
*/
@RequiredArgsConstructor
@RestController
public class ApiLoginController {
private final MemberService memberService;
@PostMapping("/api/login")
public ResponseEntity<?> login(@RequestBody @Valid MemberLogin memberLogin, Errors errors) {
if (errors.hasErrors()) {
return ResponseResult.fail("입력값이 정확하지 않습니다", ResponseError.of(errors.getAllErrors()));
}
Member member = null;
try {
member = memberService.login(memberLogin);
} catch (BizException e) {
return ResponseResult.fail(e.getMessage());
}
MemberLoginToken memberLoginToken = JWTUtils.createToken(member);
if(memberLoginToken == null){
return ResponseResult.fail("JWT 생성에 실패하였습니다.");
}
return ResponseResult.success(memberLoginToken);
}
}
public interface MemberService {
Member login(MemberLogin memberLogin);
}
@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Override
public Member login(MemberLogin memberLogin) {
Optional<Member> optionalMember = memberRepository.findByEmail(memberLogin.getEmail());
if (optionalMember.isEmpty()) {
throw new BizException("회원 정보가 존재하지 않습니다.");
}
Member member = optionalMember.get();
if (!PasswordUtils.equalPassword(memberLogin.getPassword(), member.getPassword())) {
throw new BizException("일치하는 정보가 없습니다.");
}
return member;
}
}
/**
* 로그인 히스토리 구현
*/
public interface LogService {
void add(String text);
}
@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());
}
}
|
Q2) AOP의 Around를 이용하여 게시판 상세 조회에 대한 히스토리를 기록하는 기능을 작성해 보세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| @RestController
@RequiredArgsConstructor
public class ApiBoardController {
private final BoardService boardService;
@GetMapping("/api/board/{id}")
public ResponseEntity<?> detail(@PathVariable Long id) {
Board board = null;
try {
board = boardService.detail(id);
} catch (BizException e) {
return ResponseResult.fail(e.getMessage());
}
return ResponseResult.success(board);
}
}
public interface BoardService {
Board detail(Long id);
}
public class BoardServiceImpl implements BoardService {
private final BoardRepository boardRepository;
@Override
public Board detail(Long id) {
Optional<Board> optionalBoard = boardRepository.findById(id);
if (optionalBoard.isEmpty()) {
throw new BizException("게시글이 존재하지 않습니다.");
}
return optionalBoard.get();
}
}
|
Entity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| @Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
public class Logs {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column private String text;
@Column private LocalDateTime regDate;
}
|
Repository
1
2
3
| @Repository
public interface LogsRepository extends JpaRepository<Logs,Long> {
}
|
Model
1
2
3
4
5
6
7
8
9
10
11
| @Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberLogin {
@NotBlank(message = "이메일 항목은 필수 입니다.")
private String email;
@NotBlank(message = "비밀번호 항목은 필수 입니다.")
private String password;
}
|
Common
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
| @Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ServiceResult {
private boolean result;
private String message;
public static ServiceResult fail(String message) {
return ServiceResult.builder()
.result(false)
.message(message)
.build();
}
public static ServiceResult success() {
return ServiceResult.builder()
.result(true)
.build();
}
public boolean isFail() {
return !result;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ResponseMessage {
private ResponseMessageHeader header;
private Object body;
public static ResponseMessage fail(String message, Object data) {
return ResponseMessage.builder()
.header(ResponseMessageHeader.builder()
.result(false)
.resultCode("")
.message(message)
.status(HttpStatus.BAD_REQUEST.value())
.build())
.body(data).build();
}
public static ResponseMessage fail(String message) {
return fail(message, null);
}
public static ResponseMessage success(Object data) {
return ResponseMessage.builder()
.header(ResponseMessageHeader.builder()
.result(true)
.resultCode("")
.message("")
.status(HttpStatus.OK.value())
.build())
.body(data).build();
}
public static ResponseMessage success() {
return success(null);
}
}
public class ResponseResult {
public static ResponseEntity<?> fail(String message) {
return fail(message, null);
}
public static ResponseEntity<?> fail(String message, Object data) {
return ResponseEntity.badRequest().body(ResponseMessage.fail(message, data));
}
public static ResponseEntity<?> success() {
return success(null);
}
public static ResponseEntity<?> success(Object data) {
return ResponseEntity.ok().body(ResponseMessage.success(data));
}
public static ResponseEntity<?> result(ServiceResult result) {
if (result.isFail()) {
return fail(result.getMessage());
}
return success();
}
}
|
Util
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| @UtilityClass
public class JWTUtils {
private static final String KEY = "zeroBase";
private static final String CLAIM_MEMBER_ID = "member_id";
public static MemberLoginToken createToken(Member member) {
if (member == null) {
return null;
}
LocalDateTime expiredDateTime = LocalDateTime.now().plusWeeks(1);
Date expiredDate = java.sql.Timestamp.valueOf(expiredDateTime);
String token = JWT.create()
.withExpiresAt(expiredDate)
.withClaim(CLAIM_MEMBER_ID, member.getId())
.withSubject(member.getMemberName())
.withIssuer(member.getEmail())
.sign(Algorithm.HMAC512(KEY.getBytes()));
return MemberLoginToken.builder()
.token(token)
.build();
}
public static String getIssuer(String token) {
return JWT.require(Algorithm.HMAC512(KEY.getBytes()))
.build()
.verify(token)
.getIssuer();
}
}
@UtilityClass
public class PasswordUtils {
// 패스워드를 암호화해서 리턴하는 함수
public static String encryptedPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt());
}
// 입력한 패스워드를 해시된 패스워드랑 비교하는 함수
public static boolean equalPassword(String password, String encryptedPassword) {
try {
return BCrypt.checkpw(password, encryptedPassword);
} catch (IllegalArgumentException e) {
return false;
}
}
}
|
AOP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| @Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LoginLogger {
private final LogService logService;
@Around("execution(* org.example.jpa..*.*Service*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
// 로그인에서만 캐치
if ("login".equals(joinPoint.getSignature().getName())) {
StringBuilder sb = new StringBuilder();
sb.append("\n")
.append("함수명: ").append(joinPoint.getSignature().getDeclaringType())
.append(", ").append(joinPoint.getSignature().getName())
.append("\n")
.append("매개변수: ")
.append(Arrays.toString(joinPoint.getArgs()))
.append("\n")
.append("리턴값 : ")
.append(result.toString())
.append("\n");
logService.add(sb.toString());
}
return result;
}
}
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class BoardLogger {
private final LogService logService;
@Around("execution(* org.example.jpa..*.*Controller.detail(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Controller Detail Before");
Object result = joinPoint.proceed();
if (joinPoint.getSignature().getDeclaringTypeName().contains("ApiBoardController")
&& "detail".equals(joinPoint.getSignature().getName())) {
StringBuilder sb = new StringBuilder();
sb.append("파라미터:");
Object[] args = joinPoint.getArgs();
for (Object x : args) {
sb.append(x.toString());
}
sb.append(result.toString());
logService.add(sb.toString());
log.info(sb.toString());
}
log.info("Controller Detail After");
return result;
}
}
|
API 활용
1. 공공API 활용
Q1) 공공 데이터포털(http://data.go.kr)의 전국약국목록을 가져오는 API를 작성해 보세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
| /**
* Model
* (API의 JSON 구조를 따라서 Model 생성)
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OpenApiResultResponse {
private OpenApiResultResponseHeader header;
private OpenApiResultResponseBody body;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OpenApiResultResponseHeader {
private String resultCode;
private String resultMsg;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OpenApiResultResponseBody {
private OpenApiResultResponseBodyItems items;
private int numOfRows;
private int pageNo;
private int totalCount;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OpenApiResultResponseBodyItems {
List<OpenApiResultResponseBodyItem> item;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OpenApiResultResponseBodyItem {
private String dutyAddr;
private String dutyInf;
private String dutyFax;
private String dutyName;
private String dutyTel1;
private int dutyTime1c;
private String dutyTime1s;
private int dutyTime2c;
private String dutyTime2s;
private int dutyTime3c;
private String dutyTime3s;
private int dutyTime4c;
private String dutyTime4s;
private int dutyTime5c;
private String dutyTime5s;
private int dutyTime6c;
private String dutyTime6s;
private int dutyTime7c;
private String dutyTime7s;
private int dutyTime8c;
private String dutyTime8s;
private String hpid;
private String postCdn1;
private String postCdn2;
private int rnum;
private double wgs84Lat;
private double wgs84Lon;
}
// 검색용 Model
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PharmacySearch {
private String sido;
private String gugun;
public String getSearchSido() {
return sido != null ? sido : "";
}
public String getSearchGugun() {
return gugun != null ? gugun : "";
}
}
/**
* Controller
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class ApiExtraController {
public static final String API_KEY = "";
@GetMapping("/api/extra/pharmacy")
public ResponseEntity<?> pharmacy(@RequestBody PharmacySearch pharmacySearch) {
String url = String.format("https://apis.data.go.kr/B552657/ErmctInsttInfoInqireService/getParmacyFullDown?serviceKey=%s&pageNo=1&numOfRows=10", API_KEY);
String apiResult = "";
try {
url += String.format("&Q0=%s&Q1=%s",
URLEncoder.encode(pharmacySearch.getSearchSido(), StandardCharsets.UTF_8),
URLEncoder.encode(pharmacySearch.getSearchGugun(), StandardCharsets.UTF_8));
URI uri = new URI(url);
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
apiResult = restTemplate.getForObject(uri, String.class);
} catch (Exception e) {
log.debug(e.getMessage());
}
OpenApiResult jsonResult = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
jsonResult = objectMapper.readValue(apiResult, OpenApiResult.class);
} catch (JsonProcessingException e) {
log.debug(e.getMessage());
}
return ResponseResult.success(jsonResult);
}
}
|
Q2) 미세먼지 정보 조회(공공 API)를 통해서 내용을 내리는 API를 작성해 보세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| /**
* Model
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AirInput {
private String sido;
private String getSearchSido() {
return sido != null ? sido : "";
}
}
/**
* Controller
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class ApiExtraController {
public static final String API_KEY = "";
@GetMapping("/api/extra/air")
public String air(@RequestBody AirInput airInput){
String url = "https://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty?serviceKey=%s&pageNo=1&sidoName=%s&ver=1.0";
String apiResult = "";
try {
URI uri = new URI(String.format(url,API_KEY,URLEncoder.encode(airInput.getSearchSido(), StandardCharsets.UTF_8)));
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
apiResult = restTemplate.getForObject(uri, String.class);
} catch (Exception e) {
log.debug(e.getMessage());
}
return apiResult;
}
}
|
2. 카카오API 활용
카카오 API를 활용한 게시글 번역 서비스를 구현하는 API를 작성해 보세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| /*
* Model
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class KakaoTranslateInput {
private String text;
}
/*
* Controller
*/
@RestController
@RequiredArgsConstructor
public class ApiExtraKakaoController {
public static final String API_KEY = "";
@GetMapping("/api/extra/kakao/translate")
public ResponseEntity<?> translate(@RequestBody KakaoTranslateInput kakaoTranslateInput) {
String url = "https://dapi.kakao.com/v2/translation/translate";
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> parameter = new LinkedMultiValueMap<>();
parameter.add("src_lang", "kr");
parameter.add("target_lang", "en");
parameter.add("query", kakaoTranslateInput.getText());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add("Authorization", "KakaoAK " + API_KEY);
HttpEntity formEntity = new HttpEntity<>(parameter, headers);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, formEntity, String.class);
return ResponseResult.success(responseEntity.getBody());
}
}
|
3. 인터셉터 활용
Q1) 인터셉터를 이용하여 API요청에 대한 정보를 log에 기록하는 기능을 작성해 보세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| public interface BoardService {
List<Board> list();
}
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService {
private final BoardRepository boardRepository;
@Override
public List<Board> list() {
return boardRepository.findAll();
}
}
@RestController
@RequiredArgsConstructor
public class ApiBoardController {
private final BoardService boardService;
@GetMapping("/api/board")
public ResponseEntity<?> list() {
List<Board> list = boardService.list();
return ResponseResult.success(list);
}
}
@Slf4j
public class CommonInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("############################");
log.info("[인터셉터] - preHandler Start");
log.info("############################");
log.info(request.getMethod());
log.info(request.getRequestURI());
return true;
}
}
|
Q2) 인터셉터를 활용하여 JWT 인증이 필요한 API에 대해서 토큰 유효성을 검증하는 API를 작성해 보세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
| /**
* 게시글쓰기 기능구현
* 글쓰기 API 호출시 토큰 유효성 검사
*/
public interface BoardService {
ServiceResult add(String email, BoardInput boardInput);
}
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService {
private final BoardRepository boardRepository;
private final MemberRepository memberRepository;
private final BoardTypeRepository boardTypeRepository;
@Override
public ServiceResult add(String email, BoardInput boardInput) {
Optional<Member> optionalMember = memberRepository.findByEmail(email);
if (optionalMember.isEmpty()) {
throw new BizException("회원 정보가 존재하지 않습니다.");
}
Member member = optionalMember.get();
Optional<BoardType> optionalBoardType = boardTypeRepository.findById(boardInput.getBoardType());
if (optionalBoardType.isEmpty()) {
return ServiceResult.fail("게시판 정보가 존재하지 않습니다.");
}
BoardType boardType = optionalBoardType.get();
boardRepository.save(Board.builder()
.member(member)
.title(boardInput.getTitle())
.contents(boardInput.getContents())
.boardType(boardType)
.regDate(LocalDateTime.now())
.build());
return ServiceResult.success();
}
}
@RestController
@RequiredArgsConstructor
public class ApiBoardController {
private final BoardService boardService;
@PostMapping("/api/board")
public ResponseEntity<?> add
(@RequestHeader("Z-TOKEN") String token,
@RequestBody BoardInput boardInput) {
String email = JWTUtils.getIssuer(token);
ServiceResult result = boardService.add(email, boardInput);
return ResponseResult.success(result);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BoardInput {
private Long boardType;
private String title;
private String contents;
}
public class CommonInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!validJWT(request)) {
throw new AuthFailException("인증정보가 정확하지 않습니다.");
}
return true;
}
private boolean validJWT(HttpServletRequest request) {
String token = request.getHeader("Z-TOKEN");
String email = "";
try {
email = JWTUtils.getIssuer(token);
} catch (JWTVerificationException e) {
return false;
}
return true;
}
}
public class AuthFailException extends RuntimeException {
public AuthFailException(String message) {
super(message);
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AuthFailException.class)
public ResponseEntity<?> authFailException(AuthFailException e) {
return ResponseResult.fail("[인증실패] "+e.getMessage());
}
}
|
Config
1
2
3
4
5
6
7
8
9
10
11
12
| /**
* Interceptor를 사용하기 위해선 config가 필요하다.
*/
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CommonInterceptor())
.addPathPatterns("/api/*")
.excludePathPatterns("api/public/*");
}
}
|
댓글남기기