rk
2 天以前 79aebca90280ae4d4b25ed6ba6267128cdb61d3a
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java
@@ -30,6 +30,8 @@
import com.doumee.dao.business.OrderLogMapper;
import com.doumee.dao.dto.*;
import com.doumee.dao.vo.AccountResponse;
import com.doumee.dao.vo.DriverActiveOrderCountVO;
import com.doumee.dao.vo.DriverCancelLimitVO;
import com.doumee.dao.vo.DriverCenterVO;
import com.doumee.dao.vo.DriverGrabOrderVO;
import com.doumee.dao.vo.DriverOrderDetailVO;
@@ -48,6 +50,7 @@
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -845,7 +848,7 @@
        Long todayOrderCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getAcceptDriver, driver.getId())
                .eq(Orders::getDeleted, Constants.ZERO)
                .ge(Orders::getFinishTime, todayStart));
                .ge(Orders::getAcceptTime, todayStart));
        vo.setTodayOrderCount(todayOrderCount.intValue());
        // 待取货(已接单=3)
@@ -945,10 +948,8 @@
            goodTypeIds = cats.stream().map(Category::getId).collect(Collectors.toList());
        }
        // 3. Haversine SQL公式:司机到存件门店距离(km),使用Orders自带坐标
        String depositDist = "(6371 * acos(cos(radians(" + driverLat + ")) * cos(radians(t.DEPOSIT_LGT)) "
                + "* cos(radians(t.DEPOSIT_LAT) - radians(" + driverLng + ")) "
                + "+ sin(radians(" + driverLat + ")) * sin(radians(t.DEPOSIT_LGT))))";
        // 3. ST_Distance_Sphere计算司机到存件门店距离(km),使用Orders自带坐标
        String depositDist = "(ST_Distance_Sphere(POINT(" + driverLng + ", " + driverLat + "), POINT(t.DEPOSIT_LGT, t.DEPOSIT_LAT)) / 1000)";
        // 4. 构造MPJ查询
        IPage<Orders> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
@@ -963,6 +964,9 @@
                .select("s2.link_phone as takeShopLinkPhone")
                // 物品等级贵重标识
                .select("c2.other_field as c2OtherField")
                .select("c2.name as goodLevelName")
                // 是否存在特大尺寸
                .select("IF(EXISTS(SELECT 1 FROM orders_detail od JOIN category c3 ON c3.id = od.LUGGAGE_ID AND c3.TYPE = 4 AND c3.OTHER_FIELD = '1' WHERE od.ORDER_ID = t.ID AND od.DELETED = 0), 1, 0) as hasOversized")
                // JOIN
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID and s1.DELETED = 0")
                .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID and s2.DELETED = 0")
@@ -1062,13 +1066,15 @@
                .select("s2.address", Orders::getTakeShopAddress)
                .select("s2.link_phone as takeShopLinkPhone")
                .select("c2.other_field as c2OtherField")
                .select("c2.name as goodLevelName")
                .select("IF(EXISTS(SELECT 1 FROM orders_detail od JOIN category c3 ON c3.id = od.LUGGAGE_ID AND c3.TYPE = 4 AND c3.OTHER_FIELD = '1' WHERE od.ORDER_ID = t.ID AND od.DELETED = 0), 1, 0) as hasOversized")
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID and s1.DELETED = 0")
                .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID and s2.DELETED = 0")
                .leftJoin("category c1 on c1.id = t.GOOD_TYPE and c1.DELETED = 0")
                .leftJoin("category c2 on c2.id = c1.RELATION_ID and c2.DELETED = 0 and c2.TYPE = 3")
                .eq(Orders::getAcceptDriver, driver.getId())
                .eq(Orders::getType, Constants.ONE)
                .eq(Orders::getStatus, dto.getStatus())
                .eq(Objects.nonNull(dto.getStatus()),Orders::getStatus, dto.getStatus())
                .eq(Orders::getDeleted, Constants.ZERO)
                .orderByAsc(Orders::getAcceptTime);
@@ -1107,6 +1113,7 @@
                .select("s2.address", Orders::getTakeShopAddress)
                .select("s2.link_phone as takeShopLinkPhone")
                .select("c2.other_field as c2OtherField")
                .select("IF(EXISTS(SELECT 1 FROM orders_detail od JOIN category c3 ON c3.id = od.LUGGAGE_ID AND c3.TYPE = 4 AND c3.OTHER_FIELD = '1' WHERE od.ORDER_ID = t.ID AND od.DELETED = 0), 1, 0) as hasOversized")
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID and s1.DELETED = 0")
                .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID and s2.DELETED = 0")
                .leftJoin("category c1 on c1.id = t.GOOD_TYPE and c1.DELETED = 0")
@@ -1164,11 +1171,17 @@
        vo.setDepositShopAddress(base.getDepositShopAddress());
        vo.setDepositDistance(base.getDepositDistance());
        vo.setTakeName(base.getTakeName());
        vo.setTakeAddress(base.getTakeAddress());
        vo.setTakeShopId(base.getTakeShopId());
        vo.setTakeDistance(base.getTakeDistance());
        vo.setContactPhone(base.getContactPhone());
        vo.setDriverFee(base.getDriverFee());
        vo.setUrgentAmount(base.getUrgentAmount());
        vo.setIsValuable(base.getIsValuable());
        vo.setGoodLevelName(base.getGoodLevelName());
        vo.setHasOversized(base.getHasOversized());
        vo.setDriverVerifyCode(base.getDriverVerifyCode());
        // 物品明细(转换类型)
        List<DriverOrderDetailVO.OrderItem> detailItems = new ArrayList<>();
@@ -1177,6 +1190,7 @@
                DriverOrderDetailVO.OrderItem item = new DriverOrderDetailVO.OrderItem();
                item.setName(src.getName());
                item.setQuantity(src.getQuantity());
                item.setIsOversized(src.getIsOversized());
                detailItems.add(item);
            }
        }
@@ -1380,11 +1394,13 @@
        // 5. 原子更新:带 status=2 条件防止并发重复抢单
        Date now = new Date();
        String driverVerifyCode = generateVerifyCode();
        int rows = ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                .set(Orders::getAcceptDriver, driverId)
                .set(Orders::getAcceptTime, now)
                .set(Orders::getAcceptType, 0) // 0=手动抢单
                .set(Orders::getStatus, Constants.OrderStatus.accepted.getStatus())
                .set(Orders::getDriverVerifyCode, driverVerifyCode)
                .set(Orders::getUpdateTime, now)
                .eq(Orders::getId, orderId)
                .eq(Orders::getStatus, Constants.TWO));
@@ -1486,6 +1502,7 @@
        log.setOptUserId(driver.getMemberId());
        log.setOptUserType(Constants.ONE);
        log.setOrderStatus(Constants.OrderStatus.delivering.getStatus());
        log.setRemark(dto.getRemark());
        log.setCreateTime(now);
        log.setDeleted(Constants.ZERO);
        orderLogMapper.insert(log);
@@ -1636,6 +1653,22 @@
        return String.format("%.1fkm", km);
    }
    /**
     * 生成6位数字核销码(Redis SETNX 保证唯一)
     */
    private String generateVerifyCode() {
        Random random = new Random();
        String redisKey = Constants.REDIS_VERIFY_CODE_KEY;
        for (int i = 0; i < 200; i++) {
            String code = String.format("%06d", random.nextInt(1000000));
            Boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey + code, "1", 24, TimeUnit.HOURS);
            if (success != null && success) {
                return code;
            }
        }
        throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "核销码生成失败,请重试");
    }
    private double haversine(double lat1, double lng1, double lat2, double lng2) {
        double R = 6371;
        double dLat = Math.toRadians(lat2 - lat1);
@@ -1697,9 +1730,12 @@
        boolean hasTakeShop = order.getTakeShopId() != null && StringUtils.isNotBlank(order.getTakeShopName());
        if (hasTakeShop) {
            vo.setTakeName(order.getTakeShopName());
            vo.setTakeAddress(order.getTakeShopAddress());
            vo.setTakeShopId(order.getTakeShopId());
            vo.setContactPhone(order.getTakeShopLinkPhone());
        } else {
            vo.setTakeName(order.getTakeLocation());
            vo.setTakeAddress(order.getTakeLocationRemark());
            vo.setContactPhone(order.getTakePhone());
        }
        if (driverLat != null && driverLng != null
@@ -1711,14 +1747,36 @@
        // 贵重物品
        vo.setIsValuable("1".equals(order.getC2OtherField()));
        vo.setGoodLevelName(order.getGoodLevelName());
        vo.setHasOversized(order.getHasOversized());
        // 待取货状态(status=3)返回司机取货码
        if (Constants.equalsInteger(order.getStatus(), Constants.THREE)||Constants.equalsInteger(order.getStatus(), Constants.FOUR)) {
            vo.setDriverVerifyCode(order.getDriverVerifyCode());
        }
        // 物品明细
        List<OrdersDetail> details = detailMap.getOrDefault(order.getId(), Collections.emptyList());
        // 批量查询涉及到的 luggageId 对应的 category,判断是否大件
        Set<Integer> luggageIds = details.stream()
                .map(OrdersDetail::getLuggageId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Set<Integer> oversizedIds = new HashSet<>();
        if (!luggageIds.isEmpty()) {
            categoryMapper.selectList(new QueryWrapper<Category>().lambda()
                    .in(Category::getId, luggageIds)
                    .eq(Category::getType, Constants.FOUR)
                    .eq(Category::getOtherField, "1")
                    .eq(Category::getDeleted, Constants.ZERO))
                    .forEach(c -> oversizedIds.add(c.getId()));
        }
        List<DriverGrabOrderVO.OrderItem> items = new ArrayList<>();
        for (OrdersDetail detail : details) {
            DriverGrabOrderVO.OrderItem item = new DriverGrabOrderVO.OrderItem();
            item.setName(detail.getLuggageName());
            item.setQuantity(detail.getNum());
            item.setIsOversized(oversizedIds.contains(detail.getLuggageId()) ? Constants.ONE : Constants.ZERO);
            items.add(item);
        }
        vo.setItems(items);
@@ -1804,7 +1862,7 @@
    }
    @Override
    public Map<String, Integer> getActiveOrderCount(Integer driverId) {
    public DriverActiveOrderCountVO getActiveOrderCount(Integer driverId) {
        // 已抢单(status=3)数量
        Long grabbed = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getAcceptDriver, driverId)
@@ -1815,10 +1873,121 @@
                .eq(Orders::getAcceptDriver, driverId)
                .eq(Orders::getStatus, Constants.OrderStatus.delivering.getStatus())
                .eq(Orders::getDeleted, Constants.ZERO));
        Map<String, Integer> result = new HashMap<>();
        result.put("grabbedCount", grabbed != null ? grabbed.intValue() : 0);
        result.put("deliveringCount", delivering != null ? delivering.intValue() : 0);
        return result;
        DriverActiveOrderCountVO vo = new DriverActiveOrderCountVO();
        vo.setGrabbedCount(grabbed != null ? grabbed.intValue() : 0);
        vo.setDeliveringCount(delivering != null ? delivering.intValue() : 0);
        return vo;
    }
    @Override
    public PageData<DriverGrabOrderVO> driverOrderPage(Integer driverId, PageWrap<DriverOrderPageDTO> pageWrap) {
        DriverInfo driver = driverInfoMapper.selectById(driverId);
        if (driver == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在");
        }
        DriverOrderPageDTO model = pageWrap.getModel();
        Integer status = model != null ? model.getStatus() : null;
        // 合法状态校验
        List<Integer> validStatuses = Arrays.asList(
                Constants.OrderStatus.accepted.getStatus(),
                Constants.OrderStatus.delivering.getStatus(),
                Constants.OrderStatus.finished.getStatus());
        if (status != null && !validStatuses.contains(status)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "状态只能为3(待取件)、4(配送中)、7(已完成)");
        }
        IPage<Orders> p = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        MPJLambdaWrapper<Orders> wrapper = new MPJLambdaWrapper<>();
        wrapper.selectAll(Orders.class)
                .select("s1.name", Orders::getDepositShopName)
                .select("s1.address", Orders::getDepositShopAddress)
                .select("s2.name", Orders::getTakeShopName)
                .select("s2.address", Orders::getTakeShopAddress)
                .select("s2.link_phone as takeShopLinkPhone")
                .select("c2.other_field as c2OtherField")
                .select("c2.name as goodLevelName")
                .select("IF(EXISTS(SELECT 1 FROM orders_detail od JOIN category c3 ON c3.id = od.LUGGAGE_ID AND c3.TYPE = 4 AND c3.OTHER_FIELD = '1' WHERE od.ORDER_ID = t.ID AND od.DELETED = 0), 1, 0) as hasOversized")
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID and s1.DELETED = 0")
                .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID and s2.DELETED = 0")
                .leftJoin("category c1 on c1.id = t.GOOD_TYPE and c1.DELETED = 0")
                .leftJoin("category c2 on c2.id = c1.RELATION_ID and c2.DELETED = 0 and c2.TYPE = 3")
                .eq(Orders::getAcceptDriver, driverId)
                .in(status == null, Orders::getStatus, validStatuses)
                .eq(status != null, Orders::getStatus, status)
                .eq(Orders::getDeleted, Constants.ZERO)
                .orderByDesc(Orders::getAcceptTime);
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
        List<DriverGrabOrderVO> voList = new ArrayList<>();
        if (orderPage != null && orderPage.getRecords() != null) {
            // 批量查物品明细
            List<Integer> orderIds = orderPage.getRecords().stream().map(Orders::getId).collect(Collectors.toList());
            Map<Integer, List<OrdersDetail>> detailMap = new HashMap<>();
            if (!orderIds.isEmpty()) {
                List<OrdersDetail> allDetails = ordersDetailMapper.selectList(
                        new QueryWrapper<OrdersDetail>().lambda()
                                .in(OrdersDetail::getOrderId, orderIds));
                for (OrdersDetail d : allDetails) {
                    detailMap.computeIfAbsent(d.getOrderId(), k -> new ArrayList<>()).add(d);
                }
            }
            Double driverLat = driver.getLatitude();
            Double driverLng = driver.getLongitude();
            Date now = new Date();
            for (Orders order : orderPage.getRecords()) {
                boolean needDepositDist = Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.accepted.getStatus());
                voList.add(buildDriverOrderVO(order, driverLat, driverLng, needDepositDist, now, detailMap));
            }
        }
        IPage<DriverGrabOrderVO> vPage = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        PageData<DriverGrabOrderVO> pageData = PageData.from(vPage);
        pageData.setRecords(voList);
        if (orderPage != null) {
            pageData.setTotal(orderPage.getTotal());
            pageData.setPage(orderPage.getCurrent());
            pageData.setCapacity(orderPage.getSize());
        }
        return pageData;
    }
    @Override
    public DriverCancelLimitVO getTodayCancelLimit(Integer driverId) {
        DriverInfo driver = driverInfoMapper.selectById(driverId);
        if (driver == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在");
        }
        // 每日取消上限
        String limitStr = operationConfigBiz.getConfig().getDriverDailyCancelLimit();
        int limit = 3;
        if (StringUtils.isNotBlank(limitStr)) {
            try { limit = Integer.parseInt(limitStr); } catch (NumberFormatException ignored) {}
        }
        // 今日已取消次数
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        Date todayStart = cal.getTime();
        Long todayCancelCount = orderLogMapper.selectCount(new QueryWrapper<OrderLog>().lambda()
                .eq(OrderLog::getOptUserId, driver.getMemberId())
                .eq(OrderLog::getObjType, Constants.OrderLogType.driverCancel.getStatus())
                .eq(OrderLog::getOptUserType, Constants.ONE)
                .ge(OrderLog::getCreateTime, todayStart));
        int used = todayCancelCount != null ? todayCancelCount.intValue() : 0;
        DriverCancelLimitVO vo = new DriverCancelLimitVO();
        vo.setLimit(limit);
        vo.setUsed(used);
        vo.setRemain(Math.max(limit - used, 0));
        return vo;
    }
}