优化记录日志功能
This commit is contained in:
+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.TmsPriceBillRequest;
|
||||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
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.IPrcBillService;
|
||||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||||
|
|
||||||
@@ -46,6 +49,9 @@ public class TmsPriceController extends BaseController
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TmsApiProperties tmsApiProperties;
|
private TmsApiProperties tmsApiProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IBillSearchStore billSearchStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quote by customerId + productCode + country + weight.
|
* Quote by customerId + productCode + country + weight.
|
||||||
* Match: customer package FIXED > DISCOUNT > product default.
|
* 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,
|
@RequestHeader(value = HEADER_API_CODE, required = false) String headerApiCode,
|
||||||
@Validated @RequestBody TmsPriceQuoteRequest request)
|
@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);
|
TmsPriceQuoteResult result = tmsPriceQuoteService.quote(request);
|
||||||
return success(result);
|
return success(result);
|
||||||
}
|
}
|
||||||
@@ -69,7 +84,15 @@ public class TmsPriceController extends BaseController
|
|||||||
@RequestHeader(value = HEADER_API_CODE, required = false) String headerApiCode,
|
@RequestHeader(value = HEADER_API_CODE, required = false) String headerApiCode,
|
||||||
@Validated @RequestBody TmsPriceBillRequest request)
|
@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);
|
TmsPriceQuoteResult result = prcBillService.bill(request);
|
||||||
return success(result);
|
return success(result);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -30,12 +30,13 @@ public class EsLogController extends BaseController
|
|||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public TableDataInfo list(
|
public TableDataInfo list(
|
||||||
@RequestParam(value = "logType", required = false) String logType,
|
@RequestParam(value = "logType", required = false) String logType,
|
||||||
@RequestParam(value = "bizId", required = false) String bizId)
|
@RequestParam(value = "bizId", required = false) String bizId,
|
||||||
|
@RequestParam(value = "status", required = false) String status)
|
||||||
{
|
{
|
||||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||||
int pageNum = pageDomain.getPageNum() == null ? 1 : pageDomain.getPageNum().intValue();
|
int pageNum = pageDomain.getPageNum() == null ? 1 : pageDomain.getPageNum().intValue();
|
||||||
int pageSize = pageDomain.getPageSize() == null ? 10 : pageDomain.getPageSize().intValue();
|
int pageSize = pageDomain.getPageSize() == null ? 10 : pageDomain.getPageSize().intValue();
|
||||||
BillSearchPage page = billSearchStore.search(logType, bizId, pageNum, pageSize);
|
BillSearchPage page = billSearchStore.search(logType, bizId, status, pageNum, pageSize);
|
||||||
TableDataInfo rsp = new TableDataInfo();
|
TableDataInfo rsp = new TableDataInfo();
|
||||||
rsp.setCode(HttpStatus.SUCCESS);
|
rsp.setCode(HttpStatus.SUCCESS);
|
||||||
rsp.setMsg("\u67e5\u8be2\u6210\u529f");
|
rsp.setMsg("\u67e5\u8be2\u6210\u529f");
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ public class BillSyncSnapshot
|
|||||||
private String bizId;
|
private String bizId;
|
||||||
private String requestJson;
|
private String requestJson;
|
||||||
private String responseJson;
|
private String responseJson;
|
||||||
|
private String status;
|
||||||
|
private String errorMessage;
|
||||||
private String updatedAt;
|
private String updatedAt;
|
||||||
|
|
||||||
public String getDocId()
|
public String getDocId()
|
||||||
@@ -60,6 +62,26 @@ public class BillSyncSnapshot
|
|||||||
this.responseJson = 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()
|
public String getUpdatedAt()
|
||||||
{
|
{
|
||||||
return updatedAt;
|
return updatedAt;
|
||||||
|
|||||||
@@ -49,21 +49,25 @@ public class EsBillSearchStore implements IBillSearchStore
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(String logType, String bizId, String requestJson, String responseJson)
|
public void save(String logType, String bizId, String requestJson, String responseJson, String status,
|
||||||
|
String errorMessage)
|
||||||
{
|
{
|
||||||
if (StringUtils.isEmpty(logType) || StringUtils.isEmpty(bizId))
|
if (StringUtils.isEmpty(logType) || StringUtils.isEmpty(bizId))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String docId = EsLogType.docId(logType, bizId);
|
Date now = new Date();
|
||||||
|
String docId = EsLogType.docId(logType, bizId, now);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
JSONObject doc = new JSONObject();
|
JSONObject doc = new JSONObject();
|
||||||
doc.put("logType", logType.trim());
|
doc.put("logType", logType.trim());
|
||||||
doc.put("bizId", bizId.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("request", parseOrRaw(requestJson));
|
||||||
doc.put("response", parseOrRaw(responseJson));
|
doc.put("response", parseOrRaw(responseJson));
|
||||||
doc.put("updatedAt", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
doc.put("updatedAt", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(now));
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
HttpEntity<String> entity = new HttpEntity<String>(doc.toJSONString(), headers);
|
HttpEntity<String> entity = new HttpEntity<String>(doc.toJSONString(), headers);
|
||||||
@@ -82,7 +86,12 @@ public class EsBillSearchStore implements IBillSearchStore
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return getByDocId(EsLogType.docId(logType, bizId));
|
BillSearchPage page = search(logType, bizId, null, 1, 1);
|
||||||
|
if (page.getRows() == null || page.getRows().isEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return page.getRows().get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -119,7 +128,7 @@ public class EsBillSearchStore implements IBillSearchStore
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BillSearchPage search(String logType, String bizId, int pageNum, int pageSize)
|
public BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize)
|
||||||
{
|
{
|
||||||
BillSearchPage page = new BillSearchPage();
|
BillSearchPage page = new BillSearchPage();
|
||||||
if (pageNum < 1)
|
if (pageNum < 1)
|
||||||
@@ -143,7 +152,7 @@ public class EsBillSearchStore implements IBillSearchStore
|
|||||||
sortItem.put("updatedAt.keyword", sortOpt);
|
sortItem.put("updatedAt.keyword", sortOpt);
|
||||||
sort.add(sortItem);
|
sort.add(sortItem);
|
||||||
body.put("sort", sort);
|
body.put("sort", sort);
|
||||||
body.put("query", buildQuery(logType, bizId));
|
body.put("query", buildQuery(logType, bizId, status));
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
HttpEntity<String> entity = new HttpEntity<String>(body.toJSONString(), headers);
|
HttpEntity<String> entity = new HttpEntity<String>(body.toJSONString(), headers);
|
||||||
@@ -210,14 +219,18 @@ public class EsBillSearchStore implements IBillSearchStore
|
|||||||
snap.setRequestJson(toJsonString(source.get("request")));
|
snap.setRequestJson(toJsonString(source.get("request")));
|
||||||
snap.setResponseJson(toJsonString(source.get("response")));
|
snap.setResponseJson(toJsonString(source.get("response")));
|
||||||
snap.setUpdatedAt(source.getString("updatedAt"));
|
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;
|
return snap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JSONObject buildQuery(String logType, String bizId)
|
private JSONObject buildQuery(String logType, String bizId, String status)
|
||||||
{
|
{
|
||||||
JSONArray filters = new JSONArray();
|
JSONArray filters = new JSONArray();
|
||||||
addTerm(filters, "logType", logType);
|
addTerm(filters, "logType", logType);
|
||||||
addWildcard(filters, "bizId", bizId);
|
addWildcard(filters, "bizId", bizId);
|
||||||
|
addTerm(filters, "status", status);
|
||||||
if (filters.isEmpty())
|
if (filters.isEmpty())
|
||||||
{
|
{
|
||||||
JSONObject matchAll = new JSONObject();
|
JSONObject matchAll = new JSONObject();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.ruoyi.price.search;
|
package com.ruoyi.price.search;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/** Common ES business-log types. */
|
/** Common ES business-log types. */
|
||||||
public final class EsLogType
|
public final class EsLogType
|
||||||
{
|
{
|
||||||
@@ -10,10 +13,23 @@ public final class EsLogType
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** One document per bizId + write time. */
|
||||||
public static String docId(String logType, String bizId)
|
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 type = logType == null ? "" : logType.trim();
|
||||||
String id = bizId == null ? "" : bizId.trim();
|
String id = bizId == null ? "" : bizId.trim();
|
||||||
return type + "_" + id;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ package com.ruoyi.price.search;
|
|||||||
*/
|
*/
|
||||||
public interface IBillSearchStore
|
public interface IBillSearchStore
|
||||||
{
|
{
|
||||||
void save(String logType, String bizId, String requestJson, String responseJson);
|
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 get(String logType, String bizId);
|
||||||
|
|
||||||
BillSyncSnapshot getByDocId(String docId);
|
BillSyncSnapshot getByDocId(String docId);
|
||||||
|
|
||||||
BillSearchPage search(String logType, String bizId, int pageNum, int pageSize);
|
BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ package com.ruoyi.price.search;
|
|||||||
public class NoopBillSearchStore implements IBillSearchStore
|
public class NoopBillSearchStore implements IBillSearchStore
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void save(String logType, String bizId, String requestJson, String responseJson)
|
public void save(String logType, String bizId, String requestJson, String responseJson, String status,
|
||||||
|
String errorMessage)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ public class NoopBillSearchStore implements IBillSearchStore
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BillSearchPage search(String logType, String bizId, int pageNum, int pageSize)
|
public BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize)
|
||||||
{
|
{
|
||||||
return new BillSearchPage();
|
return new BillSearchPage();
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-77
@@ -23,7 +23,7 @@ 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.EsBizLogSupport;
|
||||||
import com.ruoyi.price.search.EsLogType;
|
import com.ruoyi.price.search.EsLogType;
|
||||||
import com.ruoyi.price.search.IBillSearchStore;
|
import com.ruoyi.price.search.IBillSearchStore;
|
||||||
import com.ruoyi.price.service.IPrcBillService;
|
import com.ruoyi.price.service.IPrcBillService;
|
||||||
@@ -48,20 +48,28 @@ public class PrcBillServiceImpl implements IPrcBillService
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public TmsPriceQuoteResult bill(TmsPriceBillRequest request)
|
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());
|
catch (RuntimeException e)
|
||||||
if (StringUtils.isEmpty(request.getBizDate()))
|
|
||||||
{
|
{
|
||||||
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
|
@Override
|
||||||
@@ -261,31 +269,9 @@ 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());
|
||||||
@@ -328,42 +314,6 @@ public class PrcBillServiceImpl implements IPrcBillService
|
|||||||
return obj.toJSONString();
|
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)
|
private JSONObject parseJsonObject(String json)
|
||||||
{
|
{
|
||||||
if (StringUtils.isEmpty(json))
|
if (StringUtils.isEmpty(json))
|
||||||
@@ -380,14 +330,6 @@ public class PrcBillServiceImpl implements IPrcBillService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
||||||
|
|||||||
+15
@@ -39,6 +39,7 @@ 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.EsBizLogSupport;
|
||||||
import com.ruoyi.price.search.EsLogType;
|
import com.ruoyi.price.search.EsLogType;
|
||||||
import com.ruoyi.price.search.IBillSearchStore;
|
import com.ruoyi.price.search.IBillSearchStore;
|
||||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||||
@@ -92,6 +93,20 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public TmsPriceQuoteResult quote(TmsPriceQuoteRequest request)
|
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);
|
validateRequest(request);
|
||||||
Date bizDate = resolveBizDate(request.getBizDate());
|
Date bizDate = resolveBizDate(request.getBizDate());
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
<template #col-logType="{ row }">
|
<template #col-logType="{ row }">
|
||||||
<el-tag size="small" :type="typeTag(row.logType)">{{ typeLabel(row.logType) }}</el-tag>
|
<el-tag size="small" :type="typeTag(row.logType)">{{ typeLabel(row.logType) }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
|
<template #col-status="{ row }">
|
||||||
|
<el-tag size="small" :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag>
|
||||||
|
</template>
|
||||||
<template #col-action="{ row }">
|
<template #col-action="{ row }">
|
||||||
<el-button link type="primary" icon="View" @click="handleView(row)" v-hasPermi="['monitor:eslog:list']">查看</el-button>
|
<el-button link type="primary" icon="View" @click="handleView(row)" v-hasPermi="['monitor:eslog:list']">查看</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -23,8 +26,14 @@
|
|||||||
<el-descriptions-item label="类型">
|
<el-descriptions-item label="类型">
|
||||||
<el-tag size="small" :type="typeTag(detail.logType)">{{ typeLabel(detail.logType) }}</el-tag>
|
<el-tag size="small" :type="typeTag(detail.logType)">{{ typeLabel(detail.logType) }}</el-tag>
|
||||||
</el-descriptions-item>
|
</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="业务ID">{{ detail.bizId || '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="写入时间" :span="2">{{ detail.updatedAt || '-' }}</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>
|
</el-descriptions>
|
||||||
<div class="json-panel">
|
<div class="json-panel">
|
||||||
<div class="json-panel__head">
|
<div class="json-panel__head">
|
||||||
@@ -60,13 +69,20 @@ const typeOptions = [
|
|||||||
{ label: '试算', value: 'QUOTE' }
|
{ label: '试算', value: 'QUOTE' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: '成功', value: 'SUCCESS' },
|
||||||
|
{ label: '失败', value: 'FAIL' }
|
||||||
|
]
|
||||||
|
|
||||||
const searchColumns = [
|
const searchColumns = [
|
||||||
{ label: '类型', prop: 'logType', component: 'select', options: typeOptions, placeholder: '请选择类型', width: '160px', props: { clearable: true } },
|
{ 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' }
|
{ label: '业务ID', prop: 'bizId', component: 'input', placeholder: '业务ID', width: '220px' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ label: '类型', prop: 'logType', align: 'center', width: 110, slot: 'logType' },
|
{ 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: '业务ID', prop: 'bizId', minWidth: 180, showOverflowTooltip: true },
|
||||||
{ label: '写入时间', prop: 'updatedAt', align: 'center', width: 180 },
|
{ label: '写入时间', prop: 'updatedAt', align: 'center', width: 180 },
|
||||||
{ label: '操作', prop: 'action', align: 'center', width: 90, fixed: 'right', slot: 'action', showInToolbar: false }
|
{ label: '操作', prop: 'action', align: 'center', width: 90, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||||
@@ -87,6 +103,16 @@ function typeTag(type) {
|
|||||||
return 'info'
|
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) {
|
function handleView(row) {
|
||||||
drawerTitle.value = 'ES日志详情 #' + (row.bizId || row.docId)
|
drawerTitle.value = 'ES日志详情 #' + (row.bizId || row.docId)
|
||||||
open.value = true
|
open.value = true
|
||||||
@@ -126,6 +152,7 @@ function copyJson(val) {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.mb12 { margin-bottom: 12px; }
|
.mb12 { margin-bottom: 12px; }
|
||||||
|
.err-text { color: #f56c6c; word-break: break-all; }
|
||||||
.json-panel {
|
.json-panel {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
background: #f7f9fb;
|
background: #f7f9fb;
|
||||||
|
|||||||
@@ -101,23 +101,6 @@
|
|||||||
</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>
|
||||||
@@ -223,30 +206,6 @@ 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'
|
||||||
@@ -342,32 +301,6 @@ 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