记录同步数据日志功能
This commit is contained in:
+53
@@ -0,0 +1,53 @@
|
|||||||
|
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)
|
||||||
|
{
|
||||||
|
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, 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:
|
tms:
|
||||||
# Agreed API identification code for TMS callers
|
# Agreed API identification code for TMS callers
|
||||||
api-code: BMS.TMS.ZYaOvlQzpcfau8TUaCFfO3Hp8ieHIqGw
|
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;
|
private String orderRemark;
|
||||||
/** true=hit stored quote */
|
/** true=hit stored quote */
|
||||||
private Boolean fromCache;
|
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,72 @@
|
|||||||
|
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 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 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,330 @@
|
|||||||
|
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)
|
||||||
|
{
|
||||||
|
if (StringUtils.isEmpty(logType) || StringUtils.isEmpty(bizId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String docId = EsLogType.docId(logType, bizId);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JSONObject doc = new JSONObject();
|
||||||
|
doc.put("logType", logType.trim());
|
||||||
|
doc.put("bizId", bizId.trim());
|
||||||
|
doc.put("request", parseOrRaw(requestJson));
|
||||||
|
doc.put("response", parseOrRaw(responseJson));
|
||||||
|
doc.put("updatedAt", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return getByDocId(EsLogType.docId(logType, bizId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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, 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));
|
||||||
|
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"));
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject buildQuery(String logType, String bizId)
|
||||||
|
{
|
||||||
|
JSONArray filters = new JSONArray();
|
||||||
|
addTerm(filters, "logType", logType);
|
||||||
|
addWildcard(filters, "bizId", bizId);
|
||||||
|
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,19 @@
|
|||||||
|
package com.ruoyi.price.search;
|
||||||
|
|
||||||
|
/** Common ES business-log types. */
|
||||||
|
public final class EsLogType
|
||||||
|
{
|
||||||
|
public static final String BILL = "BILL";
|
||||||
|
public static final String QUOTE = "QUOTE";
|
||||||
|
|
||||||
|
private EsLogType()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String docId(String logType, String bizId)
|
||||||
|
{
|
||||||
|
String type = logType == null ? "" : logType.trim();
|
||||||
|
String id = bizId == null ? "" : bizId.trim();
|
||||||
|
return type + "_" + id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ruoyi.price.search;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic Elasticsearch store for business request/response logs.
|
||||||
|
*/
|
||||||
|
public interface IBillSearchStore
|
||||||
|
{
|
||||||
|
void save(String logType, String bizId, String requestJson, String responseJson);
|
||||||
|
|
||||||
|
BillSyncSnapshot get(String logType, String bizId);
|
||||||
|
|
||||||
|
BillSyncSnapshot getByDocId(String docId);
|
||||||
|
|
||||||
|
BillSearchPage search(String logType, String bizId, int pageNum, int pageSize);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@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, int pageNum, int pageSize)
|
||||||
|
{
|
||||||
|
return new BillSearchPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
-5
@@ -10,6 +10,8 @@ import java.util.UUID;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.exception.ServiceException;
|
||||||
import com.ruoyi.common.utils.MessageUtils;
|
import com.ruoyi.common.utils.MessageUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
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.domain.vo.TmsPriceQuoteResult;
|
||||||
import com.ruoyi.price.mapper.PrcBillFeeMapper;
|
import com.ruoyi.price.mapper.PrcBillFeeMapper;
|
||||||
import com.ruoyi.price.mapper.PrcBillMapper;
|
import com.ruoyi.price.mapper.PrcBillMapper;
|
||||||
|
import com.ruoyi.price.search.BillSyncSnapshot;
|
||||||
|
import com.ruoyi.price.search.EsLogType;
|
||||||
|
import com.ruoyi.price.search.IBillSearchStore;
|
||||||
import com.ruoyi.price.service.IPrcBillService;
|
import com.ruoyi.price.service.IPrcBillService;
|
||||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||||
|
|
||||||
@@ -36,6 +41,9 @@ public class PrcBillServiceImpl implements IPrcBillService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PrcBillFeeMapper billFeeMapper;
|
private PrcBillFeeMapper billFeeMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IBillSearchStore billSearchStore;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public TmsPriceQuoteResult bill(TmsPriceBillRequest request)
|
public TmsPriceQuoteResult bill(TmsPriceBillRequest request)
|
||||||
@@ -152,6 +160,11 @@ public class PrcBillServiceImpl implements IPrcBillService
|
|||||||
bill.setPayableTotal(result.getPayableTotal());
|
bill.setPayableTotal(result.getPayableTotal());
|
||||||
bill.setCurrency(result.getCurrency());
|
bill.setCurrency(result.getCurrency());
|
||||||
bill.setRoundMode(result.getRoundMode());
|
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)
|
if (existed == null)
|
||||||
{
|
{
|
||||||
billMapper.insertPrcBill(bill);
|
billMapper.insertPrcBill(bill);
|
||||||
@@ -166,11 +179,7 @@ public class PrcBillServiceImpl implements IPrcBillService
|
|||||||
{
|
{
|
||||||
billFeeMapper.insertBatch(fees);
|
billFeeMapper.insertBatch(fees);
|
||||||
}
|
}
|
||||||
result.setBillId(bill.getBillId());
|
billSearchStore.save(EsLogType.BILL, orderNo, toRequestJson(request), toResponseJson(result));
|
||||||
result.setOrderNo(orderNo);
|
|
||||||
result.setCustomerCode(request.getCustomerCode());
|
|
||||||
fillResultOrder(result, bill);
|
|
||||||
result.setFromCache(Boolean.valueOf(existed != null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PrcBillFee> toFeeRows(String billId, TmsPriceQuoteResult result)
|
private List<PrcBillFee> toFeeRows(String billId, TmsPriceQuoteResult result)
|
||||||
@@ -252,9 +261,31 @@ public class PrcBillServiceImpl implements IPrcBillService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.setFeeDetails(lines);
|
r.setFeeDetails(lines);
|
||||||
|
fillSnapshotFromSearch(r, bill);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fillSnapshotFromSearch(TmsPriceQuoteResult r, PrcBill bill)
|
||||||
|
{
|
||||||
|
BillSyncSnapshot snap = billSearchStore.get(EsLogType.BILL, bill.getOrderNo());
|
||||||
|
if (snap != null && StringUtils.isNotEmpty(snap.getRequestJson()))
|
||||||
|
{
|
||||||
|
r.setRequestJson(snap.getRequestJson());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r.setRequestJson(rebuildRequestJson(bill));
|
||||||
|
}
|
||||||
|
if (snap != null && StringUtils.isNotEmpty(snap.getResponseJson()))
|
||||||
|
{
|
||||||
|
r.setResponseJson(snap.getResponseJson());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r.setResponseJson(toResponseJson(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void fillResultOrder(TmsPriceQuoteResult r, PrcBill bill)
|
private void fillResultOrder(TmsPriceQuoteResult r, PrcBill bill)
|
||||||
{
|
{
|
||||||
r.setTrackingNo(bill.getTrackingNo());
|
r.setTrackingNo(bill.getTrackingNo());
|
||||||
@@ -274,6 +305,89 @@ public class PrcBillServiceImpl implements IPrcBillService
|
|||||||
r.setOrderRemark(bill.getOrderRemark());
|
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 String rebuildRequestJson(PrcBill bill)
|
||||||
|
{
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
putIfNotNull(obj, "orderNo", bill.getOrderNo());
|
||||||
|
putIfNotNull(obj, "trackingNo", bill.getTrackingNo());
|
||||||
|
putIfNotNull(obj, "customerId", bill.getCustomerId());
|
||||||
|
putIfNotNull(obj, "customerCode", bill.getCustomerCode());
|
||||||
|
putIfNotNull(obj, "customerName", bill.getCustomerName());
|
||||||
|
putIfNotNull(obj, "productCode", bill.getProductCode());
|
||||||
|
putIfNotNull(obj, "countryCode", bill.getCountryCode());
|
||||||
|
putIfNotNull(obj, "countryName", bill.getCountryName());
|
||||||
|
putIfNotNull(obj, "weight", bill.getWeight());
|
||||||
|
if (bill.getBizDate() != null)
|
||||||
|
{
|
||||||
|
obj.put("bizDate", new SimpleDateFormat("yyyy-MM-dd").format(bill.getBizDate()));
|
||||||
|
}
|
||||||
|
putIfNotNull(obj, "tmsId", bill.getTmsId());
|
||||||
|
putIfNotNull(obj, "pieces", bill.getPieces());
|
||||||
|
putIfNotNull(obj, "actualWeight", bill.getActualWeight());
|
||||||
|
putIfNotNull(obj, "volume", bill.getVolume());
|
||||||
|
putIfNotNull(obj, "volumeWeight", bill.getVolumeWeight());
|
||||||
|
putIfNotNull(obj, "pkgLength", bill.getPkgLength());
|
||||||
|
putIfNotNull(obj, "pkgWidth", bill.getPkgWidth());
|
||||||
|
putIfNotNull(obj, "pkgHeight", bill.getPkgHeight());
|
||||||
|
putIfNotNull(obj, "shipperName", bill.getShipperName());
|
||||||
|
putIfNotNull(obj, "consigneeName", bill.getConsigneeName());
|
||||||
|
putIfNotNull(obj, "destPostcode", bill.getDestPostcode());
|
||||||
|
putIfNotNull(obj, "warehouseCode", bill.getWarehouseCode());
|
||||||
|
if (bill.getOrderTime() != null)
|
||||||
|
{
|
||||||
|
obj.put("orderTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(bill.getOrderTime()));
|
||||||
|
}
|
||||||
|
putIfNotNull(obj, "orderRemark", bill.getOrderRemark());
|
||||||
|
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 void putIfNotNull(JSONObject obj, String key, Object value)
|
||||||
|
{
|
||||||
|
if (value != null && !(value instanceof String && ((String) value).isEmpty()))
|
||||||
|
{
|
||||||
|
obj.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Date resolveBizDate(String bizDate)
|
private Date resolveBizDate(String bizDate)
|
||||||
{
|
{
|
||||||
if (StringUtils.isEmpty(bizDate))
|
if (StringUtils.isEmpty(bizDate))
|
||||||
|
|||||||
+33
@@ -14,6 +14,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.exception.ServiceException;
|
||||||
import com.ruoyi.common.utils.MessageUtils;
|
import com.ruoyi.common.utils.MessageUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
@@ -37,6 +39,8 @@ import com.ruoyi.price.mapper.PrcPriceSetMapper;
|
|||||||
import com.ruoyi.price.mapper.PrcQuoteFeeMapper;
|
import com.ruoyi.price.mapper.PrcQuoteFeeMapper;
|
||||||
import com.ruoyi.price.mapper.PrcQuoteMapper;
|
import com.ruoyi.price.mapper.PrcQuoteMapper;
|
||||||
import com.ruoyi.price.mapper.PrcSurchargeRelMapper;
|
import com.ruoyi.price.mapper.PrcSurchargeRelMapper;
|
||||||
|
import com.ruoyi.price.search.EsLogType;
|
||||||
|
import com.ruoyi.price.search.IBillSearchStore;
|
||||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,6 +86,9 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PrcQuoteFeeMapper quoteFeeMapper;
|
private PrcQuoteFeeMapper quoteFeeMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IBillSearchStore billSearchStore;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public TmsPriceQuoteResult quote(TmsPriceQuoteRequest request)
|
public TmsPriceQuoteResult quote(TmsPriceQuoteRequest request)
|
||||||
@@ -104,6 +111,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
replaceQuoteFees(cached.getQuoteId(), refreshed);
|
replaceQuoteFees(cached.getQuoteId(), refreshed);
|
||||||
refreshed.setQuoteId(cached.getQuoteId());
|
refreshed.setQuoteId(cached.getQuoteId());
|
||||||
refreshed.setFromCache(false);
|
refreshed.setFromCache(false);
|
||||||
|
billSearchStore.save(EsLogType.QUOTE, cached.getQuoteId(), toEsRequest(request), toEsResponse(refreshed));
|
||||||
return refreshed;
|
return refreshed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,6 +539,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
}
|
}
|
||||||
result.setQuoteId(quote.getQuoteId());
|
result.setQuoteId(quote.getQuoteId());
|
||||||
result.setFromCache(false);
|
result.setFromCache(false);
|
||||||
|
billSearchStore.save(EsLogType.QUOTE, quote.getQuoteId(), toEsRequest(request), toEsResponse(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isStaleSurchargeFees(List<PrcQuoteFee> fees)
|
private boolean isStaleSurchargeFees(List<PrcQuoteFee> fees)
|
||||||
@@ -580,6 +589,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
for (TmsPriceQuoteFeeLine line : result.getFeeDetails())
|
for (TmsPriceQuoteFeeLine line : result.getFeeDetails())
|
||||||
{
|
{
|
||||||
PrcQuoteFee fee = new PrcQuoteFee();
|
PrcQuoteFee fee = new PrcQuoteFee();
|
||||||
|
fee.setFeeId(UUID.randomUUID().toString());
|
||||||
fee.setQuoteId(quoteId);
|
fee.setQuoteId(quoteId);
|
||||||
fee.setFeeType(line.getFeeType());
|
fee.setFeeType(line.getFeeType());
|
||||||
fee.setFeeName(line.getFeeName());
|
fee.setFeeName(line.getFeeName());
|
||||||
@@ -832,6 +842,29 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
return p == null ? null : p.getProductName();
|
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)
|
private static String emptyToNull(String s)
|
||||||
{
|
{
|
||||||
return StringUtils.isEmpty(s) ? null : s.trim();
|
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,155 @@
|
|||||||
|
<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-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="业务ID">{{ detail.bizId || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="写入时间" :span="2">{{ detail.updatedAt || '-' }}</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 searchColumns = [
|
||||||
|
{ label: '类型', prop: 'logType', component: 'select', options: typeOptions, placeholder: '请选择类型', width: '160px', 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: '业务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 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; }
|
||||||
|
.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>
|
||||||
@@ -101,6 +101,23 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section>
|
</section>
|
||||||
|
<section v-if="detail.requestJson || detail.responseJson" class="detail-block">
|
||||||
|
<div class="sec-title">同步报文</div>
|
||||||
|
<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>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
@@ -206,6 +223,30 @@ function displayFeeName(row) {
|
|||||||
return name || typeLabel || row.feeType || '-'
|
return name || typeLabel || row.feeType || '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
function sourceTagType(source) {
|
function sourceTagType(source) {
|
||||||
if (source === 'PACKAGE_FIXED' || source === 'FIXED') return 'success'
|
if (source === 'PACKAGE_FIXED' || source === 'FIXED') return 'success'
|
||||||
if (source === 'PACKAGE_DISCOUNT' || source === 'DISCOUNT') return 'warning'
|
if (source === 'PACKAGE_DISCOUNT' || source === 'DISCOUNT') return 'warning'
|
||||||
@@ -301,6 +342,32 @@ function sourceTagType(source) {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
.json-panel {
|
||||||
|
margin-top: 10px;
|
||||||
|
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: 240px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
.fee-amount {
|
.fee-amount {
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
Reference in New Issue
Block a user