refactor(task): 优化经销商订单结算任务的推荐关系查询逻辑
- 移除废弃的 isShopRole 扩展字段及相关 SQL 查询 - 将推荐关系查询逻辑改为基于 level=1 链路的简单向上遍历 - 添加分红金额计算精度控制,使用 3 位小数精度 - 调整定时任务执行频率从 30 秒改为 20 秒 - 优化订单结算时的分红用户和金额更新逻辑,支持增量更新 - 新增一次性排查任务用于调试订单推荐关系链路问题
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,第1个,2%)");
|
creditDealerCommission(shopRoleReferees.get(0), storeDirectMoney, order, order.getUserId(), "门店直推佣金(第1级,2%)");
|
||||||
creditDealerCommission(shopRoleReferees.get(1), storeSimpleMoney, order, order.getUserId(), "门店简推佣金(type=1,第2个,1%)");
|
creditDealerCommission(shopRoleReferees.get(1), storeSimpleMoney, order, order.getUserId(), "门店简推佣金(第2级,1%)");
|
||||||
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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user