Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.6'
id 'org.springframework.boot' version '3.3.0'
id 'io.spring.dependency-management' version '1.1.7'
}

Expand Down Expand Up @@ -39,7 +39,7 @@ dependencies {
annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
}

tasks.named('test') {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/example/umc9th/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.umc9th.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableAsync
public class AppConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.umc9th.config.discord;


import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Service
@Profile("!prod & !staging")
public class DevNotifierService implements Notifier{
@Override
public void sendNotification(Exception e, String requestUri) {
System.out.println("λ””μŠ€μ½”λ“œ μ•Œλ¦Ό 전솑(둜컬 ν™•μΈμš©)");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.example.umc9th.config.discord;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.awt.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.List;

@Service
@RequiredArgsConstructor
@Profile({"prod","staging"})
public class DiscordNotifierService implements Notifier{
private final RestTemplate restTemplate;

@Value( "${discord.webhook-url}")
private String discordWebhookUrl;

@Async
@Override
public void sendNotification(Exception e, String requestUri) {
try {
/*
RestTemplate은 HttpMessageConverterλ₯Ό μ΄μš©ν•΄ 직렬화
-> MappingJackson2HttpMessageConverter

spring-boot-starter-web : Jackson 포함
*/
Map<String, Object> payload = createDiscordPayload(e, requestUri);
restTemplate.postForEntity(discordWebhookUrl, payload, String.class);
} catch (Exception ex) {
// μ•Œλ¦Ό 전솑 μ‹€νŒ¨ μ‹œ, 원본 μ˜ˆμ™Έ μ²˜λ¦¬μ— 영ν–₯을 μ£Όμ§€ μ•Šλ„λ‘ λ‚΄λΆ€μ—μ„œ 처리
System.err.println("Discord μ•Œλ¦Ό 전솑 μ‹€νŒ¨: " + ex.getMessage());
}
}

// Discord Embed λ©”μ‹œμ§€ νŽ˜μ΄λ‘œλ“œ 생성
private Map<String, Object> createDiscordPayload(Exception e, String requestUri) {
// Embed 객체 생성
Map<String, Object> embed = new HashMap<>();
embed.put("title", "🚨 μ„œλ²„ μ—λŸ¬ λ°œμƒ!");
embed.put("color", Color.RED.getRGB() & 0xFFFFFF); // 16μ§„μˆ˜ Red 컬러

// μŠ€νƒ 트레이슀λ₯Ό λ¬Έμžμ—΄λ‘œ λ³€ν™˜ (κ°„κ²°ν•˜κ²Œ)
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String stackTrace = sw.toString().substring(0, Math.min(1000, sw.toString().length())) + "..."; // 1000자둜 μ œν•œ

// Embed에 ν•„λ“œ μΆ”κ°€
embed.put("fields", List.of(
Map.of("name", "Request URI", "value", "`" + requestUri + "`", "inline", false),
Map.of("name", "Exception Type", "value", "`" + e.getClass().getSimpleName() + "`", "inline", false),
Map.of("name", "Message", "value", e.getMessage(), "inline", false),
Map.of("name", "Stack Trace (Short)", "value", "```\n" + stackTrace + "\n```", "inline", false)
));

// μ΅œμƒμœ„ νŽ˜μ΄λ‘œλ“œ
Map<String, Object> payload = new HashMap<>();
payload.put("username", "500μ—λŸ¬ λ°œμƒ!"); // μ›Ήν›… 이름
payload.put("embeds", List.of(embed));

return payload;
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/example/umc9th/config/discord/Notifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example.umc9th.config.discord;

public interface Notifier {
void sendNotification(Exception e, String requestUri);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import com.example.umc9th.domain.review.dto.ReviewResponseDto;
import com.example.umc9th.domain.review.service.ReviewService;
import com.example.umc9th.global.apiPayload.ApiResponse;
import com.example.umc9th.global.apiPayload.code.status.GeneralSuccessCode;
import com.example.umc9th.global.dto.CursorResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -24,18 +25,18 @@ public class ReviewController {
summary = "리뷰 쑰회",
description = "κ°€κ²Œλ³„, λ³„μ λ³„λ‘œ 리뷰λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.")
@GetMapping
public CursorResponseDto<ReviewResponseDto.Review> getReviews(
public ApiResponse<CursorResponseDto<ReviewResponseDto.Review>> getReviews(
@RequestParam(required = false) String storeName,
@RequestParam(required = false, defaultValue = "0.0") Float minStar,
@RequestParam(required = false, defaultValue = "5.0") Float maxStar,
@RequestParam(required = false) Long cursorId,
@RequestParam(required = false, defaultValue = "10") Integer size) {

return reviewService.getReviews(storeName,
return ApiResponse.onSuccess(GeneralSuccessCode._OK,reviewService.getReviews(storeName,
minStar,
maxStar,
cursorId,
size);
size));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.example.umc9th.domain.store.dto.StoreResponseDto;
import com.example.umc9th.domain.store.service.StoreService;
import com.example.umc9th.global.apiPayload.ApiResponse;
import com.example.umc9th.global.apiPayload.code.status.GeneralSuccessCode;
import com.example.umc9th.global.dto.CursorResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -33,11 +35,11 @@ public class StoreController {
1-4. νŽ˜μ΄μ§•: page + size (μ»€μ„œ 기반 νŽ˜μ΄μ§• μ˜΅μ…˜ κ°€λŠ₯)
""")
@GetMapping
public CursorResponseDto<StoreResponseDto.SearchedStore> searchStore(@RequestParam(required = false) String storeName,
@RequestParam(required = false) String region,
@RequestParam(required = false) Long cursorId,
@RequestParam(required = false, defaultValue = "10") Integer size,
@RequestParam String sortType){
return storeService.searchStore(storeName, region, cursorId, size, sortType);
public ApiResponse<CursorResponseDto<StoreResponseDto.SearchedStore>> searchStore(@RequestParam(required = false) String storeName,
@RequestParam(required = false) String region,
@RequestParam(required = false) Long cursorId,
@RequestParam(required = false, defaultValue = "10") Integer size,
@RequestParam String sortType){
return ApiResponse.onSuccess(GeneralSuccessCode._OK,storeService.searchStore(storeName, region, cursorId, size, sortType));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.example.umc9th.domain.user.controller;

import com.example.umc9th.domain.user.service.UserService;
import com.example.umc9th.global.apiPayload.ApiResponse;
import com.example.umc9th.global.apiPayload.code.BaseSuccessCode;
import com.example.umc9th.global.apiPayload.code.status.GeneralSuccessCode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -13,8 +16,7 @@ public class UserController {
private final UserService userService;

@DeleteMapping("/{userId}")
public ResponseEntity<Void> deleteUser(@PathVariable("userId") Long userId) {
userService.deleteUser(userId);
return ResponseEntity.noContent().build();
public ApiResponse<String> deleteUser(@PathVariable("userId") Long userId) {
return ApiResponse.onSuccess(GeneralSuccessCode._DELETED, userService.deleteUser(userId));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.example.umc9th.domain.user.service;

public interface UserService {
void deleteUser(Long userId);
String deleteUser(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class UserServiceImpl implements UserService {

@Override
@Transactional
public void deleteUser(Long userId) {
public String deleteUser(Long userId) {
userMissionRepository.deleteByUserId(userId);
inquiryRepository.deleteByUserId(userId);
reviewRepository.deleteByUserId(userId);
Expand All @@ -32,5 +32,7 @@ public void deleteUser(Long userId) {
notificationAgreementRepository.deleteByUserId(userId);
termAgreementRepository.deleteByUserId(userId);
userRepository.softDeleteUser(userId);

return "μœ μ €" + userId.toString() + "이 μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.example.umc9th.global.apiPayload;

import com.example.umc9th.global.apiPayload.code.BaseSuccessCode;
import com.example.umc9th.global.apiPayload.code.BaseErrorCode;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess","code","message","result"})
public class ApiResponse<T> {

@JsonProperty("isSuccess")
private final Boolean isSuccess;

@JsonProperty("code")
private final String code;

@JsonProperty("message")
private final String message;

@JsonProperty("result")
private T result;

public static <T> ApiResponse<T> onSuccess(BaseSuccessCode code, T result) {
return new ApiResponse<>(
true, code.getCode(), code.getMessage(), result);
}

public static <T> ApiResponse<T> of(BaseSuccessCode code, T result) {
return new ApiResponse<>(
true,
code.getCode(),
code.getMessage(),
result);
}

public static <T> ApiResponse<T> onFailure(BaseErrorCode code, T result) {
return new ApiResponse<>(false, code.getCode(), code.getMessage(), result);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.umc9th.global.apiPayload.code;

import org.springframework.http.HttpStatus;

public interface BaseErrorCode {
HttpStatus getHttpStatus();
String getCode();
String getMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.umc9th.global.apiPayload.code;

import org.springframework.http.HttpStatus;

public interface BaseSuccessCode {
HttpStatus getHttpStatus();
String getCode();
String getMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.umc9th.global.apiPayload.code.status;

import com.example.umc9th.global.apiPayload.code.BaseErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum GeneralErrorCode implements BaseErrorCode {

// 인증
USER_NOT_EXISTED(HttpStatus.NOT_FOUND,"USER_400","μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ³„μ •μž…λ‹ˆλ‹€"),
UNEXPECTED_TOKEN(HttpStatus.NOT_FOUND,"TOKEN_400","μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 토큰 μž…λ‹ˆλ‹€"),

// 검색
PAGE_OUT_OF_RANGE(HttpStatus.BAD_REQUEST,"SEARCH_4001","μš”μ²­ν•œ νŽ˜μ΄μ§€κ°€ 전체 νŽ˜μ΄μ§€ 수λ₯Ό μ΄ˆκ³Όν•©λ‹ˆλ‹€."),
NO_RESULT(HttpStatus.NOT_FOUND,"SEARCH_40002","검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€."),

// κΈ°λ³Έ μ—λŸ¬
_BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON_400", "잘λͺ»λœ μš”μ²­μž…λ‹ˆλ‹€."),
_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON_401", "인증이 ν•„μš”ν•©λ‹ˆλ‹€."),
_FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON_403", "κΈˆμ§€λœ μš”μ²­μž…λ‹ˆλ‹€."),
_NO_RESULTS_FOUND(HttpStatus.NOT_FOUND, "COMMON_404", "검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€."),
_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON_500", "μ„œλ²„ μ—λŸ¬. κ΄€λ¦¬μžμ—κ²Œ 문의 λ°”λžλ‹ˆλ‹€."),

MULTIPLE_FIELD_VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "COMMON_001", "μž…λ ₯된 정보에 였λ₯˜κ°€ μžˆμŠ΅λ‹ˆλ‹€. ν•„λ“œλ³„ 였λ₯˜ λ©”μ‹œμ§€λ₯Ό μ°Έμ‘°ν•˜μ„Έμš”."),
NO_MATCHING_ERROR_STATUS(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON_002", "μ„œλ²„ μ—λŸ¬. μΌμΉ˜ν•˜λŠ” errorStatusλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),
REQUEST_BODY_INVALID(HttpStatus.BAD_REQUEST, "COMMON_003", "μš”μ²­ 본문을 읽을 수 μ—†μŠ΅λ‹ˆλ‹€. 빈 λ¬Έμžμ—΄ λ˜λŠ” null이 μžˆλŠ”μ§€ ν™•μΈν•΄μ£Όμ„Έμš”."),
INVALID_DATE_FORMAT(HttpStatus.BAD_REQUEST, "COMMON_004", "μš”μ²­ν•œ λ‚ μ§œ/μ‹œκ°„ ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν˜•μ‹μ„ ν™•μΈν•΄μ£Όμ„Έμš”."),
DUPLICATE_UNIQUE_KEY(HttpStatus.CONFLICT, "COMMON_005", "이미 처리된 μš”μ²­μž…λ‹ˆλ‹€."),

// s3 사진 첨뢀 μ—λŸ¬
FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE_001", "파일 μ—…λ‘œλ“œμ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.");

private final HttpStatus httpStatus;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.umc9th.global.apiPayload.code.status;

import com.example.umc9th.global.apiPayload.code.BaseSuccessCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum GeneralSuccessCode implements BaseSuccessCode {
_OK(HttpStatus.OK, "COMMON200", "μ„±κ³΅μž…λ‹ˆλ‹€."),
_CREATED(HttpStatus.CREATED, "COMMON201", "μš”μ²­ 성곡 및 λ¦¬μ†ŒμŠ€ 생성됨"),
_DELETED(HttpStatus.NO_CONTENT, "COMMON204", "μ‚­μ œκ°€ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.");


private final HttpStatus httpStatus;
private final String code;
private final String message;

}
Loading
Loading