This commit is contained in:
Mauve
2026-10-26 09:32:16 +08:00
parent 5b87fe58bc
commit 68f3daf02e
18 changed files with 546 additions and 63 deletions
+8 -3
View File
@@ -20,7 +20,7 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>21</java.version> <java.version>21</java.version>
<tomcat.version>11.0.25</tomcat.version> <tomcat.version>11.0.26</tomcat.version>
<netty.version>4.2.18.Final</netty.version> <netty.version>4.2.18.Final</netty.version>
<httpclient5.version>5.6.4</httpclient5.version> <httpclient5.version>5.6.4</httpclient5.version>
<httpclient5-win.version>5.2.3</httpclient5-win.version> <httpclient5-win.version>5.2.3</httpclient5-win.version>
@@ -29,6 +29,7 @@
<fasterxml.jackson.version>2.22.3</fasterxml.jackson.version> <fasterxml.jackson.version>2.22.3</fasterxml.jackson.version>
<tools.jackson.version>3.2.3</tools.jackson.version> <tools.jackson.version>3.2.3</tools.jackson.version>
<log4j2.version>2.26.1</log4j2.version> <log4j2.version>2.26.1</log4j2.version>
<hibernate-validator.version>9.2.0.Beta1</hibernate-validator.version>
<maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.target>${java.version}</maven.compiler.target>
</properties> </properties>
@@ -92,8 +93,12 @@
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId> <artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>4.0.0-M1</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
@@ -183,7 +188,7 @@
<dependency> <dependency>
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId> <artifactId>java-jwt</artifactId>
<version>4.6.1</version> <version>4.6.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.eatthepath</groupId> <groupId>com.eatthepath</groupId>
@@ -24,8 +24,7 @@ public enum EumnDaylightStatus {
DUEOUT("DUEOUT","DUE_OUT"), DUEOUT("DUEOUT","DUE_OUT"),
CANCELED("CANCELED","CANCELED"), CANCELED("CANCELED","CANCELED"),
CANCELLED("CANCELLED","CANCELED"), CANCELLED("CANCELLED","CANCELED"),
NO_SHOW("NO SHOW","CANCELED"), NO_SHOW("No-Show", "NO_SHOW");
NOSHOW("NOSHOW","CANCELED");
private String key; private String key;
private String value; private String value;
@@ -9,6 +9,7 @@ import com.mgm.mcrm.apigateway.daylight.repositories.CfgCompRuleDao;
import com.mgm.mcrm.apigateway.daylight.services.DaylightApiService; import com.mgm.mcrm.apigateway.daylight.services.DaylightApiService;
import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.AvailableRoomCountUpdatedHandler; import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.AvailableRoomCountUpdatedHandler;
import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.CompRuleLimitsExceededChangedHandler; import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.CompRuleLimitsExceededChangedHandler;
import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.ReservationCustomFieldsNotificationHandler;
import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.ReservationNotificationHandler; 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.eventhandlers.impl.RoomTypeDateAvailabilityChangedHandler;
import com.mgm.mcrm.apigateway.daylight.webhook.services.DaylightWebhookFactory; import com.mgm.mcrm.apigateway.daylight.webhook.services.DaylightWebhookFactory;
@@ -33,6 +34,8 @@ public class ConfigListener implements ApplicationListener<ApplicationStartedEve
private BookingService bookingService; private BookingService bookingService;
@Resource @Resource
private DaylightApiService daylightApiService; private DaylightApiService daylightApiService;
@Resource
private ReservationCustomFieldsNotificationHandler reservationCustomFieldsNotificationHandler;
@Autowired @Autowired
private SslBundles sslBundles; private SslBundles sslBundles;
@@ -49,6 +52,7 @@ public class ConfigListener implements ApplicationListener<ApplicationStartedEve
DaylightWebhookFactory.registerHandler("AvailableRoomCountUpdated", new AvailableRoomCountUpdatedHandler(roomCriteriaDaylightService)); DaylightWebhookFactory.registerHandler("AvailableRoomCountUpdated", new AvailableRoomCountUpdatedHandler(roomCriteriaDaylightService));
//--Register Reservation Notification handlers //--Register Reservation Notification handlers
DaylightWebhookFactory.registerHandler("ReservationNotification", new ReservationNotificationHandler(bookingService, daylightApiService)); DaylightWebhookFactory.registerHandler("ReservationNotification", new ReservationNotificationHandler(bookingService, daylightApiService));
DaylightWebhookFactory.registerHandler("ReservationCustomFieldsNotification", reservationCustomFieldsNotificationHandler);
try{ try{
HttpSslSupport.setSkipValidation(daylightApiSetting.isSkipTrustValidation()); HttpSslSupport.setSkipValidation(daylightApiSetting.isSkipTrustValidation());
@@ -6,13 +6,13 @@ import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController //@RestController
public class DaylightTestController { public class DaylightTestController {
@Resource @Resource
private DaylightFetchConfigurationService daylightFetchConfigurationService; private DaylightFetchConfigurationService daylightFetchConfigurationService;
@GetMapping("/test/fetch-daylight-config") // @GetMapping("/test/fetch-daylight-config")
public BaseApiResponse<Void> testFetchDaylightConfiguration() { public BaseApiResponse<Void> testFetchDaylightConfiguration() {
BaseApiResponse<Void> propertiesResponse = daylightFetchConfigurationService.fetchDaylightProperties(); BaseApiResponse<Void> propertiesResponse = daylightFetchConfigurationService.fetchDaylightProperties();
if (!propertiesResponse.isSuccess()) { if (!propertiesResponse.isSuccess()) {
@@ -4,8 +4,8 @@ import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse;
import com.mgm.mcrm.apigateway.daylight.common.services.DaylightFetchConfigurationService; import com.mgm.mcrm.apigateway.daylight.common.services.DaylightFetchConfigurationService;
import com.mgm.mcrm.apigateway.common.utils.LogUtil; import com.mgm.mcrm.apigateway.common.utils.LogUtil;
import com.mgm.mcrm.apigateway.daylight.repositories.CfgDaylightConfigurationDao; import com.mgm.mcrm.apigateway.daylight.repositories.CfgDaylightConfigurationDao;
import com.mgm.mcrm.apigateway.daylight.services.DaylightApiService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
@@ -13,14 +13,17 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@ConditionalOnProperty(
prefix = "daylight.scheduler",
name = "open",
havingValue = "true"
)
public class DaylightConfigurationScheduler { public class DaylightConfigurationScheduler {
@Resource @Resource
private DaylightFetchConfigurationService daylightFetchConfigurationService; private DaylightFetchConfigurationService daylightFetchConfigurationService;
@Resource @Resource
private CfgDaylightConfigurationDao cfgDaylightConfigurationDao; private CfgDaylightConfigurationDao cfgDaylightConfigurationDao;
@Resource
private DaylightApiService daylightApiService;
@Order(1) @Order(1)
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
@@ -93,6 +96,33 @@ public class DaylightConfigurationScheduler {
} }
} }
@Order(4)
@EventListener(ApplicationReadyEvent.class)
public void fetchResourceManagementRequestorsOnStartupIfMissing() {
LogUtil.info(DaylightConfigurationScheduler.class, "Checking saved Daylight resource requestors on startup");
try {
if (!cfgDaylightConfigurationDao.existsSavedDaylightProperties()) {
LogUtil.info(DaylightConfigurationScheduler.class, "No saved Daylight properties found, skip resource requestors startup fetch");
return;
}
if (cfgDaylightConfigurationDao.existsSavedDaylightResourceRequestors()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Saved Daylight resource requestors found, skip startup fetch");
return;
}
LogUtil.warn(DaylightConfigurationScheduler.class, "No saved Daylight resource requestors found, start startup fetch");
BaseApiResponse<Void> requestorResponse = daylightFetchConfigurationService.fetchResourceManagementRequestors();
if (requestorResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Startup resource requestors completed: " + requestorResponse.getMessage());
} else {
LogUtil.warn(DaylightConfigurationScheduler.class, "Startup resource requestors failed: " + requestorResponse.getMessage());
}
} catch (Exception e) {
LogUtil.error(DaylightConfigurationScheduler.class, "Startup resource requestors error: " + e.getMessage());
}
}
/** /**
* 按配置的时间和时区执行,拉取 Daylight 配置数据,默认北京时间每天凌晨2点 * 按配置的时间和时区执行,拉取 Daylight 配置数据,默认北京时间每天凌晨2点
*/ */
@@ -102,7 +132,7 @@ public class DaylightConfigurationScheduler {
LogUtil.info(DaylightConfigurationScheduler.class, "Start scheduled task: fetchDaylightAllData"); LogUtil.info(DaylightConfigurationScheduler.class, "Start scheduled task: fetchDaylightAllData");
try { try {
// 1. 拉取 Properties // 1. 拉取 Properties
LogUtil.info(DaylightConfigurationScheduler.class, "Step 1/3: Fetching properties..."); LogUtil.info(DaylightConfigurationScheduler.class, "Step 1/4: Fetching properties...");
BaseApiResponse<Void> propResponse = daylightFetchConfigurationService.fetchDaylightProperties(); BaseApiResponse<Void> propResponse = daylightFetchConfigurationService.fetchDaylightProperties();
if (propResponse.isSuccess()) { if (propResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Properties completed: " + propResponse.getMessage()); LogUtil.info(DaylightConfigurationScheduler.class, "Properties completed: " + propResponse.getMessage());
@@ -111,7 +141,7 @@ public class DaylightConfigurationScheduler {
} }
// 2. 拉取 Configuration // 2. 拉取 Configuration
LogUtil.info(DaylightConfigurationScheduler.class, "Step 2/3: Fetching configurations..."); LogUtil.info(DaylightConfigurationScheduler.class, "Step 2/4: Fetching configurations...");
BaseApiResponse<Void> configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration(); BaseApiResponse<Void> configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration();
if (configResponse.isSuccess()) { if (configResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Configurations completed: " + configResponse.getMessage()); LogUtil.info(DaylightConfigurationScheduler.class, "Configurations completed: " + configResponse.getMessage());
@@ -120,7 +150,7 @@ public class DaylightConfigurationScheduler {
} }
// 3. 拉取 Block Codes // 3. 拉取 Block Codes
LogUtil.info(DaylightConfigurationScheduler.class, "Step 3/3: Fetching block codes..."); LogUtil.info(DaylightConfigurationScheduler.class, "Step 3/4: Fetching block codes...");
BaseApiResponse<Void> blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes(); BaseApiResponse<Void> blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes();
if (blockResponse.isSuccess()) { if (blockResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Block codes completed: " + blockResponse.getMessage()); LogUtil.info(DaylightConfigurationScheduler.class, "Block codes completed: " + blockResponse.getMessage());
@@ -128,6 +158,15 @@ public class DaylightConfigurationScheduler {
LogUtil.warn(DaylightConfigurationScheduler.class, "Block codes failed: " + blockResponse.getMessage()); LogUtil.warn(DaylightConfigurationScheduler.class, "Block codes failed: " + blockResponse.getMessage());
} }
// 4. 拉取 Resource Requestors
LogUtil.info(DaylightConfigurationScheduler.class, "Step 4/4: Fetching resource requestors...");
BaseApiResponse<Void> requestorResponse = daylightFetchConfigurationService.fetchResourceManagementRequestors();
if (requestorResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Resource requestors completed: " + requestorResponse.getMessage());
} else {
LogUtil.warn(DaylightConfigurationScheduler.class, "Resource requestors failed: " + requestorResponse.getMessage());
}
LogUtil.info(DaylightConfigurationScheduler.class, "Scheduled task finished: fetchDaylightAllData"); LogUtil.info(DaylightConfigurationScheduler.class, "Scheduled task finished: fetchDaylightAllData");
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(DaylightConfigurationScheduler.class, "Scheduled task error: " + e.getMessage()); LogUtil.error(DaylightConfigurationScheduler.class, "Scheduled task error: " + e.getMessage());
@@ -1,6 +1,7 @@
package com.mgm.mcrm.apigateway.daylight.common.services; package com.mgm.mcrm.apigateway.daylight.common.services;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties;
import com.mgm.mcrm.apigateway.basesetting.property.repositories.PropertyDao; import com.mgm.mcrm.apigateway.basesetting.property.repositories.PropertyDao;
@@ -30,9 +31,12 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
@Service @Service
public class DaylightFetchConfigurationService { public class DaylightFetchConfigurationService {
@@ -41,6 +45,8 @@ public class DaylightFetchConfigurationService {
private static final DateTimeFormatter UTC_UPDATED_AT_FORMATTER = DateTimeFormatter private static final DateTimeFormatter UTC_UPDATED_AT_FORMATTER = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS") .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
.withZone(ZoneOffset.UTC); .withZone(ZoneOffset.UTC);
private static final String RESOURCE_REQUESTOR_CONFIG_NAME = "ResourceRequestor";
private static final int RESOURCE_REQUESTOR_PAGE_SIZE = 200;
private final HttpProxyErrorHandler httpErrorHandler = new HttpProxyErrorHandler(); private final HttpProxyErrorHandler httpErrorHandler = new HttpProxyErrorHandler();
@Resource @Resource
@@ -93,7 +99,7 @@ public class DaylightFetchConfigurationService {
boolean hasSavedConfig = cfgDaylightConfigurationDao.existsSavedConfigurationByDlPropertyId(dlPropertyId); boolean hasSavedConfig = cfgDaylightConfigurationDao.existsSavedConfigurationByDlPropertyId(dlPropertyId);
if (lastSync.isPresent() && hasSavedConfig) { if (lastSync.isPresent() && hasSavedConfig) {
updatedAtParam = "&updatedAt=" updatedAtParam = "&updatedAt="
+ UTC_UPDATED_AT_FORMATTER.format(lastSync.get().getLastSyncAt().toInstant()); + UTC_UPDATED_AT_FORMATTER.format(lastSync.get().getLastSyncAt().toInstant().minusSeconds(1));
log.info("Property {} incremental sync, updatedAt: {}", dlPropertyId, updatedAtParam); log.info("Property {} incremental sync, updatedAt: {}", dlPropertyId, updatedAtParam);
} else { } else {
if (lastSync.isPresent() && !hasSavedConfig) { if (lastSync.isPresent() && !hasSavedConfig) {
@@ -506,7 +512,7 @@ public class DaylightFetchConfigurationService {
.findByDlPropertyIdAndConfigName(dlPropertyId, "BlockCode"); .findByDlPropertyIdAndConfigName(dlPropertyId, "BlockCode");
if (lastSync.isPresent() && existingConfig != null) { if (lastSync.isPresent() && existingConfig != null) {
updatedAtParam = "&updatedAt=" updatedAtParam = "&updatedAt="
+ UTC_UPDATED_AT_FORMATTER.format(lastSync.get().getLastSyncAt().toInstant()); + UTC_UPDATED_AT_FORMATTER.format(lastSync.get().getLastSyncAt().toInstant().minusSeconds(1));
log.info("Property {} BlockCode incremental sync, updatedAt: {}", dlPropertyId, updatedAtParam); log.info("Property {} BlockCode incremental sync, updatedAt: {}", dlPropertyId, updatedAtParam);
} else { } else {
if (lastSync.isPresent() && existingConfig == null) { if (lastSync.isPresent() && existingConfig == null) {
@@ -635,4 +641,183 @@ public class DaylightFetchConfigurationService {
} }
return response; return response;
} }
/**
* 按 property 分页拉取资源请求人;首次全量同步,之后按成功同步时间增量合并。
*/
public BaseApiResponse<Void> fetchResourceManagementRequestors() {
BaseApiResponse<Void> 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<CfgProperties> allProperties = propertyDao.findByCategory("DAYLIGHT");
if (allProperties == null || allProperties.isEmpty()) {
response.setSuccess(false);
response.setMessage("No properties found with category 'DAYLIGHT'");
return response;
}
int savedCount = 0;
List<String> failedProperties = new ArrayList<>();
for (CfgProperties property : allProperties) {
String dlPropertyId = property.getDlPropertyId();
if (StringUtils.isBlank(dlPropertyId)) {
continue;
}
boolean fetchSuccess = false;
int recordCount = 0;
try {
CfgDaylightConfiguration existingConfig = cfgDaylightConfigurationDao
.findByDlPropertyIdAndConfigName(dlPropertyId, RESOURCE_REQUESTOR_CONFIG_NAME);
Optional<SysLogDaylightConfigurationSync> lastSync = syncLogDao
.findTopByConfigNameAndDlPropertyIdAndSyncStatusOrderByLastSyncAtDesc(
RESOURCE_REQUESTOR_CONFIG_NAME, dlPropertyId, "SUCCESS");
// 配置不存在时强制全量,两个日期条件均不传。
boolean isIncremental = existingConfig != null && lastSync.isPresent()
&& lastSync.get().getLastSyncAt() != null;
String dateParams = "";
if (isIncremental) {
String syncTime = DateTimeFormatter.ISO_INSTANT.format(lastSync.get().getLastSyncAt().toInstant().minusSeconds(1));
// 接口通过同一个 filter 接收两个日期条件,独立的日期 query 参数会被忽略。
dateParams = "&filter=auditData.lastUpdateTime=ge=" + syncTime
+ ",auditData.creationTime=ge=" + syncTime;
log.info("Property {} ResourceRequestor incremental sync, UTC time: {}", dlPropertyId, syncTime);
} else {
log.info("Property {} ResourceRequestor full sync, no audit date parameters", dlPropertyId);
}
List<JSONObject> allResults = fetchResourceRequestorPages(
dlPropertyId, daylightApiToken.getAccessToken(), dateParams, response);
List<JSONObject> oldItems = existingConfig != null
&& StringUtils.isNotBlank(existingConfig.getConfigValue())
? JSON.parseArray(existingConfig.getConfigValue(), JSONObject.class)
: new ArrayList<>();
// 所有页面成功后才保存,增量没有变化时保留原配置。
if (!isIncremental || !allResults.isEmpty()) {
List<JSONObject> savedItems = allResults;
if (isIncremental) {
Map<String, JSONObject> mergedMap = new LinkedHashMap<>();
for (JSONObject item : oldItems) {
mergedMap.put(getConfigItemKey(item), item);
}
for (JSONObject item : allResults) {
mergedMap.put(getConfigItemKey(item), item);
}
savedItems = new ArrayList<>(mergedMap.values());
}
CfgDaylightConfiguration config = existingConfig != null ? existingConfig : new CfgDaylightConfiguration();
config.setConfigName(RESOURCE_REQUESTOR_CONFIG_NAME);
config.setDlPropertyId(dlPropertyId);
if (config.getCreatedAt() == null) {
config.setCreatedAt(new Date());
}
config.setConfigValue(JSONObject.toJSONString(savedItems));
config.setUpdatedAt(new Date());
cfgDaylightConfigurationDao.save(config);
logConfigurationChanges("ResourceRequestor " + (isIncremental ? "incremental sync" : "full sync"),
dlPropertyId, RESOURCE_REQUESTOR_CONFIG_NAME, oldItems, savedItems);
savedCount++;
}
recordCount = allResults.size();
fetchSuccess = true;
} catch (Exception e) {
failedProperties.add(dlPropertyId);
log.warn("Property {} ResourceRequestor sync failed", dlPropertyId, e);
}
SysLogDaylightConfigurationSync syncLog = new SysLogDaylightConfigurationSync();
syncLog.setConfigName(RESOURCE_REQUESTOR_CONFIG_NAME);
syncLog.setDlPropertyId(dlPropertyId);
syncLog.setLastSyncAt(syncStartTime);
syncLog.setSyncStatus(fetchSuccess ? "SUCCESS" : "FAILED");
syncLog.setRecordCount(recordCount);
syncLog.setCreatedAt(new Date());
syncLogDao.save(syncLog);
}
response.setSuccess(failedProperties.isEmpty());
response.setMessage("Fetched and saved " + savedCount + " resource requestor configuration entries."
+ (failedProperties.isEmpty() ? "" : " Failed properties: " + StringUtils.join(failedProperties, ",")));
} catch (Exception e) {
log.warn("ResourceRequestor sync failed", e);
response.setSuccess(false);
response.setMessage(e.getMessage());
}
return response;
}
private List<JSONObject> fetchResourceRequestorPages(String dlPropertyId, String accessToken,
String dateParams, BaseApiResponse<Void> response) throws Exception {
String apiUrl = daylightApiSetting.getServerUrl() + "/api-gateway/resource-management/v1/resource";
HttpHeaders headers = new HttpHeaders();
headers.set("authorization", "Bearer " + accessToken);
headers.set("Ac-Property-Id", dlPropertyId);
List<JSONObject> allResults = new ArrayList<>();
int pageNum = 1;
Set<String> fetchedIds = new HashSet<>();
long totalCount;
do {
// 此接口的分页请求参数为 pageNumber;pageNum 会被忽略并重复返回第一页。
String queryString = "pageSize=" + RESOURCE_REQUESTOR_PAGE_SIZE + "&pageNumber=" + pageNum + dateParams;
log.info("Fetching ResourceRequestor page {} for Ac-Property-Id: {}, query: {}", pageNum, dlPropertyId, queryString);
ResponseEntity<byte[]> requestResponse = webProxyService.invokeRequest(
WebProxyService.DAYLIGHT_API, HttpMethod.GET, apiUrl, queryString, headers, new byte[0],
Duration.ofSeconds(daylightApiSetting.getApiTimeout()), httpErrorHandler).block();
if (requestResponse == null) {
throw new IllegalStateException("Http request server can not visit");
}
response.setStatusCode(requestResponse.getStatusCode().value());
if (!requestResponse.getStatusCode().is2xxSuccessful()) {
byte[] errorBody = requestResponse.getBody();
throw new IllegalStateException(errorBody != null
? new String(errorBody, StandardCharsets.UTF_8) : "ResourceRequestor request failed");
}
byte[] responseBody = requestResponse.getBody();
if (responseBody == null || responseBody.length == 0) {
throw new IllegalStateException("ResourceRequestor response body is empty");
}
JSONObject responseJson = JSONObject.parseObject(new String(responseBody, StandardCharsets.UTF_8));
JSONArray results = responseJson != null ? responseJson.getJSONArray("results") : null;
JSONObject paging = responseJson != null ? responseJson.getJSONObject("paging") : null;
Long pageTotalCount = paging != null ? paging.getLong("totalCount") : null;
if (results == null || pageTotalCount == null || pageTotalCount < 0) {
throw new IllegalStateException("ResourceRequestor response is missing valid results or paging.totalCount");
}
Integer responsePageNumber = paging.getInteger("pageNumber");
if (responsePageNumber != null && responsePageNumber != pageNum) {
throw new IllegalStateException("ResourceRequestor requested page " + pageNum
+ " but received page " + responsePageNumber + "; pagination parameters were not applied");
}
totalCount = pageTotalCount;
log.info("ResourceRequestor response for Ac-Property-Id: {}, requestedPage={}, responsePage={}, pageCount={}, totalCount={}",
dlPropertyId, pageNum, responsePageNumber, results.size(), totalCount);
if (results.isEmpty() && allResults.size() < totalCount) {
throw new IllegalStateException("ResourceRequestor returned an empty page before all records were fetched");
}
List<JSONObject> pageResults = results.toJavaList(JSONObject.class);
for (JSONObject item : pageResults) {
String id = item.getString("id");
if (StringUtils.isBlank(id) || !fetchedIds.add(id)) {
throw new IllegalStateException("ResourceRequestor returned a missing or repeated resource id on page " + pageNum);
}
}
allResults.addAll(pageResults);
} while ((long) pageNum++ * RESOURCE_REQUESTOR_PAGE_SIZE < totalCount);
if (allResults.size() != totalCount) {
throw new IllegalStateException("ResourceRequestor returned a record count different from paging.totalCount");
}
return allResults;
}
} }
@@ -17,7 +17,7 @@ public interface CfgDaylightConfigurationDao extends JpaRepository<CfgDaylightCo
@Query("select case when count(c) > 0 then true else false end " @Query("select case when count(c) > 0 then true else false end "
+ "from CfgDaylightConfiguration c " + "from CfgDaylightConfiguration c "
+ "where c.configName not in ('Property', 'BlockCode') " + "where c.configName not in ('Property', 'BlockCode', 'ResourceRequestor') "
+ "and c.configValue is not null " + "and c.configValue is not null "
+ "and c.configValue <> ''") + "and c.configValue <> ''")
boolean existsSavedDaylightConfiguration(); boolean existsSavedDaylightConfiguration();
@@ -25,7 +25,7 @@ public interface CfgDaylightConfigurationDao extends JpaRepository<CfgDaylightCo
@Query("select case when count(c) > 0 then true else false end " @Query("select case when count(c) > 0 then true else false end "
+ "from CfgDaylightConfiguration c " + "from CfgDaylightConfiguration c "
+ "where c.dlPropertyId = ?1 " + "where c.dlPropertyId = ?1 "
+ "and c.configName not in ('Property', 'BlockCode') " + "and c.configName not in ('Property', 'BlockCode', 'ResourceRequestor') "
+ "and c.configValue is not null " + "and c.configValue is not null "
+ "and c.configValue <> ''") + "and c.configValue <> ''")
boolean existsSavedConfigurationByDlPropertyId(String dlPropertyId); boolean existsSavedConfigurationByDlPropertyId(String dlPropertyId);
@@ -36,4 +36,11 @@ public interface CfgDaylightConfigurationDao extends JpaRepository<CfgDaylightCo
+ "and c.configValue is not null " + "and c.configValue is not null "
+ "and c.configValue <> ''") + "and c.configValue <> ''")
boolean existsSavedDaylightBlockCodes(); boolean existsSavedDaylightBlockCodes();
@Query("select case when count(c) > 0 then true else false end "
+ "from CfgDaylightConfiguration c "
+ "where c.configName = 'ResourceRequestor' "
+ "and c.configValue is not null "
+ "and c.configValue <> ''")
boolean existsSavedDaylightResourceRequestors();
} }
@@ -58,7 +58,7 @@ public class DaylightApiService {
BaseApiResponse<JSONObject> result = new BaseApiResponse<>(); BaseApiResponse<JSONObject> result = new BaseApiResponse<>();
try { try {
String url = "/api-gateway/booking/v1/reservations/" + dlReservationId + "?extend=PurchaseElements,CustomFields"; String url = "/api-gateway/booking/v1/reservations/" + dlReservationId + "?extend=CustomFields";
// 获取对应的值 // 获取对应的值
Header[] headers = HttpHeaderBuilder.custom() Header[] headers = HttpHeaderBuilder.custom()
.contentType("application/json") .contentType("application/json")
@@ -0,0 +1,24 @@
package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation;
import lombok.Data;
import java.util.List;
@Data
public class ReservationCustomFieldsNotificationEvent {
private String reservationId;
private String trigger;
private CustomFieldsState previous;
private CustomFieldsState current;
@Data
public static class CustomFieldsState {
private List<CustomField> customFields;
}
@Data
public static class CustomField {
private String location;
private String value;
}
}
@@ -0,0 +1,19 @@
package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
@Data
public class ReservationHeaderCustomFields {
@JSONField(name = "Booker")
private String booker;
@JSONField(name = "Approver")
private String approver;
@JSONField(name = "App_Rea")
private String approvalReason;
@JSONField(name = "WAIVEDEPOSIT", alternateNames = {"WAIVEDPOSIT"})
private String waiveDeposit;
}
@@ -0,0 +1,107 @@
package com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl;
import com.alibaba.fastjson2.JSONObject;
import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse;
import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationCustomFieldsNotificationEvent;
import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationCustomFieldsNotificationEvent.CustomField;
import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationHeaderCustomFields;
import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler;
import com.mgm.mcrm.apigateway.reservation.models.Bookings;
import com.mgm.mcrm.apigateway.reservation.repositories.BookingDao;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@RequiredArgsConstructor
public class ReservationCustomFieldsNotificationHandler implements DaylightWebhookHandler {
private final BookingDao bookingDao;
@Override
public BaseApiResponse<Void> handle(String payload, Map<String, String> headers) throws InterruptedException {
BaseApiResponse<Void> result = new BaseApiResponse<>();
ReservationCustomFieldsNotificationEvent eventData;
try {
eventData = JSONObject.parseObject(payload, ReservationCustomFieldsNotificationEvent.class);
} catch (Exception err) {
result.setMessage("Convert Payload failed:[" + err.getMessage() + "]");
return result;
}
if (eventData == null || StringUtils.isBlank(eventData.getReservationId())) {
result.setMessage("Empty reservationId for this event");
return result;
}
try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException err) {
Thread.currentThread().interrupt();
throw err;
}
String reservationId = eventData.getReservationId();
List<Bookings> bookings = bookingDao.findByDlReservationId(reservationId);
Bookings booking = null;
if (!CollectionUtils.isEmpty(bookings)) {
for (Bookings candidate : bookings) {
if ("N".equals(candidate.getApproveStatus())) {
booking = candidate;
break;
}
}
}
if (booking == null) {
log.warn("No booking with approveStatus=N for reservationId={}", reservationId);
result.setMessage("No booking with approveStatus=N for reservationId[" + reservationId + "]");
return result;
}
if (eventData.getCurrent() == null || CollectionUtils.isEmpty(eventData.getCurrent().getCustomFields())) {
result.setMessage("Empty current.customFields for this event");
return result;
}
CustomField reservationHeader = eventData.getCurrent().getCustomFields().stream()
.filter(field -> field != null && "ReservationHeader".equals(field.getLocation()))
.findFirst()
.orElse(null);
if (reservationHeader == null || StringUtils.isBlank(reservationHeader.getValue())) {
result.setMessage("Empty ReservationHeader value for this event");
return result;
}
ReservationHeaderCustomFields customFields;
Long approvedId;
try {
customFields = JSONObject.parseObject(reservationHeader.getValue(), ReservationHeaderCustomFields.class);
if (customFields == null) {
result.setMessage("Empty ReservationHeader value for this event");
return result;
}
approvedId = StringUtils.isBlank(customFields.getApprover())
? null : Long.valueOf(customFields.getApprover().trim());
} catch (Exception err) {
result.setMessage("Convert ReservationHeader value failed:[" + err.getMessage() + "]");
return result;
}
booking.setCreatedBy(customFields.getBooker());
booking.setBooker(customFields.getBooker());
booking.setApprovalReason(customFields.getApprovalReason());
booking.setApprovedId(approvedId);
bookingDao.save(booking);
log.info("Updated reservation custom fields for bookingId={}, reservationId={}", booking.getId(), reservationId);
result.setSuccess(true);
return result;
}
}
@@ -10,11 +10,13 @@ import com.mgm.mcrm.apigateway.daylight.services.DaylightApiService;
import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationNotificationEvent; 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.dtos.reservation.StatusCodeEntity;
import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler; import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler;
//import com.mgm.mcrm.apigateway.logging.services.SysBookingCacheService;
import com.mgm.mcrm.apigateway.reservation.models.Bookings; import com.mgm.mcrm.apigateway.reservation.models.Bookings;
import com.mgm.mcrm.apigateway.reservation.services.BookingService; import com.mgm.mcrm.apigateway.reservation.services.BookingService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -32,6 +34,8 @@ public class ReservationNotificationHandler implements DaylightWebhookHandler {
private final DaylightApiService daylightApiService; private final DaylightApiService daylightApiService;
// private final SysBookingCacheService sysBookingCacheService;
@Value("${daylight.system-id}") @Value("${daylight.system-id}")
private String systemId; private String systemId;
@@ -103,7 +107,17 @@ public class ReservationNotificationHandler implements DaylightWebhookHandler {
StatusCodeEntity statusCode = eventData.getStatusCode(); StatusCodeEntity statusCode = eventData.getStatusCode();
if(statusCode!=null) { if(statusCode!=null) {
result.setLastOperaStatus(EumnDaylightStatus.getStatusByDaylightStatus(statusCode.getDescription().toUpperCase())); String lastOperaStatus = EumnDaylightStatus.getStatusByDaylightStatus(statusCode.getDescription().toUpperCase());
result.setLastOperaStatus(lastOperaStatus);
if (Strings.CI.equalsAny(lastOperaStatus, "RESERVED", "CHECKED_IN", "DUE_IN")) {
result.setApproveStatus("N");
} else if ("CANCELED".equalsIgnoreCase(lastOperaStatus)) {
result.setApproveStatus("C");
} else {
result.setApproveStatus("N");
}
} }
result.setDlPropertyId(eventData.getPropertyId()); result.setDlPropertyId(eventData.getPropertyId());
result.setDlRoomTypeId(eventData.getRoomTypeId()); result.setDlRoomTypeId(eventData.getRoomTypeId());
@@ -113,49 +127,53 @@ public class ReservationNotificationHandler implements DaylightWebhookHandler {
String etaTime = dateTime.format(DateTimeFormatter.ofPattern("HH:mm")); String etaTime = dateTime.format(DateTimeFormatter.ofPattern("HH:mm"));
result.setEta(etaTime); result.setEta(etaTime);
} }
BaseApiResponse<JSONObject> response = // BaseApiResponse<JSONObject> response =
daylightApiService.getDaylightReservationByDlId(eventData.getReservationId(), eventData.getPropertyId()); // daylightApiService.getDaylightReservationByDlId(eventData.getReservationId(), eventData.getPropertyId());
//
if (response.isSuccess() && Objects.nonNull(response.getData())) { // if (response.isSuccess()) {
JSONObject embedded = response.getData().getJSONObject("_embedded"); // if (Objects.nonNull(response.getData())) {
if (embedded != null) { // JSONObject embedded = response.getData().getJSONObject("_embedded");
JSONArray customFields = embedded.getJSONArray("customFields"); // if (embedded != null) {
// JSONArray customFields = embedded.getJSONArray("customFields");
if (customFields != null) { //
for (int j = 0; j < customFields.size(); j++) { // if (customFields != null) {
JSONObject jsonObject = customFields.getJSONObject(j); // for (int j = 0; j < customFields.size(); j++) {
String value = jsonObject.getString("value"); // JSONObject jsonObject = customFields.getJSONObject(j);
JSONObject valueJson = JSONObject.parseObject(value); // String value = jsonObject.getString("value");
String booker = valueJson.getString("Booker"); // JSONObject valueJson = JSONObject.parseObject(value);
// String booker = valueJson.getString("Booker");
if (StringUtils.isNotBlank(booker)) { //
result.setCreatedBy(booker); // if (StringUtils.isNotBlank(booker)) {
result.setBooker(booker); // result.setCreatedBy(booker);
} // result.setBooker(booker);
} // }
} // }
} // }
// }
if (StringUtils.isBlank(result.getDlExternalNo())) { //
JSONArray jsonArray = response.getData().getJSONArray("externalIds"); // if (StringUtils.isBlank(result.getDlExternalNo())) {
if (!CollectionUtils.isEmpty(jsonArray)) { // JSONArray jsonArray = response.getData().getJSONArray("externalIds");
// if (!CollectionUtils.isEmpty(jsonArray)) {
for (int i = 0; i < jsonArray.size(); i++) { //
JSONObject jsonObject = jsonArray.getJSONObject(i); // for (int i = 0; i < jsonArray.size(); i++) {
// JSONObject jsonObject = jsonArray.getJSONObject(i);
if (jsonObject.getString("systemId").equals(systemId)) { //
result.setDlExternalNo(jsonObject.getString("number")); // if (jsonObject.getString("systemId").equals(systemId)) {
break; // result.setDlExternalNo(jsonObject.getString("number"));
} // break;
} // }
// }
if (StringUtils.isBlank(result.getDlExternalNo())) { //
String dlExternalNo = jsonArray.getJSONObject(0).getString("number"); // if (StringUtils.isBlank(result.getDlExternalNo())) {
result.setDlExternalNo(dlExternalNo); // String dlExternalNo = jsonArray.getJSONObject(0).getString("number");
} // result.setDlExternalNo(dlExternalNo);
} // }
} // }
} // }
// } else {
// log.warn("reservationId={} not found _embedded", eventData.getReservationId());
// }
// }
return result; return result;
} }
@@ -0,0 +1,20 @@
package com.mgm.mcrm.apigateway.logging.models;
import jakarta.persistence.*;
import lombok.Data;
@Data
@Entity
@Table(name="sys_booking_profile_mapping")
public class SysBookingProfileMapping {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="category")
private String category;
@Column(name="player_id")
private String playerId;
@Column(name="profile_id")
private String profileId;
}
@@ -0,0 +1,15 @@
package com.mgm.mcrm.apigateway.logging.repositories;
import com.mgm.mcrm.apigateway.logging.models.SysBookingProfileMapping;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface SysBookingProfileMappingDao extends JpaRepository<SysBookingProfileMapping, Long> {
List<SysBookingProfileMapping> findAllByCategoryAndPlayerId(String category, String playerId);
List<String> findByProfileId(String profileId);
}
@@ -0,0 +1,35 @@
package com.mgm.mcrm.apigateway.logging.services;
import com.mgm.mcrm.apigateway.logging.repositories.SysBookingProfileMappingDao;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
@Service
public class SysBookingCacheService {
@Value("${mcrm.daylight.booking-search-cache-seconds}")
private int daylightCacheSeconds;
@Resource
private SysBookingProfileMappingDao sysBookingProfileMappingDao;
public String getPlayerIdFromProfileId(String profileId) {
if (StringUtils.isEmpty(profileId)) {
return null;
}
List<String> result = sysBookingProfileMappingDao.findByProfileId(profileId);
if (CollectionUtils.isEmpty(result)) {
return null;
}
return result.getFirst();
}
}
@@ -105,6 +105,8 @@ public class Bookings implements Serializable {
private String mscRemark; private String mscRemark;
@Column(name="approval_remark") @Column(name="approval_remark")
private String approvalRemark; private String approvalRemark;
@Column(name="approval_reason")
private String approvalReason;
@Column(name="room") @Column(name="room")
private String room; private String room;
@Column(name="sharer") @Column(name="sharer")
@@ -396,7 +396,7 @@ public class BookingService {
// book.setGuaranteeType("CGTD"); // book.setGuaranteeType("CGTD");
dbBooking.setPaymentType(null); dbBooking.setPaymentType(null);
// book.setOperaProfileId(operaBooking.getOperaProfileId()); // book.setOperaProfileId(operaBooking.getOperaProfileId());
dbBooking.setApproveStatus("N"); dbBooking.setApproveStatus(daylightBooking.getApproveStatus());
dbBooking.setCategory("DAYLIGHT"); dbBooking.setCategory("DAYLIGHT");
dbBooking.setCreatedAt(DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss")); dbBooking.setCreatedAt(DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss"));
+4
View File
@@ -141,6 +141,8 @@ mcrm:
default-send-to: 201249946@qq.com default-send-to: 201249946@qq.com
security: security:
authorization-data: "[MCRM_APP/securItykeyOfCrmapp87895312],[MCRM_CMS/seCuritykeyOfCrmCms8wetw432]" authorization-data: "[MCRM_APP/securItykeyOfCrmapp87895312],[MCRM_CMS/seCuritykeyOfCrmCms8wetw432]"
daylight:
booking-search-cache-seconds: 300
server: server:
port: 9087 port: 9087
referrer-policy: origin-when-cross-origin referrer-policy: origin-when-cross-origin
@@ -179,6 +181,8 @@ apns:
daylight: daylight:
scheduler: scheduler:
# Enable startup fetches and scheduled tasks. Restart after changes.
open: false
# Cron fields: second minute hour day-of-month month day-of-week. Restart after changes. # Cron fields: second minute hour day-of-month month day-of-week. Restart after changes.
configuration-cron: "0 0 2 * * ?" configuration-cron: "0 0 2 * * ?"
configuration-zone: "Asia/Shanghai" configuration-zone: "Asia/Shanghai"