From cc4739711cc4a442ab4d2bbbd1d1aad3d64a7e81 Mon Sep 17 00:00:00 2001 From: Mauve Date: Tue, 8 Sep 2026 17:12:13 +0800 Subject: [PATCH] first commit --- .gitignore | 4 + pom.xml | 165 +++++ .../apigateway/CRMApiGatewayApplication.java | 27 + .../dtos/request/BlockCodeChannelDto.java | 19 + .../request/DlAllotmentUpdateRequest.java | 14 + .../models/CfgAppChannelDaylight.java | 30 + .../models/CfgAppChannelDaylightKey.java | 29 + .../models/CfgDlRoomTypeAllotment.java | 61 ++ .../models/CfgRoomCriteriasDaylight.java | 63 ++ .../repositories/AppChannelDaylightDao.java | 13 + .../DaylightRoomTypeAllotmentDao.java | 30 + .../repositories/RoomCriteriaDaylightDao.java | 23 + .../services/RoomCriteriaDaylightService.java | 200 +++++ .../appsetting/models/CfgSettings.java | 21 + .../appsetting/models/SysSetting.java | 26 + .../appsetting/repositories/SettingDao.java | 10 + .../repositories/SysSettingDao.java | 19 + .../appsetting/services/SettingService.java | 28 + .../services/SysSettingService.java | 32 + .../mapping/models/CfgBlockCodes.java | 45 ++ .../basesetting/mapping/models/CfgBlocks.java | 35 + .../mapping/models/CfgBookingTypes.java | 29 + .../mapping/models/CfgDaylightView.java | 115 +++ .../mapping/models/CfgMarketCodes.java | 33 + .../mapping/models/CfgRateCodes.java | 32 + .../mapping/models/CfgSourceCodes.java | 33 + .../mapping/repositories/BlockCodeDao.java | 9 + .../mapping/repositories/BlockDao.java | 15 + .../mapping/repositories/DaylightViewDao.java | 68 ++ .../mapping/repositories/MarketCodeDao.java | 14 + .../mapping/repositories/RateCodeDao.java | 13 + .../mapping/repositories/SourceCodeDao.java | 8 + .../property/models/CfgProperties.java | 44 ++ .../property/repositories/PropertyDao.java | 24 + .../basesetting/room/models/CfgRoomTypes.java | 34 + .../basesetting/room/models/CfgRooms.java | 75 ++ .../room/models/CfgRoomsRoomTypes.java | 27 + .../room/models/CfgRoomsRoomTypesKey.java | 46 ++ .../room/repositories/RoomDao.java | 27 + .../room/repositories/RoomRoomTypeDao.java | 12 + .../room/repositories/RoomTypeDao.java | 11 + .../basesetting/team/models/AppUsers.java | 57 ++ .../team/models/CfgDepartments.java | 34 + .../basesetting/team/models/CfgDivisions.java | 34 + .../team/models/CfgOtherUserMapping.java | 46 ++ .../basesetting/team/models/CfgSections.java | 27 + .../team/models/CfgTeamStructure.java | 37 + .../team/models/CfgTeamStructureView.java | 68 ++ .../team/repositories/AppUserDao.java | 9 + .../team/repositories/DepartmentDao.java | 13 + .../team/repositories/DivisionDao.java | 16 + .../team/repositories/SectionDao.java | 14 + .../team/repositories/TeamStructureDao.java | 14 + .../repositories/TeamStructureViewDao.java | 21 + .../common/config/DataSourceConfig.java | 30 + .../common/config/DaylightProxyConfig.java | 89 +++ .../common/config/EmailProxyConfig.java | 52 ++ .../common/config/EsbProxyConfig.java | 77 ++ .../apigateway/common/config/HttpHeader.java | 26 + .../common/config/MGMDataSourceConfig.java | 72 ++ .../common/config/OperaDataSourceConfig.java | 66 ++ .../common/config/OperaProxyConfig.java | 57 ++ .../common/config/SwaggerConfig.java | 18 + .../common/config/WebFilterConfig.java | 32 + .../common/constants/Constants.java | 173 +++++ .../common/constants/SettingConstants.java | 60 ++ .../common/constants/SysCommon.java | 102 +++ .../common/constants/SysConstants.java | 48 ++ .../common/dtos/BaseApiResponse.java | 12 + .../apigateway/common/dtos/CachedToken.java | 21 + .../apigateway/common/dtos/HttpReqResp.java | 69 ++ .../apigateway/common/dtos/HttpResult.java | 15 + .../apigateway/common/dtos/ResFileResult.java | 11 + .../apigateway/common/dtos/ServiceResult.java | 12 + .../dtos/config/DaylightApiSetting.java | 47 ++ .../common/dtos/config/EmailProxySetting.java | 34 + .../common/dtos/config/EsbApiSetting.java | 42 ++ .../common/dtos/config/OperaApiSetting.java | 38 + .../dtos/webhook/BatchWebhookEventEntry.java | 17 + .../dtos/webhook/ShijiBatchWebhookEvent.java | 11 + .../common/enums/DeliveryReceiptState.java | 56 ++ .../common/enums/EumnDaylightStatus.java | 87 +++ .../apigateway/common/enums/HttpMethods.java | 74 ++ .../common/exception/NoAccessException.java | 8 + .../common/httpclient/HttpCookieContext.java | 46 ++ .../common/httpclient/HttpEntityBuilder.java | 310 ++++++++ .../common/httpclient/HttpHeaderBuilder.java | 697 ++++++++++++++++++ .../httpclient/HttpParameterBuilder.java | 72 ++ .../common/httpclient/HttpRequestOptions.java | 375 ++++++++++ .../common/httpclient/HttpSslSupport.java | 197 +++++ .../common/listener/ConfigListener.java | 33 + .../apigateway/common/utils/DateTimeUtil.java | 169 +++++ .../mcrm/apigateway/common/utils/LogUtil.java | 68 ++ .../apigateway/common/utils/NetworkUtil.java | 355 +++++++++ .../common/utils/mockup/HttpUtilMockup.java | 261 +++++++ .../controllers/DaylightTestController.java | 27 + .../daylight/common/dtos/CachedToken.java | 12 + .../daylight/common/dtos/TokenResponse.java | 18 + .../DaylightConfigurationScheduler.java | 120 +++ .../DaylightFetchConfigurationService.java | 651 ++++++++++++++++ .../common/services/DaylightTokenService.java | 227 ++++++ .../models/CfgDaylightConfiguration.java | 32 + .../SysLogDaylightConfigurationSync.java | 35 + .../CfgDaylightConfigurationDao.java | 32 + .../SysLogDaylightConfigurationSyncDao.java | 14 + .../webhook/controllers/DaylightWebhook.java | 65 ++ .../dtos/reservation/BreakdownEntity.java | 18 + .../dtos/reservation/CheckInDataEntity.java | 9 + .../dtos/reservation/ContactEntity.java | 11 + .../reservation/ContactProfileEntity.java | 11 + .../dtos/reservation/DiscountEntity.java | 10 + .../dtos/reservation/EmbeddedEntity.java | 13 + .../reservation/ExternalSystemIdEntity.java | 9 + .../reservation/GuaranteedTimeEntity.java | 9 + .../webhook/dtos/reservation/GuestEntity.java | 9 + .../dtos/reservation/GuestMessageEntity.java | 7 + .../dtos/reservation/LinkedCompanyEntity.java | 9 + .../dtos/reservation/PolicyEntity.java | 7 + .../dtos/reservation/PreferenceEntity.java | 10 + .../webhook/dtos/reservation/PriceEntity.java | 11 + .../reservation/ProjectedRevenueEntity.java | 13 + .../reservation/PurchaseElementEntity.java | 7 + .../reservation/RatePricingDetailsEntity.java | 11 + .../dtos/reservation/ReasonEntity.java | 9 + .../ReservationNotificationEvent.java | 60 ++ .../dtos/reservation/RevenueAmountEntity.java | 12 + .../dtos/reservation/RoomDetailEntity.java | 9 + .../dtos/reservation/StatusCodeEntity.java | 9 + .../reservation/TransportationEntity.java | 7 + .../reservation/TransportationsEntity.java | 9 + .../dtos/reservation/WalkInDataEntity.java | 9 + .../common/AllocationsVariantsEntity.java | 8 + .../room/common/ChangeAvailabilityEntity.java | 15 + .../room/common/RoomAvailabilityEntity.java | 14 + .../dtos/room/common/RoomCounterEntity.java | 13 + .../AvailableRoomCountUpdatedEvent.java | 11 + .../roomcount/DateAvailabilityEntity.java | 14 + .../roomcount/PropertyAvailabilityEntity.java | 10 + .../roomcount/RoomTypeAvailabilityEntity.java | 12 + .../room/roomtype/AvailabilityEntity.java | 13 + .../DateAvailabilityChangeEntity.java | 13 + .../RoomTypeDateAvailabilityChangedEvent.java | 13 + .../eventhandlers/DaylightWebhookHandler.java | 11 + .../AvailableRoomCountUpdatedHandler.java | 69 ++ .../impl/ReservationNotificationHandler.java | 117 +++ ...oomTypeDateAvailabilityChangedHandler.java | 70 ++ .../services/DaylightWebhookFactory.java | 19 + .../apigateway/esb/common/dtos/EsbToken.java | 17 + .../dtos/mgmapi/PlayerProfileApiResult.java | 188 +++++ .../common/dtos/mgmapi/UserInfoApiRecord.java | 226 ++++++ .../common/dtos/mgmapi/UserInfoApiResult.java | 31 + .../esb/common/services/EsbTokenService.java | 158 ++++ .../esb/common/services/MGMApiService.java | 123 ++++ .../esb/player/services/MgmUserService.java | 150 ++++ .../filters/ApiGatewayLoggingFilter.java | 175 +++++ .../logging/filters/WebhookLoggingFilter.java | 162 ++++ .../logging/models/ApiGatewayLog.java | 43 ++ .../apigateway/logging/models/WebhookLog.java | 46 ++ .../repositories/ApiGatewayLogDao.java | 14 + .../logging/repositories/WebhookLogDao.java | 16 + .../services/ApiGatewayLogService.java | 29 + .../logging/services/WebhookLogService.java | 34 + .../controllers/NotificationController.java | 72 ++ .../notification/dtos/EmailProxyRequest.java | 29 + .../notification/dtos/SendEmailRequest.java | 19 + .../services/EmailNotificationService.java | 102 +++ .../player/services/PlayerService.java | 103 +++ .../proxy/config/WebClientConfig.java | 273 +++++++ .../proxy/controllers/WebProxyController.java | 173 +++++ .../exception/HttpProxyErrorHandler.java | 16 + .../proxy/exception/ProxyErrorHandler.java | 9 + .../exception/SoapProxyErrorHandler.java | 24 + .../proxy/services/WebProxyService.java | 167 +++++ .../reservation/models/Bookings.java | 219 ++++++ .../reservation/models/BookingsView.java | 263 +++++++ .../reservation/repositories/BookingDao.java | 16 + .../reservation/services/BookingService.java | 380 ++++++++++ .../services/OperaDBBookingService.java | 40 + .../system/models/SysGroupPermission.java | 23 + .../system/models/SysGroupPermissionKey.java | 38 + .../apigateway/system/models/SysUserView.java | 45 ++ .../repositories/SysGroupPermissionDao.java | 19 + .../system/repositories/SysUserViewDao.java | 14 + src/main/resources/application-dev.yml | 140 ++++ src/main/resources/application.yml | 3 + src/main/resources/i18n/messages.properties | 0 .../resources/i18n/messages_en_US.properties | 26 + .../resources/i18n/messages_zh_CN.properties | 26 + .../resources/i18n/messages_zh_TW.properties | 26 + src/main/resources/logback.xml | 60 ++ src/main/resources/mockup/app/getBlock.json | 20 + .../mockup/app/getBookingsByNameId.json | 283 +++++++ .../resources/mockup/app/getCriteria.json | 68 ++ .../resources/mockup/app/getProperty.json | 37 + src/main/resources/mockup/app/getRoom.json | 53 ++ .../resources/mockup/app/getRoomType.json | 68 ++ src/main/resources/mockup/app/getSpecial.json | 28 + .../mockup/app/getTodoDashboard_1.json | 76 ++ .../mockup/app/getTodoDashboard_2.json | 87 +++ .../mockup/app/getTodoDashboard_3.json | 66 ++ .../mockup/app/getTodoDashboard_4.json | 85 +++ .../resources/mockup/app/getTodoDetail_1.json | 67 ++ .../resources/mockup/app/getTodoDetail_2.json | 63 ++ .../resources/mockup/app/getTodoDetail_3.json | 68 ++ .../resources/mockup/app/getTodoDetail_4.json | 64 ++ .../resources/mockup/app/getTodoList_1.json | 216 ++++++ .../resources/mockup/app/getTodoList_2.json | 58 ++ .../resources/mockup/app/getTodoList_3.json | 34 + .../resources/mockup/app/getTodoList_4.json | 86 +++ .../resources/mockup/app/getTodoList_5.json | 84 +++ .../resources/mockup/app/getTodoList_6.json | 83 +++ .../resources/mockup/app/getTodoList_7.json | 58 ++ .../resources/mockup/app/getTodoList_8.json | 87 +++ .../resources/mockup/app/getTodoList_9.json | 84 +++ .../resources/mockup/app/playerProfile.json | 11 + src/main/resources/mockup/app/submitETA.json | 4 + .../http/check_player_tpd_all_pass.json | 18 + .../http/check_player_tpd_not_all_pass.json | 18 + .../http/check_player_tpd_partly_pass.json | 18 + .../http/get_player_profile_api_res1.json | 19 + .../http/get_player_profile_api_res10.json | 19 + .../http/get_player_profile_api_res11.json | 19 + .../http/get_player_profile_api_res12.json | 19 + .../http/get_player_profile_api_res13.json | 19 + .../http/get_player_profile_api_res14.json | 19 + .../http/get_player_profile_api_res15.json | 19 + .../http/get_player_profile_api_res16.json | 19 + .../http/get_player_profile_api_res17.json | 19 + .../http/get_player_profile_api_res18.json | 19 + .../http/get_player_profile_api_res19.json | 19 + .../http/get_player_profile_api_res2.json | 19 + .../http/get_player_profile_api_res20.json | 19 + .../http/get_player_profile_api_res21.json | 19 + .../http/get_player_profile_api_res22.json | 19 + .../http/get_player_profile_api_res23.json | 19 + .../http/get_player_profile_api_res24.json | 19 + .../http/get_player_profile_api_res25.json | 19 + .../http/get_player_profile_api_res26.json | 19 + .../http/get_player_profile_api_res27.json | 19 + .../http/get_player_profile_api_res28.json | 19 + .../http/get_player_profile_api_res29.json | 19 + .../http/get_player_profile_api_res3.json | 19 + .../http/get_player_profile_api_res30.json | 19 + .../http/get_player_profile_api_res31.json | 19 + .../http/get_player_profile_api_res4.json | 19 + .../http/get_player_profile_api_res5.json | 19 + .../http/get_player_profile_api_res6.json | 19 + .../http/get_player_profile_api_res7.json | 19 + .../http/get_player_profile_api_res8.json | 19 + .../http/get_player_profile_api_res9.json | 19 + .../mockup/http/get_user_info_api_res1.json | 16 + .../mockup/http/get_user_info_api_res10.json | 16 + .../mockup/http/get_user_info_api_res11.json | 16 + .../mockup/http/get_user_info_api_res12.json | 16 + .../mockup/http/get_user_info_api_res13.json | 16 + .../mockup/http/get_user_info_api_res14.json | 16 + .../mockup/http/get_user_info_api_res15.json | 16 + .../mockup/http/get_user_info_api_res16.json | 16 + .../mockup/http/get_user_info_api_res17.json | 16 + .../mockup/http/get_user_info_api_res2.json | 16 + .../mockup/http/get_user_info_api_res3.json | 16 + .../mockup/http/get_user_info_api_res4.json | 16 + .../mockup/http/get_user_info_api_res5.json | 16 + .../mockup/http/get_user_info_api_res6.json | 16 + .../mockup/http/get_user_info_api_res7.json | 16 + .../mockup/http/get_user_info_api_res8.json | 16 + .../mockup/http/get_user_info_api_res9.json | 16 + .../http/non_mgm_booking_no_record.json | 6 + .../http/non_mgm_booking_with_record.json | 24 + .../resources/mockup/mgm_api_mockup_def.json | 380 ++++++++++ src/main/resources/mockup/system/login.json | 16 + src/main/resources/static/WM.png | Bin 0 -> 8468 bytes src/main/resources/static/WP.png | Bin 0 -> 10052 bytes src/main/resources/static/logon1.png | Bin 0 -> 38970 bytes 274 files changed, 14961 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/mgm/mcrm/apigateway/CRMApiGatewayApplication.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/dtos/request/BlockCodeChannelDto.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/dtos/request/DlAllotmentUpdateRequest.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgAppChannelDaylight.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgAppChannelDaylightKey.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgDlRoomTypeAllotment.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgRoomCriteriasDaylight.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/AppChannelDaylightDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/DaylightRoomTypeAllotmentDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/RoomCriteriaDaylightDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/allotment/services/RoomCriteriaDaylightService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/appsetting/models/CfgSettings.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/appsetting/models/SysSetting.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/appsetting/repositories/SettingDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/appsetting/repositories/SysSettingDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/appsetting/services/SettingService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/appsetting/services/SysSettingService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBlockCodes.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBlocks.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBookingTypes.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgDaylightView.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgMarketCodes.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgRateCodes.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgSourceCodes.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/BlockCodeDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/BlockDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/DaylightViewDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/MarketCodeDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/RateCodeDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/SourceCodeDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/property/models/CfgProperties.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/property/repositories/PropertyDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomTypes.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRooms.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomsRoomTypes.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomsRoomTypesKey.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomRoomTypeDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomTypeDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/AppUsers.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgDepartments.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgDivisions.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgOtherUserMapping.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgSections.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgTeamStructure.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgTeamStructureView.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/AppUserDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/DepartmentDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/DivisionDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/SectionDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/TeamStructureDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/TeamStructureViewDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/DataSourceConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/DaylightProxyConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/EmailProxyConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/EsbProxyConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/HttpHeader.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/MGMDataSourceConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/OperaDataSourceConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/OperaProxyConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/SwaggerConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/config/WebFilterConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/constants/Constants.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/constants/SettingConstants.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/constants/SysCommon.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/constants/SysConstants.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/BaseApiResponse.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/CachedToken.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/HttpReqResp.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/HttpResult.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/ResFileResult.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/ServiceResult.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/DaylightApiSetting.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/EmailProxySetting.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/EsbApiSetting.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/OperaApiSetting.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/webhook/BatchWebhookEventEntry.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/dtos/webhook/ShijiBatchWebhookEvent.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/enums/DeliveryReceiptState.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/enums/EumnDaylightStatus.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/enums/HttpMethods.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/exception/NoAccessException.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpCookieContext.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpEntityBuilder.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpHeaderBuilder.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpParameterBuilder.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpRequestOptions.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpSslSupport.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/listener/ConfigListener.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/utils/DateTimeUtil.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/utils/LogUtil.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/utils/NetworkUtil.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/common/utils/mockup/HttpUtilMockup.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/common/controllers/DaylightTestController.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/CachedToken.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/TokenResponse.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/common/scheduler/DaylightConfigurationScheduler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightFetchConfigurationService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightTokenService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/models/CfgDaylightConfiguration.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/models/SysLogDaylightConfigurationSync.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/CfgDaylightConfigurationDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/SysLogDaylightConfigurationSyncDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/controllers/DaylightWebhook.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/BreakdownEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/CheckInDataEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ContactEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ContactProfileEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/DiscountEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/EmbeddedEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ExternalSystemIdEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuaranteedTimeEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuestEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuestMessageEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/LinkedCompanyEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PolicyEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PreferenceEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PriceEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ProjectedRevenueEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PurchaseElementEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RatePricingDetailsEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReasonEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationNotificationEvent.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RevenueAmountEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RoomDetailEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/StatusCodeEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/TransportationEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/TransportationsEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/WalkInDataEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/AllocationsVariantsEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/ChangeAvailabilityEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/RoomAvailabilityEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/RoomCounterEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/AvailableRoomCountUpdatedEvent.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/DateAvailabilityEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/PropertyAvailabilityEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/RoomTypeAvailabilityEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/AvailabilityEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/DateAvailabilityChangeEntity.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/RoomTypeDateAvailabilityChangedEvent.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/DaylightWebhookHandler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/AvailableRoomCountUpdatedHandler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationNotificationHandler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/RoomTypeDateAvailabilityChangedHandler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/services/DaylightWebhookFactory.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/EsbToken.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/PlayerProfileApiResult.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/UserInfoApiRecord.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/UserInfoApiResult.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/esb/common/services/EsbTokenService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/esb/common/services/MGMApiService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/esb/player/services/MgmUserService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/filters/ApiGatewayLoggingFilter.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/filters/WebhookLoggingFilter.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/models/ApiGatewayLog.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/models/WebhookLog.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/repositories/ApiGatewayLogDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/repositories/WebhookLogDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/services/ApiGatewayLogService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/services/WebhookLogService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/notification/controllers/NotificationController.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/notification/dtos/EmailProxyRequest.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/notification/dtos/SendEmailRequest.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/notification/services/EmailNotificationService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/player/services/PlayerService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/proxy/config/WebClientConfig.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/proxy/controllers/WebProxyController.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/proxy/exception/HttpProxyErrorHandler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/proxy/exception/ProxyErrorHandler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/proxy/exception/SoapProxyErrorHandler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/proxy/services/WebProxyService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/reservation/models/Bookings.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/reservation/models/BookingsView.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/reservation/repositories/BookingDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/reservation/services/BookingService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/reservation/services/OperaDBBookingService.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/system/models/SysGroupPermission.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/system/models/SysGroupPermissionKey.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/system/models/SysUserView.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/system/repositories/SysGroupPermissionDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/system/repositories/SysUserViewDao.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/i18n/messages.properties create mode 100644 src/main/resources/i18n/messages_en_US.properties create mode 100644 src/main/resources/i18n/messages_zh_CN.properties create mode 100644 src/main/resources/i18n/messages_zh_TW.properties create mode 100644 src/main/resources/logback.xml create mode 100644 src/main/resources/mockup/app/getBlock.json create mode 100644 src/main/resources/mockup/app/getBookingsByNameId.json create mode 100644 src/main/resources/mockup/app/getCriteria.json create mode 100644 src/main/resources/mockup/app/getProperty.json create mode 100644 src/main/resources/mockup/app/getRoom.json create mode 100644 src/main/resources/mockup/app/getRoomType.json create mode 100644 src/main/resources/mockup/app/getSpecial.json create mode 100644 src/main/resources/mockup/app/getTodoDashboard_1.json create mode 100644 src/main/resources/mockup/app/getTodoDashboard_2.json create mode 100644 src/main/resources/mockup/app/getTodoDashboard_3.json create mode 100644 src/main/resources/mockup/app/getTodoDashboard_4.json create mode 100644 src/main/resources/mockup/app/getTodoDetail_1.json create mode 100644 src/main/resources/mockup/app/getTodoDetail_2.json create mode 100644 src/main/resources/mockup/app/getTodoDetail_3.json create mode 100644 src/main/resources/mockup/app/getTodoDetail_4.json create mode 100644 src/main/resources/mockup/app/getTodoList_1.json create mode 100644 src/main/resources/mockup/app/getTodoList_2.json create mode 100644 src/main/resources/mockup/app/getTodoList_3.json create mode 100644 src/main/resources/mockup/app/getTodoList_4.json create mode 100644 src/main/resources/mockup/app/getTodoList_5.json create mode 100644 src/main/resources/mockup/app/getTodoList_6.json create mode 100644 src/main/resources/mockup/app/getTodoList_7.json create mode 100644 src/main/resources/mockup/app/getTodoList_8.json create mode 100644 src/main/resources/mockup/app/getTodoList_9.json create mode 100644 src/main/resources/mockup/app/playerProfile.json create mode 100644 src/main/resources/mockup/app/submitETA.json create mode 100644 src/main/resources/mockup/http/check_player_tpd_all_pass.json create mode 100644 src/main/resources/mockup/http/check_player_tpd_not_all_pass.json create mode 100644 src/main/resources/mockup/http/check_player_tpd_partly_pass.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res1.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res10.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res11.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res12.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res13.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res14.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res15.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res16.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res17.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res18.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res19.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res2.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res20.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res21.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res22.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res23.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res24.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res25.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res26.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res27.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res28.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res29.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res3.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res30.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res31.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res4.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res5.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res6.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res7.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res8.json create mode 100644 src/main/resources/mockup/http/get_player_profile_api_res9.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res1.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res10.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res11.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res12.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res13.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res14.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res15.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res16.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res17.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res2.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res3.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res4.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res5.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res6.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res7.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res8.json create mode 100644 src/main/resources/mockup/http/get_user_info_api_res9.json create mode 100644 src/main/resources/mockup/http/non_mgm_booking_no_record.json create mode 100644 src/main/resources/mockup/http/non_mgm_booking_with_record.json create mode 100644 src/main/resources/mockup/mgm_api_mockup_def.json create mode 100644 src/main/resources/mockup/system/login.json create mode 100644 src/main/resources/static/WM.png create mode 100644 src/main/resources/static/WP.png create mode 100644 src/main/resources/static/logon1.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2db5a77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/crm_runtime/logs/ +/target/ +/.idea/ +/.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..38b08b6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,165 @@ + + 4.0.0 + + mgm + MCRMApiGateway + 1.0.1-SNAPSHOT + jar + + MCRMApiGateway + + + org.springframework.boot + spring-boot-starter-parent + 3.5.7 + + + + + UTF-8 + 21 + ${java.version} + ${java.version} + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-activemq + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.microsoft.sqlserver + mssql-jdbc + + + org.apache.httpcomponents.client5 + httpclient5 + 5.5 + + + org.springframework.boot + spring-boot-starter-tomcat + + + com.alibaba + fastjson + 2.0.50 + + + org.apache.commons + commons-lang3 + 3.18.0 + + + org.apache.commons + commons-text + 1.14.0 + + + org.springframework.boot + spring-boot-devtools + true + + + org.projectlombok + lombok + provided + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.11 + + + org.mapstruct + mapstruct + 1.6.3 + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + org.mapstruct + mapstruct-processor + 1.6.3 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + com.mgm.mcrm.apigateway.CRMApiGatewayApplication + + + + + + repackage + + + + + + maven-compiler-plugin + + ${java.version} + ${java.version} + UTF-8 + + + + org.codehaus.mojo + license-maven-plugin + 2.7.0 + + + EPL-1.0 + LGPL-2.1 + + + + + + + MCRM API Gateway + diff --git a/src/main/java/com/mgm/mcrm/apigateway/CRMApiGatewayApplication.java b/src/main/java/com/mgm/mcrm/apigateway/CRMApiGatewayApplication.java new file mode 100644 index 0000000..9d07272 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/CRMApiGatewayApplication.java @@ -0,0 +1,27 @@ +package com.mgm.mcrm.apigateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.transaction.annotation.EnableTransactionManagement; + + +@SpringBootApplication +@EntityScan(basePackages="com.mgm.mcrm.apigateway") +@ComponentScan(basePackages="com.mgm.mcrm.apigateway") +@EnableTransactionManagement +@ServletComponentScan +@EnableScheduling +@EnableAsync +@EnableJms +public class CRMApiGatewayApplication { + + public static void main( String[] args ) { + SpringApplication.run(CRMApiGatewayApplication.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/dtos/request/BlockCodeChannelDto.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/dtos/request/BlockCodeChannelDto.java new file mode 100644 index 0000000..f06639a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/dtos/request/BlockCodeChannelDto.java @@ -0,0 +1,19 @@ +package com.mgm.mcrm.apigateway.allotment.dtos.request; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class BlockCodeChannelDto { + private Long blockCodeId; + private String code; + private String category; + private LocalDateTime startDate; + private LocalDateTime endDate; + private String sourceName; + private String dlBlockId; + private Integer channelTotal; + private Integer available; + private Integer pickedUp; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/dtos/request/DlAllotmentUpdateRequest.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/dtos/request/DlAllotmentUpdateRequest.java new file mode 100644 index 0000000..603e06c --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/dtos/request/DlAllotmentUpdateRequest.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.allotment.dtos.request; + +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class DlAllotmentUpdateRequest { + private Long roomId; + private Long blockCodeId; + private Long propertyId; + private LocalDate calendarDate; + private String updateSource; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgAppChannelDaylight.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgAppChannelDaylight.java new file mode 100644 index 0000000..a3bd3de --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgAppChannelDaylight.java @@ -0,0 +1,30 @@ +package com.mgm.mcrm.apigateway.allotment.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name="cfg_app_channel_daylight") +@IdClass(CfgAppChannelDaylightKey.class) +public class CfgAppChannelDaylight implements Serializable { + @Id + @Column(name="criteria_id") + private Long criteriaId; + @Id + @Column(name="block_code_id") + private Long blockCodeId; + @Column(name="created_at") + private LocalDateTime createdAt; + @Column(name="created_src") + private String createdSource; + @Column(name="channel_total") + private Integer channelTotal; + @Column(name="picked_up") + private Integer pickedUp; + @Column(name="available") + private Integer available; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgAppChannelDaylightKey.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgAppChannelDaylightKey.java new file mode 100644 index 0000000..b2d4552 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgAppChannelDaylightKey.java @@ -0,0 +1,29 @@ +package com.mgm.mcrm.apigateway.allotment.models; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Objects; + +@Data +public class CfgAppChannelDaylightKey implements Serializable{ + + private Long criteriaId; + private Long blockCodeId; + + @Override + public boolean equals(Object o) { + if(o instanceof CfgAppChannelDaylightKey key) + { + return Objects.equals(this.criteriaId, key.getCriteriaId()) && + Objects.equals(this.blockCodeId, key.getBlockCodeId()); + } + return false ; + } + + @Override + public int hashCode() { + return 1; + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgDlRoomTypeAllotment.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgDlRoomTypeAllotment.java new file mode 100644 index 0000000..ab6fd5b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgDlRoomTypeAllotment.java @@ -0,0 +1,61 @@ +package com.mgm.mcrm.apigateway.allotment.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name="cfg_daylight_roomtype_allotment") +public class CfgDlRoomTypeAllotment { + @Id + @Column(name = "allotment_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long allotmentId; + + @Column(name="allotment_date") + private String allotmentDate; + + @Column(name="dl_room_type_id") + private String daylightRoomTypeId; + + @Column(name="dl_property_id") + private String daylightPropertyId; + + @Column(name="dl_block_code_id") + private String daylightBlockCodeId; + + @Column(name="property_id") + private Long propertyId; + + @Column(name="room_type_id") + private Long roomTypeId; + + @Column(name="block_code_id") + private Long blockCodeId; + + @Column(name="total_rooms") + private Integer totalRooms; + + @Column(name="sell_limit") + private Integer sellLimit; + + @Column(name="over_booking_limit") + private Integer overBookingLimit; + + @Column(name="all_deduct") + private Integer allDeduct; + + @Column(name="all_non_deduct") + private Integer allNonDeduct; + + @Column(name="created_at") + private LocalDateTime createdAt; + + @Column(name="updated_at") + private LocalDateTime updatedAt; + + @Column(name="updated_source") + private String updateSource; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgRoomCriteriasDaylight.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgRoomCriteriasDaylight.java new file mode 100644 index 0000000..5022a6c --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/models/CfgRoomCriteriasDaylight.java @@ -0,0 +1,63 @@ +package com.mgm.mcrm.apigateway.allotment.models; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.mgm.mcrm.apigateway.allotment.dtos.request.BlockCodeChannelDto; +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +@Data +@Entity +@Table(name = "cfg_room_criterias_daylight") +public class CfgRoomCriteriasDaylight implements Serializable { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "property_id") + private Long propertyId; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @Column(name = "book_date") + private LocalDateTime bookDate; + + @Column(name = "room_id") + private Long roomId; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "created_by") + private String createdBy; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @Column(name = "updated_by") + private String updatedBy; + + @Column(name = "is_waitlist") + private Integer isWaitlist; + + @Column(name = "sharer") + private Integer sharer; + + @Column(name = "dow") + private Integer bookDow; + + @Transient + private List blockCodeChannels; + + @Column(name = "rate") + private Integer rate; + + + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn(name = "criteria_id", insertable = false, updatable = false) + private Set channels; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/AppChannelDaylightDao.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/AppChannelDaylightDao.java new file mode 100644 index 0000000..b0dc26d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/AppChannelDaylightDao.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.allotment.repositories; + +import com.mgm.mcrm.apigateway.allotment.models.CfgAppChannelDaylight; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + + +public interface AppChannelDaylightDao extends JpaRepository { + CfgAppChannelDaylight findByCriteriaIdAndBlockCodeId(Long criteriaId, Long blockCodeId); + + List findAllByCriteriaIdAndBlockCodeId(Long allotmentId, Long blockCodeId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/DaylightRoomTypeAllotmentDao.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/DaylightRoomTypeAllotmentDao.java new file mode 100644 index 0000000..3d35720 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/DaylightRoomTypeAllotmentDao.java @@ -0,0 +1,30 @@ +package com.mgm.mcrm.apigateway.allotment.repositories; + +import com.mgm.mcrm.apigateway.allotment.models.CfgDlRoomTypeAllotment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface DaylightRoomTypeAllotmentDao extends JpaRepository { + + @Query(value="select allot from CfgDlRoomTypeAllotment allot where daylightPropertyId=?1 and daylightRoomTypeId=?2 and daylightBlockCodeId=?3 and allotmentDate=?4 ") + List queryAllotmentByRoomTypeAndDate(String dlPropertyId, String dlRoomTypeId, String dlBlockCodeId, String checkDateVal); + + interface DaylightBlockCodeAllotmentSum{ + public String getCalendarDate(); + public Integer getChannelTotal(); + public Integer getPickedUp(); + public Integer getAvailable(); + } + + @Query(value = "select ?1 as calendarDate, sum(total_rooms) as channelTotal, sum(all_deduct) as pickedUp, sum(all_non_deduct) as available " + + " from cfg_daylight_roomtype_allotment " + + " where allotment_date=?1 and property_id =?2 and block_code_id =?4 and room_type_id in " + + " (select room_type_id from cfg_rooms_room_types where room_id =?3 )", nativeQuery = true) + DaylightBlockCodeAllotmentSum countRoomTypeAllotmentByBlockCode( + String calendarDate, + Long propertyId, + Long roomId, + Long blockCodeId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/RoomCriteriaDaylightDao.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/RoomCriteriaDaylightDao.java new file mode 100644 index 0000000..1fe29aa --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/repositories/RoomCriteriaDaylightDao.java @@ -0,0 +1,23 @@ +package com.mgm.mcrm.apigateway.allotment.repositories; + +import com.mgm.mcrm.apigateway.allotment.models.CfgRoomCriteriasDaylight; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDateTime; +import java.util.List; + + +public interface RoomCriteriaDaylightDao extends JpaRepository { + CfgRoomCriteriasDaylight findById(long id); + List findAll(Specification spec, Sort sort); + List findAll(Specification spec); + Page findAll(Specification spec, Pageable pageable); + + List findByRoomIdAndPropertyIdAndBookDate(Long roomId, Long propertyId, LocalDateTime bookDate); + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/allotment/services/RoomCriteriaDaylightService.java b/src/main/java/com/mgm/mcrm/apigateway/allotment/services/RoomCriteriaDaylightService.java new file mode 100644 index 0000000..2c75b6b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/allotment/services/RoomCriteriaDaylightService.java @@ -0,0 +1,200 @@ +package com.mgm.mcrm.apigateway.allotment.services; + + +import com.mgm.mcrm.apigateway.allotment.dtos.request.DlAllotmentUpdateRequest; +import com.mgm.mcrm.apigateway.allotment.models.CfgAppChannelDaylight; +import com.mgm.mcrm.apigateway.allotment.models.CfgDlRoomTypeAllotment; +import com.mgm.mcrm.apigateway.allotment.models.CfgRoomCriteriasDaylight; +import com.mgm.mcrm.apigateway.allotment.repositories.AppChannelDaylightDao; +import com.mgm.mcrm.apigateway.allotment.repositories.DaylightRoomTypeAllotmentDao; +import com.mgm.mcrm.apigateway.allotment.repositories.RoomCriteriaDaylightDao; +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgBlockCodes; +import com.mgm.mcrm.apigateway.basesetting.mapping.repositories.BlockCodeDao; +import com.mgm.mcrm.apigateway.basesetting.mapping.repositories.BlockDao; +import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; +import com.mgm.mcrm.apigateway.basesetting.property.repositories.PropertyDao; +import com.mgm.mcrm.apigateway.basesetting.room.models.CfgRoomTypes; +import com.mgm.mcrm.apigateway.basesetting.room.repositories.RoomDao; +import com.mgm.mcrm.apigateway.basesetting.room.repositories.RoomTypeDao; +import com.mgm.mcrm.apigateway.common.utils.LogUtil; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common.RoomCounterEntity; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +@Service +public class RoomCriteriaDaylightService { + + @Resource + private RoomCriteriaDaylightDao criteriaDaylightDao; + @Resource + private AppChannelDaylightDao appChannelDaylightDao; + @Resource + private PropertyDao propertyDao; + @Resource + private RoomDao roomDao; + @Resource + private BlockDao blockDao; + @Resource + private RoomTypeDao roomTypeDao; + @Resource + private BlockCodeDao blockCodeDao; + @Resource + private DaylightRoomTypeAllotmentDao daylightRoomTypeAllotmentDao; + + + public void processDaylightRoomTypeAllotmentUpdate(String updateSource, String dlPropertyId, String dlRoomTypeId, String dlBlockCodeId, String fromDate, String toDate, RoomCounterEntity roomCounter) { + if (StringUtils.isNotBlank(fromDate)) { + Long propertyId = null; + Long roomTypeId = null; + Long blockCodeId = null; + Long roomId = null; + CfgProperties cfgProperty = propertyDao.findByDlPropertyId(dlPropertyId); + if (cfgProperty != null) { + propertyId = cfgProperty.getId(); + } else { + LogUtil.warn(RoomCriteriaDaylightService.class, "[" + updateSource + "]No property config match with daylight ID[" + dlPropertyId + "]"); + } + CfgRoomTypes cfgRoomType = roomTypeDao.findByDlRoomTypeId(dlRoomTypeId); + if (propertyId!=null && cfgRoomType != null) { + roomTypeId = cfgRoomType.getId(); + roomId = roomDao.queryPropertyRoomIdByRoomTypeId(propertyId, roomTypeId); + if(roomId==null){ + LogUtil.warn(RoomCriteriaDaylightService.class, "[" + updateSource + "]No Room Category(RoomId) config match with room type ID[" + roomTypeId + "]"); + } + } else { + LogUtil.warn(RoomCriteriaDaylightService.class, "[" + updateSource + "]No Room Type config match with daylight ID[" + dlRoomTypeId + "]"); + } + if (StringUtils.isNotBlank(dlBlockCodeId)) { + CfgBlockCodes cfgBlockCode = blockCodeDao.findByDlBlockId(dlBlockCodeId); + if (cfgBlockCode != null) { + blockCodeId = cfgBlockCode.getId(); + }else{ + LogUtil.warn(RoomCriteriaDaylightService.class, "[" + updateSource + "]No Block code config match with daylight ID[" + dlBlockCodeId + "]"); + } + } + LocalDate allotmentDate = LocalDate.parse(fromDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + LocalDate endDate = null; + if (StringUtils.isNotBlank(toDate)) { + endDate = LocalDate.parse(toDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } else { + endDate = allotmentDate.plusDays(1); + } + List updateAllotmentRequests = new ArrayList(); + while (allotmentDate.isBefore(endDate)) { + String checkDateVal = allotmentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + CfgDlRoomTypeAllotment matchAllotment = null; + List roomAllotments = daylightRoomTypeAllotmentDao.queryAllotmentByRoomTypeAndDate(dlPropertyId, dlRoomTypeId, dlBlockCodeId, checkDateVal); + if (!roomAllotments.isEmpty()) { + matchAllotment = roomAllotments.get(0); + //-- Keep only one allotment for check date and room type + for (int i = 1; i < roomAllotments.size(); i++) { + daylightRoomTypeAllotmentDao.delete(roomAllotments.get(i)); + } + } + if (matchAllotment == null) { + matchAllotment = new CfgDlRoomTypeAllotment(); + matchAllotment.setAllotmentDate(checkDateVal); + matchAllotment.setDaylightPropertyId(dlPropertyId); + matchAllotment.setDaylightRoomTypeId(dlRoomTypeId); + matchAllotment.setDaylightBlockCodeId(dlBlockCodeId); + matchAllotment.setPropertyId(propertyId); + matchAllotment.setRoomTypeId(roomTypeId); + matchAllotment.setBlockCodeId(blockCodeId); + matchAllotment.setCreatedAt(LocalDateTime.now()); + } + matchAllotment.setTotalRooms(roomCounter.getTotalRooms()); + matchAllotment.setSellLimit(roomCounter.getSellLimit()); + matchAllotment.setOverBookingLimit(roomCounter.getOverbookingLimit()); + matchAllotment.setAllDeduct(roomCounter.getAllDeduct()); + matchAllotment.setAllNonDeduct(roomCounter.getAllNonDeduct()); + matchAllotment.setUpdatedAt(LocalDateTime.now()); + matchAllotment.setUpdateSource(updateSource); + daylightRoomTypeAllotmentDao.save(matchAllotment); + //--Create update request to sync daylight allotment data + if(roomId !=null && blockCodeId!=null){ + DlAllotmentUpdateRequest syncRequest = new DlAllotmentUpdateRequest(); + syncRequest.setPropertyId(propertyId); + syncRequest.setBlockCodeId(blockCodeId); + syncRequest.setRoomId(roomId); + syncRequest.setCalendarDate(allotmentDate); + syncRequest.setUpdateSource("Daylight Webhook"); + updateAllotmentRequests.add(syncRequest); + } + //-- + allotmentDate = allotmentDate.plusDays(1); + } + if(!updateAllotmentRequests.isEmpty()){ + for(DlAllotmentUpdateRequest syncRequest:updateAllotmentRequests){ + this.calculateDaylightAllotment(syncRequest); + } + } + } + } + + public void calculateDaylightAllotment(DlAllotmentUpdateRequest syncRequest) { + if(syncRequest!=null){ + CfgRoomCriteriasDaylight roomAllotment = null; + List roomCriterias = criteriaDaylightDao.findByRoomIdAndPropertyIdAndBookDate( + syncRequest.getRoomId(), + syncRequest.getPropertyId(), + syncRequest.getCalendarDate().atStartOfDay() + ); + if(!roomCriterias.isEmpty()){ + roomAllotment = roomCriterias.get(0); + if(roomCriterias.size()>1){ + //-- Remove duplicated records if has + for(int i=1;i existAppChannels = appChannelDaylightDao.findAllByCriteriaIdAndBlockCodeId(allotmentId,syncRequest.getBlockCodeId()); + if(!existAppChannels.isEmpty()){ + appChannel = existAppChannels.get(0); + for(int i=1;i { + @Query(value = "select * from cfg_settings with (nolock) where keys = ?1",nativeQuery=true) + CfgSettings getByKeys(String keys); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/appsetting/repositories/SysSettingDao.java b/src/main/java/com/mgm/mcrm/apigateway/appsetting/repositories/SysSettingDao.java new file mode 100644 index 0000000..90b3354 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/appsetting/repositories/SysSettingDao.java @@ -0,0 +1,19 @@ +package com.mgm.mcrm.apigateway.appsetting.repositories; + +import java.util.List; + +import com.mgm.mcrm.apigateway.appsetting.models.SysSetting; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + + +public interface SysSettingDao extends JpaRepository { + + @Query(value="select * from sys_settings where setting_key=?1",nativeQuery=true) + SysSetting getBySettingKey(String key); + + List findAll(Specification spec,Sort sort); + List findAll(Specification spec); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/appsetting/services/SettingService.java b/src/main/java/com/mgm/mcrm/apigateway/appsetting/services/SettingService.java new file mode 100644 index 0000000..1990d81 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/appsetting/services/SettingService.java @@ -0,0 +1,28 @@ +package com.mgm.mcrm.apigateway.appsetting.services; + +import java.util.List; + +import com.mgm.mcrm.apigateway.appsetting.models.CfgSettings; +import com.mgm.mcrm.apigateway.appsetting.repositories.SettingDao; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + + +@Service +public class SettingService { + + @Resource + private SettingDao settingDao; + + public CfgSettings getSettingByKey(String key) { + CfgSettings setting = null; + setting = settingDao.getByKeys(key); + return setting; + } + + public List getSysSettingsAll() { + return settingDao.findAll(); + } + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/appsetting/services/SysSettingService.java b/src/main/java/com/mgm/mcrm/apigateway/appsetting/services/SysSettingService.java new file mode 100644 index 0000000..f795065 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/appsetting/services/SysSettingService.java @@ -0,0 +1,32 @@ +package com.mgm.mcrm.apigateway.appsetting.services; + +import java.util.List; +import com.mgm.mcrm.apigateway.appsetting.models.SysSetting; +import com.mgm.mcrm.apigateway.appsetting.repositories.SysSettingDao; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +@Service +public class SysSettingService { + + @Resource + private SysSettingDao sysSettingDao; + + public SysSetting getSysSettingByKey(String key) { + SysSetting sysSetting = null; + sysSetting = sysSettingDao.getBySettingKey(key); + return sysSetting; + } + + public List getSysSettingsAll() { + List list = sysSettingDao.findAll(); + return list; + } + + public boolean isMockReplicate() { + SysSetting sysSetting = null; + sysSetting = sysSettingDao.getBySettingKey("APP_MOCK_REPLICATE"); + return "1".equals(sysSetting.getSettingValue()); + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBlockCodes.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBlockCodes.java new file mode 100644 index 0000000..1b4a2e1 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBlockCodes.java @@ -0,0 +1,45 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.models; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Entity +@Table(name="cfg_block_codes") +@Data +public class CfgBlockCodes implements Serializable { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="block_id") + private Long blockId; + @Column(name="code") + private String code; + @Column(name = "category") + private String category; + @Column(name="created_at") + private LocalDateTime createdAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_at") + private LocalDateTime updatedAt; + @Column(name="updated_by") + private String updatedBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") + @Column(name="start_date") + private LocalDateTime startDate; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") + @Column(name="end_date") + private LocalDateTime endDate; + @Column(name="sourceName") + private String sourceName; + + @Column(name = "dl_block_id") + private String dlBlockId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBlocks.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBlocks.java new file mode 100644 index 0000000..7cd492e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBlocks.java @@ -0,0 +1,35 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Entity +@Table(name="cfg_blocks") +@Data +public class CfgBlocks implements Serializable { + //fields in table + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="name") + private String name; + @Column(name="created_at") + private LocalDateTime createdAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_at") + private LocalDateTime updatedAt; + @Column(name="updated_by") + private String updatedBy; + + @Transient + private Integer appChannel; + @Transient + private Integer available; + @Transient + private Integer pickedUp; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBookingTypes.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBookingTypes.java new file mode 100644 index 0000000..0a7a118 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgBookingTypes.java @@ -0,0 +1,29 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "cfg_booking_types") +public class CfgBookingTypes implements Serializable { + //fields in table + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "name") + private String name; + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "created_by") + private String createdBy; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @Column(name = "updated_by") + private String updatedBy; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgDaylightView.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgDaylightView.java new file mode 100644 index 0000000..1bda6d4 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgDaylightView.java @@ -0,0 +1,115 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.models; + +import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; +import com.mgm.mcrm.apigateway.basesetting.room.models.CfgRoomTypes; +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgSections; +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Set; + +@Entity +@Table(name = "view_daylight") +@NamedEntityGraph( + name = "graph.DaylightMapping.fulldetails", + attributeNodes = { + @NamedAttributeNode("section"), + @NamedAttributeNode("property"), + @NamedAttributeNode("bookingType"), + @NamedAttributeNode("rateCode"), + @NamedAttributeNode("sourceCode"), + @NamedAttributeNode("marketCode"), + @NamedAttributeNode("daylightRoomType") + } +) +@Data +public class CfgDaylightView implements Serializable { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "section_id") + private Long sectionId; + @Column(name = "property_id") + private Long propertyId; + @Column(name = "booking_type_id") + private Long bookingTypeId; + @Column(name = "rate_code_id") + private Long rateCodeId; + @Column(name = "source_code_id") + private Long sourceCodeId; + @Column(name = "market_code_id") + private Long marketCodeId; + @Column(name = "block_code_id") + private Long blockCodeId; + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "created_by") + private String createdBy; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @Column(name = "updated_by") + private String updatedBy; + @Column(name = "block_code_name") + private String blockCodeName; + @Column(name = "start_date") + private LocalDateTime startDate; + @Column(name = "end_date") + private LocalDateTime endDate; + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "section_id", insertable = false, updatable = false) + private CfgSections section; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "property_id", insertable = false, updatable = false) + private CfgProperties property; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "booking_type_id", insertable = false, updatable = false) + private CfgBookingTypes bookingType; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "rate_code_id", insertable = false, updatable = false) + private CfgRateCodes rateCode; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "source_code_id", insertable = false, updatable = false) + private CfgSourceCodes sourceCode; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "market_code_id", insertable = false, updatable = false) + private CfgMarketCodes marketCode; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "cfg_daylight_mapping_room_types", joinColumns = {@JoinColumn(name = "daylight_id")}, inverseJoinColumns = {@JoinColumn(name = "room_type_id")}) + private Set daylightRoomType; + + //-- For export + @Transient + private String sectionName; + @Transient + private String propertyName; + @Transient + private String bookingTypeName; + @Transient + private String blockName; + @Transient + private String rateCodeName; + @Transient + private String roomTypeNames; + @Transient + private String sourceCodeName; + @Transient + private String marketCodeName; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgMarketCodes.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgMarketCodes.java new file mode 100644 index 0000000..500ed10 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgMarketCodes.java @@ -0,0 +1,33 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Entity +@Table(name="cfg_market_codes") +@Data +public class CfgMarketCodes implements Serializable { + //fields in table + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="code") + private String code; + @Column(name = "category") + private String category; + @Column(name="created_at") + private LocalDateTime createdAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_at") + private LocalDateTime updatedAt; + @Column(name="updated_by") + private String updatedBy; + + @Column(name = "dl_market_segment_id") + private String dlMarketSegmentId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgRateCodes.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgRateCodes.java new file mode 100644 index 0000000..606dc68 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgRateCodes.java @@ -0,0 +1,32 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Entity +@Table(name = "cfg_rate_codes") +@Data +public class CfgRateCodes implements Serializable { + //fields in table + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "code") + private String code; + @Column(name = "category") + private String category; + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "created_by") + private String createdBy; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @Column(name = "updated_by") + private String updatedBy; + @Column(name = "dl_rate_plan_id") + private String dlRatePlanId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgSourceCodes.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgSourceCodes.java new file mode 100644 index 0000000..400e373 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/models/CfgSourceCodes.java @@ -0,0 +1,33 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Entity +@Table(name = "cfg_source_codes") +@Data +public class CfgSourceCodes implements Serializable { + //fields in table + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "code") + private String code; + @Column(name = "category") + private String category; + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "created_by") + private String createdBy; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @Column(name = "updated_by") + private String updatedBy; + + @Column(name = "dl_source_id") + private String dlSourceId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/BlockCodeDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/BlockCodeDao.java new file mode 100644 index 0000000..b611400 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/BlockCodeDao.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.repositories; + +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgBlockCodes; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BlockCodeDao extends JpaRepository { + + CfgBlockCodes findByDlBlockId(String dlBlockId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/BlockDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/BlockDao.java new file mode 100644 index 0000000..2f6643f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/BlockDao.java @@ -0,0 +1,15 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.repositories; + +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgBlocks; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + + +public interface BlockDao extends JpaRepository { + CfgBlocks findById(long id); + + @Query(value="select * from cfg_blocks where name =?1",nativeQuery=true) + CfgBlocks getByName(String name); + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/DaylightViewDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/DaylightViewDao.java new file mode 100644 index 0000000..64dbc02 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/DaylightViewDao.java @@ -0,0 +1,68 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.repositories; + +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgDaylightView; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + + +public interface DaylightViewDao extends JpaRepository { + @EntityGraph(value = "graph.DaylightMapping.fulldetails", type = EntityGraph.EntityGraphType.LOAD) + CfgDaylightView findById(long id); + + @EntityGraph(value = "graph.DaylightMapping.fulldetails", type = EntityGraph.EntityGraphType.LOAD) + List findAll(Specification spec, Sort sort); + + @EntityGraph(value = "graph.DaylightMapping.fulldetails", type = EntityGraph.EntityGraphType.LOAD) + List findAll(Specification spec); + + Page findAll(Specification spec, Pageable pageable); + + @EntityGraph(value = "graph.DaylightMapping.fulldetails", type = EntityGraph.EntityGraphType.LOAD) + List findByPropertyIdAndBookingTypeId(Long propertyId, Long bookingTypeId); + + @EntityGraph(value = "graph.DaylightMapping.fulldetails",type = EntityGraph.EntityGraphType.LOAD) + List findByMarketCodeIdAndRateCodeId(Long marketCodeId, Long rateCodeId); + + @EntityGraph(value = "graph.DaylightMapping.fulldetails") + @Query(""" + SELECT d + FROM CfgDaylightView d + JOIN d.daylightRoomType rt + WHERE d.sectionId = :sectionId + AND d.propertyId = :propertyId + AND d.bookingTypeId = :bookingTypeId + AND rt.id = :roomTypeId + """) + Optional findBySectionPropertyBookingTypeRoomType( + @Param("sectionId") Long sectionId, + @Param("propertyId") Long propertyId, + @Param("bookingTypeId") Long bookingTypeId, + @Param("roomTypeId") Long roomTypeId + ); + + @EntityGraph(value = "graph.DaylightMapping.fulldetails") + @Query(""" + SELECT d + FROM CfgDaylightView d + JOIN d.daylightRoomType rt + WHERE d.sectionId = :sectionId + AND d.marketCodeId = :marketCodeId + AND d.propertyId = :propertyId + AND rt.id = :roomTypeId + """) + Optional findBySectionMarketPropertyRoomType( + @Param("sectionId") Long sectionId, + @Param("marketCodeId") Long marketCodeId, + @Param("propertyId") Long propertyId, + @Param("roomTypeId") Long roomTypeId + ); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/MarketCodeDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/MarketCodeDao.java new file mode 100644 index 0000000..bbe0a05 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/MarketCodeDao.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.repositories; + +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgMarketCodes; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + + +public interface MarketCodeDao extends JpaRepository { + CfgMarketCodes findById(long id); + List findByCode(String code); + + CfgMarketCodes findByDlMarketSegmentId(String dlMarketSegmentId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/RateCodeDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/RateCodeDao.java new file mode 100644 index 0000000..e97900c --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/RateCodeDao.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.repositories; + +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgRateCodes; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface RateCodeDao extends JpaRepository { + CfgRateCodes findById(long id); + List findByCode(String code); + + CfgRateCodes findByDlRatePlanId(String dlRatePlanId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/SourceCodeDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/SourceCodeDao.java new file mode 100644 index 0000000..7418408 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/mapping/repositories/SourceCodeDao.java @@ -0,0 +1,8 @@ +package com.mgm.mcrm.apigateway.basesetting.mapping.repositories; + +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgSourceCodes; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SourceCodeDao extends JpaRepository { + CfgSourceCodes findById(long id); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/property/models/CfgProperties.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/property/models/CfgProperties.java new file mode 100644 index 0000000..0fd8683 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/property/models/CfgProperties.java @@ -0,0 +1,44 @@ +package com.mgm.mcrm.apigateway.basesetting.property.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "cfg_properties") +public class CfgProperties { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "name") + private String name; + @Column(name = "code") + private String code; + @Column(name = "name_zht") + private String nameZht; + @Column(name = "name_zhs") + private String nameZhs; + @Column(name = "category") + private String category; + @Column(name = "hotel_code") + private String hotelCode; + @Column(name = "sort_order") + private Integer sortOrder; + @Column(name = "email_name") + private String emailName; + + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @Column(name = "dl_property_id") + private String dlPropertyId; + + @Column(name = "status") + private int status; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/property/repositories/PropertyDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/property/repositories/PropertyDao.java new file mode 100644 index 0000000..0e2d16b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/property/repositories/PropertyDao.java @@ -0,0 +1,24 @@ +package com.mgm.mcrm.apigateway.basesetting.property.repositories; + +import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + + +public interface PropertyDao extends JpaRepository { + + @Query(value="select * from cfg_properties where name=?1",nativeQuery=true) + CfgProperties getByName(String name); + + CfgProperties findByDlPropertyId(String dlPropertyId); + + List findByCategory(String category); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomTypes.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomTypes.java new file mode 100644 index 0000000..a3dae7b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomTypes.java @@ -0,0 +1,34 @@ +package com.mgm.mcrm.apigateway.basesetting.room.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Entity +@Table(name = "cfg_room_types") +@Data +public class CfgRoomTypes implements Serializable { + //fields in table + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "code") + private String code; + @Column(name = "name_cn") + private String nameCN; + @Column(name = "category") + private String category; + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "created_by") + private String createdBy; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @Column(name = "updated_by") + private String updatedBy; + @Column(name = "dl_room_type_id") + private String dlRoomTypeId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRooms.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRooms.java new file mode 100644 index 0000000..c56e8f7 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRooms.java @@ -0,0 +1,75 @@ +package com.mgm.mcrm.apigateway.basesetting.room.models; + +import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Table(name = "cfg_rooms") +@NamedEntityGraph( + name = "graph.RoomSetting.fulldetails", + attributeNodes = { + @NamedAttributeNode("roomRoomType"), + @NamedAttributeNode("propertyObj") + } +) +@Data +public class CfgRooms implements Serializable { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "name") + private String name; + @Column(name = "name_cn") + private String nameCN; + @Column(name = "property_id") + private Long propertyId; + @Column(name = "sharer_limit") + private Integer sharerLimit; + @Column(name = "category") + private String category; + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "created_by") + private String createdBy; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @Column(name = "updated_by") + private String updatedBy; + @Column(name = "room_category") + private String roomCategory; + @Column(name = "for_booking") + private Integer forCreateBooking; + @Column(name = "for_waitlist") + private Integer forCreateWaitlist; + @Column(name = "email_name") + private String emailName; + @Transient + private List roomTypeIds; + // For export + @Transient + private String propertyName; + @Transient + private String roomTypeNames; + @Transient + private String forCreateBookingValue; + @Transient + private String forCreateWaitlistValue; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "cfg_rooms_room_types", joinColumns = {@JoinColumn(name = "room_id")}, inverseJoinColumns = {@JoinColumn(name = "room_type_id")}) + private List roomRoomType; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "property_id", insertable = false, updatable = false) + private CfgProperties propertyObj; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomsRoomTypes.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomsRoomTypes.java new file mode 100644 index 0000000..63d3873 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomsRoomTypes.java @@ -0,0 +1,27 @@ +package com.mgm.mcrm.apigateway.basesetting.room.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name="cfg_rooms_room_types") +@IdClass(CfgRoomsRoomTypesKey.class) +public class CfgRoomsRoomTypes implements Serializable { + //fields in table + @Id + @Column(name="room_id") + private Long roomId; + @Id + @Column(name="room_type_id") + private Long roomTypeId; + @Column(name="created_at") + private LocalDateTime createdAt; + @Column(name="created_by") + private String createdBy; + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomsRoomTypesKey.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomsRoomTypesKey.java new file mode 100644 index 0000000..ceb059f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/models/CfgRoomsRoomTypesKey.java @@ -0,0 +1,46 @@ +package com.mgm.mcrm.apigateway.basesetting.room.models; + +import java.io.Serializable; + + +public class CfgRoomsRoomTypesKey implements Serializable{ + + private Long roomId; + private Long roomTypeId; + + public Long getRoomId() { + return roomId; + } + + public void setRoomId(Long roomId) { + this.roomId = roomId; + } + + public Long getRoomTypeId() { + return roomTypeId; + } + + public void setRoomTypeId(Long roomTypeId) { + this.roomTypeId = roomTypeId; + } + + @Override + public boolean equals(Object o) { + if(o instanceof CfgRoomsRoomTypesKey) + { + CfgRoomsRoomTypesKey key = (CfgRoomsRoomTypesKey)o ; + if(this.roomId == key.getRoomId() && + this.roomTypeId == key.getRoomTypeId()) + { + return true ; + } + } + return false ; + } + + @Override + public int hashCode() { + return 1; + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomDao.java new file mode 100644 index 0000000..b1394c1 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomDao.java @@ -0,0 +1,27 @@ +package com.mgm.mcrm.apigateway.basesetting.room.repositories; + +import com.mgm.mcrm.apigateway.basesetting.room.models.CfgRooms; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface RoomDao extends JpaRepository { + @EntityGraph(value = "graph.RoomSetting.fulldetails",type = EntityGraph.EntityGraphType.LOAD) + CfgRooms findById(long id); + @EntityGraph(value = "graph.RoomSetting.fulldetails",type = EntityGraph.EntityGraphType.LOAD) + List findAll(Specification spec,Sort sort); + @EntityGraph(value = "graph.RoomSetting.fulldetails",type = EntityGraph.EntityGraphType.LOAD) + List findAll(Specification spec); + + Page findAll(Specification spec, Pageable pageable); + + @Query(value="select top(1) id from cfg_rooms where property_id=?1 and " + + " id in (select room_id from cfg_rooms_room_types where room_type_id =?2) ",nativeQuery=true) + Long queryPropertyRoomIdByRoomTypeId(Long propertyId, Long roomTypeId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomRoomTypeDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomRoomTypeDao.java new file mode 100644 index 0000000..6b58b3b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomRoomTypeDao.java @@ -0,0 +1,12 @@ +package com.mgm.mcrm.apigateway.basesetting.room.repositories; + +import com.mgm.mcrm.apigateway.basesetting.room.models.CfgRoomsRoomTypes; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface RoomRoomTypeDao extends JpaRepository { + + List findByRoomTypeId(Long roomTypeId); + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomTypeDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomTypeDao.java new file mode 100644 index 0000000..f7b84e4 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/room/repositories/RoomTypeDao.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.basesetting.room.repositories; + +import com.mgm.mcrm.apigateway.basesetting.room.models.CfgRoomTypes; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RoomTypeDao extends JpaRepository { + + CfgRoomTypes findByCodeAndCategory(String code,String category); + CfgRoomTypes findByDlRoomTypeId(String dlRoomTypeId); + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/AppUsers.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/AppUsers.java new file mode 100644 index 0000000..787c093 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/AppUsers.java @@ -0,0 +1,57 @@ +package com.mgm.mcrm.apigateway.basesetting.team.models; + +import java.io.Serializable; +import java.util.Date; + +import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +import lombok.Data; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +@Data +@Entity +@Table(name="app_users") +public class AppUsers implements Serializable { + //fields in table + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="user_id") + private String userId; + @Column(name="team_id") + private Long teamId; + @Column(name="property_id") + private Long propertyId; + @Column(name="authorizer") + private String authorizer; + @Column(name="created_at") + private Date createdAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_at") + private Date updatedAt; + @Column(name="updated_by") + private String updatedBy; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name="team_id",insertable=false ,updatable=false) + private CfgDepartments userTeam; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name="property_id",insertable=false ,updatable=false) + private CfgProperties userProperty; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgDepartments.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgDepartments.java new file mode 100644 index 0000000..07b5aff --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgDepartments.java @@ -0,0 +1,34 @@ +package com.mgm.mcrm.apigateway.basesetting.team.models; + +import java.io.Serializable; +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; + +@Data +@Entity +@Table(name="cfg_departments") +public class CfgDepartments { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="name") + private String name; + @Column(name="created_at") + private Date createdAt; + @Column(name="updated_at") + private Date updatedAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_by") + private String updatedBy; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgDivisions.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgDivisions.java new file mode 100644 index 0000000..b70d92e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgDivisions.java @@ -0,0 +1,34 @@ +package com.mgm.mcrm.apigateway.basesetting.team.models; + +import java.io.Serializable; +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; + +@Data +@Entity +@Table(name="cfg_divisions") +public class CfgDivisions { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="name") + private String name; + @Column(name="created_at") + private Date createdAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_at") + private Date updatedAt; + @Column(name="updated_by") + private String updatedBy; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgOtherUserMapping.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgOtherUserMapping.java new file mode 100644 index 0000000..6b1cf4e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgOtherUserMapping.java @@ -0,0 +1,46 @@ +package com.mgm.mcrm.apigateway.basesetting.team.models; + +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; + +@Data +@Entity +@Table(name="cfg_other_user_mapping") +public class CfgOtherUserMapping { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="division_id") + private Long divisionId; + @Column(name="department_id") + private Long departmentId; + @Column(name="section_id") + private Long sectionId; + @Column(name="team_id") + private Long teamId; + @Column(name="property_id") + private Long propertyId; + @Column(name="booking_type_id") + private Long bookingTypeId; + @Column(name="market_code_id") + private Long marketCodeId; + @Column(name="segment_id") + private Long segmentId; + @Column(name="created_at") + private Date createdAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_at") + private Date updatedAt; + @Column(name="updated_by") + private String updatedBy; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgSections.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgSections.java new file mode 100644 index 0000000..b5e42e3 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgSections.java @@ -0,0 +1,27 @@ +package com.mgm.mcrm.apigateway.basesetting.team.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "cfg_sections") +public class CfgSections { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "name") + private String name; + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @Column(name = "created_by") + private String createdBy; + @Column(name = "updated_by") + private String updatedBy; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgTeamStructure.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgTeamStructure.java new file mode 100644 index 0000000..1da2e9a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgTeamStructure.java @@ -0,0 +1,37 @@ +package com.mgm.mcrm.apigateway.basesetting.team.models; + +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; + +@Data +@Entity +@Table(name="cfg_team_structure") +public class CfgTeamStructure { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="division_id") + private Long divisionId; + @Column(name="department_id") + private Long departmentId; + @Column(name="section_id") + private Long sectionId; + @Column(name="created_at") + private Date createdAt; + @Column(name="updated_at") + private Date updatedAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_by") + private String updatedBy; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgTeamStructureView.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgTeamStructureView.java new file mode 100644 index 0000000..79d7190 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/models/CfgTeamStructureView.java @@ -0,0 +1,68 @@ +package com.mgm.mcrm.apigateway.basesetting.team.models; + +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.NamedEntityGraphs; +import jakarta.persistence.Table; + +import lombok.Data; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +@Data +@Entity +@Table(name="cfg_team_structure") +@NamedEntityGraphs({ + @NamedEntityGraph( + name = "graph.TeamStructure.fulldetails", + attributeNodes = { + @NamedAttributeNode("divisionObj"), + @NamedAttributeNode("departmentObj"), + @NamedAttributeNode("sectionObj") + } + ) +}) +public class CfgTeamStructureView { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="division_id") + private Long divisionId; + @Column(name="department_id") + private Long departmentId; + @Column(name="section_id") + private Long sectionId; + @Column(name="created_at") + private Date createdAt; + @Column(name="updated_at") + private Date updatedAt; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name="division_id",insertable=false ,updatable=false) + private CfgDivisions divisionObj; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name="department_id",insertable=false ,updatable=false) + private CfgDepartments departmentObj; + + @NotFound(action = NotFoundAction.IGNORE) + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name="section_id",insertable=false ,updatable=false) + private CfgSections sectionObj; + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/AppUserDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/AppUserDao.java new file mode 100644 index 0000000..d575b3f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/AppUserDao.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.basesetting.team.repositories; + +import com.mgm.mcrm.apigateway.basesetting.team.models.AppUsers; +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface AppUserDao extends JpaRepository { + AppUsers findById(long id); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/DepartmentDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/DepartmentDao.java new file mode 100644 index 0000000..0c2c14a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/DepartmentDao.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.basesetting.team.repositories; + +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgDepartments; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + + +public interface DepartmentDao extends JpaRepository { + + @Query(value = "select * from cfg_departments with (nolock) where name=?1", nativeQuery = true) + CfgDepartments getByName(String name); + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/DivisionDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/DivisionDao.java new file mode 100644 index 0000000..0164b76 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/DivisionDao.java @@ -0,0 +1,16 @@ +package com.mgm.mcrm.apigateway.basesetting.team.repositories; + +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgDivisions; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + + +public interface DivisionDao extends JpaRepository { + CfgDivisions findById(long id); + + + @Query(value="select * from cfg_divisions where name =?1",nativeQuery=true) + CfgDivisions getByName(String name); + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/SectionDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/SectionDao.java new file mode 100644 index 0000000..059c1c5 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/SectionDao.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.basesetting.team.repositories; + +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgSections; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + + +public interface SectionDao extends JpaRepository { + + @Query(value = "select * from cfg_sections with (nolock) where name=?1", nativeQuery = true) + CfgSections getByName(String name); + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/TeamStructureDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/TeamStructureDao.java new file mode 100644 index 0000000..4ea8834 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/TeamStructureDao.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.basesetting.team.repositories; + +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgTeamStructure; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + + +public interface TeamStructureDao extends JpaRepository { + + @Query(value = "select * from cfg_team_structure with (nolock) where division_id=?1 and department_id=?2 and section_id=?3", nativeQuery = true) + CfgTeamStructure getByDivisionIdAndDepartmentIdAndSectionId(Long divisionId,Long departmentId,Long sectionId); + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/TeamStructureViewDao.java b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/TeamStructureViewDao.java new file mode 100644 index 0000000..368b46e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/basesetting/team/repositories/TeamStructureViewDao.java @@ -0,0 +1,21 @@ +package com.mgm.mcrm.apigateway.basesetting.team.repositories; + +import java.util.List; + +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgTeamStructureView; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface TeamStructureViewDao extends JpaRepository { + + @EntityGraph(value = "graph.TeamStructure.fulldetails",type = EntityGraph.EntityGraphType.LOAD) + List findAll(Specification spec); + @EntityGraph(value = "graph.TeamStructure.fulldetails",type = EntityGraph.EntityGraphType.LOAD) + List findAll(Specification spec, Sort sort); + + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/DataSourceConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/DataSourceConfig.java new file mode 100644 index 0000000..d202c0e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/DataSourceConfig.java @@ -0,0 +1,30 @@ +package com.mgm.mcrm.apigateway.common.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import javax.sql.DataSource; + + +@Configuration +public class DataSourceConfig { + + @Bean(name = "mgmDataSource") + @Qualifier("mgmDataSource") + @ConfigurationProperties(prefix = "spring.datasource.mgm") + @Primary + public DataSource mgmDataSource() { + return DataSourceBuilder.create().build(); + } + + @Bean(name = "operaDataSource") + @Qualifier("operaDataSource") + @ConfigurationProperties(prefix = "spring.datasource.opera") + public DataSource operaDataSource() { + return DataSourceBuilder.create().build(); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/DaylightProxyConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/DaylightProxyConfig.java new file mode 100644 index 0000000..a6b792d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/DaylightProxyConfig.java @@ -0,0 +1,89 @@ +package com.mgm.mcrm.apigateway.common.config; + +import com.mgm.mcrm.apigateway.common.constants.SysCommon; +import com.mgm.mcrm.apigateway.common.dtos.config.DaylightApiSetting; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DaylightProxyConfig { + + @Value("${mcrm.proxy.api-config.daylight.server-url}") + private String serverUrl; + + @Value("${mcrm.proxy.api-config.daylight.request-timeout:60}") + private int requestTimeout; + + @Value("${mcrm.proxy.api-config.daylight.token.token-url}") + private String tokenUrl; + + @Value("${mcrm.proxy.api-config.daylight.token.username}") + private String userName; + + @Value("${mcrm.proxy.api-config.daylight.token.password}") + private String password; + + @Value("${mcrm.proxy.api-config.daylight.token.grant-type}") + private String grantType; + + @Value("${mcrm.proxy.api-config.daylight.token.client-id}") + private String clientId; + + @Value("${mcrm.proxy.api-config.daylight.token.client-secret}") + private String clientSecret; + + @Value("${mcrm.proxy.api-config.daylight.token.request-timeout:60}") + private int tokenRequestTimeout; + + @Value("${mcrm.proxy.api-config.daylight.token.default-expiry-second:800}") + private long tokenExpirySecond; + + @Value("${mcrm.proxy.api-config.daylight.security.authorization-data}") + private String appAuthorizationData; + + //-- Proxy setting for daylight + @Value("${mcrm.proxy.api-config.daylight.proxy-setting.host}") + private String proxyHost; + @Value("${mcrm.proxy.api-config.daylight.proxy-setting.port:443}") + private String proxyPort; + @Value("${mcrm.proxy.api-config.daylight.proxy-setting.auth.username}") + private String proxyUsername; + @Value("${mcrm.proxy.api-config.daylight.proxy-setting.auth.password}") + private String proxyPassword; + //-- SSL config for daylight + @Value("${mcrm.proxy.api-config.daylight.ssl-bundle-name}") + private String sslBundleName; + + @Bean + public DaylightApiSetting configDaylightApi() { + DaylightApiSetting config = new DaylightApiSetting(); + config.setServerUrl(serverUrl); + config.setApiTimeout(requestTimeout); + config.setTokenUrl(tokenUrl); + config.setUsername(userName); + config.setPassword(password); + config.setGrantType(grantType); + config.setClientId(clientId); + config.setClientSecret(clientSecret); + config.setTokenRequestTimeout(tokenRequestTimeout); + config.setTokenExpireSecond(tokenExpirySecond); + //-- + config.setProxyHost(proxyHost); + config.setProxyUserName(proxyUsername); + config.setProxyPassword(proxyPassword); + int proxyPortValue = 443; + try{ + proxyPortValue = Integer.parseInt(proxyPort); + }catch(Exception ignored){ + + } + config.setProxyPort(proxyPortValue); + config.setSslBundleName(sslBundleName); + //-- + config.registerAuthorizationList(SysCommon.extractAuthorizationItems(appAuthorizationData)); + return config; + } + +} + diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/EmailProxyConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/EmailProxyConfig.java new file mode 100644 index 0000000..b400c5a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/EmailProxyConfig.java @@ -0,0 +1,52 @@ +package com.mgm.mcrm.apigateway.common.config; + +import com.mgm.mcrm.apigateway.common.constants.SysCommon; +import com.mgm.mcrm.apigateway.common.dtos.config.DaylightApiSetting; +import com.mgm.mcrm.apigateway.common.dtos.config.EmailProxySetting; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EmailProxyConfig { + + @Value("${mcrm.proxy.api-config.notification.email-sender.smtp-server}") + private String smtpServer; + + @Value("${mcrm.proxy.api-config.notification.email-sender.smtp-port}") + private int smtpPort; + + @Value("${mcrm.proxy.api-config.notification.email-sender.username}") + private String userName; + + @Value("${mcrm.proxy.api-config.notification.email-sender.password}") + private String password; + + @Value("${mcrm.proxy.api-config.notification.email-sender.smtp-auth}") + private boolean smtpAuth; + + @Value("${mcrm.proxy.api-config.notification.email-sender.starttls}") + private boolean startTls; + + @Value("${mcrm.proxy.api-config.notification.email-sender.default-send-to}") + private String defaultSentTo; + + @Value("${mcrm.proxy.api-config.notification.security.authorization-data}") + private String appAuthorizationData; + + @Bean + public EmailProxySetting configEmailProxy() { + EmailProxySetting config = new EmailProxySetting(); + config.setSmtpServer(smtpServer); + config.setSmtpPort(smtpPort); + config.setUsername(userName); + config.setPassword(password); + config.setAuth(smtpAuth); + config.setStarttls(startTls); + config.setDefaultSendTo(defaultSentTo); + config.registerAuthorizationList(SysCommon.extractAuthorizationItems(appAuthorizationData)); + return config; + } + +} + diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/EsbProxyConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/EsbProxyConfig.java new file mode 100644 index 0000000..26de60d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/EsbProxyConfig.java @@ -0,0 +1,77 @@ +package com.mgm.mcrm.apigateway.common.config; + +import com.mgm.mcrm.apigateway.common.constants.SysCommon; +import com.mgm.mcrm.apigateway.common.dtos.config.EsbApiSetting; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EsbProxyConfig { + + @Value("${mcrm.proxy.api-config.esb.server-url}") + private String serverUrl; + + @Value("${mcrm.proxy.api-config.esb.request-timeout:60}") + private int apiRequestTimeout; + + @Value("${mcrm.proxy.api-config.esb.token.token-url}") + private String tokenUrl; + + @Value("${mcrm.proxy.api-config.esb.token.app-id}") + private String appId; + + @Value("${mcrm.proxy.api-config.esb.token.app-secret}") + private String appSecret; + + @Value("${mcrm.proxy.api-config.esb.token.request-timeout:60}") + private int tokenRequestTimeout; + + @Value("${mcrm.proxy.api-config.esb.token.default-expiry-second:800}") + private long tokenExpirySecond; + + @Value("${mcrm.proxy.api-config.esb.security.authorization-data}") + private String appAuthorizationData; + + //-- Proxy setting for esb + @Value("${mcrm.proxy.api-config.esb.proxy-setting.host}") + private String proxyHost; + @Value("${mcrm.proxy.api-config.esb.proxy-setting.port:443}") + private String proxyPort; + @Value("${mcrm.proxy.api-config.esb.proxy-setting.auth.username}") + private String proxyUsername; + @Value("${mcrm.proxy.api-config.esb.proxy-setting.auth.password}") + private String proxyPassword; + //-- SSL config for esb + @Value("${mcrm.proxy.api-config.esb.ssl-bundle-name}") + private String sslBundleName; + + @Bean + public EsbApiSetting configEsbApi() { + EsbApiSetting config = new EsbApiSetting(); + config.setServerUrl(serverUrl); + config.setApiRequestTimeout(apiRequestTimeout); + config.setTokenUrl(tokenUrl); + config.setAppId(appId); + config.setAppSecret(appSecret); + config.setTokenRequestTimeout(tokenRequestTimeout); + config.setTokenExpireSecond(tokenExpirySecond); + //-- + config.setProxyHost(proxyHost); + config.setProxyUserName(proxyUsername); + config.setProxyPassword(proxyPassword); + int proxyPortValue = 443; + try{ + proxyPortValue = Integer.parseInt(proxyPort); + }catch(Exception ignored){ + + } + config.setProxyPort(proxyPortValue); + config.setSslBundleName(sslBundleName); + //-- + config.registerAuthorizationList(SysCommon.extractAuthorizationItems(appAuthorizationData)); + return config; + } +} + diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/HttpHeader.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/HttpHeader.java new file mode 100644 index 0000000..0d38765 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/HttpHeader.java @@ -0,0 +1,26 @@ +package com.mgm.mcrm.apigateway.common.config; + +import lombok.Data; + +/** + * HTTP 請求頭配置類 + * 用於自定義 HTTP 請求頭的值 + * + * @author System + */ +@Data +public class HttpHeader { + public static final String ACCEPT = "Accept"; + public static final String ACCEPT_CHARSET = "Accept-Charset"; + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + public static final String ACCEPT_LANGUAGE = "Accept-Language"; + public static final String ACCEPT_RANGES = "Accept-Ranges"; + public static final String AUTHORIZATION = "Authorization"; + public static final String CACHE_CONTROL = "Cache-Control"; + public static final String CONNECTION = "Connection"; + public static final String COOKIE = "Cookie"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String USER_AGENT = "user-agent"; +} + diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/MGMDataSourceConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/MGMDataSourceConfig.java new file mode 100644 index 0000000..d3e28d6 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/MGMDataSourceConfig.java @@ -0,0 +1,72 @@ +package com.mgm.mcrm.apigateway.common.config; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +/** + * @Description: 主数据源配置 + * @date + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory", + transactionManagerRef = "transactionManager", + basePackages = {"com.mgm.mcrm.apigateway"}) +public class MGMDataSourceConfig { + + @Autowired + @Qualifier("mgmDataSource") + private DataSource mrbsDataSource; + + @Primary + @Bean(name = "mgmJpaProperties") + @ConfigurationProperties(prefix = "spring.jpa.mgm") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + @Primary + @Bean(name = "entityManager") + public EntityManager entityManager( @Qualifier("entityManagerFactory") EntityManagerFactory factory) { + return factory.createEntityManager(); + } + + @Primary + @Bean(name = "entityManagerFactory") //mrbs实体工厂 + public LocalContainerEntityManagerFactoryBean entityManagerFactory (@Qualifier("mgmDataSource") DataSource mgmDataSource, + @Qualifier("mgmJpaProperties") JpaProperties jpaProperties,EntityManagerFactoryBuilder builder) { + return builder.dataSource(mgmDataSource) + .properties(jpaProperties.getProperties()) + .properties( + new HibernateProperties() + .determineHibernateProperties( + jpaProperties.getProperties(), new HibernateSettings())) + .packages("com.mgm.mcrm.apigateway") //实体类所在位置 + .persistenceUnit("mgmPersistenceUnit") + .build(); + } + + @Primary + @Bean(name = "transactionManager") + public PlatformTransactionManager transactionManager(@Qualifier("entityManagerFactory") EntityManagerFactory factory) { + return new JpaTransactionManager(factory); + } + +} \ No newline at end of file diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/OperaDataSourceConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/OperaDataSourceConfig.java new file mode 100644 index 0000000..c55c83e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/OperaDataSourceConfig.java @@ -0,0 +1,66 @@ +package com.mgm.mcrm.apigateway.common.config; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +/** + * @Description: 副数据源配置 + * @date + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactoryOpera", + transactionManagerRef = "transactionManagerOpera") +public class OperaDataSourceConfig { + + @Autowired + @Qualifier("operaDataSource") + private DataSource operaDataSource; + + @Bean(name = "operaJpaProperties") + @ConfigurationProperties(prefix = "spring.jpa.opera") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + @Bean(name = "entityManagerOpera") + public EntityManager entityManager( @Qualifier("entityManagerFactoryOpera") EntityManagerFactory factory) { + return factory.createEntityManager(); + } + + @Bean(name = "entityManagerFactoryOpera") + public LocalContainerEntityManagerFactoryBean entityManagerFactoryOpera (@Qualifier("operaDataSource") DataSource operaDataSource, + @Qualifier("operaJpaProperties") JpaProperties jpaProperties,EntityManagerFactoryBuilder builder) { + return builder.dataSource(operaDataSource) + .properties(jpaProperties.getProperties()) + .properties( + new HibernateProperties() + .determineHibernateProperties( + jpaProperties.getProperties(), new HibernateSettings())) + .packages("com.mgm.daylight.booking.models") //实体类所在位置 + .persistenceUnit("operaPersistenceUnit") + .build(); + } + + @Bean(name = "transactionManagerOpera") + public PlatformTransactionManager transactionManagerOpera(@Qualifier("entityManagerFactoryOpera") EntityManagerFactory factory) { + return new JpaTransactionManager(factory); + } + +} \ No newline at end of file diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/OperaProxyConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/OperaProxyConfig.java new file mode 100644 index 0000000..0756935 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/OperaProxyConfig.java @@ -0,0 +1,57 @@ +package com.mgm.mcrm.apigateway.common.config; + +import com.mgm.mcrm.apigateway.common.constants.SysCommon; +import com.mgm.mcrm.apigateway.common.dtos.config.OperaApiSetting; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OperaProxyConfig { + + @Value("${mcrm.proxy.api-config.opera.server-url}") + private String serverUrl; + + @Value("${mcrm.proxy.api-config.opera.request-timeout:10000}") + private int requestTimeout; + + @Value("${mcrm.proxy.api-config.opera.security.authorization-data}") + private String appAuthorizationData; + + //-- Proxy setting for opera + @Value("${mcrm.proxy.api-config.opera.proxy-setting.host}") + private String proxyHost; + @Value("${mcrm.proxy.api-config.opera.proxy-setting.port:443}") + private String proxyPort; + @Value("${mcrm.proxy.api-config.opera.proxy-setting.auth.username}") + private String proxyUsername; + @Value("${mcrm.proxy.api-config.opera.proxy-setting.auth.password}") + private String proxyPassword; + //-- SSL config for opera + @Value("${mcrm.proxy.api-config.opera.ssl-bundle-name}") + private String sslBundleName; + + @Bean + public OperaApiSetting configOperaApi() { + OperaApiSetting config = new OperaApiSetting(); + config.setServerUrl(serverUrl); + config.setTimeout(requestTimeout); + //-- + config.setProxyHost(proxyHost); + config.setProxyUserName(proxyUsername); + config.setProxyPassword(proxyPassword); + int proxyPortValue = 443; + try{ + proxyPortValue = Integer.parseInt(proxyPort); + }catch(Exception ignored){ + + } + config.setProxyPort(proxyPortValue); + config.setSslBundleName(sslBundleName); + //-- + config.registerAuthorizationList(SysCommon.extractAuthorizationItems(appAuthorizationData)); + return config; + } + +} + diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/SwaggerConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/SwaggerConfig.java new file mode 100644 index 0000000..cabeaca --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/SwaggerConfig.java @@ -0,0 +1,18 @@ +package com.mgm.mcrm.apigateway.common.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("API Document for MGM MCRM API Gateway Application") + .version("1.0") + .description("API Document for MGM MCRM API Gateway Application")); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/config/WebFilterConfig.java b/src/main/java/com/mgm/mcrm/apigateway/common/config/WebFilterConfig.java new file mode 100644 index 0000000..7018b01 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/config/WebFilterConfig.java @@ -0,0 +1,32 @@ +package com.mgm.mcrm.apigateway.common.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import com.mgm.mcrm.apigateway.logging.filters.WebhookLoggingFilter; + +@Configuration +public class WebFilterConfig { + + @Bean + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduling = new ThreadPoolTaskScheduler(); + scheduling.setPoolSize(20); + scheduling.initialize(); + return scheduling; + } + + @Bean + public FilterRegistrationBean registerWebhookFilter( + WebhookLoggingFilter webhookLoggingFilter) { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(webhookLoggingFilter); + registration.addUrlPatterns("/api/daylight/webhook/*"); + registration.setOrder(3); + registration.setName("webhookLoggingFilter"); + return registration; + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/constants/Constants.java b/src/main/java/com/mgm/mcrm/apigateway/common/constants/Constants.java new file mode 100644 index 0000000..2ca3c89 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/constants/Constants.java @@ -0,0 +1,173 @@ +package com.mgm.mcrm.apigateway.common.constants; + + +import java.util.Locale; + +/** + * 通用常量信息 + * + * @author Aaka + */ +public class Constants +{ + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * 系统语言 + */ + public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 所有权限标识 + */ + public static final String ALL_PERMISSION = "*:*:*"; + + /** + * 管理员角色权限标识 + */ + public static final String SUPER_ADMIN = "admin"; + + /** + * 角色权限分隔符 + */ + public static final String ROLE_DELIMETER = ","; + + /** + * 权限标识分隔符 + */ + public static final String PERMISSION_DELIMETER = ","; + + /** + * 验证码有效期(分钟) + */ + public static final Integer CAPTCHA_EXPIRATION = 2; + + /** + * 令牌 + */ + public static final String TOKEN = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + /** + * 用户ID + */ + public static final String JWT_USERID = "userid"; + + /** + * 用户名称 + */ +// public static final String JWT_USERNAME = Claims.SUBJECT; + + /** + * 用户头像 + */ + public static final String JWT_AVATAR = "avatar"; + + /** + * 创建时间 + */ + public static final String JWT_CREATED = "created"; + + /** + * 用户权限 + */ + public static final String JWT_AUTHORITIES = "authorities"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + */ + public static final String[] JSON_WHITELIST_STR = { "com.ruoyi" }; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" }; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/constants/SettingConstants.java b/src/main/java/com/mgm/mcrm/apigateway/common/constants/SettingConstants.java new file mode 100644 index 0000000..fe0ac6d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/constants/SettingConstants.java @@ -0,0 +1,60 @@ +package com.mgm.mcrm.apigateway.common.constants; + +public class SettingConstants { + public static final String ENABLE_CUSTOM_DATE = "ENABLE_CUSTOM_DATE"; + public static final String CUSTOM_CUR_DATE = "CUSTOM_CUR_DATE"; + public static final String ENABLE_EXT_IP_CHECK = "ENABLE_EXT_IP_CHECK"; + public static final String ENABLE_EXTERNAL_SEARCH_RESTRICTION = "ENABLE_EXTERNAL_SEARCH_RESTRICTION"; + public static final String ENABLE_DEFAULT_ROOM_PRICE = "ENABLE_DEFAULT_ROOM_PRICE"; + public static final String ENABLE_ROOM_EXTENSION = "ENABLE_ROOM_EXTENSION"; + + public static final String BOOKING_MAX_NIGHTS = "BOOKING_MAX_NIGHTS"; + public static final String BOOKING_MAX_ROOMS = "BOOKING_MAX_ROOMS"; + public static final String BOOKING_MAX_FUTURE_DATE = "BOOKING_MAX_FUTURE_DATE"; + public static final String BOOKING_MAX_OCCUPANCY_PER_ROOM = "BOOKING_MAX_OCCUPANCY_PER_ROOM"; + + public static final String CANCEL_CUTOFF_TIME = "CANCEL_CUTOFF_TIME"; + + public static final String ENABLE_AUTO_DEPLOY = "ENABLE_AUTO_DEPLOY"; + public static final String ENABLE_OWS_TRACE = "ENABLE_OWS_TRACE"; + public static final String ENABLE_UUID_CHECK = "ENABLE_UUID_CHECK"; + public static final String ENABLE_ADMINER = "ENABLE_ADMINER"; + public static final String SESSION_MAX_IDLE_MINUTES = "SESSION_MAX_IDLE_MINUTES"; + public static final String GCM_COMMENT_TYPE = "GCM_COMMENT_TYPE"; + public static final String ENABLE_TRACE_LOG = "ENABLE_TRACE_LOG"; + + //phase 2 + public static final String ENABLE_APPROVAL = "ENABLE_APPROVAL"; + public static final String APPROVAL_EXPIRE_MINUTES = "APPROVAL_EXPIRE_MINUTES"; + + //ows fix + public static final String OWS_FETCH_MAX_BOOKINGS = "OWS_FETCH_MAX_BOOKINGS"; + + public static final String[] EXTENDABLE_STATUSES = {"CHECKED_IN","DUE_OUT"}; + + //ApprovalConstants + public static final String APPROVAL_PERIOD = "APPROVAL_PERIOD"; + public static final String GCOMP_BALANCE = "GCOMP_BALANCE"; + public static final String DCOMP_BALANCE = "DCOMP_BALANCE"; + public static final String ROOM_EXTENSION = "ROOM_EXTENSION"; + public static final String ADD_SHARE = "ADD_SHARE"; + + //ApprovalPeriod + public static final Integer APPROVAL_PERIOD_TYPE_NEW_BOOKING = 1; + public static final Integer APPROVAL_PERIOD_TYPE_EXTENSION = 2; + + public static final Integer MEMBER_ID_LENGTH = 8; + + //RoomRCDRate + public static final Integer BOOKING_TYPE_NEW = 1; + public static final Integer BOOKING_TYPE_EXTENSION = 2; + + //NotificationHelper + public static final Integer EMAIL_TYPE_NEW_BOOKING = 1; + public static final Integer EMAIL_TYPE_MODIFY = 2; + public static final Integer EMAIL_TYPE_EXTEND = 3; + public static final Integer EMAIL_TYPE_CANCEL = 4; + +} + + diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/constants/SysCommon.java b/src/main/java/com/mgm/mcrm/apigateway/common/constants/SysCommon.java new file mode 100644 index 0000000..1600bfb --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/constants/SysCommon.java @@ -0,0 +1,102 @@ +package com.mgm.mcrm.apigateway.common.constants; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + + +import com.mgm.mcrm.apigateway.common.utils.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.system.ApplicationHome; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + + +public class SysCommon { + + public static String runtime_path = ""; + public static String runtimeName="crm_gateway_runtime"; + public static boolean isMock = true; + + public static String getSystemPath() { + ApplicationHome h = new ApplicationHome(SysCommon.class); + File f = h.getSource(); + return f.getParentFile().toString(); + } + + public static void initRuntimePath() { + runtime_path = getSystemPath(); + } + + + //-- Path Type 1= Class path 2=Application path 3=URI Path + public static String loadResFileContentByPath(String filePath, int pathType) { + String fileContent = ""; + InputStream ifs = null; + BufferedReader bufferedReader = null; + InputStreamReader fileReader = null; + try { + if(pathType==2) { + String applicationPath = SysCommon.runtime_path+"/"+runtimeName; + File dataFile = new File(applicationPath+filePath); + ifs=new FileInputStream(dataFile); + }else { + Resource resource = new ClassPathResource(filePath); + ifs = resource.getInputStream(); + } + fileReader = new InputStreamReader(ifs, "UTF-8"); + bufferedReader = new BufferedReader(fileReader); + String lineData = null; + int lineCnt =0; + StringBuffer contentBuffer = new StringBuffer(); + while ((lineData = bufferedReader.readLine()) != null) { + if(lineCnt>0) { + contentBuffer.append("\n"); + } + contentBuffer.append(lineData); + lineCnt++; + } + bufferedReader.close(); + fileContent = contentBuffer.toString(); + } catch (Exception e) { + //e.printStackTrace(); + LogUtil.error(SysCommon.class, "Get Data File failed:"+e.getMessage()); + } finally { + try { + if (bufferedReader != null) { + bufferedReader.close(); + } + if(fileReader!=null) { + fileReader.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + //-- + return fileContent; + } + + public static Map extractAuthorizationItems(String appAuthorizationData){ + Map result = new HashMap<>(); + if(StringUtils.isNotBlank(appAuthorizationData)){ + String[] securityItems = appAuthorizationData.split("[;,]"); + for(String securityItem:securityItems){ + String[] itemTags = securityItem.split("/"); + if(itemTags.length>=2){ + String appId = itemTags[0].replace("[","").replace("]","").trim().toUpperCase(); + String securityKey = itemTags[1].replace("[","").replace("]","").trim(); + if(StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(securityKey)){ + result.put(appId,securityKey); + } + } + } + } + return result; + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/constants/SysConstants.java b/src/main/java/com/mgm/mcrm/apigateway/common/constants/SysConstants.java new file mode 100644 index 0000000..b9120a4 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/constants/SysConstants.java @@ -0,0 +1,48 @@ +package com.mgm.mcrm.apigateway.common.constants; + +public class SysConstants { + + public static final String CURRENT_USER_KEY = "currentUser"; + public static final String SUPER_ADMIN_ID ="1"; + public static final int ACTIVE = 1; + public static final int INACTIVE = 0; + + public static final int STATUS_OK =200; + public static final int STATUS_AUTH_X_FAILED =401; + public static final int STATUS_PARAMETER_ERROR =422; + public static final int STATUS_GAME_NOT_AVAILABLE =301; + public static final int STATUS_NO_GAME_CHANCE =302; + public static final int STATUS_NO_RESULT =303; + public static final int STATUS_DUPLICATED =304; + public static final int STATUS_REQUEST_INVALID =305; + public static final int STATUS_REQUEST_TIMEOUT =306; + public static final int STATUS_INVALID_REWARD_SETTING =307; + public static final int STATUS_GAME_SESSION_TIMEOUT =308; + public static final int STATUS_NO_CUST_TIER =309; + public static final int STATUS_NO_REWARD =310; + public static final int STATUS_REQUEST_ERROR =701; + + public static final String TIER_RED="Red"; + public static final String TIER_GOLD="Gold"; + public static final String TIER_DIAMOND="Diamond"; + public static final String TIER_CHAIRMAN="Chairman"; + + // cfg_settings + public static final String MAX_BOOKING_STAY_DAYS="MAX_BOOKING_STAY_DAYS"; + public static final String MAX_ADVANCE_BOOKING_DAYS="MAX_ADVANCE_BOOKING_DAYS"; + public static final String NUM_OF_MULTIPLE_DATE_RANGE="NUM_OF_MULTIPLE_DATE_RANGE"; + public static final String PROTECTION_PERIOD_CHECKIN="PROTECTION_PERIOD_CHECKIN"; + public static final String PROTECTION_PERIOD_CHECKOUT="PROTECTION_PERIOD_CHECKOUT"; + public static final String PROTECT_PERIOD_ENABLE="PROTECT_PERIOD_ENABLE"; + public static final String MAX_PREVIOUS_BOOKING_DAYS="MAX_PREVIOUS_BOOKING_DAYS"; + + // Token + public static final int CMS_TOKEN_TYPE=0; + public static final int MOBILE_TOKEN_TYPE=1; + + public static final String PERMISSION_APP_VIEW_BOOKING="10001_01"; + public static final String PERMISSION_APP_NEW_BOOKING="10001_02"; + public static final String PERMISSION_APP_TODO_LIST="10001_03"; + public static final String PERMISSION_APP_DASHBOARD="10001_04"; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/BaseApiResponse.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/BaseApiResponse.java new file mode 100644 index 0000000..0106876 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/BaseApiResponse.java @@ -0,0 +1,12 @@ +package com.mgm.mcrm.apigateway.common.dtos; + +import lombok.Data; + +@Data +public class BaseApiResponse { + private boolean success = false; + private String code; + private String message; + private String content; + private int statusCode=-1; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/CachedToken.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/CachedToken.java new file mode 100644 index 0000000..82081a8 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/CachedToken.java @@ -0,0 +1,21 @@ +package com.mgm.mcrm.apigateway.common.dtos; + +import com.mgm.mcrm.apigateway.daylight.common.dtos.TokenResponse; +import lombok.Getter; + +public class CachedToken { + + @Getter + private final TokenResponse token; + private final long expireTime; + + public CachedToken(TokenResponse token, long expireTime) { + this.token = token; + this.expireTime = expireTime; + } + + + public boolean isExpired() { + return System.currentTimeMillis() > expireTime; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/HttpReqResp.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/HttpReqResp.java new file mode 100644 index 0000000..2ea868b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/HttpReqResp.java @@ -0,0 +1,69 @@ +package com.mgm.mcrm.apigateway.common.dtos; + + +import lombok.Data; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.ProtocolVersion; +import org.apache.hc.core5.http.message.StatusLine; + +@Data +public class HttpReqResp { + + /** + * 执行结果-body + */ + private String result; + + /** + * 状态码-statusCode + */ + private int statusCode; + + /** + * 状态行-StatusLine + */ + private StatusLine statusLine; + + /** + * 请求头信息 + */ + private Header[] reqHeaders; + + /** + * 响应头信息 + */ + private Header[] respHeaders; + + /** + * 协议版本 + */ + private ProtocolVersion protocolVersion; + + /** + * HttpResponse结果对象 + */ + private HttpResponse resp; + + public HttpReqResp(HttpResponse resp) { + // HttpClient 5 API: 使用 new StatusLine(response) 创建 StatusLine + this.statusLine = new StatusLine(resp); + // HttpClient 5 API: 使用 getHeaders() 获取所有响应头 + this.respHeaders = resp.getHeaders(); + // HttpClient 5 API: 从 StatusLine 获取协议版本 + this.protocolVersion = this.statusLine.getProtocolVersion(); + // HttpClient 5 API: 使用 getCode() 获取状态码 + this.statusCode = resp.getCode(); + this.resp = resp; + } + + /** + * 从返回的头信息中查询指定头信息 + * + * @param name 头信息名称 + */ + public Header getHeaders(final String name) { + Header[] headers = this.resp.getHeaders(name); + return headers!=null && headers.length>0?headers[0]:null; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/HttpResult.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/HttpResult.java new file mode 100644 index 0000000..d15e087 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/HttpResult.java @@ -0,0 +1,15 @@ +package com.mgm.mcrm.apigateway.common.dtos; + +import lombok.Data; +import org.springframework.http.HttpHeaders; + +@Data +public class HttpResult { + private String result; + private int statusCode; + private HttpHeaders responseHeaders; + private String responseBody; + private String errorMessage; + private String exceptionClass; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/ResFileResult.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/ResFileResult.java new file mode 100644 index 0000000..020d9d9 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/ResFileResult.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.common.dtos; + +import lombok.Data; + +@Data +public class ResFileResult { + private boolean success = false; + private String fileName; + private String filePath; + private String errorMessage; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/ServiceResult.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/ServiceResult.java new file mode 100644 index 0000000..05a4799 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/ServiceResult.java @@ -0,0 +1,12 @@ +package com.mgm.mcrm.apigateway.common.dtos; + +import lombok.Data; + +@Data +public class ServiceResult { + private String errorCode; + private boolean isSuccess=false; + private String errorMessage=""; + private Object returnData =null; + private Object oldData=null; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/DaylightApiSetting.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/DaylightApiSetting.java new file mode 100644 index 0000000..1b0fa5f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/DaylightApiSetting.java @@ -0,0 +1,47 @@ +package com.mgm.mcrm.apigateway.common.dtos.config; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class DaylightApiSetting { + + //-- Daylight Server + private String serverUrl=""; + private int apiTimeout=0; + private String tokenUrl=""; + private String username; + private String password; + private String grantType; + private String clientId; + private String clientSecret; + //--Proxy config + private String proxyHost; + private int proxyPort; + private String proxyUserName; + private String proxyPassword; + //--SSL config + private String sslBundleName; + + private int tokenRequestTimeout; + private long tokenExpireSecond; + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + private final Map authorizationConfig = new HashMap<>(); + + public void registerAuthorizationList(Map authItems){ + if(authItems!=null){ + authorizationConfig.putAll(authItems); + } + } + + public String getAppAuthorizatinKey(String appId){ + return authorizationConfig.getOrDefault(appId, null); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/EmailProxySetting.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/EmailProxySetting.java new file mode 100644 index 0000000..29331e1 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/EmailProxySetting.java @@ -0,0 +1,34 @@ +package com.mgm.mcrm.apigateway.common.dtos.config; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class EmailProxySetting { + + private int smtpPort=25; + private String smtpServer= ""; + private String username= ""; + private String password= ""; + private boolean auth=false; + private boolean starttls=false; + private String defaultSendTo=""; + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + private final Map authorizationConfig = new HashMap<>(); + + public void registerAuthorizationList(Map authItems){ + if(authItems!=null){ + authorizationConfig.putAll(authItems); + } + } + + public String getAppAuthorizatinKey(String appId){ + return authorizationConfig.getOrDefault(appId, null); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/EsbApiSetting.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/EsbApiSetting.java new file mode 100644 index 0000000..dc89ed0 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/EsbApiSetting.java @@ -0,0 +1,42 @@ +package com.mgm.mcrm.apigateway.common.dtos.config; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class EsbApiSetting { + //-- ESB Server + private String serverUrl=""; + private int apiRequestTimeout; + private String tokenUrl=""; + private String appId=""; + private String appSecret=""; + //--Proxy config + private String proxyHost; + private int proxyPort; + private String proxyUserName; + private String proxyPassword; + //--SSL config + private String sslBundleName; + + private int tokenRequestTimeout; + private long tokenExpireSecond; + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + private final Map authorizationConfig = new HashMap<>(); + + public void registerAuthorizationList(Map authItems){ + if(authItems!=null){ + authorizationConfig.putAll(authItems); + } + } + + public String getAppAuthorizatinKey(String appId){ + return authorizationConfig.getOrDefault(appId, null); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/OperaApiSetting.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/OperaApiSetting.java new file mode 100644 index 0000000..1573da9 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/config/OperaApiSetting.java @@ -0,0 +1,38 @@ +package com.mgm.mcrm.apigateway.common.dtos.config; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class OperaApiSetting { + //-- OWS Server + private String serverUrl= ""; + private int timeout=0; + + //--Proxy config + private String proxyHost; + private int proxyPort; + private String proxyUserName; + private String proxyPassword; + //--SSL config + private String sslBundleName; + + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + private final Map authorizationConfig = new HashMap<>(); + + public void registerAuthorizationList(Map authItems){ + if(authItems!=null){ + authorizationConfig.putAll(authItems); + } + } + + public String getAppAuthorizatinKey(String appId){ + return authorizationConfig.getOrDefault(appId, null); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/webhook/BatchWebhookEventEntry.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/webhook/BatchWebhookEventEntry.java new file mode 100644 index 0000000..d07a59d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/webhook/BatchWebhookEventEntry.java @@ -0,0 +1,17 @@ +package com.mgm.mcrm.apigateway.common.dtos.webhook; + +import lombok.Data; + +import java.time.Instant; + +@Data +public class BatchWebhookEventEntry { + private Instant timestamp; + private String messageId; + private String propertyId; + private String topic; + private String eventType; + private String correlationId; + private String payload; + private long batchSequence; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/dtos/webhook/ShijiBatchWebhookEvent.java b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/webhook/ShijiBatchWebhookEvent.java new file mode 100644 index 0000000..37c50c9 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/dtos/webhook/ShijiBatchWebhookEvent.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.common.dtos.webhook; + +import lombok.Data; + +import java.util.List; + +@Data +public class ShijiBatchWebhookEvent { + private List results; + private int count; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/enums/DeliveryReceiptState.java b/src/main/java/com/mgm/mcrm/apigateway/common/enums/DeliveryReceiptState.java new file mode 100644 index 0000000..70df84c --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/enums/DeliveryReceiptState.java @@ -0,0 +1,56 @@ +package com.mgm.mcrm.apigateway.common.enums; + +public enum DeliveryReceiptState { + + ESME_ROK(0, "Ok - Message Acceptable"), + + ESME_RINVMSGLEN(1, "Invalid Message Length"), + + ESME_RINVCMDLEN(2, "Invalid Command Length"), + + ESME_RINVCMDID(3, "Invalid Command ID"), + + ESME_RINVBNDSTS(4, "Invalid bind status"), + + ESME_RALYBND(5, "Bind attempted when already bound"), + + ESME_RINVPRTFLG(6, "Invalid priority flag"), + + ESME_RINVREGDLVFLG(7, "Invalid registered-delivery flag"), + + ESME_RSYSERR(8, "SMSC system error"), + + ESME_RINVSRCADR(9, "Invalid source address"), + + ESME_RINVDSTADR(11, "Invalid destination address"), + + ESME_RINVMSGID(12, "Invalid message-id"), + + NOT_FOUND(000, "Couldn't resolve.Ask admin to add."); + + private int value; + private String description; + + DeliveryReceiptState(int value, String description) { + this.value = value; + this.description = description; + } + + public static DeliveryReceiptState getDescription(int value) { + for (DeliveryReceiptState item : values()) { + if (item.value() == value) { + return item; + } + } + return NOT_FOUND; + } + + public int value() { + return value; + } + + public String description() { + return description; + } + +} \ No newline at end of file diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/enums/EumnDaylightStatus.java b/src/main/java/com/mgm/mcrm/apigateway/common/enums/EumnDaylightStatus.java new file mode 100644 index 0000000..781bd06 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/enums/EumnDaylightStatus.java @@ -0,0 +1,87 @@ +package com.mgm.mcrm.apigateway.common.enums; + +public enum EumnDaylightStatus { + + DUE_IN("DUE IN","DUE_IN"), + DUE_IN_UNDERSCORE("DUE_IN","DUE_IN"), + DUEIN("DUEIN","DUE_IN"), + RESERVED("RESERVED","RESERVED"), + RESERVE("Reserved","RESERVED"), + CHECKEDIN("Checked in","CHECKED_IN"), + CHECKED_IN("CHECKED IN","CHECKED_IN"), + CHECKED_IN_UNDERSCORE("CHECKED_IN","CHECKED_IN"), + CHECK_IN("CHECK_IN","CHECKED_IN"), + CHECK_IN_IN_HOUSE("CHECK_IN(IN_HOUSE)","CHECKED_IN"), + CHECKED_OUT("CHECKED OUT","CHECKED_OUT"), + CHECKED_OUT_UNDERSCORE("CHECKED_OUT","CHECKED_OUT"), + INHOUSE("INHOUSE","CHECKED_IN"), + IN_HOUSE("IN-HOUSE","CHECKED_IN"), + IN_HOUSE_UNDERSCORE("IN_HOUSE","CHECKED_IN"), + IN("IN","CHECKED_IN"), + IH("IH","CHECKED_IN"), + DUE_OUT("DUE OUT","DUE_OUT"), + DUE_OUT_UNDERSCORE("DUE_OUT","DUE_OUT"), + DUEOUT("DUEOUT","DUE_OUT"), + CANCELED("CANCELED","CANCELED"), + CANCELLED("CANCELLED","CANCELED"), + NO_SHOW("NO SHOW","CANCELED"), + NOSHOW("NOSHOW","CANCELED"); + + private String key; + private String value; + + EumnDaylightStatus(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public static String getStatusByDaylightStatus(String key) { + if (key == null) { + return "UNKNOWN"; + } + String normalizedKey = key.trim(); + EumnDaylightStatus[] objs = values(); + for (EumnDaylightStatus obj : objs) { + if (obj.getKey().equalsIgnoreCase(normalizedKey)) { + return obj.getValue(); + } + } + String compactKey = normalizeStatusKey(normalizedKey); + for (EumnDaylightStatus obj : objs) { + String compactObjKey = normalizeStatusKey(obj.getKey()); + if (compactObjKey.equalsIgnoreCase(compactKey)) { + return obj.getValue(); + } + } + if (compactKey.contains("CHECKIN") || compactKey.contains("INHOUSE")) { + return "CHECKED_IN"; + } + if (compactKey.contains("CANCEL")) { + return "CANCELED"; + } + return "UNKNOWN"; + } + + private static String normalizeStatusKey(String key) { + return key == null ? "" : key.replaceAll("[^A-Za-z0-9]", ""); + } + + public static boolean isInclude(String value){ + boolean include = false; + for (EumnDaylightStatus e: EumnDaylightStatus.values()){ + if(e.getValue().equals(value)){ + include = true; + break; + } + } + return include; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/enums/HttpMethods.java b/src/main/java/com/mgm/mcrm/apigateway/common/enums/HttpMethods.java new file mode 100644 index 0000000..5a472a7 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/enums/HttpMethods.java @@ -0,0 +1,74 @@ +package com.mgm.mcrm.apigateway.common.enums; + + +/** + * 枚举HttpMethods方法 + * + * @author Anka + */ +public enum HttpMethods { + + /** + * 求获取Request-URI所标识的资源 + */ + GET(0, "GET"), + + /** + * 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。 + * POST请求可能会导致新的资源的建立和/或已有资源的修改 + */ + POST(1, "POST"), + + /** + * 向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。 + * 这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息 + * 只获取响应信息报头 + */ + HEAD(2, "HEAD"), + + /** + * 向指定资源位置上传其最新内容(全部更新,操作幂等) + */ + PUT (3, "PUT"), + + /** + * 请求服务器删除Request-URI所标识的资源 + */ + DELETE (4, "DELETE"), + + /** + * 请求服务器回送收到的请求信息,主要用于测试或诊断 + */ + TRACE(5, "TRACE"), + + /** + * 向指定资源位置上传其最新内容(部分更新,非幂等) + */ + PATCH (6, "PATCH"), + + /** + * 返回服务器针对特定资源所支持的HTTP请求方法。 + * 也可以利用向Web服务器发送'*'的请求来测试服务器的功能性 + */ + OPTIONS (7, "OPTIONS"), + +// /** +// * HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器 +// */ +// CONNECT(99, "CONNECT"), + ; + + private int code; + private String name; + + private HttpMethods(int code, String name){ + this.code = code; + this.name = name; + } + public String getName() { + return name; + } + public int getCode() { + return code; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/exception/NoAccessException.java b/src/main/java/com/mgm/mcrm/apigateway/common/exception/NoAccessException.java new file mode 100644 index 0000000..625e907 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/exception/NoAccessException.java @@ -0,0 +1,8 @@ +package com.mgm.mcrm.apigateway.common.exception; + +public class NoAccessException extends RuntimeException { + + public NoAccessException(String message) { + super(message); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpCookieContext.java b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpCookieContext.java new file mode 100644 index 0000000..fecc325 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpCookieContext.java @@ -0,0 +1,46 @@ +package com.mgm.mcrm.apigateway.common.httpclient; + +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.protocol.HttpClientContext; + +public class HttpCookieContext { + /** + * 使用httpcontext,用于设置和携带Cookie + */ + private HttpClientContext context ; + + /** + * 储存Cookie + */ + private CookieStore cookieStore; + + public static HttpCookieContext custom(){ + return new HttpCookieContext(); + } + + private HttpCookieContext(){ + this.context = new HttpClientContext(); + this.cookieStore = new BasicCookieStore(); + this.context.setCookieStore(cookieStore); + } + + public HttpClientContext getContext() { + return context; + } + + public HttpCookieContext setContext(HttpClientContext context) { + this.context = context; + return this; + } + + public CookieStore getCookieStore() { + return cookieStore; + } + + public HttpCookieContext setCookieStore(CookieStore cookieStore) { + this.cookieStore = cookieStore; + return this; + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpEntityBuilder.java b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpEntityBuilder.java new file mode 100644 index 0000000..7d77162 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpEntityBuilder.java @@ -0,0 +1,310 @@ +package com.mgm.mcrm.apigateway.common.httpclient; + +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.FileEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.charset.Charset; +import java.util.*; +import java.util.Map.Entry; + + +public class HttpEntityBuilder { + + //传入参数特定类型 + public static final String ENTITY_STRING="$ENTITY_STRING$"; + public static final String ENTITY_JSON="$ENTITY_JSON$"; + public static final String ENTITY_FILE="$ENTITY_FILEE$"; + public static final String ENTITY_BYTES="$ENTITY_BYTES$"; + public static final String ENTITY_INPUTSTREAM="$ENTITY_INPUTSTREAM$"; + public static final String ENTITY_SERIALIZABLE="$ENTITY_SERIALIZABLE$"; + public static final String ENTITY_MULTIPART="$ENTITY_MULTIPART$"; + private static final List SPECIAL_ENTITIY = Arrays.asList(ENTITY_STRING, ENTITY_JSON, ENTITY_BYTES, ENTITY_FILE, ENTITY_INPUTSTREAM, ENTITY_SERIALIZABLE, ENTITY_MULTIPART); + + /** + * 是否开启debug, + */ + private static boolean debug = false; + + private static final Logger log = LoggerFactory.getLogger(HttpEntityBuilder.class); + + + /** + * 检测url是否含有参数,如果有,则把参数加到参数列表中 + * + * @param url 资源地址 + * @param nvps 参数列表 + * @param encoding 编码 + * @return 返回去掉参数的url + * @throws UnsupportedEncodingException 不支持的编码异常 + */ + public static String checkHasParas(String url, List nvps, String encoding) throws UnsupportedEncodingException { + // 检测url中是否存在参数 + if (url.contains("?") && url.indexOf("?") < url.indexOf("=")) { + String[] paras = url.substring(url.indexOf("?") + 1).split("&"); + for (String para : paras) { + int pos = para.indexOf("="); + if (pos >= 0) { + nvps.add(new BasicNameValuePair(para.substring(0, pos), para.substring(pos + 1))); + } else { + nvps.add(new BasicNameValuePair(para, "")); + } + } + url = url.substring(0, url.indexOf("?")); + } + return url; + } + + /** + * + * 参数转换,将map中的参数,转到参数列表中 + * + * @param nvps 参数列表 + * @param map 参数列表(map) + * @param encoding 编码 + * @return 返回HttpEntity + * @throws UnsupportedEncodingException 不支持的编码异常 + */ + public static HttpEntity map2HttpEntity(List nvps, Map map, String encoding) throws UnsupportedEncodingException { + HttpEntity entity = null; + if(map!=null && map.size()>0){ + boolean isSpecial = false; + // 拼接参数 + for (Entry entry : map.entrySet()) { + if(SPECIAL_ENTITIY.contains(entry.getKey())){//判断是否在之中 + isSpecial = true; + if(ENTITY_STRING.equals(entry.getKey())){//string + entity = new StringEntity(String.valueOf(entry.getValue()), Charset.forName(encoding)); + break; + }else if(ENTITY_JSON.equals(entry.getKey())){//json + ContentType jsonContentType; + + entity = new StringEntity(String.valueOf(entry.getValue()), Charset.forName(encoding)); + String contentType = "application/json"; + if (encoding != null && !encoding.trim().isEmpty()) { + // 创建带有编码的JSON ContentType + jsonContentType = ContentType.APPLICATION_JSON.withCharset(Charset.forName(encoding)); + }else { + // 使用默认的JSON ContentType(通常UTF-8) + jsonContentType = ContentType.APPLICATION_JSON; + } + // 创建实体时直接传入ContentType + entity = new StringEntity(String.valueOf(entry.getValue()), jsonContentType); + break; + }else if(ENTITY_BYTES.equals(entry.getKey())){//file + byte[] data = (byte[]) entry.getValue(); + entity = new ByteArrayEntity(data, ContentType.APPLICATION_OCTET_STREAM); + break; + }else if(ENTITY_FILE.equals(entry.getKey())){//file + if(File.class.isAssignableFrom(entry.getValue().getClass())){ + entity = new FileEntity((File)entry.getValue(), ContentType.APPLICATION_OCTET_STREAM); + }else if(entry.getValue().getClass()==String.class){ + entity = new FileEntity(new File((String) entry.getValue()), ContentType.create("text/plain", "UTF-8")); + } + break; + }else if(ENTITY_INPUTSTREAM.equals(entry.getKey())){//inputstream +// entity = new InputStreamEntity(); + break; + }else if(ENTITY_SERIALIZABLE.equals(entry.getKey())){//serializeable +// entity = new SerializableEntity() + break; + }else if(ENTITY_MULTIPART.equals(entry.getKey())){//MultipartEntityBuilder + File[] files = null; + if(File.class.isAssignableFrom(entry.getValue().getClass().getComponentType())){ + files=(File[])entry.getValue(); + }else if(entry.getValue().getClass().getComponentType()==String.class){ + String[] names = (String[]) entry.getValue(); + files = new File[names.length]; + for (int i = 0; i < names.length; i++) { + files[i] = new File(names[i]); + } + } + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setCharset(Charset.forName(encoding));// 设置请求的编码格式 + builder.setMode(HttpMultipartMode.LEGACY);// 设置浏览器兼容模式 + int count = 0; + for (File file : files) { +// //把文件转换成流对象FileBody +// FileBody fileBody = new FileBody(file); +// builder.addPart(String.valueOf(map.get(ENTITY_MULTIPART+".name")) + count++, fileBody); + builder.addBinaryBody(String.valueOf(map.get(ENTITY_MULTIPART+".name")) + count++,file); + } + boolean forceRemoveContentTypeCharset = (boolean)map.get(ENTITY_MULTIPART+".rmCharset"); + Map m = new HashMap(); + m.putAll(map); + m.remove(ENTITY_MULTIPART); + m.remove(ENTITY_MULTIPART+".name"); + m.remove(ENTITY_MULTIPART+".rmCharset"); + Iterator> iterator = m.entrySet().iterator(); + // 发送的数据 + while (iterator.hasNext()) { + Entry e = iterator.next(); + builder.addTextBody(e.getKey(), String.valueOf(e.getValue()), ContentType.create("text/plain", encoding)); + } + entity = builder.build();// 生成 HTTP POST 实体 + + //强制去除contentType中的编码设置,否则,在某些情况下会导致上传失败 + if(forceRemoveContentTypeCharset){ + removeContentTypeCharset(encoding, entity); + } + break; + }else { + nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue()))); + } + }else{ + nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue()))); + } + } + if(!isSpecial) { + entity = new UrlEncodedFormEntity(nvps, Charset.forName(encoding)); + } + } + return entity; + } + + /** + * 移除content-type中的charset + * + * @param encoding 编码 + * @param entity 请求参数及数据信息 + */ + private static void removeContentTypeCharset(String encoding, HttpEntity entity) { + try { + Class clazz = entity.getClass(); + Field field = clazz.getDeclaredField("contentType"); + field.setAccessible(true); //将字段的访问权限设为true:即去除private修饰符的影响 + if(Modifier.isFinal(field.getModifiers())){ + Field modifiersField = Field.class.getDeclaredField("modifiers"); //去除final修饰符的影响,将字段设为可修改的 + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } + BasicHeader o = (BasicHeader) field.get(entity); + field.set(entity, new BasicHeader(HttpHeaders.CONTENT_TYPE, o.getValue().replace("; charset="+encoding,""))); + } catch (NoSuchFieldException e) { + HttpEntityBuilder.exception(e); + } catch (SecurityException e) { + HttpEntityBuilder.exception(e); + } catch (IllegalArgumentException e) { + HttpEntityBuilder.exception(e); + } catch (IllegalAccessException e) { + HttpEntityBuilder.exception(e); + } + } + + + /** + * 生成参数 + * 参数格式:k1=v1&k2=v2 + * + * @param paras 参数列表 + * @return 返回参数列表(map) + */ + public static Map buildParas(String paras){ + String[] p = paras.split("&"); + String[][] ps = new String[p.length][2]; + int pos = 0; + for (int i = 0; i < p.length; i++) { + pos = p[i].indexOf("="); + ps[i][0]=p[i].substring(0,pos); + ps[i][1]=p[i].substring(pos+1); + pos = 0; + } + return buildParas(ps); + } + + /** + * 生成参数 + * 参数类型:{{"k1","v1"},{"k2","v2"}} + * + * @param paras 参数列表 + * @return 返回参数列表(map) + */ + public static Map buildParas(String[][] paras){ + // 创建参数队列 + Map map = new HashMap(); + for (String[] para: paras) { + map.put(para[0], para[1]); + } + return map; + } + + /** + * 打印消息 + * + * @param msg 消息 + */ + public static void info(String msg){ + if(debug){ + log.info(msg); + } + } + + /** + * 打印消息和异常堆栈 + * + * @param msg 异常消息 + * @param t 异常 + */ + public static void infoException(String msg, Throwable t){ + if(debug){ + log.info(msg,t); + } + } + + /** + * 打印错误消息 + * + * @param msg 异常消息 + */ + public static void error(String msg){ + log.error(msg); + } + + /** + * 打印错误消息和异常堆栈 + * + * @param msg 异常消息 + * @param t 异常 + */ + public static void errorException(String msg, Throwable t){ + log.error(msg, t); + } + + /** + * 打印异常堆栈 + * + * @param t 异常 + */ + public static void exception(Throwable t){ + log.error(String.valueOf(t)); + } + + /** + * 开启打印日志 + */ + public static void debug() { + debug(true); + } + /** + * 开启或关闭打印日志 + * @param debug 是否开启debug + */ + public static void debug(boolean debug) { + HttpEntityBuilder.debug = debug; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpHeaderBuilder.java b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpHeaderBuilder.java new file mode 100644 index 0000000..d01c747 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpHeaderBuilder.java @@ -0,0 +1,697 @@ +package com.mgm.mcrm.apigateway.common.httpclient; + + +import com.mgm.mcrm.apigateway.common.config.HttpHeader; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.message.BasicHeader; + +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + + +/** + * http 发送参数 + * + * @author Aaka + */ +public class HttpHeaderBuilder { + + private HttpHeaderBuilder() {}; + + public static HttpHeaderBuilder custom() { + return new HttpHeaderBuilder(); + } + + //记录head头信息 + private Map headerMaps = new HashMap(); + + /** + * 自定义header头信息 + * + * @param key header-key + * @param value header-value + * @return 返回当前对象 + */ + public HttpHeaderBuilder other(String key, String value) { + headerMaps.put(key, new BasicHeader(key, value)); + return this; + } + /** + * 指定客户端能够接收的内容类型 + * 例如:Accept: text/plain, text/html + * + * @param accept accept + * @return 返回当前对象 + */ + public HttpHeaderBuilder accept(String accept) { + headerMaps.put(HttpReqHead.ACCEPT, + new BasicHeader(HttpReqHead.ACCEPT, accept)); + return this; + } + + /** + * 浏览器可以接受的字符编码集 + * 例如:Accept-Charset: iso-8859-5 + * + * @param acceptCharset accept-charset + * @return 返回当前对象 + */ + public HttpHeaderBuilder acceptCharset(String acceptCharset) { + headerMaps.put(HttpReqHead.ACCEPT_CHARSET, + new BasicHeader(HttpReqHead.ACCEPT_CHARSET, acceptCharset)); + return this; + } + + /** + * 指定浏览器可以支持的web服务器返回内容压缩编码类型 + * 例如:Accept-Encoding: compress, gzip + * + * @param acceptEncoding accept-encoding + * @return 返回当前对象 + */ + public HttpHeaderBuilder acceptEncoding(String acceptEncoding) { + headerMaps.put(HttpReqHead.ACCEPT_ENCODING, + new BasicHeader(HttpReqHead.ACCEPT_ENCODING, acceptEncoding)); + return this; + } + + /** + * 浏览器可接受的语言 + * 例如:Accept-Language: en,zh + * + * @param acceptLanguage accept-language + * @return 返回当前对象 + */ + public HttpHeaderBuilder acceptLanguage(String acceptLanguage) { + headerMaps.put(HttpReqHead.ACCEPT_LANGUAGE, + new BasicHeader(HttpReqHead.ACCEPT_LANGUAGE, acceptLanguage)); + return this; + } + + /** + * 可以请求网页实体的一个或者多个子范围字段 + * 例如:Accept-Ranges: bytes + * + * @param acceptRanges accept-ranges + * @return 返回当前对象 + */ + public HttpHeaderBuilder acceptRanges(String acceptRanges) { + headerMaps.put(HttpReqHead.ACCEPT_RANGES, + new BasicHeader(HttpReqHead.ACCEPT_RANGES, acceptRanges)); + return this; + } + + /** + * HTTP授权的授权证书 + * 例如:Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + * + * @param authorization authorization + * @return 返回当前对象 + */ + public HttpHeaderBuilder authorization(String authorization) { + headerMaps.put(HttpReqHead.AUTHORIZATION, + new BasicHeader(HttpReqHead.AUTHORIZATION, authorization)); + return this; + } + + public HttpHeaderBuilder ac_property_id(String ac_property_id) { + headerMaps.put(HttpReqHead.AC_PROPERTY_ID, + new BasicHeader(HttpReqHead.AC_PROPERTY_ID, ac_property_id)); + return this; + } + + /** + * 指定请求和响应遵循的缓存机制 + * 例如:Cache-Control: no-cache + * + * @param cacheControl cache-control + * @return 返回当前对象 + */ + public HttpHeaderBuilder cacheControl(String cacheControl) { + headerMaps.put(HttpReqHead.CACHE_CONTROL, + new BasicHeader(HttpReqHead.CACHE_CONTROL, cacheControl)); + return this; + } + + /** + * 表示是否需要持久连接(HTTP 1.1默认进行持久连接) + * 例如:Connection: close 短链接; Connection: keep-alive 长连接 + * + * @param connection connection + * @return 返回当前对象 + */ + public HttpHeaderBuilder connection(String connection) { + headerMaps.put(HttpReqHead.CONNECTION, + new BasicHeader(HttpReqHead.CONNECTION, connection)); + return this; + } + + /** + * HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器 + * 例如:Cookie: $Version=1; Skin=new; + * + * @param cookie cookie + * @return 返回当前对象 + */ + public HttpHeaderBuilder cookie(String cookie) { + headerMaps.put(HttpReqHead.COOKIE, + new BasicHeader(HttpReqHead.COOKIE, cookie)); + return this; + } + + /** + * 请求内容长度 + * 例如:Content-Length: 348 + * + * @param contentLength content-length + * @return 返回当前对象 + */ + public HttpHeaderBuilder contentLength(String contentLength) { + headerMaps.put(HttpReqHead.CONTENT_LENGTH, + new BasicHeader(HttpReqHead.CONTENT_LENGTH, contentLength)); + return this; + } + + /** + * 请求的与实体对应的MIME信息 + * 例如:Content-Type: application/x-www-form-urlencoded + * + * @param contentType content-type + * @return 返回当前对象 + */ + public HttpHeaderBuilder contentType(String contentType) { + headerMaps.put(HttpReqHead.CONTENT_TYPE, + new BasicHeader(HttpReqHead.CONTENT_TYPE, contentType)); + return this; + } + + /** + * 请求发送的日期和时间 + * 例如:Date: Tue, 15 Nov 2010 08:12:31 GMT + * + * @param date date + * @return 返回当前对象 + */ + public HttpHeaderBuilder date(String date) { + headerMaps.put(HttpReqHead.DATE, + new BasicHeader(HttpReqHead.DATE, date)); + return this; + } + + /** + * 请求的特定的服务器行为 + * 例如:Expect: 100-continue + * + * @param expect expect + * @return 返回当前对象 + */ + public HttpHeaderBuilder expect(String expect) { + headerMaps.put(HttpReqHead.EXPECT, + new BasicHeader(HttpReqHead.EXPECT, expect)); + return this; + } + + /** + * 发出请求的用户的Email + * 例如:From: user@email.com + * + * @param from from + * @return 返回当前对象 + */ + public HttpHeaderBuilder from(String from) { + headerMaps.put(HttpReqHead.FROM, + new BasicHeader(HttpReqHead.FROM, from)); + return this; + } + + /** + * 指定请求的服务器的域名和端口号 + * 例如:Host: blog.csdn.net + * + * @param host host + * @return 返回当前对象 + */ + public HttpHeaderBuilder host(String host) { + headerMaps.put(HttpReqHead.HOST, + new BasicHeader(HttpReqHead.HOST, host)); + return this; + } + + /** + * 只有请求内容与实体相匹配才有效 + * 例如:If-Match: “737060cd8c284d8af7ad3082f209582d” + * + * @param ifMatch if-match + * @return 返回当前对象 + */ + public HttpHeaderBuilder ifMatch(String ifMatch) { + headerMaps.put(HttpReqHead.IF_MATCH, + new BasicHeader(HttpReqHead.IF_MATCH, ifMatch)); + return this; + } + + /** + * 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 + * 例如:If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT + * + * @param ifModifiedSince if-modified-Since + * @return 返回当前对象 + */ + public HttpHeaderBuilder ifModifiedSince(String ifModifiedSince) { + headerMaps.put(HttpReqHead.IF_MODIFIED_SINCE, + new BasicHeader(HttpReqHead.IF_MODIFIED_SINCE, ifModifiedSince)); + return this; + } + + /** + * 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 + * 例如:If-None-Match: “737060cd8c284d8af7ad3082f209582d” + * + * @param ifNoneMatch if-none-match + * @return 返回当前对象 + */ + public HttpHeaderBuilder ifNoneMatch(String ifNoneMatch) { + headerMaps.put(HttpReqHead.IF_NONE_MATCH, + new BasicHeader(HttpReqHead.IF_NONE_MATCH, ifNoneMatch)); + return this; + } + + /** + * 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag + * 例如:If-Range: “737060cd8c284d8af7ad3082f209582d” + * + * @param ifRange if-range + * @return 返回当前对象 + */ + public HttpHeaderBuilder ifRange(String ifRange) { + headerMaps.put(HttpReqHead.IF_RANGE, + new BasicHeader(HttpReqHead.IF_RANGE, ifRange)); + return this; + } + + /** + * 只在实体在指定时间之后未被修改才请求成功 + * 例如:If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT + * + * @param ifUnmodifiedSince if-unmodified-since + * @return 返回当前对象 + */ + public HttpHeaderBuilder ifUnmodifiedSince(String ifUnmodifiedSince) { + headerMaps.put(HttpReqHead.IF_UNMODIFIED_SINCE, + new BasicHeader(HttpReqHead.IF_UNMODIFIED_SINCE, ifUnmodifiedSince)); + return this; + } + + /** + * 限制信息通过代理和网关传送的时间 + * 例如:Max-Forwards: 10 + * + * @param maxForwards max-forwards + * @return 返回当前对象 + */ + public HttpHeaderBuilder maxForwards(String maxForwards) { + headerMaps.put(HttpReqHead.MAX_FORWARDS, + new BasicHeader(HttpReqHead.MAX_FORWARDS, maxForwards)); + return this; + } + + /** + * 用来包含实现特定的指令 + * 例如:Pragma: no-cache + * + * @param pragma pragma + * @return 返回当前对象 + */ + public HttpHeaderBuilder pragma(String pragma) { + headerMaps.put(HttpReqHead.PRAGMA, + new BasicHeader(HttpReqHead.PRAGMA, pragma)); + return this; + } + + /** + * 连接到代理的授权证书 + * 例如:Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + * + * @param proxyAuthorization proxy-authorization + * @return 返回当前对象 + */ + public HttpHeaderBuilder proxyAuthorization(String proxyAuthorization) { + headerMaps.put(HttpReqHead.PROXY_AUTHORIZATION, + new BasicHeader(HttpReqHead.PROXY_AUTHORIZATION, proxyAuthorization)); + return this; + } + + /** + * 只请求实体的一部分,指定范围 + * 例如:Range: bytes=500-999 + * + * @param range range + * @return 返回当前对象 + */ + public HttpHeaderBuilder range(String range) { + headerMaps.put(HttpReqHead.RANGE, + new BasicHeader(HttpReqHead.RANGE, range)); + return this; + } + + /** + * 先前网页的地址,当前请求网页紧随其后,即来路 + * 例如:Referer: http://www.zcmhi.com/archives/71.html + * + * @param referer referer + * @return 返回当前对象 + */ + public HttpHeaderBuilder referer(String referer) { + headerMaps.put(HttpReqHead.REFERER, + new BasicHeader(HttpReqHead.REFERER, referer)); + return this; + } + + /** + * 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 + * 例如:TE: trailers,deflate;q=0.5 + * + * @param te te + * @return 返回当前对象 + */ + public HttpHeaderBuilder te(String te) { + headerMaps.put(HttpReqHead.TE, + new BasicHeader(HttpReqHead.TE, te)); + return this; + } + + /** + * 向服务器指定某种传输协议以便服务器进行转换(如果支持) + * 例如:Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 + * + * @param upgrade upgrade + * @return 返回当前对象 + */ + public HttpHeaderBuilder upgrade(String upgrade) { + headerMaps.put(HttpReqHead.UPGRADE, + new BasicHeader(HttpReqHead.UPGRADE, upgrade)); + return this; + } + + /** + * User-Agent的内容包含发出请求的用户信息 + * + * @param userAgent user-agent + * @return 返回当前对象 + */ + public HttpHeaderBuilder userAgent(String userAgent) { + headerMaps.put(HttpReqHead.USER_AGENT, + new BasicHeader(HttpReqHead.USER_AGENT, userAgent)); + return this; + } + + /** + * 关于消息实体的警告信息 + * 例如:Warn: 199 Miscellaneous warning + * + * @param warning warning + * @return 返回当前对象 + */ + public HttpHeaderBuilder warning(String warning) { + headerMaps.put(HttpReqHead.WARNING, + new BasicHeader(HttpReqHead.WARNING, warning)); + return this; + } + + /** + * 通知中间网关或代理服务器地址,通信协议 + * 例如:Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) + * + * @param via via + * @return 返回当前对象 + */ + public HttpHeaderBuilder via(String via) { + headerMaps.put(HttpReqHead.VIA, + new BasicHeader(HttpReqHead.VIA, via)); + return this; + } + + /** + * 设置此HTTP连接的持续时间(超时时间) + * 例如:Keep-Alive: 300 + * + * @param keepAlive keep-alive + * @return 返回当前对象 + + */ + public HttpHeaderBuilder keepAlive(String keepAlive) { + headerMaps.put(HttpReqHead.KEEP_ALIVE, + new BasicHeader(HttpReqHead.KEEP_ALIVE, keepAlive)); + return this; + } + + public String accept() { + return get(HttpReqHead.ACCEPT); + } + + public String acceptCharset() { + return get(HttpReqHead.ACCEPT_CHARSET); + } + + public String acceptEncoding() { + return get(HttpReqHead.ACCEPT_ENCODING); + } + + public String acceptLanguage() { + return get(HttpReqHead.ACCEPT_LANGUAGE); + } + + public String acceptRanges() { + return get(HttpReqHead.ACCEPT_RANGES); + } + + public String authorization() { + return get(HttpReqHead.AUTHORIZATION); + } + + public String cacheControl() { + return get(HttpReqHead.CACHE_CONTROL); + } + + public String connection() { + return get(HttpReqHead.CONNECTION); + } + + public String cookie() { + return get(HttpReqHead.COOKIE); + } + + public String contentLength() { + return get(HttpReqHead.CONTENT_LENGTH); + } + + public String contentType() { + return get(HttpReqHead.CONTENT_TYPE); + } + + public String date() { + return get(HttpReqHead.DATE); + } + + public String expect() { + return get(HttpReqHead.EXPECT); + } + + public String from() { + return get(HttpReqHead.FROM); + } + + public String host() { + return get(HttpReqHead.HOST); + } + + public String ifMatch() { + return get(HttpReqHead.IF_MATCH); + } + + public String ifModifiedSince() { + return get(HttpReqHead.IF_MODIFIED_SINCE); + } + + public String ifNoneMatch() { + return get(HttpReqHead.IF_NONE_MATCH); + } + + public String ifRange() { + return get(HttpReqHead.IF_RANGE); + } + + public String ifUnmodifiedSince() { + return get(HttpReqHead.IF_UNMODIFIED_SINCE); + } + + public String maxForwards() { + return get(HttpReqHead.MAX_FORWARDS); + } + + public String pragma() { + return get(HttpReqHead.PRAGMA); + } + + public String proxyAuthorization() { + return get(HttpReqHead.PROXY_AUTHORIZATION); + } + + public String referer() { + return get(HttpReqHead.REFERER); + } + + public String te() { + return get(HttpReqHead.TE); + } + + public String upgrade() { + return get(HttpReqHead.UPGRADE); + } + + public String userAgent() { + return get(HttpReqHead.USER_AGENT); + } + + public String via() { + return get(HttpReqHead.VIA); + } + + public String warning() { + return get(HttpReqHead.WARNING); + } + + public String keepAlive() { + return get(HttpReqHead.KEEP_ALIVE); + } + + + /** + * 获取head信息 + * + * @return + */ + private String get(String headName) { + if (headerMaps.containsKey(headName)) { + return headerMaps.get(headName).getValue(); + } + return null; + } + + /** + * 返回header头信息 + * + * @return 返回构建的header头信息数组 + */ + public Header[] build() { + Header[] headers = new Header[headerMaps.size()]; + int i = 0; + for (Header header : headerMaps.values()) { + headers[i] = header; + i++; + } + headerMaps.clear(); + headerMaps = null; + return headers; + } + + /** + * Http头信息 + * + * @author arron + * @date 2015年11月9日 上午11:29:04 + * @version 1.0 + */ + private static class HttpReqHead { + public static final String ACCEPT = "Accept"; + public static final String ACCEPT_CHARSET = "Accept-Charset"; + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + public static final String ACCEPT_LANGUAGE = "Accept-Language"; + public static final String ACCEPT_RANGES = "Accept-Ranges"; + public static final String AUTHORIZATION = "Authorization"; + public static final String AC_PROPERTY_ID = "AC-Property-Id"; + public static final String CACHE_CONTROL = "Cache-Control"; + public static final String CONNECTION = "Connection"; + public static final String COOKIE = "Cookie"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String DATE= "Date"; + public static final String EXPECT = "Expect"; + public static final String FROM = "From"; + public static final String HOST = "Host"; + public static final String IF_MATCH = "If-Match"; + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + public static final String IF_NONE_MATCH = "If-None-Match"; + public static final String IF_RANGE = "If-Range"; + public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + public static final String KEEP_ALIVE = "Keep-Alive"; + public static final String MAX_FORWARDS = "Max-Forwards"; + public static final String PRAGMA = "Pragma"; + public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + public static final String RANGE = "Range"; + public static final String REFERER = "Referer"; + public static final String TE = "TE"; + public static final String UPGRADE = "Upgrade"; + public static final String USER_AGENT = "User-Agent"; + public static final String VIA = "Via"; + public static final String WARNING = "Warning"; + } + + /** + * 常用头信息配置 + * + * @author arron + * @version 1.0 + */ + public static class Headers{ + public static final String APP_FORM_URLENCODED="application/x-www-form-urlencoded"; + public static final String TEXT_PLAIN="text/plain"; + public static final String TEXT_HTML="text/html"; + public static final String TEXT_XML="text/xml"; + public static final String TEXT_JSON="text/json"; + public static final String CONTENT_CHARSET_ISO_8859_1 = StandardCharsets.ISO_8859_1.name(); + public static final String CONTENT_CHARSET_UTF8 = StandardCharsets.UTF_8.name(); + public static final String DEF_PROTOCOL_CHARSET = StandardCharsets.US_ASCII.name(); + public static final String CONN_CLOSE = "close"; + public static final String KEEP_ALIVE = "keep-alive"; + public static final String EXPECT_CONTINUE = "100-continue"; + } + + + + + + public static Map getDefaultHeaders(URLConnection connection) { + Map defaultHeaders = new HashMap<>(); + defaultHeaders.put(HttpHeader.ACCEPT, "*/*"); + defaultHeaders.put(HttpHeader.CONNECTION, "Keep-Alive"); + defaultHeaders.put(HttpHeader.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + defaultHeaders.put(HttpHeader.ACCEPT_CHARSET, "utf-8"); + return defaultHeaders; + } + + /** + * 設置請求頭到連接對象 + * + * @param conn URL連接對象 + * @param contentType 內容類型 + * @param headers HTTP 請求頭 Map(可選,如果為 null 或空則使用默認值) + */ + public static void setRequestHeaders(URLConnection conn, String contentType, Map headers) { + Map defaultHeaders = getDefaultHeaders(conn); + // 如果傳入了自定義 headers,則合併(自定義的優先) + if (headers != null && !headers.isEmpty()) { + defaultHeaders.putAll(headers); + } + // 設置所有請求頭 + for (Map.Entry entry : defaultHeaders.entrySet()) { + conn.setRequestProperty(entry.getKey(), entry.getValue()); + } + // Content-Type 優先級最高,如果指定了則覆蓋 + if (contentType != null) { + conn.setRequestProperty("Content-Type", contentType); + } + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpParameterBuilder.java b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpParameterBuilder.java new file mode 100644 index 0000000..850f89f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpParameterBuilder.java @@ -0,0 +1,72 @@ +package com.mgm.mcrm.apigateway.common.httpclient; + + +import com.alibaba.fastjson.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * HTTP 参数构建器 + * + * 用于构建 HTTP 请求参数(查询字符串、请求体等)。 + * + * @author Aaka + */ +public class HttpParameterBuilder { + private static final Logger log = LoggerFactory.getLogger(HttpParameterBuilder.class); + + /** + * 將參數 Map 轉換為查詢字符串(用於 GET 請求) + * + * @param params 參數 Map + * @return 查詢字符串,格式為 name1=value1&name2=value2 + */ + public static String buildQueryString(Map params) { + if (params == null || params.isEmpty()) { + return ""; + } + return params.entrySet().stream() + .filter(entry -> entry.getKey() != null && entry.getValue() != null) + .map(entry -> { + try { + String key = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8); + String value = URLEncoder.encode(String.valueOf(entry.getValue()), StandardCharsets.UTF_8); + return key + "=" + value; + } catch (Exception e) { + log.warn("Failed to encode parameter: {}={}", entry.getKey(), entry.getValue(), e); + return null; + } + }) + .filter(java.util.Objects::nonNull) + .collect(Collectors.joining("&")); + } + + + /** + * 將參數 Map 轉換為請求體字符串 + * + * @param params 參數 Map + * @param contentType 內容類型 + * @return 請求體字符串 + */ + public static String buildRequestBody(Map params, String contentType) { + if (params == null || params.isEmpty()) { + return ""; + } + // 如果是 JSON 格式 + if (contentType != null && contentType.contains("application/json")) { + // 使用 FastJSON 將 Map 轉換為 JSON 字符串 + return JSON.toJSONString(params); + } else { + // 默認使用 form-urlencoded 格式 + return buildQueryString(params); + } + } + +} + diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpRequestOptions.java b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpRequestOptions.java new file mode 100644 index 0000000..016d23e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpRequestOptions.java @@ -0,0 +1,375 @@ +package com.mgm.mcrm.apigateway.common.httpclient; + +import com.mgm.mcrm.apigateway.common.enums.HttpMethods; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.Header; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.util.Timeout; + +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +public class HttpRequestOptions { + + private HttpRequestOptions(){}; + + /** + * 获取实例 + * @return 返回当前对象 + */ + public static HttpRequestOptions create(){ + return new HttpRequestOptions(); + } + + + /** + * HttpClient对象 + */ + private HttpClient client; + + /** + * Header头信息 + */ + private Header[] headers; + + /** + * 是否返回response的headers + */ + private boolean isReturnRespHeaders; + + /** + * 请求方法 + */ + private HttpMethods method=HttpMethods.POST; + + /** + * 请求方法名称 + */ + private String methodName; + + /** + * 用于cookie操作 + */ + private HttpContext context; + + // private Map map; + + /** + * 以json格式作为输入参数 + */ + private String json; + + /** + * 输入输出编码 + */ + private String encoding= Charset.defaultCharset().displayName(); + + /** + * 输入编码 + */ + private String inenc; + + /** + * 输出编码 + */ + private String outenc; + + /** + * 设置RequestConfig + */ + private RequestConfig requestConfig; + + /** + * 解决多线程下载时,strean被close的问题 + */ + private static final ThreadLocal outs = new ThreadLocal(); + + /** + * 解决多线程处理时,url被覆盖问题 + */ + private static final ThreadLocal urls = new ThreadLocal(); + + /** + * 解决多线程处理时,url被覆盖问题 + */ + private static final ThreadLocal> maps = new ThreadLocal>(); + + /** + * @param client HttpClient对象 + * @return 返回当前对象 + */ + public HttpRequestOptions client(HttpClient client) { + this.client = client; + return this; + } + + /** + * @param url 资源url + * @return 返回当前对象 + */ + public HttpRequestOptions url(String url) { + urls.set(url); + return this; + } + + /** + * @param headers Header头信息 + * @return 返回当前对象 + */ + public HttpRequestOptions headers(Header[] headers) { + this.headers = headers; + return this; + } + + /** + * Header头信息(是否返回response中的headers) + * + * @param headers Header头信息 + * @param isReturnRespHeaders 是否返回response中的headers + * @return 返回当前对象 + */ + public HttpRequestOptions headers(Header[] headers, boolean isReturnRespHeaders) { + this.headers = headers; + this.isReturnRespHeaders=isReturnRespHeaders; + return this; + } + + /** + * @param method 请求方法 + * @return 返回当前对象 + */ + public HttpRequestOptions method(HttpMethods method) { + this.method = method; + return this; + } + + /** + * @param methodName 请求方法 + * @return 返回当前对象 + */ + public HttpRequestOptions methodName(String methodName) { + this.methodName = methodName; + return this; + } + + /** + * @param context cookie操作相关 + * @return 返回当前对象 + */ + public HttpRequestOptions context(HttpContext context) { + this.context = context; + return this; + } + + /** + * @param map 传递参数 + * @return 返回当前对象 + */ + public HttpRequestOptions map(Map map) { +// synchronized (getClass()) { +// if(this.map==null || map==null){ +// this.map = map; +// }else { +// this.map.putAll(map);; +// } +// } + Map m = maps.get(); + if(m == null || map.isEmpty()){ + m = map; + }else { + m.putAll(map); + } + maps.set(m); + return this; + } + + /** + * @param json 以json格式字符串作为参数 + * @return 返回当前对象 + */ + public HttpRequestOptions json(String json) { + this.json = json; + Map map = new HashMap(); + map.put(HttpEntityBuilder.ENTITY_JSON, json); + maps.set(map); + return this; + } + + /** + * @param filePaths 待上传文件所在路径 + * @return 返回当前对象 + */ + public HttpRequestOptions files(String[] filePaths) { + return files(filePaths, "file"); + } + /** + * 上传文件时用到 + * @param filePaths 待上传文件所在路径 + * @param inputName 即file input 标签的name值,默认为file + * @return 返回当前对象 + */ + public HttpRequestOptions files(String[] filePaths, String inputName) { + return files(filePaths, inputName, false); + } + /** + * 上传文件时用到 + * @param filePaths 待上传文件所在路径 + * @param inputName 即file input 标签的name值,默认为file + * @param forceRemoveContentTypeChraset 是否强制一处content-type中设置的编码类型 + * @return 返回当前对象 + */ + public HttpRequestOptions files(String[] filePaths, String inputName, boolean forceRemoveContentTypeChraset) { +// synchronized (getClass()) { +// if(this.map==null){ +// this.map= new HashMap(); +// } +// } +// map.put(Utils.ENTITY_MULTIPART, filePaths); +// map.put(Utils.ENTITY_MULTIPART+".name", inputName); +// map.put(Utils.ENTITY_MULTIPART+".rmCharset", forceRemoveContentTypeChraset); + + Map m = maps.get(); + if(m.isEmpty()){ + m = new HashMap(); + } + m.put(HttpEntityBuilder.ENTITY_MULTIPART, filePaths); + m.put(HttpEntityBuilder.ENTITY_MULTIPART+".name", inputName); + m.put(HttpEntityBuilder.ENTITY_MULTIPART+".rmCharset", forceRemoveContentTypeChraset); + maps.set(m); + return this; + } + + /** + * @param encoding 输入输出编码 + * @return 返回当前对象 + */ + public HttpRequestOptions encoding(String encoding) { + //设置输入输出 + inenc(encoding); + outenc(encoding); + this.encoding = encoding; + return this; + } + + /** + * @param inenc 输入编码 + */ + public void inenc(String inenc) { + this.inenc = inenc; + } + + /** + * @param outenc 输出编码 + */ + public void outenc(String outenc) { + this.outenc = outenc; + } + + /** + * @param out 输出流对象 + * @return 返回当前对象 + */ + public HttpRequestOptions out(OutputStream out) { + outs.set(out); + return this; + } + + /** + * 设置超时时间 + * + * @param timeout 超市时间,单位-毫秒 + * @return 返回当前对象 + */ + public HttpRequestOptions timeout(int timeout){ + return timeout(timeout, true); + } + + /** + * 设置超时时间以及是否允许网页重定向(自动跳转 302) + * + * @param timeout 超时时间,单位-毫秒 + * @param redirectEnable 自动跳转 + * @return 返回当前对象 + */ + public HttpRequestOptions timeout(int timeout, boolean redirectEnable){ + // 配置请求的超时设置 + // 注意:HttpClient 5 中需要使用 Timeout 对象,而不是直接的 int 值 + Timeout timeoutValue = Timeout.ofMilliseconds(timeout); + RequestConfig config = RequestConfig.custom() + .setConnectionRequestTimeout(timeoutValue) + .setResponseTimeout(timeoutValue) + .setRedirectsEnabled(redirectEnable) + .build(); + return timeout(config); + } + + /** + * 设置代理、超时时间、允许网页重定向等 + * + * @param requestConfig 超时时间,单位-毫秒 + * @return 返回当前对象 + */ + public HttpRequestOptions timeout(RequestConfig requestConfig){ + this.requestConfig = requestConfig; + return this; + } + + public HttpClient client() { + return client; + } + + public Header[] headers() { + return headers; + } + public boolean isReturnRespHeaders() { + return isReturnRespHeaders; + } + + public String url() { + return urls.get(); + } + + public HttpMethods method() { + return method; + } + + public String methodName() { + return methodName; + } + + public HttpContext context() { + return context; + } + + public Map map() { +// return map; + return maps.get(); + } + + public String json() { + return json; + } + + public String encoding() { + return encoding; + } + + public String inenc() { + return inenc == null ? encoding : inenc; + } + + public String outenc() { + return outenc == null ? encoding : outenc; + } + + public OutputStream out() { + return outs.get(); + } + + public RequestConfig requestConfig() { + return requestConfig; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpSslSupport.java b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpSslSupport.java new file mode 100644 index 0000000..b6e003d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/httpclient/HttpSslSupport.java @@ -0,0 +1,197 @@ +package com.mgm.mcrm.apigateway.common.httpclient; + +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.ssl.SSLContexts; + +import javax.net.ssl.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +/** + * SSL 工具类(基于 HttpClient 5) + * + * 提供: + * - 信任所有证书的 SSLSocketFactory + * - 信任所有证书的 SSLConnectionSocketFactory + * + * 注意:默认实现信任所有证书,仅适用于内部系统或测试环境。 + * + * @author Anka + * @version 2.0 (client5) + */ +public class HttpSslSupport { + private static final SSLHandler SIMPLE_VERIFIER = new SSLHandler(); + + private static SSLSocketFactory sslFactory; + // 注意:SSLConnectionSocketFactory 在 HttpClient 5 中被标记为 deprecated, + // 但这是目前唯一可用的方式,新的 API 尚未稳定 + @SuppressWarnings("deprecation") + private static SSLConnectionSocketFactory sslConnFactory; + + private static final HttpSslSupport INSTANCE = new HttpSslSupport(); + + private SSLContext sc; + + public static HttpSslSupport getInstance() { + return INSTANCE; + } + + public static HttpSslSupport custom() { + return new HttpSslSupport(); + } + + /** + * 重写 X509TrustManager 和 HostnameVerifier,信任所有证书与主机名。 + * 仅推荐在内部环境或调试场景使用。 + */ + private static class SSLHandler implements X509TrustManager, HostnameVerifier { + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, + String authType) throws CertificateException { + // 信任所有服务器证书 + } + + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, + String authType) throws CertificateException { + // 信任所有客户端证书 + } + + @Override + public boolean verify(String paramString, SSLSession paramSSLSession) { + // 信任所有主机名 + return true; + } + } + + /** + * 获取默认 HostnameVerifier(信任所有主机名) + */ + public static HostnameVerifier getVerifier() { + return SIMPLE_VERIFIER; + } + + /** + * 获取信任所有证书的 {@link SSLSocketFactory} + */ + public synchronized SSLSocketFactory getSSLSF(SSLProtocolVersion sslpv) { + if (sslFactory != null) { + return sslFactory; + } + try { + SSLContext context = getSSLContext(sslpv); + context.init(null, new TrustManager[]{SIMPLE_VERIFIER}, null); + sslFactory = context.getSocketFactory(); + return sslFactory; + } catch (KeyManagementException e) { + throw new RuntimeException("初始化 SSLSocketFactory 失败", e); + } + } + + /** + * 获取信任所有证书的 {@link SSLConnectionSocketFactory}(HttpClient5) + * + * 注意:SSLConnectionSocketFactory 在 HttpClient 5 中被标记为 deprecated, + * 但这是目前唯一可用的方式,新的 API 尚未稳定 + */ + @SuppressWarnings("deprecation") + public synchronized SSLConnectionSocketFactory getSSLCONNSF(SSLProtocolVersion sslpv) { + if (sslConnFactory != null) { + return sslConnFactory; + } + try { + SSLContext context = getSSLContext(sslpv); + context.init(null, new TrustManager[]{SIMPLE_VERIFIER}, new java.security.SecureRandom()); + sslConnFactory = new SSLConnectionSocketFactory(context, SIMPLE_VERIFIER); + return sslConnFactory; + } catch (KeyManagementException e) { + throw new RuntimeException("初始化 SSLConnectionSocketFactory 失败", e); + } + } + + /** + * 使用自定义证书(trustStore)初始化 SSLContext + */ + public HttpSslSupport customSSL(String keyStorePath, String keyStorepass) { + FileInputStream instream = null; + try { + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + instream = new FileInputStream(new File(keyStorePath)); + trustStore.load(instream, keyStorepass.toCharArray()); + // 相信自己的 CA 和所有自签名证书 + this.sc = SSLContexts.custom() + .loadTrustMaterial(trustStore, (chain, authType) -> true) + .build(); + return this; + } catch (KeyManagementException | KeyStoreException | + NoSuchAlgorithmException | CertificateException | IOException e) { + throw new RuntimeException("初始化自定义 SSLContext 失败", e); + } finally { + if (instream != null) { + try { + instream.close(); + } catch (IOException ignore) { + // ignore + } + } + } + } + + /** + * 获取(或创建)指定协议版本的 {@link SSLContext} + */ + public SSLContext getSSLContext(SSLProtocolVersion sslpv) { + try { + if (this.sc == null) { + this.sc = SSLContext.getInstance(sslpv.getName()); + } + return this.sc; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("不支持的 SSL 协议版本: " + sslpv.getName(), e); + } + } + + /** + * 支持的 SSL/TLS 协议版本枚举 + */ + public enum SSLProtocolVersion { + SSL("SSL"), + SSLv3("SSLv3"), + TLSv1("TLSv1"), + TLSv1_1("TLSv1.1"), + TLSv1_2("TLSv1.2"), + TLSv1_3("TLSv1.3"); + + private final String name; + + SSLProtocolVersion(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public static SSLProtocolVersion find(String name) { + for (SSLProtocolVersion pv : SSLProtocolVersion.values()) { + if (pv.getName().equalsIgnoreCase(name)) { + return pv; + } + } + throw new IllegalArgumentException("未支持当前 SSL 版本号:" + name); + } + + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/listener/ConfigListener.java b/src/main/java/com/mgm/mcrm/apigateway/common/listener/ConfigListener.java new file mode 100644 index 0000000..e5d444b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/listener/ConfigListener.java @@ -0,0 +1,33 @@ +package com.mgm.mcrm.apigateway.common.listener; + +import com.mgm.mcrm.apigateway.allotment.services.RoomCriteriaDaylightService; +import com.mgm.mcrm.apigateway.common.constants.SysCommon; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.AvailableRoomCountUpdatedHandler; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.ReservationNotificationHandler; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.RoomTypeDateAvailabilityChangedHandler; +import com.mgm.mcrm.apigateway.daylight.webhook.services.DaylightWebhookFactory; +import com.mgm.mcrm.apigateway.reservation.services.BookingService; +import jakarta.annotation.Resource; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Component +public class ConfigListener implements ApplicationListener { + + @Resource + private RoomCriteriaDaylightService roomCriteriaDaylightService; + @Resource + private BookingService bookingService; + + @Override + public void onApplicationEvent(ApplicationStartedEvent event) { + + SysCommon.initRuntimePath(); + //--Register Allotment handlers + DaylightWebhookFactory.registerHandler("RoomTypeDateAvailabilityChanged", new RoomTypeDateAvailabilityChangedHandler(roomCriteriaDaylightService)); + DaylightWebhookFactory.registerHandler("AvailableRoomCountUpdated", new AvailableRoomCountUpdatedHandler(roomCriteriaDaylightService)); + //--Register Reservation Notification handlers + DaylightWebhookFactory.registerHandler("ReservationNotification", new ReservationNotificationHandler(bookingService)); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/utils/DateTimeUtil.java b/src/main/java/com/mgm/mcrm/apigateway/common/utils/DateTimeUtil.java new file mode 100644 index 0000000..917ec9a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/utils/DateTimeUtil.java @@ -0,0 +1,169 @@ +package com.mgm.mcrm.apigateway.common.utils; + +import org.apache.commons.lang3.StringUtils; + +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Date; + +public class DateTimeUtil { + + + public static LocalDateTime getDateFromString(String dateTimeValue, String dateFormat) { + if (StringUtils.isBlank(dateTimeValue)) { + return null; + } + + try { + DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern(dateFormat); + + // 根据格式判断是纯日期还是包含时间 + if (dateFormat.contains("HH") || dateFormat.contains("mm") || dateFormat.contains("ss")) { + // 包含时间格式,使用LocalDateTime解析 + return LocalDateTime.parse(dateTimeValue, dtFormat); + } else { + // 纯日期格式,使用LocalDate解析后转换为LocalDateTime + LocalDate localDate = LocalDate.parse(dateTimeValue, dtFormat); + return localDate.atStartOfDay(); // 设置为当天的00:00:00 + } + } catch (Exception e) { + System.err.println("日期解析失败: " + e.getMessage()); + return null; + } + } + + public static String getDateByFormat(LocalDateTime dtDateTime, String strFormat) { + + String returnDate = null; + if (dtDateTime != null) { + try { + DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern(strFormat); + returnDate = dtDateTime.format(dtFormat); + } catch (RuntimeException ignored) { + + } + } + return returnDate; + } + + + public static String getCurrentDate(String strFormat) { + return getDateByFormat(LocalDateTime.now(), strFormat); + } + + public static LocalDateTime getCurrentDateTime(String strFormat) { + + LocalDateTime returnDate = null; + try { + DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern(strFormat); + String dateTimeValue = getDateByFormat(LocalDateTime.now(), strFormat); + returnDate = LocalDateTime.parse(dateTimeValue,dtFormat); + } catch (Exception ignored) { + + } + + return returnDate; + } + + public static LocalDateTime addDay(LocalDateTime currdate,long days) { + return currdate.plusDays(days); + } + + public static String getDateByFormatUseTimeStamp(Long timeStamp, String strFormat) { + + String returnDate = null; + + if (timeStamp != null) { + try { + SimpleDateFormat formatter; + formatter = new SimpleDateFormat(strFormat); + returnDate = formatter.format(timeStamp); + } catch (Exception err) { + + } + } + return returnDate; + } + + public static String getDiffDate(LocalDateTime DateStart, LocalDateTime DateEnd, String dateType) { + String returnDate = null; + try { + + Duration duration = Duration.between(DateStart, DateEnd); + //long diffTime = DateEnd.getTime() - DateStart.getTime(); + if("day".equalsIgnoreCase(dateType)) { + long days = duration.toDays(); + returnDate = Long.toString(days); + }else if("hour".equalsIgnoreCase(dateType)) { + long hours = duration.toHours(); + returnDate = Long.toString(hours); + }else { //minute + long minutes = duration.toMinutes(); + returnDate = Long.toString(minutes); + } + } catch (Exception err) { + + } + return returnDate; + } + public static boolean isValidDate(String dateStr) { + try { + dateStr=dateStr.split(" ")[0]; + String[] parts = dateStr.split("-"); + + int year = Integer.parseInt(parts[0]); + int month = Integer.parseInt(parts[1]); + int day = Integer.parseInt(parts[2]); + + + if (month < 1 || month > 12) { + return false; + } + + YearMonth yearMonth = YearMonth.of(year, month); + int maxDay = yearMonth.lengthOfMonth(); + + boolean isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + if (month == 2 && isLeapYear) { + maxDay = 29; + } else if (month == 2) { + maxDay = 28; + } + + if (day < 1 || day > maxDay) { + return false; + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate.parse(dateStr, formatter); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + + public static long fromDateStringToLong(String inVal) { + LocalDateTime date = null; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss:SSS"); + try { + date = LocalDateTime.parse(inVal,formatter); + } catch (Exception e) { + e.printStackTrace(); + } + return date!=null?date.toEpochSecond(ZoneOffset.UTC):0; + } + + public static long getTimeSpanSSS(LocalDateTime startDate, LocalDateTime endDate) { + DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss:SSS"); + String startStr = timeFormat.format(startDate); + String endStr = timeFormat.format(endDate); + + long startMS = fromDateStringToLong(startStr); + long endMS = fromDateStringToLong(endStr); + long returnMS = endMS-startMS; + return returnMS; + } + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/utils/LogUtil.java b/src/main/java/com/mgm/mcrm/apigateway/common/utils/LogUtil.java new file mode 100644 index 0000000..f396152 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/utils/LogUtil.java @@ -0,0 +1,68 @@ +package com.mgm.mcrm.apigateway.common.utils; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSONObject; + + +public class LogUtil { + private static String loggerName = LogUtil.class.getName(); + private static Logger logger = LoggerFactory.getLogger(loggerName); + + public static Logger getLogger(Class callClass){ + try + { + logger = LoggerFactory.getLogger(callClass.getName()); + }catch(Exception err){ + loggerName = LogUtil.class.getName(); + logger = LoggerFactory.getLogger(loggerName); + } + return logger; + } + + public static void info(Class callClass,String message) { + getLogger(callClass).info(message); + } + + public static void warn(Class callClass,String message) { + getLogger(callClass).warn(message); + } + + public static void error(Class callClass,String message) { + getLogger(callClass).error(message); + } + + public static String getObjectAttr(Object srcObj, String[] attrNames) { + String attrText = ""; + if(srcObj!=null && attrNames!=null) { + try { + JSONObject jsonObject = (JSONObject)JSONObject.toJSON(srcObj); + for(String attr:attrNames) { + attrText+=(attr+"="); + if(jsonObject.containsKey(attr)) { + attrText+=jsonObject.getString(attr); + } + attrText+=";"; + } + }catch(Exception ignored) { + + } + } + return attrText; + } + + public static String getFormatLog(String operationTag, String dataTag, boolean isSuccess, String resultTag,String message) { + String logMessage = ""; + String statusMsg = (resultTag==null?"":resultTag); + String errorMsg = (message==null?"":message); + logMessage = operationTag+(StringUtils.isBlank(dataTag)?"":" ["+dataTag+"]"); + if(isSuccess) { + logMessage += (" successed" + (StringUtils.isBlank(statusMsg)?"":" with ["+statusMsg+"]")); + }else { + logMessage += (" failed with error ["+errorMsg+"]"); + } + + return logMessage; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/utils/NetworkUtil.java b/src/main/java/com/mgm/mcrm/apigateway/common/utils/NetworkUtil.java new file mode 100644 index 0000000..ad09a7e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/utils/NetworkUtil.java @@ -0,0 +1,355 @@ +package com.mgm.mcrm.apigateway.common.utils; + +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.mgm.mcrm.apigateway.common.constants.SysCommon; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.springframework.web.util.UriUtils; + +public class NetworkUtil { + private static final List interalIpPatterns = new ArrayList(); + private static final Pattern IPV4_PATTERN = + Pattern.compile("^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); + private static final List INTERNAL_IP_PREFIXES = Arrays.asList( + "10.", "192.168.", "172.16.", "172.17.", "172.18.", "172.19.", + "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.", + "172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31." + ); + + public static void setInternalIpPattern(String patternConfig) { + interalIpPatterns.clear(); + String[] patternItems = patternConfig.split(";"); + for(String pItem:patternItems) { + String pVal = pItem.trim(); + if(StringUtils.isNotBlank(patternConfig)) { + interalIpPatterns.add(pVal); + } + } + } + + public static boolean isMatchInternal(String ipContent) { + boolean internalFlag = false; + if(StringUtils.isNotBlank(ipContent) && !interalIpPatterns.isEmpty()) { + String ipPart = "\\b(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\b"; + for(String ipItem:interalIpPatterns) { + if(ipItem.contains("*")) { + String itemPattern = ipItem.replace(".", "\\."); + itemPattern = itemPattern.replace("*", ipPart); + Pattern pt = Pattern.compile(itemPattern); + Matcher mt = pt.matcher(ipContent); + if(mt.find()) { + internalFlag=true; + break; + } + }else { + if(ipContent.contains(ipItem)) { + internalFlag=true; + break; + } + } + } + } + return internalFlag; + } + + /** + * Gets the client ip addr. + * + * @param request the request + * @return the client ip addr + */ + public static String getClientIpAddr(HttpServletRequest request) { + String ip = getIpFromHeaders(request); + if(ip==null){ + ip = request.getRemoteAddr(); + } + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; + } + + private static String getIpFromHeaders(HttpServletRequest request) { + String[] headerNames = { + "X-Real-IP", + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_CLIENT_IP", + "HTTP_X_FORWARDED_FOR" + }; + boolean isContainForwardHeader=false; + for (String headerName : headerNames) { + String headerValue = request.getHeader(headerName); + if (headerValue != null && !headerValue.isEmpty()) { + isContainForwardHeader=true; + //LogUtil.info(NetworkUtil.class,"Check header["+headerName+"] of value["+headerValue+"]"); + String ip = extractValidIp(headerValue); + if (ip != null) { + return ip; + } + } + } + return (isContainForwardHeader?"INVALID_IP":null); + } + + private static String extractValidIp(String headerValue) { + if (headerValue.contains(",")) { + String[] ips = headerValue.split(","); + for(int i=ips.length-1;i>=0;i--){ + String ip = ips[i].trim(); + if (isValidPublicIp(ip)) { + return ip; + } + } + return isValidIp(ips[ips.length - 1].trim())?ips[ips.length - 1].trim():null; + } else { + String ip = headerValue.trim(); + return isValidIp(ip) ? ip : null; + } + } + + private static boolean isValidPublicIp(String ip) { + return isValidIp(ip) && !isInternalIp(ip); + } + + private static boolean isValidIp(String ip) { + return ip != null && !ip.isEmpty() && + !"unknown".equalsIgnoreCase(ip) && + IPV4_PATTERN.matcher(ip).matches(); + } + + private static boolean isInternalIp(String ip) { + if (ip == null) return false; + if (ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")) { + return true; + } + for (String prefix : INTERNAL_IP_PREFIXES) { + if (ip.startsWith(prefix)) { + return true; + } + } + return false; + } + + /** + * Gets the real ips. + * + * @return the real ips + */ + public static List getRealIps() { + List ips = new ArrayList(); + String localip = null;// 本地IP,如果没有配置外网IP则返回它 + String netip = null;// 外网IP + Enumeration netInterfaces; + try { + netInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + return null; + } + + InetAddress ip = null; + boolean finded = false;// 是否找到外网IP + while (netInterfaces.hasMoreElements() && !finded) { + NetworkInterface ni = netInterfaces.nextElement(); + Enumeration address = ni.getInetAddresses(); + while (address.hasMoreElements()) { + ip = address.nextElement(); + if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && !ip.getHostAddress().contains(":")) { + netip = ip.getHostAddress(); + ips.add(netip); + finded = true; + break; + } else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() + && !ip.getHostAddress().contains(":")) {// 内网IP + localip = ip.getHostAddress(); + ips.add(localip); + } + } + } + return ips; + } + + /** + * Gets the real ip. + * + * @return the real ip + */ + public static String getRealIp() { + return getRealIps().iterator().next(); + } + + /** + * 获取访问者内网IP + * + * @return the server ip + */ + public static String getInternalIp() { + // 本地IP,如果没有配置外网IP则返回它 + String localip = null; + // 外网IP + String netip = null; + try { + Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces(); + InetAddress ip = null; + // 是否找到外网IP + boolean finded = false; + while (netInterfaces.hasMoreElements() && !finded) { + NetworkInterface ni = (NetworkInterface) netInterfaces.nextElement(); + Enumeration address = ni.getInetAddresses(); + while (address.hasMoreElements()) { + ip = (InetAddress) address.nextElement(); + if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) { + // 外网IP + netip = ip.getHostAddress(); + finded = true; + break; + } else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() + && ip.getHostAddress().indexOf(":") == -1) { + // 内网IP + localip = ip.getHostAddress(); + } + } + } + } catch (SocketException e) { + e.printStackTrace(); + } + + if (netip != null && !"".equals(netip)) { + return netip; + } else { + return localip; + } + } + + /** + * 判断ip是否在网段cidr内,cidr格式192.168.0.1/255 + * + * @return the server ip + */ + public static boolean ipInRange(String ip, String cidr) { + try { + String[] tmp = cidr.split("/"); + + if (tmp.length > 1) { + + String[] ips = ip.split("\\."); + int ipAddr = (Integer.parseInt(ips[0]) << 24) | (Integer.parseInt(ips[1]) << 16) + | (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]); + int type = Integer.parseInt(cidr.replaceAll(".*/", "")); + int mask = 0xFFFFFFFF << (32 - type); + String cidrIp = cidr.replaceAll("/.*", ""); + String[] cidrIps = cidrIp.split("\\."); + int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24) | (Integer.parseInt(cidrIps[1]) << 16) + | (Integer.parseInt(cidrIps[2]) << 8) | Integer.parseInt(cidrIps[3]); + return (ipAddr & mask) == (cidrIpAddr & mask); + } else if (tmp.length == 1 && ip.equals(cidr)) { + return true; + } else { + return false; + } + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static String getServerName() { + String serverName=""; + try { + InetAddress localhost = InetAddress.getLocalHost(); + serverName = localhost.getHostName(); + } catch (Exception e) { + e.printStackTrace(); + } + return serverName; + } + + /** + * 判断ip是否在网段cidr内,cidr格式192.168.0.1/255 + * + * @return the server ip + */ + public static boolean isMacauIp(String ip,String macauIps) { + try { + String[] cidrArr = StringUtils.split(macauIps,";"); + if(cidrArr.length>0) { + for(String cidr : cidrArr) { + String[] tmp = cidr.split("/"); + if (tmp.length > 1) { + String[] ips = ip.split("\\."); + try { + Integer.parseInt(ips[0]); + Integer.parseInt(ips[1]); + Integer.parseInt(ips[2]); + Integer.parseInt(ips[3]); + } catch (Exception e) { + continue; + } + int ipAddr = (Integer.parseInt(ips[0]) << 24) | (Integer.parseInt(ips[1]) << 16) + | (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]); + int type = Integer.parseInt(cidr.replaceAll(".*/", "")); + int mask = 0xFFFFFFFF << (32 - type); + String cidrIp = cidr.replaceAll("/.*", ""); + String[] cidrIps = cidrIp.split("\\."); + int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24) | (Integer.parseInt(cidrIps[1]) << 16) + | (Integer.parseInt(cidrIps[2]) << 8) | Integer.parseInt(cidrIps[3]); + if((ipAddr & mask) == (cidrIpAddr & mask)) return true; + } else if (tmp.length == 1 && ip.equals(cidr)) { + return true; + } + } + }else { + return false; + } + + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + return false; + } + + public static boolean isValidURL(String requestUrl){ + try { + URI uri = new URI(requestUrl); + String scheme = uri.getScheme(); + String host = uri.getHost(); + if(!"https".equals(scheme) && !"http".equals(scheme)) { + return false; + } + if(host.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) { + return false; + } + return true; + } catch (Exception e) { + return false; + } + } + + public static String encodeParaWithSpring(String value) { + if(value==null) + value=""; + value= value.replace("\r", "").replace("\n", ""); + return UriUtils.encode(value, StandardCharsets.UTF_8); + } + + public static String sanitizeHeaderValue(String headerValue) { + return headerValue==null? null :(StringEscapeUtils.escapeJava( + headerValue.replace("\r", "").replace("\n", "") + )); + } + + public static String encodePathWithSpring(String value) { + if(value==null) + value=""; + value= value.replace("\r", "").replace("\n", ""); + return UriUtils.encodePath(value, StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/utils/mockup/HttpUtilMockup.java b/src/main/java/com/mgm/mcrm/apigateway/common/utils/mockup/HttpUtilMockup.java new file mode 100644 index 0000000..db5fbde --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/common/utils/mockup/HttpUtilMockup.java @@ -0,0 +1,261 @@ +package com.mgm.mcrm.apigateway.common.utils.mockup; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import com.mgm.mcrm.apigateway.common.constants.SysCommon; +import com.mgm.mcrm.apigateway.common.utils.LogUtil; + +public class HttpUtilMockup { + private static JSONArray mockupConfig=null; + private static JSONArray getMockupConfig() { + if(mockupConfig==null) { + try { + String fileContent = SysCommon.loadResFileContentByPath("/mockup/mgm_api_mockup_def.json",1); + LogUtil.info(HttpUtilMockup.class, "[HttpUtilMockup] config file content length=" + (fileContent == null ? 0 : fileContent.length()) + ", first 100 chars=" + (fileContent == null ? "null" : fileContent.substring(0, Math.min(100, fileContent.length())))); + mockupConfig = JSONArray.parseArray(fileContent); + if(mockupConfig==null) { + LogUtil.error(HttpUtilMockup.class, "[HttpUtilMockup] config parsed to null, using empty array"); + mockupConfig = new JSONArray(); + } else { + LogUtil.info(HttpUtilMockup.class, "[HttpUtilMockup] config loaded successfully, items count=" + mockupConfig.size()); + } + }catch(Exception err) { + LogUtil.error(HttpUtilMockup.class, "[HttpUtilMockup] failed to load config: " + err.getMessage()); + mockupConfig = new JSONArray(); + } + } + return mockupConfig; + } + + public static String findMockResultByLink(String requestLink,String requestContent) { + String result =""; + JSONArray configData = getMockupConfig(); + LogUtil.info(HttpUtilMockup.class, "[HttpUtilMockup] findMockResultByLink requestLink=" + requestLink + ", requestContent=" + (requestContent == null ? "" : requestContent.substring(0, Math.min(requestContent.length(), 300)))); + LogUtil.info(HttpUtilMockup.class, "[HttpUtilMockup] configData size=" + configData.size()); + for(int i=0;i operateList = new ArrayList(); + String[] andList = condiction.split("&&"); + for(int i=0;i0) { + operateList.add("&&"); + } + operateList.add(andList[i].trim()); + } + for(int i=0;i0) { + operateList.add(i,"||"); + i++; + } + if(j==0) { + operateList.set(i, orList[j].trim()); + i++; + }else { + operateList.add(i, orList[j].trim()); + i++; + } + } + } + } + String lastOperate = "AND"; + boolean isFirst =true; + for(int i=0;i0) { + int operIndex = express.indexOf("=="); + operate="=="; + valName1 = express.substring(0, operIndex).trim(); + valName2 =express.substring(operIndex+2).trim(); + }else if(express.indexOf(">=")>0) { + int operIndex = express.indexOf(">="); + operate=">="; + valName1 = express.substring(0, operIndex).trim(); + valName2 =express.substring(operIndex+2).trim(); + }else if(express.indexOf("<=")>0) { + int operIndex = express.indexOf("<="); + operate="<="; + valName1 = express.substring(0, operIndex).trim(); + valName2 =express.substring(operIndex+2).trim(); + }else if(express.indexOf(">")>0) { + int operIndex = express.indexOf(">"); + operate=">"; + valName1 = express.substring(0, operIndex).trim(); + valName2 =express.substring(operIndex+1).trim(); + }else if(express.indexOf("<")>0) { + int operIndex = express.indexOf("<"); + operate="<"; + valName1 = express.substring(0, operIndex).trim(); + valName2 =express.substring(operIndex+1).trim(); + } + if(!operate.isEmpty()) { + double numVal1 =0; + double numVal2 =0; + boolean isNum=true; + if(requestObj.containsKey(valName1)) { + try { + numVal1 = requestObj.getDoubleValue(valName1); + }catch(Exception err) { + isNum=false; + } + }else { + try { + numVal1 = Double.parseDouble(valName1); + }catch(Exception err) { + isNum=false; + } + } + if(isNum==true) { + if(requestObj.containsKey(valName2)) { + try { + numVal2 = requestObj.getDoubleValue(valName2); + }catch(Exception err) { + isNum=false; + } + }else { + try { + numVal2 = Double.parseDouble(valName2); + }catch(Exception err) { + isNum=false; + } + } + } + if(isNum==true) { + if(operate.equals("==")) { + result = (numVal1==numVal2); + }else if(operate.equals("<=")) { + result = (numVal1<=numVal2); + }else if(operate.equals(">=")) { + result = (numVal1>=numVal2); + }else if(operate.equals("<")) { + result = (numVal1")) { + result = (numVal1>numVal2); + } + }else { + String textVal1 = valName1; + String textVal2 = valName2; + if(requestObj.containsKey(valName1)) { + textVal1 = requestObj.getString(valName1); + } + if(requestObj.containsKey(valName2)) { + textVal2 = requestObj.getString(valName2); + } + if(operate.equals("==")) { + result = (textVal1.compareTo(textVal2)==0); + }else if(operate.equals("<=")) { + result = (textVal1.compareTo(textVal2)<=0); + }else if(operate.equals(">=")) { + result = (textVal1.compareTo(textVal2)>=0); + }else if(operate.equals("<")) { + result = (textVal1.compareTo(textVal2)<0); + }else if(operate.equals(">")) { + result = (textVal1.compareTo(textVal2)>0); + } + } + } + return result; + } + + public static String processMockRequest(String requestUrl, String requestContent) { + String responseEntity = findMockResultByLink(requestUrl, requestContent); + LogUtil.info(HttpUtilMockup.class, "[HttpUtilMockup] requestUrl=" + requestUrl + " mockResponse=" + (responseEntity == null ? "" : responseEntity.substring(0, Math.min(responseEntity.length(), 200)))); + return responseEntity; + } + + public static void clearConfigCache() { + mockupConfig = null; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/controllers/DaylightTestController.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/controllers/DaylightTestController.java new file mode 100644 index 0000000..7dd7c31 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/controllers/DaylightTestController.java @@ -0,0 +1,27 @@ +package com.mgm.mcrm.apigateway.daylight.common.controllers; + +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.daylight.common.services.DaylightFetchConfigurationService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DaylightTestController { + + @Resource + private DaylightFetchConfigurationService daylightFetchConfigurationService; + + @GetMapping("/test/fetch-daylight-config") + public BaseApiResponse testFetchDaylightConfiguration() { + BaseApiResponse propertiesResponse = daylightFetchConfigurationService.fetchDaylightProperties(); + if (!propertiesResponse.isSuccess()) { + return propertiesResponse; + } + BaseApiResponse configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration(); + if (!configResponse.isSuccess()) { + return configResponse; + } + return daylightFetchConfigurationService.fetchDaylightBlockCodes(); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/CachedToken.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/CachedToken.java new file mode 100644 index 0000000..e3e9114 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/CachedToken.java @@ -0,0 +1,12 @@ +package com.mgm.mcrm.apigateway.daylight.common.dtos; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CachedToken { + private TokenResponse token; + private LocalDateTime expireTime; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/TokenResponse.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/TokenResponse.java new file mode 100644 index 0000000..8a365b1 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/TokenResponse.java @@ -0,0 +1,18 @@ +package com.mgm.mcrm.apigateway.daylight.common.dtos; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +@Data +public class TokenResponse { + @JSONField(name = "access_token") + private String accessToken; + @JSONField(name = "expires_in") + private String expiresIn; + @JSONField(name = "token_type") + private String tokenType; + @JSONField(name = "refresh_token") + private String refreshToken; + @JSONField(name = "scope") + private String scope; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/scheduler/DaylightConfigurationScheduler.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/scheduler/DaylightConfigurationScheduler.java new file mode 100644 index 0000000..7c3bc90 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/scheduler/DaylightConfigurationScheduler.java @@ -0,0 +1,120 @@ +package com.mgm.mcrm.apigateway.daylight.common.scheduler; + +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.daylight.common.services.DaylightFetchConfigurationService; +import com.mgm.mcrm.apigateway.common.utils.LogUtil; +import com.mgm.mcrm.apigateway.daylight.repositories.CfgDaylightConfigurationDao; +import jakarta.annotation.Resource; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +public class DaylightConfigurationScheduler { + + @Resource + private DaylightFetchConfigurationService daylightFetchConfigurationService; + @Resource + private CfgDaylightConfigurationDao cfgDaylightConfigurationDao; + + @Order(1) + @EventListener(ApplicationReadyEvent.class) + public void fetchDaylightPropertiesOnStartupIfMissing() { + LogUtil.info(DaylightConfigurationScheduler.class, "Checking saved Daylight properties on startup"); + try { + if (cfgDaylightConfigurationDao.existsSavedDaylightProperties()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Saved Daylight properties found, skip startup fetch"); + return; + } + + LogUtil.warn(DaylightConfigurationScheduler.class, "No saved Daylight properties found, start startup fetch"); + BaseApiResponse propResponse = daylightFetchConfigurationService.fetchDaylightProperties(); + if (propResponse.isSuccess()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Startup properties completed: " + propResponse.getMessage()); + } else { + LogUtil.warn(DaylightConfigurationScheduler.class, "Startup properties failed: " + propResponse.getMessage()); + } + } catch (Exception e) { + LogUtil.error(DaylightConfigurationScheduler.class, "Startup properties check error: " + e.getMessage()); + } + } + + @Order(2) + @EventListener(ApplicationReadyEvent.class) + public void fetchDaylightConfigurationOnStartupIfMissing() { + LogUtil.info(DaylightConfigurationScheduler.class, "Checking saved Daylight configurations on startup"); + try { + BaseApiResponse configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration(); + if (configResponse.isSuccess()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Startup configurations completed: " + configResponse.getMessage()); + } else { + LogUtil.warn(DaylightConfigurationScheduler.class, "Startup configurations failed: " + configResponse.getMessage()); + } + } catch (Exception e) { + LogUtil.error(DaylightConfigurationScheduler.class, "Startup configurations check error: " + e.getMessage()); + } + } + + @Order(3) + @EventListener(ApplicationReadyEvent.class) + public void fetchDaylightBlockCodesOnStartupIfMissing() { + LogUtil.info(DaylightConfigurationScheduler.class, "Checking saved Daylight block codes on startup"); + try { + if (!cfgDaylightConfigurationDao.existsSavedDaylightProperties()) { + LogUtil.info(DaylightConfigurationScheduler.class, "No saved Daylight properties found, skip block codes startup fetch"); + return; + } + + BaseApiResponse blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes(); + if (blockResponse.isSuccess()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Startup block codes completed: " + blockResponse.getMessage()); + } else { + LogUtil.warn(DaylightConfigurationScheduler.class, "Startup block codes failed: " + blockResponse.getMessage()); + } + } catch (Exception e) { + LogUtil.error(DaylightConfigurationScheduler.class, "Startup block codes check error: " + e.getMessage()); + } + } + + /** + * 每天凌晨2点执行一次,拉取 Daylight 配置数据 + */ + @Scheduled(cron = "0 0 2 * * ?") + public void fetchDaylightConfigurationDaily() { + LogUtil.info(DaylightConfigurationScheduler.class, "Start scheduled task: fetchDaylightAllData"); + try { + // 1. 拉取 Properties + LogUtil.info(DaylightConfigurationScheduler.class, "Step 1/3: Fetching properties..."); + BaseApiResponse propResponse = daylightFetchConfigurationService.fetchDaylightProperties(); + if (propResponse.isSuccess()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Properties completed: " + propResponse.getMessage()); + } else { + LogUtil.warn(DaylightConfigurationScheduler.class, "Properties failed: " + propResponse.getMessage()); + } + + // 2. 拉取 Configuration + LogUtil.info(DaylightConfigurationScheduler.class, "Step 2/3: Fetching configurations..."); + BaseApiResponse configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration(); + if (configResponse.isSuccess()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Configurations completed: " + configResponse.getMessage()); + } else { + LogUtil.warn(DaylightConfigurationScheduler.class, "Configurations failed: " + configResponse.getMessage()); + } + + // 3. 拉取 Block Codes + LogUtil.info(DaylightConfigurationScheduler.class, "Step 3/3: Fetching block codes..."); + BaseApiResponse blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes(); + if (blockResponse.isSuccess()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Block codes completed: " + blockResponse.getMessage()); + } else { + LogUtil.warn(DaylightConfigurationScheduler.class, "Block codes failed: " + blockResponse.getMessage()); + } + + LogUtil.info(DaylightConfigurationScheduler.class, "Scheduled task finished: fetchDaylightAllData"); + } catch (Exception e) { + LogUtil.error(DaylightConfigurationScheduler.class, "Scheduled task error: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightFetchConfigurationService.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightFetchConfigurationService.java new file mode 100644 index 0000000..a49a672 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightFetchConfigurationService.java @@ -0,0 +1,651 @@ +package com.mgm.mcrm.apigateway.daylight.common.services; + +import com.alibaba.fastjson.JSONObject; +import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; +import com.mgm.mcrm.apigateway.basesetting.property.repositories.PropertyDao; +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.common.dtos.config.DaylightApiSetting; +import com.mgm.mcrm.apigateway.daylight.common.dtos.TokenResponse; +import com.mgm.mcrm.apigateway.daylight.models.CfgDaylightConfiguration; +import com.mgm.mcrm.apigateway.daylight.models.SysLogDaylightConfigurationSync; +import com.mgm.mcrm.apigateway.daylight.repositories.CfgDaylightConfigurationDao; +import com.mgm.mcrm.apigateway.daylight.repositories.SysLogDaylightConfigurationSyncDao; +import com.mgm.mcrm.apigateway.proxy.exception.HttpProxyErrorHandler; +import com.mgm.mcrm.apigateway.proxy.services.WebProxyService; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +public class DaylightFetchConfigurationService { + + private static final Logger log = LoggerFactory.getLogger(DaylightFetchConfigurationService.class); + + private final HttpProxyErrorHandler httpErrorHandler = new HttpProxyErrorHandler(); + @Resource + private WebProxyService webProxyService; + @Resource + private DaylightTokenService daylightTokenService; + @Autowired + private DaylightApiSetting daylightApiSetting; + @Resource + private PropertyDao propertyDao; + @Resource + private CfgDaylightConfigurationDao cfgDaylightConfigurationDao; + @Resource + private SysLogDaylightConfigurationSyncDao syncLogDao; + + public BaseApiResponse fetchDaylightConfiguration() { + BaseApiResponse response = new BaseApiResponse(); + Date syncStartTime = new Date(); + + try { + TokenResponse daylightApiToken = daylightTokenService.getApiAccessToken(); + if (daylightApiToken == null || StringUtils.isBlank(daylightApiToken.getAccessToken())) { + response.setSuccess(false); + response.setMessage("Invoke daylight token failed"); + return response; + } + + List allProperties = propertyDao.findByCategory("DAYLIGHT"); + int savedCount = 0; + + if (allProperties == null || allProperties.isEmpty()) { + response.setSuccess(false); + response.setMessage("No properties found with category 'DAYLIGHT'"); + return response; + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + + for (CfgProperties property : allProperties) { + String dlPropertyId = property.getDlPropertyId(); + if (StringUtils.isBlank(dlPropertyId)) { + continue; + } + + // 按每个 property 查询上次成功同步时间 + // 注意:仅当 sys_log 有 SUCCESS 日志,且 cfg_daylight_configuration 表中确实存在该 property 的 + // 配置数据时,才走增量同步;否则强制走全量同步,避免数据空缺时漏掉 configType + String updatedAtParam = ""; + Optional lastSync = syncLogDao + .findTopByConfigNameAndDlPropertyIdAndSyncStatusOrderByLastSyncAtDesc( + "Configuration", dlPropertyId, "SUCCESS"); + boolean hasSavedConfig = cfgDaylightConfigurationDao.existsSavedConfigurationByDlPropertyId(dlPropertyId); + if (lastSync.isPresent() && hasSavedConfig) { + updatedAtParam = "&updatedAt=" + sdf.format(lastSync.get().getLastSyncAt()); + log.info("Property {} incremental sync, updatedAt: {}", dlPropertyId, updatedAtParam); + } else { + if (lastSync.isPresent() && !hasSavedConfig) { + log.warn("Property {} sync log found but config data missing, fallback to full sync", dlPropertyId); + } else { + log.info("Property {} first sync, no updatedAt parameter", dlPropertyId); + } + } + + // 每个 property 先删除旧配置,再重新拉取 + Map> accumulatedResults = new HashMap<>(); + + String path = "/api-gateway/aggregator/v1/configuration/extract"; + String apiUrl = daylightApiSetting.getServerUrl() + path; + String types = "PurchaseElement,MarketSegment,RatePlan,BookingSource,MembershipLevel," + + "Attribute,RoomType,GuaranteeType,DistributionChannel,AgeBucket,CancellationReason," + + "ComplimentaryAccountingPreset,ComplimentaryType,TransactionCode"; + + String pagingNext; + int pageNumber = 1; + boolean fetchSuccess = true; + + do { + HttpHeaders headers = new HttpHeaders(); + headers.set("authorization", "Bearer " + daylightApiToken.getAccessToken()); + headers.set("Ac-Property-Id", dlPropertyId); + + String queryString = "types=" + types + "&pageSize=500&pageNumber=" + pageNumber + updatedAtParam; + log.info("Fetching page {} for Ac-Property-Id: {}", pageNumber, dlPropertyId); + + ResponseEntity requestResponse = webProxyService.invokeRequest( + WebProxyService.DAYLIGHT_API, + HttpMethod.GET, + apiUrl, + queryString, + headers, + new byte[0], + Duration.ofSeconds(daylightApiSetting.getApiTimeout()), + httpErrorHandler + ).block(); + + if (requestResponse == null) { + log.warn("Property {} configuration fetch failed: Http request server can not visit", dlPropertyId); + fetchSuccess = false; + break; + } + + response.setStatusCode(requestResponse.getStatusCode().value()); + + if (!requestResponse.getStatusCode().is2xxSuccessful()) { + byte[] errorBody = requestResponse.getBody(); + String errorMsg = errorBody != null ? new String(errorBody, StandardCharsets.UTF_8) : "Request failed"; + log.warn("Property {} configuration fetch failed: {}", dlPropertyId, errorMsg); + fetchSuccess = false; + break; + } + + byte[] responseBody = requestResponse.getBody(); + if (responseBody == null) { + break; + } + + String jsonStr = new String(responseBody, StandardCharsets.UTF_8); + JSONObject responseJson = JSONObject.parseObject(jsonStr); + + // 累积 results 中每种 configType 的数据 + JSONObject results = responseJson.getJSONObject("results"); + if (results != null) { + for (String configType : results.keySet()) { + List pageItems = results.getJSONArray(configType).toJavaList(JSONObject.class); + accumulatedResults.computeIfAbsent(configType, k -> new ArrayList<>()).addAll(pageItems); + } + } + + // 解析 paging.next,判断是否有下一页 + pagingNext = null; + JSONObject paging = responseJson.getJSONObject("paging"); + if (paging != null && paging.containsKey("next") && StringUtils.isNotBlank(paging.getString("next"))) { + pagingNext = paging.getString("next"); + pageNumber++; + } + } while (pagingNext != null); + + // 将累积的结果保存为 CfgDaylightConfiguration + boolean isIncremental = StringUtils.isNotBlank(updatedAtParam); + for (Map.Entry> entry : accumulatedResults.entrySet()) { + String configType = entry.getKey(); + List newItems = entry.getValue(); + + if (isIncremental) { + // 增量同步:合并新旧数据 + CfgDaylightConfiguration existing = cfgDaylightConfigurationDao + .findByDlPropertyIdAndConfigName(dlPropertyId, configType); + if (existing != null && StringUtils.isNotBlank(existing.getConfigValue())) { + // 解析旧数据,用 id 建索引 + List oldItems = JSONObject.parseArray(existing.getConfigValue(), JSONObject.class); + Map mergedMap = new HashMap<>(); + for (JSONObject item : oldItems) { + mergedMap.put(getConfigItemKey(item), item); + } + logConfigurationChanges("Configuration incremental sync", dlPropertyId, configType, oldItems, newItems); + // 用新数据覆盖/新增 + for (JSONObject item : newItems) { + mergedMap.put(getConfigItemKey(item), item); + } + // 保存合并后的结果 + existing.setConfigValue(JSONObject.toJSONString(new ArrayList<>(mergedMap.values()))); + existing.setUpdatedAt(new Date()); + cfgDaylightConfigurationDao.save(existing); + log.info("Merged {} for {}: old={}, new={}, merged={}", + configType, dlPropertyId, oldItems.size(), newItems.size(), mergedMap.size()); + } else { + // DB 中没有旧记录,直接保存新数据 + CfgDaylightConfiguration config = new CfgDaylightConfiguration(); + config.setConfigName(configType); + config.setConfigValue(JSONObject.toJSONString(newItems)); + config.setDlPropertyId(dlPropertyId); + config.setCreatedAt(new Date()); + config.setUpdatedAt(new Date()); + cfgDaylightConfigurationDao.save(config); + logConfigurationCreated("Configuration incremental sync created", dlPropertyId, configType, newItems); + } + } else { + // 全量同步:直接保存 + CfgDaylightConfiguration existing = cfgDaylightConfigurationDao + .findByDlPropertyIdAndConfigName(dlPropertyId, configType); + List oldItems = existing != null && StringUtils.isNotBlank(existing.getConfigValue()) + ? JSONObject.parseArray(existing.getConfigValue(), JSONObject.class) + : new ArrayList<>(); + CfgDaylightConfiguration config = existing != null ? existing : new CfgDaylightConfiguration(); + config.setConfigName(configType); + config.setConfigValue(JSONObject.toJSONString(newItems)); + config.setDlPropertyId(dlPropertyId); + if (config.getCreatedAt() == null) { + config.setCreatedAt(new Date()); + } + config.setUpdatedAt(new Date()); + cfgDaylightConfigurationDao.save(config); + logConfigurationChanges("Configuration full sync", dlPropertyId, configType, oldItems, newItems); + } + savedCount++; + } + + // 按 property 记录同步日志 + SysLogDaylightConfigurationSync syncLog = new SysLogDaylightConfigurationSync(); + syncLog.setConfigName("Configuration"); + syncLog.setDlPropertyId(dlPropertyId); + syncLog.setLastSyncAt(syncStartTime); + syncLog.setSyncStatus(fetchSuccess ? "SUCCESS" : "FAILED"); + syncLog.setRecordCount(accumulatedResults.size()); + syncLog.setCreatedAt(new Date()); + syncLogDao.save(syncLog); + } + + response.setSuccess(true); + response.setMessage("Successfully fetched and saved " + savedCount + " configuration entries."); + } catch (Exception e) { + response.setSuccess(false); + response.setMessage(e.getMessage()); + } + return response; + } + + /** + * 从 Daylight API 拉取 properties 列表,存入 cfg_daylight_configuration 表 + */ + public BaseApiResponse fetchDaylightProperties() { + BaseApiResponse response = new BaseApiResponse(); + Date syncStartTime = new Date(); + + try { + TokenResponse daylightApiToken = daylightTokenService.getApiAccessToken(); + if (daylightApiToken == null || StringUtils.isBlank(daylightApiToken.getAccessToken())) { + response.setSuccess(false); + response.setMessage("Invoke daylight token failed"); + return response; + } + + String path = "/api-gateway/configuration/v1/properties"; + String apiUrl = daylightApiSetting.getServerUrl() + path; + int pageNumber = 1; + List allResults = new ArrayList<>(); + String pagingNext = null; + + do { + HttpHeaders headers = new HttpHeaders(); + headers.set("authorization", "Bearer " + daylightApiToken.getAccessToken()); + + String queryString = "pageSize=500&statusCode=Published&recursive=true&isActive=true&pageNumber=" + pageNumber; + log.info("Fetching properties page {}", pageNumber); + + ResponseEntity requestResponse = webProxyService.invokeRequest( + WebProxyService.DAYLIGHT_API, + HttpMethod.GET, + apiUrl, + queryString, + headers, + new byte[0], + Duration.ofSeconds(daylightApiSetting.getApiTimeout()), + httpErrorHandler + ).block(); + + if (requestResponse == null) { + response.setSuccess(false); + response.setMessage("Http request server can not visit."); + return response; + } + + response.setStatusCode(requestResponse.getStatusCode().value()); + + if (!requestResponse.getStatusCode().is2xxSuccessful()) { + byte[] errorBody = requestResponse.getBody(); + response.setSuccess(false); + response.setMessage(errorBody != null ? new String(errorBody, StandardCharsets.UTF_8) : "Request failed"); + return response; + } + + byte[] responseBody = requestResponse.getBody(); + if (responseBody == null) { + break; + } + + String jsonStr = new String(responseBody, StandardCharsets.UTF_8); + JSONObject responseJson = JSONObject.parseObject(jsonStr); + + // 累积 results 数组 + List pageResults = responseJson.getJSONArray("results").toJavaList(JSONObject.class); + if (pageResults != null) { + allResults.addAll(pageResults); + } + + // 判断是否有下一页 + pagingNext = null; + JSONObject paging = responseJson.getJSONObject("paging"); + if (paging != null) { + int totalCount = paging.getIntValue("totalCount"); + int pageSize = paging.getIntValue("pageSize"); + if (pageNumber * pageSize < totalCount) { + pagingNext = "next"; + pageNumber++; + } + } + } while (pagingNext != null); + + // 保存为一条 CfgDaylightConfiguration,configName = "Property" + if (!allResults.isEmpty()) { + CfgDaylightConfiguration config = cfgDaylightConfigurationDao + .findByDlPropertyIdAndConfigName(null, "Property"); + List oldItems = config != null && StringUtils.isNotBlank(config.getConfigValue()) + ? JSONObject.parseArray(config.getConfigValue(), JSONObject.class) + : new ArrayList<>(); + if (config == null) { + config = new CfgDaylightConfiguration(); + config.setConfigName("Property"); + config.setDlPropertyId(null); + config.setCreatedAt(new Date()); + } + config.setConfigValue(JSONObject.toJSONString(allResults)); + config.setUpdatedAt(new Date()); + cfgDaylightConfigurationDao.save(config); + logConfigurationChanges("Property sync", null, "Property", oldItems, allResults); + } + + response.setSuccess(true); + response.setMessage("Successfully fetched and saved " + allResults.size() + " properties."); + + // 记录同步日志 + SysLogDaylightConfigurationSync syncLog = new SysLogDaylightConfigurationSync(); + syncLog.setConfigName("Property"); + syncLog.setLastSyncAt(syncStartTime); + syncLog.setSyncStatus("SUCCESS"); + syncLog.setRecordCount(allResults.size()); + syncLog.setCreatedAt(new Date()); + syncLogDao.save(syncLog); + } catch (Exception e) { + response.setSuccess(false); + response.setMessage(e.getMessage()); + + // 记录失败日志 + SysLogDaylightConfigurationSync syncLog = new SysLogDaylightConfigurationSync(); + syncLog.setConfigName("Property"); + syncLog.setLastSyncAt(syncStartTime); + syncLog.setSyncStatus("FAILED"); + syncLog.setRecordCount(0); + syncLog.setCreatedAt(new Date()); + syncLogDao.save(syncLog); + } + return response; + } + + private void logConfigurationChanges(String action, String dlPropertyId, String configType, + List oldItems, List newItems) { + Map oldItemMap = toConfigItemMap(oldItems); + List addedItems = new ArrayList<>(); + List updatedItems = new ArrayList<>(); + List removedItems = new ArrayList<>(); + + for (JSONObject newItem : newItems) { + String itemKey = getConfigItemKey(newItem); + JSONObject oldItem = oldItemMap.remove(itemKey); + if (oldItem == null) { + addedItems.add(formatConfigItem(newItem)); + } else if (!JSONObject.toJSONString(oldItem).equals(JSONObject.toJSONString(newItem))) { + updatedItems.add(formatConfigItem(newItem)); + } + } + + for (JSONObject removedItem : oldItemMap.values()) { + removedItems.add(formatConfigItem(removedItem)); + } + + log.info("{} configName={}, dlPropertyId={}, oldCount={}, newCount={}, addedCount={}, updatedCount={}, removedCount={}, added={}, updated={}, removed={}", + action, configType, dlPropertyId, oldItems.size(), newItems.size(), addedItems.size(), updatedItems.size(), + removedItems.size(), limitLogItems(addedItems), limitLogItems(updatedItems), limitLogItems(removedItems)); + } + + private void logConfigurationCreated(String action, String dlPropertyId, String configType, List newItems) { + log.info("{} configName={}, dlPropertyId={}, newCount={}, items={}", + action, configType, dlPropertyId, newItems.size(), limitLogItems(formatConfigItems(newItems))); + } + + private Map toConfigItemMap(List items) { + Map itemMap = new HashMap<>(); + for (int i = 0; i < items.size(); i++) { + JSONObject item = items.get(i); + itemMap.put(getConfigItemKey(item, i), item); + } + return itemMap; + } + + private List formatConfigItems(List items) { + List formattedItems = new ArrayList<>(); + for (JSONObject item : items) { + formattedItems.add(formatConfigItem(item)); + } + return formattedItems; + } + + private String getConfigItemKey(JSONObject item) { + return getConfigItemKey(item, -1); + } + + private String getConfigItemKey(JSONObject item, int index) { + String itemKey = firstNotBlank(item, "id", "code", "propertyId", "propertyCode", "name"); + if (StringUtils.isNotBlank(itemKey)) { + return itemKey; + } + return index >= 0 ? "index:" + index : JSONObject.toJSONString(item); + } + + private String formatConfigItem(JSONObject item) { + List parts = new ArrayList<>(); + addIfNotBlank(parts, "id", item.getString("id")); + addIfNotBlank(parts, "code", firstNotBlank(item, "code", "propertyCode")); + addIfNotBlank(parts, "name", firstNotBlank(item, "name", "displayName", "shortName")); + if (parts.isEmpty()) { + return JSONObject.toJSONString(item); + } + return StringUtils.join(parts, ","); + } + + private String firstNotBlank(JSONObject item, String... fieldNames) { + for (String fieldName : fieldNames) { + String value = item.getString(fieldName); + if (StringUtils.isNotBlank(value)) { + return value; + } + } + return null; + } + + private void addIfNotBlank(List parts, String name, String value) { + if (StringUtils.isNotBlank(value)) { + parts.add(name + "=" + value); + } + } + + private List limitLogItems(List items) { + int maxLogItemCount = 50; + if (items.size() <= maxLogItemCount) { + return items; + } + + List limitedItems = new ArrayList<>(items.subList(0, maxLogItemCount)); + limitedItems.add("... total " + items.size() + " items"); + return limitedItems; + } + + /** + * 从 Daylight API 拉取 block codes 列表,按 property 遍历,存入 cfg_daylight_configuration 表 + */ + public BaseApiResponse fetchDaylightBlockCodes() { + BaseApiResponse response = new BaseApiResponse(); + Date syncStartTime = new Date(); + + String dlPropertyId = null; + try { + TokenResponse daylightApiToken = daylightTokenService.getApiAccessToken(); + if (daylightApiToken == null || StringUtils.isBlank(daylightApiToken.getAccessToken())) { + response.setSuccess(false); + response.setMessage("Invoke daylight token failed"); + return response; + } + + List allProperties = propertyDao.findByCategory("DAYLIGHT"); + int savedCount = 0; + + if (allProperties == null || allProperties.isEmpty()) { + response.setSuccess(false); + response.setMessage("No properties found with category 'DAYLIGHT'"); + return response; + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + + for (CfgProperties property : allProperties) { + dlPropertyId = property.getDlPropertyId(); + if (StringUtils.isBlank(dlPropertyId)) { + continue; + } + + // 查上次成功同步时间,决定是否走增量 + String updatedAtParam = ""; + Optional lastSync = syncLogDao + .findTopByConfigNameAndDlPropertyIdAndSyncStatusOrderByLastSyncAtDesc( + "BlockCode", dlPropertyId, "SUCCESS"); + CfgDaylightConfiguration existingConfig = cfgDaylightConfigurationDao + .findByDlPropertyIdAndConfigName(dlPropertyId, "BlockCode"); + if (lastSync.isPresent() && existingConfig != null) { + updatedAtParam = "&updatedAt=" + sdf.format(lastSync.get().getLastSyncAt()); + log.info("Property {} BlockCode incremental sync, updatedAt: {}", dlPropertyId, updatedAtParam); + } else { + if (lastSync.isPresent() && existingConfig == null) { + log.warn("Property {} BlockCode sync log found but config data missing, fallback to full sync", dlPropertyId); + } else { + log.info("Property {} BlockCode first sync, no updatedAt parameter", dlPropertyId); + } + } + + String path = "/api-gateway/data-api/v1/blocks"; + String apiUrl = daylightApiSetting.getServerUrl() + path; + List allResults = new ArrayList<>(); + + HttpHeaders headers = new HttpHeaders(); + headers.set("authorization", "Bearer " + daylightApiToken.getAccessToken()); + headers.set("Ac-Property-Id", dlPropertyId); + + String queryString = "pageSize=200" + updatedAtParam; + log.info("Fetching block codes for Ac-Property-Id: {}", dlPropertyId); + + ResponseEntity requestResponse = webProxyService.invokeRequest( + WebProxyService.DAYLIGHT_API, + HttpMethod.GET, + apiUrl, + queryString, + headers, + new byte[0], + Duration.ofSeconds(daylightApiSetting.getApiTimeout()), + httpErrorHandler + ).block(); + + if (requestResponse == null) { + response.setSuccess(false); + response.setMessage("Http request server can not visit."); + return response; + } + + response.setStatusCode(requestResponse.getStatusCode().value()); + + if (!requestResponse.getStatusCode().is2xxSuccessful()) { + byte[] errorBody = requestResponse.getBody(); + response.setSuccess(false); + response.setMessage(errorBody != null ? new String(errorBody, StandardCharsets.UTF_8) : "Request failed"); + return response; + } + + byte[] responseBody = requestResponse.getBody(); + if (responseBody != null) { + String jsonStr = new String(responseBody, StandardCharsets.UTF_8); + JSONObject responseJson = JSONObject.parseObject(jsonStr); + + List pageResults = responseJson.getJSONArray("results").toJavaList(JSONObject.class); + if (pageResults != null) { + allResults.addAll(pageResults); + } + } + + // 保存为一条 CfgDaylightConfiguration,configName = "BlockCode" + if (!allResults.isEmpty()) { + boolean isIncremental = StringUtils.isNotBlank(updatedAtParam); + if (isIncremental && existingConfig != null + && StringUtils.isNotBlank(existingConfig.getConfigValue())) { + // 增量同步:按 id 合并新旧数据 + List oldItems = JSONObject.parseArray(existingConfig.getConfigValue(), JSONObject.class); + Map mergedMap = new HashMap<>(); + for (JSONObject item : oldItems) { + mergedMap.put(getConfigItemKey(item), item); + } + logConfigurationChanges("BlockCode incremental sync", dlPropertyId, "BlockCode", oldItems, allResults); + for (JSONObject item : allResults) { + mergedMap.put(getConfigItemKey(item), item); + } + existingConfig.setConfigValue(JSONObject.toJSONString(new ArrayList<>(mergedMap.values()))); + existingConfig.setUpdatedAt(new Date()); + cfgDaylightConfigurationDao.save(existingConfig); + log.info("Merged BlockCode for {}: old={}, new={}, merged={}", + dlPropertyId, oldItems.size(), allResults.size(), mergedMap.size()); + } else { + // 全量同步:直接保存 + List oldItems = existingConfig != null + && StringUtils.isNotBlank(existingConfig.getConfigValue()) + ? JSONObject.parseArray(existingConfig.getConfigValue(), JSONObject.class) + : new ArrayList<>(); + CfgDaylightConfiguration config = existingConfig != null ? existingConfig : new CfgDaylightConfiguration(); + if (config.getConfigName() == null) { + config.setConfigName("BlockCode"); + config.setDlPropertyId(dlPropertyId); + } + if (config.getCreatedAt() == null) { + config.setCreatedAt(new Date()); + } + config.setConfigValue(JSONObject.toJSONString(allResults)); + config.setUpdatedAt(new Date()); + cfgDaylightConfigurationDao.save(config); + logConfigurationChanges("BlockCode full sync", dlPropertyId, "BlockCode", oldItems, allResults); + } + savedCount++; + } + } + + response.setSuccess(true); + response.setMessage("Successfully fetched and saved " + savedCount + " block code entries."); + + // 记录同步日志 + SysLogDaylightConfigurationSync syncLog = new SysLogDaylightConfigurationSync(); + syncLog.setConfigName("BlockCode"); + syncLog.setLastSyncAt(syncStartTime); + syncLog.setSyncStatus("SUCCESS"); + syncLog.setDlPropertyId(dlPropertyId); + syncLog.setRecordCount(savedCount); + syncLog.setCreatedAt(new Date()); + syncLogDao.save(syncLog); + } catch (Exception e) { + response.setSuccess(false); + response.setMessage(e.getMessage()); + + // 记录失败日志 + SysLogDaylightConfigurationSync syncLog = new SysLogDaylightConfigurationSync(); + syncLog.setConfigName("BlockCode"); + syncLog.setDlPropertyId(dlPropertyId); + syncLog.setLastSyncAt(syncStartTime); + syncLog.setSyncStatus("FAILED"); + syncLog.setRecordCount(0); + syncLog.setCreatedAt(new Date()); + syncLogDao.save(syncLog); + } + return response; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightTokenService.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightTokenService.java new file mode 100644 index 0000000..f754355 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightTokenService.java @@ -0,0 +1,227 @@ +package com.mgm.mcrm.apigateway.daylight.common.services; + +import com.alibaba.fastjson.JSON; +import com.mgm.mcrm.apigateway.common.dtos.HttpResult; +import com.mgm.mcrm.apigateway.common.dtos.config.DaylightApiSetting; +import com.mgm.mcrm.apigateway.daylight.common.dtos.CachedToken; +import com.mgm.mcrm.apigateway.daylight.common.dtos.TokenResponse; +import com.mgm.mcrm.apigateway.proxy.exception.HttpProxyErrorHandler; +import com.mgm.mcrm.apigateway.proxy.services.WebProxyService; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.LocalDateTime; + +@Service +public class DaylightTokenService { + private final Logger logger = LoggerFactory.getLogger(DaylightTokenService.class); + private final HttpProxyErrorHandler httpProxyErrorHandler = new HttpProxyErrorHandler(); + @Resource + private WebProxyService webProxyService; + @Autowired + private DaylightApiSetting daylightApiSetting; + private CachedToken cachedToken = null; + + private static final long RETRY_DELAY_MS = 1000; + private static final int MAX_PASSWORD_GRANT_ATTEMPTS = 3; + + public synchronized TokenResponse getApiAccessToken() throws Exception { + if (cachedToken != null && !isTokenExpire(cachedToken)) { + logger.info("Using cached Daylight access token because it has not expired."); + return cachedToken.getToken(); + } + + TokenResponse token = null; + TokenResponse expiredToken = cachedToken == null ? null : cachedToken.getToken(); + boolean passwordGrantRequired = true; + + // access token 过期后,优先使用缓存的 refresh token 获取新 token。 + if (expiredToken != null && StringUtils.isNotBlank(expiredToken.getRefreshToken())) { + logger.info("Daylight access token has expired; requesting a new token with refresh_token."); + token = requestToken(buildRefreshTokenRequestParams(expiredToken.getRefreshToken())); + if (token == null) { + logger.warn("Daylight refresh_token is expired or the refresh request failed; " + + "falling back to password grant."); + } else if (StringUtils.isBlank(token.getRefreshToken())) { + // 部分 OAuth 服务刷新成功后不会再次返回 refresh token,此时继续使用原 refresh token。 + token.setRefreshToken(expiredToken.getRefreshToken()); + } + if (token != null) { + passwordGrantRequired = false; + logger.info("Successfully obtained a new Daylight access token with refresh_token."); + } + } else if (expiredToken != null) { + logger.info("Daylight access token has expired and no refresh_token is available; " + + "requesting a new token with password grant."); + } else { + logger.info("No cached Daylight token is available; requesting a token with password grant."); + } + + // 首次请求、没有 refresh token 或刷新失败时,使用密码模式获取 token。 + if (passwordGrantRequired) { + token = requestTokenWithRetry(buildPasswordTokenRequestParams()); + if (token != null) { + logger.info("Successfully obtained a new Daylight access token with password grant."); + } + } + + if (token == null) { + logger.error("Failed to get a Daylight access token."); + return null; + } + + cacheToken(token); + return token; + } + + private TokenResponse requestTokenWithRetry(MultiValueMap params) + throws InterruptedException { + for (int attempt = 1; attempt <= MAX_PASSWORD_GRANT_ATTEMPTS; attempt++) { + HttpResult requestResult = invokeTokenRequest(params); + TokenResponse token = parseTokenResponse(requestResult); + if (token != null) { + return token; + } + + boolean shouldRetry = requestResult.getStatusCode() > 0 + && !isAuthenticationError(requestResult.getStatusCode()); + if (!shouldRetry || attempt == MAX_PASSWORD_GRANT_ATTEMPTS) { + break; + } + Thread.sleep(RETRY_DELAY_MS); + } + return null; + } + + private TokenResponse requestToken(MultiValueMap params) { + return parseTokenResponse(invokeTokenRequest(params)); + } + + private TokenResponse parseTokenResponse(HttpResult requestResult) { + if (requestResult.getStatusCode() < 200 || requestResult.getStatusCode() >= 300) { + return null; + } + try { + TokenResponse token = JSON.parseObject(requestResult.getResponseBody(), TokenResponse.class); + return token != null && StringUtils.isNotBlank(token.getAccessToken()) ? token : null; + } catch (Exception err) { + logger.error("Invalid Daylight token response:", err); + return null; + } + } + + private void cacheToken(TokenResponse token) { + if (cachedToken == null) { + cachedToken = new CachedToken(); + } + cachedToken.setToken(token); + + LocalDateTime currentTime = LocalDateTime.now(); + LocalDateTime expiryTime = currentTime.plusSeconds(daylightApiSetting.getTokenExpireSecond()); + try { + if (StringUtils.isNotBlank(token.getExpiresIn())) { + long tokenExpireVal = Math.max(0, Long.parseLong(token.getExpiresIn()) - 60); + expiryTime = currentTime.plusSeconds(tokenExpireVal); + } + } catch (NumberFormatException err) { + logger.warn("Invalid expires_in value in Daylight token response: {}", token.getExpiresIn()); + } + cachedToken.setExpireTime(expiryTime); + } + + private boolean isTokenExpire(CachedToken token) { + return token == null + || token.getExpireTime() == null + || !token.getExpireTime().isAfter(LocalDateTime.now()); + } + + private HttpHeaders buildHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + return headers; + } + + private MultiValueMap buildPasswordTokenRequestParams() { + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("username", daylightApiSetting.getUsername()); + formData.add("password", daylightApiSetting.getPassword()); + formData.add("grant_type", daylightApiSetting.getGrantType()); + formData.add("client_id", daylightApiSetting.getClientId()); + formData.add("client_secret", daylightApiSetting.getClientSecret()); + + if (StringUtils.isBlank(daylightApiSetting.getClientId())) { + logger.error("client_id is null or empty!"); + } + if (StringUtils.isBlank(daylightApiSetting.getClientSecret())) { + logger.error("client_secret is null or empty!"); + } + if (StringUtils.isBlank(daylightApiSetting.getGrantType())) { + logger.error("grant_type is null or empty!"); + } + return formData; + } + + private MultiValueMap buildRefreshTokenRequestParams(String refreshToken) { + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("grant_type", "refresh_token"); + formData.add("refresh_token", refreshToken); + formData.add("client_id", daylightApiSetting.getClientId()); + formData.add("client_secret", daylightApiSetting.getClientSecret()); + return formData; + } + + private boolean isAuthenticationError(int statusCode) { + return statusCode >= 400 && statusCode < 500; + } + + private HttpResult invokeTokenRequest(MultiValueMap params) { + HttpResult result = new HttpResult(); + String tokenUrl = daylightApiSetting.getServerUrl() + daylightApiSetting.getTokenUrl(); + try { + ResponseEntity requestRes = webProxyService.invokeRequest( + WebProxyService.DAYLIGHT_API, + HttpMethod.POST, + tokenUrl, + null, + buildHeaders(), + params, + Duration.ofSeconds(daylightApiSetting.getTokenRequestTimeout()), + httpProxyErrorHandler + ).block(); + if (requestRes != null) { + result.setStatusCode(requestRes.getStatusCode().value()); + result.setResponseHeaders(requestRes.getHeaders()); + byte[] resData = requestRes.getBody(); + if (resData != null) { + String bodyContent = new String(resData, StandardCharsets.UTF_8); + result.setResponseBody(bodyContent); + if (result.getStatusCode() < 200 || result.getStatusCode() >= 300) { + result.setErrorMessage(bodyContent); + } + } + } else { + result.setStatusCode(-1); + result.setErrorMessage("Http request server can not visit."); + } + } catch (Exception e) { + result.setStatusCode(-1); + result.setErrorMessage(e.getMessage()); + result.setExceptionClass(e.getClass().toString()); + logger.error("Failed to retrieve token from url: {}, Exception: {}", + tokenUrl, e.getMessage(), e); + } + return result; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/models/CfgDaylightConfiguration.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/models/CfgDaylightConfiguration.java new file mode 100644 index 0000000..1fa7ec2 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/models/CfgDaylightConfiguration.java @@ -0,0 +1,32 @@ +package com.mgm.mcrm.apigateway.daylight.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.util.Date; + +@Data +@Entity +@Table(name = "cfg_daylight_configuration") +public class CfgDaylightConfiguration { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "config_name") + private String configName; + + @Column(name = "config_value") + private String configValue; + + @Column(name = "dl_property_id") + private String dlPropertyId; + + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "updated_at") + private Date updatedAt; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/models/SysLogDaylightConfigurationSync.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/models/SysLogDaylightConfigurationSync.java new file mode 100644 index 0000000..4de389a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/models/SysLogDaylightConfigurationSync.java @@ -0,0 +1,35 @@ +package com.mgm.mcrm.apigateway.daylight.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.util.Date; + +@Data +@Entity +@Table(name = "sys_log_daylight_configuration_sync") +public class SysLogDaylightConfigurationSync { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "config_name") + private String configName; + + @Column(name = "last_sync_at") + private Date lastSyncAt; + + @Column(name = "sync_status") + private String syncStatus; + + @Column(name = "record_count") + private int recordCount; + + @Column(name = "dl_property_id") + private String dlPropertyId; + + @Column(name = "created_at") + private Date createdAt; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/CfgDaylightConfigurationDao.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/CfgDaylightConfigurationDao.java new file mode 100644 index 0000000..69c07ae --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/CfgDaylightConfigurationDao.java @@ -0,0 +1,32 @@ +package com.mgm.mcrm.apigateway.daylight.repositories; + +import com.mgm.mcrm.apigateway.daylight.models.CfgDaylightConfiguration; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface CfgDaylightConfigurationDao extends JpaRepository { + + CfgDaylightConfiguration findByDlPropertyIdAndConfigName(String dlPropertyId, String configName); + + @Query("select case when count(c) > 0 then true else false end " + + "from CfgDaylightConfiguration c " + + "where c.configName = 'Property' " + + "and c.configValue is not null " + + "and c.configValue <> ''") + boolean existsSavedDaylightProperties(); + + @Query("select case when count(c) > 0 then true else false end " + + "from CfgDaylightConfiguration c " + + "where c.configName not in ('Property', 'BlockCode') " + + "and c.configValue is not null " + + "and c.configValue <> ''") + boolean existsSavedDaylightConfiguration(); + + @Query("select case when count(c) > 0 then true else false end " + + "from CfgDaylightConfiguration c " + + "where c.dlPropertyId = ?1 " + + "and c.configName not in ('Property', 'BlockCode') " + + "and c.configValue is not null " + + "and c.configValue <> ''") + boolean existsSavedConfigurationByDlPropertyId(String dlPropertyId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/SysLogDaylightConfigurationSyncDao.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/SysLogDaylightConfigurationSyncDao.java new file mode 100644 index 0000000..4b95cb9 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/SysLogDaylightConfigurationSyncDao.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.daylight.repositories; + +import com.mgm.mcrm.apigateway.daylight.models.SysLogDaylightConfigurationSync; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface SysLogDaylightConfigurationSyncDao extends JpaRepository { + + Optional findTopByConfigNameAndSyncStatusOrderByLastSyncAtDesc(String configName, String syncStatus); + + Optional findTopByConfigNameAndDlPropertyIdAndSyncStatusOrderByLastSyncAtDesc( + String configName, String dlPropertyId, String syncStatus); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/controllers/DaylightWebhook.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/controllers/DaylightWebhook.java new file mode 100644 index 0000000..6806c2b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/controllers/DaylightWebhook.java @@ -0,0 +1,65 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.controllers; + +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler; +import com.mgm.mcrm.apigateway.daylight.webhook.services.DaylightWebhookFactory; +import com.mgm.mcrm.apigateway.logging.services.WebhookLogService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@Tag(name = "Allotment", description = "APIs for Room Allotment functions") +@RestController +@RequestMapping("/api/daylight/webhook") +public class DaylightWebhook { + @Resource + private WebhookLogService webhookLogService; + + @PostMapping(value = "/receive", produces = "application/json;charset=UTF-8") + public ResponseEntity handleRoomAvailableWebhook( + @RequestBody(required = true) String payload, + @RequestHeader Map headers, + HttpServletRequest request + ){ + BaseApiResponse result = new BaseApiResponse(); + String messageId = request.getHeader("Ac-Events-Message-Id"); + String eventType = request.getHeader("Ac-Events-Type"); + + if(StringUtils.isBlank(messageId)){ + result.setSuccess(false); + result.setMessage("Empty message id for this event"); + }else if(StringUtils.isBlank(eventType)){ + result.setSuccess(false); + result.setMessage("Empty event type for this event"); + }else if(StringUtils.isBlank(payload)){ + result.setSuccess(false); + result.setMessage("Empty payload for this event"); + }else{ + DaylightWebhookHandler handler = DaylightWebhookFactory.getHandler(eventType); + if(handler==null){ + result.setSuccess(false); + result.setMessage("No registered handler for event type["+eventType+"]"); + }else{ + boolean isDuplicated = webhookLogService.isMessageDuplicated(messageId); + if(isDuplicated){ + result.setSuccess(false); + result.setMessage("Message ["+messageId+"] already processed before"); + }else{ + try { + result = handler.handle(payload, headers); + }catch(Exception err){ + result.setSuccess(false); + result.setMessage("Handle Message ["+messageId+"] failed with error:"+err.getMessage()); + } + } + } + } + return ResponseEntity.status(HttpStatus.OK).body(result); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/BreakdownEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/BreakdownEntity.java new file mode 100644 index 0000000..f39db60 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/BreakdownEntity.java @@ -0,0 +1,18 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +import java.util.List; + +@Data +public class BreakdownEntity { + private String fromDate; + private String toDate; + private List guests; + private String marketSegmentId; + private RatePricingDetailsEntity ratePricingDetails; + private PriceEntity accommodationPrice; + private PriceEntity totalDailyPrice; + private List linkedPurchaseElements; + private DiscountEntity discount; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/CheckInDataEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/CheckInDataEntity.java new file mode 100644 index 0000000..77b4a98 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/CheckInDataEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class CheckInDataEntity { + private String userId; + private String utcTimestamp; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ContactEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ContactEntity.java new file mode 100644 index 0000000..d6e65b7 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ContactEntity.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +import java.util.List; + +@Data +public class ContactEntity { + private String id; + private List contactProfileRole; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ContactProfileEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ContactProfileEntity.java new file mode 100644 index 0000000..c839fd4 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ContactProfileEntity.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +import java.util.List; + +@Data +public class ContactProfileEntity { + private ContactEntity primaryContact; + private List otherContacts; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/DiscountEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/DiscountEntity.java new file mode 100644 index 0000000..3a40aba --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/DiscountEntity.java @@ -0,0 +1,10 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class DiscountEntity { + private double amount; + private ReasonEntity reason; + private String type; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/EmbeddedEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/EmbeddedEntity.java new file mode 100644 index 0000000..68f0119 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/EmbeddedEntity.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +import java.util.List; + +@Data +public class EmbeddedEntity { + private List breakdown; + private List purchaseElements; + private Object policies; + private List guestMessages; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ExternalSystemIdEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ExternalSystemIdEntity.java new file mode 100644 index 0000000..8e1abe0 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ExternalSystemIdEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class ExternalSystemIdEntity { + private String id; + private String value; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuaranteedTimeEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuaranteedTimeEntity.java new file mode 100644 index 0000000..8bc0d5d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuaranteedTimeEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class GuaranteedTimeEntity { + private String guaranteedTimeOfArrival; + private String guaranteedTimeOfDeparture; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuestEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuestEntity.java new file mode 100644 index 0000000..4f8ff2d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuestEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class GuestEntity { + private String ageBucketId; + private int count; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuestMessageEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuestMessageEntity.java new file mode 100644 index 0000000..91205ed --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/GuestMessageEntity.java @@ -0,0 +1,7 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class GuestMessageEntity { +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/LinkedCompanyEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/LinkedCompanyEntity.java new file mode 100644 index 0000000..519b58a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/LinkedCompanyEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class LinkedCompanyEntity { + private String id; + private ContactProfileEntity contactProfiles; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PolicyEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PolicyEntity.java new file mode 100644 index 0000000..00b30d1 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PolicyEntity.java @@ -0,0 +1,7 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class PolicyEntity { +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PreferenceEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PreferenceEntity.java new file mode 100644 index 0000000..ec0c387 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PreferenceEntity.java @@ -0,0 +1,10 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class PreferenceEntity { + private String id; + private boolean isRequired; + private int weight; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PriceEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PriceEntity.java new file mode 100644 index 0000000..4e3348a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PriceEntity.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class PriceEntity { + private RevenueAmountEntity amount; + private String currency; + private String basePriceType; + private boolean isConfidential; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ProjectedRevenueEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ProjectedRevenueEntity.java new file mode 100644 index 0000000..23eecd4 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ProjectedRevenueEntity.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class ProjectedRevenueEntity { + private String amount; + private String currency; + private boolean isConfidential; + private double gross; + private double net; + private String basePriceType; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PurchaseElementEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PurchaseElementEntity.java new file mode 100644 index 0000000..cbc5623 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/PurchaseElementEntity.java @@ -0,0 +1,7 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class PurchaseElementEntity { +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RatePricingDetailsEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RatePricingDetailsEntity.java new file mode 100644 index 0000000..3fa5716 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RatePricingDetailsEntity.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class RatePricingDetailsEntity { + private String ratePlanId; + private String roomTypeToChargeId; + private boolean isConfidential; + private boolean isEmergencyOffer; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReasonEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReasonEntity.java new file mode 100644 index 0000000..55305be --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReasonEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class ReasonEntity { + private String id; + private String comment; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationNotificationEvent.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationNotificationEvent.java new file mode 100644 index 0000000..1312f2d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationNotificationEvent.java @@ -0,0 +1,60 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +import java.util.List; + +@Data +public class ReservationNotificationEvent { + private String trigger; + private String propertyId; + private String reservationId; + private String correlationId; + private String profileId; + private StatusCodeEntity statusCode; + private String confirmationNumber; + private String eta; + private String etd; + private String checkInTime; + private String checkOutTime; + private String arrivalDate; + private String departureDate; + private GuaranteedTimeEntity guaranteedTimes; + private String roomTypeId; + private String roomId; + private String guaranteeTypeId; + private String channelId; + private List preferences; + private String sourceId; + private List accompanyingGuestProfileIds; + private List applicableMembershipIds; + private boolean isRoomFixed; + private String cancellationComment; + private String cancellingUserId; + private String cancellationTime; + private String cancellationReasonId; + private String cancellationNumber; + private String sharingId; + private String groupId; + private ProjectedRevenueEntity projectedRevenue; + private List externalSystemIds; + private TransportationsEntity transportations; + private String bookingDate; + private String accountId; + private LinkedCompanyEntity linkedCompany; + private LinkedCompanyEntity linkedTravelAgent; + private ContactProfileEntity guestContactProfiles; + private int version; + private String purposeOfStayId; + private String updatedAt; + private String createdAt; + private ProjectedRevenueEntity projectedRevenueAmount; + private String bookingTimestamp; + private RoomDetailEntity previousRoomDetails; + private WalkInDataEntity walkInData; + private boolean emergencyOffer; + private String blockId; + private EmbeddedEntity _embedded; + + private String dlExternalNo; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RevenueAmountEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RevenueAmountEntity.java new file mode 100644 index 0000000..28e5b31 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RevenueAmountEntity.java @@ -0,0 +1,12 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class RevenueAmountEntity { + private boolean isConfidential; + private double gross; + private double net; + private String currency; + private String basePriceType; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RoomDetailEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RoomDetailEntity.java new file mode 100644 index 0000000..c9d395f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/RoomDetailEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class RoomDetailEntity { + private String roomTypeId; + private String roomId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/StatusCodeEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/StatusCodeEntity.java new file mode 100644 index 0000000..9c2d9e5 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/StatusCodeEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class StatusCodeEntity { + private String code; + private String description; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/TransportationEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/TransportationEntity.java new file mode 100644 index 0000000..2028556 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/TransportationEntity.java @@ -0,0 +1,7 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class TransportationEntity { +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/TransportationsEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/TransportationsEntity.java new file mode 100644 index 0000000..f77fd2f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/TransportationsEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class TransportationsEntity { + private TransportationEntity arrivalTransportation; + private TransportationEntity departureTransportation; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/WalkInDataEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/WalkInDataEntity.java new file mode 100644 index 0000000..3b4f493 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/WalkInDataEntity.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +@Data +public class WalkInDataEntity { + private CheckInDataEntity checkInData; + private boolean walkInIntention; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/AllocationsVariantsEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/AllocationsVariantsEntity.java new file mode 100644 index 0000000..a5726ac --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/AllocationsVariantsEntity.java @@ -0,0 +1,8 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common; + +import lombok.Data; + +@Data +public class AllocationsVariantsEntity { + private int additionalProperties; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/ChangeAvailabilityEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/ChangeAvailabilityEntity.java new file mode 100644 index 0000000..15a3e94 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/ChangeAvailabilityEntity.java @@ -0,0 +1,15 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common; + +import lombok.Data; + +@Data +public class ChangeAvailabilityEntity { + private int available; + private int availableWithOverbooking; + private int allDeduct; + private int allNonDeduct; + private int blocksDeduct; + private int blocksNonDeduct; + private int outOfService; + private int outOfOrder; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/RoomAvailabilityEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/RoomAvailabilityEntity.java new file mode 100644 index 0000000..7ca4b3b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/RoomAvailabilityEntity.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common; + +import lombok.Data; + +@Data +public class RoomAvailabilityEntity { + private int definitelyAvailableRooms; + private int tentativelySoldRooms; + private int ooo; + private int oos; + private int definitelySoldRooms; + private int availableRoomsWithOverbooking; + private int physicalRooms; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/RoomCounterEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/RoomCounterEntity.java new file mode 100644 index 0000000..046fbfd --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/common/RoomCounterEntity.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common; + +import lombok.Data; + +@Data +public class RoomCounterEntity { + private int totalRooms; + private int sellLimit; + private int overbookingLimit; + private int allDeduct; + private int allNonDeduct; + private AllocationsVariantsEntity allocationsVariants; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/AvailableRoomCountUpdatedEvent.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/AvailableRoomCountUpdatedEvent.java new file mode 100644 index 0000000..4b071aa --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/AvailableRoomCountUpdatedEvent.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomcount; + +import lombok.Data; + +import java.util.List; + +@Data +public class AvailableRoomCountUpdatedEvent { + private PropertyAvailabilityEntity propertyAvailability; + private List roomTypesAvailabilities; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/DateAvailabilityEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/DateAvailabilityEntity.java new file mode 100644 index 0000000..5fc1a2e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/DateAvailabilityEntity.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomcount; + + +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common.RoomAvailabilityEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common.RoomCounterEntity; +import lombok.Data; + +@Data +public class DateAvailabilityEntity { + private String from; + private String to; + private RoomAvailabilityEntity availability; + private RoomCounterEntity counters; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/PropertyAvailabilityEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/PropertyAvailabilityEntity.java new file mode 100644 index 0000000..9b79c9b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/PropertyAvailabilityEntity.java @@ -0,0 +1,10 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomcount; + +import lombok.Data; + +import java.util.List; + +@Data +public class PropertyAvailabilityEntity { + private List dateAvailabilities; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/RoomTypeAvailabilityEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/RoomTypeAvailabilityEntity.java new file mode 100644 index 0000000..78464e7 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomcount/RoomTypeAvailabilityEntity.java @@ -0,0 +1,12 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomcount; + +import lombok.Data; + +import java.util.List; + +@Data +public class RoomTypeAvailabilityEntity { + private String roomTypeId; + private String blockId; + private List dateAvailabilities; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/AvailabilityEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/AvailabilityEntity.java new file mode 100644 index 0000000..d65e366 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/AvailabilityEntity.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomtype; + +import lombok.Data; + +import java.util.List; + +@Data +public class AvailabilityEntity { + private String roomTypeId; + private String blockId; + private List dateAvailabilityChanges; + private String to; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/DateAvailabilityChangeEntity.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/DateAvailabilityChangeEntity.java new file mode 100644 index 0000000..9e70289 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/DateAvailabilityChangeEntity.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomtype; + +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common.ChangeAvailabilityEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common.RoomCounterEntity; +import lombok.Data; + +@Data +public class DateAvailabilityChangeEntity { + private ChangeAvailabilityEntity availability; + private RoomCounterEntity counters; + private String from; + private String blockId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/RoomTypeDateAvailabilityChangedEvent.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/RoomTypeDateAvailabilityChangedEvent.java new file mode 100644 index 0000000..4ffc252 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/room/roomtype/RoomTypeDateAvailabilityChangedEvent.java @@ -0,0 +1,13 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomtype; + +import lombok.Data; + +import java.util.List; + +@Data +public class RoomTypeDateAvailabilityChangedEvent { + private String groupId; + private List availabilities; + private String inventoryId; + private String blockId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/DaylightWebhookHandler.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/DaylightWebhookHandler.java new file mode 100644 index 0000000..3212682 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/DaylightWebhookHandler.java @@ -0,0 +1,11 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers; + + + +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; + +import java.util.Map; + +public interface DaylightWebhookHandler { + BaseApiResponse handle(String payload, Map headers) throws Exception; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/AvailableRoomCountUpdatedHandler.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/AvailableRoomCountUpdatedHandler.java new file mode 100644 index 0000000..017f367 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/AvailableRoomCountUpdatedHandler.java @@ -0,0 +1,69 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.mgm.mcrm.apigateway.allotment.services.RoomCriteriaDaylightService; +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common.RoomCounterEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomcount.AvailableRoomCountUpdatedEvent; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomcount.DateAvailabilityEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomcount.RoomTypeAvailabilityEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class AvailableRoomCountUpdatedHandler implements DaylightWebhookHandler { + private final RoomCriteriaDaylightService roomCriteriaDaylightService; + @Override + public BaseApiResponse handle(String payload, Map headers) { + BaseApiResponse result = new BaseApiResponse(); + AvailableRoomCountUpdatedEvent eventData = null; + try{ + eventData = JSONObject.parseObject(payload, AvailableRoomCountUpdatedEvent.class); + }catch(Exception err){ + result.setSuccess(false); + result.setMessage("Convert Payload failed:["+err.getMessage()+"]"); + } + if(eventData!=null){ + String propertyId = headers.get("ac-property-id"); + if(StringUtils.isNotBlank(propertyId)){ + List roomTypeAvailabilities = eventData.getRoomTypesAvailabilities(); + if(roomTypeAvailabilities!= null && !roomTypeAvailabilities.isEmpty()){ + for(RoomTypeAvailabilityEntity roomItem:roomTypeAvailabilities){ + String roomTypeId = roomItem.getRoomTypeId(); + String blockCodeId = roomItem.getBlockId(); + List dateAvailabilities = roomItem.getDateAvailabilities(); + if(dateAvailabilities!=null && !dateAvailabilities.isEmpty()){ + for(DateAvailabilityEntity dateItem : dateAvailabilities){ + RoomCounterEntity counter = dateItem.getCounters(); + if(counter!=null){ + String fromDateValue = dateItem.getFrom(); + String toDateValue = dateItem.getTo(); + roomCriteriaDaylightService.processDaylightRoomTypeAllotmentUpdate( + "AvailableRoomCountUpdated", + propertyId, + roomTypeId, + blockCodeId, + fromDateValue, + toDateValue, + counter); + } + } + } + } + } + result.setSuccess(true); + }else{ + result.setSuccess(false); + result.setMessage("Daylight Property Id is empty in event"); + } + + } + return result; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationNotificationHandler.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationNotificationHandler.java new file mode 100644 index 0000000..8ac1f20 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationNotificationHandler.java @@ -0,0 +1,117 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSON; +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.common.enums.EumnDaylightStatus; +import com.mgm.mcrm.apigateway.common.utils.DateTimeUtil; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationNotificationEvent; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.StatusCodeEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler; +import com.mgm.mcrm.apigateway.reservation.models.Bookings; +import com.mgm.mcrm.apigateway.reservation.services.BookingService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ReservationNotificationHandler implements DaylightWebhookHandler { + + private final BookingService bookingService; + + + @Override + public BaseApiResponse handle(String payload, Map headers) throws Exception { + BaseApiResponse result = new BaseApiResponse(); + ReservationNotificationEvent eventData = null; + try{ + eventData = JSONObject.parseObject(payload, ReservationNotificationEvent.class); + }catch(Exception err){ + result.setSuccess(false); + result.setMessage("Convert Payload failed:["+err.getMessage()+"]"); + } + if(eventData!=null){ + + Bookings daylightBooking = convertEventToBooking(eventData); + bookingService.processDaylightUpdateBooking(daylightBooking); + result.setSuccess(true); + } + return result; + } + + private Bookings convertEventToBooking(ReservationNotificationEvent eventData) { + + log.info("Reservation Notification Event Data: {}", JSON.toJSONString(eventData)); + + Bookings result = new Bookings(); + LocalDateTime checkoutDate = DateTimeUtil.getDateFromString(eventData.getDepartureDate(), "yyyy-MM-dd"); + result.setConfirmationNo(eventData.getConfirmationNumber()); + result.setDlReservationId(eventData.getReservationId()); + result.setDlProfileId(eventData.getProfileId()); + result.setDlGuaranteeTypeId(eventData.getGuaranteeTypeId()); + result.setDlSourceId(eventData.getSourceId()); + result.setDlChannelId(eventData.getChannelId()); + result.setDlAccountId(eventData.getAccountId()); + result.setDlBlockId(eventData.getBlockId()); + result.setDlExternalNo(eventData.getDlExternalNo()); + if (eventData.getExternalSystemIds() != null + && !eventData.getExternalSystemIds().isEmpty()) { + result.setDlExternalNo(eventData.getExternalSystemIds().getFirst().getValue()); + } + result.setCreatedAt(parseDateTime(eventData.getCreatedAt())); + result.setUpdatedAt(parseDateTime(eventData.getUpdatedAt())); + result.setCheckinDate(DateTimeUtil.getDateFromString(eventData.getArrivalDate(),"yyyy-MM-dd")); + result.setCheckoutDate(checkoutDate); + result.setCategory("DAYLIGHT"); + + String dlMarketCodeId = eventData.get_embedded().getBreakdown().getFirst().getMarketSegmentId(); + String dlRatePlanId = eventData.get_embedded().getBreakdown().getFirst().getRatePricingDetails().getRatePlanId(); + + result.setDlMarketSegmentId(dlMarketCodeId); + result.setDlRatePlanId(dlRatePlanId); + + log.info("Webhook: Reservation confirmationNumber={}, reservationId={}, profileId={}", + eventData.getConfirmationNumber(), eventData.getReservationId(), eventData.getProfileId()); + Bookings oneByDlProfileId = bookingService.findOneByDlProfileId(eventData.getProfileId()); + if (oneByDlProfileId != null) { + result.setPlayerId(oneByDlProfileId.getPlayerId()); + log.info("PlayerId={}", oneByDlProfileId.getPlayerId()); + } else { + log.info("Player id not found"); + // TODO 去pms查 profile, 如果这个 profile 没有membership,那么不保存进 db by Mauve 20260731 + } + + StatusCodeEntity statusCode = eventData.getStatusCode(); + if(statusCode!=null) { + result.setLastOperaStatus(EumnDaylightStatus.getStatusByDaylightStatus(statusCode.getDescription().toUpperCase())); + } + result.setDlPropertyId(eventData.getPropertyId()); + result.setDlRoomTypeId(eventData.getRoomTypeId()); + String eta = eventData.getEta(); + if(StringUtils.isNotBlank(eta)) { + LocalDateTime dateTime = LocalDateTime.parse(eta); + String etaTime = dateTime.format(DateTimeFormatter.ofPattern("HH:mm")); + result.setEta(etaTime); + } + return result; + } + + private LocalDateTime parseDateTime(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + try { + return LocalDateTime.parse(value); + } catch (Exception err) { + log.warn("Unable to parse reservation timestamp: {}", value); + return null; + } + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/RoomTypeDateAvailabilityChangedHandler.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/RoomTypeDateAvailabilityChangedHandler.java new file mode 100644 index 0000000..ef19c38 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/RoomTypeDateAvailabilityChangedHandler.java @@ -0,0 +1,70 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl; + +import com.alibaba.fastjson.JSONObject; +import com.mgm.mcrm.apigateway.allotment.services.RoomCriteriaDaylightService; +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.common.RoomCounterEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomtype.AvailabilityEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomtype.DateAvailabilityChangeEntity; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.room.roomtype.RoomTypeDateAvailabilityChangedEvent; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class RoomTypeDateAvailabilityChangedHandler implements DaylightWebhookHandler { + private final RoomCriteriaDaylightService roomCriteriaDaylightService; + + @Override + public BaseApiResponse handle(String payload, Map headers) { + BaseApiResponse result = new BaseApiResponse(); + RoomTypeDateAvailabilityChangedEvent eventData = null; + try{ + eventData = JSONObject.parseObject(payload, RoomTypeDateAvailabilityChangedEvent.class); + }catch(Exception err){ + result.setSuccess(false); + result.setMessage("Convert Payload failed:["+err.getMessage()+"]"); + } + if(eventData!=null){ + String propertyId = headers.get("ac-property-id"); + if(StringUtils.isNotBlank(propertyId)){ + List roomTypeAvailabilities = eventData.getAvailabilities(); + if(roomTypeAvailabilities!= null && !roomTypeAvailabilities.isEmpty()){ + for(AvailabilityEntity roomItem:roomTypeAvailabilities){ + String roomTypeId = roomItem.getRoomTypeId(); + String dlBlockCodeId = roomItem.getBlockId(); + String toDateValue = roomItem.getTo(); + List dateAvailabilities = roomItem.getDateAvailabilityChanges(); + if(dateAvailabilities!=null && !dateAvailabilities.isEmpty()){ + for(DateAvailabilityChangeEntity changeItem : dateAvailabilities){ + RoomCounterEntity counter =changeItem.getCounters(); + if(counter!=null){ + String fromDateValue = changeItem.getFrom(); + roomCriteriaDaylightService.processDaylightRoomTypeAllotmentUpdate( + "RoomTypeDateAvailabilityChanged", + propertyId, + roomTypeId, + dlBlockCodeId, + fromDateValue, + toDateValue, + counter); + } + } + } + } + } + result.setSuccess(true); + }else{ + result.setSuccess(false); + result.setMessage("Daylight Property Id is empty in event"); + } + + } + return result; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/services/DaylightWebhookFactory.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/services/DaylightWebhookFactory.java new file mode 100644 index 0000000..215faad --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/services/DaylightWebhookFactory.java @@ -0,0 +1,19 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.services; + + +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DaylightWebhookFactory { + private static final Map handlers = new ConcurrentHashMap<>(); + + public static DaylightWebhookHandler getHandler(String eventType) { + return handlers.get(eventType); + } + + public static void registerHandler(String eventType, DaylightWebhookHandler handler) { + handlers.put(eventType, handler); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/EsbToken.java b/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/EsbToken.java new file mode 100644 index 0000000..15fa9a0 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/EsbToken.java @@ -0,0 +1,17 @@ +package com.mgm.mcrm.apigateway.esb.common.dtos; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class EsbToken { + @JSONField(name = "StatusCode") + private String statusCode; + @JSONField(name = "Token") + private String accessToken; + @JSONField(name = "ExpireTime") + private String expireTimeVal; + private LocalDateTime expireTime; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/PlayerProfileApiResult.java b/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/PlayerProfileApiResult.java new file mode 100644 index 0000000..4d0d069 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/PlayerProfileApiResult.java @@ -0,0 +1,188 @@ +package com.mgm.mcrm.apigateway.esb.common.dtos.mgmapi; + +import java.util.ArrayList; +import java.util.List; + + +public class PlayerProfileApiResult { + + private String statusCode = null; + private String statusDescription=""; + private String playerId; + private String userId; + private String tierName; + private String tierId; + private String chineseName; + private String englishName; + private String salutation; + private Integer codedFlag; + private String name; + private String playerPhone = null; + private String phoneType = null; + private String birthday = null; + private String country = null; + private String state; + private String passport = null; + private String language = null; + private List nameIds=new ArrayList(); + + public PlayerProfileApiResult() { + + } + + public String getStatusCode() { + return statusCode; + } + + public void setStatusCode(String statusCode) { + this.statusCode = statusCode; + } + + public String getStatusDescription() { + return statusDescription; + } + + public void setStatusDescription(String statusDescription) { + this.statusDescription = statusDescription; + } + + public String getPlayerId() { + return playerId; + } + + public void setPlayerId(String playerId) { + this.playerId = playerId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getTierName() { + return tierName; + } + + public void setTierName(String tierName) { + this.tierName = tierName; + } + + public String getTierId() { + return tierId; + } + + public void setTierId(String tierId) { + this.tierId = tierId; + } + + public String getChineseName() { + return chineseName; + } + + public void setChineseName(String chineseName) { + this.chineseName = chineseName; + } + + public String getEnglishName() { + return englishName; + } + + public void setEnglishName(String englishName) { + this.englishName = englishName; + } + + public String getSalutation() { + return salutation; + } + + public void setSalutation(String salutation) { + this.salutation = salutation; + } + + public List getNameIds() { + return nameIds; + } + + public void setNameIds(List nameIds) { + this.nameIds = nameIds; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getCodedFlag() { + return codedFlag; + } + + public void setCodedFlag(Integer codedFlag) { + this.codedFlag = codedFlag; + } + + public String getPlayerPhone() { + return playerPhone; + } + + public void setPlayerPhone(String playerPhone) { + this.playerPhone = playerPhone; + } + + public String getPhoneType() { + return phoneType; + } + + public void setPhoneType(String phoneType) { + this.phoneType = phoneType; + } + + public String getBirthday() { + return birthday; + } + + public void setBirthday(String birthday) { + this.birthday = birthday; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getPassport() { + return passport; + } + + public void setPassport(String passport) { + this.passport = passport; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/UserInfoApiRecord.java b/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/UserInfoApiRecord.java new file mode 100644 index 0000000..7c2226e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/UserInfoApiRecord.java @@ -0,0 +1,226 @@ +package com.mgm.mcrm.apigateway.esb.common.dtos.mgmapi; + + +public class UserInfoApiRecord { + + private String statusCode = null; + private String statusDescription=""; + private String userId; + private String userName; + private String property; + private String division; + private String department; + private String section; + private String block; + private Long propertyId; + private Long divisionId; + private Long departmentId; + private Long sectionId; + private Long blockId; + private Long teamStructureId; + private Boolean canViewBooking=false; + private Boolean canNewBooking=false; + private Boolean canViewTodoList=false; + private Boolean canViewDashboard=false; + private String groupName; + private String phone; + private boolean loginTag; + + public UserInfoApiRecord() { + + } + + + public String getStatusCode() { + return statusCode; + } + + public void setStatusCode(String statusCode) { + this.statusCode = statusCode; + } + + public String getStatusDescription() { + return statusDescription; + } + + public void setStatusDescription(String statusDescription) { + this.statusDescription = statusDescription; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + + public String getDivision() { + return division; + } + + public void setDivision(String division) { + this.division = division; + } + + public String getDepartment() { + return department; + } + + public void setDepartment(String department) { + this.department = department; + } + + public String getSection() { + return section; + } + + public void setSection(String section) { + this.section = section; + } + + public String getBlock() { + return block; + } + + public void setBlock(String block) { + this.block = block; + } + + public Long getPropertyId() { + return propertyId; + } + + public void setPropertyId(Long propertyId) { + this.propertyId = propertyId; + } + + public Long getDivisionId() { + return divisionId; + } + + public void setDivisionId(Long divisionId) { + this.divisionId = divisionId; + } + + public Long getDepartmentId() { + return departmentId; + } + + public void setDepartmentId(Long departmentId) { + this.departmentId = departmentId; + } + + public Long getSectionId() { + return sectionId; + } + + public void setSectionId(Long sectionId) { + this.sectionId = sectionId; + } + + public Long getBlockId() { + return blockId; + } + + public void setBlockId(Long blockId) { + this.blockId = blockId; + } + + public Long getTeamStructureId() { + return teamStructureId; + } + + public void setTeamStructureId(Long teamStructureId) { + this.teamStructureId = teamStructureId; + } + + public boolean isLoginTag() { + return loginTag; + } + + public void setLoginTag(boolean loginTag) { + this.loginTag = loginTag; + } + + + public Boolean getCanViewBooking() { + return canViewBooking; + } + + + public void setCanViewBooking(Boolean canViewBooking) { + this.canViewBooking = canViewBooking; + } + + + public Boolean getCanNewBooking() { + return canNewBooking; + } + + + public void setCanNewBooking(Boolean canNewBooking) { + this.canNewBooking = canNewBooking; + } + + + + + public Boolean getCanViewTodoList() { + return canViewTodoList; + } + + + public void setCanViewTodoList(Boolean canViewTodoList) { + this.canViewTodoList = canViewTodoList; + } + + + public Boolean getCanViewDashboard() { + return canViewDashboard; + } + + + public void setCanViewDashboard(Boolean canViewDashboard) { + this.canViewDashboard = canViewDashboard; + } + + + public String getGroupName() { + return groupName; + } + + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + + public String getPhone() { + return phone; + } + + + public void setPhone(String phone) { + this.phone = phone; + } + + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/UserInfoApiResult.java b/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/UserInfoApiResult.java new file mode 100644 index 0000000..e66f452 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/esb/common/dtos/mgmapi/UserInfoApiResult.java @@ -0,0 +1,31 @@ +package com.mgm.mcrm.apigateway.esb.common.dtos.mgmapi; + + +import lombok.Data; + +import java.util.List; + +@Data +public class UserInfoApiResult { + + private String statusCode = null; + private String statusDescription=""; + private String userId; + private String userName; + private String property; + private String division; + private String department; + private String section; + private String block; + private Long propertyId; + private Long divisionId; + private Long departmentId; + private Long sectionId; + private Long blockId; + private Long teamStructureId; + private Boolean canViewBooking=false; + private Boolean canNewBooking=false; + private String groupName; + private boolean loginTag; + private List records; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/esb/common/services/EsbTokenService.java b/src/main/java/com/mgm/mcrm/apigateway/esb/common/services/EsbTokenService.java new file mode 100644 index 0000000..2a0f62b --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/esb/common/services/EsbTokenService.java @@ -0,0 +1,158 @@ +package com.mgm.mcrm.apigateway.esb.common.services; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.mgm.mcrm.apigateway.common.dtos.HttpResult; +import com.mgm.mcrm.apigateway.common.dtos.config.EsbApiSetting; +import com.mgm.mcrm.apigateway.common.utils.DateTimeUtil; +import com.mgm.mcrm.apigateway.esb.common.dtos.EsbToken; +import com.mgm.mcrm.apigateway.proxy.exception.HttpProxyErrorHandler; +import com.mgm.mcrm.apigateway.proxy.services.WebProxyService; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.LocalDateTime; + + +@Service +public class EsbTokenService { + private final Logger logger = LoggerFactory.getLogger(EsbTokenService.class); + private final HttpProxyErrorHandler httpProxyErrorHandler=new HttpProxyErrorHandler(); + @Resource + private WebProxyService webProxyService; + @Autowired + private EsbApiSetting esbApiSetting; + private EsbToken cachedToken = new EsbToken(); + + // 重试间隔时间:1秒 + private final long RETRY_DELAY_MS = 1000; + + public EsbToken getApiAccessToken() throws Exception { + EsbToken returnToken = null; + int retryCnt=0; + boolean isNeedTry=true; + String lastErrorMsg =""; + if (!isTokenExpire(cachedToken)) { + return cachedToken; + }else{ + while(returnToken==null && isNeedTry && retryCnt<3){ + HttpResult requestResult = this.invokeTokenRequest(); + if(requestResult.getStatusCode()>=200 && requestResult.getStatusCode()<300){ + try{ + EsbToken newToken = JSON.parseObject(requestResult.getResponseBody(), EsbToken.class); + if("OK".equalsIgnoreCase(newToken.getStatusCode()) + && StringUtils.isNotBlank(newToken.getAccessToken()) + ){ + LocalDateTime expiryTime =DateTimeUtil.getDateFromString(newToken.getExpireTimeVal(), "yyyy-MM-dd HH:mm:ss"); + if(expiryTime!=null){ + newToken.setExpireTime(expiryTime.minusSeconds(60)); + }else{ + expiryTime = LocalDateTime.now().plusSeconds(esbApiSetting.getTokenExpireSecond()); + newToken.setExpireTime(expiryTime); + } + returnToken = newToken; + } + }catch(Exception err){ + logger.error("Invalid token response after cache expired:", err); + } + }else{ + lastErrorMsg = requestResult.getErrorMessage(); + } + isNeedTry = (returnToken==null && requestResult.getStatusCode()>0 && !(isAuthenticationError(requestResult.getStatusCode()))); + if(isNeedTry) { + retryCnt++; + Thread.sleep(RETRY_DELAY_MS); + } + } + } + if(returnToken==null){ + logger.error("Failed to get new token after cache expired with error: {}", lastErrorMsg); + }else{ + cachedToken = returnToken; + } + return returnToken; + } + + private boolean isTokenExpire(EsbToken token){ + return token==null + || StringUtils.isBlank(token.getAccessToken()) + || token.getExpireTime()==null + || token.getExpireTime().isBefore(LocalDateTime.now()); + } + + /** + * 构建请求头 + */ + private HttpHeaders buildHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-type", "application/json;charset=utf-8"); + return headers; + } + + private byte[] buildTokenRequestBody() { + JSONObject requestObj = new JSONObject(); + requestObj.put("AppId", esbApiSetting.getAppId()); + requestObj.put("AppSecret", esbApiSetting.getAppSecret()); + return requestObj.toJSONString().getBytes(StandardCharsets.UTF_8); + } + /** + * 检查是否是认证相关的错误(不应该重试) + */ + private boolean isAuthenticationError(int statusCode) { + return statusCode>=400 && statusCode<500; + } + /** + * 发送 token 请求(单次),返回详细结果包括状态码 + */ + private HttpResult invokeTokenRequest() { + HttpResult result = new HttpResult(); + String tokenUrl =esbApiSetting.getServerUrl() + esbApiSetting.getTokenUrl(); + try { + byte[] requestBody = buildTokenRequestBody(); + HttpHeaders headers = buildHeaders(); + // 构建完整的 URL + ResponseEntity requestRes = webProxyService.invokeRequest( + WebProxyService.ESB_API, + HttpMethod.POST, + tokenUrl, + null, + headers, + requestBody, + Duration.ofSeconds(esbApiSetting.getTokenRequestTimeout()), + httpProxyErrorHandler + ).block(); + if(requestRes!=null){ + result.setStatusCode(requestRes.getStatusCode().value()); + result.setResponseHeaders(requestRes.getHeaders()); + byte[] resData = requestRes.getBody(); + if(resData!=null){ + String bodyContent = new String(resData, StandardCharsets.UTF_8); + result.setResponseBody(bodyContent); + if(result.getStatusCode()<200 || result.getStatusCode()>=300){ + result.setErrorMessage(bodyContent); + } + } + }else{ + result.setStatusCode(-1); + result.setErrorMessage("Http request server can not visit."); + } + } catch (Exception e) { + result.setStatusCode(-1); + result.setErrorMessage(e.getMessage()); + result.setExceptionClass(e.getClass().toString()); + logger.error("Failed to retrieve token from url: {}, Exception: {}", + tokenUrl, e.getMessage(), e); + } + return result; + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/esb/common/services/MGMApiService.java b/src/main/java/com/mgm/mcrm/apigateway/esb/common/services/MGMApiService.java new file mode 100644 index 0000000..4d6e0b3 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/esb/common/services/MGMApiService.java @@ -0,0 +1,123 @@ +package com.mgm.mcrm.apigateway.esb.common.services; + +import com.alibaba.fastjson2.JSONObject; +import com.mgm.mcrm.apigateway.common.dtos.HttpResult; +import com.mgm.mcrm.apigateway.common.dtos.config.EsbApiSetting; +import com.mgm.mcrm.apigateway.esb.common.dtos.EsbToken; +import com.mgm.mcrm.apigateway.proxy.exception.HttpProxyErrorHandler; +import com.mgm.mcrm.apigateway.proxy.services.WebProxyService; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; + +@Service +public class MGMApiService { + private static final String API_GET_USER_INFO="/GetLoginUserInfo"; + private static final String API_GET_ALL_PLAYER_PROFILE="/GetPlayerInfo"; + private final HttpProxyErrorHandler httpProxyErrorHandler=new HttpProxyErrorHandler(); + @Autowired + private EsbApiSetting esbApiSetting; + @Resource + private WebProxyService webProxyService; + @Resource + private EsbTokenService esbTokenService; + + public HttpResult invokeGetAllPlayerProfile(String playerId) throws Exception { + HttpResult result = new HttpResult(); + String apiLink = esbApiSetting.getServerUrl() + API_GET_ALL_PLAYER_PROFILE; + EsbToken esbToken = esbTokenService.getApiAccessToken(); + if(esbToken==null || StringUtils.isBlank(esbToken.getAccessToken())){ + throw new Exception("Invoke ESB token failed"); + } + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", esbToken.getAccessToken()); + headers.add("Content-type", "application/json;charset=utf-8"); + headers.add("PlayerId", playerId); + JSONObject requestObj = new JSONObject(); + requestObj.put("playerId", playerId); + byte[] requestBody = requestObj.toJSONString().getBytes(StandardCharsets.UTF_8); + ResponseEntity requestRes = webProxyService.invokeRequest( + WebProxyService.ESB_API, + HttpMethod.GET, + apiLink, + null, + headers, + requestBody, + Duration.ofSeconds(esbApiSetting.getApiRequestTimeout()), + httpProxyErrorHandler + ).block(); + if(requestRes!=null){ + result.setStatusCode(requestRes.getStatusCode().value()); + result.setResponseHeaders(requestRes.getHeaders()); + byte[] resData = requestRes.getBody(); + if(resData!=null){ + String bodyContent = new String(resData, StandardCharsets.UTF_8); + result.setResponseBody(bodyContent); + if(result.getStatusCode()<200 || result.getStatusCode()>=300){ + result.setErrorMessage(bodyContent); + } + } + }else{ + result.setStatusCode(-1); + result.setErrorMessage("Http request server can not visit."); + } + return result; + } + + public HttpResult invokeGetUserInfo(String userId,String userName) throws Exception { + HttpResult result = new HttpResult();; + String apiLink = esbApiSetting.getServerUrl() + API_GET_USER_INFO; + EsbToken esbToken = esbTokenService.getApiAccessToken(); + if(esbToken==null || StringUtils.isBlank(esbToken.getAccessToken())){ + throw new Exception("Invoke ESB token failed"); + } + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", esbToken.getAccessToken()); + headers.add("Content-type", "application/json;charset=utf-8"); + JSONObject requestObj = new JSONObject(); + if(StringUtils.isNotBlank(userId)) { + headers.add("UserId", userId); + requestObj.put("userId", userId); + } + if(StringUtils.isNotBlank(userName)) { + headers.add("UserName", userName); + requestObj.put("userName", userName); + } + byte[] requestBody = requestObj.toJSONString().getBytes(StandardCharsets.UTF_8); + ResponseEntity requestRes = webProxyService.invokeRequest( + WebProxyService.ESB_API, + HttpMethod.GET, + apiLink, + null, + headers, + requestBody, + Duration.ofSeconds(esbApiSetting.getApiRequestTimeout()), + httpProxyErrorHandler + ).block(); + if(requestRes!=null){ + result.setStatusCode(requestRes.getStatusCode().value()); + result.setResponseHeaders(requestRes.getHeaders()); + byte[] resData = requestRes.getBody(); + if(resData!=null){ + String bodyContent = new String(resData, StandardCharsets.UTF_8); + result.setResponseBody(bodyContent); + if(result.getStatusCode()<200 || result.getStatusCode()>=300){ + result.setErrorMessage(bodyContent); + } + } + }else{ + result.setStatusCode(-1); + result.setErrorMessage("Http request server can not visit."); + } + return result; + } +} + + diff --git a/src/main/java/com/mgm/mcrm/apigateway/esb/player/services/MgmUserService.java b/src/main/java/com/mgm/mcrm/apigateway/esb/player/services/MgmUserService.java new file mode 100644 index 0000000..3f71baf --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/esb/player/services/MgmUserService.java @@ -0,0 +1,150 @@ +package com.mgm.mcrm.apigateway.esb.player.services; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONReader; +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgBlocks; +import com.mgm.mcrm.apigateway.basesetting.mapping.repositories.BlockDao; +import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; +import com.mgm.mcrm.apigateway.basesetting.property.repositories.PropertyDao; +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgDepartments; +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgDivisions; +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgSections; +import com.mgm.mcrm.apigateway.basesetting.team.models.CfgTeamStructure; +import com.mgm.mcrm.apigateway.basesetting.team.repositories.DepartmentDao; +import com.mgm.mcrm.apigateway.basesetting.team.repositories.DivisionDao; +import com.mgm.mcrm.apigateway.basesetting.team.repositories.SectionDao; +import com.mgm.mcrm.apigateway.basesetting.team.repositories.TeamStructureDao; +import com.mgm.mcrm.apigateway.common.constants.SysConstants; +import com.mgm.mcrm.apigateway.common.dtos.HttpResult; +import com.mgm.mcrm.apigateway.common.dtos.ServiceResult; +import com.mgm.mcrm.apigateway.esb.common.dtos.mgmapi.UserInfoApiRecord; +import com.mgm.mcrm.apigateway.esb.common.dtos.mgmapi.UserInfoApiResult; +import com.mgm.mcrm.apigateway.esb.common.services.MGMApiService; +import com.mgm.mcrm.apigateway.system.models.SysGroupPermission; +import com.mgm.mcrm.apigateway.system.models.SysUserView; +import com.mgm.mcrm.apigateway.system.repositories.SysGroupPermissionDao; +import com.mgm.mcrm.apigateway.system.repositories.SysUserViewDao; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class MgmUserService { + + @Resource + private MGMApiService mgmApiService; + @Resource + private PropertyDao propertyDao; + @Resource + private BlockDao blockDao; + @Resource + private DivisionDao divisionDao; + @Resource + private DepartmentDao departmentDao; + @Resource + private SectionDao sectionDao; + @Resource + private TeamStructureDao teamStructureDao; + @Resource + private SysUserViewDao sysUserViewDao; + @Resource + private SysGroupPermissionDao sysGroupPermissionDao; + + public ServiceResult findUserInfoByUserId(String userId) { + ServiceResult result = new ServiceResult(); + try { + HttpResult httpResult = mgmApiService.invokeGetUserInfo(userId,""); + if(httpResult.getStatusCode()==200) { + try { + UserInfoApiResult resultObj = JSONObject.parseObject(httpResult.getResponseBody(),UserInfoApiResult.class,JSONReader.Feature.FieldBased); + if(resultObj!=null && resultObj.getStatusCode()!=null) { + String resultCode = resultObj.getStatusCode(); + if("OK".equals(resultCode)) { + List recrods = resultObj.getRecords(); + if(!recrods.isEmpty()) { + UserInfoApiRecord user = recrods.get(0); + if(!"NULL".equals(user.getUserId())) { + user = this.supplementInfoId(userId,user); + user.setStatusCode(resultObj.getStatusCode()); + user.setStatusDescription(resultObj.getStatusDescription()); + result.setSuccess(true); + result.setReturnData(user); + }else { + result.setSuccess(false); + result.setErrorMessage("User not found"); + } + }else { + result.setSuccess(false); + result.setErrorMessage("User not found"); + } + + }else { + result.setSuccess(false); + result.setErrorMessage("UserInfo API return error:["+resultCode+"] "+resultObj.getStatusDescription()); + } + }else { + result.setSuccess(false); + result.setErrorMessage("Invalid return data"); + } + }catch(Exception err) { + result.setSuccess(false); + result.setErrorMessage("Parse return data failed:"+err.getMessage()); + } + + }else { + result.setSuccess(false); + result.setErrorMessage("Invoke get user info failed:["+httpResult.getStatusCode()+"] "+httpResult.getErrorMessage()); + } + }catch(Exception err) { + result.setSuccess(false); + result.setErrorMessage("Invoke get user info API error:"+err.getMessage()); + } + return result; + } + + + private UserInfoApiRecord supplementInfoId(String userId,UserInfoApiRecord info) { + // 待完善,有可能需要一层一层获得property,division,department,team + CfgProperties property = propertyDao.getByName(info.getProperty()); + if(property!=null) { + info.setPropertyId(property.getId()); + } + CfgBlocks block = blockDao.getByName(info.getBlock()); + if(block!=null) { + info.setBlockId(block.getId()); + } + CfgDivisions division = divisionDao.getByName(info.getDivision()); + if(division!=null) { + info.setDivisionId(division.getId()); + } + CfgDepartments department = departmentDao.getByName(info.getDepartment()); + if(department!=null) { + info.setDepartmentId(department.getId()); + } + CfgSections section = sectionDao.getByName(info.getSection()); + if(section!=null) { + info.setSectionId(section.getId()); + } + CfgTeamStructure team = teamStructureDao.getByDivisionIdAndDepartmentIdAndSectionId(info.getDivisionId(),info.getDepartmentId(),info.getSectionId()); + if(team!=null) { + info.setTeamStructureId(team.getId()); + } + SysUserView userGroup = sysUserViewDao.getByLoginId(userId); + if(userGroup!=null) { + if(userGroup.getGroupId()!=null) { + info.setGroupName(userGroup.getGroupName()); + List permission = sysGroupPermissionDao.getAllByGroupId(userGroup.getGroupId()); + if(!permission.isEmpty()) { + for(SysGroupPermission per:permission) { + if(SysConstants.PERMISSION_APP_VIEW_BOOKING.equals(per.getPermissionId())) info.setCanViewBooking(true); + if(SysConstants.PERMISSION_APP_NEW_BOOKING.equals(per.getPermissionId())) info.setCanNewBooking(true); + if(SysConstants.PERMISSION_APP_TODO_LIST.equals(per.getPermissionId())) info.setCanViewTodoList(true); + if(SysConstants.PERMISSION_APP_DASHBOARD.equals(per.getPermissionId())) info.setCanViewDashboard(true); + } + } + } + } + return info; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/filters/ApiGatewayLoggingFilter.java b/src/main/java/com/mgm/mcrm/apigateway/logging/filters/ApiGatewayLoggingFilter.java new file mode 100644 index 0000000..6f92154 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/filters/ApiGatewayLoggingFilter.java @@ -0,0 +1,175 @@ +package com.mgm.mcrm.apigateway.logging.filters; + + +import com.alibaba.fastjson2.JSONObject; +import com.mgm.mcrm.apigateway.common.utils.NetworkUtil; +import com.mgm.mcrm.apigateway.logging.models.ApiGatewayLog; +import com.mgm.mcrm.apigateway.logging.services.ApiGatewayLogService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +@RequiredArgsConstructor +public class ApiGatewayLoggingFilter extends OncePerRequestFilter { + + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + @Setter + private List excludePatterns; + + private final ApiGatewayLogService apiGatewayLogService; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) + throws IOException, ServletException { + Throwable handleException = null; + ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); + ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); + LocalDateTime startTime = LocalDateTime.now(); + try { + filterChain.doFilter(wrappedRequest, wrappedResponse); + }catch(Exception err){ + handleException = err; + throw err; + } + finally { + String errorMessage = handleException!=null?handleException.getMessage():""; + if(StringUtils.isBlank(errorMessage)){ + errorMessage = checkResponseErrors(wrappedRequest); + } + if(!"OPTIONS".equalsIgnoreCase(request.getMethod())){ + int statusCode = wrappedResponse.getStatus(); + ApiGatewayLog requestLog = createRequestLog(wrappedRequest, startTime); + requestLog.setResponseTime(LocalDateTime.now()); + if(StringUtils.isNotBlank(errorMessage)){ + requestLog.setResponseBody(errorMessage); + }else{ + requestLog.setResponseBody(getResponseContent(wrappedResponse)); + } + requestLog.setStatusCode(String.valueOf(statusCode)); + apiGatewayLogService.saveApiGatewayLog(requestLog); + } + wrappedResponse.copyBodyToResponse(); + } + } + + private String checkResponseErrors(HttpServletRequest request) { + String errorMessage = ""; + Object exceptionVal = request.getAttribute("AUTH_EXCEPTION"); + errorMessage = exceptionVal!=null?exceptionVal.toString():""; + if(StringUtils.isBlank(errorMessage)){ + exceptionVal = request.getAttribute(DispatcherServlet.EXCEPTION_ATTRIBUTE); + errorMessage = exceptionVal!=null?exceptionVal.toString():""; + } + return errorMessage; + } + + private ApiGatewayLog createRequestLog(HttpServletRequest request, LocalDateTime startTime) { + ApiGatewayLog log = new ApiGatewayLog(); + log.setRequestMethod(request.getMethod()); + String requestUrl =getFullUrl(request); + log.setUrl(requestUrl); + log.setRequestTime(startTime); + log.setHeader(getHeadersAsString(request)); + log.setRequestSource(getRequestSource(request)); + if (request instanceof ContentCachingRequestWrapper wrappedRequest) { + byte[] content = wrappedRequest.getContentAsByteArray(); + if (content.length > 0) { + try { + String body = new String(content, wrappedRequest.getCharacterEncoding()); + log.setRequestBody(body); + }catch(Exception ignored){ + log.setRequestBody(new String(content)); + } + } + } + return log; + } + + private String getFullUrl(HttpServletRequest request) { + String scheme = request.getScheme(); + String serverName = request.getServerName(); + int serverPort = request.getServerPort(); + String contextPath = request.getContextPath(); + String servletPath = request.getServletPath(); + String pathInfo = request.getPathInfo(); + String queryString = request.getQueryString(); + + StringBuilder url = new StringBuilder(); + url.append(scheme).append("://").append(serverName); + if ((scheme.equals("http") && serverPort != 80) || + (scheme.equals("https") && serverPort != 443)) { + url.append(":").append(serverPort); + } + url.append(contextPath).append(servletPath); + if (pathInfo != null) { + url.append(pathInfo); + } + if (StringUtils.isNotBlank(queryString)) { + url.append("?").append(queryString); + } + return url.toString(); + } + + private String getRequestSource(HttpServletRequest request){ + Map requestSource = new HashMap(); + requestSource.put("requestIP", NetworkUtil.getClientIpAddr(request)); + return JSONObject.toJSONString(requestSource); + } + + private String getResponseContent(HttpServletResponse response) { + String responseBody=""; + if (response instanceof ContentCachingResponseWrapper wrappedResponse) { + byte[] content = wrappedResponse.getContentAsByteArray(); + if (content.length > 0) { + try { + responseBody = new String(content, wrappedResponse.getCharacterEncoding()); + }catch(Exception ignored){ + responseBody = new String(content); + } + } + } + return responseBody; + } + + private String getHeadersAsString(HttpServletRequest request) { + StringBuilder headers = new StringBuilder(); + request.getHeaderNames().asIterator().forEachRemaining(headerName -> { + headers.append(headerName).append(": ").append(request.getHeader(headerName)).append("; "); + }); + return headers.toString(); + } + + @Override + protected boolean shouldNotFilter(@NonNull HttpServletRequest request) { + if (excludePatterns == null) { + return false; + } + return excludePatterns.stream() + .anyMatch(pattern -> pathMatcher.match(pattern, request.getRequestURI())); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/filters/WebhookLoggingFilter.java b/src/main/java/com/mgm/mcrm/apigateway/logging/filters/WebhookLoggingFilter.java new file mode 100644 index 0000000..48ebdf3 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/filters/WebhookLoggingFilter.java @@ -0,0 +1,162 @@ +package com.mgm.mcrm.apigateway.logging.filters; + + +import com.alibaba.fastjson2.JSONObject; + +import com.mgm.mcrm.apigateway.logging.models.WebhookLog; +import com.mgm.mcrm.apigateway.logging.services.WebhookLogService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +import java.io.IOException; +import java.time.LocalDateTime; + +@Component +@RequiredArgsConstructor +public class WebhookLoggingFilter extends OncePerRequestFilter { + + private final WebhookLogService webhookLogService; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) + throws IOException, ServletException { + Throwable handleException = null; + ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); + ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); + LocalDateTime startTime = LocalDateTime.now(); + try { + filterChain.doFilter(wrappedRequest, wrappedResponse); + }catch(Exception err){ + handleException = err; + throw err; + } + finally { + String errorMessage = handleException!=null?handleException.getMessage():""; + if(StringUtils.isBlank(errorMessage)){ + errorMessage = checkResponseErrors(wrappedRequest); + } + if(!"OPTIONS".equalsIgnoreCase(request.getMethod())){ + int statusCode = wrappedResponse.getStatus(); + WebhookLog requestLog = createRequestLog(wrappedRequest, startTime); + requestLog.setResponseTime(LocalDateTime.now()); + if(StringUtils.isNotBlank(errorMessage)){ + requestLog.setResponseBody(errorMessage); + }else{ + requestLog.setResponseBody(getResponseContent(wrappedResponse)); + requestLog.setSuccess(false); + try{ + JSONObject resultObj = JSONObject.parseObject(requestLog.getResponseBody()); + if(resultObj.containsKey("success")){ + requestLog.setSuccess(resultObj.getBoolean("success")); + } + }catch(Exception ignored){ + + } + } + requestLog.setStatusCode(String.valueOf(statusCode)); + webhookLogService.saveWebhookLog(requestLog); + } + wrappedResponse.copyBodyToResponse(); + } + } + + private String checkResponseErrors(HttpServletRequest request) { + String errorMessage = ""; + Object exceptionVal = request.getAttribute("AUTH_EXCEPTION"); + errorMessage = exceptionVal!=null?exceptionVal.toString():""; + if(StringUtils.isBlank(errorMessage)){ + exceptionVal = request.getAttribute(DispatcherServlet.EXCEPTION_ATTRIBUTE); + errorMessage = exceptionVal!=null?exceptionVal.toString():""; + } + return errorMessage; + } + + private WebhookLog createRequestLog(HttpServletRequest request, LocalDateTime startTime) { + WebhookLog log = new WebhookLog(); + log.setRequestMethod(request.getMethod()); + log.setUrl(getFullUrl(request)); + log.setRequestTime(startTime); + log.setHeader(getHeadersAsString(request)); + String messageId = request.getHeader("Ac-Events-Message-Id"); + String propertyId = request.getHeader("Ac-Property-Id"); + String eventType = request.getHeader("Ac-Events-Type"); + log.setMessageId(messageId); + log.setEventType(eventType); + log.setPropertyId(propertyId); + log.setSuccess(false); + if (request instanceof ContentCachingRequestWrapper wrappedRequest) { + byte[] content = wrappedRequest.getContentAsByteArray(); + if (content.length > 0) { + try { + String body = new String(content, wrappedRequest.getCharacterEncoding()); + log.setRequestBody(body); + }catch(Exception ignored){ + log.setRequestBody(new String(content)); + } + } + } + return log; + } + + private String getFullUrl(HttpServletRequest request) { + String scheme = request.getScheme(); + String serverName = request.getServerName(); + int serverPort = request.getServerPort(); + String contextPath = request.getContextPath(); + String servletPath = request.getServletPath(); + String pathInfo = request.getPathInfo(); + String queryString = request.getQueryString(); + + StringBuilder url = new StringBuilder(); + url.append(scheme).append("://").append(serverName); + if ((scheme.equals("http") && serverPort != 80) || + (scheme.equals("https") && serverPort != 443)) { + url.append(":").append(serverPort); + } + url.append(contextPath).append(servletPath); + if (pathInfo != null) { + url.append(pathInfo); + } + if (StringUtils.isNotBlank(queryString)) { + url.append("?").append(queryString); + } + return url.toString(); + } + + private String getResponseContent(HttpServletResponse response) { + String responseBody=""; + if (response instanceof ContentCachingResponseWrapper wrappedResponse) { + byte[] content = wrappedResponse.getContentAsByteArray(); + if (content.length > 0) { + try { + responseBody = new String(content, wrappedResponse.getCharacterEncoding()); + }catch(Exception ignored){ + responseBody = new String(content); + } + } + } + return responseBody; + } + + private String getHeadersAsString(HttpServletRequest request) { + StringBuilder headers = new StringBuilder(); + request.getHeaderNames().asIterator().forEachRemaining(headerName -> { + headers.append(headerName).append(": ").append(request.getHeader(headerName)).append("; "); + }); + return headers.toString(); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/models/ApiGatewayLog.java b/src/main/java/com/mgm/mcrm/apigateway/logging/models/ApiGatewayLog.java new file mode 100644 index 0000000..48ea9d7 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/models/ApiGatewayLog.java @@ -0,0 +1,43 @@ +package com.mgm.mcrm.apigateway.logging.models; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@Entity +@Table(name="api_gateway_logs") +public class ApiGatewayLog { + + @Id + @Column(name = "api_log_id",nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long apiLogId; + @Column(name = "url") + private String url; + @Column(name = "category") + private String category; + @Column(name = "request_method") + private String requestMethod; + @Column(name = "request_dtm") + private LocalDateTime requestTime; + @Column(name = "response_dtm") + private LocalDateTime responseTime; + @Column(name = "header") + private String header=""; + @Column(name = "request_body") + private String requestBody=""; + @Column(name = "request_source") + private String requestSource; + @Column(name = "response_body") + private String responseBody=""; + @Column(name = "success_flag") + private boolean successFlag=false; + @Column(name = "status_code") + private String statusCode=""; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/models/WebhookLog.java b/src/main/java/com/mgm/mcrm/apigateway/logging/models/WebhookLog.java new file mode 100644 index 0000000..19bfb59 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/models/WebhookLog.java @@ -0,0 +1,46 @@ +package com.mgm.mcrm.apigateway.logging.models; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@Entity +@Table(name="webhook_logs") +public class WebhookLog { + + @Id + @Column(name = "webhook_log_id",nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long webhookLogId; + @Column(name = "message_id") + private String messageId; + @Column(name = "property_id") + private String propertyId; + @Column(name = "event_type") + private String eventType; + @Column(name = "url") + private String url; + @Column(name = "request_method") + private String requestMethod; + @Column(name = "request_time") + private LocalDateTime requestTime; + @Column(name = "response_time") + private LocalDateTime responseTime; + @Column(name = "header") + private String header=""; + @Column(name = "request_body") + private String requestBody=""; + @Column(name = "response_body") + private String responseBody=""; + @Column(name = "success") + private Boolean success; + @Column(name = "status_code") + private String statusCode=""; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/ApiGatewayLogDao.java b/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/ApiGatewayLogDao.java new file mode 100644 index 0000000..8e758e3 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/ApiGatewayLogDao.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.logging.repositories; + +import com.mgm.mcrm.apigateway.logging.models.ApiGatewayLog; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ApiGatewayLogDao extends JpaRepository { + + Page findAll(Specification listSpec, Pageable pageable); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/WebhookLogDao.java b/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/WebhookLogDao.java new file mode 100644 index 0000000..761c829 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/WebhookLogDao.java @@ -0,0 +1,16 @@ +package com.mgm.mcrm.apigateway.logging.repositories; + +import com.mgm.mcrm.apigateway.logging.models.WebhookLog; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface WebhookLogDao extends JpaRepository { + + Page findAll(Specification listSpec, Pageable pageable); + + int countByMessageId(String messageId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/services/ApiGatewayLogService.java b/src/main/java/com/mgm/mcrm/apigateway/logging/services/ApiGatewayLogService.java new file mode 100644 index 0000000..e474e62 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/services/ApiGatewayLogService.java @@ -0,0 +1,29 @@ +package com.mgm.mcrm.apigateway.logging.services; + +import com.mgm.mcrm.apigateway.logging.models.ApiGatewayLog; +import com.mgm.mcrm.apigateway.logging.repositories.ApiGatewayLogDao; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ApiGatewayLogService { + private final Logger logger = LoggerFactory.getLogger(ApiGatewayLogService.class); + + private final ApiGatewayLogDao apiGatewayLogDao; + @Async + public void saveApiGatewayLog(ApiGatewayLog requestLog) { + try { + if (requestLog.getRequestBody() == null) { + requestLog.setRequestBody(""); + } + apiGatewayLogDao.save(requestLog); + } catch (Exception e) { + logger.error("Failed to save api gateway log to database with error:{}", e.getMessage()); + } + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/services/WebhookLogService.java b/src/main/java/com/mgm/mcrm/apigateway/logging/services/WebhookLogService.java new file mode 100644 index 0000000..0f5cbeb --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/services/WebhookLogService.java @@ -0,0 +1,34 @@ +package com.mgm.mcrm.apigateway.logging.services; + +import com.mgm.mcrm.apigateway.logging.models.WebhookLog; +import com.mgm.mcrm.apigateway.logging.repositories.WebhookLogDao; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +public class WebhookLogService { + private final Logger logger = LoggerFactory.getLogger(WebhookLogService.class); + + private final WebhookLogDao webhookLogDao; + + @Async + public void saveWebhookLog(WebhookLog requestLog) { + try { + if (requestLog.getRequestBody() == null) { + requestLog.setRequestBody(""); + } + webhookLogDao.save(requestLog); + } catch (Exception e) { + logger.error("Failed to save webhook log to database with error:{}", e.getMessage()); + } + } + + public boolean isMessageDuplicated(String messageId) { + return webhookLogDao.countByMessageId(messageId)>0; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/notification/controllers/NotificationController.java b/src/main/java/com/mgm/mcrm/apigateway/notification/controllers/NotificationController.java new file mode 100644 index 0000000..f2f040d --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/notification/controllers/NotificationController.java @@ -0,0 +1,72 @@ +package com.mgm.mcrm.apigateway.notification.controllers; + +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.common.dtos.config.EmailProxySetting; +import com.mgm.mcrm.apigateway.notification.dtos.EmailProxyRequest; +import com.mgm.mcrm.apigateway.notification.dtos.SendEmailRequest; +import com.mgm.mcrm.apigateway.notification.services.EmailNotificationService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Notification", description = "APIs to Send notifications") +@RestController +@RequestMapping("/api/notification") +public class NotificationController { + private final Logger logger = LoggerFactory.getLogger(NotificationController.class); + @Resource + private EmailNotificationService emailNotificationService; + @Autowired + private EmailProxySetting emailProxySetting; + + @PostMapping(value = "/send_email", produces = "application/json;charset=UTF-8") + public ResponseEntity handleSendEmailRequest( + @RequestBody(required = true) SendEmailRequest requestData, + HttpServletRequest request + ){ + + String appId = request.getHeader("application-id"); + String securityKey = request.getHeader("authorization"); + + BaseApiResponse result = emailNotificationService.sendEmailByRequest(requestData,emailProxySetting); + return ResponseEntity.status(HttpStatus.OK).body(result); + } + + /** + * 接收后端A转发过来的邮件请求,字段名与 SendEmailRequest 不同,这里做转换。 + * showTemp 为 HTML 正文,优先于 content;有 cc 时按群发模式发送。 + */ + @PostMapping(value = "/proxy/send_email", produces = "application/json;charset=UTF-8") + public ResponseEntity handleSendEmailProxyRequest( + @RequestBody(required = true) EmailProxyRequest requestData, + HttpServletRequest request + ) { + logger.info("handleSendEmailProxyRequest start, id={}, subject={}, to={}, cc={}, sender={}", + requestData.getId(), requestData.getSubject(), requestData.getTo(), + requestData.getCc(), requestData.getSender()); + SendEmailRequest sendEmailRequest = new SendEmailRequest(); + sendEmailRequest.setSendTo(requestData.getTo()); + sendEmailRequest.setCc(requestData.getCc()); + sendEmailRequest.setSubject(requestData.getSubject()); + String bodyContent = StringUtils.isNotBlank(requestData.getShowTemp()) + ? requestData.getShowTemp() + : requestData.getContent(); + sendEmailRequest.setBodyContent(bodyContent); + sendEmailRequest.setContentType("HTML"); + sendEmailRequest.setSender(requestData.getSender()); + // 有 cc 时用群发模式(false),让 to + cc 在同一封邮件里 + sendEmailRequest.setSendSeparately(StringUtils.isBlank(requestData.getCc())); + + BaseApiResponse result = emailNotificationService.sendEmailByRequest(sendEmailRequest, emailProxySetting); + logger.info("handleSendEmailProxyRequest end, id={}, success={}, statusCode={}, message={}", + requestData.getId(), result.isSuccess(), result.getStatusCode(), result.getMessage()); + return ResponseEntity.status(HttpStatus.OK).body(result); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/notification/dtos/EmailProxyRequest.java b/src/main/java/com/mgm/mcrm/apigateway/notification/dtos/EmailProxyRequest.java new file mode 100644 index 0000000..972d10f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/notification/dtos/EmailProxyRequest.java @@ -0,0 +1,29 @@ +package com.mgm.mcrm.apigateway.notification.dtos; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 后端A转发过来的发送邮件参数。 + * showTemp 为 HTML 正文,content 为纯文本备用正文。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class EmailProxyRequest { + private int id; + private String name; + private int type; + private String field; + private String bookIds; + private int propertyId; + private String subject; + private String content; + private String showTemp; + private String sender; + private String to; + private String cc; + private String createdBy; + private String updatedBy; + private String createdAt; + private String updatedAt; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/notification/dtos/SendEmailRequest.java b/src/main/java/com/mgm/mcrm/apigateway/notification/dtos/SendEmailRequest.java new file mode 100644 index 0000000..4413dce --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/notification/dtos/SendEmailRequest.java @@ -0,0 +1,19 @@ +package com.mgm.mcrm.apigateway.notification.dtos; + +import lombok.Data; + +@Data +public class SendEmailRequest { + private String sendTo; + private String cc; + private String contentType="TEXT"; + private String contentCharset="UTF-8"; + private String subject; + private String bodyContent; + private String fileName; + private String dataContent; + private String sender; + private String senderPassword; + private boolean sendSeparately=true; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/notification/services/EmailNotificationService.java b/src/main/java/com/mgm/mcrm/apigateway/notification/services/EmailNotificationService.java new file mode 100644 index 0000000..51cbfe9 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/notification/services/EmailNotificationService.java @@ -0,0 +1,102 @@ +package com.mgm.mcrm.apigateway.notification.services; + +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.common.dtos.config.EmailProxySetting; +import com.mgm.mcrm.apigateway.notification.dtos.SendEmailRequest; +import jakarta.mail.Session; +import jakarta.mail.internet.MimeMessage; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +@Service +public class EmailNotificationService { + private final Logger logger = LoggerFactory.getLogger(EmailNotificationService.class); + + public BaseApiResponse sendEmailByRequest(SendEmailRequest requestData, EmailProxySetting emailConfig) { + BaseApiResponse result = new BaseApiResponse(); + try { + JavaMailSenderImpl emailSender = new JavaMailSenderImpl(); + emailSender.setHost(emailConfig.getSmtpServer()); + emailSender.setPort(emailConfig.getSmtpPort()); + try{ + Charset.forName(requestData.getContentCharset()); + emailSender.setDefaultEncoding(requestData.getContentCharset()); + }catch(Exception ignored){ + emailSender.setDefaultEncoding("UTF-8"); + } + if(StringUtils.isNotBlank(requestData.getSender()) && StringUtils.isNotBlank(requestData.getSenderPassword())){ + emailSender.setUsername(requestData.getSender()); + emailSender.setPassword(requestData.getSenderPassword()); + }else{ + emailSender.setUsername(emailConfig.getUsername()); + emailSender.setPassword(emailConfig.getPassword()); + } + Properties props = new Properties(); + props.put("mail.smtp.auth", emailConfig.isAuth()); + props.put("mail.smtp.starttls.enable", emailConfig.isStarttls()); + Session session = Session.getInstance(props); + emailSender.setSession(session); + MimeMessage mail = emailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mail, true); + helper.setSubject(requestData.getSubject()==null?"":requestData.getSubject()); + helper.setText(requestData.getBodyContent()==null?"":requestData.getBodyContent(), "HTML".equalsIgnoreCase(requestData.getContentType())); + String fromAddress = StringUtils.isNotBlank(requestData.getSender()) ? requestData.getSender() : emailConfig.getUsername(); + if (StringUtils.isNotBlank(fromAddress)) { + helper.setFrom(fromAddress); + } + if (StringUtils.isNotBlank(requestData.getCc())) { + List ccList = new ArrayList<>(); + for (String addr : requestData.getCc().split("[,;]")) { + if (StringUtils.isNotBlank(addr)) { + ccList.add(addr.trim()); + } + } + if (!ccList.isEmpty()) { + helper.setCc(ccList.toArray(new String[0])); + } + } + String sendToList = StringUtils.isNotBlank(requestData.getSendTo())? requestData.getSendTo() : emailConfig.getDefaultSendTo(); + String[] toAddresses = sendToList.split("[,;]"); + if(requestData.isSendSeparately()){ + for (String address : toAddresses) { + helper.setTo(address); + try { + emailSender.send(mail); + result.setSuccess(true); + result.setStatusCode(200); + } catch (Exception err) { + result.setMessage("Send Email to ["+address+"] in separate mode failed:"+err.getMessage()); + result.setSuccess(false); + result.setStatusCode(-1); + } + } + }else{ + helper.setTo(toAddresses); + try { + emailSender.send(mail); + result.setSuccess(true); + result.setStatusCode(200); + } catch (Exception err) { + result.setMessage("Send Email to ["+sendToList+"] in batch mode failed:"+err.getMessage()); + result.setSuccess(false); + result.setStatusCode(-1); + } + } + + }catch(Exception err){ + result.setSuccess(false); + result.setMessage("Send email failed with error:"+err.getMessage()); + result.setStatusCode(-1); + } + return result; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/player/services/PlayerService.java b/src/main/java/com/mgm/mcrm/apigateway/player/services/PlayerService.java new file mode 100644 index 0000000..0b4b8bd --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/player/services/PlayerService.java @@ -0,0 +1,103 @@ +package com.mgm.mcrm.apigateway.player.services; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONReader; +import com.mgm.mcrm.apigateway.appsetting.services.SysSettingService; +import com.mgm.mcrm.apigateway.common.dtos.HttpResult; +import com.mgm.mcrm.apigateway.common.dtos.ServiceResult; +import com.mgm.mcrm.apigateway.esb.common.dtos.mgmapi.PlayerProfileApiResult; +import com.mgm.mcrm.apigateway.esb.common.services.MGMApiService; +import com.mgm.mcrm.apigateway.reservation.services.OperaDBBookingService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Service +public class PlayerService { + + @Resource + private MGMApiService mgmApiService; + + @Resource + private SysSettingService sysSettingService; + + @Resource + private OperaDBBookingService operaDBBookingService; + + + public ServiceResult findWebPlayerProfileByPlayerId(String playerId) { + ServiceResult result = new ServiceResult(); + HttpResult httpResult; + try { + httpResult = mgmApiService.invokeGetAllPlayerProfile(playerId); + if (httpResult.getStatusCode() != 200) { + result.setSuccess(false); + result.setErrorMessage("Invoke find player profile Failed:[" + httpResult.getStatusCode() + "] " + httpResult.getErrorMessage()); + return result; + } + } catch (Exception err) { + result.setSuccess(false); + result.setErrorMessage("Invoke find player profile API error:" + err.getMessage()); + return result; + } + + PlayerProfileApiResult resultObj; + try { + resultObj = JSONObject.parseObject( + httpResult.getResponseBody(), + PlayerProfileApiResult.class, + JSONReader.Feature.FieldBased + ); + + if (resultObj == null || resultObj.getStatusCode() == null) { + result.setSuccess(false); + result.setErrorMessage("Invalid return data"); + return result; + } + + } catch (Exception err) { + result.setSuccess(false); + result.setErrorMessage("Parse return data failed:" + err.getMessage()); + return result; + } + + String resultCode = resultObj.getStatusCode(); + if (!"OK".equals(resultCode)) { + result.setSuccess(false); + result.setErrorMessage(resultObj.getStatusDescription()); + return result; + } + + if (StringUtils.isNotEmpty(resultObj.getPlayerId())) { + resultObj.setName(resultObj.getEnglishName()); + this.supplementProfileNameIds(resultObj); + result.setSuccess(true); + result.setReturnData(resultObj); + } else { + result.setSuccess(false); + result.setErrorMessage("Player not found"); + } + return result; + } + + private void supplementProfileNameIds(PlayerProfileApiResult profile) { + boolean isMock = sysSettingService.isMockReplicate(); + if (isMock) { + List lists = new ArrayList<>(); + lists.add(710825); + profile.setNameIds(lists); + } else { + List lists = operaDBBookingService.getOperaNameIDByPlayerID(profile.getPlayerId()); + List nameList = new ArrayList<>(); + if (lists != null && !lists.isEmpty()) { + nameList.add(Integer.valueOf(lists.getFirst())); + } + profile.setNameIds(nameList); + } + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/proxy/config/WebClientConfig.java b/src/main/java/com/mgm/mcrm/apigateway/proxy/config/WebClientConfig.java new file mode 100644 index 0000000..c0bf39c --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/proxy/config/WebClientConfig.java @@ -0,0 +1,273 @@ +package com.mgm.mcrm.apigateway.proxy.config; + +import com.mgm.mcrm.apigateway.common.dtos.config.DaylightApiSetting; +import com.mgm.mcrm.apigateway.common.dtos.config.EsbApiSetting; +import com.mgm.mcrm.apigateway.common.dtos.config.OperaApiSetting; +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; +import reactor.netty.transport.ProxyProvider; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +@Configuration +public class WebClientConfig { + + @Value("${mcrm.proxy.max-connection-pool:100}") + private int maxConnectionPool; + + @Value("${mcrm.proxy.connect-timeout:10}") + private int connectTimeout; + + @Value("${mcrm.proxy.read-timeout:10}") + private int readTimeout; + + private final int maxMemoryCacheM = 30; + + @Autowired + private DaylightApiSetting daylightApiSetting; + @Autowired + private EsbApiSetting esbApiSetting; + @Autowired + private OperaApiSetting operaApiSetting; + @Autowired + private SslBundles sslBundles; + + private final Logger logger = LoggerFactory.getLogger(WebClientConfig.class); + + + + @Bean + public WebClient daylightWebClient() { + ConnectionProvider provider = ConnectionProvider.builder("proxy-pool") + .maxConnections(maxConnectionPool) + .pendingAcquireTimeout(Duration.ofSeconds(60)) + .maxIdleTime(Duration.ofSeconds(30)) + .build(); + + HttpClient httpClient = HttpClient.create(provider) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout*1000) + .responseTimeout(Duration.ofSeconds(readTimeout)) + .secure(spec -> { + try { + spec.sslContext(this.createDaylightSslContent()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .doOnConnected(conn -> { + logger.info("Daylight Connected using proxy: {}", conn.channel().remoteAddress()); + conn.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.SECONDS)) + .addHandlerLast(new WriteTimeoutHandler(readTimeout, TimeUnit.SECONDS)); + }) + .doOnRequest((request, connection) -> + logger.info("Daylight Request via proxy: {}", request) + ); + if(StringUtils.isNotBlank(daylightApiSetting.getProxyHost())){ + httpClient = httpClient.proxy(this::initDaylightProxy); + } + + return WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .codecs(configurer -> { + configurer.defaultCodecs().maxInMemorySize(maxMemoryCacheM * 1024 * 1024); + }) + .filter(retryFilter()) + .build(); + } + + @Bean + public WebClient esbWebClient() { + ConnectionProvider provider = ConnectionProvider.builder("proxy-pool") + .maxConnections(maxConnectionPool) + .pendingAcquireTimeout(Duration.ofSeconds(60)) + .maxIdleTime(Duration.ofSeconds(30)) + .build(); + + HttpClient httpClient = HttpClient.create(provider) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout*1000) + .responseTimeout(Duration.ofSeconds(readTimeout)) + .secure(spec -> { + try { + spec.sslContext(this.createEsbSslContent()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .doOnConnected(conn -> { + logger.info("ESB Connected using proxy: {}", conn.channel().remoteAddress()); + conn.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.SECONDS)) + .addHandlerLast(new WriteTimeoutHandler(readTimeout, TimeUnit.SECONDS)); + }) + .doOnRequest((request, connection) -> + logger.info("ESB Request via proxy: {}", request) + ); + if(StringUtils.isNotBlank(esbApiSetting.getProxyHost())){ + httpClient = httpClient.proxy(this::initEsbProxy); + } + + return WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .codecs(configurer -> { + configurer.defaultCodecs().maxInMemorySize(maxMemoryCacheM * 1024 * 1024); + }) + .filter(retryFilter()) + .build(); + } + + @Bean + public WebClient operaWebClient() { + ConnectionProvider provider = ConnectionProvider.builder("proxy-pool") + .maxConnections(maxConnectionPool) + .pendingAcquireTimeout(Duration.ofSeconds(60)) + .maxIdleTime(Duration.ofSeconds(30)) + .build(); + + HttpClient httpClient = HttpClient.create(provider) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout*1000) + .responseTimeout(Duration.ofSeconds(readTimeout)) + .secure(spec -> { + try { + spec.sslContext(this.createOperaSslContent()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .doOnConnected(conn -> { + logger.info("Opera Connected using proxy: {}", conn.channel().remoteAddress()); + conn.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.SECONDS)) + .addHandlerLast(new WriteTimeoutHandler(readTimeout, TimeUnit.SECONDS)); + }) + .doOnRequest((request, connection) -> + logger.info("Opera Request via proxy: {}", request) + ); + if(StringUtils.isNotBlank(operaApiSetting.getProxyHost())){ + httpClient = httpClient.proxy(this::initOperaProxy); + } + + return WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .codecs(configurer -> { + configurer.defaultCodecs().maxInMemorySize(maxMemoryCacheM * 1024 * 1024); + }) + .filter(retryFilter()) + .build(); + } + // retry filter + private ExchangeFilterFunction retryFilter() { + return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { + if (clientResponse.statusCode().is5xxServerError()) { + return clientResponse.mutate() + .build() + .bodyToMono(String.class) + .flatMap(body -> { + return Mono.error(new RuntimeException("Server error: " + clientResponse.statusCode())); + }); + } + return Mono.just(clientResponse); + }); + } + //-- Daylight init + private SslContext createDaylightSslContent() throws SSLException { + if(StringUtils.isBlank(daylightApiSetting.getSslBundleName())){ + return this.createNoVerifySslContext(); + }else{ + return this.createSslContextByConfig(daylightApiSetting.getSslBundleName()); + } + } + + private void initDaylightProxy(ProxyProvider.TypeSpec proxy){ + this.setProxySettingByConfig( + proxy, + daylightApiSetting.getProxyHost(), + daylightApiSetting.getProxyPort(), + daylightApiSetting.getProxyUserName(), + daylightApiSetting.getProxyPassword() + ); + } + //-- Opera init + private SslContext createOperaSslContent() throws SSLException { + if(StringUtils.isBlank(operaApiSetting.getSslBundleName())){ + return this.createNoVerifySslContext(); + }else{ + return this.createSslContextByConfig(operaApiSetting.getSslBundleName()); + } + } + + private void initOperaProxy(ProxyProvider.TypeSpec proxy){ + this.setProxySettingByConfig( + proxy, + operaApiSetting.getProxyHost(), + operaApiSetting.getProxyPort(), + operaApiSetting.getProxyUserName(), + operaApiSetting.getProxyPassword() + ); + } + //-- Esb init + private SslContext createEsbSslContent() throws SSLException { + if(StringUtils.isBlank(esbApiSetting.getSslBundleName())){ + return this.createNoVerifySslContext(); + }else{ + return this.createSslContextByConfig(esbApiSetting.getSslBundleName()); + } + } + + private void initEsbProxy(ProxyProvider.TypeSpec proxy){ + this.setProxySettingByConfig( + proxy, + esbApiSetting.getProxyHost(), + esbApiSetting.getProxyPort(), + esbApiSetting.getProxyUserName(), + esbApiSetting.getProxyPassword() + ); + } + + //---------- + private void setProxySettingByConfig(ProxyProvider.TypeSpec proxy, String proxyHost, int proxyPort, String authUserName, String authPassword){ + if (StringUtils.isNotBlank(authUserName) && StringUtils.isNotBlank(authPassword)) { + proxy.type(ProxyProvider.Proxy.HTTP) + .host(proxyHost) + .port(proxyPort) + .username(authUserName) + .password(s->authPassword); + }else{ + proxy.type(ProxyProvider.Proxy.HTTP) + .host(proxyHost) + .port(proxyPort); + } + } + + private SslContext createNoVerifySslContext() throws SSLException { + return SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); + } + + private SslContext createSslContextByConfig(String sslBundleName) throws SSLException { + SslBundle sslBundle = sslBundles.getBundle(sslBundleName); + SSLContext netSslContext = sslBundle.createSslContext(); + return SslContextBuilder.forClient() + .sslContextProvider(netSslContext.getProvider()) + .build(); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/proxy/controllers/WebProxyController.java b/src/main/java/com/mgm/mcrm/apigateway/proxy/controllers/WebProxyController.java new file mode 100644 index 0000000..fabfb8e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/proxy/controllers/WebProxyController.java @@ -0,0 +1,173 @@ +package com.mgm.mcrm.apigateway.proxy.controllers; + +import com.mgm.mcrm.apigateway.appsetting.services.SysSettingService; +import com.mgm.mcrm.apigateway.common.constants.SysCommon; +import com.mgm.mcrm.apigateway.common.dtos.config.DaylightApiSetting; +import com.mgm.mcrm.apigateway.common.dtos.config.EsbApiSetting; +import com.mgm.mcrm.apigateway.common.dtos.config.OperaApiSetting; +import com.mgm.mcrm.apigateway.daylight.common.services.DaylightTokenService; +import com.mgm.mcrm.apigateway.daylight.common.dtos.TokenResponse; +import com.mgm.mcrm.apigateway.esb.common.dtos.EsbToken; +import com.mgm.mcrm.apigateway.esb.common.services.EsbTokenService; +import com.mgm.mcrm.apigateway.proxy.exception.HttpProxyErrorHandler; +import com.mgm.mcrm.apigateway.proxy.exception.SoapProxyErrorHandler; +import com.mgm.mcrm.apigateway.proxy.services.WebProxyService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.time.Duration; +import java.util.*; + +@Tag(name = "Proxy", description = "Controller for API proxy") +@RestController +public class WebProxyController { + private final Logger logger = LoggerFactory.getLogger(WebProxyController.class); + private final HttpProxyErrorHandler httpErrorHandler= new HttpProxyErrorHandler(); + private final SoapProxyErrorHandler soapProxyErrorHandler = new SoapProxyErrorHandler(); + @Resource + private WebProxyService webProxyService; + @Resource + private DaylightTokenService daylightTokenService; + @Resource + private EsbTokenService esbTokenService; + @Autowired + private DaylightApiSetting daylightApiSetting; + @Autowired + private EsbApiSetting esbApiSetting; + @Autowired + private OperaApiSetting operaApiSetting; + + @Resource + private SysSettingService sysSettingService; + + private volatile boolean cachedIsMock = true; + private volatile long lastMockCheckTime = 0; + private static final long MOCK_CHECK_INTERVAL_MS = 30000; + + @RequestMapping("/api/daylight/proxy/**") + public ResponseEntity invokeDaylightApiProxy(HttpServletRequest request,HttpServletResponse response) throws Exception { + String apiUrl = daylightApiSetting.getServerUrl() + request.getRequestURI().replace("/api/daylight/proxy", ""); + HttpHeaders headers = this.extractHeaders(request); + TokenResponse daylightApiToken = daylightTokenService.getApiAccessToken(); + if(daylightApiToken==null || StringUtils.isBlank(daylightApiToken.getAccessToken())){ + throw new Exception("Invoke daylight token failed"); + } + headers.set("authorization","Bearer "+daylightApiToken.getAccessToken()); + return webProxyService.invokeRequest( + WebProxyService.DAYLIGHT_API, + HttpMethod.valueOf(request.getMethod()), + apiUrl, + request.getQueryString(), + headers, + getRequestBody(request), + Duration.ofSeconds(daylightApiSetting.getApiTimeout()), + httpErrorHandler + ).block(); + } + @RequestMapping("/api/esb/proxy/**") + public ResponseEntity invokeEsbApiProxy(HttpServletRequest request) throws Exception { + logger.info("invokeEsbApiProxy start, method={}, uri={}, query={}", + request.getMethod(), request.getRequestURI(), request.getQueryString()); + String apiUrl = esbApiSetting.getServerUrl() + request.getRequestURI().replace("/api/esb/proxy", ""); + logger.info("invokeEsbApiProxy target apiUrl={}", apiUrl); + HttpHeaders headers = this.extractHeaders(request); + + if (isMockFromDb()) { + return webProxyService.invokeMockRequest( + WebProxyService.ESB_API, + HttpMethod.valueOf(request.getMethod()), + apiUrl, + request.getQueryString(), + extractHeaders(request), + getRequestBody(request), + Duration.ofSeconds(esbApiSetting.getApiRequestTimeout()), + httpErrorHandler + ).block(); + } + + EsbToken esbToken = esbTokenService.getApiAccessToken(); + if(esbToken==null || StringUtils.isBlank(esbToken.getAccessToken())){ + logger.error("invokeEsbApiProxy failed, ESB token is null or blank, uri={}", request.getRequestURI()); + throw new Exception("Invoke ESB token failed"); + } + headers.set("authorization", esbToken.getAccessToken()); + + ResponseEntity response = webProxyService.invokeRequest( + WebProxyService.ESB_API, + HttpMethod.valueOf(request.getMethod()), + apiUrl, + request.getQueryString(), + extractHeaders(request), + getRequestBody(request), + Duration.ofSeconds(esbApiSetting.getApiRequestTimeout()), + httpErrorHandler + ).block(); + logger.info("invokeEsbApiProxy end, uri={}, status={}", + request.getRequestURI(), response == null ? null : response.getStatusCode()); + return response; + } + + @RequestMapping("/api/opera/proxy/**") + public ResponseEntity invokeOperaApiProxy(HttpServletRequest request) throws Exception { + String apiUrl = operaApiSetting.getServerUrl() + request.getRequestURI().replace("/api/opera/proxy", ""); + return webProxyService.invokeRequest( + WebProxyService.OPERA_API, + HttpMethod.valueOf(request.getMethod()), + apiUrl, + request.getQueryString(), + extractHeaders(request), + getRequestBody(request), + Duration.ofSeconds(operaApiSetting.getTimeout()), + soapProxyErrorHandler + ).block(); + } + + private HttpHeaders extractHeaders(HttpServletRequest request) { + HttpHeaders headers = new HttpHeaders(); + Collections.list(request.getHeaderNames()).forEach(name -> { + if (!name.equalsIgnoreCase("host") && + !name.equalsIgnoreCase("content-length") && + !name.equalsIgnoreCase("application-id") && + !name.equalsIgnoreCase("authorization") + ) { + headers.add(name, request.getHeader(name)); + } + }); + return headers; + } + + private byte[] getRequestBody(HttpServletRequest request) { + try { + return request.getInputStream().readAllBytes(); + } catch (IOException e) { + logger.error("Failed to read request body of request[{}]",request.getRequestURI(), e); + return new byte[0]; + } + } + + private boolean isMockFromDb() { + long now = System.currentTimeMillis(); + if (now - lastMockCheckTime > MOCK_CHECK_INTERVAL_MS) { + try { + var setting = sysSettingService.getSysSettingByKey("APP_MOCK"); + cachedIsMock = setting != null && "1".equals(setting.getSettingValue()); + } catch (Exception e) { + logger.error("Failed to read APP_MOCK from sys_settings, using cached value", e); + } + lastMockCheckTime = now; + } + return cachedIsMock; + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/HttpProxyErrorHandler.java b/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/HttpProxyErrorHandler.java new file mode 100644 index 0000000..ec5f9a8 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/HttpProxyErrorHandler.java @@ -0,0 +1,16 @@ +package com.mgm.mcrm.apigateway.proxy.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; + +public class HttpProxyErrorHandler implements ProxyErrorHandler { + @Override + public Mono> handleError(Throwable error) { + return Mono.just( + ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(("Forward proxy error: [" + error.getClass().getName() + "] " + error.getMessage()).getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/ProxyErrorHandler.java b/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/ProxyErrorHandler.java new file mode 100644 index 0000000..0c8494c --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/ProxyErrorHandler.java @@ -0,0 +1,9 @@ +package com.mgm.mcrm.apigateway.proxy.exception; + +import org.springframework.http.ResponseEntity; +import reactor.core.publisher.Mono; + +@FunctionalInterface +public interface ProxyErrorHandler { + Mono> handleError(Throwable error); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/SoapProxyErrorHandler.java b/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/SoapProxyErrorHandler.java new file mode 100644 index 0000000..3754b74 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/proxy/exception/SoapProxyErrorHandler.java @@ -0,0 +1,24 @@ +package com.mgm.mcrm.apigateway.proxy.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import reactor.core.publisher.Mono; + +import java.nio.charset.Charset; + +public class SoapProxyErrorHandler implements ProxyErrorHandler { + @Override + public Mono> handleError(Throwable error) { + String faultXml = String.format( + "" + + "soap:Server" + + "%s" + + "", + error.getMessage() + ); + + return Mono.just( + ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(faultXml.getBytes(Charset.forName("GBK")))); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/proxy/services/WebProxyService.java b/src/main/java/com/mgm/mcrm/apigateway/proxy/services/WebProxyService.java new file mode 100644 index 0000000..a83da35 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/proxy/services/WebProxyService.java @@ -0,0 +1,167 @@ +package com.mgm.mcrm.apigateway.proxy.services; + +import com.mgm.mcrm.apigateway.common.utils.LogUtil; +import com.mgm.mcrm.apigateway.common.utils.mockup.HttpUtilMockup; +import com.mgm.mcrm.apigateway.proxy.exception.HttpProxyErrorHandler; +import com.mgm.mcrm.apigateway.proxy.exception.ProxyErrorHandler; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; + +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.concurrent.ForkJoinPool; + +@RequiredArgsConstructor +@Service +public class WebProxyService { + public final static String DAYLIGHT_API="DAYLIGHT_API"; + public final static String ESB_API="ESB_API"; + public final static String OPERA_API="OPERA_API"; + private final WebClient daylightWebClient; + private final WebClient esbWebClient; + private final WebClient operaWebClient; + private final Logger logger = LoggerFactory.getLogger(WebProxyService.class); + + public Mono> invokeRequest( + String apiType, + HttpMethod method, + String apiUrl, + String requestQuery, + HttpHeaders headers, + Object body, + Duration timeout, + ProxyErrorHandler errorHandler) throws Exception { + + WebClient targetWebClient = null; + + if(DAYLIGHT_API.equalsIgnoreCase(apiType)){ + targetWebClient=daylightWebClient; + }else if(ESB_API.equalsIgnoreCase(apiType)){ + targetWebClient=esbWebClient; + }else if(OPERA_API.equalsIgnoreCase(apiType)){ + targetWebClient=operaWebClient; + }else{ + throw new Exception("Unknown request API type of "+apiType); + } + + return targetWebClient.method(method) + .uri(uriBuilder -> this.buildRequestUri(apiUrl,requestQuery) + ) + .headers(httpHeaders -> filterAndSetHeaders(httpHeaders, headers)) + .bodyValue(body) + .exchangeToMono(response -> + response.bodyToMono(byte[].class) + .defaultIfEmpty(new byte[0]) + .map(responseBody ->{ + HttpHeaders responseHeaders = new HttpHeaders(); + this.filterIgnoreResponseHeaders(responseHeaders,response.headers().asHttpHeaders()); + return ResponseEntity.status(response.statusCode()) + .headers(extractHeaders(response)) + .body(responseBody); + }) + ) + .doOnError(e -> logger.error("Forward request failed: [{}] {}", e.getClass().getName(), e.getMessage(), e)) + .onErrorResume(errorHandler::handleError + ).timeout(timeout); + } + + private HttpHeaders extractHeaders(ClientResponse response) { + HttpHeaders headers = new HttpHeaders(); + response.headers().asHttpHeaders().forEach((name, values) -> { + if (!name.equalsIgnoreCase("transfer-encoding")) { + headers.addAll(name, values); + } + }); + return headers; + } + + private void filterAndSetHeaders(HttpHeaders targetHeaders, HttpHeaders sourceHeaders) { + sourceHeaders.forEach((key, values) -> { + if (!isIgnoreRequestHeader(key)) { + targetHeaders.addAll(key, values); + } + }); + } + + private boolean isIgnoreRequestHeader(String headerName) { + String lowerHeader = headerName.toLowerCase(); + return lowerHeader.equals("host") || + lowerHeader.equals("content-length") || + lowerHeader.equals("transfer-encoding") || + lowerHeader.equals("connection"); + } + + private void filterIgnoreResponseHeaders(HttpHeaders targetHeaders, HttpHeaders sourceHeaders) { + sourceHeaders.forEach((key, values) -> { + if (!isIgnoreResponseHeader(key)) { + targetHeaders.addAll(key, values); + } + }); + } + + private boolean isIgnoreResponseHeader(String headerName) { + String lowerHeader = headerName.toLowerCase(); + return lowerHeader.equals("transfer-encoding") || + lowerHeader.equals("content-encoding"); + } + + private URI buildRequestUri(String apiUrl, String requestQuery) { + UriComponents components = UriComponentsBuilder.fromUriString(apiUrl) + .replaceQuery(requestQuery) + .build(false); + return components.toUri(); + } + + public Mono> invokeMockRequest( + String apiType, + HttpMethod method, + String apiUrl, + String requestQuery, + HttpHeaders headers, + Object body, + Duration timeout, + ProxyErrorHandler errorHandler) throws Exception { + + String fullUrl = apiUrl; + if (requestQuery != null && !requestQuery.isEmpty()) { + fullUrl = apiUrl + "?" + requestQuery; + } + + String requestContent = ""; + if (body != null) { + if (body instanceof byte[]) { + requestContent = new String((byte[]) body, StandardCharsets.UTF_8); + } else { + requestContent = body.toString(); + } + } + + String mockResponse = HttpUtilMockup.processMockRequest(fullUrl, requestContent); + if (mockResponse == null) { + mockResponse = ""; + } + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.setContentType(MediaType.APPLICATION_JSON); + + ResponseEntity responseEntity = ResponseEntity.status(200) + .headers(responseHeaders) + .body(mockResponse.getBytes(StandardCharsets.UTF_8)); + + return Mono.just(responseEntity); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/reservation/models/Bookings.java b/src/main/java/com/mgm/mcrm/apigateway/reservation/models/Bookings.java new file mode 100644 index 0000000..23ca4d4 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/reservation/models/Bookings.java @@ -0,0 +1,219 @@ +package com.mgm.mcrm.apigateway.reservation.models; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.persistence.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name="bookings") +public class Bookings implements Serializable { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="revision_of") + private Long revisionOf; + @Column(name="confirmation_no") + private String confirmationNo; + @Column(name="resv_id") + private String resvId; + @Column(name="last_opera_status") + private String lastOperaStatus; + @Column(name="player_id") + private String playerId; + @Column(name="member_title") + private String memberTitle; + @Column(name="member_first_name") + private String memberFirstName; + @Column(name="member_last_name") + private String memberLastName; + @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") + @Column(name="checkin_date") + private LocalDateTime checkinDate; + @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") + @Column(name="checkout_date") + private LocalDateTime checkoutDate; + @Column(name="property_id") + private Long propertyId; + @Column(name="room_type_id") + private Long roomTypeId; + @Column(name="dl_profile_id") + private String dlProfileId; + @Column(name="notify_language") + private String notifyLanguage; + @Column(name="payment_amount") + private Double paymentAmount; + @Column(name="opera_total") + private Double operaTotal; + @Column(name="comment") + private String comment; + @Column(name="dl_block_id") + private String dlBlockId; + @Column(name="rate_plan_code") + private String ratePlanCode; + @Column(name="chain_code") + private String chainCode; + @Column(name="hotel_code") + private String hotelCode; + @Column(name="room_type_code") + private String roomTypeCode; + @Column(name="promotion_code") + private String promotionCode; + @Column(name="dl_guarantee_type_id") + private String dlGuaranteeTypeId; + @Column(name="payment_type") + private String paymentType; + @Column(name="market_code") + private String marketCode; + @Column(name="dl_source_id") + private String dlSourceId; + @Column(name="cancelled_by") + private String cancelledBy; + @Column(name="cancelled_at") + private LocalDateTime cancelledAt; + @Column(name="section_id") + private Long sectionId; + @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") + @Column(name="created_at") + private LocalDateTime createdAt; + @Column(name="created_by") + private String createdBy; + @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") + @Column(name="updated_at") + private LocalDateTime updatedAt; + @Column(name="updated_by") + private String updatedBy; + @Column(name="approve_status") + private String approveStatus; + @Column(name="remark") + private String remark; + @Column(name="book_type") + private String bookType; + @Column(name="block") + private String block; + @Column(name="num_of_night") + private Integer numOfNight; + @Column(name="section_name") + private String sectionName; + @Column(name="booker") + private String booker; + @Column(name="msc_remark") + private String mscRemark; + @Column(name="approval_remark") + private String approvalRemark; + @Column(name="room") + private String room; + @Column(name="sharer") + private String sharer; + @Column(name="feature_code") + private String featureCode; + @Column(name="item_code") + private String itemCode; + @Column(name="bed_type_master_group_id") + private Integer bedTypeMasterGroupId; + @Column(name="bed_type_name") + private String bedTypeName; + @Column(name="created_by_name") + private String createdByName; + @Column(name="room_id") + private Integer roomId; + @Column(name = "source_code") + private String sourceCode; + @Column(name="block_id") + private Integer blockId; + @Column(name="try_time") + private Integer tryTime; + @Column(name="error_message") + private String errorMessage; + @Column(name="failed_at") + private LocalDateTime failedAt; + @Column(name="approved_by") + private String approvedBy; + @Column(name="approved_at") + private LocalDateTime approvedAt; + @Column(name="member") + private String member; + @Column(name="booking_type_id") + private Integer bookingTypeId; + @Column(name="group_id") + private String groupId; + @Column(name="date_list") + private String dateList; + @Column(name="failed_room_type") + private String failedRoomType; + @Column(name="process_status") + private Integer processStatus; + @Column(name="waitlist_id") + private Long waitlistId; + @Column(name="segment_id") + private Long segmentId; + @Column(name="other_room_status_id") + private Long otherRoomStatusId; + @Column(name="other_follow_up_status_id") + private Long otherFollowUpStatusId; + @Column(name="approved_id") + private Long approvedId; + @Column(name="requested_co_date") + private LocalDateTime requestedCoDate; + @Column(name="eta") + private String eta; + @Column(name="change_checkin_date") + private String changeCheckinDate; + @Column(name="change_checkout_date") + private String changeCheckoutDate; + @Column(name="dl_channel_id") + private String dlChannelId; + @Column(name="dl_account_id") + private String dlAccountId; + @Column(name="dl_reservation_id") + private String dlReservationId; + @Column(name="opera_profile_id") + private Long operaProfileId; + + @Column(name = "category") + private String category; + + @Column(name = "dl_purchase_element_id") + private String dlPurchaseElementId; + + @Column(name = "misc_charge") + private String miscCharge; + + @Column(name = "guarantee_type") + private String guaranteeType; + + @Column(name = "opera_comment") + private String operaComment; + + @Column(name = "opera_comment_id") + private Long operaCommentId; + + @Column(name = "dl_external_no") + private String dlExternalNo; + + @Transient + private String dlPropertyId; + @Transient + private String dlRoomTypeId; + //-- For export + @Transient + private String approveStatusName; + @Transient + private String bookTypeName; + @Transient + private int numOfRooms=1; + @Transient + private String otherRoomStatusName; + @Transient + private String otherFollowUpStatusName; + + @Transient + private String dlMarketSegmentId; + + @Transient + private String dlRatePlanId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/reservation/models/BookingsView.java b/src/main/java/com/mgm/mcrm/apigateway/reservation/models/BookingsView.java new file mode 100644 index 0000000..65fa371 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/reservation/models/BookingsView.java @@ -0,0 +1,263 @@ +package com.mgm.mcrm.apigateway.reservation.models; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.Immutable; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@Data +@Entity +@Table(name="view_booking") +@Immutable +@NamedEntityGraphs({ + @NamedEntityGraph( + name = "graph.BookingsView.fulldetails", + attributeNodes = { + + } + ) +}) +public class BookingsView implements Serializable { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="revision_of") + private Long revisionOf; + @Column(name="confirmation_no") + private String confirmationNo; + @Column(name="resv_id") + private String resvId; + @Column(name="last_opera_status") + private String lastOperaStatus; + @Column(name="player_id") + private String playerId; + @Column(name="member_title") + private String memberTitle; + @Column(name="member_first_name") + private String memberFirstName; + @Column(name="member_last_name") + private String memberLastName; + @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") + @Column(name="checkin_date") + private Date checkinDate; + @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") + @Column(name="checkout_date") + private Date checkoutDate; + @Column(name="property_id") + private Long propertyId; + @Column(name="room_type_id") + private Long roomTypeId; + @Column(name="dl_profile_id") + private Long dlProfileId; + @Column(name="notify_language") + private String notifyLanguage; + @Column(name="payment_amount") + private Double paymentAmount; + @Column(name="opera_total") + private Double operaTotal; + @Column(name="comment") + private String comment; + @Column(name="rate_plan_code") + private String ratePlanCode; + @Column(name="chain_code") + private String chainCode; + @Column(name="hotel_code") + private String hotelCode; + @Column(name="room_type_code") + private String roomTypeCode; + @Column(name="promotion_code") + private String promotionCode; + @Column(name="dl_guarantee_type_id") + private String dlGuaranteeTypeId; + @Column(name="payment_type") + private String paymentType; + @Column(name="market_code") + private String marketCode; + @Column(name="dl_source_id") + private String dlSourceId; + @Column(name="cancelled_by") + private String cancelledBy; + @Column(name="cancelled_at") + private Date cancelledAt; + @Column(name="section_id") + private Long sectionId; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") + @Column(name="created_at") + private Date createdAt; + @Column(name="created_by") + private String createdBy; + @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") + @Column(name="updated_at") + private Date updatedAt; + @Column(name="updated_by") + private String updatedBy; + @Column(name="approve_status") + private String approveStatus; + @Column(name="remark") + private String remark; + @Column(name="book_type") + private String bookType; + @Column(name="dl_block_id") + private String dlBlockId; + @Column(name="num_of_night") + private Integer numOfNight; + @Column(name="section_name") + private String sectionName; + @Column(name="booker") + private String booker; + @Column(name="msc_remark") + private String mscRemark; + @Column(name="approval_remark") + private String approvalRemark; + @Column(name="room") + private String room; + @Column(name="sharer") + private String sharer; + @Column(name="feature_code") + private String featureCode; + @Column(name="item_code") + private String itemCode; + @Column(name="group_id") + private String groupId; + @Column(name="property_name") + private String propertyName; + @Column(name="block_id") + private Long blockId; + @Column(name="room_id") + private Long roomId; + @Column(name="property_category") + private String propertyCategory; + @Column(name="bed_type_master_group_id") + private Integer bedTypeMasterGroupId; + @Column(name="bed_type_name") + private String bedTypeName; + @Column(name="created_by_name") + private String createdByName; + @Column(name="date_list") + private String dateLists; + @Column(name="room_cn") + private String roomCN; + @Column(name="error_message") + private String errorMessage; + @Column(name="try_time") + private Integer tryTime; + @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") + @Column(name="failed_at") + private Date failedAt; + @Column(name="booking_type_name") + private String bookingTypeName; + @Column(name="room_category") + private String roomCategory; + @Column(name="for_booking") + private Integer roomForBooking; + @Column(name="for_waitlist") + private Integer roomForWaitlist; + @Column(name="segment_id") + private Long segmentId; + @Column(name="requested_co_date") + private Date requestedCoDate; + @Column(name="tier") + private String tier; + @Column(name="gaming_id") + private Long gamingId; + @Column(name="overlap_booking") + private String overlapBooking; + @Column(name="ma_booking") + private String maBooking; + @Column(name="r3_theo_win") + private String r3TheoWin; + @Column(name="r3_tpd") + private String r3Tpd; + @Column(name="r3_actual_win") + private String r3ActualWin; + @Column(name="r6_theo_win") + private String r6TheoWin; + @Column(name="r6_tpd") + private String r6Tpd; + @Column(name="r6_actual_win") + private String r6ActualWin; + @Column(name="r12_theo_win") + private String r12TheoWin; + @Column(name="r12_tpd") + private String r12Tpd; + @Column(name="r12_actual_win") + private String r12ActualWin; + @Column(name="final_segment") + private String finalSegment; + @Column(name="trip_theo_profit") + private String tripTheoProfit; + @Column(name="daily_theo_profit") + private String dailyTheoProfit; + @Column(name="player_type") + private String playerType; + @Column(name="ranking_by_player") + private String rankingByPlayer; + @Column(name="booker_team") + private String bookerTeam; + @Column(name="current_trip_theo_win_amount") + private String currentTripTheoWinAmount; + @Column(name="last_2day_theo_win_amount") + private String last2dayTheoWinAmount; + @Column(name="approved_id") + private Long approvedId; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") + @Column(name="approved_at") + private Date approvedAt; + @Column(name="approved_by") + private String approvedBy; + @Column(name="property_email_name") + private String propertyEmailName; + @Column(name="room_email_name") + private String roomEmailName; + @Column(name="eta") + private String eta; + @Column(name="change_checkin_date") + private String changeCheckinDate; + @Column(name="change_checkout_date") + private String changeCheckoutDate; + @Column(name="dl_channel_id") + private String dlChannelId; + @Column(name="dl_account_id") + private String dlAccountId; + @Column(name="dl_reservation_id") + private String dlReservationId; + + + @Transient + private String status; + @Transient + private Long pendingRevisionId; + @Transient + private String specialName; + @Transient + private List dateList; + @Transient + private JSONArray sharerList; + @Transient + private String memberTier; + @Transient + private String sharerName; + @Transient + private String expDate; + @Transient + private String profieExpDate; + @Transient + private String roomNo; + @Transient + private Boolean isShare; + @Transient + private Boolean isQueue; + @Transient + private String dlPropertyId; + @Transient + private String dlRoomTypeId; + + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/reservation/repositories/BookingDao.java b/src/main/java/com/mgm/mcrm/apigateway/reservation/repositories/BookingDao.java new file mode 100644 index 0000000..1f3e486 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/reservation/repositories/BookingDao.java @@ -0,0 +1,16 @@ +package com.mgm.mcrm.apigateway.reservation.repositories; + +import com.mgm.mcrm.apigateway.reservation.models.Bookings; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + + +public interface BookingDao extends JpaRepository { + + List findByConfirmationNo(String confirmationNo); + List findByDlReservationId(String dlReservationId); + + Bookings findFirstByDlProfileIdAndPlayerIdIsNotNull(String dlProfileId); +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/reservation/services/BookingService.java b/src/main/java/com/mgm/mcrm/apigateway/reservation/services/BookingService.java new file mode 100644 index 0000000..5f63aab --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/reservation/services/BookingService.java @@ -0,0 +1,380 @@ +package com.mgm.mcrm.apigateway.reservation.services; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; + +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgBlockCodes; +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgDaylightView; +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgMarketCodes; +import com.mgm.mcrm.apigateway.basesetting.mapping.models.CfgRateCodes; +import com.mgm.mcrm.apigateway.basesetting.mapping.repositories.BlockCodeDao; +import com.mgm.mcrm.apigateway.basesetting.mapping.repositories.DaylightViewDao; +import com.mgm.mcrm.apigateway.basesetting.mapping.repositories.MarketCodeDao; +import com.mgm.mcrm.apigateway.basesetting.mapping.repositories.RateCodeDao; +import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; +import com.mgm.mcrm.apigateway.basesetting.property.repositories.PropertyDao; +import com.mgm.mcrm.apigateway.basesetting.room.models.CfgRoomTypes; +import com.mgm.mcrm.apigateway.basesetting.room.models.CfgRooms; +import com.mgm.mcrm.apigateway.basesetting.room.models.CfgRoomsRoomTypes; +import com.mgm.mcrm.apigateway.basesetting.room.repositories.RoomDao; +import com.mgm.mcrm.apigateway.basesetting.room.repositories.RoomRoomTypeDao; +import com.mgm.mcrm.apigateway.basesetting.room.repositories.RoomTypeDao; +import com.mgm.mcrm.apigateway.common.dtos.ServiceResult; +import com.mgm.mcrm.apigateway.common.utils.DateTimeUtil; +import com.mgm.mcrm.apigateway.common.utils.LogUtil; +import com.mgm.mcrm.apigateway.esb.common.dtos.mgmapi.PlayerProfileApiResult; +import com.mgm.mcrm.apigateway.esb.common.dtos.mgmapi.UserInfoApiRecord; +import com.mgm.mcrm.apigateway.esb.player.services.MgmUserService; +import com.mgm.mcrm.apigateway.player.services.PlayerService; +import com.mgm.mcrm.apigateway.reservation.models.Bookings; +import com.mgm.mcrm.apigateway.reservation.repositories.BookingDao; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + + +@Service +public class BookingService { + + @Resource + private BookingDao bookingDao; + @Resource + private RoomTypeDao roomTypeDao; + @Resource + private RoomRoomTypeDao roomRoomTypeDao; + @Resource + private RoomDao roomDao; + @Resource + private MarketCodeDao marketCodeDao; + @Resource + private RateCodeDao rateCodeDao; + @Resource + private DaylightViewDao daylightViewDao; + @Resource + private PropertyDao propertyDao; + @Resource + private PlayerService playerService; + @Resource + private MgmUserService mgmUserService; + @Resource + private BlockCodeDao blockCodeDao; + + public void processDaylightUpdateBooking(Bookings daylightBooking) throws Exception { + if(daylightBooking!=null){ + String dlReservationId=daylightBooking.getDlReservationId(); + String confirmNo = daylightBooking.getConfirmationNo(); + if(StringUtils.isNotBlank(confirmNo) + && StringUtils.isNotBlank(dlReservationId) + ){ + List dbBookings = bookingDao.findByDlReservationId(dlReservationId); + if (CollectionUtils.isEmpty(dbBookings)) { + dbBookings = bookingDao.findByConfirmationNo(confirmNo); + } + + if (CollectionUtils.isEmpty(dbBookings)) { + TimeUnit.SECONDS.sleep(5); + dbBookings = bookingDao.findByDlReservationId(dlReservationId); + + if (CollectionUtils.isEmpty(dbBookings)) { + dbBookings = bookingDao.findByConfirmationNo(confirmNo); + } + } + + Bookings dbBooking=null; + if(dbBookings!=null && !dbBookings.isEmpty()) { + for(Bookings booking:dbBookings){ + if("N".equalsIgnoreCase(booking.getApproveStatus())) { + dbBooking = booking; + break; + } + } + } + if(dbBooking!=null){ + this.mergeDaylightToDbBooking(daylightBooking,dbBooking); + }else{ + dbBooking = this.createBookingFromDaylight(daylightBooking); + } + bookingDao.save(dbBooking); + } + } + } + + public Bookings findOneByDlProfileId(String dlProfileId) { + return bookingDao.findFirstByDlProfileIdAndPlayerIdIsNotNull(dlProfileId); + } + + // 第一条是daylight返回过来的booking,第二条是本地数据库的booking + public void mergeDaylightToDbBooking(Bookings daylightBooking, Bookings updateBooking) throws Exception { + String nights = DateTimeUtil.getDiffDate(daylightBooking.getCheckinDate(), daylightBooking.getCheckoutDate(), "day"); + daylightBooking.setNumOfNight(Integer.valueOf(nights)); + if(!"PN".equals(updateBooking.getApproveStatus())) { + updateBooking.setCheckinDate(daylightBooking.getCheckinDate()); + updateBooking.setCheckoutDate(daylightBooking.getCheckoutDate()); + updateBooking.setNumOfNight(daylightBooking.getNumOfNight()); + } + updateBooking.setConfirmationNo(daylightBooking.getConfirmationNo()); + updateBooking.setHotelCode(daylightBooking.getHotelCode()); + updateBooking.setDlProfileId(daylightBooking.getDlProfileId()); + updateBooking.setDlSourceId(daylightBooking.getDlSourceId()); + updateBooking.setDlGuaranteeTypeId(daylightBooking.getDlGuaranteeTypeId()); + updateBooking.setDlChannelId(daylightBooking.getDlChannelId()); + updateBooking.setDlAccountId(daylightBooking.getDlAccountId()); + updateBooking.setDlBlockId(daylightBooking.getDlBlockId()); + updateBooking.setDlExternalNo(daylightBooking.getDlExternalNo()); +// updateBooking.setGroupId(daylightBooking.getGroupId()); + updateBooking.setDlReservationId(daylightBooking.getDlReservationId()); + // 通过 dlRoomTypeId 查找 room type(与 createBookingFromDaylight 流程保持一致) + CfgRoomTypes daylightRoomType = roomTypeDao.findByDlRoomTypeId(daylightBooking.getDlRoomTypeId()); + if(daylightRoomType!=null) { + updateBooking.setRoomTypeId(daylightRoomType.getId()); + updateBooking.setRoomTypeCode(daylightRoomType.getCode()); + List roomroomType = roomRoomTypeDao.findByRoomTypeId(daylightRoomType.getId()); + if(!roomroomType.isEmpty()) { + for(CfgRoomsRoomTypes r:roomroomType) { + CfgRooms room = roomDao.findById(r.getRoomId()).orElse(null); + if(room!=null) { + if(room.getPropertyId().equals(updateBooking.getPropertyId())) { + //updateBooking.setRoom(room.getName()); + updateBooking.setRoomId(Math.toIntExact(room.getId())); + break; + } + } + } + } + } + //get Booking Type + List operaMarketCode = marketCodeDao.findByCode(daylightBooking.getMarketCode()); + List operRateCode = rateCodeDao.findByCode(daylightBooking.getRatePlanCode()); + if(!operaMarketCode.isEmpty() && !operRateCode.isEmpty()) { + Long operaMarketCodeId = operaMarketCode.getFirst().getId(); + Long operaRateCodeId = operRateCode.getFirst().getId(); + List bookingTypeOperas = daylightViewDao.findByMarketCodeIdAndRateCodeId(operaMarketCodeId,operaRateCodeId); + if(!bookingTypeOperas.isEmpty()) { + Long operaBookingTypeId = bookingTypeOperas.getFirst().getBookingTypeId(); + updateBooking.setBookingTypeId(Math.toIntExact(operaBookingTypeId)); + } + } + + if("CANCELED".equals(daylightBooking.getLastOperaStatus())) { + updateBooking.setApproveStatus("C"); + daylightBooking.setApproveStatus("C"); + }else { + if("C".equals(updateBooking.getApproveStatus())||daylightBooking.getConfirmationNo()!=null) { + updateBooking.setApproveStatus("N"); + daylightBooking.setApproveStatus("N"); + } + } + updateBooking.setLastOperaStatus(daylightBooking.getLastOperaStatus()); + + //get player info + if(updateBooking.getMember()==null||StringUtils.isEmpty(updateBooking.getMember())) { + if(!StringUtils.isEmpty(updateBooking.getPlayerId())) { + ServiceResult serviceResult = playerService.findWebPlayerProfileByPlayerId(updateBooking.getPlayerId()); + if(serviceResult.isSuccess()) { + PlayerProfileApiResult profile = (PlayerProfileApiResult) serviceResult.getReturnData(); + updateBooking.setMemberLastName(profile.getName()); + updateBooking.setMemberTitle(profile.getSalutation()); + updateBooking.setMember(JSONObject.toJSONString(profile)); + } + } + } + //get user info + boolean getUser = false; + String booker = StringUtils.trim(updateBooking.getBooker()); + if(booker==null||StringUtils.isEmpty(booker)) getUser=true; + if(daylightBooking.getCreatedBy()!=null && !daylightBooking.getCreatedBy().equals(updateBooking.getCreatedBy())) { + updateBooking.setCreatedBy(daylightBooking.getCreatedBy()); + getUser=true; + } + if(getUser) { + if(!StringUtils.isEmpty(updateBooking.getCreatedBy())) { + ServiceResult userInfoRes = mgmUserService.findUserInfoByUserId(updateBooking.getCreatedBy()); + if(userInfoRes.isSuccess()) { + UserInfoApiRecord userInfo = (UserInfoApiRecord)userInfoRes.getReturnData(); + updateBooking.setBooker(userInfo.getUserName()); + updateBooking.setCreatedByName(userInfo.getUserName()); + } + } + } + updateBooking.setUpdatedAt(LocalDateTime.now()); + } + + public Bookings createBookingFromDaylight(Bookings daylightBooking) throws Exception { + String dlReservationId = daylightBooking.getDlReservationId(); + + List dbBookings = bookingDao.findByDlReservationId(dlReservationId); + + if (!dbBookings.isEmpty()) { + Bookings dbBooking = null; + for (Bookings booking : dbBookings) { + if ("N".equalsIgnoreCase(booking.getApproveStatus())) { + dbBooking = booking; + break; + } + } + + if (dbBooking != null) { + mergeDaylightToDbBooking(daylightBooking, dbBooking); + return dbBooking; + } + } + + Bookings dbBooking = new Bookings(); + CfgProperties daylightProperty = propertyDao.findByDlPropertyId(daylightBooking.getDlPropertyId()); + CfgRoomTypes daylightRoomType =roomTypeDao.findByDlRoomTypeId(daylightBooking.getDlRoomTypeId()); + CfgRooms daylightRoom = new CfgRooms(); + //set room type + if(daylightRoomType!=null) { + List roomroomTypes = roomRoomTypeDao.findByRoomTypeId(daylightRoomType.getId()); + if(!roomroomTypes.isEmpty()) { + for(CfgRoomsRoomTypes r:roomroomTypes) { + CfgRooms room = roomDao.findById(r.getRoomId()).orElse(null); + if(room!=null) { + if(room.getPropertyId().equals(daylightProperty.getId())) { + daylightRoom = room; + break; + } + } + } + } + + } + String operaMemberTitle = ""; + String operaMemberFirstName = ""; + String operaMemberLastName = ""; + + CfgBlockCodes byDlBlockId = blockCodeDao.findByDlBlockId(daylightBooking.getDlBlockId()); + if (byDlBlockId != null) { + dbBooking.setBlockId(Math.toIntExact(byDlBlockId.getId())); + } + + dbBooking.setCheckinDate(daylightBooking.getCheckinDate()); + dbBooking.setCheckoutDate(daylightBooking.getCheckoutDate()); + dbBooking.setPropertyId(daylightProperty.getId()); + dbBooking.setNotifyLanguage("en"); + //dbBooking.setRoom(daylightRoom.getId()!=null?daylightRoom.getName():""); + dbBooking.setRoomId(daylightRoom.getId()!=null?Integer.parseInt(String.valueOf(daylightRoom.getId())):-1); + dbBooking.setRoomTypeId(daylightRoomType!=null?daylightRoomType.getId():-1); + dbBooking.setRatePlanCode(daylightBooking.getRatePlanCode()); + dbBooking.setChainCode(daylightProperty.getCode()); + dbBooking.setHotelCode(daylightBooking.getHotelCode()); + + dbBooking.setRoomTypeCode(daylightRoomType!=null?daylightRoomType.getCode():null); + dbBooking.setRoomId(daylightRoom.getId()!=null?daylightRoom.getId().intValue():-1); + dbBooking.setCreatedBy(daylightBooking.getCreatedBy()); + dbBooking.setCreatedByName(daylightBooking.getCreatedBy()); + + dbBooking.setMemberTitle(operaMemberTitle); + dbBooking.setMemberFirstName(operaMemberFirstName); + dbBooking.setMemberLastName(operaMemberLastName); + + dbBooking.setMarketCode(daylightBooking.getMarketCode()); + dbBooking.setDlSourceId(daylightBooking.getDlSourceId()); + dbBooking.setDlProfileId(daylightBooking.getDlProfileId()); + dbBooking.setDlGuaranteeTypeId(daylightBooking.getDlGuaranteeTypeId()); + dbBooking.setDlChannelId(daylightBooking.getDlChannelId()); + dbBooking.setDlAccountId(daylightBooking.getDlAccountId()); + dbBooking.setDlBlockId(daylightBooking.getDlBlockId()); + dbBooking.setDlExternalNo(daylightBooking.getDlExternalNo()); + dbBooking.setGroupId(daylightBooking.getGroupId()); + + dbBooking.setPaymentAmount(daylightBooking.getPaymentAmount()); +// book.setGuaranteeType("CGTD"); + dbBooking.setPaymentType(null); +// book.setOperaProfileId(operaBooking.getOperaProfileId()); + dbBooking.setApproveStatus("N"); + dbBooking.setCategory("DAYLIGHT"); + + dbBooking.setCreatedAt(DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss")); + dbBooking.setUpdatedAt(DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss")); +// book.setTeamId(objTeam.getId()); + dbBooking.setLastOperaStatus(daylightBooking.getLastOperaStatus()); + dbBooking.setConfirmationNo(daylightBooking.getConfirmationNo()); + dbBooking.setResvId(daylightBooking.getResvId()); + dbBooking.setSharer(new JSONArray().toJSONString()); + dbBooking.setBookType("0"); + dbBooking.setDlReservationId(daylightBooking.getDlReservationId()); + + //get player info + if(!StringUtils.isEmpty(dbBooking.getPlayerId())) { + ServiceResult serviceResult = playerService.findWebPlayerProfileByPlayerId(dbBooking.getPlayerId()); + if(serviceResult.isSuccess()) { + PlayerProfileApiResult profile = (PlayerProfileApiResult) serviceResult.getReturnData(); + dbBooking.setMemberLastName(profile.getName()); + dbBooking.setMemberTitle(profile.getSalutation()); + dbBooking.setMember(JSONObject.toJSONString(profile)); + } + } + //get user info + // 这个判断不会走进去,因为createdBy必定是null,因为eventData没有这个字段 by Mauve 20260806 + if(!StringUtils.isEmpty(dbBooking.getCreatedBy())) { + ServiceResult userInfoRes = mgmUserService.findUserInfoByUserId(dbBooking.getCreatedBy()); + if(userInfoRes.isSuccess()) { + UserInfoApiRecord userInfo = (UserInfoApiRecord)userInfoRes.getReturnData(); + + dbBooking.setBooker(userInfo.getUserName()); + dbBooking.setCreatedByName(userInfo.getUserName()); + dbBooking.setSectionId(userInfo.getSectionId()); + + CfgMarketCodes byDlMarketSegmentId = + marketCodeDao.findByDlMarketSegmentId(daylightBooking.getDlMarketSegmentId()); + + LogUtil.info(BookingService.class, "sectionId=" + userInfo.getSectionId() + + ", marketCodeId=" + byDlMarketSegmentId.getId() + + ", propertyId=" + daylightProperty.getId() + + ", roomTypeId=" + daylightRoomType.getId()); + + Optional bySectionPropertyRoomTypeOptional = daylightViewDao.findBySectionMarketPropertyRoomType( + userInfo.getSectionId(), + byDlMarketSegmentId.getId(), + daylightProperty.getId(), + daylightRoomType.getId() + ); + + CfgDaylightView bySectionPropertyRoomType = bySectionPropertyRoomTypeOptional.orElse(null); + if (bySectionPropertyRoomType != null) { + dbBooking.setSectionId(bySectionPropertyRoomType.getBookingTypeId()); + } + + LogUtil.info(BookingService.class, "DaylightView=" + JSONObject.toJSONString(bySectionPropertyRoomType)); + } + } + + //get Booking Type + List operaMarketCode = marketCodeDao.findByCode(daylightBooking.getMarketCode()); + List operRateCode = rateCodeDao.findByCode(daylightBooking.getRatePlanCode()); + if(!operaMarketCode.isEmpty() && !operRateCode.isEmpty()) { + Long operaMarketCodeId = operaMarketCode.getFirst().getId(); + Long operaRateCodeId = operRateCode.getFirst().getId(); + List bookingTypeOperas = daylightViewDao.findByMarketCodeIdAndRateCodeId(operaMarketCodeId,operaRateCodeId); + if(!bookingTypeOperas.isEmpty()) { + Long operaBookingTypeId = bookingTypeOperas.getFirst().getBookingTypeId(); + dbBooking.setBookingTypeId(Integer.valueOf(String.valueOf(operaBookingTypeId))); + } + } + // build datelist + int numOfNight = Integer.parseInt(DateTimeUtil.getDiffDate(dbBooking.getCheckinDate(), dbBooking.getCheckoutDate(), "day")); + ArrayList dates = new ArrayList<>(); + ArrayList dateList = new ArrayList<>(); + JSONObject obj = new JSONObject(); + for(int j=0;j getOperaNameIDByPlayerID(String playerId){ + EntityManager entity = this.factory.createEntityManager(); + List lists = new ArrayList(); + try { + StoredProcedureQuery store = entity.createNamedStoredProcedureQuery("getOperaNameIDByPlayerID"); + store.setParameter("player_id", playerId); + lists = store.getResultList(); + entity.close(); + } catch (Exception e) { + entity.close(); + e.printStackTrace(); + } + return lists; + } + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/system/models/SysGroupPermission.java b/src/main/java/com/mgm/mcrm/apigateway/system/models/SysGroupPermission.java new file mode 100644 index 0000000..5ca9978 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/system/models/SysGroupPermission.java @@ -0,0 +1,23 @@ +package com.mgm.mcrm.apigateway.system.models; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@Entity +@Table(name="sys_group_permissions") +@IdClass(SysGroupPermissionKey.class) +public class SysGroupPermission { + //fields in table + @Id + @Column(name = "group_id") + private Long groupId; + @Id + @Column(name="permission_id") + private String permissionId; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/system/models/SysGroupPermissionKey.java b/src/main/java/com/mgm/mcrm/apigateway/system/models/SysGroupPermissionKey.java new file mode 100644 index 0000000..8623dc1 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/system/models/SysGroupPermissionKey.java @@ -0,0 +1,38 @@ +package com.mgm.mcrm.apigateway.system.models; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Objects; + +@Getter +@Setter +@NoArgsConstructor +public class SysGroupPermissionKey implements Serializable{ + + @Serial + private static final long serialVersionUID = 6060166117433738173L; + private Long groupId ; + private String permissionId; + @Override + public boolean equals(Object o) { + if(o instanceof SysGroupPermissionKey) + { + SysGroupPermissionKey key = (SysGroupPermissionKey)o ; + if(Objects.equals(this.groupId, key.getGroupId()) && Objects.equals(this.permissionId, key.getPermissionId())) + { + return true ; + } + } + return false ; + } + + @Override + public int hashCode() { + return 1; + } + +} \ No newline at end of file diff --git a/src/main/java/com/mgm/mcrm/apigateway/system/models/SysUserView.java b/src/main/java/com/mgm/mcrm/apigateway/system/models/SysUserView.java new file mode 100644 index 0000000..8161e7a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/system/models/SysUserView.java @@ -0,0 +1,45 @@ +package com.mgm.mcrm.apigateway.system.models; + +import jakarta.persistence.*; +import lombok.Data; + +import java.util.Date; + +@Data +@Entity +@Table(name="view_sys_user_group") +public class SysUserView { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="login_id") + private String loginId; + @Column(name="display_name") + private String displayName; + @Column(name="password") + private String password; + @Column(name="category") + private Integer category; + @Column(name="status") + private Integer status; + @Column(name="try_times") + private Integer tryTimes; + @Column(name="created_at") + private Date createdAt; + @Column(name="created_by") + private String createdBy; + @Column(name="updated_at") + private Date updatedAt; + @Column(name="updated_by") + private String updatedBy; + @Column(name="group_id") + private Long groupId; + @Column(name="group_name") + private String groupName; + @Column(name="default_property") + private String defaultProperty; + @Transient + private String token; + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/system/repositories/SysGroupPermissionDao.java b/src/main/java/com/mgm/mcrm/apigateway/system/repositories/SysGroupPermissionDao.java new file mode 100644 index 0000000..f40ea4e --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/system/repositories/SysGroupPermissionDao.java @@ -0,0 +1,19 @@ +package com.mgm.mcrm.apigateway.system.repositories; + +import com.mgm.mcrm.apigateway.system.models.SysGroupPermission; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + + +public interface SysGroupPermissionDao extends JpaRepository { + List findAll(Specification spec,Sort sort); + List findAll(Specification spec); + + @Query(value="select * from sys_group_permissions where group_id=?1",nativeQuery=true) + List getAllByGroupId(long groupId); + +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/system/repositories/SysUserViewDao.java b/src/main/java/com/mgm/mcrm/apigateway/system/repositories/SysUserViewDao.java new file mode 100644 index 0000000..17dc6bd --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/system/repositories/SysUserViewDao.java @@ -0,0 +1,14 @@ +package com.mgm.mcrm.apigateway.system.repositories; + +import com.mgm.mcrm.apigateway.system.models.SysUserView; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + + +public interface SysUserViewDao extends JpaRepository { + SysUserView findById(long id); + + @Query(value="select * from view_sys_user_group where upper(login_id) =?1",nativeQuery=true) + SysUserView getByLoginId(String loginId); + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..1a518b3 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,140 @@ +spring: + datasource: + mgm: + driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver + jdbc-url: jdbc:sqlserver://47.238.46.81:1433;databasename=db_mgm_mcrm_daylight;encrypt=false;trustServerCertificate=false;hostNameInCertificate=*;loginTimeout=30 + username: sa + password: 666888@Welcomewilltech + continue-on-error: true + hikari: + minimum-idle: 10 + maximum-pool-size: 50 + idle-timeout: 600000 + connection-timeout: 600000 + max-lifetime: 1800000 + + opera: + driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver + jdbc-url: jdbc:sqlserver://47.238.46.81:1433;databasename=db_mgm_mcrm_daylight;encrypt=false;trustServerCertificate=false;hostNameInCertificate=*;loginTimeout=30 + username: sa + password: 666888@Welcomewilltech + continue-on-error: true + hikari: + minimum-idle: 10 + maximum-pool-size: 50 + idle-timeout: 600000 + connection-timeout: 600000 + max-lifetime: 1800000 + + http: + encoding: + force: true + charset: UTF-8 + enabled: true + messages: + basename: i18n/messages + jpa: + show-sql: false + properties: + hibernate: + default_schema: dbo + format_sql: true + + servlet: + multipart: + max-request-size: 10MB + max-file-size: 10MB + + devtools: + restart: + enabled: false + +#============== App Configuration =============== +mcrm: + proxy: + max-connection-pool: 100 + connect-timeout: 20 + read-timeout: 60 + api-config: + daylight: + proxy-setting: + host: + port: + auth: + username: + password: + ssl-bundle-name: + server-url: https://eu1.api.uat.development.abovecloud.io + request-timeout: 30 + token: + token-url: /connect/token + username: mcrm.mcrm@MGMUAT + password: Shiji@123 + grant-type: password + client-id: 3rd.willtech_technology + client-secret: HJIvAynCSCqvjEe8MC9K + request-timeout: 30 + default-expiry-second: 700 + security: + authorization-data: "[MCRM_APP/securItykeyOfCrmapp87895312],[MCRM_CMS/seCuritykeyOfCrmCms8wetw432]" + opera: + proxy-setting: + host: + port: + auth: + username: + password: + ssl-bundle-name: + server-url: + request-timeout: 30 + security: + authorization-data: "[MCRM_APP/securItykeyOfCrmapp87895312],[MCRM_CMS/seCuritykeyOfCrmCms8wetw432]" + esb: + proxy-setting: + host: + port: + auth: + username: + password: + ssl-bundle-name: + server-url: https://hca.mcmsapoc.com + request-timeout: 30 + token: + token-url: /RequestAccessToken + app-id: crmappid + app-secret: crmsecuritykey + request-timeout: 30 + default-expiry-second: 700 + security: + authorization-data: "[MCRM_APP/securItykeyOfCrmapp87895312],[MCRM_CMS/seCuritykeyOfCrmCms8wetw432]" + notification: + email-sender: + smtp-server: smtp.qq.com + smtp-port: 587 + smtp-auth: true + username: 201249946@qq.com + password: oheejklduyydbife + starttls: true + default-send-to: 201249946@qq.com + security: + authorization-data: "[MCRM_APP/securItykeyOfCrmapp87895312],[MCRM_CMS/seCuritykeyOfCrmCms8wetw432]" +server: + port: 9087 + referrer-policy: origin-when-cross-origin + tomcat: + uri-encoding: UTF-8 + +logging: + config: classpath:logback.xml + + +springdoc: + swagger-ui: + path: /swagger-ui.html + operations-sorter: alpha + tags-sorter: alpha + doc-expansion: none + api-docs: + path: /v3/api-docs + default-consumes-media-type: application/json + default-produces-media-type: application/json \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..caf4dfc --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/i18n/messages_en_US.properties b/src/main/resources/i18n/messages_en_US.properties new file mode 100644 index 0000000..bb655c6 --- /dev/null +++ b/src/main/resources/i18n/messages_en_US.properties @@ -0,0 +1,26 @@ +gamecenter.title=Game List +gamecenter.welcome=Welcome, +gamecenter.will_text=CARD NO.: +gamecenter.button.back=BACK +gamecenter.gamechance=Game Chance(s): + +common.error_message.error_code_200=OK +common.error_message.error_code_401=Authorization failed +common.error_message.error_code_422=Parameter error +common.error_message.error_code_301=Game is unavailable +common.error_message.error_code_302=Game chance has been exhausted +common.error_message.error_code_303=Error result return +common.error_message.error_code_304=Data duplicated +common.error_message.error_code_305=Invalid request +common.error_message.error_code_306=Session timeout, please login and re-enter game +common.error_message.error_code_307=Reward exhausted, game is finished +common.error_message.error_code_701=Request error +common.error_message.error_code_308=Game Session Timeout +common.error_message.error_code_309=No Customer Tier +common.error_message.error_code_310=Game is finished, gamechance returned + +common.no_game_chance=Game chance has been exhausted +common.get_game_chance_error=Get game chance error +common.get_game_chance_request_error=Get game chance request error +common.no_game_available=No Game Available +common.game_session_timeout=Session timeout \ No newline at end of file diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..198a24e --- /dev/null +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -0,0 +1,26 @@ +gamecenter.title=\u6e38\u620f\u4e2d\u5fc3 +gamecenter.welcome=\u6b22\u8fce, +gamecenter.will_text=\u5361\u53f7: +gamecenter.button.back=\u8fd4\u56de +gamecenter.gamechance=\u6e38\u620f\u673a\u4f1a: + +common.error_message.error_code_200=\u6210\u529f +common.error_message.error_code_401=\u8bf7\u6c42\u5b89\u5168\u9a8c\u8bc1\u5931\u8d25 +common.error_message.error_code_422=\u53c2\u6570\u9519\u8bef +common.error_message.error_code_301=\u6e38\u620f\u672a\u5f00\u653e +common.error_message.error_code_302=\u6e38\u620f\u6b21\u6570\u5df2\u7528\u5b8c +common.error_message.error_code_303=\u9519\u8bef\u7684\u8fd4\u56de\u7ed3\u679c +common.error_message.error_code_304=\u6570\u636e\u91cd\u590d +common.error_message.error_code_305=\u975e\u6cd5\u8bf7\u6c42 +common.error_message.error_code_306=\u6e38\u620f\u8d85\u65f6, \u8bf7\u91cd\u65b0\u8fdb\u5165 +common.error_message.error_code_307=\u5956\u9879\u5df2\u6d3e\u53d1\u5b8c\uff0c\u6e38\u620f\u5df2\u7ed3\u675f +common.error_message.error_code_701=\u8bf7\u6c42\u5931\u8d25 +common.error_message.error_code_308=\u6e38\u620f\u8d85\u65f6, \u8bf7\u91cd\u65b0\u8fdb\u5165 +common.error_message.error_code_309=\u7372\u53d6\u6703\u54e1\u7b49\u7d1a\u5931\u6557 +common.error_message.error_code_310=\u6e38\u620f\u5df2\u7ed3\u675f, \u4e0d\u4f1a\u6263\u9664\u6e38\u620f\u673a\u4f1a + +common.no_game_chance=\u6e38\u620f\u6b21\u6570\u5df2\u7528\u5b8c +common.get_game_chance_error=\u83b7\u53d6\u6e38\u620f\u6b21\u6570\u9519\u8bef +common.get_game_chance_request_error=\u83b7\u53d6\u6e38\u620f\u6b21\u6570\u8bf7\u6c42\u9519\u8bef +common.no_game_available=\u6ca1\u6709\u53ef\u53c2\u4e0e\u7684\u6e38\u620f +common.game_session_timeout=\u6e38\u620f\u8d85\u65f6 \ No newline at end of file diff --git a/src/main/resources/i18n/messages_zh_TW.properties b/src/main/resources/i18n/messages_zh_TW.properties new file mode 100644 index 0000000..dd24883 --- /dev/null +++ b/src/main/resources/i18n/messages_zh_TW.properties @@ -0,0 +1,26 @@ +gamecenter.title=\u904a\u6232\u4e2d\u5fc3 +gamecenter.welcome=\u6b61\u8fce, +gamecenter.will_text=\u5361\u865f: +gamecenter.button.back=\u8fd4\u56de +gamecenter.gamechance=\u904a\u6232\u6a5f\u6703: + +common.error_message.error_code_200=\u6210\u529f +common.error_message.error_code_401=\u8acb\u6c42\u5b89\u5168\u9a57\u8b49\u5931\u6557 +common.error_message.error_code_422=\u53c3\u6578\u932f\u8aa4 +common.error_message.error_code_301=\u904a\u6232\u672a\u958b\u653e +common.error_message.error_code_302=\u904a\u6232\u6b21\u6578\u5df2\u7528\u5b8c +common.error_message.error_code_303=\u932f\u8aa4\u7684\u8fd4\u56de\u7d50\u679c +common.error_message.error_code_304=\u6578\u64da\u91cd\u8907 +common.error_message.error_code_305=\u975e\u6cd5\u8acb\u6c42 +common.error_message.error_code_306=\u904a\u6232\u8d85\u6642\uff0c\u8acb\u91cd\u65b0\u9032\u5165 +common.error_message.error_code_307=\u5956\u52b1\u5df2\u6d3e\u767c\u5b8c\uff0c\u904a\u6232\u5df2\u7d50\u675f +common.error_message.error_code_701=\u8acb\u6c42\u932f\u8aa4 +common.error_message.error_code_308=\u904a\u620f\u8d85\u65f6, \u8bf7\u91cd\u65b0\u8fdb\u5165 +common.error_message.error_code_309=\u7372\u53d6\u6703\u54e1\u7b49\u7d1a\u5931\u6557 +common.error_message.error_code_310=\u904a\u6232\u5df2\u7d50\u675f, \u4e0d\u6703\u6263\u9664\u904a\u6232\u6a5f\u6703 + +common.no_game_chance=\u904a\u6232\u6b21\u6578\u5df2\u7528\u5b8c +common.get_game_chance_error=\u7372\u53d6\u904a\u6232\u6b21\u6578\u932f\u8aa4 +common.get_game_chance_request_error=\u7372\u53d6\u904a\u6232\u6b21\u6578\u8acb\u6c42\u932f\u8aa4 +common.no_game_available=\u6c92\u6709\u53ef\u53c3\u8207\u7684\u904a\u6232 +common.game_session_timeout=\u904a\u6232\u8d85\u6642 \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..3545290 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5level] %logger{50} - %msg%n + + + + + + + ${LOG_APP_HOME}/base.%d{yyyy-MM-dd}.log + + + ${LOG_MAX_HISTORY} + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{500} - %msg%n + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mockup/app/getBlock.json b/src/main/resources/mockup/app/getBlock.json new file mode 100644 index 0000000..227939b --- /dev/null +++ b/src/main/resources/mockup/app/getBlock.json @@ -0,0 +1,20 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "blocks":[ + { + "id":1, + "name":"Mass" + }, + { + "id":2, + "name":"IM" + }, + { + "id":3, + "name":"GM" + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getBookingsByNameId.json b/src/main/resources/mockup/app/getBookingsByNameId.json new file mode 100644 index 0000000..c21d4e5 --- /dev/null +++ b/src/main/resources/mockup/app/getBookingsByNameId.json @@ -0,0 +1,283 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "bookings":[ + { + "id":11, + "resort":"Macau", + "confirmationNo":"", + "roomType":"GTO", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"1", + "approveStatus":"WAITLIST" + }, + { + "id":10, + "resort":"Macau", + "confirmationNo":"", + "roomType":"GTO", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"1", + "approveStatus":"WAITLIST" + }, + { + "id":3, + "resort":"Macau", + "confirmationNo":"315586", + "roomType":"KE", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"CHECKED IN", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"", + "approveStatus":"" + }, + { + "id":9, + "resort":"Cotai", + "confirmationNo":"", + "roomType":"TE", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"", + "approveStatus":"PENDING" + }, + { + "id":8, + "resort":"Macau", + "confirmationNo":"315585", + "roomType":"GTO", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"DUE IN", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"", + "approveStatus":"" + }, + { + "id":7, + "resort":"Macau", + "confirmationNo":"315587", + "roomType":"TR", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"DUE IN", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"", + "approveStatus":"" + }, + { + "id":6, + "resort":"Macau", + "confirmationNo":"315589", + "roomType":"GTO", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"DUE IN", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"", + "approveStatus":"" + }, + { + "id":5, + "resort":"Macau", + "confirmationNo":"315590", + "roomType":"GTO", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"DUE IN", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"", + "approveStatus":"" + }, + { + "id":4, + "resort":"Cotai", + "confirmationNo":"315588", + "roomType":"KE", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"DUE OUT", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"", + "groupId":"", + "approveStatus":"" + }, + { + "id":2, + "resort":"Macau", + "confirmationNo":"315583", + "roomType":"GTO", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"CHECKED IN", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"1101236", + "groupId":"2", + "approveStatus":"" + }, + { + "id":1, + "resort":"Macau", + "confirmationNo":"315582", + "roomType":"KE", + "roomTypeDesc":"", + "arrivalDate":"", + "arrivalTime":"", + "departureDate":"", + "departureTime":"", + "resvStatus":"CHECKED IN", + "blockCode":"", + "currentBalance":"", + "operaProfileId":"", + "bookedBy":"", + "authorizer":"", + "resvNameId":"", + "rateCode":"", + "marketSegment":"", + "sourceCode":"", + "room":"", + "sharer":"1101238", + "groupId":"2", + "approveStatus":"" + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getCriteria.json b/src/main/resources/mockup/app/getCriteria.json new file mode 100644 index 0000000..a017f47 --- /dev/null +++ b/src/main/resources/mockup/app/getCriteria.json @@ -0,0 +1,68 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "criterias":[ + { + "id":1, + "propertyId":1, + "roomId":1, + "blockId":1, + "bookDate":"", + "appChannel":100, + "isWaitlist":0, + "noSharer":0 + }, + { + "id":2, + "propertyId":1, + "roomId":1, + "blockId":2, + "bookDate":"", + "appChannel":50, + "isWaitlist":0, + "noSharer":0 + }, + { + "id":3, + "propertyId":1, + "roomId":1, + "blockId":3, + "bookDate":"", + "appChannel":20, + "isWaitlist":0, + "noSharer":0 + }, + { + "id":4, + "propertyId":1, + "roomId":2, + "blockId":1, + "bookDate":"", + "appChannel":100, + "isWaitlist":0, + "noSharer":0 + }, + { + "id":5, + "propertyId":1, + "roomId":2, + "blockId":2, + "bookDate":"", + "appChannel":50, + "isWaitlist":0, + "noSharer":0 + }, + { + "id":6, + "propertyId":1, + "roomId":2, + "blockId":3, + "bookDate":"", + "appChannel":20, + "isWaitlist":0, + "noSharer":0 + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getProperty.json b/src/main/resources/mockup/app/getProperty.json new file mode 100644 index 0000000..8c0eafe --- /dev/null +++ b/src/main/resources/mockup/app/getProperty.json @@ -0,0 +1,37 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "properties":[ + { + "id":1, + "name":"Macau", + "code":"MM", + "hotelCode":"Macau", + "category":"1" + }, + { + "id":2, + "name":"Cotai", + "code":"MC", + "hotelCode":"Cotai", + "category":"1" + }, + { + "id":3, + "name":"MO", + "code":"", + "hotelCode":"MO", + "category":"2" + }, + { + "id":4, + "name":"Asscot", + "code":"", + "hotelCode":"Asscot", + "category":"2" + } + + ] + } +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getRoom.json b/src/main/resources/mockup/app/getRoom.json new file mode 100644 index 0000000..5bdade5 --- /dev/null +++ b/src/main/resources/mockup/app/getRoom.json @@ -0,0 +1,53 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "rooms":[ + { + "id":1, + "propertyId":1, + "name":"Grand Room" + }, + { + "id":2, + "propertyId":1, + "name":"Deluxe" + }, + { + "id":3, + "propertyId":1, + "name":"Studio" + }, + { + "id":4, + "propertyId":1, + "name":"Resort Room" + }, + { + "id":5, + "propertyId":1, + "name":"1 Bedroom Suite" + }, + { + "id":6, + "propertyId":1, + "name":"2 Bedroom Suite" + }, + { + "id":7, + "propertyId":2, + "name":"Grand Room" + }, + { + "id":8, + "propertyId":2, + "name":"Deluxe" + }, + { + "id":9, + "propertyId":2, + "name":"Studio" + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getRoomType.json b/src/main/resources/mockup/app/getRoomType.json new file mode 100644 index 0000000..dd1ad21 --- /dev/null +++ b/src/main/resources/mockup/app/getRoomType.json @@ -0,0 +1,68 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "roomTypes":[ + { + "id":1, + "roomId":1, + "name":"GTO" + }, + { + "id":2, + "roomId":1, + "name":"GKO" + }, + { + "id":3, + "roomId":1, + "name":"GTU" + }, + { + "id":4, + "roomId":1, + "name":"GKU" + }, + { + "id":5, + "roomId":2, + "name":"STO" + }, + { + "id":6, + "roomId":2, + "name":"SKO" + }, + { + "id":7, + "roomId":2, + "name":"STO" + }, + { + "id":8, + "roomId":2, + "name":"SKO" + }, + { + "id":9, + "roomId":7, + "name":"GTO" + }, + { + "id":10, + "roomId":7, + "name":"GKO" + }, + { + "id":11, + "roomId":8, + "name":"STO" + }, + { + "id":12, + "roomId":8, + "name":"SKO" + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getSpecial.json b/src/main/resources/mockup/app/getSpecial.json new file mode 100644 index 0000000..f141870 --- /dev/null +++ b/src/main/resources/mockup/app/getSpecial.json @@ -0,0 +1,28 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "specials":[ + { + "id":1, + "name":"Smoking", + "code":"SM" + }, + { + "id":2, + "name":"Non-Smoking", + "code":"NS" + }, + { + "id":3, + "name":"High Floor", + "code":"HF" + }, + { + "id":4, + "name":"Near Elevator", + "code":"NE" + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoDashboard_1.json b/src/main/resources/mockup/app/getTodoDashboard_1.json new file mode 100644 index 0000000..3fec868 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoDashboard_1.json @@ -0,0 +1,76 @@ +{ + "StatusCode": "OK", + "StatusDescription": "BALABALABALABALADASHBOARD", + "Records": [ + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "Achievement", + "Identifier": 1, + "ItemName": "Team Target", + "Level": 2, + "CurrentValue": 1132045024.0, + "TargetValue": 12054210450.0 + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "Achievement", + "Identifier": 2, + "ItemName": "Individual Target", + "Level": 2, + "CurrentValue": 10000.0, + "TargetValue": 30000.0 + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "KPI", + "Identifier": 1, + "ItemName": "New & Reactive Theo", + "Level": null, + "CurrentValue": 10000.0, + "TargetValue": 30000.0 + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "KPI", + "Identifier": 2, + "ItemName": "Direct offer Redemption Rate", + "Level": null, + "CurrentValue": 0.7, + "TargetValue": 0.8 + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "KPI", + "Identifier": 3, + "ItemName": "Hosted Player Active Rate", + "Level": null, + "CurrentValue": 0.2, + "TargetValue": 0.8 + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "KPI", + "Identifier": 4, + "ItemName": "Event Attendance (w/ gaming action)", + "Level": null, + "CurrentValue": 0.29, + "TargetValue": 0.2 + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "DataAsOf", + "Identifier": 4, + "ItemName": "2025-10-14", + "Level": null, + "CurrentValue": null, + "TargetValue": null + } + ] +} diff --git a/src/main/resources/mockup/app/getTodoDashboard_2.json b/src/main/resources/mockup/app/getTodoDashboard_2.json new file mode 100644 index 0000000..3f4ecf4 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoDashboard_2.json @@ -0,0 +1,87 @@ +{ + "statusCode":"OK", + "statusDescription":"statusDescription Test", + "Records":[{ + "UserId":80002, + "UserSegment":"IM", + "Type":"Achievement", + "Identifier":1, + "Level":2, + "ItemName":"Team Target", + "CurrentValue":70456, + "TargetValue":100000 + }, + { + "UserId":80002, + "UserSegment":"IM", + "Type":"Achievement", + "Identifier":2, + "Level":2, + "ItemName":"Individual Target", + "CurrentValue":43328, + "TargetValue":100000 + }, + { + "UserId":80002, + "UserSegment":"IM", + "Type":"KPI", + "Identifier":3, + "Level":2, + "ItemName":"Reactive Player", + "CurrentValue":5, + "TargetValue":40 + }, + { + "UserId":80002, + "UserSegment":"IM", + "Type":"KPI", + "Identifier":4, + "Level":2, + "ItemName":"Active Primary Program", + "CurrentValue":20, + "TargetValue":100 + }, + { + "UserId":80002, + "UserSegment":"IM", + "Type":"Achievement", + "Identifier":5, + "Level":2, + "ItemName":"MF Turnover", + "CurrentValue":112, + "TargetValue":0 + }, + { + "UserId":80002, + "UserSegment":"IM", + "Type":"Achievement", + "Identifier":6, + "Level":2, + "ItemName":"IM Turnover", + "CurrentValue":8896, + "TargetValue":0 + }, + { + "UserId":80002, + "UserSegment":"IM", + "Type":"Achievement", + "Identifier":7, + "Level":2, + "ItemName":"NFRO Turnover", + "CurrentValue":1123, + "TargetValue":0 + }, + { + "UserId":80002, + "UserSegment":"IM", + "Type":"Achievement", + "Identifier":8, + "Level":2, + "ItemName":"MRI Turnover", + "CurrentValue":3323, + "TargetValue":0 + } + + ] + +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoDashboard_3.json b/src/main/resources/mockup/app/getTodoDashboard_3.json new file mode 100644 index 0000000..9d7abc0 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoDashboard_3.json @@ -0,0 +1,66 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "Achievement", + "Identifier": 1, + "ItemName": "Team Target", + "Level": 2, + "CurrentValue": 12125, + "TargetValue":null + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "Achievement", + "Identifier": 2, + "ItemName": "Individual Target", + "Level": 2, + "CurrentValue": 65353, + "TargetValue":null + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "KPI", + "Identifier": 1, + "ItemName": "New & Reactive Theo", + "Level": null, + "CurrentValue": 97843, + "TargetValue":0 + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "KPI", + "Identifier": 2, + "ItemName": "Direct offer Redemption Rate", + "Level": null, + "CurrentValue": 0.7, + "TargetValue": null + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "KPI", + "Identifier": 3, + "ItemName": "Hosted Player Active Rate", + "Level": null, + "CurrentValue": 0.2, + "TargetValue": null + }, + { + "UserId": 123457, + "UserSegment": "Mass", + "Type": "KPI", + "Identifier": 4, + "ItemName": "Event Attendance (w/ gaming action)", + "Level": null, + "CurrentValue": 4.0, + "TargetValue": null + } + ] +} diff --git a/src/main/resources/mockup/app/getTodoDashboard_4.json b/src/main/resources/mockup/app/getTodoDashboard_4.json new file mode 100644 index 0000000..fd480eb --- /dev/null +++ b/src/main/resources/mockup/app/getTodoDashboard_4.json @@ -0,0 +1,85 @@ +{ + "statusCode":"OK", + "statusDescription":"statusDescription Test", + "Records":[{ + "UserId":80004, + "UserSegment":"IM", + "Type":"Achievement", + "Identifier":1, + "Level":2, + "ItemName":"Team Target", + "CurrentValue":5151217626.07, + "TargetValue":null + }, + { + "UserId":80004, + "UserSegment":"IM", + "Type":"Achievement", + "Identifier":2, + "Level":2, + "ItemName":"Individual Target", + "CurrentValue":85428626.15, + "TargetValue":null + }, + { + "UserId":80004, + "UserSegment":"IM", + "Type":"KPI", + "Identifier":3, + "Level":2, + "ItemName":"Reactive Player", + "CurrentValue":5, + "TargetValue":null + }, + { + "UserId":80004, + "UserSegment":"IM", + "Type":"KPI", + "Identifier":4, + "Level":2, + "ItemName":"Active Primary Program", + "CurrentValue":20, + "TargetValue":null + }, + { + "UserId":80004, + "UserSegment":"IM", + "Type":"", + "Identifier":5, + "Level":2, + "ItemName":"MFTurnover", + "CurrentValue":112, + "TargetValue":null + }, + { + "UserId":80004, + "UserSegment":"IM", + "Type":"", + "Identifier":6, + "Level":2, + "ItemName":"IMTurnover", + "CurrentValue":8896, + "TargetValue":null + }, + { + "UserId":80004, + "UserSegment":"IM", + "Type":"", + "Identifier":7, + "Level":2, + "ItemName":"NFROTurnover", + "CurrentValue":1123, + "TargetValue":null + }, + { + "UserId":80004, + "UserSegment":"IM", + "Type":"", + "Identifier":8, + "Level":2, + "ItemName":"MRITurnover", + "CurrentValue":3323, + "TargetValue":null + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoDetail_1.json b/src/main/resources/mockup/app/getTodoDetail_1.json new file mode 100644 index 0000000..85543f4 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoDetail_1.json @@ -0,0 +1,67 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "PlayerId": "11111111", + "Date": "2025-07-24", + "TierName": "A", + "ChineseName": "陳大文", + "EnglishName": "Chan Tai Man", + "Salutation": "Mr.", + "Location": "19BB21 (Macau) 08:55", + "AvticePrgId": "C12345", + "PrgTO": 123465, + "PrgWin": 11000, + "TORIPercentage": 0.28, + "FrontMoney": 100000, + "TTO": 999999, + "ComBalance": 100101, + "R3MTO": 30000, + "R3MTOD": 35000, + "R3MWin": 1000, + "R3MTPD": 1000, + "R3MRIPercentage": 0.28, + "R6MTO": 20000, + "R6MTOD": 25000, + "R6MWin": 10000, + "R6MTPD": 10000, + "R6MRIPercentage": 0.26, + "TripTheo": 1000, + "TodayTheo": 100, + "R3MTheo": 1000, + "R6MTheo": 10000, + "ExpComm": 50000, + "NetCreditOutstanding": 225, + "UnsettledExpenses": 125851, + "TodayTO": 2000000, + "TodayTPD": 100, + "TodayWin": 500000, + "TripWin": 1000, + "TripTPD": 1000, + "TripRIPercentage": 0.15, + "YesterdayTO": 200000, + "YesterdayWin": 500000, + "YesterdayPlayHour": 4, + "AveragePlayHour": 3, + "Descriptions": [ + { + "ItemRecordId": 2, + "ItemRecordType": "Flight", + "ItemRecordDescription": "Jul 1 10:00 NX333 Macau - Beijing;" + }, + { + "ItemRecordId": 3, + "ItemRecordType": "Limo", + "ItemRecordDescription": "Jul 1 07:00 MGM Macau – MGM Cotai" + }, + { + "ItemRecordId": 4, + "ItemRecordType": "Limo", + "ItemRecordDescription": "Jul 1 08:00 MGM Cotai – Macau Airport" + }, + { + "ItemRecordId": 5, + "ItemRecordType": "Room", + "ItemRecordDescription": "Jun 25 – Jul 1 Macau – GR | Conf#12596936 | Sharer: Y" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoDetail_2.json b/src/main/resources/mockup/app/getTodoDetail_2.json new file mode 100644 index 0000000..81ccddc --- /dev/null +++ b/src/main/resources/mockup/app/getTodoDetail_2.json @@ -0,0 +1,63 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "PlayerId": "11111111", + "Date": "2025-07-24", + "TierName": "A", + "ChineseName": "陳大文", + "EnglishName": "Chan Tai Man", + "Salutation": "Mr.", + "Location": "19BB21 (Macau) 08:55", + "AvticePrgId": "C12345", + "PrgTO": 123465, + "PrgWin": 123151, + "TORIPercentage": 0.28, + "FrontMoney": 3357, + "TTO": 5241, + "ComBalance": 5814, + "R3MTO": 872, + "R3MTOD": 785, + "R3MWin": 457, + "R3MTPD": 9645, + "R3MRIPercentage": 0.28, + "R6MTO": 728, + "R6MTOD": 7854, + "R6MWin": 87525, + "R6MTPD": 87417, + "R6MRIPercentage": 0.26, + "ExpComm": 78521, + "NetCreditOutstanding": 0, + "UnsettledExpenses": 7854, + "TodayTO": 741, + "TodayTPD": 7455, + "TodayWin": 8563, + "TripWin": 84667, + "TripTPD": 2674, + "TripRIPercentage": 0.15, + "YesterdayTO": 28576654, + "YesterdayWin": 2544675, + "YesterdayPlayHour": 1, + "AveragePlayHour": 5, + "Descriptions": [ + { + "ItemRecordId": 2, + "ItemRecordType": "Flight", + "ItemRecordDescription": "Jul 1 10:00 NX333 Macau - Beijing" + }, + { + "ItemRecordId": 3, + "ItemRecordType": "Limo", + "ItemRecordDescription": "Jul 1 07:00 MGM Macau – MGM Cotai" + }, + { + "ItemRecordId": 4, + "ItemRecordType": "Limo", + "ItemRecordDescription": "Jul 1 08:00 MGM Cotai – Macau Airport" + }, + { + "ItemRecordId": 5, + "ItemRecordType": "Room", + "ItemRecordDescription": "Jun 25 – Jul 1 Macau – GR | Conf#12596936 | Sharer: Y" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoDetail_3.json b/src/main/resources/mockup/app/getTodoDetail_3.json new file mode 100644 index 0000000..510eb21 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoDetail_3.json @@ -0,0 +1,68 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "PlayerId": "11010003", + "Date": "2025-07-24", + "TierName": "A", + "ChineseName": "李美美", + "EnglishName": "Lei MeiMei", + "Salutation": "Mr.", + "Location": "19BB21 (Macau) 08:55", + "ActivePrgId": null, + "PrgTO": null, + "PrgWin": null, + "TORIPercentage": null, + "FrontMoney": null, + "TTO": null, + "01CompBalance": null, + "R3MTO": null, + "R3MTOD": null, + "R3MWin": "5200", + "R3MTPD": "150", + "R3MRIPercentage": 0.15, + "R6MTO": null, + "R6MTOD": null, + "R6MWin": "1500", + "R6MTPD": "5000", + "R6MRIPercentage": 0.15, + "ExpComm": null, + "NetCreditOutstanding": null, + "UnsettledExpenses": null, + "TodayTO": null, + "TodayTPD": "150", + "TodayWin": "130", + "TripWin": "1250", + "TripTPD": "1300", + "TripRIPercentage": 0.25, + "YesterdayTO": null, + "YesterdayWin": null, + "YesterdayPlayHour": null, + "AveragePlayHour": 3, + "Descriptions": [ + { + "ItemRecordId": 2, + "ItemRecordType": "Flight", + "ItemRecordDescription": "Jul 1 10:00 NX333 Macau - Beijing" + }, + { + "ItemRecordId": 1, + "ItemRecordType": "Offer", + "ItemRecordDescription": "20863 Play Upfront Jul1" + }, + { + "ItemRecordId": 5, + "ItemRecordType": "Room", + "ItemRecordDescription": "Jun 25 – Jul 1 Macau – GR | Conf#12596936 | Sharer: Y" + }, + { + "ItemRecordId": 4, + "ItemRecordType": "Room", + "ItemRecordDescription": "Jun 25 – Jul 2 Macau – GR | Conf#12596937" + }, + { + "ItemRecordId": 3, + "ItemRecordType": "Limo", + "ItemRecordDescription": "Jul 1 07:00 MGM Macau – MGM Cotai" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoDetail_4.json b/src/main/resources/mockup/app/getTodoDetail_4.json new file mode 100644 index 0000000..8677bb6 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoDetail_4.json @@ -0,0 +1,64 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "PlayerId": "11010002", + "Date": "2025-07-24", + "TierName": "A", + "ChineseName": "梁大文", + "EnglishName": "Leong Tai Man", + "Salutation": "Mr.", + "Location": "19BB21 (Macau) 08:55", + "ActivePrgId": null, + "PrgTO": null, + "PrgWin": null, + "TORIPercentage": null, + "FrontMoney": null, + "TTO": null, + "01CompBalance": null, + "R3MTO": null, + "R3MTOD": null, + "R3MWin": "1000", + "R3MTPD": "1000", + "R3MRIPercentage": 0.15, + "R6MTO": null, + "R6MTOD": null, + "R6MWin": "10000", + "R6MTPD": "10000", + "R6MRIPercentage": 0.15, + "ExpComm": null, + "NetCreditOutstanding": null, + "UnsettledExpenses": null, + "TodayTO": null, + "TodayTPD": "100", + "TodayWin": "100", + "TripWin": "1000", + "TripTPD": "1000", + "TripRIPercentage": 0.15, + "YesterdayTO": null, + "YesterdayWin": null, + "YesterdayPlayHour": null, + "AveragePlayHour": 3, + "Descriptions": [ + { + "ItemRecordId": 1, + "ItemRecordType": "Offer", + "ItemRecordDescription": "20863 Play Upfront Jul1" + }, + { + "ItemRecordId": 2, + "ItemRecordType": "Room", + "ItemRecordDescription": "Jun 25 – Jul 1 Macau – GR | Conf#12596936 | Sharer: Y" + }, + { + "ItemRecordId": 3, + "ItemRecordType": "Room", + "ItemRecordDescription": "Jun 25 – Jul 2 Macau – GR | Conf#12596937" + }, + + { + "ItemRecordId": 4, + "ItemRecordType": "Limo", + "ItemRecordDescription": "Jul 1 07:00 MGM Macau – MGM Cotai" + }, + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoList_1.json b/src/main/resources/mockup/app/getTodoList_1.json new file mode 100644 index 0000000..b67b9a5 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_1.json @@ -0,0 +1,216 @@ +{ + "StatusCode": "OK", + "StatusDescription": "BALABALABALABALA", + "Records": [ + { + "PlayerId": 11010001, + "Date": "Oct 20 (Mon)", + "ActionDate":"2025-10-20T00:00:00", + "OperaConfirmationNo":"Ascott;ForDefect001,Ascott;ForDefect002", + "TierName": "A", + "ChineseName": "ꐴóÎÄ", + "EnglishName": "Chan Tai Man", + "Salutation": "Mr.", + "HasArrivalRecord": 1, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": "15:00", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Trip TPD: -|Trip Win: -| Trip Rl: 18%", + "Jun 24 - Jun 27 MO - GR | Conf#11251242 | Ref TPD: 20,000", + "Jun 24 - Jun 28 Ascott - GR | Conf#11251243 |" + ] + }, + { + "PlayerId": 11010002, + "Date": "Oct 20 (Mon)", + "ActionDate":"2025-10-20T00:00:00", + "OperaConfirmationNo":"Ascott;101235,MO;102226", + "TierName": "A", + "ChineseName": "Áº´óÎÄ", + "EnglishName": "Leong Tai Man", + "Salutation": "Mr.", + "HasArrivalRecord": 1, + "HasDepartureRecord": 1, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 0, + "HasFlightRecord": 1, + "ETA": "16:40", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000", + "May 6 - May 8 Macau - GR | Ref TPD: 20,000 | Ref TPD: 20,000 | Ref TPD: 20,000" , + "May 6 - May 27 Macau - GR" + ] + }, + { + "PlayerId": 11010003, + "Date": "Oct 20 (Mon)", + "ActionDate":"2025-10-20T00:00:00", + "OperaConfirmationNo":"2387461,235612,2134897", + "TierName": "A", + "ChineseName": "ÀîÃÀÃÀ", + "EnglishName": "Lei MeiMei", + "Salutation": "MS.", + "HasArrivalRecord": 1, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 0, + "ETA": "15:59", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 17 - May 19 Macau - GR", + "May 25 - May 27 Macau - GR" + ] + }, + { + "PlayerId": 11010004, + "Date": "Oct 20 (Mon)", + "ActionDate":"2025-10-20T00:00:00", + "OperaConfirmationNo":"Ascott;123345", + "TierName": "A", + "ChineseName": "ÀîÃÀÃÀ", + "EnglishName": "Rebecca", + "Salutation": "MS.", + "HasArrivalRecord": 0, + "HasDepartureRecord":1, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 0, + "HasFlightRecord": 1, + "ETA": "17:00", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000", + "May 6 - May 8 Macau - GR", + ] + }, + { + "PlayerId": 11010005, + "Date": "Jun 26 (Wed)", + "ActionDate":"2025-10-16T00:00:00", + "OperaConfirmationNo":"2387461,235612,2134897", + "TierName": "A", + "ChineseName": "ÍõÀòÀò", + "EnglishName": "Wong Lily", + "Salutation": "MS.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 1, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": "16:39", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 8 Macau - GR", + "May 6 - May 27 Macau - GR" + ] + }, + { + "PlayerId": 11010006, + "Date": "Jun 27 (Wed)", + "ActionDate":"2025-10-17T00:00:00", + "OperaConfirmationNo":"2387461,235612,2134897", + "TierName": "A", + "ChineseName": "³ÂÐñÎÄ", + "EnglishName": "Chen XuMan", + "Salutation": "MS.", + "HasArrivalRecord": 1, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 1, + "HasLimoRecord": 0, + "HasFlightRecord": 1, + "ETA": "11:22", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000 123 123 123 123123 123", + "May 6 - May 27 Macau - GR" + ] + }, + { + "PlayerId": 11010009, + "Date": "Jun 27 (Wed)", + "ActionDate":"2025-10-16T00:00:00", + "OperaConfirmationNo":"2387461,235612,2134897", + "TierName": "A", + "ChineseName": "³ÂÐñÎÄ", + "EnglishName": "Chen XuMan", + "Salutation": "MS.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 1, + "HasLimoRecord": 0, + "HasFlightRecord": 1, + "ETA": "09:22", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000 123 123 123 123123 123", + "May 6 - May 27 Macau - GR" + ] + }, + { + "PlayerId": 11010007, + "Date": "Jun 27 (Wed)", + "ActionDate":"2025-10-15T00:00:00", + "OperaConfirmationNo":"2387461,235612,2134897", + "TierName": "A", + "ChineseName": "³ÂÐñÎÄ", + "EnglishName": "Chen XuMan", + "Salutation": "MS.", + "HasArrivalRecord": 1, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 1, + "HasLimoRecord": 0, + "HasFlightRecord": 1, + "ETA":"06:22", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000 123 123 123 123123 123", + "May 6 - May 27 Macau - GR" + ] + } + ] +} diff --git a/src/main/resources/mockup/app/getTodoList_2.json b/src/main/resources/mockup/app/getTodoList_2.json new file mode 100644 index 0000000..c049d18 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_2.json @@ -0,0 +1,58 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "PlayerId": 22020001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"MGM M;101235,MGM M;102226", + "TierName": "SUPREME", + "ChineseName": "张海斌", + "EnglishName": "HaiBin Zhang", + "Salutation": "Ms", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": "13:45", + "BigWinFlag": 0, + "BigLossFlag": 1, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Jun 24 - Jun 27 MGM M - GR | Ref TPD: 20,000", + "Jun 24 - Jun 28 MGM C - GR" + ] + }, + { + "PlayerId": 11010001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"MGM M;101235,MGM M;102226", + "TierName": "SUPREME 88", + "ChineseName": "劉明浩", + "EnglishName": "IVAN LAU", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 1, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": "13:45", + "BigWinFlag": 1, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Jun 24 - Jun 27 Macau - GR | Ref TPD: 20,000", + "Jun 24 - Jun 28 Macau - GR" + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoList_3.json b/src/main/resources/mockup/app/getTodoList_3.json new file mode 100644 index 0000000..83be198 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_3.json @@ -0,0 +1,34 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "PlayerId": 11010002, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"", + "TierName": "", + "ChineseName": "", + "EnglishName": "", + "Salutation": "", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 0, + "HasFlightRecord": 0, + "ETA": "", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 10000001, + "MA": "", + "Descriptions": [ + "acky Chueng Ticket to be collected C4 #11001961 CHAN TAI MAN (A) ", + "Make confirm call for Alan Walker event", + "Birthday offer will be expired on May 14 for #1101961 CHAN TAI MAN(A) ďD" + + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoList_4.json b/src/main/resources/mockup/app/getTodoList_4.json new file mode 100644 index 0000000..d22e348 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_4.json @@ -0,0 +1,86 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "PlayerId": 11010001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"12312312", + "TierName": "SUPREME 88", + "ChineseName": "劉明浩", + "EnglishName": "IVAN LAU", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 0, + "ETA": "13:45", + "BigWinFlag": 1, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Active Prg:N", + "May7 - May8 Macau -GR | Conf#12312312 | Sharer: Y" + ] + }, + { + "PlayerId": 11010002, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"4562534,3456345,2345534", + "TierName": "PLATINUM", + "ChineseName": "李四", + "EnglishName": "ELIZABETH LEE", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": "13:45", + "BigWinFlag": 0, + "BigLossFlag": 1, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Active Prg:Y", + "May7 - May8 Macau -GR | Conf#4562534 | Sharer: N", + "May7 - May8 Macau -GR | Conf#3456345 | Sharer: Y", + "May7 - May8 Macau -GR | Conf#2345534 | Sharer: N" + ] + }, + { + "PlayerId": 22020001, + "Date": "Jun 25 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"12596988", + "TierName": "SUPREME", + "ChineseName": "李莉莉", + "EnglishName": "LILY LEE", + "Salutation": "Ms", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 1, + "HasOthersRecord": 1, + "HasLimoRecord": 1, + "HasFlightRecord": 0, + "ETA": "13:45", + "BigWinFlag": 1, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Active Prg:N", + "May7 - May8 Macau -GR | Conf#12596988 | Sharer: Y" + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoList_5.json b/src/main/resources/mockup/app/getTodoList_5.json new file mode 100644 index 0000000..e350c10 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_5.json @@ -0,0 +1,84 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "PlayerId": 11010001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"12312312", + "TierName": "SUPREME 88", + "ChineseName": "劉明浩", + "EnglishName": "IVAN LAU", + "Salutation": "Mr.", + "HasArrivalRecord": 1, + "HasDepartureRecord": 1, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": "13:45", + "BigWinFlag": 1, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Active Prg:N", + "May7 - May8 Macau -GR | Conf#12312312 | Sharer: Y" + ] + }, + { + "PlayerId": 11010002, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"4562534", + "TierName": "PLATINUM", + "ChineseName": "李思思", + "EnglishName": "SISI LEE", + "Salutation": "Mr.", + "HasArrivalRecordHasArrivalRecord": 1, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 1, + "HasLimoRecord": 0, + "HasFlightRecord": 1, + "ETA": "13:45", + "BigWinFlag": 0, + "BigLossFlag": 1, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Active Prg:Y", + "May7 - May8 Macau -GR | Conf#4562534| Sharer: N" + ] + }, + { + "PlayerId": 22020001, + "Date": "Jun 25 (Tue)", + "ActionDate":"2025-07-25", + "OperaConfirmationNo":"12596988", + "TierName": "SUPREME", + "ChineseName": "李莉莉", + "EnglishName": "LILY LEE", + "Salutation": "Ms", + "HasArrivalRecord": 1, + "HasDepartureRecord": 0, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 0, + "HasFlightRecord": 0, + "ETA": "13:45", + "BigWinFlag": 1, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Active Prg:N", + "May7 - May8 Macau -GR | Conf#12596988| Sharer: N" + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoList_6.json b/src/main/resources/mockup/app/getTodoList_6.json new file mode 100644 index 0000000..abe3a18 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_6.json @@ -0,0 +1,83 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "PlayerId": 11010001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"", + "TierName": "SUPREME 88", + "ChineseName": "彭超", + "EnglishName": "Chao Peng", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": null, + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 1, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Test1Test1Test1#REC#Test2Test2Test2" + ] + }, + { + "PlayerId": 11010002, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"", + "TierName": "PLATINUM", + "ChineseName": "李四", + "EnglishName": "ELIZABETH LEE", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 1, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 0, + "HasFlightRecord": 1, + "ETA": null, + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Trip TPD:200,000|Trip Win:100,000|Trip RI%:10%#REC#TESTTSETTEST" + ] + }, + { + "PlayerId": 22020001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"", + "TierName": "SUPREME", + "ChineseName": "李莉莉", + "EnglishName": "LILY LEE", + "Salutation": "Ms", + "HasArrivalRecord": 1, + "HasDepartureRecord": 1, + "HasInhouseRecord": 1, + "HasOthersRecord": 1, + "HasLimoRecord": 0, + "HasFlightRecord": 0, + "ETA": null, + "BigWinFlag": 1, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Trip TPD:200,000|Trip Win:100,000|Trip RI%:80%", + "Remark1 | Remark2", + "Offer:Y" + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoList_7.json b/src/main/resources/mockup/app/getTodoList_7.json new file mode 100644 index 0000000..d33e32d --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_7.json @@ -0,0 +1,58 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "PlayerId": 11010001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"", + "TierName": "SUPREME 88", + "ChineseName": "劉明浩", + "EnglishName": "IVAN LAU", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": "13:45", + "BigWinFlag": 1, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Prg TPD:2,000,000|Prg Win:100,000|A 0.1:200,000", + "T/O RI%:10%|Yesterday Playhour:3hours" + ] + }, + { + "PlayerId": 22020001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"", + "TierName": "SUPREME", + "ChineseName": "李莉莉", + "EnglishName": "LILY LEE", + "Salutation": "Ms", + "HasArrivalRecord": 1, + "HasDepartureRecord": 0, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 0, + "HasFlightRecord": 0, + "ETA": "13:45", + "BigWinFlag": 1, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "Prg TPD:2,000,000|Prg Win:100,000|A 0.1:200,000", + "T/O RI%:10%|Yesterday Playhour:3hours" + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/getTodoList_8.json b/src/main/resources/mockup/app/getTodoList_8.json new file mode 100644 index 0000000..477c8f0 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_8.json @@ -0,0 +1,87 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "PlayerId": 11010001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"7893245", + "TierName": "A", + "ChineseName": "戴大卫", + "EnglishName": "David Dai", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 1, + "HasInhouseRecord": 0, + "HasOthersRecord": 1, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": "13:45", + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000" + ] + }, + { + "PlayerId": 22020001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"1925312,124774,238446", + "TierName": "A", + "ChineseName": "李荣浩", + "EnglishName": "Ronghao Lee", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": null, + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000", + "May 6 - May 8 Macau - GR", + "May 6 - May 27 Macau - GR" + ] + }, + { + "PlayerId": 13333333, + "Date": "Jun 25 (Wed)", + "ActionDate":"2025-07-25", + "OperaConfirmationNo":"651234,912843,451263", + "ActionDate":"2025-07-24", + "OperaConfirmationNo":"12312312", + "TierName": "A", + "ChineseName": "夏美美", + "EnglishName": "Meimei Xia", + "Salutation": "MS.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 1, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": null, + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000", + "May 6 - May 8 Macau - GR", + "May 6 - May 27 Macau - GR" + ] + } + ] +} diff --git a/src/main/resources/mockup/app/getTodoList_9.json b/src/main/resources/mockup/app/getTodoList_9.json new file mode 100644 index 0000000..f8c1be2 --- /dev/null +++ b/src/main/resources/mockup/app/getTodoList_9.json @@ -0,0 +1,84 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "PlayerId": 11010001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2026-07-24T00:00:00", + "OperaConfirmationNo":"MGM SIT - Integration Testing;00003709COCON,ProperName;201212ASDASD", + "TierName": "A", + "ChineseName": "戴大卫", + "EnglishName": "David Dai", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 1, + "HasInhouseRecord": 0, + "HasOthersRecord": 1, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": null, + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000" + ] + }, + { + "PlayerId": 22020001, + "Date": "Jun 24 (Tue)", + "ActionDate":"2026-07-24T00:00:00", + "OperaConfirmationNo":"MGM SIT - Integration Testing;00003567COCON", + "TierName": "A", + "ChineseName": "李荣浩", + "EnglishName": "Ronghao Lee", + "Salutation": "Mr.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 1, + "HasOthersRecord": 0, + "HasLimoRecord": 1, + "HasFlightRecord": 1, + "ETA": null, + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "MAId": 123456, + "MA": "Jojo Chan", + "Descriptions": [ + "May 6 - May 7 Macau - GR | Ref TPD: 20,000", + "May 6 - May 8 Macau - GR", + "May 6 - May 27 Macau - GR" + ] + }, + { + "PlayerId": 12357795, + "Date": "Jul 2 (Thu)", + "ActionDate": "2026-07-02T00:00:00", + "TierName": "G", + "ChineseName": "", + "EnglishName": "SA, CHI", + "Salutation": "Ms.", + "HasArrivalRecord": 0, + "HasDepartureRecord": 0, + "HasInhouseRecord": 0, + "HasOthersRecord": 0, + "HasLimoRecord": 0, + "HasFlightRecord": 0, + "ETA": null, + "BigWinFlag": 0, + "BigLossFlag": 0, + "NoPlayFlag": 0, + "OperaConfirmationNo": "MGM SIT - Integration Testing;00003483COCON", + "MAId": null, + "MA": null, + "Descriptions": [ + "Ref TPD: 5,280", + "Jul 2 - Jul 3 MGM SIT - Integration Testing - RSR | Conf#00003483COCON | Sharer: N" + ] + } + ] +} diff --git a/src/main/resources/mockup/app/playerProfile.json b/src/main/resources/mockup/app/playerProfile.json new file mode 100644 index 0000000..fe4c9e7 --- /dev/null +++ b/src/main/resources/mockup/app/playerProfile.json @@ -0,0 +1,11 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "playerId":"1101234", + "name":"CHAN, TAI MAN", + "tier":"SUPREME", + "salutation":"Mr", + "nameIds":[1001,1002,1003] + } +} \ No newline at end of file diff --git a/src/main/resources/mockup/app/submitETA.json b/src/main/resources/mockup/app/submitETA.json new file mode 100644 index 0000000..9a9d1ac --- /dev/null +++ b/src/main/resources/mockup/app/submitETA.json @@ -0,0 +1,4 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success" +} diff --git a/src/main/resources/mockup/http/check_player_tpd_all_pass.json b/src/main/resources/mockup/http/check_player_tpd_all_pass.json new file mode 100644 index 0000000..9d8cba1 --- /dev/null +++ b/src/main/resources/mockup/http/check_player_tpd_all_pass.json @@ -0,0 +1,18 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "PlayerId": "", + "RoomCategory": "GR", + "BookingDate": "2024/06/01", + "BookingDateQualificationFlag": 1 + }, + { + "PlayerId": "", + "RoomCategory": "GR", + "BookingDate": "2024/06/02", + "BookingDateQualificationFlag": 1 + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/check_player_tpd_not_all_pass.json b/src/main/resources/mockup/http/check_player_tpd_not_all_pass.json new file mode 100644 index 0000000..af55ebf --- /dev/null +++ b/src/main/resources/mockup/http/check_player_tpd_not_all_pass.json @@ -0,0 +1,18 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "PlayerId": "", + "RoomCategory": "GR", + "BookingDate": "2024/06/01", + "BookingDateQualificationFlag": 0 + }, + { + "PlayerId": "", + "RoomCategory": "GR", + "BookingDate": "2024/06/02", + "BookingDateQualificationFlag": 0 + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/check_player_tpd_partly_pass.json b/src/main/resources/mockup/http/check_player_tpd_partly_pass.json new file mode 100644 index 0000000..44f2f28 --- /dev/null +++ b/src/main/resources/mockup/http/check_player_tpd_partly_pass.json @@ -0,0 +1,18 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "PlayerId": "", + "RoomCategory": "GR", + "BookingDate": "2024/06/01", + "BookingDateQualificationFlag": 0 + }, + { + "PlayerId": "", + "RoomCategory": "GR", + "BookingDate": "2024/06/02", + "BookingDateQualificationFlag": 1 + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res1.json b/src/main/resources/mockup/http/get_player_profile_api_res1.json new file mode 100644 index 0000000..04f5cb4 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res1.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010001", + "UserId":"", + "TierName":"SUPREME 88", + "TierId":34, + "ChineseName":"劉明浩", + "EnglishName":"IVAN LAU", + "Salutation":"Mr." , + "CodedFlag":1, + "PlayerPhone":"123456", + "PhoneType":"MOBILE", + "Birthday":"2000-01-01", + "Country":"CN", + "State":"MO", + "Passport":"123456", + "Language":"E" +} diff --git a/src/main/resources/mockup/http/get_player_profile_api_res10.json b/src/main/resources/mockup/http/get_player_profile_api_res10.json new file mode 100644 index 0000000..06e9575 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res10.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"", + "UserId":"", + "TierName":null, + "TierId":null, + "ChineseName":null, + "EnglishName":null, + "Salutation":null , + "CodedFlag":null, + "PlayerPhone":null, + "PhoneType":null, + "Birthday":null, + "Country":null, + "State":null, + "Passport":null, + "Language":null +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res11.json b/src/main/resources/mockup/http/get_player_profile_api_res11.json new file mode 100644 index 0000000..5edc3fc --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res11.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020001", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"李莉莉", + "EnglishName":"LILY LEE", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"223461", + "PhoneType":"MOBILE", + "Birthday":"2000-02-11", + "Country":"CN", + "State":"MO", + "Passport":"223461", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res12.json b/src/main/resources/mockup/http/get_player_profile_api_res12.json new file mode 100644 index 0000000..694f40b --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res12.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020002", + "UserId":"", + "TierName":"PLATINUM", + "TierId":16, + "ChineseName":"楊陽", + "EnglishName":"SUNNY YUENG", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"223411", + "PhoneType":"MOBILE", + "Birthday":"2000-02-11", + "Country":"CN", + "State":"MO", + "Passport":"223411", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res13.json b/src/main/resources/mockup/http/get_player_profile_api_res13.json new file mode 100644 index 0000000..43864d8 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res13.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020003", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"陳皮", + "EnglishName":"PETER CHAN", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"323461", + "PhoneType":"MOBILE", + "Birthday":"2000-03-21", + "Country":"CN", + "State":"MO", + "Passport":"323461", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res14.json b/src/main/resources/mockup/http/get_player_profile_api_res14.json new file mode 100644 index 0000000..fa7e53b --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res14.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020004", + "UserId":"", + "TierName":null, + "TierId":null, + "ChineseName":null, + "EnglishName":null, + "Salutation":null , + "CodedFlag":0, + "PlayerPhone":null, + "PhoneType":null, + "Birthday":null, + "Country":null, + "State":null, + "Passport":null, + "Language":null +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res15.json b/src/main/resources/mockup/http/get_player_profile_api_res15.json new file mode 100644 index 0000000..7b50741 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res15.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020005", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"費達", + "EnglishName":"HIFAI FEI", + "Salutation":"Mr" , + "CodedFlag":1, + "PlayerPhone":"163461", + "PhoneType":"MOBILE", + "Birthday":"1986-06-12", + "Country":"CN", + "State":"MO", + "Passport":"163461", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res16.json b/src/main/resources/mockup/http/get_player_profile_api_res16.json new file mode 100644 index 0000000..ce64314 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res16.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020006", + "UserId":"", + "TierName":"ALPHA", + "TierId":17, + "ChineseName":"許老七", + "EnglishName":"SEVEN HUI", + "Salutation":"Mr" , + "CodedFlag":1, + "PlayerPhone":"423461", + "PhoneType":"MOBILE", + "Birthday":"1998-08-20", + "Country":"CN", + "State":"MO", + "Passport":"423461", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res17.json b/src/main/resources/mockup/http/get_player_profile_api_res17.json new file mode 100644 index 0000000..993b7a5 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res17.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020007", + "UserId":"", + "TierName":"SUPREME 88 IM", + "TierId":35, + "ChineseName":"梁大大", + "EnglishName":"MAY LUENG", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"422462", + "PhoneType":"MOBILE", + "Birthday":"1988-08-20", + "Country":"CN", + "State":"MO", + "Passport":"422462", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res18.json b/src/main/resources/mockup/http/get_player_profile_api_res18.json new file mode 100644 index 0000000..ddd5a78 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res18.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020008", + "UserId":"", + "TierName":"PLATINUM IM", + "TierId":26, + "ChineseName":"鄭凱凱", + "EnglishName":"KATE CHENG", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"222461", + "PhoneType":"MOBILE", + "Birthday":"1978-03-20", + "Country":"CN", + "State":"MO", + "Passport":"222461", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res19.json b/src/main/resources/mockup/http/get_player_profile_api_res19.json new file mode 100644 index 0000000..9a72489 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res19.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"22020009", + "UserId":"", + "TierName":"PLATINUM IM", + "TierId":17, + "ChineseName":"郭成", + "EnglishName":"PHILIP KOWK", + "Salutation":"Mr" , + "CodedFlag":1 , + "PlayerPhone":"663232", + "PhoneType":"MOBILE", + "Birthday":"1990-04-20", + "Country":"CN", + "State":"MO", + "Passport":"663232", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res2.json b/src/main/resources/mockup/http/get_player_profile_api_res2.json new file mode 100644 index 0000000..f45f0bb --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res2.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010002", + "UserId":"", + "TierName":"PLATINUM", + "TierId":16, + "ChineseName":"李四", + "EnglishName":"ELIZABETH LEE", + "Salutation":"Ms" , + "CodedFlag":1, + "PlayerPhone":"123457", + "PhoneType":"MOBILE", + "Birthday":"2000-01-02", + "Country":"CN", + "State":"MO", + "Passport":"123457", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res20.json b/src/main/resources/mockup/http/get_player_profile_api_res20.json new file mode 100644 index 0000000..128b6b9 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res20.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030001", + "UserId":"", + "TierName":"SUPREME 88", + "TierId":34, + "ChineseName":"李明浩", + "EnglishName":"KEVIN LEE", + "Salutation":"Mr" , + "CodedFlag":1 , + "PlayerPhone":"633232", + "PhoneType":"MOBILE", + "Birthday":"1992-03-06", + "Country":"CN", + "State":"MO", + "Passport":"633232", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res21.json b/src/main/resources/mockup/http/get_player_profile_api_res21.json new file mode 100644 index 0000000..4866291 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res21.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030002", + "UserId":"", + "TierName":"PLATINUM", + "TierId":16, + "ChineseName":"王四", + "EnglishName":"HELLEN WONG", + "Salutation":"Ms" , + "CodedFlag":1, + "PlayerPhone":"333232", + "PhoneType":"MOBILE", + "Birthday":"1990-04-20", + "Country":"CN", + "State":"MO", + "Passport":"663237", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res22.json b/src/main/resources/mockup/http/get_player_profile_api_res22.json new file mode 100644 index 0000000..8126be2 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res22.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030003", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"王五", + "EnglishName":"WILLIAMS WONG", + "Salutation":"Mr" , + "CodedFlag":1 , + "PlayerPhone":"883232", + "PhoneType":"MOBILE", + "Birthday":"1990-04-20", + "Country":"CN", + "State":"MO", + "Passport":"663288", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res23.json b/src/main/resources/mockup/http/get_player_profile_api_res23.json new file mode 100644 index 0000000..f9b8139 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res23.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030004", + "UserId":"", + "TierName":null, + "TierId":null, + "ChineseName":null, + "EnglishName":null, + "Salutation":null , + "CodedFlag":0 , + "PlayerPhone":null, + "PhoneType":null, + "Birthday":null, + "Country":null, + "State":null, + "Passport":null, + "Language":null +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res24.json b/src/main/resources/mockup/http/get_player_profile_api_res24.json new file mode 100644 index 0000000..6289aef --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res24.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030005", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"刘珊珊", + "EnglishName":"CANDY LAU", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"883231", + "PhoneType":"MOBILE", + "Birthday":"1993-02-20", + "Country":"CN", + "State":"MO", + "Passport":"663688", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res25.json b/src/main/resources/mockup/http/get_player_profile_api_res25.json new file mode 100644 index 0000000..faf0410 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res25.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030006", + "UserId":"", + "TierName":"ALPHA", + "TierId":17, + "ChineseName":"許老六", + "EnglishName":"CHIMING HUI", + "Salutation":"Mr" , + "CodedFlag":1 , + "PlayerPhone":"873232", + "PhoneType":"MOBILE", + "Birthday":"1991-01-20", + "Country":"CN", + "State":"MO", + "Passport":"663636", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res26.json b/src/main/resources/mockup/http/get_player_profile_api_res26.json new file mode 100644 index 0000000..344782e --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res26.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030007", + "UserId":"", + "TierName":"SUPREME 88 IM", + "TierId":35, + "ChineseName":"梁小", + "EnglishName":"GARY LUENG", + "Salutation":"Mr" , + "CodedFlag":1, + "PlayerPhone":"886231", + "PhoneType":"MOBILE", + "Birthday":"1983-09-19", + "Country":"CN", + "State":"MO", + "Passport":"6636322", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res27.json b/src/main/resources/mockup/http/get_player_profile_api_res27.json new file mode 100644 index 0000000..7acdf09 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res27.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030008", + "UserId":"", + "TierName":"PLATINUM IM", + "TierId":26, + "ChineseName":"鄭雯雯", + "EnglishName":"WENDY CHENG", + "Salutation":"Ms" , + "CodedFlag":1, + "PlayerPhone":"883232", + "PhoneType":"MOBILE", + "Birthday":"1983-02-23", + "Country":"CN", + "State":"MO", + "Passport":"6623266", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res28.json b/src/main/resources/mockup/http/get_player_profile_api_res28.json new file mode 100644 index 0000000..709c71c --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res28.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"33030009", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"张明明", + "EnglishName":"SAM CHUENG", + "Salutation":"Mr" , + "CodedFlag":1, + "PlayerPhone":"881132", + "PhoneType":"MOBILE", + "Birthday":"1989-11-19", + "Country":"CN", + "State":"MO", + "Passport":"6626236", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res29.json b/src/main/resources/mockup/http/get_player_profile_api_res29.json new file mode 100644 index 0000000..8bc8f20 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res29.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"1212121", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"张明明", + "EnglishName":"SAM CHUENG", + "Salutation":"Mr" , + "CodedFlag":1, + "PlayerPhone":"881132", + "PhoneType":"MOBILE", + "Birthday":"1989-11-19", + "Country":"CN", + "State":"MO", + "Passport":"6626236", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res3.json b/src/main/resources/mockup/http/get_player_profile_api_res3.json new file mode 100644 index 0000000..5077b07 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res3.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010003", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"王五", + "EnglishName":"WILLIAMS WONG", + "Salutation":"Mr" , + "CodedFlag":1 , + "PlayerPhone":"123458", + "PhoneType":"MOBILE", + "Birthday":"2000-01-03", + "Country":"CN", + "State":"MO", + "Passport":"123458", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res30.json b/src/main/resources/mockup/http/get_player_profile_api_res30.json new file mode 100644 index 0000000..8d9fb32 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res30.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"12357817", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"张明明", + "EnglishName":"SAM CHUENG", + "Salutation":"Mr" , + "CodedFlag":1, + "PlayerPhone":"881132", + "PhoneType":"MOBILE", + "Birthday":"1989-11-19", + "Country":"CN", + "State":"MO", + "Passport":"6626236", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res31.json b/src/main/resources/mockup/http/get_player_profile_api_res31.json new file mode 100644 index 0000000..04e5a49 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res31.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11335577", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"张明明", + "EnglishName":"SAM CHUENG", + "Salutation":"Mr" , + "CodedFlag":1, + "PlayerPhone":"881132", + "PhoneType":"MOBILE", + "Birthday":"1989-11-19", + "Country":"CN", + "State":"MO", + "Passport":"6626236", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res4.json b/src/main/resources/mockup/http/get_player_profile_api_res4.json new file mode 100644 index 0000000..5d205e1 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res4.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010004", + "UserId":"", + "TierName":null, + "TierId":null, + "ChineseName":null, + "EnglishName":null, + "Salutation":null , + "CodedFlag":0 , + "PlayerPhone":null, + "PhoneType":null, + "Birthday":null, + "Country":null, + "State":null, + "Passport":null, + "Language":null +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res5.json b/src/main/resources/mockup/http/get_player_profile_api_res5.json new file mode 100644 index 0000000..43cde29 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res5.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010005", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"陳珊珊", + "EnglishName":"SANDY CHAN", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"123459", + "PhoneType":"MOBILE", + "Birthday":"2000-01-04", + "Country":"CN", + "State":"MO", + "Passport":"123459", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res6.json b/src/main/resources/mockup/http/get_player_profile_api_res6.json new file mode 100644 index 0000000..1a43cbb --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res6.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010006", + "UserId":"", + "TierName":"ALPHA", + "TierId":17, + "ChineseName":"許老六", + "EnglishName":"ANDY HUI", + "Salutation":"Mr" , + "CodedFlag":1 , + "PlayerPhone":"123460", + "PhoneType":"MOBILE", + "Birthday":"2000-01-06", + "Country":"CN", + "State":"MO", + "Passport":"123460", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res7.json b/src/main/resources/mockup/http/get_player_profile_api_res7.json new file mode 100644 index 0000000..f543a80 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res7.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010007", + "UserId":"", + "TierName":"SUPREME 88 IM", + "TierId":35, + "ChineseName":"梁小小", + "EnglishName":"CINDY LUENG", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"123461", + "PhoneType":"MOBILE", + "Birthday":"2000-01-11", + "Country":"CN", + "State":"MO", + "Passport":"123461", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res8.json b/src/main/resources/mockup/http/get_player_profile_api_res8.json new file mode 100644 index 0000000..0db4dd5 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res8.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010008", + "UserId":"", + "TierName":"PLATINUM IM", + "TierId":26, + "ChineseName":"鄭雯雯", + "EnglishName":"SAMMY CHENG", + "Salutation":"Ms" , + "CodedFlag":1 , + "PlayerPhone":"123462", + "PhoneType":"MOBILE", + "Birthday":"2000-01-12", + "Country":"CN", + "State":"MO", + "Passport":"123462", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_player_profile_api_res9.json b/src/main/resources/mockup/http/get_player_profile_api_res9.json new file mode 100644 index 0000000..1fa6a75 --- /dev/null +++ b/src/main/resources/mockup/http/get_player_profile_api_res9.json @@ -0,0 +1,19 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "PlayerId":"11010009", + "UserId":"", + "TierName":"SUPREME", + "TierId":17, + "ChineseName":"黎明明", + "EnglishName":"ALLEN LAI", + "Salutation":"Mr" , + "CodedFlag":1 , + "PlayerPhone":"123463", + "PhoneType":"MOBILE", + "Birthday":"2000-01-16", + "Country":"CN", + "State":"MO", + "Passport":"123463", + "Language":"E" +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res1.json b/src/main/resources/mockup/http/get_user_info_api_res1.json new file mode 100644 index 0000000..3dc7b14 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res1.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "NULL", + "UserName": "Andy.lau", + "Property": "MGM MACAU", + "Division": "International Marketing", + "Department": "Regional Marketing", + "Section": "Slot", + "Block": "GM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res10.json b/src/main/resources/mockup/http/get_user_info_api_res10.json new file mode 100644 index 0000000..64b250f --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res10.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90007", + "UserName": "MGM User07", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res11.json b/src/main/resources/mockup/http/get_user_info_api_res11.json new file mode 100644 index 0000000..b3b7cc7 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res11.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90008", + "UserName": "MGM User08", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res12.json b/src/main/resources/mockup/http/get_user_info_api_res12.json new file mode 100644 index 0000000..52eb106 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res12.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90009", + "UserName": "MGM User09", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res13.json b/src/main/resources/mockup/http/get_user_info_api_res13.json new file mode 100644 index 0000000..1c0fe6c --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res13.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "80003", + "UserName": "LC", + "Property": "MGM MACAU", + "Division": "Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res14.json b/src/main/resources/mockup/http/get_user_info_api_res14.json new file mode 100644 index 0000000..9051b38 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res14.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "80004", + "UserName": "Feng", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res15.json b/src/main/resources/mockup/http/get_user_info_api_res15.json new file mode 100644 index 0000000..46bb711 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res15.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "80005", + "UserName": "Beyond", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res16.json b/src/main/resources/mockup/http/get_user_info_api_res16.json new file mode 100644 index 0000000..a4b6a18 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res16.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "800004", + "UserName": "Feng", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res17.json b/src/main/resources/mockup/http/get_user_info_api_res17.json new file mode 100644 index 0000000..3152d19 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res17.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "80008", + "UserName": "Corso", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "123123123" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res2.json b/src/main/resources/mockup/http/get_user_info_api_res2.json new file mode 100644 index 0000000..4535d53 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res2.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90001", + "UserName": "IWIWIN", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res3.json b/src/main/resources/mockup/http/get_user_info_api_res3.json new file mode 100644 index 0000000..48a5dc1 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res3.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90002", + "UserName": "Judy Wong", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res4.json b/src/main/resources/mockup/http/get_user_info_api_res4.json new file mode 100644 index 0000000..260de8e --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res4.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "80001", + "UserName": "Steven Lei", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res5.json b/src/main/resources/mockup/http/get_user_info_api_res5.json new file mode 100644 index 0000000..2630aff --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res5.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "80002", + "UserName": "JianQi Yan", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res6.json b/src/main/resources/mockup/http/get_user_info_api_res6.json new file mode 100644 index 0000000..8ee8890 --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res6.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90003", + "UserName": "Octavio", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res7.json b/src/main/resources/mockup/http/get_user_info_api_res7.json new file mode 100644 index 0000000..18acccd --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res7.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90004", + "UserName": "Inmei Lei", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res8.json b/src/main/resources/mockup/http/get_user_info_api_res8.json new file mode 100644 index 0000000..1b696af --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res8.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90005", + "UserName": "MGM User05", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/get_user_info_api_res9.json b/src/main/resources/mockup/http/get_user_info_api_res9.json new file mode 100644 index 0000000..4ff6f9d --- /dev/null +++ b/src/main/resources/mockup/http/get_user_info_api_res9.json @@ -0,0 +1,16 @@ +{ + "StatusCode": "OK", + "StatusDescription": "SUCCESS", + "Records": [ + { + "UserId": "90006", + "UserName": "MGM User06", + "Property": "MGM MACAU", + "Division": "VIP Marketing", + "Department": "Global Marketing", + "Section": "Global Marketing", + "Block": "CM", + "Phone": "85366988316" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/non_mgm_booking_no_record.json b/src/main/resources/mockup/http/non_mgm_booking_no_record.json new file mode 100644 index 0000000..92c4f56 --- /dev/null +++ b/src/main/resources/mockup/http/non_mgm_booking_no_record.json @@ -0,0 +1,6 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/http/non_mgm_booking_with_record.json b/src/main/resources/mockup/http/non_mgm_booking_with_record.json new file mode 100644 index 0000000..32a2a8a --- /dev/null +++ b/src/main/resources/mockup/http/non_mgm_booking_with_record.json @@ -0,0 +1,24 @@ +{ + "StatusCode": "OK", + "StatusDescription": "Success", + "Records": [ + { + "ConfirmationNumber": "5747311", + "Property":"Ascott", + "RoomTypeStatusCode":"CKI", + "CheckInDate": "2024-07-11", + "CheckOutDate":"2024-07-19", + "CreatedBy":"Judy Wong", + "CreatedOn":"2024-07-09" + }, + { + "ConfirmationNumber": "5747312", + "Property":"Ascott", + "RoomTypeStatusCode":"CKI", + "CheckInDate": "2024-07-20", + "CheckOutDate":"2024-07-21", + "CreatedBy":"Judy Wong", + "CreatedOn":"2024-07-18" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/mockup/mgm_api_mockup_def.json b/src/main/resources/mockup/mgm_api_mockup_def.json new file mode 100644 index 0000000..f877d2c --- /dev/null +++ b/src/main/resources/mockup/mgm_api_mockup_def.json @@ -0,0 +1,380 @@ +[ +{ + "apiLink": "GetReportingLinePlayerInfo", + "resultIndex":9, + "mockupRules":[ + { + "condiction":"playerId==11010001", + "fileIndex":0 + }, + { + "condiction":"playerId==11010002", + "fileIndex":1 + }, + { + "condiction":"playerId==11010003", + "fileIndex":2 + }, + { + "condiction":"playerId==11010004", + "fileIndex":3 + }, + { + "condiction":"playerId==11010005", + "fileIndex":4 + }, + { + "condiction":"playerId==11010006", + "fileIndex":5 + }, + { + "condiction":"playerId==11010007", + "fileIndex":6 + }, + { + "condiction":"playerId==11010008", + "fileIndex":7 + }, + { + "condiction":"playerId==11010009", + "fileIndex":8 + }, + { + "condiction":"playerId==22020001", + "fileIndex":10 + }, + { + "condiction":"playerId==22020002", + "fileIndex":11 + }, + { + "condiction":"playerId==22020003", + "fileIndex":12 + }, + { + "condiction":"playerId==22020004", + "fileIndex":13 + }, + { + "condiction":"playerId==22020005", + "fileIndex":14 + }, + { + "condiction":"playerId==22020006", + "fileIndex":15 + }, + { + "condiction":"playerId==22020007", + "fileIndex":16 + }, + { + "condiction":"playerId==22020008", + "fileIndex":17 + }, + { + "condiction":"playerId==22020009", + "fileIndex":18 + }, + { + "condiction":"playerId==33030001", + "fileIndex":19 + }, + { + "condiction":"playerId==33030002", + "fileIndex":20 + }, + { + "condiction":"playerId==33030003", + "fileIndex":21 + }, + { + "condiction":"playerId==33030004", + "fileIndex":22 + }, + { + "condiction":"playerId==33030005", + "fileIndex":23 + }, + { + "condiction":"playerId==33030006", + "fileIndex":24 + }, + { + "condiction":"playerId==33030007", + "fileIndex":25 + }, + { + "condiction":"playerId==33030008", + "fileIndex":26 + }, + { + "condiction":"playerId==33030009", + "fileIndex":27 + }, + { + "condiction":"playerId==1212121", + "fileIndex":28 + }, + { + "condiction":"playerId==12357817", + "fileIndex":29 + }, + { + "condiction":"playerId==11335577", + "fileIndex":30 + } + ], + "resultFiles": [ + "/mockup/http/get_player_profile_api_res1.json", + "/mockup/http/get_player_profile_api_res2.json", + "/mockup/http/get_player_profile_api_res3.json", + "/mockup/http/get_player_profile_api_res4.json", + "/mockup/http/get_player_profile_api_res5.json", + "/mockup/http/get_player_profile_api_res6.json", + "/mockup/http/get_player_profile_api_res7.json", + "/mockup/http/get_player_profile_api_res8.json", + "/mockup/http/get_player_profile_api_res9.json", + "/mockup/http/get_player_profile_api_res10.json", + "/mockup/http/get_player_profile_api_res11.json", + "/mockup/http/get_player_profile_api_res12.json", + "/mockup/http/get_player_profile_api_res13.json", + "/mockup/http/get_player_profile_api_res14.json", + "/mockup/http/get_player_profile_api_res15.json", + "/mockup/http/get_player_profile_api_res16.json", + "/mockup/http/get_player_profile_api_res17.json", + "/mockup/http/get_player_profile_api_res18.json", + "/mockup/http/get_player_profile_api_res19.json", + "/mockup/http/get_player_profile_api_res20.json", + "/mockup/http/get_player_profile_api_res21.json", + "/mockup/http/get_player_profile_api_res22.json", + "/mockup/http/get_player_profile_api_res23.json", + "/mockup/http/get_player_profile_api_res24.json", + "/mockup/http/get_player_profile_api_res25.json", + "/mockup/http/get_player_profile_api_res26.json", + "/mockup/http/get_player_profile_api_res27.json", + "/mockup/http/get_player_profile_api_res28.json", + "/mockup/http/get_player_profile_api_res29.json", + "/mockup/http/get_player_profile_api_res30.json", + "/mockup/http/get_player_profile_api_res31.json" + ] +}, +{ + "apiLink": "GetLoginUserInfo", + "resultIndex":0, + "mockupRules":[ + { + "condiction":"userId==90001", + "fileIndex":1 + }, + { + "condiction":"userId==90002", + "fileIndex":2 + }, + { + "condiction":"userId==90003", + "fileIndex":5 + }, + { + "condiction":"userId==90004", + "fileIndex":6 + }, + { + "condiction":"userId==90005", + "fileIndex":7 + }, + { + "condiction":"userId==90006", + "fileIndex":8 + }, + { + "condiction":"userId==90007", + "fileIndex":9 + }, + { + "condiction":"userId==90008", + "fileIndex":10 + }, + { + "condiction":"userId==90009", + "fileIndex":11 + }, + { + "condiction":"userId==80001", + "fileIndex":3 + }, + { + "condiction":"userId==80002", + "fileIndex":4 + }, + { + "condiction":"userId==80003", + "fileIndex":12 + }, + { + "condiction":"userId==80004", + "fileIndex":13 + }, + { + "condiction":"userId==80005", + "fileIndex":14 + }, + { + "condiction":"userId==80008", + "fileIndex":16 + } + ], + "resultFiles": [ + "/mockup/http/get_user_info_api_res1.json", + "/mockup/http/get_user_info_api_res2.json", + "/mockup/http/get_user_info_api_res3.json", + "/mockup/http/get_user_info_api_res4.json", + "/mockup/http/get_user_info_api_res5.json", + "/mockup/http/get_user_info_api_res6.json", + "/mockup/http/get_user_info_api_res7.json", + "/mockup/http/get_user_info_api_res8.json", + "/mockup/http/get_user_info_api_res9.json", + "/mockup/http/get_user_info_api_res10.json", + "/mockup/http/get_user_info_api_res11.json", + "/mockup/http/get_user_info_api_res12.json", + "/mockup/http/get_user_info_api_res13.json", + "/mockup/http/get_user_info_api_res14.json", + "/mockup/http/get_user_info_api_res15.json", + "/mockup/http/get_user_info_api_res16.json", + "/mockup/http/get_user_info_api_res17.json" + ] +}, +{ + "apiLink": "GetRoomBookingQualificationStatus", + "resultIndex":0, + "mockupRules":[ + { + "condiction":"playerId==11010001 || playerId==11010005 || playerId==11010008 || playerId==11010009 || playerId==22020001 || playerId==22020005 || playerId==22020008 || playerId==22020009 || playerId==33030001 || playerId==33030005 || playerId==33030008 || playerId==33030009 || playerId==1212121", + "fileIndex":0 + }, + { + "condiction":"playerId==11010002 || playerId==11010006 || playerId==22020002 || playerId==22020006 || playerId==33030002 || playerId==33030006", + "fileIndex":1 + }, + { + "condiction":"playerId==11010003 || playerId==11010007 || playerId==22020003 || playerId==22020007 || playerId==33030003 || playerId==33030007", + "fileIndex":2 + } + ], + "resultFiles": [ + "/mockup/http/check_player_tpd_all_pass.json", + "/mockup/http/check_player_tpd_not_all_pass.json", + "/mockup/http/check_player_tpd_partly_pass.json" + ] +}, +{ + "apiLink": "GetNonMGMRoomBookingRecord", + "resultIndex":0, + "mockupRules":[ + { + "condiction":"playerId==11010005", + "fileIndex":1 + } + ], + "resultFiles": [ + "/mockup/http/non_mgm_booking_no_record.json", + "/mockup/http/non_mgm_booking_with_record.json" + ] +}, +{ + "apiLink":"GetDashboardData", + "resultIndex":0, + "mockupRules":[ + { + "condiction":"UserId==80001", + "fileIndex":0 + }, + { + "condiction":"UserId==80002", + "fileIndex":1 + }, + { + "condiction":"UserId==80003", + "fileIndex":2 + }, + { + "condiction":"UserId==80004", + "fileIndex":3 + } + ], + "resultFiles":[ + "/mockup/app/getTodoDashboard_1.json", + "/mockup/app/getTodoDashboard_2.json", + "/mockup/app/getTodoDashboard_3.json", + "/mockup/app/getTodoDashboard_4.json" + ] +}, +{ + "apiLink":"GetTodolistDetails", + "resultIndex":0, + "mockupRules":[ + { + "condiction":"PlayerId==11010001", + "fileIndex":0 + }, + { + "condiction":"PlayerId==11010002", + "fileIndex":1 + }, + { + "condiction":"PlayerId==11010003", + "fileIndex":2 + } + ], + "resultFiles":[ + "/mockup/app/getTodoDetail_1.json", + "/mockup/app/getTodoDetail_2.json", + "/mockup/app/getTodoDetail_3.json" + ] +}, +{ + "apiLink":"GetTodolist", + "resultIndex":0, + "mockupRules":[ + { + "condiction":"TodoCategory == Arrival && TodoSegment == MASS", + "fileIndex":8 + }, + { + "condiction":"TodoCategory == Others", + "fileIndex":2 + }, + { + "condiction":"TodoCategory == Arrival && TodoSegment == IM", + "fileIndex":3 + }, + { + "condiction":"TodoCategory == Departure && TodoSegment == IM", + "fileIndex":4 + }, + { + "condiction":"TodoCategory == Inhouse && TodoSegment == MASS", + "fileIndex":5 + }, + { + "condiction":"TodoCategory == Inhouse && TodoSegment == IM", + "fileIndex":6 + }, + { + "condiction":"TodoCategory == Departure && TodoSegment == MASS", + "fileIndex":7 + } + ], + "resultFiles":[ + "/mockup/app/getTodoList_1.json", + "/mockup/app/getTodoList_2.json", + "/mockup/app/getTodoList_3.json", + "/mockup/app/getTodoList_4.json", + "/mockup/app/getTodoList_5.json", + "/mockup/app/getTodoList_6.json", + "/mockup/app/getTodoList_7.json", + "/mockup/app/getTodoList_8.json", + "/mockup/app/getTodoList_9.json" + ] +} + +] diff --git a/src/main/resources/mockup/system/login.json b/src/main/resources/mockup/system/login.json new file mode 100644 index 0000000..56aec66 --- /dev/null +++ b/src/main/resources/mockup/system/login.json @@ -0,0 +1,16 @@ +{ + "dataClass":"JSON", + "isList":false, + "dataContent":{ + "id":"admin", + "userCode":"admin", + "userName":"admin", + "roles":[ + { + "id":"admin_role", + "roleName":"admin role", + "roleCode":"ADMIN_ROLE" + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/static/WM.png b/src/main/resources/static/WM.png new file mode 100644 index 0000000000000000000000000000000000000000..e0b337233de35ed29470eb887a3dc330be59df04 GIT binary patch literal 8468 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 N8R!{8D|H>B{tuavN{;{l literal 0 HcmV?d00001 diff --git a/src/main/resources/static/WP.png b/src/main/resources/static/WP.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc32bad90c0293423a0ead2aa0f64c569b7f8fc GIT binary patch literal 10052 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

#%-V4(arr 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