订单列表优化

This commit is contained in:
luojiaquan
2026-10-14 14:20:13 +08:00
parent 8c4db2f80f
commit 2d7bd678f3
10 changed files with 327 additions and 87 deletions
@@ -27,6 +27,7 @@ import com.bms.price.domain.PrcBillingTaskRetryTask;
import com.bms.price.domain.vo.TabOrderMonitorQuery;
import com.bms.price.domain.vo.TabOrderMonitorVo;
import com.bms.price.domain.vo.TabOrderProductCheckResult;
import com.bms.price.oracle.CustomerKeywordResolver;
import com.bms.price.oracle.TabOrderMonitorService;
import com.bms.price.oracle.TabOrderProductCheckService;
import com.bms.price.search.BillSearchPage;
@@ -54,6 +55,9 @@ public class TabOrderMonitorController extends BaseController
@Autowired
private IBillSearchStore billSearchStore;
@Autowired
private CustomerKeywordResolver customerKeywordResolver;
@PreAuthorize("@ss.hasPermi('monitor:orderSource:list')")
@GetMapping("/list")
public TableDataInfo list(TabOrderMonitorQuery query)
@@ -61,12 +65,14 @@ public class TabOrderMonitorController extends BaseController
TableDataInfo rsp = new TableDataInfo();
rsp.setCode(HttpStatus.SUCCESS);
rsp.setMsg("\u67e5\u8be2\u6210\u529f");
if (!hasRequiredFilters(query))
if (!hasListFilters(query))
{
rsp.setRows(Collections.emptyList());
rsp.setTotal(0L);
return rsp;
}
// 在切换 Oracle 数据源之前,把客户名称解析为客户代码
customerKeywordResolver.resolve(query);
PageDomain pageDomain = TableSupport.buildPageRequest();
int pageNum = pageDomain.getPageNum() == null ? 1 : pageDomain.getPageNum().intValue();
int pageSize = pageDomain.getPageSize() == null ? 10 : pageDomain.getPageSize().intValue();
@@ -87,6 +93,7 @@ public class TabOrderMonitorController extends BaseController
{
return error("customerCodes and pickup time required");
}
customerKeywordResolver.resolve(query);
TabOrderProductCheckResult data = tabOrderProductCheckService.check(query);
return success(data);
}
@@ -103,6 +110,7 @@ public class TabOrderMonitorController extends BaseController
{
return error("productCode required");
}
customerKeywordResolver.resolve(query);
return success(tabOrderMonitorService.selectOrderIdsByProduct(query));
}
@@ -115,6 +123,7 @@ public class TabOrderMonitorController extends BaseController
{
return error("customerCodes and pickup time required");
}
customerKeywordResolver.resolve(query);
tabOrderMonitorService.count(query);
String asyncTaskId = UUID.randomUUID().toString().replace("-", "");
tabOrderSyncAsyncRunner.put(asyncTaskId, PrcBillingTaskRetryTask.STATUS_PENDING, 0, "queued",
@@ -211,25 +220,39 @@ public class TabOrderMonitorController extends BaseController
return prefix + "_" + System.currentTimeMillis();
}
/**
* 列表查询:有任一单号即可查询;无单号时必须客户(名称或代码)+提货时间。
*/
private static boolean hasListFilters(TabOrderMonitorQuery query)
{
return hasRequiredFilters(query) || hasNonBlankItem(query.getOrderIdentifiers());
}
private static boolean hasRequiredFilters(TabOrderMonitorQuery query)
{
if (query == null)
{
return false;
}
boolean hasCustomer = false;
if (query.getCustomerCodes() != null)
{
for (String code : query.getCustomerCodes())
{
if (StringUtils.isNotEmpty(code))
{
hasCustomer = true;
break;
}
}
}
boolean hasCustomer = hasNonBlankItem(query.getCustomerCodes())
|| hasNonBlankItem(query.getCustomerKeywords());
boolean hasTime = StringUtils.isNotEmpty(query.getBeginTime()) && StringUtils.isNotEmpty(query.getEndTime());
return hasCustomer && hasTime;
}
private static boolean hasNonBlankItem(List<String> list)
{
if (list == null)
{
return false;
}
for (String item : list)
{
if (StringUtils.isNotEmpty(item))
{
return true;
}
}
return false;
}
}
@@ -15,6 +15,11 @@ public class TabOrderMonitorQuery implements Serializable
/** 客户代码列表 */
private List<String> customerCodes;
/**
* 客户名称或客户代码混合录入,每行一个,
* CUSTOMER_CODE / CUSTOMER_NAME 任一列精确命中即可。
*/
private List<String> customerKeywords;
/** 提货时间起(yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss) */
private String beginTime;
/** 提货时间止(yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss) */
@@ -25,6 +30,12 @@ public class TabOrderMonitorQuery implements Serializable
private String orgOrderId;
/** 转单号 TRANSFER_ORDER_ID */
private String transferOrderId;
/**
* 订单号/运单号/转单号混合录入,每行一个,
* ORDER_ID / ORG_ORDER_ID / TRANSFER_ORDER_ID 任一列精确命中即可。
* 有单号时不再强制要求客户+提货时间。
*/
private List<String> orderIdentifiers;
/** 上游产品代码 PRODUCT_CODE(产品比对点进订单号时使用) */
private String productCode;
/** 分批同步计费每批条数 */
@@ -0,0 +1,17 @@
package com.bms.price.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
/**
* 订单监控查询专用:把用户混合录入的客户名称/客户代码解析为客户代码集合。
* 走默认(MASTER/MySQL)数据源;Oracle TAB_ORDER 的 CUSTOMER_NAME 无索引,
* 不能在 Oracle 侧做名称 OR,否则会全表扫描。
*/
public interface CustomerCodeResolveMapper
{
/**
* 按客户代码或客户名称精确匹配,返回命中的客户代码(同名客户可能对应多个代码)。
*/
List<String> selectCustomerCodesByKeywords(@Param("keywords") List<String> keywords);
}
@@ -0,0 +1,99 @@
package com.bms.price.oracle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bms.common.utils.StringUtils;
import com.bms.price.domain.vo.TabOrderMonitorQuery;
import com.bms.price.mapper.CustomerCodeResolveMapper;
/**
* 把客户长文本(客户名称/客户代码混合,一行一条)解析为客户代码集合,
* 并入查询条件。必须在切换到 Oracle(SLAVE)数据源之前调用。
* Oracle TAB_ORDER.CUSTOMER_NAME 无索引,名称匹配只能先在 MySQL 主数据解析成代码,
* 否则跨列 OR 会导致 Oracle 全表扫描。
*/
@Component
public class CustomerKeywordResolver
{
@Autowired
private CustomerCodeResolveMapper customerCodeResolveMapper;
/**
* 原地解析:query.customerKeywords 归一化后查主数据转成 customerCodes,
* 解析完成清空 keywords(Oracle 侧不再使用该字段)。
*/
public void resolve(TabOrderMonitorQuery query)
{
if (query == null)
{
return;
}
List<String> keywords = normalizeLineList(query.getCustomerKeywords());
query.setCustomerKeywords(keywords);
if (keywords == null || keywords.isEmpty())
{
return;
}
List<String> resolved = customerCodeResolveMapper.selectCustomerCodesByKeywords(keywords);
Set<String> codes = new LinkedHashSet<String>();
if (query.getCustomerCodes() != null)
{
for (String c : query.getCustomerCodes())
{
if (StringUtils.isNotEmpty(c))
{
codes.add(c.trim());
}
}
}
if (resolved != null)
{
for (String c : resolved)
{
if (StringUtils.isNotEmpty(c))
{
codes.add(c.trim());
}
}
}
query.setCustomerCodes(codes.isEmpty() ? null : new ArrayList<String>(codes));
// 名称已全部解析为代码;Oracle 侧不保留跨列 OR 条件
query.setCustomerKeywords(null);
}
/**
* 多行文本归一化:按换行/逗号/分号/顿号拆分(不按空格拆,客户名称可能含空格),去空去重。
*/
private static List<String> normalizeLineList(List<String> lines)
{
if (lines == null || lines.isEmpty())
{
return null;
}
Set<String> set = new LinkedHashSet<String>();
for (String raw : lines)
{
if (StringUtils.isEmpty(raw))
{
continue;
}
for (String part : Arrays.asList(raw.split("[,,;;、\\r\\n]+")))
{
if (StringUtils.isNotEmpty(part))
{
String t = part.trim();
if (!t.isEmpty())
{
set.add(t);
}
}
}
}
return set.isEmpty() ? null : new ArrayList<String>(set);
}
}
@@ -160,39 +160,75 @@ public class TabOrderMonitorService
{
if (query == null)
{
throw new ServiceException("请先选择客户和提货时间");
throw new ServiceException("请输入单号,或选择客户和提货时间");
}
// 单号长文本:一行一条(兼容逗号/分号/顿号,不按空格拆)。
// 客户名称/代码混合条件由 CustomerKeywordResolver 在切库前解析为 customerCodes。
List<String> identifiers = normalizeLineList(query.getOrderIdentifiers());
query.setOrderIdentifiers(identifiers);
List<String> codes = query.getCustomerCodes();
if (codes == null || codes.isEmpty())
List<String> cleanedCodes = new ArrayList<String>();
if (codes != null)
{
throw new ServiceException("请选择客户");
}
List<String> cleaned = new ArrayList<String>();
for (String c : codes)
{
if (StringUtils.isNotEmpty(c))
for (String c : codes)
{
cleaned.add(c.trim());
if (StringUtils.isNotEmpty(c))
{
cleanedCodes.add(c.trim());
}
}
}
if (cleaned.isEmpty())
{
throw new ServiceException("请选择客户");
}
query.setCustomerCodes(cleaned);
query.setCustomerCodes(cleanedCodes.isEmpty() ? null : cleanedCodes);
String begin = normalizeTime(query.getBeginTime(), true);
String end = normalizeTime(query.getEndTime(), false);
if (begin == null || end == null)
{
throw new ServiceException("请选择提货时间范围");
}
query.setBeginTime(begin);
query.setEndTime(end);
boolean hasTime = begin != null && end != null;
boolean hasCustomer = !cleanedCodes.isEmpty();
boolean hasIdentifier = identifiers != null && !identifiers.isEmpty();
// 有单号即可查询;无单号时必须客户+提货时间,避免全表扫描(同步/产品检测在入口另有校验)
if (!hasIdentifier && !(hasCustomer && hasTime))
{
throw new ServiceException("请输入单号,或选择客户和提货时间");
}
query.setOrderId(trimToNull(query.getOrderId()));
query.setOrgOrderId(trimToNull(query.getOrgOrderId()));
query.setTransferOrderId(trimToNull(query.getTransferOrderId()));
}
/**
* 多行文本归一化:按换行/逗号/分号/顿号拆分(不按空格拆),trim、去空去重。
*/
private static List<String> normalizeLineList(List<String> lines)
{
if (lines == null || lines.isEmpty())
{
return null;
}
java.util.LinkedHashSet<String> set = new java.util.LinkedHashSet<String>();
for (String raw : lines)
{
if (StringUtils.isEmpty(raw))
{
continue;
}
for (String part : raw.split("[,,;;、\\r\\n]+"))
{
if (StringUtils.isNotEmpty(part))
{
String t = part.trim();
if (!t.isEmpty())
{
set.add(t);
}
}
}
}
return set.isEmpty() ? null : new ArrayList<String>(set);
}
private static String trimToNull(String s)
{
if (StringUtils.isEmpty(s))
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bms.price.mapper.CustomerCodeResolveMapper">
<!-- 客户主数据仅 200 余行,名称精确匹配在 MySQL 侧代价极低 -->
<select id="selectCustomerCodesByKeywords" resultType="java.lang.String">
SELECT customer_code
FROM ctr_customer
WHERE
<foreach collection="keywords" item="kw" separator=" OR ">
customer_code = #{kw} OR customer_name = #{kw}
</foreach>
</select>
</mapper>
@@ -48,12 +48,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="tabOrderMonitorWhere">
o.BL_PICKUP = 1
AND o.ORDER_STATUS IN (3, 4, 5, 6, 7, 8, 11)
AND o.CUSTOMER_CODE IN
<foreach collection="query.customerCodes" item="code" open="(" separator="," close=")">
#{code}
</foreach>
AND o.PICKUP_DATE &gt;= TO_TIMESTAMP(#{query.beginTime}, 'YYYY-MM-DD HH24:MI:SS')
AND o.PICKUP_DATE &lt;= TO_TIMESTAMP(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS')
<if test="query.customerCodes != null and query.customerCodes.size() > 0">
AND o.CUSTOMER_CODE IN
<foreach collection="query.customerCodes" item="code" open="(" separator="," close=")">
#{code}
</foreach>
</if>
<if test="query.beginTime != null and query.beginTime != ''">
AND o.PICKUP_DATE &gt;= TO_TIMESTAMP(#{query.beginTime}, 'YYYY-MM-DD HH24:MI:SS')
</if>
<if test="query.endTime != null and query.endTime != ''">
AND o.PICKUP_DATE &lt;= TO_TIMESTAMP(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS')
</if>
<if test="query.orderId != null and query.orderId != ''">
AND o.ORDER_ID = #{query.orderId}
</if>
@@ -63,6 +69,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="query.transferOrderId != null and query.transferOrderId != ''">
AND o.TRANSFER_ORDER_ID = #{query.transferOrderId}
</if>
<if test="query.orderIdentifiers != null and query.orderIdentifiers.size() > 0">
AND (
o.ORDER_ID IN
<foreach collection="query.orderIdentifiers" item="oid" open="(" separator="," close=")">
#{oid}
</foreach>
OR o.ORG_ORDER_ID IN
<foreach collection="query.orderIdentifiers" item="oid" open="(" separator="," close=")">
#{oid}
</foreach>
OR o.TRANSFER_ORDER_ID IN
<foreach collection="query.orderIdentifiers" item="oid" open="(" separator="," close=")">
#{oid}
</foreach>
)
</if>
</sql>
<sql id="tabOrderMonitorColumns">