diff --git a/api/src/main/kotlin/com/few/api/web/controller/hello/_HelloController.kt b/api/src/main/kotlin/com/few/api/web/controller/hello/_HelloController.kt new file mode 100644 index 000000000..df85c2d07 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/controller/hello/_HelloController.kt @@ -0,0 +1,46 @@ +package com.few.api.web.controller.hello + +import com.few.api.web.controller.hello.request._HelloBody +import com.few.api.web.controller.hello.request._HelloParam +import com.few.api.web.support.ApiResponse +import com.few.api.web.support.ApiResponseGenerator +import com.few.api.web.support.MessageCode +import org.springframework.http.HttpStatus +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.* + +@Validated +@RestController +@RequestMapping("/api/v1/hello") +class _HelloController { + + /** + * @param param 객체로 파라미터를 받는 경우 + * @param club RequestParam을 사용하는 경우 + */ + @GetMapping + fun helloGet( + param: _HelloParam?, + @RequestParam(required = true) club: String + ): ApiResponse>> { + val name = param?.name ?: "few" + val age = param?.age ?: 0 + val club = club + val data = + mapOf("hello" to "world", "name" to name, "age" to age.toString(), "club" to club) + return ApiResponseGenerator.success(data, HttpStatus.OK) + } + + @PostMapping + fun helloPost( + @RequestBody body: _HelloBody + ): ApiResponse>> { + val data = mapOf("hello" to "world", "name" to body.name) + return ApiResponseGenerator.success(data, HttpStatus.OK, MessageCode.RESOURCE_CREATED) + } + + @GetMapping("/error") + fun helloError(): ApiResponse { + throw RuntimeException("Hello Error") + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/controller/hello/request/_HelloBody.kt b/api/src/main/kotlin/com/few/api/web/controller/hello/request/_HelloBody.kt new file mode 100644 index 000000000..b0ece9e5d --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/controller/hello/request/_HelloBody.kt @@ -0,0 +1,5 @@ +package com.few.api.web.controller.hello.request + +data class _HelloBody( + val name: String +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/controller/hello/request/_HelloParam.kt b/api/src/main/kotlin/com/few/api/web/controller/hello/request/_HelloParam.kt new file mode 100644 index 000000000..2dd016669 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/controller/hello/request/_HelloParam.kt @@ -0,0 +1,6 @@ +package com.few.api.web.controller.hello.request + +data class _HelloParam( + val name: String?, + val age: Int? +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/filter/MDCFilter.kt b/api/src/main/kotlin/com/few/api/web/filter/MDCFilter.kt new file mode 100644 index 000000000..92d7b0a06 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/filter/MDCFilter.kt @@ -0,0 +1,44 @@ +package com.few.api.web.filter + +import com.fasterxml.jackson.databind.ObjectMapper +import org.jboss.logging.MDC +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.http.HttpHeaders +import org.springframework.stereotype.Component +import org.springframework.web.server.ServerWebExchange +import org.springframework.web.server.WebFilter +import org.springframework.web.server.WebFilterChain +import reactor.core.publisher.Mono + +@Component +class MDCFilter(private val mapper: ObjectMapper) : WebFilter { + private val log: Logger = LoggerFactory.getLogger(MDCFilter::class.java) + override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { + val requestStartTime = System.currentTimeMillis() + MDC.put("Type", "Request MDC Info") + MDC.put("TraceId", exchange.request.id) + MDC.put("Request-Remote-Address", exchange.request.remoteAddress) + MDC.put("Request-URL", exchange.request.uri) + MDC.put("Request-Method", exchange.request.method) + MDC.put(HttpHeaders.REFERER, exchange.request.headers.getFirst(HttpHeaders.REFERER)) + MDC.put(HttpHeaders.USER_AGENT, exchange.request.headers.getFirst(HttpHeaders.USER_AGENT)) + + val request = mapOf( + "Type" to "Request Info", + "TraceId" to exchange.request.id, + "Request-Remote-Address" to exchange.request.remoteAddress.toString(), + "Request-URL" to exchange.request.uri.toString(), + "Request-Method" to exchange.request.method.toString() + ) + log.info("{}", mapper.writeValueAsString(request)) + + return chain.filter(exchange).doFinally { + val requestEndTime = System.currentTimeMillis() + val elapsedTime = requestEndTime - requestStartTime + MDC.put("ElapsedTime", elapsedTime.toString() + "ms") + log.info("{}", mapper.writeValueAsString(MDC.getMap())) + MDC.clear() + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/handler/ApiControllerExceptionHandler.kt b/api/src/main/kotlin/com/few/api/web/handler/ApiControllerExceptionHandler.kt new file mode 100644 index 000000000..b9da8ac8e --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/handler/ApiControllerExceptionHandler.kt @@ -0,0 +1,105 @@ +package com.few.api.web.handler + +import com.few.api.web.support.ApiResponse +import com.few.api.web.support.ApiResponseGenerator +import jakarta.validation.ConstraintViolationException +import org.springframework.beans.TypeMismatchException +import org.springframework.core.codec.DecodingException +import org.springframework.http.HttpStatus +import org.springframework.http.converter.HttpMessageNotReadableException +import org.springframework.validation.BindException +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.bind.support.WebExchangeBindException +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException +import org.springframework.web.server.ServerWebInputException +import java.nio.file.AccessDeniedException + +/** API 요청 처리 중 발생하는 예외를 처리하는 핸들러 */ +@RestControllerAdvice +class ApiControllerExceptionHandler( + private val loggingHandler: LoggingHandler +) { + + @ExceptionHandler(IllegalArgumentException::class) + fun handleBadRequest( + ex: IllegalArgumentException + ): ApiResponse { + return ApiResponseGenerator.fail(ExceptionMessage.FAIL.message, HttpStatus.BAD_REQUEST) + } + + @ExceptionHandler( + MethodArgumentTypeMismatchException::class, + TypeMismatchException::class, + WebExchangeBindException::class, + BindException::class, + MethodArgumentNotValidException::class, + DecodingException::class, + ConstraintViolationException::class, + ServerWebInputException::class, + HttpMessageNotReadableException::class + ) + fun handleBadRequest( + ex: Exception + ): ApiResponse { + return handleRequestDetails(ex) + } + + private fun handleRequestDetails(ex: Exception): ApiResponse { + if (ex is MethodArgumentTypeMismatchException) { + return handleRequestDetail(ex) + } + if (ex is MethodArgumentNotValidException) { + return handleRequestDetail(ex) + } + return ApiResponseGenerator.fail( + ExceptionMessage.FAIL_REQUEST.message, + HttpStatus.BAD_REQUEST + ) + } + + private fun handleRequestDetail(ex: MethodArgumentTypeMismatchException): ApiResponse { + val messageDetail = ExceptionMessage.REQUEST_INVALID_FORMAT.message + " : " + ex.name + return ApiResponseGenerator.fail(messageDetail, HttpStatus.BAD_REQUEST) + } + + private fun handleRequestDetail(ex: MethodArgumentNotValidException): ApiResponse { + val filedErrors = ex.fieldErrors.toList() + val messageDetail = ExceptionMessage.REQUEST_INVALID.message + " : " + filedErrors + return ApiResponseGenerator.fail( + messageDetail, + HttpStatus.BAD_REQUEST + ) + } + + @ExceptionHandler(IllegalStateException::class) + fun handleIllegalState( + ex: Exception + ): ApiResponse { + return ApiResponseGenerator.fail( + ExceptionMessage.FAIL.message, + HttpStatus.INTERNAL_SERVER_ERROR + ) + } + + @ExceptionHandler(NoSuchElementException::class) + fun handleForbidden( + ex: AccessDeniedException + ): ApiResponse { + return ApiResponseGenerator.fail( + ExceptionMessage.ACCESS_DENIED.message, + HttpStatus.FORBIDDEN + ) + } + + @ExceptionHandler(Exception::class) + fun handleInternalServerError( + ex: Exception + ): ApiResponse { + return ApiResponseGenerator.fail( + ExceptionMessage.FAIL.message, + HttpStatus.INTERNAL_SERVER_ERROR + ) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/handler/ExceptionMessage.kt b/api/src/main/kotlin/com/few/api/web/handler/ExceptionMessage.kt new file mode 100644 index 000000000..b4ddd97de --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/handler/ExceptionMessage.kt @@ -0,0 +1,13 @@ +package com.few.api.web.handler + +enum class ExceptionMessage(val code: String, val message: String) { + FAIL("fail", "알 수 없는 오류가 발생했어요."), + FAIL_NOT_FOUND("fail.notfound", "일치하는 결과를 찾을 수 없어요."), + FAIL_AUTHENTICATION("fail.authentication", "인증이 필요해요."), + FAIL_REQUEST("fail.request", "잘못된 요청입니다."), + RESOURCE_NOT_FOUND("resource.notfound", "요청과 일치하는 결과를 찾을 수 없어요."), + RESOURCE_DELETED("resource.deleted", "요청에 대한 응답을 찾을 수 없어요."), + ACCESS_DENIED("access.denied", "접근 권한이 없어요."), + REQUEST_INVALID_FORMAT("request.%s.invalid", "잘못된 요청입니다."), + REQUEST_INVALID("request.invalid", "잘못된 요청입니다.") +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/handler/LoggingHandler.kt b/api/src/main/kotlin/com/few/api/web/handler/LoggingHandler.kt new file mode 100644 index 000000000..cc1fb6fce --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/handler/LoggingHandler.kt @@ -0,0 +1,663 @@ +package com.few.api.web.handler + +import org.slf4j.LoggerFactory +import org.springframework.http.server.reactive.ServerHttpRequest +import org.springframework.stereotype.Component + +/** 로깅을 담당하는 핸들러 */ +@Component +class LoggingHandler { + private val log = LoggerFactory.getLogger(LoggingHandler::class.java) + + fun writeLog(ex: Exception, request: ServerHttpRequest) { + try { + log.error( + LOG_MESSAGE_FORMAT, + request.method, + request.uri, + ex.message, + ex + ) + } catch (e: Exception) { + log.error(LOG_MESSAGE_FORMAT, UNCAUGHT_LOG_MESSAGE, UNCAUGHT_LOG_MESSAGE, e.message, e) + } + } + + companion object { + private const val LOG_MESSAGE_FORMAT = "{} '{}' - {}" + private const val UNCAUGHT_LOG_MESSAGE = "??" + private val IGNORE_REQUEST_URI_LIST: Set = HashSet( + mutableListOf( + "/", + "/.aws/credentials", + "/.env", + "/.env.", + "/.env.backup", + "/.env.bak", + "/.env.dev", + "/.env.dev.local", + "/.env.development.local", + "/.env.ec2-3-34-67-210", + "/.env.ec2-3-37-143-124", + "/.env.example", + "/.env.live", + "/.env.local", + "/.env.old", + "/.env.prod", + "/.env.prod.local", + "/.env.production", + "/.env.production.local", + "/.env.save", + "/.env.stage", + "/.env.www", + "/.env_1", + "/.env_sample", + "/.git/HEAD", + "/.git/config", + "/.git2/config", + "/.svn/wc.db", + "/.well-known/security.txt", + "/1-200611214053U8.jpg", + "/202110/images/public.css", + "/6/", + "/71624567", + "/99vt", + "/99vu", + "/9h/", + "/:80:undefined?id=", + "/?XDEBUG_SESSION_START=phpstorm", + "/AWSconf.git/config", + "/Application/Buy/Static/js/if.js", + "/Autodiscover/Autodiscover.xml", + "/Autodiscover/autodiscover.json?a=margart@rodriguez.io/mapi/nspi/", + "/Content/Wap/base.css", + "/Content/css/wzwstylel.css", + "/Content/favicon.ico", + "/Content/m_1/js/m_1_Jquery.js", + "/Core/Skin/Login.aspx", + "/Css/Hm.css", + "/HNAP1", + "/Home/Bind/binding", + "/Home/Get/getJnd28", + "/Home/GetAllGameCategory", + "/Home/GetInitSource", + "/Home/Index/ajaxTJ", + "/JS/loginstatus.js", + "/Pay_Index.html", + "/Pc/Lang/index.html", + "/Public/Home/ecshe_css/main.css?v=1543997196", + "/Public/Home/js/cls.js", + "/Public/Mobile/ecshe_css/wapmain.css?v=1545408652", + "/Public/Wchat/css/index.css", + "/Public/Wchat/js/cvphp.js", + "/Public/css/_pk10.css", + "/Public/css/errorCss.css", + "/Public/css/hall.css", + "/Public/home/common/js/index.js", + "/Public/home/js/check.js", + "/Public/home/js/fukuang.js", + "/Public/home/wap/css/qdgame.css", + "/Public/initJs.php", + "/Public/js/common.js", + "/Public/uploads/web/step1.png", + "/ReportServer", + "/Res/font/font.css", + "/Res/login.html", + "/Scripts/common.js", + "/SiteLoader", + "/T9sA/static/image/t-06.jpg", + "/Telerik.Web.UI.WebResource.axd?type=rau", + "/Template/Mobile/js/main.js", + "/Templates/user/finance/css/userPay.css", + "/Templates/user/js/global.js", + "/V5Wz", + "/WuEL", + "/__MACOSX/.git/config", + "/_ignition/execute-solution", + "/_profiler/empty/search/results", + "/_profiler/phpinfo", + "/_wpeprivate/config.json", + "/a", + "/a/.git/config", + "/a/other/codepay/js/codepay_util.js", + "/aaaaaaaaaaaaaaaaaaaaaaaaaqr", + "/aab8", + "/aab9", + "/ab2g", + "/ab2h", + "/actuator", + "/actuator/env", + "/actuator/gateway/routes", + "/admin", + "/admin/", + "/admin/.env", + "/admin/.git/config", + "/admin/assets/js/views/login.js", + "/admin/config.php", + "/admin/webadmin.php?mod=do&act=login", + "/ajax/allcoin_a/id/0?t=0.3782499195965951", + "/amphtml/.git/config", + "/anquan/qgga.asp", + "/aomanalyzer/.git/config", + "/api", + "/api/", + "/api/.env", + "/api/.git/config", + "/api/GetConfigByKeys?keys=of_we", + "/api/Index/getLottery", + "/api/Index/getconfig", + "/api/app-info", + "/api/appVersion?mobile_system=2", + "/api/apps", + "/api/apps/config", + "/api/auth", + "/api/config", + "/api/config-init", + "/api/config/getwebsitename", + "/api/config/info", + "/api/config/proxy_servers?key=1", + "/api/contactWay", + "/api/currency/quotation_new", + "/api/customerServiceLink", + "/api/exclude/siteConfig/webSiteConfig", + "/api/getUserCertificationStatus", + "/api/getconfig.aspx", + "/api/grame/getHomePtLottery", + "/api/help_documents/nitice_list", + "/api/hevent", + "/api/home_index", + "/api/ht/xy1", + "/api/im/conf", + "/api/im/v2/app/config", + "/api/index", + "/api/index/config", + "/api/index/grailindex", + "/api/index/index", + "/api/index/webconfig", + "/api/jsonws/", + "/api/lastestBetLog", + "/api/link/platform", + "/api/linkPF", + "/api/lottery/color", + "/api/message/webInfo", + "/api/notice", + "/api/other/appSetting", + "/api/pay/query_order", + "/api/public/?service=Home.getConfig", + "/api/shares/hqStrList", + "/api/site/getInfo.do", + "/api/st/index/?ac=2", + "/api/stock/getSingleStock.do?code=002405", + "/api/sys/var", + "/api/system/system/config/get", + "/api/system/systemConfigs/getCustomerServiceLink", + "/api/uploads/apimap", + "/api/user/dataDictionaryService/list", + "/api/user/index", + "/api/user/ismustmobile", + "/api/user/mobilelogin", + "/api/v/index/queryOfficePage?officeCode=customHomeLink", + "/api/v1", + "/api/v1/about", + "/api/v1/app-info", + "/api/v1/clients", + "/api/v1/config", + "/api/v1/notices?page=1&per_page=1&thread_name=general", + "/api/v2/static/not.found", + "/api/vue/transaction/config", + "/apiApp/app/site/info", + "/apiapp/Ygn_Girl.CityList", + "/apis/apps/v1/namespaces/kube-system/daemonsets", + "/app/", + "/app/.git/config", + "/app/common/getRegisterSet", + "/app/config/getConfig", + "/app/js/base.js", + "/application.ini", + "/application/.git/config", + "/application/application.ini", + "/application/configs/application.ini", + "/appspec.yaml", + "/appspec.yml", + "/appxz/index.html", + "/assets../.git/config", + "/assets/AssetManifest.json", + "/assets/app-manifest.json", + "/assets/etc/query.css", + "/assets/extension/market/css/mt4.css", + "/assets/images/dy.jpg", + "/assets/images/redian.jpg", + "/assets/js/dmshub.js", + "/assets/mstock/newimg/guid-info.png", + "/assets/res/mods/room.js", + "/assets/shebao/banner.png", + "/assets/source/list.css", + "/assets/yibao/Nweshebao/img/icon2.png", + "/auth.asp", + "/autodiscover/autodiscover.json", + "/autodiscover/autodiscover.json?@test.com/owa/?&Email=autodiscover/autodiscover.json%3F@test.com", + "/autodiscover/autodiscover.json?@zdi/Powershell", + "/autodiscover/autodiscover.json?a..foo.var/owa/?&Email=autodiscover/autodiscover.json?a..foo.var&Protocol=XYZ&FooProtocol=%50owershell", + "/aws.yml", + "/backup/.git/config", + "/banner.do?code=1", + "/bapi/st/news/", + "/baseConfig", + "/bet/lotteryinfo/allLotteryInfoList", + "/beta/.git/config", + "/biz/server/config", + "/blog/.git/config", + "/blog/wp-content/themes/.git/config", + "/build/.git/config", + "/c/", + "/c/version.js", + "/cgi-bin/printenv.pl", + "/chat.html", + "/client/api/findConfigByKey?configKey=level_config", + "/client/static/icon/hangqingicon.png", + "/cms/.git/config", + "/code/js/config.js", + "/common.js", + "/common/.git/config", + "/common/member/js/user.util.js", + "/common/template/lottery/lecai/css/style.css", + "/config", + "/config.js", + "/config.js.backup", + "/config.js.bak", + "/config.js.old", + "/config.js.save", + "/config.json", + "/config/application.ini", + "/config/aws.yml", + "/config/config.json", + "/config/default.json", + "/configs/application.ini", + "/configuration.php-dist", + "/console/", + "/content../.git/config", + "/core/.env", + "/credentials/config.json", + "/csjs/bankCheck.js", + "/css../.git/config", + "/css/all.css", + "/css/m.css", + "/css/main.css", + "/css/nsc/reset.css", + "/css/other.css", + "/css/scanner.css", + "/css/skin/ymPrompt.css", + "/css/style.css", + "/d1/OK.php", + "/data/.git/config", + "/data/json/config.json", + "/data/ticker_24hr", + "/database/.git/config", + "/demo/.git/config", + "/detail1/js/add.js", + "/detaila/images/header_v1b.css", + "/detaila/images/logo.png", + "/dev/.git/config", + "/developer/.git/config", + "/ding/", + "/dist/images/mask/bg1.jpg", + "/dist/images/mask/guide/cn/step1.jpg", + "/dist/index.html?v=32d9d4", + "/dns-query?name=dnsscan.shadowserver.org&type=A", + "/dot.git/config", + "/download/file.ext", + "/dqgqoeCXckuwPtxov", + "/ecp/Current/exporttool/microsoft.exchange.ediscovery.exporttool.application", + "/env.dev.js", + "/env.development.js", + "/env.js", + "/env.prod.js", + "/env.production.js", + "/env.test.js", + "/events../.git/config", + "/evox/about", + "/favicon-16x16.png", + "/favicon-32x32.png", + "/favicon.ico", + "/fePublicInfo/", + "/files/pub_rem.js", + "/files/pub_reset.css", + "/flock/.git/config", + "/flu/403.html", + "/friendGroup/list", + "/front/index/getSiteSetting", + "/gate.php", + "/getConfig/getArticle.do?code=1", + "/getConfig/getArticle.do?code=19", + "/getConfig/listPopFrame.do?code=1&position=index&_=1601489645097", + "/getConfig/listPopFrame.do?code=14&position=index&_=1601489645097", + "/getLocale", + "/git/.git/config", + "/guocanju/shijiuling/", + "/h5", + "/h5/", + "/h5/static/cert/icon_yanzhengma.png", + "/h5/static/tabbar/txl.png", + "/hetong/yq?id=500", + "/home/", + "/home/GetQrCodeInfo", + "/home/banner/getLogo", + "/home/help", + "/home/login.jpg", + "/home/login/login_index.html", + "/home/main/login", + "/homes/", + "/hooked-center/config/offline/list", + "/im/", + "/im/App/config", + "/im/h5/", + "/im/in/GetUuid", + "/images../.git/config", + "/images/favicon.ico", + "/images/no.jpg", + "/images/src_images_but_dianz_s.png", + "/img../.git/config", + "/img/close.png", + "/img/phonetrackeronline.png", + "/img/subsidy/kh.png", + "/img/xxing.png", + "/img/zllqdk.png", + "/index", + "/index.php/User/sendsmscode", + "/index.php/Wap/Api/getBanner", + "/index.php/Wap/Api/getSystemNotice?id=1", + "/index.php/sign", + "/index.php?m=api&c=app&a=getPlatformConfig", + "/index/", + "/index/about/index.html", + "/index/api/getconfig", + "/index/aurl", + "/index/common.css", + "/index/gzid.js", + "/index/home/login.html", + "/index/images/tradeicon.png", + "/index/index/getchatLog", + "/index/index/info/", + "/index/login", + "/index/login/index", + "/index/login/index.html", + "/index/login/login", + "/index/login/reg.html", + "/index/login/register", + "/index/newapi/api", + "/index/open/login.html", + "/index/police/index.html?agent=1000", + "/index/user/get_server_info", + "/index/user/login.html", + "/index_files/bankCheck.js", + "/infe/rest/fig/advertise/common.json?mobile_open=1", + "/info.json", + "/info.php", + "/install.inc/vipsignInstall.css", + "/ipl/app/flash/publicbmw/ball/FigLeaf.js?site=member", + "/jiaoyimao/", + "/jiaoyimao/default.css", + "/jquery-3.3.1.slim.min.js", + "/jquery-3.3.2.slim.min.js", + "/js../.git/config", + "/js/a.script", + "/js/app.js", + "/js/base1.js", + "/js/basic.js", + "/js/bk.min.js", + "/js/config.js", + "/js/home.js", + "/js/index.js", + "/js/lang.js", + "/js/options.js", + "/js/post.js/", + "/js/pups.js", + "/js/subsidy/bk.min.js", + "/js/xz.js", + "/jym-wn/", + "/kkrp/site/info", + "/kkrps/im_group/show_members", + "/km.asmx/getPlatParam", + "/langConfig.js", + "/lanren/css/global.css", + "/laravel/.env", + "/leftDao.php?callback=jQuery183016740860980352856_1604309800583", + "/lib../.git/config", + "/live/.git/config", + "/loan/css/index.css", + "/login", + "/login.html", + "/login.php", + "/lottery/lottery_list", + "/m/", + "/m/.git/config", + "/m/allticker/1", + "/m/env.js", + "/mPlayer", + "/main", + "/manager/js/left.js", + "/market/market-ws/iframe.html", + "/masterControl/getSystemSetting", + "/media../.git/config", + "/melody/api/v1/pageconfig/list", + "/member/js/lang_zh_CN.js", + "/metrics", + "/mg/other/codepay/js/codepay_util.js", + "/mifs/.;/services/LogService", + "/mindex.html", + "/mmj/index.html", + "/mobile", + "/mobile/config.js", + "/mobile/css/base.css", + "/mobile/lists.html", + "/mobile/login.html", + "/mobile/lottery/list", + "/mobile/v3/appSuperDownload.do", + "/mobile21/js/index/gameManagement.js?v=7", + "/myConfig.js", + "/mytio/config/base", + "/n9Im", + "/new/.git/config", + "/newApp/winMessTopQuery.php", + "/nmaplowercheck1673396272", + "/nmaplowercheck1673717885", + "/nyyh/chkjs.js", + "/nyyh/game.css", + "/old-cuburn/.git/config", + "/openApi/systemConfig/findSystemConfigById?id=3", + "/otc/", + "/owa", + "/owa/", + "/owa/auth/logon.aspx", + "/owa/auth/logon.aspx?url=https%3a%2f%2f1%2fecp%2f", + "/owa/auth/x.js", + "/pages/console/js/common.js", + "/ph_acquireSession", + "/phalapi/public/", + "/phalapi/public/?s=System.index", + "/phone/images/icon_01.png", + "/php-info", + "/phpinfo", + "/phpinfo.php", + "/phpmyadmin/index.php", + "/phpmyadmin4.8.5/index.php", + "/platform", + "/pmd/index.php", + "/portal/index/protocol.html", + "/prod.git/config", + "/projectConfig", + "/proxy/games", + "/proxy/settings", + "/public/.git/config", + "/public/config.js", + "/public/css/style.css", + "/public/h5static/js/main.js", + "/public/img/cz1.png", + "/public/static/css/public.css", + "/public/static/home/js/moblie/login.js", + "/public/static/index/picture/img_33.png", + "/public/wap/js/basis.js", + "/qa/.git/config", + "/reg.php", + "/remote/login", + "/repos/.git/config", + "/repository/.git/config", + "/res/common/addfriend.png", + "/res/kuai/1.css", + "/res/kuai/21.css", + "/resource/home/css/person.css", + "/resource/home/js/common.js", + "/resources/css/headernav.css", + "/resources/main/common.js", + "/robots.txt", + "/room/getRoomBangFans", + "/room/script/face.js", + "/s3/.git/config", + "/s_api/basic/config_js?callback=__set_config", + "/s_api/basic/download/info", + "/saconfig/secure/yunwei.js", + "/samples/.git/config", + "/sdk", + "/sdk.html", + "/server/business/api/customer/list", + "/service?action=getBasicInfo&terminal_id=2&token=&debug=true", + "/shop/.git/config", + "/showLogin.cc", + "/site.js", + "/site/.git/config", + "/site/api/v1/site/vipExclusiveDomain/getGuestDomain", + "/site/get-hq?proNo=btc&panType=1&pid=1", + "/site/info", + "/sitemap.xml", + "/skin/js/common.js", + "/skin/main/onload.js", + "/solr/", + "/source/20220119/static/wap/css/trade-history.css", + "/source/20220119/static/wap/js/order.js", + "/stage-api/common/configKey/all", + "/staging/.git/config", + "/stalker_portal/c/version.js", + "/stalker_portal/server/tools/auth_simple.php", + "/static../.git/config", + "/static/.git/config", + "/static/Mobile/js/common.js", + "/static/_zxzx/bottom/index_bottom.shtml", + "/static/admin/javascript/hetong.js", + "/static/admincp/js/common.js", + "/static/common/js/common.js", + "/static/common/js/vant/vant.min.js", + "/static/config.js", + "/static/content.html", + "/static/css/index.css", + "/static/css/mobile.css", + "/static/css/public.css", + "/static/css/reset.css", + "/static/customer/js/xiaotian.cli.v2.js", + "/static/data/configjs.js", + "/static/data/gamedatas.js", + "/static/data/thirdgames.json", + "/static/diff_worker.js", + "/static/f_title.png", + "/static/guide/ab.css", + "/static/home/2022/index1.js", + "/static/home/css/common.css", + "/static/home/css/css.css", + "/static/home/css/feiqi-ee5401a8e6.css", + "/static/home/imgs/jkjl.png", + "/static/home/imgs/pico.png", + "/static/home/js/rooms.js", + "/static/image/bg1.jpg", + "/static/image/logo.png?v=4", + "/static/images/login_bg.jpg", + "/static/img/bitbeb-logo.png", + "/static/index/3.png", + "/static/index/css/trade-history.css", + "/static/index/js/lk/order.js", + "/static/js/app.js", + "/static/js/config.js", + "/static/js/download.js", + "/static/js/user.js", + "/static/js/view.js", + "/static/local/img/userCenter/hourlyPrivilege.svg", + "/static/m_text.png", + "/static/mobile/user.html", + "/static/tabBar/trade.png", + "/static/v/v2/image/star.png", + "/static/v2/css/index.css", + "/static/voice/default.wav", + "/static/wap/css/common.css", + "/static/wap/css/index.css", + "/static/wap/css/tipmask.css", + "/static/wap/css/trade-history.css", + "/static/wap/js/common.js", + "/static/wap/js/order.js", + "/statics/js/API.js", + "/step1.asp", + "/stock/mzhishu", + "/stock/search.html?keyword=00202", + "/stock2c1/api/site/getInfo.do", + "/store/.git/config", + "/stream/live.php", + "/streaming/clients_live.php", + "/sucai/zxkf.png", + "/system_api.php", + "/t000/login-img-3.svg", + "/template/920ka/css/lsy.css", + "/template/920ka/js/woodyapp.js", + "/template/css/login.css", + "/template/mb/lang/text-zh.json", + "/template/tmp1/js/common.js", + "/test.php", + "/themes/pay/assets/core.css", + "/thriveGame.css", + "/uis/app/get/config", + "/uploads/20210928/01737e4f85f971bdf4d892dfaee6579c.png", + "/uploads/admin/202103/60584c5b88017.png", + "/user/Login", + "/user/getAllNicknames", + "/uw79", + "/v1/getConfig", + "/v1/management/tenant/getSpeedDomain", + "/v2/", + "/v2/block/home/app/hot", + "/v2/game/rooms", + "/v6/getAppContent", + "/vendor/.git/config", + "/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", + "/verification.asp", + "/version", + "/video/65788.html", + "/video/67950.html", + "/views/commData/commonSite.js", + "/views/home/home.js", + "/wap", + "/wap.html", + "/wap/", + "/wap/api/exchangerateuserconfig!get.action", + "/wap/assets/images/icon1/12306.png", + "/wap/forward", + "/wap/trading/lastKlineParameter", + "/waplogin.php", + "/web/.git/config", + "/webclient/", + "/wiki/.git/config", + "/wp-content/.git/config", + "/wp-content/plugins/.git/config", + "/wp-content/themes/.git/config", + "/wp-includes/js/.git/config", + "/wp-login.php", + "/ws/index/getTheLotteryInitList", + "/ws/market", + "/xianyu/", + "/xianyu/image/jiantou.png", + "/xy/", + "/xy/image/jiantou.png", + "/yinjian/", + "/yr99", + "/ztp/cgi-bin/handle", + "/zz/address.php?gid=651", + "/zz2/address.php?gid=651" + ) + ) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/support/ApiResponse.kt b/api/src/main/kotlin/com/few/api/web/support/ApiResponse.kt new file mode 100644 index 000000000..1fc980767 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/support/ApiResponse.kt @@ -0,0 +1,29 @@ +package com.few.api.web.support + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import java.io.Serializable + +/** API 응답 객체 */ +class ApiResponse : ResponseEntity { + constructor(status: HttpStatus?) : super(status!!) + constructor(body: B, status: HttpStatus?) : super(body, status!!) + + /** API 응답 실패 객체 */ + class FailureBody(val message: String) : Serializable + + /** + * API 응답 성공 객체 + * + * @param 데이터 타입 + * */ + class SuccessBody( + val data: D, + val message: String + ) : Serializable + + /** API 응답 성공 객체 */ + class Success( + val message: String + ) : Serializable +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/support/ApiResponseGenerator.kt b/api/src/main/kotlin/com/few/api/web/support/ApiResponseGenerator.kt new file mode 100644 index 000000000..e0c422f2f --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/support/ApiResponseGenerator.kt @@ -0,0 +1,112 @@ +package com.few.api.web.support + +import org.springframework.http.HttpStatus + +/** API 응답 객체 생성기 */ +object ApiResponseGenerator { + /** + * Http 상태 코드만 포함하는 API 응답 성공 객체 생성한다. + * + * @param status Http 상태 코드 + * @return API 응답 객체 + */ + fun success(status: HttpStatus): ApiResponse { + return ApiResponse( + ApiResponse.Success(MessageCode.SUCCESS.value), + status + ) + } + + /** + * Http 상태 코드와 메시지 코드를 포함하는 API 응답 성공 객체 생성한다. + * + * @param status Http 상태 코드 + * @param code 메시지 코드 + * @return API 응답 객체 + */ + fun success( + status: HttpStatus, + code: MessageCode + ): ApiResponse { + return ApiResponse(ApiResponse.Success(code.value), status) + } + + /** + * Http 상태 코드와 데이터를 포함하는 API 응답 성공 객체 생성한다. + * + * @param data 데이터 + * @param status Http 상태 코드 + * @return API 응답 객체 + * @param 데이터 타입 + */ + fun success( + data: D, + status: HttpStatus + ): ApiResponse> { + return ApiResponse( + ApiResponse.SuccessBody( + data, + MessageCode.SUCCESS.value + ), + status + ) + } + + /** + * Http 상태 코드, 데이터, 메시지 코드를 포함하는 API 응답 성공 객체 생성한다. + * + * @param data 데이터 + * @param status Http 상태 코드 + * @param code 메시지 코드 + * @return API 응답 객체 + * @param 데이터 타입 + */ + fun success( + data: D, + status: HttpStatus, + code: MessageCode + ): ApiResponse> { + return ApiResponse( + ApiResponse.SuccessBody(data, code.value), + status + ) + } + + /** + * Http 상태 코드만 포함하는 API 응답 실패 객체 생성한다. + * + * @param status Http 상태 코드 + * @return API 응답 객체 + */ + fun fail(status: HttpStatus): ApiResponse { + return ApiResponse(status) + } + + /** + * Http 상태 코드와 응답 실패 바디를 포함하는 API 응답 실패 객체 생성한다. + * + * @param body 응답 실패 바디 + * @param status Http 상태 코드 + * @return API 응답 객체 + */ + fun fail( + body: ApiResponse.FailureBody, + status: HttpStatus + ): ApiResponse { + return ApiResponse(body, status) + } + + /** + * Http 상태 코드와 코드 그리고 응답 실패 바디를 포함하는 API 응답 실패 객체 생성한다. + * + * @param message 메시지 + * @param status Http 상태 코드 + * @return API 응답 객체 + */ + fun fail( + message: String, + status: HttpStatus + ): ApiResponse { + return ApiResponse(ApiResponse.FailureBody(message), status) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/web/support/MessageCode.kt b/api/src/main/kotlin/com/few/api/web/support/MessageCode.kt new file mode 100644 index 000000000..526e8683a --- /dev/null +++ b/api/src/main/kotlin/com/few/api/web/support/MessageCode.kt @@ -0,0 +1,16 @@ +package com.few.api.web.support + +/** 메시지 코드 */ +enum class MessageCode(val code: String, val value: String) { + /** 성공 메시지 코드 */ + SUCCESS("success", "성공"), + + /** 삭제 메시지 코드 */ + RESOURCE_DELETED("resource.deleted", "삭제되었습니다."), + + /** 수정 메시지 코드 */ + RESOURCE_UPDATED("resource.updated", "수정되었습니다."), + + /** 생성 메시지 코드 */ + RESOURCE_CREATED("resource.created", "새로 생성되었습니다.") +} \ No newline at end of file