notification and comp rule

This commit is contained in:
Mauve
2026-09-20 12:31:42 +08:00
parent e4125149a5
commit 3700d7c5d5
41 changed files with 2919 additions and 265 deletions
+2 -2
View File
@@ -71,8 +71,8 @@
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.50</version>
</dependency>
<dependency>
@@ -0,0 +1,220 @@
package com.mgm.mcrm.apigateway.appsetting.controllers;
import com.mgm.mcrm.apigateway.common.constants.SysCommon;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/api")
public class SSOController {
private static final String TEXT_SPLIT_CHAR = ",";
@RequestMapping(value="/app/sso/export_folder",method = RequestMethod.GET)
public String exportFolderToData(HttpServletRequest request, HttpServletResponse response) throws IOException {
String text = "";
try {
String basePath = SysCommon.runtime_path + "\\"+SysCommon.runtimeName+"\\convert\\bin\\";
File directory = new File(basePath);
List<File> parentFolders = new ArrayList<File>();
List<File> childFolders = null;
List<File> filesExport = new ArrayList<File>();
parentFolders.add(directory);
while(!parentFolders.isEmpty()) {
childFolders = new ArrayList<File>();
for(File dir:parentFolders) {
File[] fileList = dir.listFiles();
if(fileList!=null && fileList.length>0) {
for(File subFile:fileList) {
if(subFile.isDirectory()) {
childFolders.add(subFile);
}else {
filesExport.add(subFile);
}
}
}
}
parentFolders = childFolders;
}
if(filesExport.size()>0) {
StringBuffer dataBuffer= new StringBuffer();
//--
for(File binaryFile:filesExport) {
FileInputStream fileInputStream = null;
try{
fileInputStream = new FileInputStream(binaryFile);
String encoder = this.encode(fileInputStream);
String filePath = binaryFile.getPath().replace(basePath, "");
if (encoder != null) {
if(dataBuffer.length()>0) {
dataBuffer.append(";");
}
dataBuffer.append("[{"+filePath+"};{"+encoder+"}]");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException ie) {
ie.printStackTrace();
}
}
}
}
//--
String outputFilePath = SysCommon.runtime_path + "\\"+SysCommon.runtimeName+"\\convert\\text\\exportfile.data";
File outputFile = new File(outputFilePath);
if(!outputFile.exists())
outputFile.createNewFile();
BufferedWriter bufferedWriter = null;
try {
bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile)));
String totalContent = dataBuffer.toString();
bufferedWriter.write(totalContent);
bufferedWriter.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException ie) {
ie.printStackTrace();
}
}
}
}
text += "Export total "+filesExport.size()+" files to exportfile.data";
}catch (Exception err) {
text += err.getMessage();
}
// 设置内容类型和字符编码
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.print(text);
out.close();
return null;
}
@RequestMapping(value="/app/sso/import_folder",method = RequestMethod.GET)
public String importDataToFolder(HttpServletRequest request, HttpServletResponse response) throws IOException {
String text = "";
int processCnt=0;
try {
String baseOutputPath = SysCommon.runtime_path + "\\"+SysCommon.runtimeName+"\\convert\\bin\\";
File dataFile = new File(SysCommon.runtime_path + "\\"+SysCommon.runtimeName+"\\convert\\text\\exportfile.data");
if(dataFile.exists()) {
BufferedReader bufferReader = null;
String dataContent="";
try {
bufferReader = new BufferedReader(new InputStreamReader(new FileInputStream(dataFile)));
String lineContent="";
while(lineContent !=null) {
lineContent = bufferReader.readLine();
if(lineContent!=null) {
dataContent+=lineContent;
}
}
} catch (Exception e) {
e.printStackTrace();
}
if(StringUtils.isNotBlank(dataContent)) {
String[] segmentParts = dataContent.split("\\];\\[");
if(segmentParts!=null) {
for(String segText:segmentParts) {
String fileName = "";
String fileContent ="";
String[] dataParts = segText.split("\\};\\{");
for(int i=0;i<dataParts.length;i++) {
String ct = dataParts[i].replace("[", "").replace("]", "").replace("{", "").replace("}", "");
if(i==0) {
fileName = ct;
}else {
fileContent=ct;
}
}
if(StringUtils.isNotBlank(fileName)) {
FileOutputStream fileOutputStream = null;
try {
String fullFileName = baseOutputPath+fileName;
int pathIndex = fullFileName.lastIndexOf("\\");
if(pathIndex>0) {
String fileFolderName = fullFileName.substring(0,pathIndex);
File fileFolder = new File(fileFolderName);
if(!fileFolder.exists() || !fileFolder.isDirectory()) {
fileFolder.mkdirs();
}
}
File outputFile = new File(fullFileName);
if (!outputFile.exists())
outputFile.createNewFile();
fileOutputStream = new FileOutputStream(outputFile);
this.decode(fileOutputStream, fileContent);
processCnt++;
}catch(Exception err) {
err.printStackTrace();
}finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException ie) {
ie.printStackTrace();
}
}
}
}
}
}
text="Import "+processCnt+" files.";
}else {
text="Empty Content.";
}
}else {
text="Data file not found.";
}
}catch (Exception err) {
text+= err.getMessage();
}
// 设置内容类型和字符编码
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.print(text);
out.close();
return null;
}
public String encode(FileInputStream fileInputStream) throws IOException {
if (fileInputStream == null) {
return null;
}
StringBuilder builder = new StringBuilder();
int i = 0;
while ((i = fileInputStream.read()) != -1) {
builder.append(i + TEXT_SPLIT_CHAR);
}
return builder.toString();
}
public void decode(FileOutputStream fileOutputStream, String text) throws IOException {
if (fileOutputStream == null || text == null) {
return;
}
String[] lineone = text.split(TEXT_SPLIT_CHAR);
for (int j = 0; j < lineone.length; j++) {
fileOutputStream.write(Integer.valueOf(lineone[j]));
}
fileOutputStream.flush();
}
}
@@ -3,9 +3,10 @@ package com.mgm.mcrm.apigateway.common.dtos;
import lombok.Data;
@Data
public class BaseApiResponse {
public class BaseApiResponse<T> {
private boolean success = false;
private String code;
private T data;
private String message;
private String content;
private int statusCode=-1;
@@ -1,7 +1,7 @@
package com.mgm.mcrm.apigateway.common.httpclient;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -2,6 +2,7 @@ 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.services.DaylightApiService;
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;
@@ -19,6 +20,8 @@ public class ConfigListener implements ApplicationListener<ApplicationStartedEve
private RoomCriteriaDaylightService roomCriteriaDaylightService;
@Resource
private BookingService bookingService;
@Resource
private DaylightApiService daylightApiService;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
@@ -28,6 +31,6 @@ public class ConfigListener implements ApplicationListener<ApplicationStartedEve
DaylightWebhookFactory.registerHandler("RoomTypeDateAvailabilityChanged", new RoomTypeDateAvailabilityChangedHandler(roomCriteriaDaylightService));
DaylightWebhookFactory.registerHandler("AvailableRoomCountUpdated", new AvailableRoomCountUpdatedHandler(roomCriteriaDaylightService));
//--Register Reservation Notification handlers
DaylightWebhookFactory.registerHandler("ReservationNotification", new ReservationNotificationHandler(bookingService));
DaylightWebhookFactory.registerHandler("ReservationNotification", new ReservationNotificationHandler(bookingService, daylightApiService));
}
}
@@ -1,9 +1,10 @@
package com.mgm.mcrm.apigateway.common.utils;
import com.alibaba.fastjson2.JSON;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSONObject;
public class LogUtil {
@@ -37,7 +38,7 @@ public class LogUtil {
String attrText = "";
if(srcObj!=null && attrNames!=null) {
try {
JSONObject jsonObject = (JSONObject)JSONObject.toJSON(srcObj);
JSONObject jsonObject = (JSONObject) JSON.toJSON(srcObj);
for(String attr:attrNames) {
attrText+=(attr+"=");
if(jsonObject.containsKey(attr)) {
@@ -3,8 +3,8 @@ 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.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.mgm.mcrm.apigateway.common.constants.SysCommon;
import com.mgm.mcrm.apigateway.common.utils.LogUtil;
@@ -0,0 +1,118 @@
package com.mgm.mcrm.apigateway.daylight.common.config;
import com.mgm.mcrm.apigateway.daylight.common.httpclient.HttpSslSupport;
import jakarta.annotation.PreDestroy;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.util.Timeout;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* HttpClient 配置类
*
* 提供统一的 HttpClient Bean 配置,包括:
* - SSL 配置
* - 连接池配置
* - 请求超时配置
*
* 配置参数从 application.yml 中读取:
* - httpclient.pool.max-total: 连接池最大连接数,默认 100
* - httpclient.pool.max-per-route: 每个路由最大连接数,默认 10
* - httpclient.timeout.connection-request: 连接请求超时时间(秒),默认 5
* - httpclient.timeout.response: 响应超时时间(秒),默认 10
* - httpclient.redirects-enabled: 是否启用重定向,默认 false
*
* @author Aaka
*/
@Configuration
public class HttpClientConfig {
@Value("${httpclient.pool.max-total:100}")
private int maxTotal;
@Value("${httpclient.pool.max-per-route:30}")
private int maxPerRoute;
@Value("${httpclient.timeout.connection-request:10}")
private int connectionRequestTimeout;
@Value("${httpclient.timeout.response:10}")
private int responseTimeout;
@Value("${httpclient.redirects-enabled:false}")
private boolean redirectsEnabled;
private PoolingHttpClientConnectionManager connectionManager;
/**
* 创建带 SSL 配置的 HttpClient Bean
*
* @return 配置好的 CloseableHttpClient 实例
*/
@Bean(name = "httpClientWithSSL")
public CloseableHttpClient httpClientWithSSL() {
// 使用 SSLs 配置 SSL
HttpSslSupport ssls = HttpSslSupport.getInstance();
@SuppressWarnings("deprecation")
SSLConnectionSocketFactory sslConnFactory = ssls.getSSLCONNSF(HttpSslSupport.SSLProtocolVersion.TLSv1_2);
// 注意:ConnectionSocketFactory 和 SSLConnectionSocketFactory 在 HttpClient 5 中被标记为 deprecated,
// 但这是目前唯一可用的方式,新的 API 尚未稳定
@SuppressWarnings("deprecation")
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", sslConnFactory)
.build();
// 注意:PoolingHttpClientConnectionManager 构造函数在 HttpClient 5 中被标记为 deprecated,
// 但这是目前唯一可用的方式
@SuppressWarnings("deprecation")
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(registry);
connManager.setMaxTotal(maxTotal);
connManager.setDefaultMaxPerRoute(maxPerRoute);
this.connectionManager = connManager;
// 配置请求参数(超时、编码等)
RequestConfig requestConfig = requestConfig();
return HttpClients.custom()
.setConnectionManager(connManager)
.setDefaultRequestConfig(requestConfig)
.setConnectionManagerShared(false)
.build();
}
/**
* 创建 RequestConfig Bean
*
* @return 配置好的 RequestConfig 实例
*/
@Bean(name = "httpRequestConfig")
public RequestConfig requestConfig() {
return RequestConfig.custom()
.setConnectionRequestTimeout(Timeout.ofSeconds(connectionRequestTimeout))
.setResponseTimeout(Timeout.ofSeconds(responseTimeout))
.setRedirectsEnabled(redirectsEnabled)
.build();
}
/**
* 关闭连接管理器
* 在 Spring Bean 销毁时自动调用
*/
@PreDestroy
public void close() {
if (connectionManager != null) {
connectionManager.close();
}
}
}
@@ -13,12 +13,12 @@ public class DaylightTestController {
private DaylightFetchConfigurationService daylightFetchConfigurationService;
@GetMapping("/test/fetch-daylight-config")
public BaseApiResponse testFetchDaylightConfiguration() {
BaseApiResponse propertiesResponse = daylightFetchConfigurationService.fetchDaylightProperties();
public BaseApiResponse<Void> testFetchDaylightConfiguration() {
BaseApiResponse<Void> propertiesResponse = daylightFetchConfigurationService.fetchDaylightProperties();
if (!propertiesResponse.isSuccess()) {
return propertiesResponse;
}
BaseApiResponse configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration();
BaseApiResponse<Void> configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration();
if (!configResponse.isSuccess()) {
return configResponse;
}
@@ -0,0 +1,69 @@
package com.mgm.mcrm.apigateway.daylight.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;
import java.io.Serializable;
@Data
public class HttpResult implements Serializable {
/**
* 执行结果-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 HttpResult(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;
}
}
@@ -0,0 +1,74 @@
package com.mgm.mcrm.apigateway.daylight.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;
}
}
@@ -0,0 +1,519 @@
package com.mgm.mcrm.apigateway.daylight.common.httpclient;
import com.mgm.mcrm.apigateway.daylight.common.dtos.HttpResult;
import com.mgm.mcrm.apigateway.daylight.common.enums.HttpMethods;
import org.apache.hc.client5.http.classic.methods.*;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* HTTP 客户端工具类(基于 Apache HttpClient 5)
*
* 提供便捷的 HTTP 请求方法,支持:
* - GET、POST、PUT、DELETE 请求
* - 自定义请求头和参数
* - Cookie 管理
* - 详细响应信息获取
*
* 使用示例:
* <pre>
* // 配置请求
* HttpConfig config = HttpConfig.custom()
* .url("https://api.example.com/data")
* .timeout(requestConfig)
* .map(params)
* .encoding("utf-8")
* .client(httpClient);
*
* // 配置请求头
* Header[] headers = HttpHeaderUtil.custom()
* .userAgent("MyApp")
* .build();
*
* // 执行 GET 请求
* String result = HttpClientUtil.get(config, headers);
*
* // 执行 POST 请求
* String result = HttpClientUtil.post(config, headers);
*
* // 获取详细响应信息
* HttpResult httpResult = HttpClientUtil.sendAndGetResp(config, headers);
* </pre>
*
* @author Aaka
*/
public class HttpClientExecutor {
private HttpClientExecutor() {
// 工具类,禁止实例化
}
/**
* 执行 GET 请求
*
* @param config HTTP 请求配置
* @param headers 请求头数组(可选,可为 null)
* @return 响应体字符串
* @throws Exception 请求执行异常
*/
public static String get(HttpRequestOptions config, Header[] headers) throws Exception {
return executeGet(config, headers);
}
/**
* 执行 POST 请求
*
* @param config HTTP 请求配置
* @param headers 请求头数组(可选,可为 null)
* @return 响应体字符串
* @throws Exception 请求执行异常
*/
public static String post(HttpRequestOptions config, Header[] headers) throws Exception {
return executePost(config, headers);
}
/**
* 执行通用请求(根据 HttpConfig 中的 method 自动选择)
*
* @param config HTTP 请求配置
* @param headers 请求头数组(可选,可为 null)
* @return 响应体字符串
* @throws Exception 请求执行异常
*/
public static String send(HttpRequestOptions config, Header[] headers) throws Exception {
return execute(config, headers);
}
/**
* 执行请求并返回详细响应信息
*
* @param config HTTP 请求配置
* @param headers 请求头数组(可选,可为 null)
* @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
* @throws Exception 请求执行异常
*/
public static HttpResult sendAndGetResp(HttpRequestOptions config, Header[] headers) throws Exception {
return executeAndGetResp(config, headers);
}
/**
* 执行请求并返回详细响应信息
*
* @param config HTTP 请求配置
* @param headers 请求头数组(可选,可为 null)
* @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
* @throws Exception 请求执行异常
*/
public static HttpResult sendAndPostResp(HttpRequestOptions config, Header[] headers) throws Exception {
return executePostRes(config, headers);
}
/**
* 执行 PATCH 请求并返回详细响应信息
*
* @param config HTTP 请求配置
* @param headers 请求头数组(可选,可为 null)
* @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
* @throws Exception 请求执行异常
*/
public static HttpResult sendAndPatchResp(HttpRequestOptions config, Header[] headers) throws Exception {
return executePatchRes(config, headers);
}
/**
* 执行 GET 请求(内部方法)
*/
private static String executeGet(HttpRequestOptions config, Header[] headers) throws Exception {
String url = config.url();
List<NameValuePair> nvps = new ArrayList<>();
// 1. 先解析 URL 中原本就带的查询参数(如果有)
// 2. 再把 HttpConfig.map() 里的参数也加入到 nvps 中
// 对于 GET 请求,这些参数应该拼到 URL 上,而不是放到 body 里
Map<String, Object> map = config.map();
if (map != null && !map.isEmpty()) {
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
// 这里主要是为了复用参数编码逻辑,返回的 entity 我们不用
HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
}
// 3. 如果 nvps 中有参数,手动构建查询字符串
// 注意:某些服务器(如接口测试工具)可能期望参数值中的特殊字符(如 == 和 ;)不被编码
// 因此我们只对参数名进行编码,参数值保持原样或只编码必要的字符
if (!nvps.isEmpty()) {
StringBuilder queryBuilder = new StringBuilder();
for (int i = 0; i < nvps.size(); i++) {
NameValuePair nvp = nvps.get(i);
if (i > 0) {
queryBuilder.append("&");
}
try {
// 只对参数名进行编码
String encodedName = encodeQueryParam(nvp.getName(), config.inenc());
// 参数值保持原样,不进行编码(与接口测试工具行为一致)
// 如果参数值中包含需要编码的字符(如中文、空格等),可以在这里添加编码逻辑
String paramValue = nvp.getValue();
queryBuilder.append(encodedName).append("=").append(paramValue);
} catch (Exception ex) {
// 如果编码失败,使用原始值
queryBuilder.append(nvp.getName()).append("=").append(nvp.getValue());
}
}
String query = queryBuilder.toString();
if (url.contains("?")) {
url = url + "&" + query;
} else {
url = url + "?" + query;
}
}
// System.out.println(url);
HttpGet httpGet = new HttpGet(url);
setHeaders(httpGet, headers);
setRequestConfig(httpGet, config);
// 如果配置了 context,使用 context 执行请求以支持 Cookie
// 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
CloseableHttpResponse response;
if (config.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet, config.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet);
response = tempResponse;
}
try (response) {
return extractResponseBody(response, config);
}
}
/**
* 执行 POST 请求(内部方法)
*/
private static String executePost(HttpRequestOptions config, Header[] headers) throws Exception {
String url = config.url();
List<NameValuePair> nvps = new ArrayList<>();
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
HttpPost httpPost = new HttpPost(url);
setHeaders(httpPost, headers);
setRequestConfig(httpPost, config);
// 设置请求体
Map<String, Object> map = config.map();
if (map != null && !map.isEmpty()) {
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
httpPost.setEntity(entity);
}
// 如果配置了 context,使用 context 执行请求以支持 Cookie
// 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
CloseableHttpResponse response;
if (config.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost, config.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost);
response = tempResponse;
}
try (response) {
return extractResponseBody(response, config);
}
}
/**
* 执行 POST 请求(内部方法)
*/
private static HttpResult executePostRes(HttpRequestOptions config, Header[] headers) throws Exception {
String url = config.url();
List<NameValuePair> nvps = new ArrayList<>();
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
HttpPost httpPost = new HttpPost(url);
setHeaders(httpPost, headers);
setRequestConfig(httpPost, config);
// 设置请求体
Map<String, Object> map = config.map();
if (map != null && !map.isEmpty()) {
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
httpPost.setEntity(entity);
}
// 如果配置了 context,使用 context 执行请求以支持 Cookie
// 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
CloseableHttpResponse response;
if (config.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost, config.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost);
response = tempResponse;
}
try (response) {
return buildHttpResult(response, config);
}
}
/**
* 执行 PATCH 请求(内部方法)
*/
private static HttpResult executePatchRes(HttpRequestOptions config, Header[] headers) throws Exception {
String url = config.url();
List<NameValuePair> nvps = new ArrayList<>();
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
HttpPatch httpPatch = new HttpPatch(url);
setHeaders(httpPatch, headers);
setRequestConfig(httpPatch, config);
// 设置请求体
Map<String, Object> map = config.map();
if (map != null && !map.isEmpty()) {
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
httpPatch.setEntity(entity);
}
// 如果配置了 context,使用 context 执行请求以支持 Cookie
// 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
CloseableHttpResponse response;
if (config.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPatch, config.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPatch);
response = tempResponse;
}
try (response) {
return buildHttpResult(response, config);
}
}
/**
* 执行通用请求(根据 HttpMethods 自动选择)(内部方法)
*/
private static String execute(HttpRequestOptions config, Header[] headers) throws Exception {
HttpMethods method = config.method();
String url = config.url();
List<NameValuePair> nvps = new ArrayList<>();
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
ClassicHttpRequest request = createRequest(method, url);
setHeaders(request, headers);
setRequestConfig(request, config);
// 设置请求体(仅 POST 和 PUT)
Map<String, Object> map = config.map();
if (map != null && !map.isEmpty() && (request instanceof HttpPost || request instanceof HttpPut)) {
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
if (request instanceof HttpPost) {
((HttpPost) request).setEntity(entity);
} else if (request instanceof HttpPut) {
((HttpPut) request).setEntity(entity);
}
}
// 如果配置了 context,使用 context 执行请求以支持 Cookie
// 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
CloseableHttpResponse response;
if (config.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(request, config.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(request);
response = tempResponse;
}
try (response) {
return extractResponseBody(response, config);
}
}
/**
* 执行请求并返回详细响应信息(内部方法)
*/
private static HttpResult executeAndGetResp(HttpRequestOptions config, Header[] headers) throws Exception {
String url = config.url();
List<NameValuePair> nvps = new ArrayList<>();
// 1. 先解析 URL 中原本就带的查询参数(如果有)
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
// 2. 再把 HttpConfig.map() 里的参数也加入到 nvps 中
// 对于 GET 请求,这些参数应该拼到 URL 上,而不是放到 body 里
Map<String, Object> map = config.map();
if (map != null && !map.isEmpty()) {
// 这里主要是为了复用参数编码逻辑,返回的 entity 我们不用
HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
}
// 3. 如果 nvps 中有参数,手动构建查询字符串
// 注意:某些服务器(如接口测试工具)可能期望参数值中的特殊字符(如 == 和 ;)不被编码
// 因此我们只对参数名进行编码,参数值保持原样或只编码必要的字符
if (!nvps.isEmpty()) {
StringBuilder queryBuilder = new StringBuilder();
for (int i = 0; i < nvps.size(); i++) {
NameValuePair nvp = nvps.get(i);
if (i > 0) {
queryBuilder.append("&");
}
try {
// 只对参数名进行编码
String encodedName = encodeQueryParam(nvp.getName(), config.inenc());
// 参数值保持原样,不进行编码(与接口测试工具行为一致)
// 如果参数值中包含需要编码的字符(如中文、空格等),可以在这里添加编码逻辑
String paramValue = nvp.getValue();
queryBuilder.append(encodedName).append("=").append(paramValue);
} catch (Exception ex) {
// 如果编码失败,使用原始值
queryBuilder.append(nvp.getName()).append("=").append(nvp.getValue());
}
}
String query = queryBuilder.toString();
if (url.contains("?")) {
url = url + "&" + query;
} else {
url = url + "?" + query;
}
}
// System.out.println(url);
HttpGet httpGet = new HttpGet(url);
setHeaders(httpGet, headers);
setRequestConfig(httpGet, config);
// 如果配置了 context,使用 context 执行请求以支持 Cookie
// 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
CloseableHttpResponse response;
if (config.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet, config.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet);
response = tempResponse;
}
try (response) {
return buildHttpResult(response, config);
}
}
/**
* 创建 HTTP 请求对象
*/
private static ClassicHttpRequest createRequest(HttpMethods method, String url) {
switch (method) {
case GET:
return new HttpGet(url);
case POST:
return new HttpPost(url);
case PUT:
return new HttpPut(url);
case DELETE:
return new HttpDelete(url);
default:
return new HttpGet(url);
}
}
/**
* 设置请求头
*/
private static void setHeaders(ClassicHttpRequest request, Header[] headers) {
if (headers != null) {
for (Header header : headers) {
request.addHeader(header.getName(), header.getValue());
}
}
}
/**
* 设置请求配置
*/
private static void setRequestConfig(ClassicHttpRequest request, HttpRequestOptions config) {
if (config.requestConfig() == null) {
return;
}
if (request instanceof HttpGet) {
((HttpGet) request).setConfig(config.requestConfig());
} else if (request instanceof HttpPost) {
((HttpPost) request).setConfig(config.requestConfig());
} else if (request instanceof HttpPut) {
((HttpPut) request).setConfig(config.requestConfig());
} else if (request instanceof HttpPatch) {
((HttpPatch) request).setConfig(config.requestConfig());
} else if (request instanceof HttpDelete) {
((HttpDelete) request).setConfig(config.requestConfig());
}
}
/**
* 提取响应体内容
*/
private static String extractResponseBody(CloseableHttpResponse response, HttpRequestOptions config) throws Exception {
HttpEntity entity = response.getEntity();
if (entity == null) {
return "";
}
try {
String result = EntityUtils.toString(entity, Charset.forName(config.outenc()));
return result;
} finally {
EntityUtils.consume(entity);
}
}
/**
* 构建 HttpResult 对象
*/
private static HttpResult buildHttpResult(CloseableHttpResponse response, HttpRequestOptions config) throws Exception {
// 直接使用 HttpResult 构造函数,自动设置状态行、响应头、协议版本和状态码
HttpResult result = new HttpResult(response);
// 读取响应体内容
HttpEntity entity = response.getEntity();
if (entity != null) {
String responseBody = EntityUtils.toString(entity, Charset.forName(config.outenc()));
result.setResult(responseBody);
EntityUtils.consume(entity);
}
return result;
}
/**
* 编码查询参数名(只编码参数名,不编码参数值)
* 这样可以保持与接口测试工具的行为一致
*
* @param param 参数名
* @param encoding 编码格式
* @return 编码后的参数名
*/
private static String encodeQueryParam(String param, String encoding) {
if (param == null) {
return "";
}
try {
// 只对参数名进行标准 URL 编码
return java.net.URLEncoder.encode(param, encoding).replace("+", "%20");
} catch (java.io.UnsupportedEncodingException e) {
return param;
}
}
}
@@ -0,0 +1,310 @@
package com.mgm.mcrm.apigateway.daylight.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<String> 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<NameValuePair> 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<NameValuePair> nvps, Map<String, Object> map, String encoding) throws UnsupportedEncodingException {
HttpEntity entity = null;
if(map!=null && map.size()>0){
boolean isSpecial = false;
// 拼接参数
for (Entry<String, Object> 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<String, Object> m = new HashMap<String, Object>();
m.putAll(map);
m.remove(ENTITY_MULTIPART);
m.remove(ENTITY_MULTIPART+".name");
m.remove(ENTITY_MULTIPART+".rmCharset");
Iterator<Entry<String, Object>> iterator = m.entrySet().iterator();
// 发送的数据
while (iterator.hasNext()) {
Entry<String, Object> 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&amp;k2=v2
*
* @param paras 参数列表
* @return 返回参数列表(map)
*/
public static Map<String,Object> 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<String,Object> buildParas(String[][] paras){
// 创建参数队列
Map<String,Object> map = new HashMap<String, Object>();
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;
}
}
@@ -0,0 +1,375 @@
package com.mgm.mcrm.apigateway.daylight.common.httpclient;
import com.mgm.mcrm.apigateway.daylight.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<String, Object> 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<OutputStream> outs = new ThreadLocal<OutputStream>();
/**
* 解决多线程处理时,url被覆盖问题
*/
private static final ThreadLocal<String> urls = new ThreadLocal<String>();
/**
* 解决多线程处理时,url被覆盖问题
*/
private static final ThreadLocal<Map<String,Object>> maps = new ThreadLocal<Map<String,Object>>();
/**
* @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<String, Object> map) {
// synchronized (getClass()) {
// if(this.map==null || map==null){
// this.map = map;
// }else {
// this.map.putAll(map);;
// }
// }
Map<String, Object> 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<String, Object> map = new HashMap<String, Object>();
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<String, Object>();
// }
// }
// map.put(Utils.ENTITY_MULTIPART, filePaths);
// map.put(Utils.ENTITY_MULTIPART+".name", inputName);
// map.put(Utils.ENTITY_MULTIPART+".rmCharset", forceRemoveContentTypeChraset);
Map<String, Object> m = maps.get();
if(m.isEmpty()){
m = new HashMap<String, Object>();
}
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<String, Object> 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;
}
}
@@ -0,0 +1,197 @@
package com.mgm.mcrm.apigateway.daylight.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);
}
}
}
@@ -4,6 +4,7 @@ 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 com.mgm.mcrm.apigateway.daylight.services.DaylightApiService;
import jakarta.annotation.Resource;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
@@ -18,6 +19,8 @@ public class DaylightConfigurationScheduler {
private DaylightFetchConfigurationService daylightFetchConfigurationService;
@Resource
private CfgDaylightConfigurationDao cfgDaylightConfigurationDao;
@Resource
private DaylightApiService daylightApiService;
@Order(1)
@EventListener(ApplicationReadyEvent.class)
@@ -30,7 +33,7 @@ public class DaylightConfigurationScheduler {
}
LogUtil.warn(DaylightConfigurationScheduler.class, "No saved Daylight properties found, start startup fetch");
BaseApiResponse propResponse = daylightFetchConfigurationService.fetchDaylightProperties();
BaseApiResponse<Void> propResponse = daylightFetchConfigurationService.fetchDaylightProperties();
if (propResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Startup properties completed: " + propResponse.getMessage());
} else {
@@ -52,7 +55,7 @@ public class DaylightConfigurationScheduler {
}
LogUtil.warn(DaylightConfigurationScheduler.class, "No saved Daylight configurations found, start startup fetch");
BaseApiResponse configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration();
BaseApiResponse<Void> configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration();
if (configResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Startup configurations completed: " + configResponse.getMessage());
} else {
@@ -79,7 +82,7 @@ public class DaylightConfigurationScheduler {
}
LogUtil.warn(DaylightConfigurationScheduler.class, "No saved Daylight block codes found, start startup fetch");
BaseApiResponse blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes();
BaseApiResponse<Void> blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes();
if (blockResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Startup block codes completed: " + blockResponse.getMessage());
} else {
@@ -99,7 +102,7 @@ public class DaylightConfigurationScheduler {
try {
// 1. 拉取 Properties
LogUtil.info(DaylightConfigurationScheduler.class, "Step 1/3: Fetching properties...");
BaseApiResponse propResponse = daylightFetchConfigurationService.fetchDaylightProperties();
BaseApiResponse<Void> propResponse = daylightFetchConfigurationService.fetchDaylightProperties();
if (propResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Properties completed: " + propResponse.getMessage());
} else {
@@ -108,7 +111,7 @@ public class DaylightConfigurationScheduler {
// 2. 拉取 Configuration
LogUtil.info(DaylightConfigurationScheduler.class, "Step 2/3: Fetching configurations...");
BaseApiResponse configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration();
BaseApiResponse<Void> configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration();
if (configResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Configurations completed: " + configResponse.getMessage());
} else {
@@ -117,7 +120,7 @@ public class DaylightConfigurationScheduler {
// 3. 拉取 Block Codes
LogUtil.info(DaylightConfigurationScheduler.class, "Step 3/3: Fetching block codes...");
BaseApiResponse blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes();
BaseApiResponse<Void> blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes();
if (blockResponse.isSuccess()) {
LogUtil.info(DaylightConfigurationScheduler.class, "Block codes completed: " + blockResponse.getMessage());
} else {
@@ -1,6 +1,7 @@
package com.mgm.mcrm.apigateway.daylight.common.services;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.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;
@@ -51,8 +52,8 @@ public class DaylightFetchConfigurationService {
@Resource
private SysLogDaylightConfigurationSyncDao syncLogDao;
public BaseApiResponse fetchDaylightConfiguration() {
BaseApiResponse response = new BaseApiResponse();
public BaseApiResponse<Void> fetchDaylightConfiguration() {
BaseApiResponse<Void> response = new BaseApiResponse<Void>();
Date syncStartTime = new Date();
try {
@@ -185,7 +186,7 @@ public class DaylightFetchConfigurationService {
.findByDlPropertyIdAndConfigName(dlPropertyId, configType);
if (existing != null && StringUtils.isNotBlank(existing.getConfigValue())) {
// 解析旧数据,用 id 建索引
List<JSONObject> oldItems = JSONObject.parseArray(existing.getConfigValue(), JSONObject.class);
List<JSONObject> oldItems = JSON.parseArray(existing.getConfigValue(), JSONObject.class);
Map<String, JSONObject> mergedMap = new HashMap<>();
for (JSONObject item : oldItems) {
mergedMap.put(getConfigItemKey(item), item);
@@ -217,7 +218,7 @@ public class DaylightFetchConfigurationService {
CfgDaylightConfiguration existing = cfgDaylightConfigurationDao
.findByDlPropertyIdAndConfigName(dlPropertyId, configType);
List<JSONObject> oldItems = existing != null && StringUtils.isNotBlank(existing.getConfigValue())
? JSONObject.parseArray(existing.getConfigValue(), JSONObject.class)
? JSON.parseArray(existing.getConfigValue(), JSONObject.class)
: new ArrayList<>();
CfgDaylightConfiguration config = existing != null ? existing : new CfgDaylightConfiguration();
config.setConfigName(configType);
@@ -256,8 +257,8 @@ public class DaylightFetchConfigurationService {
/**
* 从 Daylight API 拉取 properties 列表,存入 cfg_daylight_configuration 表
*/
public BaseApiResponse fetchDaylightProperties() {
BaseApiResponse response = new BaseApiResponse();
public BaseApiResponse<Void> fetchDaylightProperties() {
BaseApiResponse<Void> response = new BaseApiResponse<>();
Date syncStartTime = new Date();
try {
@@ -339,7 +340,7 @@ public class DaylightFetchConfigurationService {
CfgDaylightConfiguration config = cfgDaylightConfigurationDao
.findByDlPropertyIdAndConfigName(null, "Property");
List<JSONObject> oldItems = config != null && StringUtils.isNotBlank(config.getConfigValue())
? JSONObject.parseArray(config.getConfigValue(), JSONObject.class)
? JSON.parseArray(config.getConfigValue(), JSONObject.class)
: new ArrayList<>();
if (config == null) {
config = new CfgDaylightConfiguration();
@@ -481,8 +482,8 @@ public class DaylightFetchConfigurationService {
/**
* 从 Daylight API 拉取 block codes 列表,按 property 遍历,存入 cfg_daylight_configuration 表
*/
public BaseApiResponse fetchDaylightBlockCodes() {
BaseApiResponse response = new BaseApiResponse();
public BaseApiResponse<Void> fetchDaylightBlockCodes() {
BaseApiResponse<Void> response = new BaseApiResponse<>();
Date syncStartTime = new Date();
String dlPropertyId = null;
@@ -532,6 +533,7 @@ public class DaylightFetchConfigurationService {
String path = "/api-gateway/data-api/v1/blocks";
String apiUrl = daylightApiSetting.getServerUrl() + path;
List<JSONObject> allResults = new ArrayList<>();
boolean fetchSuccess = true;
HttpHeaders headers = new HttpHeaders();
headers.set("authorization", "Bearer " + daylightApiToken.getAccessToken());
@@ -552,38 +554,37 @@ public class DaylightFetchConfigurationService {
).block();
if (requestResponse == null) {
response.setSuccess(false);
response.setMessage("Http request server can not visit.");
return response;
}
log.warn("Property {} BlockCode fetch failed: Http request server can not visit", dlPropertyId);
fetchSuccess = false;
} else {
response.setStatusCode(requestResponse.getStatusCode().value());
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 {} BlockCode fetch failed: {}", dlPropertyId, errorMsg);
fetchSuccess = false;
} else {
byte[] responseBody = requestResponse.getBody();
if (responseBody != null) {
String jsonStr = new String(responseBody, StandardCharsets.UTF_8);
JSONObject responseJson = JSONObject.parseObject(jsonStr);
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<JSONObject> pageResults = responseJson.getJSONArray("results").toJavaList(JSONObject.class);
if (pageResults != null) {
allResults.addAll(pageResults);
List<JSONObject> pageResults = responseJson.getJSONArray("results").toJavaList(JSONObject.class);
if (pageResults != null) {
allResults.addAll(pageResults);
}
}
}
}
// 保存为一条 CfgDaylightConfiguration,configName = "BlockCode"
if (!allResults.isEmpty()) {
if (fetchSuccess && !allResults.isEmpty()) {
boolean isIncremental = StringUtils.isNotBlank(updatedAtParam);
if (isIncremental && existingConfig != null
&& StringUtils.isNotBlank(existingConfig.getConfigValue())) {
// 增量同步:按 id 合并新旧数据
List<JSONObject> oldItems = JSONObject.parseArray(existingConfig.getConfigValue(), JSONObject.class);
List<JSONObject> oldItems = JSON.parseArray(existingConfig.getConfigValue(), JSONObject.class);
Map<String, JSONObject> mergedMap = new HashMap<>();
for (JSONObject item : oldItems) {
mergedMap.put(getConfigItemKey(item), item);
@@ -601,7 +602,7 @@ public class DaylightFetchConfigurationService {
// 全量同步:直接保存
List<JSONObject> oldItems = existingConfig != null
&& StringUtils.isNotBlank(existingConfig.getConfigValue())
? JSONObject.parseArray(existingConfig.getConfigValue(), JSONObject.class)
? JSON.parseArray(existingConfig.getConfigValue(), JSONObject.class)
: new ArrayList<>();
CfgDaylightConfiguration config = existingConfig != null ? existingConfig : new CfgDaylightConfiguration();
if (config.getConfigName() == null) {
@@ -618,20 +619,20 @@ public class DaylightFetchConfigurationService {
}
savedCount++;
}
// 按 property 记录同步日志
SysLogDaylightConfigurationSync syncLog = new SysLogDaylightConfigurationSync();
syncLog.setConfigName("BlockCode");
syncLog.setDlPropertyId(dlPropertyId);
syncLog.setLastSyncAt(syncStartTime);
syncLog.setSyncStatus(fetchSuccess ? "SUCCESS" : "FAILED");
syncLog.setRecordCount(allResults.size());
syncLog.setCreatedAt(new Date());
syncLogDao.save(syncLog);
}
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());
@@ -0,0 +1,88 @@
package com.mgm.mcrm.apigateway.daylight.common.services;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.mgm.mcrm.apigateway.common.dtos.config.DaylightApiSetting;
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.ResponseEntity;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
@Service
public class DaylightReservationService {
private final Logger logger = LoggerFactory.getLogger(DaylightReservationService.class);
private final HttpProxyErrorHandler httpProxyErrorHandler = new HttpProxyErrorHandler();
@Resource
private WebProxyService webProxyService;
@Autowired
private DaylightApiSetting daylightApiSetting;
@Resource
private DaylightTokenService daylightTokenService;
public JSONObject getDaylightReservationByDlId(
String dlReservationId,
String dlPropertyId
) {
logger.info("Entering getDaylightReservationByDlId with dlReservationId: {}, dlPropertyId: {}", dlReservationId, dlPropertyId);
try {
TokenResponse daylightApiToken = daylightTokenService.getApiAccessToken();
if (daylightApiToken == null || StringUtils.isBlank(daylightApiToken.getAccessToken())) {
logger.error("Failed to obtain Daylight API access token.");
throw new RuntimeException("Invoke daylight token failed");
}
String apiUrl = daylightApiSetting.getServerUrl() + "/api-gateway/booking/v1/reservations/" + dlReservationId;
String queryString = "extend=PurchaseElements,CustomFields";
HttpHeaders headers = new HttpHeaders();
headers.set("authorization", "Bearer " + daylightApiToken.getAccessToken());
headers.set("AC-Property-Id", dlPropertyId);
headers.set("Content-Type", "application/json");
logger.info("Invoking Daylight API: {} with query: {}", apiUrl, queryString);
ResponseEntity<byte[]> requestResponse = webProxyService.invokeRequest(
WebProxyService.DAYLIGHT_API,
HttpMethod.GET,
apiUrl,
queryString,
headers,
new byte[0],
Duration.ofSeconds(daylightApiSetting.getApiTimeout()),
httpProxyErrorHandler
).block();
if (requestResponse != null) {
logger.info("Daylight API response status: {}", requestResponse.getStatusCode());
if (requestResponse.getBody() != null) {
String responseData = new String(requestResponse.getBody(), StandardCharsets.UTF_8);
logger.info("Successfully retrieved daylight reservation data.");
JSONObject response = JSONObject.parseObject(responseData);
JSONArray reservations = response.getJSONArray("results");
JSONObject reservation = reservations.getJSONObject(0);
return reservation;
} else {
logger.warn("Daylight API returned an empty body for reservation: {}", dlReservationId);
}
} else {
logger.error("Daylight API request returned null for reservation: {}", dlReservationId);
}
} catch (Exception e) {
logger.error("Error occurred while fetching daylight reservation by ID: {}", dlReservationId, e);
throw new RuntimeException(e);
}
return null;
}
}
@@ -1,6 +1,6 @@
package com.mgm.mcrm.apigateway.daylight.common.services;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.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;
@@ -0,0 +1,209 @@
package com.mgm.mcrm.apigateway.daylight.common.services;
import com.alibaba.fastjson2.JSON;
import com.mgm.mcrm.apigateway.daylight.common.dtos.HttpResult;
import com.mgm.mcrm.apigateway.daylight.common.enums.HttpMethods;
import com.mgm.mcrm.apigateway.daylight.common.httpclient.HttpClientExecutor;
import com.mgm.mcrm.apigateway.daylight.common.httpclient.HttpRequestOptions;
import jakarta.annotation.Resource;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.Header;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* 請求服務
*
* @author Aaka
*/
@Service
public class HttpService {
private static final Logger log = LoggerFactory.getLogger(HttpService.class);
@Resource
@Qualifier("httpClientWithSSL")
private CloseableHttpClient httpClient;
@Resource
@Qualifier("httpRequestConfig")
private RequestConfig requestConfig;
@Value("${daylight.url}")
private String daylightUrl;
public String sslGet(String url, Header[] headers, Map<String, Object> map) throws Exception {
log.info("======sslGet======url:{}===========headers:{} =============== map:{}", daylightUrl + url, JSON.toJSONString(headers), JSON.toJSONString(map));
// 使用 HttpConfig 配置 GET 请求(带参数)
HttpRequestOptions getConfig = HttpRequestOptions.create()
.url(daylightUrl + url)
.timeout(requestConfig)
.map(map)
.encoding("utf-8")
.client(httpClient);
String result = HttpClientExecutor.get(getConfig, headers);
log.info("====== GET request result size:{}", result.length());
return result;
}
public String sslGetNoLog(String url, Header[] headers, Map<String, Object> map) throws Exception {
// todo 在這裏需要把請求頭的信息插入到數據庫裏
log.info("======sslGet======url:{}===========headers:{} =============== map:{}", daylightUrl + url, JSON.toJSONString(headers), JSON.toJSONString(map));
// 使用 HttpConfig 配置 GET 请求(带参数)
HttpRequestOptions getConfig = HttpRequestOptions.create()
.url(daylightUrl + url)
.timeout(requestConfig)
.map(map)
.encoding("utf-8")
.client(httpClient);
String result = HttpClientExecutor.get(getConfig, headers);
log.info("====== GET request result size={}", result.length());
return result;
}
public HttpResult sslGetHttpResult(String url, Header[] headers, Map<String, Object> map) throws Exception {
// todo 在這裏需要把請求頭的信息插入到數據庫裏
log.info("======sslGet======url:{}===========headers:{} =============== map:{}", daylightUrl + url, JSON.toJSONString(headers), JSON.toJSONString(map));
// 使用 HttpConfig 配置 GET 请求(带参数)
HttpRequestOptions getConfig = HttpRequestOptions.create()
.url(daylightUrl + url)
.timeout(requestConfig)
.map(map)
.encoding("utf-8")
.client(httpClient);
HttpResult result = HttpClientExecutor.sendAndGetResp(getConfig, headers);
log.info("====== GET request result:{}", result.getResult());
return result;
}
/**
* 使用 HttpConfig 配置 PUT 请求
*
* @param url 請求路徑
* @param headers 請求頭
* @param map 請求參數
* @return 接口結果的json數據
* @throws Exception 異常
*/
public String sslPut(String url, Header[] headers, Map<String, Object> map) throws Exception {
log.info("======sslPut======url:{}===========headers:{} =============== map:{}", daylightUrl + url, JSON.toJSONString(headers), JSON.toJSONString(map));
HttpRequestOptions putConfig = HttpRequestOptions.create()
.url(daylightUrl + url)
.timeout(requestConfig)
.method(HttpMethods.PUT)
.json(JSON.toJSONString(map))
.encoding("utf-8")
.client(httpClient);
String result = HttpClientExecutor.send(putConfig, headers);
log.info("====== PUT request result:{}", result);
return result;
}
/**
* 使用 HttpConfig 配置 Delete 请求
*
* @param url 請求路徑
* @param headers 請求頭
* @param requestBody 請求參數
* @return 接口結果的json數據
* @throws Exception 異常
*/
public String sslDelete(String url, Header[] headers, Object requestBody) throws Exception {
log.info("======sslDelete======url:{}===========headers:{} =============== map:{}", daylightUrl + url, JSON.toJSONString(headers), JSON.toJSONString(requestBody));
HttpRequestOptions deleteConfig = HttpRequestOptions.create()
.url(daylightUrl + url)
.timeout(requestConfig)
.json(JSON.toJSONString(requestBody))
.method(HttpMethods.DELETE)
.client(httpClient);
String result = HttpClientExecutor.send(deleteConfig, headers);
log.info("====== DELETE request result:{}", result);
return result;
}
/**
* 使用 HttpConfig 配置 Post 请求
*
* @param url 請求路徑
* @param headers 請求頭
* @param requestBody request 数据
* @return 接口結果的json數據
* @throws Exception 異常
*/
public HttpResult sslPost(String url, Header[] headers, Object requestBody) throws Exception {
log.info("======sslCreatePost======url:{}===========headers:{}", daylightUrl + url, JSON.toJSONString(headers));
HttpRequestOptions postConfig = HttpRequestOptions.create()
.url(daylightUrl + url)
.timeout(requestConfig)
.json(JSON.toJSONString(requestBody))
.method(HttpMethods.POST)
.client(httpClient);
HttpResult result = HttpClientExecutor.sendAndPostResp(postConfig, headers);
// System.out.println("POST 请求结果:\n" + result);
log.info("====== POST request result:{}", result);
return result;
}
/**
* 使用 HttpConfig 配置 PATCH 请求(JSON 请求体)
*
* @param url 請求路徑
* @param headers 請求頭
* @param requestBody request 数据
* @return 接口結果的json數據
* @throws Exception 異常
*/
public HttpResult sslPatch(String url, Header[] headers, Object requestBody) throws Exception {
log.info("======sslCreatePatch======url:{}===========headers:{}", daylightUrl + url, JSON.toJSONString(headers));
HttpRequestOptions patchConfig = HttpRequestOptions.create()
.url(daylightUrl + url)
.timeout(requestConfig)
.json(JSON.toJSONString(requestBody))
.method(HttpMethods.PATCH)
.encoding("utf-8")
.client(httpClient);
HttpResult result = HttpClientExecutor.sendAndPatchResp(patchConfig, headers);
log.info("====== PATCH request result:{}", result);
return result;
}
/**
* 使用 HttpConfig 配置 Delete 请求
*
* @param url 請求路徑
* @param headers 請求頭
* @param map Cancel数据
* @return 接口結果的json數據
* @throws Exception 異常
*/
public HttpResult sslCancelPost(String url, Header[] headers, Map<String, Object> map) throws Exception {
log.info("======sslCancelPost======url:{}===========headers:{}", daylightUrl + url, JSON.toJSONString(headers));
HttpRequestOptions postConfig = HttpRequestOptions.create()
.url(daylightUrl + url)
.timeout(requestConfig)
.json(JSON.toJSONString(map))
.method(HttpMethods.POST)
.client(httpClient);
HttpResult result = HttpClientExecutor.sendAndPostResp(postConfig, headers);
// System.out.println("POST 请求结果:\n" + result);
log.info("====== POST request result:{}", result);
return result;
}
}
@@ -0,0 +1,61 @@
package com.mgm.mcrm.apigateway.daylight.dtos.response;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class CompRuleResponse {
private String id;
private Integer version;
private String accountId;
private String profileId;
private String folioWindowId;
private String complimentaryTypeId;
private List<String> complimentaryAccountingPresetsIds;
private String requestorId;
private String approverId;
private String paymentMethodTransactionCodeId;
private String justification;
private ComplimentaryRuleLimits complimentaryRuleLimits;
private List<String> settledFolioWindowIds;
private Boolean isExecutive;
private Boolean doNotIssueInvoice;
private Boolean isActive;
private Boolean canBeDeleted;
private Integer issuedVouchersCount;
@Data
public static class ComplimentaryRuleLimits {
private Amount totalAmountLimit;
private SoftTotalLimit softTotalLimit;
private List<String> limitExcludedPresetIds;
private List<String> limitExcludedTransactionCodeIds;
private TotalLimit totalLimit;
@Data
public static class SoftTotalLimit {
private Amount amountLimit;
private Amount remainingLimit;
private Boolean isExceeded;
}
@Data
public static class TotalLimit {
private Amount amountLimit;
private Amount remainingLimit;
}
@Data
public static class Amount {
private BigDecimal amount;
private String currency;
}
}
}
@@ -0,0 +1,39 @@
package com.mgm.mcrm.apigateway.daylight.models;
import jakarta.persistence.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "cfg_comp_rule")
public class CfgCompRule implements Serializable {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "dl_reservation_id")
private String dlReservationId;
@Column(name = "comp_rule_id", nullable = false, unique = true)
private String compRuleId;
@Column(name = "account_id")
private String accountId;
@Column(name = "is_send_soft_limit", nullable = false)
private Integer isSendSoftLimit = 0;
@Column(name = "is_send_total_limit", nullable = false)
private Integer isSendTotalLimit = 0;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
@@ -0,0 +1,10 @@
package com.mgm.mcrm.apigateway.daylight.repositories;
import com.mgm.mcrm.apigateway.daylight.models.CfgCompRule;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CfgCompRuleDao extends JpaRepository<CfgCompRule, Long> {
CfgCompRule findByCompRuleId(String compRuleId);
}
@@ -0,0 +1,84 @@
package com.mgm.mcrm.apigateway.daylight.services;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse;
import com.mgm.mcrm.apigateway.common.httpclient.HttpHeaderBuilder;
import com.mgm.mcrm.apigateway.daylight.common.services.HttpService;
import com.mgm.mcrm.apigateway.daylight.dtos.response.CompRuleResponse;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.core5.http.Header;
import org.springframework.stereotype.Service;
import java.util.*;
@Slf4j
@Service
public class DaylightApiService {
@Resource
private HttpService httpService;
public BaseApiResponse<List<CompRuleResponse>> daylightGetCompRulesByAccount(String dlPropertyId, String dlAccountId) {
BaseApiResponse<List<CompRuleResponse>> result = new BaseApiResponse<>();
try {
String url = "/api-gateway/cashiering/v1/accounts/" + dlAccountId + "/complimentary-rules";
// 获取对应的值
Header[] headers = HttpHeaderBuilder.custom()
.contentType("application/json")
.ac_property_id(dlPropertyId)
.build();
// 参数设置
Map<String, Object> map = new HashMap<>();
String responseData = httpService.sslGet(url, headers, map);
List<CompRuleResponse> compRuleResponses = JSON.parseArray(responseData, CompRuleResponse.class);
result.setSuccess(true);
result.setData(compRuleResponses);
} catch (Exception e) {
result.setSuccess(false);
result.setData(Collections.emptyList());
result.setMessage("Failed to get dayLight comp rules for account: " + dlAccountId);
log.error("Failed to get dayLight comp rules: ", e);
}
return result;
}
public BaseApiResponse<JSONObject> getDaylightReservationByDlId(
String dlReservationId,
String dlPropertyId
) {
BaseApiResponse<JSONObject> result = new BaseApiResponse<>();
try {
String url = "/api-gateway/booking/v1/reservations/" + dlReservationId + "?extend=PurchaseElements,CustomFields";
// 获取对应的值
Header[] headers = HttpHeaderBuilder.custom()
.contentType("application/json")
.ac_property_id(dlPropertyId)
.build();
// 参数设置
Map<String, Object> map = new HashMap<>();
String responseData = httpService.sslGet(url, headers, map);
JSONObject jsonObject = JSON.parseObject(responseData);
result.setSuccess(true);
result.setData(jsonObject);
} catch (Exception e) {
result.setSuccess(false);
result.setMessage("Failed to get reservation , id=" + dlReservationId);
log.error("Failed to get reservation , id={}", dlReservationId, e);
}
return result;
}
}
@@ -24,12 +24,12 @@ public class DaylightWebhook {
private WebhookLogService webhookLogService;
@PostMapping(value = "/receive", produces = "application/json;charset=UTF-8")
public ResponseEntity<BaseApiResponse> handleRoomAvailableWebhook(
public ResponseEntity<BaseApiResponse<Void>> handleRoomAvailableWebhook(
@RequestBody(required = true) String payload,
@RequestHeader Map<String, String> headers,
HttpServletRequest request
){
BaseApiResponse result = new BaseApiResponse();
BaseApiResponse<Void> result = new BaseApiResponse<>();
String messageId = request.getHeader("Ac-Events-Message-Id");
String eventType = request.getHeader("Ac-Events-Type");
@@ -0,0 +1,12 @@
package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation;
import lombok.Data;
@Data
public class ExceededCompRuleEvent {
private String complimentaryRuleId;
private boolean softTotalLimitIsExceeded;
private boolean softDailyLimitIsExceeded;
private boolean isExecutive;
private String trigger;
}
@@ -1,11 +1,10 @@
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<String, String> headers) throws Exception;
BaseApiResponse<Void> handle(String payload, Map<String, String> headers) throws Exception;
}
@@ -20,8 +20,8 @@ import java.util.Map;
public class AvailableRoomCountUpdatedHandler implements DaylightWebhookHandler {
private final RoomCriteriaDaylightService roomCriteriaDaylightService;
@Override
public BaseApiResponse handle(String payload, Map<String, String> headers) {
BaseApiResponse result = new BaseApiResponse();
public BaseApiResponse<Void> handle(String payload, Map<String, String> headers) {
BaseApiResponse<Void> result = new BaseApiResponse<>();
AvailableRoomCountUpdatedEvent eventData = null;
try{
eventData = JSONObject.parseObject(payload, AvailableRoomCountUpdatedEvent.class);
@@ -0,0 +1,224 @@
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.common.utils.LogUtil;
import com.mgm.mcrm.apigateway.common.utils.NotificationUtil;
import com.mgm.mcrm.apigateway.daylight.dtos.response.CompRuleResponse;
import com.mgm.mcrm.apigateway.daylight.models.CfgCompRule;
import com.mgm.mcrm.apigateway.daylight.repositories.CfgCompRuleDao;
import com.mgm.mcrm.apigateway.daylight.services.DaylightApiService;
import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ExceededCompRuleEvent;
import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler;
import com.mgm.mcrm.apigateway.logging.dao.SysLogDeviceTokenRepository;
import com.mgm.mcrm.apigateway.logging.models.SysLogDeviceToken;
import com.mgm.mcrm.apigateway.notification.services.ApnNotificationService;
import com.mgm.mcrm.apigateway.reservation.models.Bookings;
import com.mgm.mcrm.apigateway.reservation.repositories.BookingDao;
import com.mgm.mcrm.apigateway.reservation.services.BookingService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor
public class CompRuleLimitsExceededChangedHandler implements DaylightWebhookHandler {
@Resource
private CfgCompRuleDao cfgCompRuleDao;
@Resource
private BookingDao bookingDao;
@Resource
private SysLogDeviceTokenRepository sysLogDeviceTokenRepository;
@Resource
private NotificationUtil notificationUtil;
@Resource
private ApnNotificationService apnNotificationService;
@Resource
private DaylightApiService daylightApiService;
@Override
public BaseApiResponse<Void> handle(String payload, Map<String, String> headers) {
BaseApiResponse<Void> result = new BaseApiResponse<>();
ExceededCompRuleEvent eventData = null;
try{
eventData = JSONObject.parseObject(payload, ExceededCompRuleEvent.class);
}catch(Exception err){
result.setSuccess(false);
result.setMessage("Convert Payload failed:["+err.getMessage()+"]");
}
if(eventData!=null){
result = compRuleSendNotificationEvevnt(eventData);
}
return result;
}
public BaseApiResponse<Void> compRuleSendNotificationEvevnt(ExceededCompRuleEvent event){
BaseApiResponse<Void> apiResponse = new BaseApiResponse<>();
String compRuleId = event.getComplimentaryRuleId();
boolean isExceededSoftLimit = false;
boolean isExceededTotalLimit = false;
//TO*DO Misc Charge notification by Corso 20260915 完成 / 20260916
//拿到CompRuleId 获取到对应的Booking
CfgCompRule byCompRuleId = cfgCompRuleDao.findByCompRuleId(compRuleId);
List<Bookings> bookings = bookingDao.findByDlReservationId(byCompRuleId.getDlReservationId());
Bookings originBooking = new Bookings();
//判断是否为主Booking (非WaitList 且 非ApprovalRouting)
for (Bookings booking : bookings) {
if (!Strings.CS.containsAny(booking.getApproveStatus(),"W","A") && booking.getDlReservationId() !=null){
originBooking = booking;
break;
}
}
//TODO 根据CompRuleId 查到对应的CompRule 是超过了SoftLimit 还是 Total Limit 需要CCJ去拿到CompRule For CCJ By Corso 2026-09-16
//查看超过SoftLimit/TotalLimit后 修改对应参数
BaseApiResponse<List<CompRuleResponse>> listBaseApiResponse =
daylightApiService.daylightGetCompRulesByAccount(originBooking.getDlPropertyId(), byCompRuleId.getAccountId());
CompRuleResponse crr = null;
for (CompRuleResponse c : listBaseApiResponse.getData()) {
if (c.getId().equals(compRuleId)) {
crr = c;
break;
}
}
if (crr != null) {
String miscChargeValue = originBooking.getMiscCharge();
if (miscChargeValue != null && !miscChargeValue.isBlank()
&& crr.getComplimentaryRuleLimits() != null) {
BigDecimal miscCharge = new BigDecimal(miscChargeValue);
CompRuleResponse.ComplimentaryRuleLimits limits =
crr.getComplimentaryRuleLimits();
// Soft Total Limit
if (limits.getSoftTotalLimit() != null
&& limits.getSoftTotalLimit().getAmountLimit() != null
&& limits.getSoftTotalLimit().getAmountLimit().getAmount() != null) {
BigDecimal softLimit =
limits.getSoftTotalLimit()
.getAmountLimit()
.getAmount();
if (softLimit.compareTo(miscCharge) > 0) {
isExceededSoftLimit = true;
}
}
// Total Limit
if (limits.getTotalLimit() != null
&& limits.getTotalLimit().getAmountLimit() != null
&& limits.getTotalLimit().getAmountLimit().getAmount() != null) {
BigDecimal totalLimit =
limits.getTotalLimit()
.getAmountLimit()
.getAmount();
if (totalLimit.compareTo(miscCharge) > 0) {
isExceededTotalLimit = true;
}
}
}
}
//去DB中拿到当前CompRule对应的记录 检查是否已经发送过对应的Notification
if (byCompRuleId != null) {
boolean isSandSoftLimit = byCompRuleId.getIsSendSoftLimit() == 1;
boolean isSandTotalLimit = byCompRuleId.getIsSendTotalLimit() == 1;
if ((isSandSoftLimit && isExceededSoftLimit) || (isSandTotalLimit && isExceededTotalLimit)) {
//如果发送过 直接跳过该条记录
return apiResponse;
}
}
//如果没有发送过 调用apns接口去发送 Notification
if (isExceededSoftLimit){
//调用APNs方法 发送超过Soft Limit Notification
LogUtil.info(BookingService.class,"Send Exceeded Soft Limit Notification Booking Dl ReservationId = "+originBooking.getDlReservationId()+
", PlayerId = "+originBooking.getPlayerId());
// //设置Notification 内容
String notificationTitle = "#EN#1 Misc-Charge Reminder#EN#" +
"#CN#1 個雜費提示#CN#";
String notificationContent = "#EN#[Misc-charge reminder] Please be informed that the Misc charge of #{playerId} ID is reach to 50%.#EN#" +
"#CN#[客人雜費提醒 ] 請注意! 閣下提供給#{playerId} 的雜費已經到達50%。#CN#";
String[] placeholders = {
"{playerId}"
};
LogUtil.info(BookingService.class,"Set notification info");
String[] values = {
StringUtils.defaultString(originBooking.getPlayerId()),
};
LogUtil.info(BookingService.class,"Replace each");
notificationContent = StringUtils.replaceEach(notificationContent, placeholders, values);
String transactionId = "BKCRSL"+originBooking.getDlReservationId();
LogUtil.info(BookingService.class,"Get device token from DB Start");
SysLogDeviceToken deviceToken = sysLogDeviceTokenRepository.findByUserId(originBooking.getCreatedBy().trim());
LogUtil.info(BookingService.class,"Get device token from DB:" + (deviceToken == null ? "null" : deviceToken.getDeviceToken()));
String deviceTokenValue =null;
String finalTitle = null;
if(deviceToken!=null){
deviceTokenValue = deviceToken.getDeviceToken();
finalTitle = notificationUtil.distinguishLanguage(notificationTitle,deviceToken.getUseLanguageType());
notificationContent = notificationUtil.distinguishLanguage(notificationContent,deviceToken.getUseLanguageType());
}
// NOTIFICATION
apnNotificationService.pushApnMessage(transactionId,deviceTokenValue,finalTitle,notificationContent,true);
} else if (isExceededTotalLimit) {
//调用APNs方法 发送超过Total Limit Notificaiton
LogUtil.info(BookingService.class,"Send Exceeded Total Limit Notification Booking Dl ReservationId = "+originBooking.getDlReservationId()+
", PlayerId = "+originBooking.getPlayerId());
// //设置Notification 内容
String notificationTitle = "#EN#1 Misc-Charge Reminder#EN#" +
"#CN#1 個雜費提示#CN#";
String notificationContent = "#EN#[Mis-charge reminder] Please be informed that the misc charge of #{playerId} ID is reach to 100%, call FO to settle it ASAP. If you want to increase the limit amount, please contact related departments. \n#EN#" +
"#CN#[客人雜費提醒 ] 請注意! 閣下提供給#{playerId} 的雜費已經到達100%. 請儘快聯絡相關部門。#CN#";
String[] placeholders = {
"{playerId}"
};
LogUtil.info(BookingService.class,"Set notification info");
String[] values = {
StringUtils.defaultString(originBooking.getPlayerId()),
};
LogUtil.info(BookingService.class,"Replace each");
notificationContent = StringUtils.replaceEach(notificationContent, placeholders, values);
String transactionId = "BKCRTL"+originBooking.getDlReservationId();
LogUtil.info(BookingService.class,"Get device token from DB Start");
SysLogDeviceToken deviceToken = sysLogDeviceTokenRepository.findByUserId(originBooking.getCreatedBy().trim());
LogUtil.info(BookingService.class,"Get device token from DB:" + (deviceToken == null ? "null" : deviceToken.getDeviceToken()));
String deviceTokenValue =null;
String finalTitle = null;
if(deviceToken!=null){
deviceTokenValue = deviceToken.getDeviceToken();
finalTitle = notificationUtil.distinguishLanguage(notificationTitle,deviceToken.getUseLanguageType());
notificationContent = notificationUtil.distinguishLanguage(notificationContent,deviceToken.getUseLanguageType());
}
// NOTIFICATION
apnNotificationService.pushApnMessage(transactionId,deviceTokenValue,finalTitle,notificationContent,true);
}
return apiResponse;
}
}
@@ -1,10 +1,12 @@
package com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl;
import com.alibaba.fastjson2.JSONArray;
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.services.DaylightApiService;
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;
@@ -14,6 +16,7 @@ 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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -26,10 +29,11 @@ public class ReservationNotificationHandler implements DaylightWebhookHandler {
private final BookingService bookingService;
private final DaylightApiService daylightApiService;
@Override
public BaseApiResponse handle(String payload, Map<String, String> headers) throws Exception {
BaseApiResponse result = new BaseApiResponse();
public BaseApiResponse<Void> handle(String payload, Map<String, String> headers) throws Exception {
BaseApiResponse<Void> result = new BaseApiResponse<>();
ReservationNotificationEvent eventData = null;
try{
eventData = JSONObject.parseObject(payload, ReservationNotificationEvent.class);
@@ -101,6 +105,36 @@ public class ReservationNotificationHandler implements DaylightWebhookHandler {
String etaTime = dateTime.format(DateTimeFormatter.ofPattern("HH:mm"));
result.setEta(etaTime);
}
BaseApiResponse<JSONObject> response =
daylightApiService.getDaylightReservationByDlId(eventData.getReservationId(), eventData.getPropertyId());
if (response.isSuccess()) {
JSONObject embedded = response.getData().getJSONObject("_embedded");
if (embedded != null) {
JSONArray customFields = embedded.getJSONArray("customFields");
if (customFields != null) {
for (int j = 0; j < customFields.size(); j++) {
JSONObject jsonObject = customFields.getJSONObject(j);
String value = jsonObject.getString("value");
JSONObject valueJson = JSONObject.parseObject(value);
String booker = valueJson.getString("Booker");
if (StringUtils.isNotBlank(booker)) {
result.setCreatedBy(booker);
result.setBooker(booker);
}
}
}
}
JSONArray jsonArray = response.getData().getJSONArray("externalIds");
if (!CollectionUtils.isEmpty(jsonArray)) {
String dlExternalNo = jsonArray.getJSONObject(0).getString("number");
result.setDlExternalNo(dlExternalNo);
}
}
return result;
}
@@ -1,6 +1,6 @@
package com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl;
import com.alibaba.fastjson.JSONObject;
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;
@@ -21,8 +21,8 @@ public class RoomTypeDateAvailabilityChangedHandler implements DaylightWebhookHa
private final RoomCriteriaDaylightService roomCriteriaDaylightService;
@Override
public BaseApiResponse handle(String payload, Map<String, String> headers) {
BaseApiResponse result = new BaseApiResponse();
public BaseApiResponse<Void> handle(String payload, Map<String, String> headers) {
BaseApiResponse<Void> result = new BaseApiResponse<>();
RoomTypeDateAvailabilityChangedEvent eventData = null;
try{
eventData = JSONObject.parseObject(payload, RoomTypeDateAvailabilityChangedEvent.class);
@@ -27,7 +27,7 @@ public class NotificationController {
private EmailProxySetting emailProxySetting;
@PostMapping(value = "/send_email", produces = "application/json;charset=UTF-8")
public ResponseEntity<BaseApiResponse> handleSendEmailRequest(
public ResponseEntity<BaseApiResponse<Void>> handleSendEmailRequest(
@RequestBody(required = true) SendEmailRequest requestData,
HttpServletRequest request
){
@@ -35,7 +35,7 @@ public class NotificationController {
String appId = request.getHeader("application-id");
String securityKey = request.getHeader("authorization");
BaseApiResponse result = emailNotificationService.sendEmailByRequest(requestData,emailProxySetting);
BaseApiResponse<Void> result = emailNotificationService.sendEmailByRequest(requestData,emailProxySetting);
return ResponseEntity.status(HttpStatus.OK).body(result);
}
@@ -44,7 +44,7 @@ public class NotificationController {
* showTemp 为 HTML 正文,优先于 content;有 cc 时按群发模式发送。
*/
@PostMapping(value = "/proxy/send_email", produces = "application/json;charset=UTF-8")
public ResponseEntity<BaseApiResponse> handleSendEmailProxyRequest(
public ResponseEntity<BaseApiResponse<Void>> handleSendEmailProxyRequest(
@RequestBody(required = true) EmailProxyRequest requestData,
HttpServletRequest request
) {
@@ -64,7 +64,7 @@ public class NotificationController {
// 有 cc 时用群发模式(false),让 to + cc 在同一封邮件里
sendEmailRequest.setSendSeparately(StringUtils.isBlank(requestData.getCc()));
BaseApiResponse result = emailNotificationService.sendEmailByRequest(sendEmailRequest, emailProxySetting);
BaseApiResponse<Void> 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);
@@ -33,12 +33,12 @@ public class ApnNotificationService {
private final Logger logger = LoggerFactory.getLogger(ApnNotificationService.class);
public BaseApiResponse pushApnMessage(String transactionId, String deviceToken, String title, String body, boolean isWaitResponse) {
public BaseApiResponse<Void> pushApnMessage(String transactionId, String deviceToken, String title, String body, boolean isWaitResponse) {
return pushApnMessage(transactionId, deviceToken, title, body, isWaitResponse, null);
}
public BaseApiResponse pushApnMessage(String transactionId, String deviceToken, String title, String body, boolean isWaitResponse, String confirmNo) {
BaseApiResponse result = new BaseApiResponse();
public BaseApiResponse<Void> pushApnMessage(String transactionId, String deviceToken, String title, String body, boolean isWaitResponse, String confirmNo) {
BaseApiResponse<Void> result = new BaseApiResponse<>();
if(StringUtils.isBlank(deviceToken)){
result.setSuccess(false);
result.setMessage("Device Token is empty");
@@ -21,8 +21,8 @@ import java.util.Properties;
public class EmailNotificationService {
private final Logger logger = LoggerFactory.getLogger(EmailNotificationService.class);
public BaseApiResponse sendEmailByRequest(SendEmailRequest requestData, EmailProxySetting emailConfig) {
BaseApiResponse result = new BaseApiResponse();
public BaseApiResponse<Void> sendEmailByRequest(SendEmailRequest requestData, EmailProxySetting emailConfig) {
BaseApiResponse<Void> result = new BaseApiResponse<>();
try {
JavaMailSenderImpl emailSender = new JavaMailSenderImpl();
emailSender.setHost(emailConfig.getSmtpServer());
@@ -40,7 +40,7 @@ public class NotificationProxyController {
private NotificationProxyService notificationProxyService;
@RequestMapping("/api/notification/proxy/**")
public ResponseEntity<BaseApiResponse> invokeNotificationApiProxy(HttpServletRequest request, @RequestBody SendNotificationRequest notificationRequest) throws Exception {
public ResponseEntity<BaseApiResponse<Void>> invokeNotificationApiProxy(HttpServletRequest request, @RequestBody SendNotificationRequest notificationRequest) throws Exception {
LoginUserInfo requestUser = SysCommon.parseUserFromToken(request.getHeader("Authorization"));
return ResponseEntity.status(HttpStatus.OK).body(notificationProxyService.sendNotificationByRequest(notificationRequest,requestUser.getLoginId()));
}
@@ -1,7 +1,6 @@
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;
@@ -64,6 +63,7 @@ public class WebProxyController {
throw new Exception("Invoke daylight token failed");
}
headers.set("authorization","Bearer "+daylightApiToken.getAccessToken());
return webProxyService.invokeRequest(
WebProxyService.DAYLIGHT_API,
HttpMethod.valueOf(request.getMethod()),
@@ -29,8 +29,8 @@ public class NotificationProxyService {
@Resource
private ApnNotificationService apnNotificationService;
public BaseApiResponse sendNotificationByRequest(SendNotificationRequest requestData, String senderId) {
BaseApiResponse result = new BaseApiResponse();
public BaseApiResponse<Void> sendNotificationByRequest(SendNotificationRequest requestData, String senderId) {
BaseApiResponse<Void> result = new BaseApiResponse<>();
String[] userIds = requestData.getEmployeeId().split("[;,]");
String transactionId = requestData.getTransactionId();
if(StringUtils.isBlank(transactionId)){
@@ -62,7 +62,7 @@ public class NotificationProxyService {
}
sysLogNotificationRepository.save(notificationLog);
//-- Process APN sending
BaseApiResponse sendResult = apnNotificationService.pushApnMessage(
BaseApiResponse<Void> sendResult = apnNotificationService.pushApnMessage(
transactionId,
deviceTokenValue,
notificationTitle,
@@ -1,7 +1,7 @@
package com.mgm.mcrm.apigateway.reservation.models;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.persistence.*;
import lombok.Data;
@@ -2,7 +2,6 @@ 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;
@@ -13,4 +12,6 @@ public interface BookingDao extends JpaRepository<Bookings, Long> {
List<Bookings> findByDlReservationId(String dlReservationId);
Bookings findFirstByDlProfileIdAndPlayerIdIsNotNull(String dlProfileId);
List<Bookings> findByDlExternalNo(String dlExternalNo);
}
@@ -33,17 +33,13 @@ 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 lombok.extern.java.Log;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.TimeUnit;
@@ -80,131 +76,134 @@ public class BookingService {
private ApnNotificationService apnNotificationService;
public void processDaylightUpdateBooking(Bookings daylightBooking) throws Exception {
if(daylightBooking!=null){
String dlReservationId=daylightBooking.getDlReservationId();
String confirmNo = daylightBooking.getConfirmationNo();
String dbBookingStatus = null;
LocalDateTime checkInDate = null;
LocalDateTime checkOutDate = null;
if(StringUtils.isNotBlank(confirmNo)
&& StringUtils.isNotBlank(dlReservationId)
){
List<Bookings> dbBookings = bookingDao.findByDlReservationId(dlReservationId);
if (CollectionUtils.isEmpty(dbBookings)) {
dbBookings = bookingDao.findByConfirmationNo(confirmNo);
if (daylightBooking == null)
return;
String dlReservationId = daylightBooking.getDlReservationId();
String confirmNo = daylightBooking.getConfirmationNo();
String dlExternalNo = daylightBooking.getDlExternalNo();
List<Bookings> dbBookings = null;
if (StringUtils.isNotEmpty(dlExternalNo)) {
dbBookings = bookingDao.findByDlExternalNo(dlExternalNo);
}
if (CollectionUtils.isEmpty(dbBookings) && StringUtils.isNotEmpty(dlReservationId)) {
TimeUnit.SECONDS.sleep(2);
dbBookings = bookingDao.findByDlReservationId(dlReservationId);
}
if (CollectionUtils.isEmpty(dbBookings) && StringUtils.isNotEmpty(confirmNo)) {
TimeUnit.SECONDS.sleep(2);
dbBookings = bookingDao.findByConfirmationNo(confirmNo);
}
Bookings dbBooking = null;
LocalDateTime checkInDate = null;
LocalDateTime checkOutDate = null;
String dbBookingStatus = null;
if(!CollectionUtils.isEmpty(dbBookings)) {
for(Bookings booking:dbBookings){
if("N".equalsIgnoreCase(booking.getApproveStatus())) {
dbBooking = booking;
dbBookingStatus = dbBooking.getLastOperaStatus();
checkInDate = dbBooking.getCheckinDate();
checkOutDate = dbBooking.getCheckoutDate();
break;
}
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;
dbBookingStatus = dbBooking.getLastOperaStatus();
checkInDate = dbBooking.getCheckinDate();
checkOutDate = dbBooking.getCheckoutDate();
break;
}
}
}
if(dbBooking!=null){
this.mergeDaylightToDbBooking(daylightBooking,dbBooking);
}else{
dbBooking = this.createBookingFromDaylight(daylightBooking);
}
bookingDao.save(dbBooking);
CfgProperties property = propertyDao.findByDlPropertyId(dbBooking.getDlPropertyId());
//Send Notification to Booker when booking lastOpera status Changed
LogUtil.info(BookingService.class,"Daylight webhook updated, prepare send notification");
LogUtil.info(BookingService.class,"DaylightBooking status=" + daylightBooking.getLastOperaStatus());
LogUtil.info(BookingService.class,"Db booking status=" + dbBookingStatus);
if (Strings.CS.equals(daylightBooking.getLastOperaStatus(),"RESERVED") && !Strings.CS.equals(dbBookingStatus,daylightBooking.getLastOperaStatus())){
LogUtil.info(BookingService.class,"Booking last opera status changed, DB Record status:"+dbBookingStatus+", DlBooking status:"+daylightBooking.getLastOperaStatus());
//设置Notification 内容
String notificationTitle = "#EN# Room status updated#EN#" +
"#CN#房間狀態已更改#CN#";
String notificationContent = "#EN#Player {playerId}, {propertyName}, {roomCode},room status changed to reserved.#EN#" +
"#CN#Player {playerId}, {propertyName}, {roomCode}, 房間狀態已預訂。#CN#";
String[] placeholders = {
"{playerId}", "{propertyName}", "{roomCode}"
};
LogUtil.info(BookingService.class,"Set notification info");
String[] values = {
StringUtils.defaultString(dbBooking.getPlayerId()),
StringUtils.defaultString(property == null ? null : property.getName()),
StringUtils.defaultString(dbBooking.getRoomTypeCode())
};
LogUtil.info(BookingService.class,"Replace each");
notificationContent = StringUtils.replaceEach(notificationContent, placeholders, values);
String transactionId = "DLBKSC"+dbBooking.getId();
LogUtil.info(BookingService.class,"Get device token from DB Start");
SysLogDeviceToken deviceToken = sysLogDeviceTokenRepository.findByUserId(StringUtils.trim(dbBooking.getCreatedBy()));
LogUtil.info(BookingService.class,"Get device token from DB:" + (deviceToken == null ? "null" : deviceToken.getDeviceToken()));
String deviceTokenValue =null;
String finalTitle = null;
String finalContent = null;
if(deviceToken!=null){
deviceTokenValue = deviceToken.getDeviceToken();
finalTitle = notificationUtil.distinguishLanguage(notificationTitle,deviceToken.getUseLanguageType());
notificationContent = notificationUtil.distinguishLanguage(notificationContent,deviceToken.getUseLanguageType());
}
// NOTIFICATION
apnNotificationService.pushApnMessage(transactionId,deviceTokenValue,finalTitle,notificationContent,true);
}
//Send notification to Booker when Booking C/I | C/O date changed
if ((checkInDate != null && !checkInDate.equals(daylightBooking.getCheckinDate()))||
(checkOutDate != null && !checkOutDate.equals(daylightBooking.getCheckoutDate()))) {
LogUtil.info(BookingService.class,"Booking date changed, DB Record C/I:"+checkInDate+", DB Record C/O:"+checkOutDate+
", Dl Record C/I"+daylightBooking.getCheckinDate()+", Dl Record C/O"+daylightBooking.getCheckoutDate());
//设置Notification 内容
String notificationTitle = "#EN# Room status updated#EN#" +
"#CN#房間狀態已更改#CN#";
String notificationContent = "#EN#Player {playerId}, {propertyName}, {roomCode},Stay date changed.#EN#" +
"#CN#Player {playerId}, {propertyName}, {roomCode}, 住宿日期已變更。#CN#";
String[] placeholders = {
"{playerId}", "{propertyName}", "{roomCode}"
};
String[] values = {
StringUtils.defaultString(dbBooking.getPlayerId()),
StringUtils.defaultString(property == null ? null : property.getName()),
StringUtils.defaultString(dbBooking.getRoomTypeCode())
};
notificationContent = StringUtils.replaceEach(notificationContent, placeholders, values);
String transactionId = "DLBKDC" + dbBooking.getId();
SysLogDeviceToken deviceToken = sysLogDeviceTokenRepository.findByUserId(StringUtils.trim(dbBooking.getCreatedBy()));
LogUtil.info(BookingService.class,"Get device token from DB:" + (deviceToken == null ? "null" : deviceToken.getDeviceToken()));
String deviceTokenValue = null;
String finalTitle = null;
String finalContent = null;
if (deviceToken != null) {
LogUtil.info(BookingService.class,"Get device Token success");
deviceTokenValue = deviceToken.getDeviceToken();
finalTitle = notificationUtil.distinguishLanguage(notificationTitle, deviceToken.getUseLanguageType());
notificationContent = notificationUtil.distinguishLanguage(notificationContent, deviceToken.getUseLanguageType());
}
// NOTIFICATION
apnNotificationService.pushApnMessage(transactionId, deviceTokenValue, finalTitle, notificationContent, true);
}
LogUtil.info(BookingService.class,"End send notification function");
}
}
if(dbBooking!=null){
LogUtil.info(getClass(), "Find db Booking, id=" + dbBooking.getId());
this.mergeDaylightToDbBooking(daylightBooking,dbBooking);
}else{
LogUtil.info(getClass(), "Not found db Booking");
dbBooking = this.createBookingFromDaylight(daylightBooking);
}
bookingDao.save(dbBooking);
CfgProperties property = propertyDao.findByDlPropertyId(dbBooking.getDlPropertyId());
//Send Notification to Booker when booking lastOpera status Changed
LogUtil.info(BookingService.class,"Daylight webhook updated, prepare send notification");
LogUtil.info(BookingService.class,"DaylightBooking status=" + daylightBooking.getLastOperaStatus());
LogUtil.info(BookingService.class,"Db booking status=" + dbBookingStatus);
if (Strings.CS.equals(daylightBooking.getLastOperaStatus(),"RESERVED") && !Strings.CS.equals(dbBookingStatus,daylightBooking.getLastOperaStatus())){
LogUtil.info(BookingService.class,"Booking last opera status changed, DB Record status:"+dbBookingStatus+", DlBooking status:"+daylightBooking.getLastOperaStatus());
//设置Notification 内容
String notificationTitle = "#EN# Room status updated#EN#" +
"#CN#房間狀態已更改#CN#";
String notificationContent = "#EN#Player {playerId}, {propertyName}, {roomCode},room status changed to reserved.#EN#" +
"#CN#Player {playerId}, {propertyName}, {roomCode}, 房間狀態已預訂。#CN#";
String[] placeholders = {
"{playerId}", "{propertyName}", "{roomCode}"
};
LogUtil.info(BookingService.class,"Set notification info");
String[] values = {
StringUtils.defaultString(dbBooking.getPlayerId()),
StringUtils.defaultString(property == null ? null : property.getName()),
StringUtils.defaultString(dbBooking.getRoomTypeCode())
};
LogUtil.info(BookingService.class,"Replace each");
notificationContent = StringUtils.replaceEach(notificationContent, placeholders, values);
String transactionId = "DLBKSC"+dbBooking.getId();
LogUtil.info(BookingService.class,"Get device token from DB Start");
LogUtil.info(BookingService.class,"dbBooking.getCreatedBy()=" + dbBooking.getCreatedBy());
SysLogDeviceToken deviceToken = sysLogDeviceTokenRepository.findByUserId(StringUtils.trim(dbBooking.getCreatedBy()));
LogUtil.info(BookingService.class,"Get device token from DB:" + (deviceToken == null ? "null" : deviceToken.getDeviceToken()));
String deviceTokenValue =null;
String finalTitle = null;
String finalContent = null;
if(deviceToken!=null){
deviceTokenValue = deviceToken.getDeviceToken();
finalTitle = notificationUtil.distinguishLanguage(notificationTitle,deviceToken.getUseLanguageType());
notificationContent = notificationUtil.distinguishLanguage(notificationContent,deviceToken.getUseLanguageType());
}
// NOTIFICATION
apnNotificationService.pushApnMessage(transactionId,deviceTokenValue,finalTitle,notificationContent,true);
}
//Send notification to Booker when Booking C/I | C/O date changed
if ((checkInDate != null && !checkInDate.equals(daylightBooking.getCheckinDate()))||
(checkOutDate != null && !checkOutDate.equals(daylightBooking.getCheckoutDate()))) {
LogUtil.info(BookingService.class,"Booking date changed, DB Record C/I:"+checkInDate+", DB Record C/O:"+checkOutDate+
", Dl Record C/I"+daylightBooking.getCheckinDate()+", Dl Record C/O"+daylightBooking.getCheckoutDate());
//设置Notification 内容
String notificationTitle = "#EN# Room status updated#EN#" +
"#CN#房間狀態已更改#CN#";
String notificationContent = "#EN#Player {playerId}, {propertyName}, {roomCode},Stay date changed.#EN#" +
"#CN#Player {playerId}, {propertyName}, {roomCode}, 住宿日期已變更。#CN#";
String[] placeholders = {
"{playerId}", "{propertyName}", "{roomCode}"
};
String[] values = {
StringUtils.defaultString(dbBooking.getPlayerId()),
StringUtils.defaultString(property == null ? null : property.getName()),
StringUtils.defaultString(dbBooking.getRoomTypeCode())
};
notificationContent = StringUtils.replaceEach(notificationContent, placeholders, values);
String transactionId = "DLBKDC" + dbBooking.getId();
SysLogDeviceToken deviceToken = sysLogDeviceTokenRepository.findByUserId(StringUtils.trim(dbBooking.getCreatedBy()));
LogUtil.info(BookingService.class,"Get device token from DB:" + (deviceToken == null ? "null" : deviceToken.getDeviceToken()));
String deviceTokenValue = null;
String finalTitle = null;
String finalContent = null;
if (deviceToken != null) {
LogUtil.info(BookingService.class,"Get device Token success");
deviceTokenValue = deviceToken.getDeviceToken();
finalTitle = notificationUtil.distinguishLanguage(notificationTitle, deviceToken.getUseLanguageType());
notificationContent = notificationUtil.distinguishLanguage(notificationContent, deviceToken.getUseLanguageType());
}
// NOTIFICATION
apnNotificationService.pushApnMessage(transactionId, deviceTokenValue, finalTitle, notificationContent, true);
}
LogUtil.info(BookingService.class,"End send notification function");
}
public Bookings findOneByDlProfileId(String dlProfileId) {
@@ -212,46 +211,46 @@ public class BookingService {
}
// 第一条是daylight返回过来的booking,第二条是本地数据库的booking
public void mergeDaylightToDbBooking(Bookings daylightBooking, Bookings updateBooking) throws Exception {
public void mergeDaylightToDbBooking(Bookings daylightBooking, Bookings dbBooking) 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());
if(!"PN".equals(dbBooking.getApproveStatus())) {
dbBooking.setCheckinDate(daylightBooking.getCheckinDate());
dbBooking.setCheckoutDate(daylightBooking.getCheckoutDate());
dbBooking.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());
dbBooking.setConfirmationNo(daylightBooking.getConfirmationNo());
dbBooking.setHotelCode(daylightBooking.getHotelCode());
dbBooking.setDlProfileId(daylightBooking.getDlProfileId());
dbBooking.setDlSourceId(daylightBooking.getDlSourceId());
dbBooking.setDlGuaranteeTypeId(daylightBooking.getDlGuaranteeTypeId());
dbBooking.setDlChannelId(daylightBooking.getDlChannelId());
dbBooking.setDlAccountId(daylightBooking.getDlAccountId());
dbBooking.setDlBlockId(daylightBooking.getDlBlockId());
dbBooking.setDlExternalNo(daylightBooking.getDlExternalNo());
if (updateBooking.getWaitlistId() != null) {
bookingDao.findById(updateBooking.getWaitlistId()).ifPresent(booking -> {
if (dbBooking.getWaitlistId() != null) {
bookingDao.findById(dbBooking.getWaitlistId()).ifPresent(booking -> {
booking.setConfirmationNo(daylightBooking.getConfirmationNo());
bookingDao.save(booking);
});
}
// updateBooking.setGroupId(daylightBooking.getGroupId());
updateBooking.setDlReservationId(daylightBooking.getDlReservationId());
// dbBooking.setGroupId(daylightBooking.getGroupId());
dbBooking.setDlReservationId(daylightBooking.getDlReservationId());
// 通过 dlRoomTypeId 查找 room type(与 createBookingFromDaylight 流程保持一致)
CfgRoomTypes daylightRoomType = roomTypeDao.findByDlRoomTypeId(daylightBooking.getDlRoomTypeId());
if(daylightRoomType!=null) {
updateBooking.setRoomTypeId(daylightRoomType.getId());
updateBooking.setRoomTypeCode(daylightRoomType.getCode());
dbBooking.setRoomTypeId(daylightRoomType.getId());
dbBooking.setRoomTypeCode(daylightRoomType.getCode());
List<CfgRoomsRoomTypes> 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()));
if(room.getPropertyId().equals(dbBooking.getPropertyId())) {
//dbBooking.setRoom(room.getName());
dbBooking.setRoomId(Math.toIntExact(room.getId()));
break;
}
}
@@ -267,52 +266,52 @@ public class BookingService {
List<CfgDaylightView> bookingTypeOperas = daylightViewDao.findByMarketCodeIdAndRateCodeId(operaMarketCodeId,operaRateCodeId);
if(!bookingTypeOperas.isEmpty()) {
Long operaBookingTypeId = bookingTypeOperas.getFirst().getBookingTypeId();
updateBooking.setBookingTypeId(Math.toIntExact(operaBookingTypeId));
dbBooking.setBookingTypeId(Math.toIntExact(operaBookingTypeId));
}
}
if("CANCELED".equals(daylightBooking.getLastOperaStatus())) {
updateBooking.setApproveStatus("C");
dbBooking.setApproveStatus("C");
daylightBooking.setApproveStatus("C");
}else {
if("C".equals(updateBooking.getApproveStatus())||daylightBooking.getConfirmationNo()!=null) {
updateBooking.setApproveStatus("N");
if("C".equals(dbBooking.getApproveStatus())||daylightBooking.getConfirmationNo()!=null) {
dbBooking.setApproveStatus("N");
daylightBooking.setApproveStatus("N");
}
}
updateBooking.setLastOperaStatus(daylightBooking.getLastOperaStatus());
dbBooking.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(StringUtils.isEmpty(dbBooking.getMember())) {
if(StringUtils.isNotEmpty(dbBooking.getPlayerId())) {
ServiceResult serviceResult = playerService.findWebPlayerProfileByPlayerId(dbBooking.getPlayerId());
if(serviceResult.isSuccess()) {
PlayerProfileApiResult profile = (PlayerProfileApiResult) serviceResult.getReturnData();
updateBooking.setMemberLastName(profile.getName());
updateBooking.setMemberTitle(profile.getSalutation());
updateBooking.setMember(JSONObject.toJSONString(profile));
dbBooking.setMemberLastName(profile.getName());
dbBooking.setMemberTitle(profile.getSalutation());
dbBooking.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());
String booker = StringUtils.trim(dbBooking.getBooker());
if(StringUtils.isEmpty(booker)) getUser=true;
if(daylightBooking.getCreatedBy()!=null && !daylightBooking.getCreatedBy().equals(dbBooking.getCreatedBy())) {
// dbBooking.setCreatedBy(daylightBooking.getCreatedBy());
getUser=true;
}
if(getUser) {
if(!StringUtils.isEmpty(updateBooking.getCreatedBy())) {
ServiceResult userInfoRes = mgmUserService.findUserInfoByUserId(updateBooking.getCreatedBy());
if(StringUtils.isNotEmpty(dbBooking.getCreatedBy())) {
ServiceResult userInfoRes = mgmUserService.findUserInfoByUserId(dbBooking.getCreatedBy());
if(userInfoRes.isSuccess()) {
UserInfoApiRecord userInfo = (UserInfoApiRecord)userInfoRes.getReturnData();
updateBooking.setBooker(userInfo.getUserName());
updateBooking.setCreatedByName(userInfo.getUserName());
dbBooking.setBooker(userInfo.getUserName());
dbBooking.setCreatedByName(userInfo.getUserName());
}
}
}
updateBooking.setUpdatedAt(LocalDateTime.now());
dbBooking.setUpdatedAt(LocalDateTime.now());
}
public Bookings createBookingFromDaylight(Bookings daylightBooking) throws Exception {
@@ -412,7 +411,7 @@ public class BookingService {
dbBooking.setDlReservationId(daylightBooking.getDlReservationId());
//get player info
if(!StringUtils.isEmpty(dbBooking.getPlayerId())) {
if(StringUtils.isNotEmpty(dbBooking.getPlayerId())) {
ServiceResult serviceResult = playerService.findWebPlayerProfileByPlayerId(dbBooking.getPlayerId());
if(serviceResult.isSuccess()) {
PlayerProfileApiResult profile = (PlayerProfileApiResult) serviceResult.getReturnData();
@@ -423,7 +422,8 @@ public class BookingService {
}
//get user info
// 这个判断不会走进去,因为createdBy必定是null,因为eventData没有这个字段 by Mauve 20260806
if(!StringUtils.isEmpty(dbBooking.getCreatedBy())) {
// 现在可以拿到createdBy了 by Mauve 20260920
if(StringUtils.isNotEmpty(dbBooking.getCreatedBy())) {
ServiceResult userInfoRes = mgmUserService.findUserInfoByUserId(dbBooking.getCreatedBy());
if(userInfoRes.isSuccess()) {
UserInfoApiRecord userInfo = (UserInfoApiRecord)userInfoRes.getReturnData();
+4 -2
View File
@@ -127,7 +127,6 @@ server:
logging:
config: classpath:logback.xml
springdoc:
swagger-ui:
path: /swagger-ui.html
@@ -153,4 +152,7 @@ apns:
# Dev host
host: api.sandbox.push.apple.com
# Production Host
# host: api.push.apple.com
# host: api.push.apple.com
daylight:
url: http://localhost:9087/api/daylight/proxy