refactor(task): 优化经销商订单结算任务的推荐关系查询逻辑

- 移除废弃的 isShopRole 扩展字段及相关 SQL 查询
- 将推荐关系查询逻辑改为基于 level=1 链路的简单向上遍历
- 添加分红金额计算精度控制,使用 3 位小数精度
- 调整定时任务执行频率从 30 秒改为 20 秒
- 优化订单结算时的分红用户和金额更新逻辑,支持增量更新
- 新增一次性排查任务用于调试订单推荐关系链路问题
This commit is contained in:
2026-01-26 11:52:53 +08:00
parent 375a65be6a
commit 803ac3301e
5 changed files with 291 additions and 128 deletions

View File

@@ -67,10 +67,6 @@ public class ShopDealerReferee implements Serializable {
@Schema(description = "推荐关系层级(弃用)") @Schema(description = "推荐关系层级(弃用)")
private Integer level; private Integer level;
@Schema(description = "上级是否门店分红用户(ShopDealerUser.type=1扩展字段)")
@TableField(exist = false)
private Boolean isShopRole;
@Schema(description = "来源(如 goods_share)") @Schema(description = "来源(如 goods_share)")
// NOTE: 表 shop_dealer_referee 若未新增该字段,需要 exist=false避免 MyBatis-Plus 自动生成SQL时报 Unknown column。 // NOTE: 表 shop_dealer_referee 若未新增该字段,需要 exist=false避免 MyBatis-Plus 自动生成SQL时报 Unknown column。
@TableField(exist = false) @TableField(exist = false)

View File

@@ -34,22 +34,4 @@ public interface ShopDealerRefereeMapper extends BaseMapper<ShopDealerReferee> {
*/ */
List<ShopDealerReferee> selectListRel(@Param("param") ShopDealerRefereeParam param); List<ShopDealerReferee> selectListRel(@Param("param") ShopDealerRefereeParam param);
/**
* 查询指定用户的推荐关系链路(按 level 升序),并计算每个上级是否包含门店角色(shop)。
* <p>
* 用于定时任务分佣:按 ShopDealerUser.type=1 判定“门店分红”上级,避免查询 core 的 sys_user_role。
* <p>
* 注意对应XML里为了兼容 MyBatis-Plus 的 SQL 解析limit 使用字面量拼接(${limit}
* 这里的 limit 必须由服务端常量传入(不要透传用户输入)。
*/
List<ShopDealerReferee> selectRefereeChainWithShopRole(@Param("tenantId") Integer tenantId,
@Param("userId") Integer userId,
@Param("limit") Integer limit);
/**
* 查询指定用户的一级推荐人level=1并按 ShopDealerUser.type=1 判定该推荐人是否为“门店分红”上级。
*/
ShopDealerReferee selectFirstLevelRefereeWithShopRole(@Param("tenantId") Integer tenantId,
@Param("userId") Integer userId);
} }

View File

@@ -8,7 +8,6 @@
d.nickname AS dealerName, d.nickname AS dealerName,
d.avatar AS dealerAvatar, d.avatar AS dealerAvatar,
d.phone AS dealerPhone, d.phone AS dealerPhone,
CASE WHEN du.user_id IS NULL THEN 0 ELSE 1 END AS isShopRole,
u.nickname, u.nickname,
u.avatar, u.avatar,
u.alias, u.alias,
@@ -16,11 +15,6 @@
u.is_admin as isAdmin u.is_admin as isAdmin
FROM shop_dealer_referee a FROM shop_dealer_referee a
INNER JOIN gxwebsoft_core.sys_user d ON a.dealer_id = d.user_id AND d.deleted = 0 INNER JOIN gxwebsoft_core.sys_user d ON a.dealer_id = d.user_id AND d.deleted = 0
LEFT JOIN shop_dealer_user du
ON du.user_id = a.dealer_id
AND du.tenant_id = a.tenant_id
AND du.type = 1
AND (du.is_delete = 0 OR du.is_delete IS NULL)
INNER JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id AND u.deleted = 0 INNER JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id AND u.deleted = 0
<where> <where>
<if test="param.tenantId != null"> <if test="param.tenantId != null">
@@ -64,50 +58,4 @@
<include refid="selectSql"></include> <include refid="selectSql"></include>
</select> </select>
<!-- 定时任务:查某个用户的推荐关系链路(按 level 升序),并带出是否门店角色 -->
<select id="selectRefereeChainWithShopRole" resultType="com.gxwebsoft.shop.entity.ShopDealerReferee">
SELECT a.id,
a.dealer_id AS dealerId,
a.user_id AS userId,
a.level,
a.tenant_id AS tenantId,
CASE WHEN du.user_id IS NULL THEN 0 ELSE 1 END AS isShopRole
FROM shop_dealer_referee a
LEFT JOIN shop_dealer_user du
ON du.user_id = a.dealer_id
AND du.tenant_id = a.tenant_id
AND du.type = 1
AND (du.is_delete = 0 OR du.is_delete IS NULL)
WHERE a.tenant_id = #{tenantId}
AND a.user_id = #{userId}
ORDER BY a.level ASC, a.id DESC
<!--
MyBatis-Plus TenantLineInnerInterceptor uses JSqlParser to parse SQL.
Some versions can't parse "LIMIT ?" (prepared placeholder), so use a literal here.
`limit` is an Integer passed from server-side constants (no user input).
-->
LIMIT ${limit}
</select>
<!-- 定时任务:查某个用户的一级推荐关系(level=1),并带出是否门店角色 -->
<select id="selectFirstLevelRefereeWithShopRole" resultType="com.gxwebsoft.shop.entity.ShopDealerReferee">
SELECT a.id,
a.dealer_id AS dealerId,
a.user_id AS userId,
a.level,
a.tenant_id AS tenantId,
CASE WHEN du.user_id IS NULL THEN 0 ELSE 1 END AS isShopRole
FROM shop_dealer_referee a
LEFT JOIN shop_dealer_user du
ON du.user_id = a.dealer_id
AND du.tenant_id = a.tenant_id
AND du.type = 1
AND (du.is_delete = 0 OR du.is_delete IS NULL)
WHERE a.tenant_id = #{tenantId}
AND a.user_id = #{userId}
AND a.level = 1
ORDER BY a.id DESC
LIMIT 1
</select>
</mapper> </mapper>

View File

@@ -10,7 +10,6 @@ import com.gxwebsoft.shop.entity.ShopDealerUser;
import com.gxwebsoft.shop.entity.ShopOrder; import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.common.system.entity.User; import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.mapper.UserMapper; import com.gxwebsoft.common.system.mapper.UserMapper;
import com.gxwebsoft.shop.mapper.ShopDealerRefereeMapper;
import com.gxwebsoft.shop.service.ShopDealerCapitalService; import com.gxwebsoft.shop.service.ShopDealerCapitalService;
import com.gxwebsoft.shop.service.ShopDealerOrderService; import com.gxwebsoft.shop.service.ShopDealerOrderService;
import com.gxwebsoft.shop.service.ShopDealerRefereeService; import com.gxwebsoft.shop.service.ShopDealerRefereeService;
@@ -46,6 +45,7 @@ public class DealerOrderSettlement10584Task {
private static final int MAX_ORDERS_PER_RUN = 50; private static final int MAX_ORDERS_PER_RUN = 50;
private static final int MAX_REFEREE_CHAIN_DEPTH = 20; private static final int MAX_REFEREE_CHAIN_DEPTH = 20;
private static final int DIVIDEND_SCALE = 3;
@Resource @Resource
private TransactionTemplate transactionTemplate; private TransactionTemplate transactionTemplate;
@@ -65,16 +65,13 @@ public class DealerOrderSettlement10584Task {
@Resource @Resource
private ShopDealerOrderService shopDealerOrderService; private ShopDealerOrderService shopDealerOrderService;
@Resource
private ShopDealerRefereeMapper shopDealerRefereeMapper;
@Resource @Resource
private UserMapper userMapper; private UserMapper userMapper;
/** /**
* 每30秒执行一次。 * 每30秒执行一次。
*/ */
@Scheduled(cron = "0/30 * * * * ?") @Scheduled(cron = "0/20 * * * * ?")
@IgnoreTenant("该定时任务仅处理租户10584但需要显式按tenantId过滤避免定时任务线程无租户上下文导致查询异常") @IgnoreTenant("该定时任务仅处理租户10584但需要显式按tenantId过滤避免定时任务线程无租户上下文导致查询异常")
public void settleTenant10584Orders() { public void settleTenant10584Orders() {
try { try {
@@ -199,8 +196,8 @@ public class DealerOrderSettlement10584Task {
} }
private ShopRoleCommission settleShopRoleRefereeCommission(ShopOrder order, BigDecimal baseAmount) { private ShopRoleCommission settleShopRoleRefereeCommission(ShopOrder order, BigDecimal baseAmount) {
List<Integer> shopRoleReferees = findFirstTwoShopRoleRefereesByDealerReferee(order.getUserId()); List<Integer> shopRoleReferees = findFirstTwoUpstreamReferees(order.getUserId());
log.info("门店(type=1)上级链路结果 - orderNo={}, buyerUserId={}, shopRoleReferees={}", log.info("分红上级链路结果(level=1链路取前两级) - orderNo={}, buyerUserId={}, referees={}",
order.getOrderNo(), order.getUserId(), shopRoleReferees); order.getOrderNo(), order.getUserId(), shopRoleReferees);
if (shopRoleReferees.isEmpty()) { if (shopRoleReferees.isEmpty()) {
return ShopRoleCommission.empty(); return ShopRoleCommission.empty();
@@ -208,23 +205,28 @@ public class DealerOrderSettlement10584Task {
if (shopRoleReferees.size() == 1) { if (shopRoleReferees.size() == 1) {
// 门店直推2% // 门店直推2%
BigDecimal money = calcMoney(baseAmount, RATE_0_02); BigDecimal money = calcDividendMoney(baseAmount, RATE_0_02);
log.info("门店直推/简推发放(仅1人) - orderNo={}, storeDirectUserId={}, money={}", order.getOrderNo(), shopRoleReferees.get(0), money); log.info("分红发放(仅1人) - orderNo={}, firstDividendUserId={}, money={}", order.getOrderNo(), shopRoleReferees.get(0), money);
creditDealerCommission(shopRoleReferees.get(0), money, order, order.getUserId(), "门店直推佣金(type=1仅1人2%)"); creditDealerCommission(shopRoleReferees.get(0), money, order, order.getUserId(), "门店直推佣金(仅1人2%)");
return new ShopRoleCommission(shopRoleReferees.get(0), money, null, BigDecimal.ZERO); return new ShopRoleCommission(shopRoleReferees.get(0), money, null, BigDecimal.ZERO);
} }
// 两个或以上第一个0.02第二个0.01 // 两个或以上第一个0.02第二个0.01
BigDecimal storeDirectMoney = calcMoney(baseAmount, RATE_0_02); BigDecimal storeDirectMoney = calcDividendMoney(baseAmount, RATE_0_02);
BigDecimal storeSimpleMoney = calcMoney(baseAmount, RATE_0_01); BigDecimal storeSimpleMoney = calcDividendMoney(baseAmount, RATE_0_01);
log.info("门店直推/门店简推发放 - orderNo={}, storeDirectUserId={}, storeDirectMoney={}, storeSimpleUserId={}, storeSimpleMoney={}", log.info("分红发放(2人) - orderNo={}, firstDividendUserId={}, firstDividendMoney={}, secondDividendUserId={}, secondDividendMoney={}",
order.getOrderNo(), shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney); order.getOrderNo(), shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney);
creditDealerCommission(shopRoleReferees.get(0), storeDirectMoney, order, order.getUserId(), "门店直推佣金(type=1第12%)"); creditDealerCommission(shopRoleReferees.get(0), storeDirectMoney, order, order.getUserId(), "门店直推佣金(第12%)");
creditDealerCommission(shopRoleReferees.get(1), storeSimpleMoney, order, order.getUserId(), "门店简推佣金(type=1第21%)"); creditDealerCommission(shopRoleReferees.get(1), storeSimpleMoney, order, order.getUserId(), "门店简推佣金(第21%)");
return new ShopRoleCommission(shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney); return new ShopRoleCommission(shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney);
} }
private List<Integer> findFirstTwoShopRoleRefereesByDealerReferee(Integer buyerUserId) { /**
* 沿 shop_dealer_referee(level=1) 链路向上找到最近两级上级。
* <p>
* 旧逻辑依赖扩展字段 isShopRole(已废弃) 来过滤门店角色;目前按链路取前两级,保证能回填/结算。
*/
private List<Integer> findFirstTwoUpstreamReferees(Integer buyerUserId) {
if (buyerUserId == null) { if (buyerUserId == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
@@ -232,47 +234,23 @@ public class DealerOrderSettlement10584Task {
List<Integer> result = new ArrayList<>(2); List<Integer> result = new ArrayList<>(2);
Set<Integer> visited = new HashSet<>(); Set<Integer> visited = new HashSet<>();
// 优先:直接从该买家(userId)的多级关系(level=1/2/3/...)里,按 level 升序找到最近的两级门店(type=1) // 仅依赖 level=1 的推荐关系一路向上查找(兼容“只维护 level=1”的数据形态
// 背景:部分数据只维护“买家 -> 上级们”的多级(level)记录,但不一定维护“上级 -> 更上级”的记录;
// 若仅做链路向上查找,会导致门店直推/简推无法命中,收益与资金明细都不会写入。
// 注意isShopRole 为扩展字段(非表字段)由自定义SQL按 ShopDealerUser.type=1 计算带出。
List<ShopDealerReferee> relList = shopDealerRefereeMapper.selectRefereeChainWithShopRole(
TENANT_ID, buyerUserId, MAX_REFEREE_CHAIN_DEPTH
);
for (ShopDealerReferee rel : relList) {
Integer dealerId = rel != null ? rel.getDealerId() : null;
if (dealerId == null || !visited.add(dealerId)) {
continue;
}
if (Boolean.TRUE.equals(rel.getIsShopRole())) {
result.add(dealerId);
if (result.size() >= 2) {
return result;
}
}
}
// 兜底:若只维护了 level=1 的推荐关系,则尝试按“查两次/多次”的方式一路向上找门店(type=1)。
Integer current = buyerUserId; Integer current = buyerUserId;
for (int i = 0; i < MAX_REFEREE_CHAIN_DEPTH && current != null; i++) { for (int i = 0; i < MAX_REFEREE_CHAIN_DEPTH && current != null; i++) {
ShopDealerReferee parentRel = shopDealerRefereeMapper.selectFirstLevelRefereeWithShopRole(TENANT_ID, current); Integer parentId = getDealerRefereeId(current, 1);
Integer parentId = parentRel != null ? parentRel.getDealerId() : null;
if (parentId == null) { if (parentId == null) {
break; break;
} }
log.debug("门店(type=1)链路向上 - buyerOrChildUserId={}, parentId={}", current, parentId); log.debug("分红链路向上 - buyerOrChildUserId={}, parentId={}", current, parentId);
if (!visited.add(parentId)) { if (!visited.add(parentId)) {
break; break;
} }
if (parentRel != null && Boolean.TRUE.equals(parentRel.getIsShopRole())) { result.add(parentId);
log.debug("门店(type=1)命中 - parentId={}", parentId); if (result.size() >= 2) {
result.add(parentId); break;
if (result.size() >= 2) {
break;
}
} }
current = parentId; current = parentId;
@@ -407,15 +385,33 @@ public class DealerOrderSettlement10584Task {
.eq(ShopDealerOrder::getTenantId, TENANT_ID) .eq(ShopDealerOrder::getTenantId, TENANT_ID)
.eq(ShopDealerOrder::getOrderNo, order.getOrderNo()); .eq(ShopDealerOrder::getOrderNo, order.getOrderNo());
boolean needUpdate = false; boolean needUpdate = false;
if (existed.getFirstDividendUser() == null && shopRoleCommission.storeDirectUserId != null) { if (shopRoleCommission.storeDirectUserId != null) {
uw.set(ShopDealerOrder::getFirstDividendUser, shopRoleCommission.storeDirectUserId); Integer existedUser = existed.getFirstDividendUser();
uw.set(ShopDealerOrder::getFirstDividend, shopRoleCommission.storeDirectMoney); boolean needSetUser = existedUser == null;
needUpdate = true; boolean needSetMoney = existed.getFirstDividend() == null || existed.getFirstDividend().signum() == 0;
if (needSetUser) {
uw.set(ShopDealerOrder::getFirstDividendUser, shopRoleCommission.storeDirectUserId);
needUpdate = true;
}
boolean sameUser = existedUser == null || Objects.equals(existedUser, shopRoleCommission.storeDirectUserId);
if (sameUser && needSetMoney && shopRoleCommission.storeDirectMoney != null && shopRoleCommission.storeDirectMoney.signum() > 0) {
uw.set(ShopDealerOrder::getFirstDividend, shopRoleCommission.storeDirectMoney);
needUpdate = true;
}
} }
if (existed.getSecondDividendUser() == null && shopRoleCommission.storeSimpleUserId != null) { if (shopRoleCommission.storeSimpleUserId != null) {
uw.set(ShopDealerOrder::getSecondDividendUser, shopRoleCommission.storeSimpleUserId); Integer existedUser = existed.getSecondDividendUser();
uw.set(ShopDealerOrder::getSecondDividend, shopRoleCommission.storeSimpleMoney); boolean needSetUser = existedUser == null;
needUpdate = true; boolean needSetMoney = existed.getSecondDividend() == null || existed.getSecondDividend().signum() == 0;
if (needSetUser) {
uw.set(ShopDealerOrder::getSecondDividendUser, shopRoleCommission.storeSimpleUserId);
needUpdate = true;
}
boolean sameUser = existedUser == null || Objects.equals(existedUser, shopRoleCommission.storeSimpleUserId);
if (sameUser && needSetMoney && shopRoleCommission.storeSimpleMoney != null && shopRoleCommission.storeSimpleMoney.signum() > 0) {
uw.set(ShopDealerOrder::getSecondDividend, shopRoleCommission.storeSimpleMoney);
needUpdate = true;
}
} }
if (needUpdate) { if (needUpdate) {
shopDealerOrderService.update(uw); shopDealerOrderService.update(uw);
@@ -479,6 +475,13 @@ public class DealerOrderSettlement10584Task {
return base.multiply(rate).setScale(2, RoundingMode.HALF_UP); return base.multiply(rate).setScale(2, RoundingMode.HALF_UP);
} }
private BigDecimal calcDividendMoney(BigDecimal base, BigDecimal rate) {
if (base == null || rate == null) {
return BigDecimal.ZERO;
}
return base.multiply(rate).setScale(DIVIDEND_SCALE, RoundingMode.HALF_UP);
}
private static class DealerRefereeCommission { private static class DealerRefereeCommission {
private final Integer directDealerId; private final Integer directDealerId;
private final BigDecimal directMoney; private final BigDecimal directMoney;

View File

@@ -0,0 +1,234 @@
package com.gxwebsoft.shop.task;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.common.core.context.TenantContext;
import com.gxwebsoft.shop.entity.ShopDealerReferee;
import com.gxwebsoft.shop.entity.ShopDealerUser;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.mapper.ShopDealerRefereeMapper;
import com.gxwebsoft.shop.service.ShopDealerRefereeService;
import com.gxwebsoft.shop.service.ShopDealerUserService;
import com.gxwebsoft.shop.service.ShopOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 租户10584一次性排查任务
* <p>
* 应用启动后延迟10秒执行一次查询指定订单的下单用户上级推荐人/门店(type=1)链路。
*/
@Slf4j
@Component
public class ShopOrderSettlement10584Task {
private static final Integer TENANT_ID = 10584;
/**
* 目标订单编号(按用户要求写死;如需动态,可改成配置项或入参)。
*/
private static final String TARGET_ORDER_NO = "2015591043920007168";
private static final int MAX_REFEREE_CHAIN_DEPTH = 20;
private final AtomicBoolean executed = new AtomicBoolean(false);
@Resource
private ShopOrderService shopOrderService;
@Resource
private ShopDealerRefereeService shopDealerRefereeService;
@Resource
private ShopDealerRefereeMapper shopDealerRefereeMapper;
@Resource
private ShopDealerUserService shopDealerUserService;
/**
* 启动10秒后执行只执行一次。
*/
@EventListener(ApplicationReadyEvent.class)
public void scheduleOnceAfterStartup() {
log.info("ShopOrderSettlement10584Task 已注册10秒后执行一次 - tenantId={}, orderNo={}", TENANT_ID, TARGET_ORDER_NO);
CompletableFuture
.delayedExecutor(10, TimeUnit.SECONDS)
.execute(() -> TenantContext.runIgnoreTenant(this::runOnceIgnoreTenant));
}
private void runOnceIgnoreTenant() {
if (!executed.compareAndSet(false, true)) {
return;
}
ShopOrder order = shopOrderService.getOne(
new LambdaQueryWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, TENANT_ID)
.eq(ShopOrder::getOrderNo, TARGET_ORDER_NO)
.eq(ShopOrder::getDeleted, 0)
.orderByDesc(ShopOrder::getOrderId)
.last("limit 1")
);
if (order == null) {
log.warn("未找到目标订单 - tenantId={}, orderNo={}", TENANT_ID, TARGET_ORDER_NO);
return;
}
Integer buyerUserId = order.getUserId();
Integer refereeId = getRefereeIdByLevel(buyerUserId, 1);
Integer refereeRefereeId = getRefereeIdByLevel(buyerUserId, 2);
List<Integer> upstreamReferees = findFirstTwoUpstreamReferees(buyerUserId);
Integer upstreamRefereeId = upstreamReferees.size() > 0 ? upstreamReferees.get(0) : null;
Integer upstreamRefereeRefereeId = upstreamReferees.size() > 1 ? upstreamReferees.get(1) : null;
List<Integer> shopRoleReferees = findFirstTwoShopRoleReferees(buyerUserId);
Integer shopRoleRefereeId = shopRoleReferees.size() > 0 ? shopRoleReferees.get(0) : null;
Integer shopRoleRefereeRefereeId = shopRoleReferees.size() > 1 ? shopRoleReferees.get(1) : null;
log.info("订单推荐关系查询结果 - orderNo={}, buyerUserId={}, refereeId(level=1)={}, refereeRefereeId(level=2)={}, upstreamRefereeId(level=1链路第1级)={}, upstreamRefereeRefereeId(level=1链路第2级)={}, shopRoleRefereeId(type=1)={}, shopRoleRefereeRefereeId(type=1)={}",
order.getOrderNo(), buyerUserId, refereeId, refereeRefereeId, upstreamRefereeId, upstreamRefereeRefereeId, shopRoleRefereeId, shopRoleRefereeRefereeId);
// 便于排查:从下单用户开始一路向上打印每一跳的门店(type=1)判定情况。
logShopRoleTraversal(buyerUserId);
}
private Integer getRefereeIdByLevel(Integer userId, int level) {
if (userId == null) {
return null;
}
ShopDealerReferee rel = shopDealerRefereeService.getOne(
new LambdaQueryWrapper<ShopDealerReferee>()
.eq(ShopDealerReferee::getTenantId, TENANT_ID)
.eq(ShopDealerReferee::getUserId, userId)
.eq(ShopDealerReferee::getLevel, level)
.orderByDesc(ShopDealerReferee::getId)
.last("limit 1")
);
return rel != null ? rel.getDealerId() : null;
}
/**
* 基于 shop_dealer_referee(level=1) 的链路,找出“最近的两级上级用户”。
* <p>
* 旧逻辑依赖扩展字段 isShopRole(已废弃) 过滤门店角色;目前仅按链路取前两级用于排查。
*/
private List<Integer> findFirstTwoUpstreamReferees(Integer buyerUserId) {
if (buyerUserId == null) {
return Collections.emptyList();
}
List<Integer> result = new ArrayList<>(2);
Set<Integer> visited = new HashSet<>();
// 仅依赖 level=1 的推荐关系一路向上查找(可兼容“只维护 level=1”的数据形态
Integer current = buyerUserId;
for (int i = 0; i < MAX_REFEREE_CHAIN_DEPTH && current != null && result.size() < 2; i++) {
Integer parentId = getRefereeIdByLevel(current, 1);
if (parentId == null || !visited.add(parentId)) {
break;
}
result.add(parentId);
current = parentId;
}
return result;
}
/**
* 基于 shop_dealer_referee 的链路,找出“最近的两级门店角色用户”(ShopDealerUser.type=1)。
* <p>
* 判定逻辑由 SQL join shop_dealer_user(type=1) 得到 isShopRole。
*/
private List<Integer> findFirstTwoShopRoleReferees(Integer buyerUserId) {
if (buyerUserId == null) {
return Collections.emptyList();
}
List<Integer> result = new ArrayList<>(2);
Set<Integer> visited = new HashSet<>();
// 优先:直接从该买家(userId)的多级关系(level=1/2/3/...)里,按 level 升序找到最近的两级门店(type=1)。
// List<ShopDealerReferee> relList = shopDealerRefereeMapper.selectRefereeChainWithShopRole(
// TENANT_ID, buyerUserId, MAX_REFEREE_CHAIN_DEPTH
// );
// for (ShopDealerReferee rel : relList) {
// Integer dealerId = rel != null ? rel.getDealerId() : null;
// if (dealerId == null || !visited.add(dealerId)) {
// continue;
// }
// if (Boolean.TRUE.equals(rel.getIsShopRole())) {
// result.add(dealerId);
// if (result.size() >= 2) {
// return result;
// }
// }
// }
// 兜底:若只维护了 level=1 的推荐关系,则尝试按“向上查找”的方式一路找门店(type=1)。
// Integer current = buyerUserId;
// for (int i = 0; i < MAX_REFEREE_CHAIN_DEPTH && current != null && result.size() < 2; i++) {
// ShopDealerReferee parentRel = shopDealerRefereeMapper.selectFirstLevelRefereeWithShopRole(TENANT_ID, current);
// Integer parentId = parentRel != null ? parentRel.getDealerId() : null;
// if (parentId == null || !visited.add(parentId)) {
// break;
// }
//
// if (Boolean.TRUE.equals(parentRel.getIsShopRole())) {
// result.add(parentId);
// }
// current = parentId;
// }
//
return result;
}
private void logShopRoleTraversal(Integer buyerUserId) {
if (buyerUserId == null) {
return;
}
Integer current = buyerUserId;
Set<Integer> visited = new HashSet<>();
/*for (int depth = 1; depth <= MAX_REFEREE_CHAIN_DEPTH && current != null; depth++) {
ShopDealerReferee parentRel = shopDealerRefereeMapper.selectFirstLevelRefereeWithShopRole(TENANT_ID, current);
Integer parentId = parentRel != null ? parentRel.getDealerId() : null;
if (parentId == null) {
log.info("type=1链路检查结束 - buyerUserId={}, depth={}, currentUserId={}, reason=no_parent", buyerUserId, depth, current);
return;
}
if (!visited.add(parentId)) {
log.info("type=1链路检查结束 - buyerUserId={}, depth={}, currentUserId={}, parentUserId={}, reason=cycle", buyerUserId, depth, current, parentId);
return;
}
ShopDealerUser du = shopDealerUserService.getOne(
new LambdaQueryWrapper<ShopDealerUser>()
.eq(ShopDealerUser::getTenantId, TENANT_ID)
.eq(ShopDealerUser::getUserId, parentId)
.orderByDesc(ShopDealerUser::getId)
.last("limit 1")
);
Integer duType = du != null ? du.getType() : null;
Integer duIsDelete = du != null ? du.getIsDelete() : null;
log.info("type=1链路检查 - buyerUserId={}, depth={}, childUserId={}, parentUserId={}, isShopRole(sql)={}, shopDealerUser.type={}, shopDealerUser.is_delete={}",
buyerUserId, depth, current, parentId, parentRel != null ? parentRel.getIsShopRole() : null, duType, duIsDelete);
current = parentId;
}*/
log.info("type=1链路检查结束 - buyerUserId={}, depthReached={}, reason=max_depth", buyerUserId, MAX_REFEREE_CHAIN_DEPTH);
}
}