diff --git a/hiver-admin/src/main/resources/application.yml b/hiver-admin/src/main/resources/application.yml index 366dd1f2..758473f8 100644 --- a/hiver-admin/src/main/resources/application.yml +++ b/hiver-admin/src/main/resources/application.yml @@ -403,8 +403,17 @@ ignored: - /hiver/mall/order/getOrdersByUserId/** - /hiver/mall/delivery/page - /hiver/app/shopArea/getByParentId/** + - /hiver/app/shopArea/matchByLocation - /hiver/mall/delivery/countOrderByStatus + - /hiver/mall/admin/orderGroup/page - /hiver/mall/order/countByShop/** + - /hiver/app/shop/getByCondition + - /hiver/app/campus/categories + - /hiver/app/campus/posts/page + - /hiver/app/fishmarket/goods/page + - /hiver/app/fishmarket/categories + - /hiver/mall/admin/seckillGroup/baseDeliveryFee/get + - /hiver/app/ie/real-name/audit-setting # 选择物流公司 - /hiver/app/logitics/chooseCompany # 根据客户id分页获取销售历史(按商品) diff --git a/hiver-admin/test-output/test-report.html b/hiver-admin/test-output/test-report.html index 1531ffda..267b60c5 100644 --- a/hiver-admin/test-output/test-report.html +++ b/hiver-admin/test-output/test-report.html @@ -35,7 +35,7 @@ Hiver
  • - 02, 2026 15:11:22 + 09, 2026 10:15:14
  • @@ -84,7 +84,7 @@

    passTest

    -

    15:11:23 / 0.028 secs

    +

    10:15:14 / 0.021 secs

    @@ -92,9 +92,9 @@
    #test-id=1
    passTest
    -09.02.2026 15:11:23 -09.02.2026 15:11:23 -0.028 secs +09.09.2026 10:15:14 +09.09.2026 10:15:14 +0.021 secs
    @@ -104,7 +104,7 @@ Pass - 15:11:23 + 10:15:14 Test passed @@ -128,13 +128,13 @@

    Started

    -

    02, 2026 15:11:22

    +

    09, 2026 10:15:14

    Ended

    -

    02, 2026 15:11:23

    +

    09, 2026 10:15:14

    diff --git a/hiver-core/src/main/java/cc/hiver/core/common/constant/DealingsRecordConstant.java b/hiver-core/src/main/java/cc/hiver/core/common/constant/DealingsRecordConstant.java index c0475b36..74fb2b74 100644 --- a/hiver-core/src/main/java/cc/hiver/core/common/constant/DealingsRecordConstant.java +++ b/hiver-core/src/main/java/cc/hiver/core/common/constant/DealingsRecordConstant.java @@ -15,7 +15,7 @@ public interface DealingsRecordConstant { /** * */ - String[] TYPE_REMARK = {"配送订单佣金","售后退款","配送排名奖励","商家提现","配送员提现","免单奖励","商家结算","用户提现","白嫖中奖","学院排位赛排名奖励","配送员原因退款扣款","商家原因退款补发配送佣金","商家退款/售后扣款","二级代理返佣"}; + String[] TYPE_REMARK = {"配送订单佣金","售后退款","配送排名奖励","商家提现","配送员提现","免单奖励","商家结算","用户提现","白嫖中奖","排位赛排名奖励","配送员原因退款扣款","商家原因退款补发配送佣金","商家退款/售后扣款","二级代理返佣"}; /** *交易类型:0:增加 1 提现 2消费 3售后 diff --git a/hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java b/hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java index faeb9fd2..49ba54f0 100644 --- a/hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java +++ b/hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java @@ -4,9 +4,15 @@ import cc.hiver.core.service.JPushService; import cn.jpush.api.JPushClient; import cn.jpush.api.push.PushResult; import cn.jpush.api.push.model.Message; +import cn.jpush.api.push.model.Options; +import cn.jpush.api.push.model.Platform; import cn.jpush.api.push.model.PushPayload; +import cn.jpush.api.push.model.audience.Audience; +import cn.jpush.api.push.model.notification.AndroidNotification; +import cn.jpush.api.push.model.notification.IosNotification; import cn.jpush.api.push.model.notification.Notification; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.PreDestroy; @@ -22,6 +28,9 @@ public class JPushServiceImpl implements JPushService { @Autowired private JPushClient jPushClient; + @Value("${jpush.apnsProduction:false}") + private Boolean apnsProduction; + private final ScheduledExecutorService pushScheduler = Executors.newSingleThreadScheduledExecutor(); @Autowired @@ -43,16 +52,35 @@ public class JPushServiceImpl implements JPushService { private void doSendPushNotification(String registrationId, String message, String orderId) { try { + String timestamp = String.valueOf(System.currentTimeMillis()); PushPayload payload = PushPayload.newBuilder() - .setPlatform(cn.jpush.api.push.model.Platform.all()) - .setAudience(cn.jpush.api.push.model.audience.Audience.registrationId(registrationId)) - .setNotification(Notification.alert(message)) + .setPlatform(Platform.all()) + .setAudience(Audience.registrationId(registrationId)) + .setNotification(Notification.newBuilder() + .addPlatformNotification(AndroidNotification.newBuilder() + .setAlert(message) + .addExtra("type", "订单通知") + .addExtra("order_id", orderId) + .addExtra("timestamp", timestamp) + .build()) + .addPlatformNotification(IosNotification.newBuilder() + .setAlert(message) + .setSound("default") + .incrBadge(1) + .addExtra("type", "订单通知") + .addExtra("order_id", orderId) + .addExtra("timestamp", timestamp) + .build()) + .build()) .setMessage(Message.newBuilder() .setMsgContent("消息内容") .addExtra("custom_key", "custom_value") // 额外参数 .addExtra("type", "订单通知") .addExtra("order_id",orderId) - .addExtra("timestamp", System.currentTimeMillis()) + .addExtra("timestamp", timestamp) + .build()) + .setOptions(Options.newBuilder() + .setApnsProduction(Boolean.TRUE.equals(apnsProduction)) .build()) .build(); PushResult result = jPushClient.sendPush(payload); diff --git a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java index 44a95fac..7480cafd 100644 --- a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java +++ b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java @@ -335,6 +335,52 @@ public class AuthController { return ResultUtil.data(result); } + @RequestMapping(value = "/updateClientId", method = RequestMethod.POST) + @ApiOperation("更新当前登录用户推送ID") + public Result updateClientId(@RequestParam(value = "clientId", required = false) String clientId) { + if (StringUtils.isEmpty(clientId)) { + return ResultUtil.error("clientId不能为空"); + } + final User user = securityUtil.getCurrUser(); + if (user == null || StringUtils.isEmpty(user.getId())) { + return ResultUtil.error("用户未登录"); + } + updateClientIdByUserId(user.getId(), clientId); + return ResultUtil.success("修改成功"); + } + + private void updateClientIdByUserId(String userId, String clientId) { + if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(clientId)) { + return; + } + List shop = shopService.getShopByUserid(userId); + if (shop != null && !shop.isEmpty()) { + for (Shop item : shop) { + item.setClientId(clientId); + shopService.update(item); + shopService.refreshShopCache(item.getId(), item.getRegionId()); + } + } + Worker worker = workerService.findByUserId(userId); + if (worker != null) { + worker.setClientId(clientId); + workerService.update(worker); + String oldRegion = worker.getRegion(); + WorkerRedisVo workerRedisVo = StrUtil.isNotBlank(oldRegion) ? workerRedisCacheUtil.get(oldRegion, worker.getWorkerId()) : null; + if (workerRedisVo != null) { + workerRedisVo.setWorker(worker); + if (Objects.equals(oldRegion, worker.getRegion())) { + workerRedisCacheUtil.update(worker.getRegion(), workerRedisVo); + } else { + workerRedisCacheUtil.remove(oldRegion, worker.getWorkerId()); + if (StrUtil.isNotBlank(worker.getRegion())) { + workerRedisCacheUtil.put(worker.getRegion(), workerRedisVo); + } + } + } + } + } + @RequestMapping(value = "/getLoginQRCode", method = RequestMethod.GET) @ApiOperation("获取扫码登录二维码") @RateLimiter(rate = 1, ipLimit = true) diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java index 9a09ae0c..7779cbf8 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java @@ -46,7 +46,7 @@ public class AdminDataStatisticsController { private static final BigDecimal CONTENT_AUDIT_PACKAGE_PRICE = new BigDecimal("0.05"); private static final BigDecimal REAL_NAME_UNIT_PRICE = BigDecimal.ONE; - private static final String REWARD_COLLEGE_RANK = "学院排位赛排名奖励"; + private static final String REWARD_COLLEGE_RANK = "排位赛排名奖励"; private static final String REWARD_DELIVERY_RANK = "配送排名奖励"; private static final String REWARD_DRAW_POOL = "白嫖中奖"; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopAreaController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopAreaController.java index 7ee76b6a..05a25797 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopAreaController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopAreaController.java @@ -272,6 +272,20 @@ public class ShopAreaController { return new ResultUtil>().setData(list); } + @RequestMapping(value = "/matchByLocation", method = RequestMethod.GET) + @ApiOperation(value = "根据经纬度匹配一级区域") + public Result matchByLocation(@ApiParam(value = "纬度", required = true) @RequestParam Double lat, + @ApiParam(value = "经度", required = true) @RequestParam Double lng) { + if (lat == null || lng == null) { + return ResultUtil.error("经纬度不能为空"); + } + if (lat < -90 || lat > 90 || lng < -180 || lng > 180) { + return ResultUtil.error("经纬度不合法"); + } + ShopArea matched = shopAreaService.matchByLocation(lat, lng); + return new ResultUtil().setData(matched); + } + public void setInfo(List list) { // lambda表达式 list.forEach(item -> { @@ -290,16 +304,35 @@ public class ShopAreaController { shopArea.setParentAgentId(null); shopArea.setParentAgentName(null); shopArea.setCommissionRatio(0); + shopArea.setCenterLng(null); + shopArea.setCenterLat(null); + shopArea.setRadiusMeters(null); + shopArea.setAreaKind(1); return null; } + Integer areaKind = shopArea.getAreaKind(); + if (areaKind == null) { + shopArea.setAreaKind(1); + } else if (areaKind != 1 && areaKind != 2) { + return ResultUtil.error("区域类型仅支持:1校园区域,2社会区域"); + } Integer commissionRatio = shopArea.getCommissionRatio(); if (commissionRatio == null) { shopArea.setCommissionRatio(0); - return null; - } - if (commissionRatio < 0 || commissionRatio > 100) { + } else if (commissionRatio < 0 || commissionRatio > 100) { return ResultUtil.error("返佣比例需为0或1-100之间的整数"); } + boolean hasLng = shopArea.getCenterLng() != null; + boolean hasLat = shopArea.getCenterLat() != null; + boolean hasRadius = shopArea.getRadiusMeters() != null; + if (hasLng || hasLat || hasRadius) { + if (!hasLng || !hasLat) { + return ResultUtil.error("区域中心经纬度需同时设置"); + } + if (!hasRadius || shopArea.getRadiusMeters() <= 0) { + return ResultUtil.error("匹配半径需为正整数(米)"); + } + } return null; } } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java index 539ed6f5..0b400ea2 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java @@ -242,7 +242,7 @@ public class WorkerController { @ApiOperation(value = "修改配送员信息-APP", notes = "需要通过下单编号获取订单信息") public Result editApp(WorkerAppVO u) { //修改配送员信息时如涉及到充值押金情况,需要更新余额且重置应缴 - Worker worker = workerService.get(u.getWorkerId()); + Worker worker = workerService.findByWorkerId(u.getWorkerId()); if (u.getDepoNum() != null && u.getDepoNum().compareTo(BigDecimal.ZERO) > 0) { Recharge recharge = new Recharge(); @@ -268,7 +268,7 @@ public class WorkerController { @RequestMapping(value = "/admin/disable/{workerId}", method = RequestMethod.POST) @ApiOperation(value = "后台人工禁用配送员") public Result disable(@ApiParam("用户唯一id标识") @PathVariable String workerId) { - Worker worker = workerService.get(workerId); + Worker worker = workerService.findByWorkerId(workerId); if (worker == null) { return ResultUtil.error("配送员不存在"); } @@ -284,7 +284,7 @@ public class WorkerController { @RequestMapping(value = "/admin/enable/{workerId}", method = RequestMethod.POST) @ApiOperation(value = "后台人工启用配送员") public Result enable(@ApiParam("配送员唯一id标识") @PathVariable String workerId) { - Worker worker = workerService.get(workerId); + Worker worker = workerService.findByWorkerId(workerId); if (worker == null) { return ResultUtil.error("配送员不存在"); } @@ -300,7 +300,8 @@ public class WorkerController { @ApiOperation(value = "后台设置配送员全职状态") public Result changeFullTime(@ApiParam("配送员唯一id标识") @PathVariable String workerId, @RequestParam("isFullTime") Integer isFullTime) { - Worker worker = workerService.get(workerId); + // 注意:不要用 workerService.get()(底层 getById 返回懒加载代理,仅有主键) + Worker worker = workerService.findByWorkerId(workerId); if (worker == null) { return ResultUtil.error("配送员不存在"); } @@ -308,7 +309,12 @@ public class WorkerController { return ResultUtil.error("全职状态参数错误"); } workerService.changeFullTime(workerId, isFullTime); + // 批量更新后重新查询完整对象,避免把残缺数据写入配送员缓存 + worker = workerService.findByWorkerId(workerId); worker.setIsFullTime(isFullTime); + if (worker == null) { + return ResultUtil.error("配送员不存在"); + } if (StrUtil.isNotBlank(worker.getRegion())) { WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(worker.getRegion(), worker.getWorkerId()); if (workerRedisVo != null) { @@ -331,7 +337,7 @@ public class WorkerController { @ApiOperation(value = "配送员上线") public Result onLine(@RequestParam("id") String id) { try { - Worker worker = workerService.get(id); + Worker worker = workerService.findByWorkerId(id); if (worker == null) { return ResultUtil.error("配送员不存在"); } @@ -405,6 +411,11 @@ public class WorkerController { * orderBkge -> 配送佣金正序 */ private String sortField; + /** + * 是否后台查询(仅后台管理系统传 true;小程序不传) + * 为 true 时:优先按取收货区域规则匹配,未匹配到则补充返回全职且上线的配送员 + */ + private Boolean fromAdmin; /** 页码(从1开始),默认1 */ private Integer pageNum = 1; /** 每页条数,默认10 */ @@ -461,27 +472,34 @@ public class WorkerController { } // ---- 配送规则匹配:shopAreaId + putAreaId + orderType + getPushOrder ---- - if (vo.getWorkerRelaPriceList() == null || vo.getWorkerRelaPriceList().isEmpty()) continue; - WorkerRelaPrice matchedRule = null; - for (WorkerRelaPrice rp : vo.getWorkerRelaPriceList()) { - // 缓存可能残留旧数据,规则也要限定当前校区 - if (StrUtil.isNotBlank(dto.getRegionId()) && !dto.getRegionId().equals(rp.getRegionId())) continue; - // 规则必须接收指派 - if (rp.getGetPushOrder() == null || rp.getGetPushOrder() != 1) continue; - // 取货区域匹配 - if (StrUtil.isNotBlank(dto.getShopAreaId()) && !dto.getShopAreaId().equals(rp.getGetAreaId())) continue; - // 送货区域匹配 - if (StrUtil.isNotBlank(dto.getPutAreaId()) && !dto.getPutAreaId().equals(rp.getPutAreaId())) continue; - // 订单类型匹配 - if (dto.getOrderType() != null && !dto.getOrderType().equals(rp.getOrderType())) continue; - // 改派时只允许选择接单佣金不超过当前配送单总佣金的规则 - if (dto.getCommissionAmount() != null - && (rp.getOrderBkge() == null || rp.getOrderBkge().compareTo(dto.getCommissionAmount()) > 0)) continue; - matchedRule = rp; - break; + if (vo.getWorkerRelaPriceList() != null && !vo.getWorkerRelaPriceList().isEmpty()) { + for (WorkerRelaPrice rp : vo.getWorkerRelaPriceList()) { + // 缓存可能残留旧数据,规则也要限定当前校区 + if (StrUtil.isNotBlank(dto.getRegionId()) && !dto.getRegionId().equals(rp.getRegionId())) continue; + // 规则必须接收指派 + if (rp.getGetPushOrder() == null || rp.getGetPushOrder() != 1) continue; + // 取货区域匹配 + if (StrUtil.isNotBlank(dto.getShopAreaId()) && !dto.getShopAreaId().equals(rp.getGetAreaId())) continue; + // 送货区域匹配 + if (StrUtil.isNotBlank(dto.getPutAreaId()) && !dto.getPutAreaId().equals(rp.getPutAreaId())) continue; + // 订单类型匹配 + if (dto.getOrderType() != null && !dto.getOrderType().equals(rp.getOrderType())) continue; + // 改派时只允许选择接单佣金不超过当前配送单总佣金的规则 + if (dto.getCommissionAmount() != null + && (rp.getOrderBkge() == null || rp.getOrderBkge().compareTo(dto.getCommissionAmount()) > 0)) continue; + matchedRule = rp; + break; + } + } + // 后台查询:取收货区域未匹配时,全职且已上线的配送员也返回(上线已在上方过滤) + if (matchedRule == null) { + boolean allowFullTimeFallback = Boolean.TRUE.equals(dto.getFromAdmin()) + && w.getIsFullTime() != null && w.getIsFullTime() == 1; + if (!allowFullTimeFallback) { + continue; + } } - if (matchedRule == null) continue; // ---- 从订单缓存获取该配送员的待完成订单,统计各状态数量 ---- List workerOrders = workerOrderCacheUtil.getAllAsList(w.getWorkerId()); @@ -524,8 +542,8 @@ public class WorkerController { matchVO.setIsFullTime(w.getIsFullTime()); matchVO.setHighFloorFee(w.getHighFloorFee()); matchVO.setDeliveryLocation(w.getDeliveryLocation()); - matchVO.setOrderBkge(matchedRule.getOrderBkge()); - matchVO.setRemark(matchedRule.getRemark()); + matchVO.setOrderBkge(matchedRule != null ? matchedRule.getOrderBkge() : null); + matchVO.setRemark(matchedRule != null ? matchedRule.getRemark() : null); matchVO.setOrderWaitCount(orderWaitCount); matchVO.setOrderGetCount(orderGetCount); matchVO.setOrderPutCount(orderPutCount); @@ -587,6 +605,7 @@ public class WorkerController { StrUtil.isBlank(dto.getKeyword()) ? null : dto.getKeyword().trim(), dto.getBaozhang(), dto.getXiangtong(), + dto.getFromAdmin(), dto.getSortField(), sortOrder ); diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerRelaPriceMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerRelaPriceMapper.java index 89e69aa7..7f1dfca9 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerRelaPriceMapper.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerRelaPriceMapper.java @@ -34,6 +34,7 @@ public interface WorkerRelaPriceMapper extends BaseMapper { * @param keyword 模糊搜索姓名或手机号(为 null 时不过滤) * @param baozhang 交易保障:true 则只查 rebate_amount > 0 的配送员 * @param xiangtong 相同地址:true 则只查手上有相同 putAreaId 订单的配送员 + * @param fromAdmin 后台查询:true 时未匹配取收货区域的全职上线配送员也返回 * @param sortField 排序字段:score/avgTime/orderBkge(默认 score) * @param sortOrder 排序方向:DESC/ASC * @return 配送员匹配 VO 分页结果 @@ -48,6 +49,7 @@ public interface WorkerRelaPriceMapper extends BaseMapper { @Param("keyword") String keyword, @Param("baozhang") Boolean baozhang, @Param("xiangtong") Boolean xiangtong, + @Param("fromAdmin") Boolean fromAdmin, @Param("sortField") String sortField, @Param("sortOrder") String sortOrder ); diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrder.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrder.java index ea8d4281..87a6dab8 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrder.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrder.java @@ -36,6 +36,8 @@ public class MallOrder implements Serializable { private Integer orderType; @ApiModelProperty(value = "订单类型 null:外卖 1:快递跑腿 2:团购 3:二手") private Integer otherOrder; + @ApiModelProperty(value = "是否虚拟成团 0否 1是") + private Integer isVirtualGroup; @ApiModelProperty(value = "是否秒杀订单 0否 1是") private Integer seckillOrder; @ApiModelProperty(value = "配送方式 1:外卖配送 2:到店消费") diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopArea.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopArea.java index 93123225..18cf04a7 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopArea.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopArea.java @@ -64,6 +64,21 @@ public class ShopArea extends HiverBaseEntity { @ApiModelProperty(value = "返佣比例,按百分比计算") private Integer commissionRatio = 0; + @ApiModelProperty(value = "区域中心经度(仅一级区域,用于定位自动匹配)") + @Column(precision = 12, scale = 6) + private BigDecimal centerLng; + + @ApiModelProperty(value = "区域中心纬度(仅一级区域,用于定位自动匹配)") + @Column(precision = 12, scale = 6) + private BigDecimal centerLat; + + @ApiModelProperty(value = "自动匹配半径(米,仅一级区域)") + private Integer radiusMeters; + + @ApiModelProperty(value = "区域类型:1校园区域 2社会区域(仅一级区域)") + @Column(nullable = false, columnDefinition = "int default 1") + private Integer areaKind = 1; + @Transient @TableField(exist = false) @ApiModelProperty(value = "父节点名称") diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopTakeaway.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopTakeaway.java index 56c22f98..bde29c40 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopTakeaway.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/ShopTakeaway.java @@ -55,6 +55,9 @@ public class ShopTakeaway extends HiverBaseEntity { @ApiModelProperty(value = "拼团合作佣金比例") private BigDecimal commissionRateMore; + @ApiModelProperty(value = "虚拟成团合作佣金比例") + private BigDecimal virtualGroupCommissionRate; + @ApiModelProperty(value = "单独购买合作佣金比例") private BigDecimal commissionRateOne; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeAdminController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeAdminController.java index b4e185fb..85f1849e 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeAdminController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeAdminController.java @@ -91,13 +91,16 @@ public class IeAdminController { public Result> auditSetting() { java.util.Map result = new java.util.HashMap<>(); result.put("realNameAuditEnabled", realNameAuthService.isRealNameAuditEnabled()); + result.put("ieListingAuditEnabled", realNameAuthService.isIeListingAuditEnabled()); return new ResultUtil>().setData(result); } @RequestMapping(value = "/audit-setting/save", method = RequestMethod.POST) @ApiOperation("保存i/e审核管理配置") - public Result saveAuditSetting(@RequestParam(required = false) Boolean realNameAuditEnabled) { + public Result saveAuditSetting(@RequestParam(required = false) Boolean realNameAuditEnabled, + @RequestParam(required = false) Boolean ieListingAuditEnabled) { realNameAuthService.setRealNameAuditEnabled(realNameAuditEnabled); + realNameAuthService.setIeListingAuditEnabled(ieListingAuditEnabled); return ResultUtil.success("保存成功"); } } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeRealNameAuthController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeRealNameAuthController.java index c13c9823..b2611187 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeRealNameAuthController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeRealNameAuthController.java @@ -50,6 +50,7 @@ public class IeRealNameAuthController { public Result> auditSetting() { java.util.Map result = new java.util.HashMap<>(); result.put("realNameAuditEnabled", realNameAuthService.isRealNameAuditEnabled()); + result.put("ieListingAuditEnabled", realNameAuthService.isIeListingAuditEnabled()); return new ResultUtil>().setData(result); } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/IeRealNameAuthService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/IeRealNameAuthService.java index b90b0f8c..fe4589e9 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/IeRealNameAuthService.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/IeRealNameAuthService.java @@ -39,6 +39,20 @@ public interface IeRealNameAuthService { */ void setRealNameAuditEnabled(Boolean enabled); + /** + * 查询i/e上架审核开关。 + * + * @return true=开启上架审核入口,false=保持正常i/e入口 + */ + boolean isIeListingAuditEnabled(); + + /** + * 后台保存i/e上架审核开关。 + * + * @param enabled true=开启上架审核入口 + */ + void setIeListingAuditEnabled(Boolean enabled); + /** * 匹配/建档前的服务端兜底校验,防止用户绕过前端直接请求匹配接口。 * diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeRealNameAuthServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeRealNameAuthServiceImpl.java index f820c080..0a798d2b 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeRealNameAuthServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeRealNameAuthServiceImpl.java @@ -61,6 +61,7 @@ public class IeRealNameAuthServiceImpl implements IeRealNameAuthService { private static final DateTimeFormatter BIRTHDAY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final int MAX_CERT_PICTURE_BASE64_BYTES = 50 * 1024; private static final String REAL_NAME_AUDIT_ENABLED_KEY = "IE:REVIEW:REAL_NAME_AUDIT_ENABLED"; + private static final String IE_LISTING_AUDIT_ENABLED_KEY = "IE:REVIEW:LISTING_AUDIT_ENABLED"; private static final String ALIYUN_CONSISTENT_PASS = "1"; private static final String ALIYUN_CONSISTENT_PHOTO_MISMATCH = "0"; private static final String REPEATED_PHOTO_MISMATCH_AUTO_PASS_REASON = "连续两次照片识别为非同一人,系统默认通过实名认证"; @@ -281,6 +282,18 @@ public class IeRealNameAuthServiceImpl implements IeRealNameAuthService { redisTemplateHelper.set(REAL_NAME_AUDIT_ENABLED_KEY, Boolean.FALSE.equals(enabled) ? "0" : "1"); } + @Override + public boolean isIeListingAuditEnabled() { + String value = redisTemplateHelper == null ? null : redisTemplateHelper.get(IE_LISTING_AUDIT_ENABLED_KEY); + // 默认关闭,不影响正常i/e入口;后台开启后用于小程序上架审核入口。 + return "1".equals(value) || "true".equalsIgnoreCase(value); + } + + @Override + public void setIeListingAuditEnabled(Boolean enabled) { + redisTemplateHelper.set(IE_LISTING_AUDIT_ENABLED_KEY, Boolean.TRUE.equals(enabled) ? "1" : "0"); + } + private AliyunThreeElementResult callAliyunThreeElement(String certName, String certNo, String certPicture, String regionId, Long userId) { if (isBlank(aliyunAccessKeyId) || isBlank(aliyunAccessKeySecret) || isBlank(aliyunAuthCode)) { diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderDelayConsumer.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderDelayConsumer.java index f499b21e..8c53c283 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderDelayConsumer.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderDelayConsumer.java @@ -11,6 +11,7 @@ import cc.hiver.mall.entity.MallDeliveryOrder; import cc.hiver.mall.entity.MallOrder; import cc.hiver.mall.entity.MallRefundRecord; import cc.hiver.mall.entity.Shop; +import cc.hiver.mall.service.mybatis.MallOrderGroupService; import cc.hiver.mall.service.mybatis.MallOrderService; import cc.hiver.mall.service.mybatis.MallRefundRecordService; import cc.hiver.mall.utils.WaitOrderCacheUtil; @@ -44,6 +45,9 @@ public class OrderDelayConsumer { @Autowired private MallOrderService mallOrderService; + @Autowired + private MallOrderGroupService mallOrderGroupService; + @Autowired private ShopMapper shopMapper; @@ -75,6 +79,7 @@ public class OrderDelayConsumer { String orderId = msg.getString("orderId"); String delayType = msg.getString("delayType"); String assignedWorkerId = msg.getString("assignedWorkerId"); + String groupId = msg.getString("groupId"); switch (delayType) { case "Worker_Timeout_5m": @@ -105,6 +110,9 @@ public class OrderDelayConsumer { case "Order_Pay_Timeout_1h": handleOrderPayTimeout(orderId); break; + case "Group_Virtual_Form_5m": + handleGroupVirtualForm(groupId); + break; default: log.warn("【订单延时处理】未知的延迟类型: {}", delayType); } @@ -175,6 +183,14 @@ public class OrderDelayConsumer { mallOrderService.autoCancelUnpaidOrder(orderId); } + private void handleGroupVirtualForm(String groupId) { + if (StringUtils.isBlank(groupId)) { + log.warn("【虚拟成团处理】groupId 为空,跳过"); + return; + } + mallOrderGroupService.virtualActivateGroup(groupId); + } + private void moveAssignedOrderBackToWaitCache(String workerId, MallDeliveryOrder delivery) { if (delivery == null || StringUtils.isBlank(delivery.getId())) { return; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java index 0b7ef280..dce0c85e 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/MerchantSettlementTask.java @@ -395,6 +395,10 @@ public class MerchantSettlementTask { if (takeaway != null) { if (order.getOrderType() != null && order.getOrderType() == 1) { commissionRate = takeaway.getCommissionRateOne(); + } else if (Integer.valueOf(1).equals(order.getIsVirtualGroup())) { + commissionRate = takeaway.getVirtualGroupCommissionRate() != null + ? takeaway.getVirtualGroupCommissionRate() + : takeaway.getCommissionRateMore(); } else if (order.getOrderType() != null && (order.getOrderType() == 2 || order.getOrderType() == 3)) { commissionRate = takeaway.getCommissionRateMore(); } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetAdventureSettleTask.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetAdventureSettleTask.java index 0c90e086..92fe7da4 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetAdventureSettleTask.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetAdventureSettleTask.java @@ -167,10 +167,10 @@ public class PlanetAdventureSettleTask { dealingsRecord.setLinkType(DealingsRecordConstant.TYPE[0]); dealingsRecord.setDealingsType(DealingsRecordConstant.DEALINGS_TYPE[0]); dealingsRecord.setCreateBy(session.getUserId()); - dealingsRecord.setRemark("学院助推赛荣耀榜第" + rankNo + "名现金奖励"); + dealingsRecord.setRemark("区域排位赛荣耀榜第" + rankNo + "名现金奖励"); dealingsRecordService.save(dealingsRecord); homeReminderService.addPlanetReward(String.valueOf(session.getUserId()), session.getRegionId(), "rankTop", - "学院助推赛第" + rankNo + "名奖励 ¥" + amount, linkId); + "区域排位赛第" + rankNo + "名奖励 ¥" + amount, linkId); return true; } } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/ShopAreaService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/ShopAreaService.java index b7794143..82d271c7 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/ShopAreaService.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/ShopAreaService.java @@ -58,6 +58,16 @@ public interface ShopAreaService extends HiverBaseService { */ ShopArea findTopShopAreaById(String id); + /** + * 根据经纬度匹配一级区域(点+半径) + * 仅返回半径内命中的校区;都不在半径内则返回 null + * + * @param lat 纬度 + * @param lng 经度 + * @return 命中的一级区域,未命中返回 null + */ + ShopArea matchByLocation(double lat, double lng); + void removeShopAreaCache(String parentId,String shopAreaId); void refreshShopAreaCache(String parentId,String shopAreaId); diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderGroupService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderGroupService.java index 79a133b1..83393438 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderGroupService.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderGroupService.java @@ -20,5 +20,10 @@ public interface MallOrderGroupService extends IService { */ void expireGroup(String groupId); + /** + * 普通拼团长时间未满员时,按虚拟成团激活当前已支付待成团订单。 + */ + void virtualActivateGroup(String groupId); + List selectMallGroup(MallOrderGroup group); } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopAreaServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopAreaServiceImpl.java index 99d1ea99..0e9e4d0d 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopAreaServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopAreaServiceImpl.java @@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.List; /** @@ -24,6 +25,7 @@ import java.util.List; @Service @Transactional public class ShopAreaServiceImpl implements ShopAreaService { + private static final double EARTH_RADIUS_METERS = 6371000D; @Autowired private ShopAreaDao shopAreaDao; @@ -104,6 +106,41 @@ public class ShopAreaServiceImpl implements ShopAreaService { } } + @Override + public ShopArea matchByLocation(double lat, double lng) { + List rootAreas = shopAreaDao.findByParentIdAndStatusOrderBySortOrder( + CommonConstant.PARENT_ID, CommonConstant.STATUS_NORMAL); + if (rootAreas == null || rootAreas.isEmpty()) { + return null; + } + ShopArea best = null; + double bestDistance = Double.MAX_VALUE; + for (ShopArea area : rootAreas) { + BigDecimal centerLat = area.getCenterLat(); + BigDecimal centerLng = area.getCenterLng(); + Integer radiusMeters = area.getRadiusMeters(); + if (centerLat == null || centerLng == null || radiusMeters == null || radiusMeters <= 0) { + continue; + } + double distance = distanceMeters(lat, lng, centerLat.doubleValue(), centerLng.doubleValue()); + if (distance <= radiusMeters && distance < bestDistance) { + best = area; + bestDistance = distance; + } + } + return best; + } + + private double distanceMeters(double lat1, double lng1, double lat2, double lng2) { + double dLat = Math.toRadians(lat2 - lat1); + double dLng = Math.toRadians(lng2 - lng1); + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) + * Math.sin(dLng / 2) * Math.sin(dLng / 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return EARTH_RADIUS_METERS * c; + } + @Override public void removeShopAreaCache(String parentId,String shopAreaId) { if (CharSequenceUtil.isBlank(parentId)) { diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java index fbe05276..22ff8fd9 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java @@ -52,9 +52,11 @@ public class MallOrderGroupServiceImpl extends ServiceImpl 激活 (超出拼团人数也可以参与) LambdaUpdateWrapper guw = new LambdaUpdateWrapper<>(); @@ -289,6 +291,55 @@ public class MallOrderGroupServiceImpl extends ServiceImpl= group.getTargetMembers()) { + checkAndActivateGroup(groupId); + return; + } + + List orderIdList = parseGroupOrderIds(group); + if (orderIdList.isEmpty()) return; + + LambdaUpdateWrapper guw = new LambdaUpdateWrapper<>(); + guw.eq(MallOrderGroup::getId, groupId) + .eq(MallOrderGroup::getStatus, GROUP_STATUS_FORMING) + .lt(MallOrderGroup::getCurrentMembers, group.getTargetMembers()) + .set(MallOrderGroup::getCurrentMembers, group.getTargetMembers()); + if (!this.update(guw)) { + return; + } + + LambdaUpdateWrapper ouw = new LambdaUpdateWrapper<>(); + ouw.in(MallOrder::getId, orderIdList) + .eq(MallOrder::getOrderType, ORDER_TYPE_GROUP) + .in(MallOrder::getStatus, Arrays.asList(ORDER_STATUS_WAIT_PAY, ORDER_STATUS_WAIT_GROUP)) + .set(MallOrder::getIsVirtualGroup, 1); + mallOrderService.update(ouw); + + group.setCurrentMembers(group.getTargetMembers()); + shopGroupOrderCacheUtil.update(group.getShopId(), group); + checkAndActivateGroup(groupId); + log.info("普通拼团 {} 触发虚拟成团,已标记 {} 条订单", groupId, orderIdList.size()); + } + + private List parseGroupOrderIds(MallOrderGroup group) { + if (group == null || StringUtils.isBlank(group.getGroupOrderIds())) { + return Collections.emptyList(); + } + return Arrays.stream(group.getGroupOrderIds().split(",")) + .map(String::trim) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toList()); + } + private void notifyShopDeliveryOrder(MallOrder order) { if (order == null || order.getShopDelivery() == null || order.getShopDelivery() != 1) { return; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java index d4172937..946f9aa4 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java @@ -80,7 +80,9 @@ public class MallOrderServiceImpl extends ServiceImpl extraParams = new HashMap<>(); + extraParams.put("groupId", group.getId()); + orderAsyncProducer.sendDelayMessage(order.getId(), DELAY_TYPE_GROUP_VIRTUAL_FORM, GROUP_VIRTUAL_FORM_DELAY_MILLIS, extraParams); } }else{ LambdaUpdateWrapper guw = new LambdaUpdateWrapper<>(); @@ -2515,6 +2520,10 @@ public class MallOrderServiceImpl extends ServiceImpl + @@ -135,7 +136,7 @@ o.package_fee, o.address_id, o.remark, o.create_time, o.pay_time, o.receiver_name, o.receiver_phone, o.receiver_address, o.shop_name, o.shop_phone, o.shop_address,o.number_code,o.region_id, - o.settlement_status,o.shop_make_time,o.other_order,o.seckill_order,o.user_require_make + o.settlement_status,o.shop_make_time,o.other_order,o.is_virtual_group,o.seckill_order,o.user_require_make ,o.is_free_order,o.free_amount,o.saved_amount,o.is_pack,o.shop_delivery, o.transfer_delivery,o.transfer_address_id,o.transfer_address_name FROM mall_order o @@ -257,7 +258,7 @@ o.package_fee, o.address_id, o.remark, o.create_time, o.pay_time, o.receiver_name, o.receiver_phone, o.receiver_address, o.shop_name, o.shop_phone, o.shop_address,o.number_code,o.region_id, - o.settlement_status,o.shop_make_time,o.other_order,o.seckill_order,o.user_require_make + o.settlement_status,o.shop_make_time,o.other_order,o.is_virtual_group,o.seckill_order,o.user_require_make ,o.is_free_order,o.free_amount,o.is_pack,o.shop_delivery, o.transfer_delivery,o.transfer_address_id,o.transfer_address_name FROM mall_order o LEFT JOIN mall_order_group og ON o.id = og.head_order_id diff --git a/hiver-modules/hiver-mall/src/main/resources/mapper/ShopTakeawayMapper.xml b/hiver-modules/hiver-mall/src/main/resources/mapper/ShopTakeawayMapper.xml index e402861b..6b8c9c8a 100644 --- a/hiver-modules/hiver-mall/src/main/resources/mapper/ShopTakeawayMapper.xml +++ b/hiver-modules/hiver-mall/src/main/resources/mapper/ShopTakeawayMapper.xml @@ -12,6 +12,7 @@ + @@ -25,7 +26,7 @@ id,create_by, create_time, update_time, update_by, - del_flag,shop_id,shop_name,region_id,commission_rate_more,commission_rate_one,business_hour_begin,business_hour_end,status,type,is_delivery,cooking_time,shoprank,is_price_lock,join_time + del_flag,shop_id,shop_name,region_id,commission_rate_more,virtual_group_commission_rate,commission_rate_one,business_hour_begin,business_hour_end,status,type,is_delivery,cooking_time,shoprank,is_price_lock,join_time