Compare commits
2 Commits
e6d57a9000
...
719d8c7208
| Author | SHA1 | Date | |
|---|---|---|---|
| 719d8c7208 | |||
| 06211adaac |
+61
@@ -0,0 +1,61 @@
|
||||
package com.ruoyi.web.controller.api;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||
import com.ruoyi.price.search.EsBizLogSupport;
|
||||
import com.ruoyi.price.search.EsLogType;
|
||||
import com.ruoyi.price.search.IBillSearchStore;
|
||||
import com.ruoyi.web.controller.price.BillingController;
|
||||
|
||||
/**
|
||||
* Persist validation failures of quote/bill APIs to Elasticsearch.
|
||||
*/
|
||||
@Order(0)
|
||||
@RestControllerAdvice(assignableTypes = { TmsPriceController.class, BillingController.class })
|
||||
public class PriceSyncEsLogAdvice
|
||||
{
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public AjaxResult handleValid(MethodArgumentNotValidException e)
|
||||
{
|
||||
String message = e.getBindingResult().getFieldError() == null
|
||||
? e.getMessage()
|
||||
: e.getBindingResult().getFieldError().getDefaultMessage();
|
||||
saveFail(e.getBindingResult().getTarget(), message);
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(BindException.class)
|
||||
public AjaxResult handleBind(BindException e)
|
||||
{
|
||||
String message = e.getAllErrors().isEmpty() ? e.getMessage() : e.getAllErrors().get(0).getDefaultMessage();
|
||||
saveFail(e.getTarget(), message);
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
private void saveFail(Object target, String message)
|
||||
{
|
||||
if (target instanceof TmsPriceBillRequest)
|
||||
{
|
||||
TmsPriceBillRequest request = (TmsPriceBillRequest) target;
|
||||
EsBizLogSupport.saveFail(billSearchStore, EsLogType.BILL, EsBizLogSupport.billFailBizId(request), request,
|
||||
message);
|
||||
return;
|
||||
}
|
||||
if (target instanceof TmsPriceQuoteRequest)
|
||||
{
|
||||
TmsPriceQuoteRequest request = (TmsPriceQuoteRequest) target;
|
||||
EsBizLogSupport.saveFail(billSearchStore, EsLogType.QUOTE, EsBizLogSupport.quoteFailBizId(request),
|
||||
request, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-2
@@ -19,6 +19,9 @@ import com.ruoyi.framework.config.properties.TmsApiProperties;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.ruoyi.price.search.EsBizLogSupport;
|
||||
import com.ruoyi.price.search.EsLogType;
|
||||
import com.ruoyi.price.search.IBillSearchStore;
|
||||
import com.ruoyi.price.service.IPrcBillService;
|
||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||
|
||||
@@ -46,6 +49,9 @@ public class TmsPriceController extends BaseController
|
||||
@Autowired
|
||||
private TmsApiProperties tmsApiProperties;
|
||||
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
/**
|
||||
* Quote by customerId + productCode + country + weight.
|
||||
* Match: customer package FIXED > DISCOUNT > product default.
|
||||
@@ -55,7 +61,16 @@ public class TmsPriceController extends BaseController
|
||||
@RequestHeader(value = HEADER_API_CODE, required = false) String headerApiCode,
|
||||
@Validated @RequestBody TmsPriceQuoteRequest request)
|
||||
{
|
||||
assertApiCode(headerApiCode, request == null ? null : request.getApiCode());
|
||||
try
|
||||
{
|
||||
assertApiCode(headerApiCode, request == null ? null : request.getApiCode());
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
EsBizLogSupport.saveFail(billSearchStore, EsLogType.QUOTE, EsBizLogSupport.quoteFailBizId(request),
|
||||
request, e);
|
||||
throw e;
|
||||
}
|
||||
TmsPriceQuoteResult result = tmsPriceQuoteService.quote(request);
|
||||
return success(result);
|
||||
}
|
||||
@@ -69,7 +84,15 @@ public class TmsPriceController extends BaseController
|
||||
@RequestHeader(value = HEADER_API_CODE, required = false) String headerApiCode,
|
||||
@Validated @RequestBody TmsPriceBillRequest request)
|
||||
{
|
||||
assertApiCode(headerApiCode, request == null ? null : request.getApiCode());
|
||||
try
|
||||
{
|
||||
assertApiCode(headerApiCode, request == null ? null : request.getApiCode());
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
EsBizLogSupport.saveFail(billSearchStore, EsLogType.BILL, EsBizLogSupport.billFailBizId(request), request, e);
|
||||
throw e;
|
||||
}
|
||||
TmsPriceQuoteResult result = prcBillService.bill(request);
|
||||
return success(result);
|
||||
}
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.ruoyi.web.controller.monitor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.PageDomain;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.page.TableSupport;
|
||||
import com.ruoyi.price.search.BillSearchPage;
|
||||
import com.ruoyi.price.search.IBillSearchStore;
|
||||
|
||||
/**
|
||||
* Generic Elasticsearch business-log viewer.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/monitor/eslog")
|
||||
public class EsLogController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('monitor:eslog:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(
|
||||
@RequestParam(value = "logType", required = false) String logType,
|
||||
@RequestParam(value = "bizId", required = false) String bizId,
|
||||
@RequestParam(value = "status", required = false) String status)
|
||||
{
|
||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||
int pageNum = pageDomain.getPageNum() == null ? 1 : pageDomain.getPageNum().intValue();
|
||||
int pageSize = pageDomain.getPageSize() == null ? 10 : pageDomain.getPageSize().intValue();
|
||||
BillSearchPage page = billSearchStore.search(logType, bizId, status, pageNum, pageSize);
|
||||
TableDataInfo rsp = new TableDataInfo();
|
||||
rsp.setCode(HttpStatus.SUCCESS);
|
||||
rsp.setMsg("\u67e5\u8be2\u6210\u529f");
|
||||
rsp.setRows(page.getRows());
|
||||
rsp.setTotal(page.getTotal());
|
||||
return rsp;
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('monitor:eslog:list')")
|
||||
@GetMapping("/{docId}")
|
||||
public AjaxResult getInfo(@PathVariable("docId") String docId)
|
||||
{
|
||||
return success(billSearchStore.getByDocId(docId));
|
||||
}
|
||||
}
|
||||
@@ -151,3 +151,9 @@ bms:
|
||||
tms:
|
||||
# Agreed API identification code for TMS callers
|
||||
api-code: BMS.TMS.ZYaOvlQzpcfau8TUaCFfO3Hp8ieHIqGw
|
||||
elasticsearch:
|
||||
enabled: true
|
||||
uris: http://127.0.0.1:9200
|
||||
username:
|
||||
password:
|
||||
index: bms_biz_log
|
||||
|
||||
@@ -74,4 +74,8 @@ public class TmsPriceQuoteResult implements Serializable
|
||||
private String orderRemark;
|
||||
/** true=hit stored quote */
|
||||
private Boolean fromCache;
|
||||
/** original TMS sync request JSON from Elasticsearch */
|
||||
private String requestJson;
|
||||
/** original billing response JSON from Elasticsearch */
|
||||
private String responseJson;
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Bill sync search-store beans.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ElasticsearchProperties.class)
|
||||
public class BillSearchConfiguration
|
||||
{
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "bms.elasticsearch", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public IBillSearchStore esBillSearchStore(ElasticsearchProperties properties)
|
||||
{
|
||||
return new EsBillSearchStore(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(IBillSearchStore.class)
|
||||
public IBillSearchStore noopBillSearchStore()
|
||||
{
|
||||
return new NoopBillSearchStore();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Paginated ES search result. */
|
||||
public class BillSearchPage
|
||||
{
|
||||
private long total;
|
||||
private List<BillSyncSnapshot> rows = new ArrayList<BillSyncSnapshot>();
|
||||
|
||||
public long getTotal()
|
||||
{
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total)
|
||||
{
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public List<BillSyncSnapshot> getRows()
|
||||
{
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(List<BillSyncSnapshot> rows)
|
||||
{
|
||||
this.rows = rows == null ? new ArrayList<BillSyncSnapshot>() : rows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
/** Generic Elasticsearch business log. Distinguished by logType + bizId. */
|
||||
public class BillSyncSnapshot
|
||||
{
|
||||
private String docId;
|
||||
private String logType;
|
||||
private String bizId;
|
||||
private String requestJson;
|
||||
private String responseJson;
|
||||
private String status;
|
||||
private String errorMessage;
|
||||
private String updatedAt;
|
||||
|
||||
public String getDocId()
|
||||
{
|
||||
return docId;
|
||||
}
|
||||
|
||||
public void setDocId(String docId)
|
||||
{
|
||||
this.docId = docId;
|
||||
}
|
||||
|
||||
public String getLogType()
|
||||
{
|
||||
return logType;
|
||||
}
|
||||
|
||||
public void setLogType(String logType)
|
||||
{
|
||||
this.logType = logType;
|
||||
}
|
||||
|
||||
public String getBizId()
|
||||
{
|
||||
return bizId;
|
||||
}
|
||||
|
||||
public void setBizId(String bizId)
|
||||
{
|
||||
this.bizId = bizId;
|
||||
}
|
||||
|
||||
public String getRequestJson()
|
||||
{
|
||||
return requestJson;
|
||||
}
|
||||
|
||||
public void setRequestJson(String requestJson)
|
||||
{
|
||||
this.requestJson = requestJson;
|
||||
}
|
||||
|
||||
public String getResponseJson()
|
||||
{
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
public void setResponseJson(String responseJson)
|
||||
{
|
||||
this.responseJson = responseJson;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getErrorMessage()
|
||||
{
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage)
|
||||
{
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public String getUpdatedAt()
|
||||
{
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt)
|
||||
{
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Elasticsearch connection for bill sync search store.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "bms.elasticsearch")
|
||||
public class ElasticsearchProperties
|
||||
{
|
||||
/** false = skip ES write/read */
|
||||
private boolean enabled = true;
|
||||
|
||||
/** e.g. http://127.0.0.1:9200 */
|
||||
private String uris = "http://127.0.0.1:9200";
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
/** index name */
|
||||
private String index = "bms_biz_log";
|
||||
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getUris()
|
||||
{
|
||||
return uris;
|
||||
}
|
||||
|
||||
public void setUris(String uris)
|
||||
{
|
||||
this.uris = uris;
|
||||
}
|
||||
|
||||
public String getUsername()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username)
|
||||
{
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword()
|
||||
{
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password)
|
||||
{
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getIndex()
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(String index)
|
||||
{
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* Persist / load generic business logs via Elasticsearch REST API.
|
||||
*/
|
||||
public class EsBillSearchStore implements IBillSearchStore
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(EsBillSearchStore.class);
|
||||
|
||||
private final ElasticsearchProperties properties;
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public EsBillSearchStore(ElasticsearchProperties properties)
|
||||
{
|
||||
this.properties = properties;
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(3000);
|
||||
factory.setReadTimeout(5000);
|
||||
RestTemplate template = new RestTemplate(factory);
|
||||
if (StringUtils.isNotEmpty(properties.getUsername()))
|
||||
{
|
||||
String user = properties.getUsername();
|
||||
String pass = properties.getPassword() == null ? "" : properties.getPassword();
|
||||
template.getInterceptors().add((request, body, execution) -> {
|
||||
request.getHeaders().setBasicAuth(user, pass);
|
||||
return execution.execute(request, body);
|
||||
});
|
||||
}
|
||||
this.restTemplate = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(String logType, String bizId, String requestJson, String responseJson, String status,
|
||||
String errorMessage)
|
||||
{
|
||||
if (StringUtils.isEmpty(logType) || StringUtils.isEmpty(bizId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Date now = new Date();
|
||||
String docId = EsLogType.docId(logType, bizId, now);
|
||||
try
|
||||
{
|
||||
JSONObject doc = new JSONObject();
|
||||
doc.put("logType", logType.trim());
|
||||
doc.put("bizId", bizId.trim());
|
||||
doc.put("status", StringUtils.isEmpty(status) ? EsBizLogSupport.STATUS_SUCCESS : status.trim());
|
||||
doc.put("error", StringUtils.isEmpty(errorMessage) ? null : errorMessage);
|
||||
doc.put("request", parseOrRaw(requestJson));
|
||||
doc.put("response", parseOrRaw(responseJson));
|
||||
doc.put("updatedAt", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(now));
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<String> entity = new HttpEntity<String>(doc.toJSONString(), headers);
|
||||
restTemplate.exchange(docUrl(docId), HttpMethod.PUT, entity, String.class);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("elasticsearch save {} / {} failed: {}", logType, bizId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSyncSnapshot get(String logType, String bizId)
|
||||
{
|
||||
if (StringUtils.isEmpty(logType) || StringUtils.isEmpty(bizId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
BillSearchPage page = search(logType, bizId, null, 1, 1);
|
||||
if (page.getRows() == null || page.getRows().isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return page.getRows().get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSyncSnapshot getByDocId(String docId)
|
||||
{
|
||||
if (StringUtils.isEmpty(docId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
ResponseEntity<String> resp = restTemplate.exchange(docUrl(docId), HttpMethod.GET, HttpEntity.EMPTY, String.class);
|
||||
String body = resp.getBody();
|
||||
if (StringUtils.isEmpty(body))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
JSONObject root = JSON.parseObject(body);
|
||||
if (root == null || !Boolean.TRUE.equals(root.getBoolean("found")))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return toSnapshot(root.getJSONObject("_source"), root.getString("_id"));
|
||||
}
|
||||
catch (HttpClientErrorException.NotFound e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("elasticsearch get {} failed: {}", docId, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize)
|
||||
{
|
||||
BillSearchPage page = new BillSearchPage();
|
||||
if (pageNum < 1)
|
||||
{
|
||||
pageNum = 1;
|
||||
}
|
||||
if (pageSize < 1)
|
||||
{
|
||||
pageSize = 10;
|
||||
}
|
||||
try
|
||||
{
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("from", Integer.valueOf((pageNum - 1) * pageSize));
|
||||
body.put("size", Integer.valueOf(pageSize));
|
||||
JSONArray sort = new JSONArray();
|
||||
JSONObject sortItem = new JSONObject();
|
||||
JSONObject sortOpt = new JSONObject();
|
||||
sortOpt.put("order", "desc");
|
||||
sortOpt.put("unmapped_type", "keyword");
|
||||
sortItem.put("updatedAt.keyword", sortOpt);
|
||||
sort.add(sortItem);
|
||||
body.put("sort", sort);
|
||||
body.put("query", buildQuery(logType, bizId, status));
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<String> entity = new HttpEntity<String>(body.toJSONString(), headers);
|
||||
ResponseEntity<String> resp = restTemplate.exchange(searchUrl(), HttpMethod.POST, entity, String.class);
|
||||
JSONObject root = JSON.parseObject(resp.getBody());
|
||||
if (root == null)
|
||||
{
|
||||
return page;
|
||||
}
|
||||
JSONObject hits = root.getJSONObject("hits");
|
||||
if (hits == null)
|
||||
{
|
||||
return page;
|
||||
}
|
||||
page.setTotal(readTotal(hits.get("total")));
|
||||
JSONArray hitList = hits.getJSONArray("hits");
|
||||
if (hitList == null)
|
||||
{
|
||||
return page;
|
||||
}
|
||||
for (int i = 0; i < hitList.size(); i++)
|
||||
{
|
||||
JSONObject hit = hitList.getJSONObject(i);
|
||||
if (hit == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
BillSyncSnapshot snap = toSnapshot(hit.getJSONObject("_source"), hit.getString("_id"));
|
||||
if (snap != null)
|
||||
{
|
||||
page.getRows().add(snap);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpClientErrorException.NotFound e)
|
||||
{
|
||||
return page;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("elasticsearch search failed: {}", e.getMessage());
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
private BillSyncSnapshot toSnapshot(JSONObject source, String id)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
BillSyncSnapshot snap = new BillSyncSnapshot();
|
||||
snap.setDocId(id);
|
||||
snap.setLogType(source.getString("logType"));
|
||||
snap.setBizId(source.getString("bizId"));
|
||||
if (StringUtils.isEmpty(snap.getBizId()))
|
||||
{
|
||||
snap.setBizId(source.getString("orderNo"));
|
||||
}
|
||||
if (StringUtils.isEmpty(snap.getLogType()) && StringUtils.isNotEmpty(source.getString("billId")))
|
||||
{
|
||||
snap.setLogType(EsLogType.BILL);
|
||||
}
|
||||
snap.setRequestJson(toJsonString(source.get("request")));
|
||||
snap.setResponseJson(toJsonString(source.get("response")));
|
||||
snap.setUpdatedAt(source.getString("updatedAt"));
|
||||
String status = source.getString("status");
|
||||
snap.setStatus(StringUtils.isEmpty(status) ? EsBizLogSupport.STATUS_SUCCESS : status);
|
||||
snap.setErrorMessage(source.getString("error"));
|
||||
return snap;
|
||||
}
|
||||
|
||||
private JSONObject buildQuery(String logType, String bizId, String status)
|
||||
{
|
||||
JSONArray filters = new JSONArray();
|
||||
addTerm(filters, "logType", logType);
|
||||
addWildcard(filters, "bizId", bizId);
|
||||
addTerm(filters, "status", status);
|
||||
if (filters.isEmpty())
|
||||
{
|
||||
JSONObject matchAll = new JSONObject();
|
||||
matchAll.put("match_all", new JSONObject());
|
||||
return matchAll;
|
||||
}
|
||||
JSONObject bool = new JSONObject();
|
||||
bool.put("must", filters);
|
||||
JSONObject query = new JSONObject();
|
||||
query.put("bool", bool);
|
||||
return query;
|
||||
}
|
||||
|
||||
private void addTerm(JSONArray filters, String field, String value)
|
||||
{
|
||||
if (StringUtils.isEmpty(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
JSONObject term = new JSONObject();
|
||||
term.put(field + ".keyword", value.trim());
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("term", term);
|
||||
filters.add(wrap);
|
||||
}
|
||||
|
||||
private void addWildcard(JSONArray filters, String field, String value)
|
||||
{
|
||||
if (StringUtils.isEmpty(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
JSONObject wildcard = new JSONObject();
|
||||
JSONObject opts = new JSONObject();
|
||||
opts.put("value", "*" + escapeWildcard(value.trim()) + "*");
|
||||
opts.put("case_insensitive", Boolean.TRUE);
|
||||
wildcard.put(field + ".keyword", opts);
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("wildcard", wildcard);
|
||||
filters.add(wrap);
|
||||
}
|
||||
|
||||
private String escapeWildcard(String value)
|
||||
{
|
||||
return value.replace("\\", "\\\\").replace("*", "\\*").replace("?", "\\?");
|
||||
}
|
||||
|
||||
private long readTotal(Object total)
|
||||
{
|
||||
if (total instanceof JSONObject)
|
||||
{
|
||||
return ((JSONObject) total).getLongValue("value");
|
||||
}
|
||||
if (total instanceof Number)
|
||||
{
|
||||
return ((Number) total).longValue();
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
|
||||
private String searchUrl()
|
||||
{
|
||||
return baseUrl() + "/" + properties.getIndex() + "/_search";
|
||||
}
|
||||
|
||||
private String docUrl(String docId)
|
||||
{
|
||||
String encodedId = URLEncoder.encode(docId, StandardCharsets.UTF_8);
|
||||
return baseUrl() + "/" + properties.getIndex() + "/_doc/" + encodedId;
|
||||
}
|
||||
|
||||
private String baseUrl()
|
||||
{
|
||||
String base = properties.getUris();
|
||||
if (base.endsWith("/"))
|
||||
{
|
||||
base = base.substring(0, base.length() - 1);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
private Object parseOrRaw(String json)
|
||||
{
|
||||
if (StringUtils.isEmpty(json))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
return JSON.parse(json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
private String toJsonString(Object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (value instanceof String)
|
||||
{
|
||||
return (String) value;
|
||||
}
|
||||
return JSON.toJSONString(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
import java.util.UUID;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.sign.Md5Utils;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||
|
||||
/** Helpers for ES business logs (success / fail). */
|
||||
public final class EsBizLogSupport
|
||||
{
|
||||
public static final String STATUS_SUCCESS = "SUCCESS";
|
||||
public static final String STATUS_FAIL = "FAIL";
|
||||
|
||||
private static final int ERROR_MAX = 2000;
|
||||
|
||||
private EsBizLogSupport()
|
||||
{
|
||||
}
|
||||
|
||||
public static String requestJson(Object request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
JSONObject obj = JSON.parseObject(JSON.toJSONString(request));
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
obj.remove("apiCode");
|
||||
return obj.toJSONString();
|
||||
}
|
||||
|
||||
public static String failResponseJson(String errorMessage)
|
||||
{
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("success", Boolean.FALSE);
|
||||
obj.put("msg", errorMessage);
|
||||
return obj.toJSONString();
|
||||
}
|
||||
|
||||
public static String errorText(Throwable e)
|
||||
{
|
||||
if (e == null)
|
||||
{
|
||||
return "UNKNOWN_ERROR";
|
||||
}
|
||||
String msg = e.getMessage();
|
||||
if (StringUtils.isEmpty(msg))
|
||||
{
|
||||
msg = e.getClass().getSimpleName();
|
||||
}
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null && StringUtils.isNotEmpty(cause.getMessage())
|
||||
&& (msg == null || !msg.contains(cause.getMessage())))
|
||||
{
|
||||
msg = msg + " | " + cause.getMessage();
|
||||
}
|
||||
return cut(msg);
|
||||
}
|
||||
|
||||
public static String cut(String msg)
|
||||
{
|
||||
if (msg == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String text = msg.trim();
|
||||
if (text.length() > ERROR_MAX)
|
||||
{
|
||||
return text.substring(0, ERROR_MAX);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
public static String billBizId(TmsPriceBillRequest request)
|
||||
{
|
||||
if (request != null && StringUtils.isNotEmpty(request.getOrderNo()))
|
||||
{
|
||||
return request.getOrderNo().trim();
|
||||
}
|
||||
return "FAIL-" + UUID.randomUUID();
|
||||
}
|
||||
|
||||
public static String billFailBizId(TmsPriceBillRequest request)
|
||||
{
|
||||
return billBizId(request);
|
||||
}
|
||||
|
||||
public static String quoteFailBizId(TmsPriceQuoteRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
return "FAIL-" + UUID.randomUUID();
|
||||
}
|
||||
String raw = nvl(request.getCustomerCode()) + "|" + nvl(request.getCustomerId()) + "|"
|
||||
+ nvl(request.getProductCode()) + "|" + nvl(request.getCountryCode()) + "|"
|
||||
+ nvl(request.getCountryName()) + "|" + nvl(request.getWeight()) + "|"
|
||||
+ nvl(request.getBizDate());
|
||||
return "FAIL-" + Md5Utils.hash(raw);
|
||||
}
|
||||
|
||||
public static void saveFail(IBillSearchStore store, String logType, String bizId, Object request, Throwable e)
|
||||
{
|
||||
saveFail(store, logType, bizId, request, errorText(e));
|
||||
}
|
||||
|
||||
public static void saveFail(IBillSearchStore store, String logType, String bizId, Object request, String errorMessage)
|
||||
{
|
||||
if (store == null || StringUtils.isEmpty(logType) || StringUtils.isEmpty(bizId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
String msg = cut(errorMessage);
|
||||
store.save(logType, bizId, requestJson(request), failResponseJson(msg), STATUS_FAIL, msg);
|
||||
}
|
||||
|
||||
private static String nvl(Object value)
|
||||
{
|
||||
return value == null ? "" : String.valueOf(value).trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/** Common ES business-log types. */
|
||||
public final class EsLogType
|
||||
{
|
||||
public static final String BILL = "BILL";
|
||||
public static final String QUOTE = "QUOTE";
|
||||
|
||||
private EsLogType()
|
||||
{
|
||||
}
|
||||
|
||||
/** One document per bizId + write time. */
|
||||
public static String docId(String logType, String bizId)
|
||||
{
|
||||
return docId(logType, bizId, new Date());
|
||||
}
|
||||
|
||||
public static String docId(String logType, String bizId, Date at)
|
||||
{
|
||||
String type = logType == null ? "" : logType.trim();
|
||||
String id = bizId == null ? "" : bizId.trim();
|
||||
Date time = at == null ? new Date() : at;
|
||||
String ts = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(time);
|
||||
String uniq = Long.toHexString(System.nanoTime());
|
||||
if (uniq.length() > 6)
|
||||
{
|
||||
uniq = uniq.substring(uniq.length() - 6);
|
||||
}
|
||||
return type + "_" + id + "_" + ts + "_" + uniq;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
/**
|
||||
* Generic Elasticsearch store for business request/response logs.
|
||||
*/
|
||||
public interface IBillSearchStore
|
||||
{
|
||||
default void save(String logType, String bizId, String requestJson, String responseJson)
|
||||
{
|
||||
save(logType, bizId, requestJson, responseJson, EsBizLogSupport.STATUS_SUCCESS, null);
|
||||
}
|
||||
|
||||
void save(String logType, String bizId, String requestJson, String responseJson, String status, String errorMessage);
|
||||
|
||||
BillSyncSnapshot get(String logType, String bizId);
|
||||
|
||||
BillSyncSnapshot getByDocId(String docId);
|
||||
|
||||
BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.price.search;
|
||||
|
||||
/**
|
||||
* No-op when Elasticsearch is disabled.
|
||||
*/
|
||||
public class NoopBillSearchStore implements IBillSearchStore
|
||||
{
|
||||
@Override
|
||||
public void save(String logType, String bizId, String requestJson, String responseJson, String status,
|
||||
String errorMessage)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSyncSnapshot get(String logType, String bizId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSyncSnapshot getByDocId(String docId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize)
|
||||
{
|
||||
return new BillSearchPage();
|
||||
}
|
||||
}
|
||||
+71
-15
@@ -10,6 +10,8 @@ import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
@@ -21,6 +23,9 @@ import com.ruoyi.price.domain.vo.TmsPriceQuoteFeeLine;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.ruoyi.price.mapper.PrcBillFeeMapper;
|
||||
import com.ruoyi.price.mapper.PrcBillMapper;
|
||||
import com.ruoyi.price.search.EsBizLogSupport;
|
||||
import com.ruoyi.price.search.EsLogType;
|
||||
import com.ruoyi.price.search.IBillSearchStore;
|
||||
import com.ruoyi.price.service.IPrcBillService;
|
||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||
|
||||
@@ -36,24 +41,35 @@ public class PrcBillServiceImpl implements IPrcBillService
|
||||
@Autowired
|
||||
private PrcBillFeeMapper billFeeMapper;
|
||||
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult bill(TmsPriceBillRequest request)
|
||||
{
|
||||
if (request == null || StringUtils.isEmpty(request.getOrderNo()))
|
||||
try
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.orderNoRequired"));
|
||||
if (request == null || StringUtils.isEmpty(request.getOrderNo()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.orderNoRequired"));
|
||||
}
|
||||
request.setOrderNo(request.getOrderNo().trim());
|
||||
if (StringUtils.isEmpty(request.getBizDate()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.bizDateRequired"));
|
||||
}
|
||||
Date bizDate = resolveBizDate(request.getBizDate());
|
||||
BigDecimal weight = request.getWeight();
|
||||
TmsPriceQuoteResult result = tmsPriceQuoteService.calculate(request);
|
||||
persistBill(request, weight, bizDate, result);
|
||||
return result;
|
||||
}
|
||||
request.setOrderNo(request.getOrderNo().trim());
|
||||
if (StringUtils.isEmpty(request.getBizDate()))
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.bizDateRequired"));
|
||||
EsBizLogSupport.saveFail(billSearchStore, EsLogType.BILL, EsBizLogSupport.billFailBizId(request), request, e);
|
||||
throw e;
|
||||
}
|
||||
Date bizDate = resolveBizDate(request.getBizDate());
|
||||
BigDecimal weight = request.getWeight();
|
||||
TmsPriceQuoteResult result = tmsPriceQuoteService.calculate(request);
|
||||
persistBill(request, weight, bizDate, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,6 +168,11 @@ public class PrcBillServiceImpl implements IPrcBillService
|
||||
bill.setPayableTotal(result.getPayableTotal());
|
||||
bill.setCurrency(result.getCurrency());
|
||||
bill.setRoundMode(result.getRoundMode());
|
||||
result.setBillId(bill.getBillId());
|
||||
result.setOrderNo(orderNo);
|
||||
result.setCustomerCode(request.getCustomerCode());
|
||||
fillResultOrder(result, bill);
|
||||
result.setFromCache(Boolean.valueOf(existed != null));
|
||||
if (existed == null)
|
||||
{
|
||||
billMapper.insertPrcBill(bill);
|
||||
@@ -166,11 +187,7 @@ public class PrcBillServiceImpl implements IPrcBillService
|
||||
{
|
||||
billFeeMapper.insertBatch(fees);
|
||||
}
|
||||
result.setBillId(bill.getBillId());
|
||||
result.setOrderNo(orderNo);
|
||||
result.setCustomerCode(request.getCustomerCode());
|
||||
fillResultOrder(result, bill);
|
||||
result.setFromCache(Boolean.valueOf(existed != null));
|
||||
billSearchStore.save(EsLogType.BILL, orderNo, toRequestJson(request), toResponseJson(result));
|
||||
}
|
||||
|
||||
private List<PrcBillFee> toFeeRows(String billId, TmsPriceQuoteResult result)
|
||||
@@ -274,6 +291,45 @@ public class PrcBillServiceImpl implements IPrcBillService
|
||||
r.setOrderRemark(bill.getOrderRemark());
|
||||
}
|
||||
|
||||
private String toRequestJson(TmsPriceBillRequest request)
|
||||
{
|
||||
JSONObject obj = parseJsonObject(JSON.toJSONString(request));
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
obj.remove("apiCode");
|
||||
return obj.toJSONString();
|
||||
}
|
||||
|
||||
private String toResponseJson(TmsPriceQuoteResult result)
|
||||
{
|
||||
JSONObject obj = parseJsonObject(JSON.toJSONString(result));
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
obj.remove("requestJson");
|
||||
obj.remove("responseJson");
|
||||
return obj.toJSONString();
|
||||
}
|
||||
|
||||
private JSONObject parseJsonObject(String json)
|
||||
{
|
||||
if (StringUtils.isEmpty(json))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
return JSON.parseObject(json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Date resolveBizDate(String bizDate)
|
||||
{
|
||||
if (StringUtils.isEmpty(bizDate))
|
||||
|
||||
+48
@@ -14,6 +14,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
@@ -37,6 +39,9 @@ import com.ruoyi.price.mapper.PrcPriceSetMapper;
|
||||
import com.ruoyi.price.mapper.PrcQuoteFeeMapper;
|
||||
import com.ruoyi.price.mapper.PrcQuoteMapper;
|
||||
import com.ruoyi.price.mapper.PrcSurchargeRelMapper;
|
||||
import com.ruoyi.price.search.EsBizLogSupport;
|
||||
import com.ruoyi.price.search.EsLogType;
|
||||
import com.ruoyi.price.search.IBillSearchStore;
|
||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||
|
||||
/**
|
||||
@@ -82,9 +87,26 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
@Autowired
|
||||
private PrcQuoteFeeMapper quoteFeeMapper;
|
||||
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult quote(TmsPriceQuoteRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
return quoteInternal(request);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
EsBizLogSupport.saveFail(billSearchStore, EsLogType.QUOTE, EsBizLogSupport.quoteFailBizId(request),
|
||||
request, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private TmsPriceQuoteResult quoteInternal(TmsPriceQuoteRequest request)
|
||||
{
|
||||
validateRequest(request);
|
||||
Date bizDate = resolveBizDate(request.getBizDate());
|
||||
@@ -104,6 +126,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
replaceQuoteFees(cached.getQuoteId(), refreshed);
|
||||
refreshed.setQuoteId(cached.getQuoteId());
|
||||
refreshed.setFromCache(false);
|
||||
billSearchStore.save(EsLogType.QUOTE, cached.getQuoteId(), toEsRequest(request), toEsResponse(refreshed));
|
||||
return refreshed;
|
||||
}
|
||||
|
||||
@@ -531,6 +554,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
}
|
||||
result.setQuoteId(quote.getQuoteId());
|
||||
result.setFromCache(false);
|
||||
billSearchStore.save(EsLogType.QUOTE, quote.getQuoteId(), toEsRequest(request), toEsResponse(result));
|
||||
}
|
||||
|
||||
private boolean isStaleSurchargeFees(List<PrcQuoteFee> fees)
|
||||
@@ -580,6 +604,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
for (TmsPriceQuoteFeeLine line : result.getFeeDetails())
|
||||
{
|
||||
PrcQuoteFee fee = new PrcQuoteFee();
|
||||
fee.setFeeId(UUID.randomUUID().toString());
|
||||
fee.setQuoteId(quoteId);
|
||||
fee.setFeeType(line.getFeeType());
|
||||
fee.setFeeName(line.getFeeName());
|
||||
@@ -832,6 +857,29 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
||||
return p == null ? null : p.getProductName();
|
||||
}
|
||||
|
||||
private String toEsRequest(TmsPriceQuoteRequest request)
|
||||
{
|
||||
JSONObject obj = JSON.parseObject(JSON.toJSONString(request));
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
obj.remove("apiCode");
|
||||
return obj.toJSONString();
|
||||
}
|
||||
|
||||
private String toEsResponse(TmsPriceQuoteResult result)
|
||||
{
|
||||
JSONObject obj = JSON.parseObject(JSON.toJSONString(result));
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
obj.remove("requestJson");
|
||||
obj.remove("responseJson");
|
||||
return obj.toJSONString();
|
||||
}
|
||||
|
||||
private static String emptyToNull(String s)
|
||||
{
|
||||
return StringUtils.isEmpty(s) ? null : s.trim();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
-- ES log menu under system monitor (parent_id = 2)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2320, N'ES日志', 2, 7, 'eslog', 'monitor/eslog/index', '', 'EsLog', 1, 0, 'C', '0', '0', 'monitor:eslog:list', 'log', 'admin', sysdate(), N'查看Elasticsearch计费同步报文'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2320);
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
SELECT 2321, N'查询', 2320, 1, '', NULL, 1, 0, 'F', '0', '0', 'monitor:eslog:list', '#', 'admin', sysdate()
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2321);
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, m.menu_id FROM (SELECT 2320 AS menu_id UNION SELECT 2321) m
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id);
|
||||
@@ -0,0 +1,16 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listEsLog(query) {
|
||||
return request({
|
||||
url: '/monitor/eslog/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getEsLog(docId) {
|
||||
return request({
|
||||
url: '/monitor/eslog/' + encodeURIComponent(docId),
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<basic-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:search-columns="searchColumns"
|
||||
:query="queryList"
|
||||
row-key="docId"
|
||||
>
|
||||
<template #col-logType="{ row }">
|
||||
<el-tag size="small" :type="typeTag(row.logType)">{{ typeLabel(row.logType) }}</el-tag>
|
||||
</template>
|
||||
<template #col-status="{ row }">
|
||||
<el-tag size="small" :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag>
|
||||
</template>
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="View" @click="handleView(row)" v-hasPermi="['monitor:eslog:list']">查看</el-button>
|
||||
</template>
|
||||
</basic-table>
|
||||
|
||||
<el-drawer v-model="open" size="720px" :title="drawerTitle" append-to-body destroy-on-close>
|
||||
<div v-loading="loading">
|
||||
<el-empty v-if="!detail" description="暂无详情" />
|
||||
<div v-else>
|
||||
<el-descriptions :column="2" border size="small" class="mb12">
|
||||
<el-descriptions-item label="类型">
|
||||
<el-tag size="small" :type="typeTag(detail.logType)">{{ typeLabel(detail.logType) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag size="small" :type="statusTag(detail.status)">{{ statusLabel(detail.status) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="业务ID">{{ detail.bizId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="写入时间">{{ detail.updatedAt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.errorMessage" label="错误信息" :span="2">
|
||||
<span class="err-text">{{ detail.errorMessage }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="json-panel">
|
||||
<div class="json-panel__head">
|
||||
<span>请求报文</span>
|
||||
<el-button size="small" icon="CopyDocument" @click="copyJson(detail.requestJson)">复制</el-button>
|
||||
</div>
|
||||
<pre class="code-pre">{{ formatJson(detail.requestJson) }}</pre>
|
||||
</div>
|
||||
<div class="json-panel">
|
||||
<div class="json-panel__head">
|
||||
<span>返回报文</span>
|
||||
<el-button size="small" icon="CopyDocument" @click="copyJson(detail.responseJson)">复制</el-button>
|
||||
</div>
|
||||
<pre class="code-pre">{{ formatJson(detail.responseJson) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="EsLog">
|
||||
import { listEsLog, getEsLog } from '@/api/monitor/eslog'
|
||||
|
||||
const tableRef = ref()
|
||||
const open = ref(false)
|
||||
const loading = ref(false)
|
||||
const detail = ref(null)
|
||||
const drawerTitle = ref('ES日志详情')
|
||||
|
||||
const typeOptions = [
|
||||
{ label: '计费', value: 'BILL' },
|
||||
{ label: '试算', value: 'QUOTE' }
|
||||
]
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '成功', value: 'SUCCESS' },
|
||||
{ label: '失败', value: 'FAIL' }
|
||||
]
|
||||
|
||||
const searchColumns = [
|
||||
{ label: '类型', prop: 'logType', component: 'select', options: typeOptions, placeholder: '请选择类型', width: '160px', props: { clearable: true } },
|
||||
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '请选择状态', width: '140px', props: { clearable: true } },
|
||||
{ label: '业务ID', prop: 'bizId', component: 'input', placeholder: '业务ID', width: '220px' }
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ label: '类型', prop: 'logType', align: 'center', width: 110, slot: 'logType' },
|
||||
{ label: '状态', prop: 'status', align: 'center', width: 100, slot: 'status' },
|
||||
{ label: '业务ID', prop: 'bizId', minWidth: 180, showOverflowTooltip: true },
|
||||
{ label: '写入时间', prop: 'updatedAt', align: 'center', width: 180 },
|
||||
{ label: '操作', prop: 'action', align: 'center', width: 90, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
]
|
||||
|
||||
function queryList(params) {
|
||||
return listEsLog(params)
|
||||
}
|
||||
|
||||
function typeLabel(type) {
|
||||
const found = typeOptions.find(item => item.value === type)
|
||||
return found ? found.label : (type || '-')
|
||||
}
|
||||
|
||||
function typeTag(type) {
|
||||
if (type === 'BILL') return 'success'
|
||||
if (type === 'QUOTE') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function statusLabel(status) {
|
||||
const found = statusOptions.find(item => item.value === status)
|
||||
if (found) return found.label
|
||||
return status === 'FAIL' ? '失败' : '成功'
|
||||
}
|
||||
|
||||
function statusTag(status) {
|
||||
return status === 'FAIL' ? 'danger' : 'success'
|
||||
}
|
||||
|
||||
function handleView(row) {
|
||||
drawerTitle.value = 'ES日志详情 #' + (row.bizId || row.docId)
|
||||
open.value = true
|
||||
loading.value = true
|
||||
detail.value = null
|
||||
getEsLog(row.docId).then(res => {
|
||||
detail.value = res.data || row
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function formatJson(val) {
|
||||
if (val === null || val === undefined || val === '') return '无数据'
|
||||
if (typeof val === 'object') {
|
||||
try { return JSON.stringify(val, null, 2) } catch (e) { return String(val) }
|
||||
}
|
||||
try { return JSON.stringify(JSON.parse(val), null, 2) } catch (e) { return String(val) }
|
||||
}
|
||||
|
||||
function copyJson(val) {
|
||||
const text = formatJson(val)
|
||||
const done = () => ElMessage({ message: '已复制', type: 'success', duration: 1500 })
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(done)
|
||||
return
|
||||
}
|
||||
const ta = document.createElement('textarea')
|
||||
ta.value = text
|
||||
document.body.appendChild(ta)
|
||||
ta.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(ta)
|
||||
done()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 { margin-bottom: 12px; }
|
||||
.err-text { color: #f56c6c; word-break: break-all; }
|
||||
.json-panel {
|
||||
margin-bottom: 12px;
|
||||
background: #f7f9fb;
|
||||
border: 1px solid #e8ecf0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.json-panel__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
background: #fafbfc;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.json-panel .code-pre {
|
||||
margin: 0;
|
||||
padding: 12px 14px;
|
||||
max-height: 280px;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user