From 96abe868efef1397b7990c012d620225941d7b2d Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Fri, 9 Oct 2026 13:47:26 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=B6=88=E6=81=AF=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E6=9F=A5=E8=AF=A2=20fix=EF=BC=9A=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E6=9C=BA=E5=BA=93=E5=AD=98=E6=98=BE=E7=A4=BA=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canteen/vo/app/DailyMenuAppVO.java | 4 +- .../impl/CanteenDailyMenuServiceImpl.java | 2 +- .../impl/CanteenDailyMenuStockSupport.java | 14 ++-- .../appmessage/AppPushDeliveryRetryDto.java | 25 +++++++ .../vo/appmessage/AppPushDeliveryVo.java | 3 + .../ThirdAppMessagePcController.java | 22 +++++- .../appmessage/ThirdPushDeliveryService.java | 12 +++- .../ThirdPushDeliveryServiceImpl.java | 68 ++++++++++++++++++- 8 files changed, 136 insertions(+), 14 deletions(-) create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/appmessage/AppPushDeliveryRetryDto.java diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/app/DailyMenuAppVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/app/DailyMenuAppVO.java index fe00ceef7..f900a2c01 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/app/DailyMenuAppVO.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/app/DailyMenuAppVO.java @@ -93,7 +93,7 @@ public class DailyMenuAppVO { /** * 当前可售库存。 - *

App/移动端:移动端可预订剩余;AD:总库存 − 移动端已预订。字段名保持 stock 兼容旧客户端。 + *

App/移动端:移动端可预订剩余;AD:剩余总库存(总库存 − 线上已订 − 线下已售)。字段名保持 stock 兼容旧客户端。 */ private Integer stock; @@ -112,7 +112,7 @@ public class DailyMenuAppVO { /** 移动端可预订剩余 */ private Integer remainOnlineStock; - /** App:剩余总库存;AD:与 stock 同为 总库存−移动端已预订 */ + /** 剩余总库存(AD 与 stock 同值;管理端 remainStock 同口径) */ private Integer remainOfflineStock; /** diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuServiceImpl.java index d8b411975..8049b9af8 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuServiceImpl.java @@ -385,7 +385,7 @@ public class CanteenDailyMenuServiceImpl extends ServiceImpl getCanteenMenuListScope(String day, Integer hourType, Long categoryId, Long merchantId) { List list = baseMapper.getCanteenMenuList(day, hourType, categoryId, merchantId, DataScope.of()); fillDailyMenuAppCombo(list); - // 安卓消费机:stock = 当日线下剩余 + // 安卓消费机:stock = 剩余总库存(与管理端 remainStock、线下扣减边界同口径) LocalDate mealDate = null; try { mealDate = day == null ? null : LocalDate.parse(day); diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuStockSupport.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuStockSupport.java index 63e7eb8d9..5588b7e89 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuStockSupport.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuStockSupport.java @@ -30,7 +30,7 @@ import java.util.stream.Collectors; *

  * 剩余总库存 = max(0, 总库存 − 移动端已预订 − 消费机已销售)
  * 移动端可预订剩余 = min(移动端上限 − 已预订, 剩余总库存)
- * AD stock = max(0, 总库存 − 移动端已预订)
+ * AD stock = 剩余总库存(与管理端 remainStock、线下扣减边界同口径)
  * 
* PC 管理端只展示总库存、移动端上限、移动端已订、消费机已售、剩余总库存。 * 扣减仍受「剩余总库存」边界约束,防止超卖。 @@ -95,7 +95,8 @@ public class CanteenDailyMenuStockSupport { } /** - * AD 返回的 stock:总库存 − 移动端已预订(不含消费机已售;已售由 offline_sold 单独体现)。 + * 总库存 − 移动端已预订(不含消费机已售)。 + * 仅作库存扣减流程的线下游标基线;AD 列表展示已改用 {@link #sharedRemain}。 */ public static int adStockOf(Integer totalStock, Integer onlineSold) { return Math.max(0, totalStockOf(totalStock) - soldOrDefault(onlineSold)); @@ -126,7 +127,7 @@ public class CanteenDailyMenuStockSupport { applyAppComboLinkedRemain(rows, true); } - /** 安卓消费机:stock = 总库存 − 移动端已预订 */ + /** 安卓消费机:stock = 剩余总库存(管理端 remainStock 同口径) */ public void fillAdOfflineRemain(List rows, Long merchantId, LocalDate mealDate) { if (CollUtil.isEmpty(rows)) { return; @@ -206,15 +207,14 @@ public class CanteenDailyMenuStockSupport { int offlineSold = soldOrDefault(row.getOfflineSold()); int shared = sharedRemain(total, onlineSold, offlineSold); int onlineRemain = onlineRemain(total, mobile, onlineSold, offlineSold); - int adStock = adStockOf(total, onlineSold); row.setOnlineStock(mobile); row.setOfflineStock(total); row.setOnlineSold(onlineSold); row.setOfflineSold(offlineSold); row.setRemainOnlineStock(onlineRemain); - // AD:remainOfflineStock / stock = 总库存 − 线上已订;App 侧 remainOffline 仍给剩余总库存供参考 - row.setRemainOfflineStock(stockAsOnline ? shared : adStock); - row.setStock(stockAsOnline ? onlineRemain : adStock); + // remainOfflineStock = 剩余总库存;stock:App=移动端可订剩余,AD=剩余总库存(与线下扣减边界一致) + row.setRemainOfflineStock(shared); + row.setStock(stockAsOnline ? onlineRemain : shared); } /** diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/appmessage/AppPushDeliveryRetryDto.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/appmessage/AppPushDeliveryRetryDto.java new file mode 100644 index 000000000..755fa78ea --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/appmessage/AppPushDeliveryRetryDto.java @@ -0,0 +1,25 @@ +package com.bsd.pjcloud.thirdparty.dto.appmessage; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * PC重新推送失败终态投递记录的请求。 + */ +@Data +public class AppPushDeliveryRetryDto implements Serializable { + + /** 当前操作的商户ID,用于复核管理员数据范围。 */ + @NotNull(message = "商户ID不能为空") + private Long mchId; + + /** 待重新推送的投递记录ID列表,仅失败终态记录会被重置。 */ + @NotEmpty(message = "请选择要重新推送的投递记录") + @Size(max = 100, message = "单次最多重新推送100条投递记录") + private List deliveryIds; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/vo/appmessage/AppPushDeliveryVo.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/vo/appmessage/AppPushDeliveryVo.java index f8c34ca5a..e9c7e9780 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/vo/appmessage/AppPushDeliveryVo.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/vo/appmessage/AppPushDeliveryVo.java @@ -23,6 +23,9 @@ public class AppPushDeliveryVo implements Serializable { /** 目标用户ID。 */ private Long recipientUserId; + /** 接收用户姓名,远程用户服务不可用或用户已删除时为空。 */ + private String recipientName; + /** 投递状态。 */ private String status; diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/appmessage/ThirdAppMessagePcController.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/appmessage/ThirdAppMessagePcController.java index dd24ba9f7..f9b589a03 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/appmessage/ThirdAppMessagePcController.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/appmessage/ThirdAppMessagePcController.java @@ -6,6 +6,7 @@ import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.common.security.util.SecurityUtils; +import com.bsd.pjcloud.thirdparty.dto.appmessage.AppPushDeliveryRetryDto; import com.bsd.pjcloud.thirdparty.dto.appmessage.AppPushPolicySaveDto; import com.bsd.pjcloud.thirdparty.dto.appmessage.AppPushTestDto; import com.bsd.pjcloud.thirdparty.service.appmessage.ThirdAppMessageSettingService; @@ -121,6 +122,7 @@ public class ThirdAppMessagePcController { * @param mchId 商户ID * @param status 可选投递状态 * @param eventCode 可选事件编码 + * @param recipientUserId 可选接收用户ID * @return 商户投递记录分页 */ @Operation(summary = "分页查询商户业务Push投递记录") @@ -128,9 +130,10 @@ public class ThirdAppMessagePcController { @GetMapping("/deliveries") public R> pageDeliveries(@ParameterObject Page page, @RequestParam Long mchId, @RequestParam(required = false) String status, - @RequestParam(required = false) String eventCode) { + @RequestParam(required = false) String eventCode, + @RequestParam(required = false) Long recipientUserId) { requireMerchantAccess(mchId); - return R.ok(deliveryService.page(page, mchId, status, eventCode)); + return R.ok(deliveryService.page(page, mchId, status, eventCode, recipientUserId)); } /** @@ -148,6 +151,21 @@ public class ThirdAppMessagePcController { return R.ok(deliveryService.listAttempts(deliveryId, mchId)); } + /** + * 把失败终态的投递记录重置回待投递,由投递扫描任务在数秒内重新推送。 + * + * @param dto 商户与投递记录ID集合 + * @return 实际重置的记录数量 + */ + @Operation(summary = "重新推送App投递记录") + @SysLog("重新推送App投递记录") + @PreAuthorize("@pms.hasPermission('wallet_mchManage_setting')") + @PostMapping("/deliveries/retry") + public R retryDeliveries(@Valid @RequestBody AppPushDeliveryRetryDto dto) { + requireMerchantAccess(dto.getMchId()); + return R.ok(deliveryService.retryDeliveries(dto.getMchId(), dto.getDeliveryIds())); + } + /** * 查询当前部署Push供应商只读状态。 * diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/appmessage/ThirdPushDeliveryService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/appmessage/ThirdPushDeliveryService.java index 0bc601a08..85690c424 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/appmessage/ThirdPushDeliveryService.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/appmessage/ThirdPushDeliveryService.java @@ -35,9 +35,10 @@ public interface ThirdPushDeliveryService { * @param mchId 商户ID * @param status 可选投递状态 * @param eventCode 可选事件编码 + * @param recipientUserId 可选接收用户ID * @return 商户投递记录分页 */ - Page page(Page page, Long mchId, String status, String eventCode); + Page page(Page page, Long mchId, String status, String eventCode, Long recipientUserId); /** * 查询指定商户投递任务的供应商调用详情。 @@ -48,6 +49,15 @@ public interface ThirdPushDeliveryService { */ List listAttempts(Long deliveryId, Long mchId); + /** + * 把失败终态的投递记录重置回待投递,由既有扫描任务在数秒内重新推送。 + * + * @param mchId 商户ID + * @param deliveryIds 待重新推送的投递记录ID + * @return 实际重置的记录数量,非失败终态或非本商户记录不计入 + */ + int retryDeliveries(Long mchId, List deliveryIds); + /** * 返回供应商只读健康状态,不包含部署密钥。 * diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appmessage/ThirdPushDeliveryServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appmessage/ThirdPushDeliveryServiceImpl.java index 2cd47554a..7cb353777 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appmessage/ThirdPushDeliveryServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appmessage/ThirdPushDeliveryServiceImpl.java @@ -1,9 +1,14 @@ package com.bsd.pjcloud.thirdparty.service.impl.appmessage; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.admin.api.entity.SysUser; +import com.bsd.pjcloud.admin.api.feign.RemoteUserService; +import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.data.tenant.TenantBroker; import com.bsd.pjcloud.thirdparty.constant.appmessage.AppMessageConstants; import com.bsd.pjcloud.thirdparty.constant.push.ThirdPushBizTypeEnum; @@ -83,6 +88,9 @@ public class ThirdPushDeliveryServiceImpl implements ThirdPushDeliveryService { /** 事务模板,保证任务领取、供应商尝试留痕与最终投递状态在同一数据库事务中提交。 */ private final TransactionTemplate transactionTemplate; + /** UPMS远程用户服务,用于PC列表补齐接收用户姓名。 */ + private final RemoteUserService remoteUserService; + /** * 创建一条唯一Push决策;没有有效设备时把原本待投递任务记录为跳过。 * @@ -157,23 +165,56 @@ public class ThirdPushDeliveryServiceImpl implements ThirdPushDeliveryService { * @param mchId 商户ID * @param status 可选投递状态 * @param eventCode 可选事件编码 + * @param recipientUserId 可选接收用户ID * @return PC投递记录分页 */ @Override - public Page page(Page page, Long mchId, String status, String eventCode) { + public Page page(Page page, Long mchId, String status, String eventCode, Long recipientUserId) { Page entityPage = deliveryMapper.selectPage( new Page<>(page.getCurrent(), page.getSize()), Wrappers.lambdaQuery() .eq(ThirdPushDeliveryEntity::getSourceMchId, mchId) .eq(StrUtil.isNotBlank(status), ThirdPushDeliveryEntity::getStatus, status) .eq(StrUtil.isNotBlank(eventCode), ThirdPushDeliveryEntity::getEventCode, eventCode) + .eq(recipientUserId != null, ThirdPushDeliveryEntity::getRecipientUserId, recipientUserId) .orderByDesc(ThirdPushDeliveryEntity::getCreateTime) .orderByDesc(ThirdPushDeliveryEntity::getId)); Page result = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal()); result.setRecords(entityPage.getRecords().stream().map(this::toDeliveryVo).toList()); + fillRecipientNames(result.getRecords()); return result; } + /** + * 批量补齐投递记录接收用户姓名,远程服务异常或用户已删除不阻断诊断列表。 + * + * @param records 当前页PC投递记录 + */ + private void fillRecipientNames(List records) { + List userIdList = records.stream().map(AppPushDeliveryVo::getRecipientUserId) + .filter(ObjectUtil::isNotEmpty).distinct().toList(); + if (ObjectUtil.isEmpty(userIdList)) { + return; + } + try { + R> result = remoteUserService.getInnerUserById(userIdList); + List userList = result == null || !result.isOk() ? null : result.getData(); + if (ObjectUtil.isEmpty(userList)) { + return; + } + Map userMap = IterUtil.toMap(userList, SysUser::getUserId); + for (AppPushDeliveryVo record : records) { + SysUser targetUser = userMap.get(record.getRecipientUserId()); + if (targetUser == null) { + continue; + } + record.setRecipientName(targetUser.getName()); + } + } catch (Exception exception) { + log.warn("投递记录列表补充接收用户姓名失败,errorType:{}", exception.getClass().getSimpleName()); + } + } + /** * 查询指定商户投递任务的供应商调用详情,先校验任务确实属于该商户。 * @@ -196,6 +237,31 @@ public class ThirdPushDeliveryServiceImpl implements ThirdPushDeliveryService { .stream().map(this::toAttemptVo).toList(); } + /** + * 把失败终态的投递记录条件重置回待投递,交由既有扫描任务重新领取处理。 + * + * @param mchId 商户ID + * @param deliveryIds 待重新推送的投递记录ID + * @return 实际重置的记录数量 + */ + @Override + public int retryDeliveries(Long mchId, List deliveryIds) { + if (ObjectUtil.isEmpty(deliveryIds)) { + return 0; + } + return deliveryMapper.update(null, Wrappers.lambdaUpdate() + .set(ThirdPushDeliveryEntity::getStatus, AppMessageConstants.DELIVERY_PENDING) + .set(ThirdPushDeliveryEntity::getAttemptCount, 0) + .set(ThirdPushDeliveryEntity::getNextRetryTime, null) + .set(ThirdPushDeliveryEntity::getFinalProviderCode, null) + .set(ThirdPushDeliveryEntity::getLastError, null) + .set(ThirdPushDeliveryEntity::getSuccessTime, null) + .set(ThirdPushDeliveryEntity::getUpdateTime, LocalDateTime.now()) + .in(ThirdPushDeliveryEntity::getId, deliveryIds) + .eq(ThirdPushDeliveryEntity::getSourceMchId, mchId) + .eq(ThirdPushDeliveryEntity::getStatus, AppMessageConstants.DELIVERY_FAILED)); + } + /** * 返回全部供应商只读状态。 *