doum
8 小时以前 59b1f0e9967902aa10f5e017d5a0bdfd1b60c9ea
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.doumee.biz.system.impl.AreasBizImpl;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
@@ -22,27 +23,21 @@
import com.doumee.dao.business.ShopInfoMapper;
import com.doumee.dao.business.OrdersDetailMapper;
import com.doumee.dao.business.RevenueMapper;
import com.doumee.dao.business.model.*;
import com.doumee.service.business.AreasService;
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.biz.system.OperationConfigBiz;
import com.doumee.dao.business.OrderLogMapper;
import com.doumee.dao.business.model.Category;
import com.doumee.dao.business.model.DriverInfo;
import com.doumee.dao.business.model.OrderLog;
import com.doumee.dao.business.model.OrderComment;
import com.doumee.dao.business.model.ShopInfo;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.model.Multifile;
import com.doumee.dao.business.model.Smsrecord;
import com.doumee.dao.business.model.Orders;
import com.doumee.dao.business.model.OrdersDetail;
import com.doumee.dao.business.model.Revenue;
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.system.model.SystemUser;
import com.doumee.service.system.SystemUserService;
import com.doumee.dao.vo.DriverOrderDetailVO;
import com.doumee.core.utils.aliyun.AliSmsService;
import com.doumee.dao.business.model.Notice;
import com.doumee.service.business.DriverInfoService;
import com.doumee.service.business.NoticeService;
import com.alibaba.fastjson.JSONObject;
@@ -57,6 +52,7 @@
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -118,6 +114,12 @@
    @Autowired
    private NoticeService noticeService;
    @Autowired
    private AreasBizImpl areasBiz;
    @Autowired
    private SystemUserService systemUserService;
    /**
     * 发送订单站内信通知
@@ -273,13 +275,7 @@
                .select(" ( select ifnull(sum(r.OPT_TYPE * r.AMOUNT),0) from revenue r where r.MEMBER_TYPE = 1 and r.MEMBER_ID= t.id and r.VAILD_STATUS = 1 ) as memberAmount ")
                .selectAs(Category::getName,DriverInfo::getCarTypeName)
                .leftJoin(Category.class, Category::getId,DriverInfo::getCarType);
        for (PageWrap.SortData sortData : pageWrap.getSorts()) {
            if (sortData.getDirection().equalsIgnoreCase(PageWrap.DESC)) {
                queryWrapper.orderByDesc(sortData.getProperty());
            } else {
                queryWrapper.orderByAsc(sortData.getProperty());
            }
        }
        queryWrapper.orderByDesc(DriverInfo::getId);
        PageData<DriverInfo> pageData = PageData.from(driverInfoMapper.selectPage(page, queryWrapper));
        for (DriverInfo d : pageData.getRecords()) {
            d.setGender(Constants.getGenderByIdCard(d.getIdcard()));
@@ -304,7 +300,7 @@
        String code = RandomStringUtils.randomNumeric(6);
        // 发送短信
        String templateParam = "{\"code\":\"" + code + "\"}";
        AliSmsService.sendSms(telephone, "SMS_491325122", templateParam);
        AliSmsService.sendSms(telephone, Constants.SmsNotify.VERIFY_CODE.getTemplateCode(), templateParam);
        // 保存短信记录
        Smsrecord smsrecord = new Smsrecord();
        smsrecord.setDeleted(Constants.ZERO);
@@ -383,7 +379,7 @@
            member.setUpdateTime(now);
            member.setTelephone(telephone);
            member.setNickName(telephone.substring(0, 3) + "****" + telephone.substring(7));
            member.setName(telephone);
            member.setName(member.getNickName());
            member.setUserType(Constants.ONE);
            member.setBusinessStatus(Constants.ZERO);
            member.setPassword(secure.encryptPassword(defaultPassword, salt));
@@ -401,13 +397,15 @@
            // 创建司机基础信息
            DriverInfo driverInfo = new DriverInfo();
            driverInfo.setId(member.getId());
            driverInfo.setDeleted(Constants.ZERO);
            driverInfo.setCreateTime(now);
            driverInfo.setUpdateTime(now);
            driverInfo.setTelephone(telephone);
            driverInfo.setName(member.getNickName());
            driverInfo.setMemberId(member.getId());
            driverInfo.setStatus(Constants.ZERO);
            driverInfo.setAuditStatus(null);
            driverInfo.setAuditStatus(99);
            driverInfoMapper.insert(driverInfo);
        }
@@ -481,7 +479,7 @@
        }
        // 状态校验:auditStatus=null(未提交)或auditStatus=2(审批驳回)可提交认证
        if (driverInfo.getAuditStatus() != null
                && !Constants.equalsInteger(driverInfo.getAuditStatus(), Constants.TWO)) {
                && !(Constants.equalsInteger(driverInfo.getAuditStatus(), Constants.TWO)||Constants.equalsInteger(driverInfo.getAuditStatus(), 99))) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前状态不允许提交认证");
        }
        // 根据车辆类型判断是否需要驾驶证
@@ -531,6 +529,7 @@
                .set(DriverInfo::getAliAccount, request.getAliAccount())
                .set(DriverInfo::getAliName, request.getAliName())
                .set(DriverInfo::getUpdateTime, now)
                .set(DriverInfo::getAuditStatus, Constants.ZERO)
                .set(DriverInfo::getAuditRemark, null)
                .set(DriverInfo::getAuditTime, null)
                .eq(DriverInfo::getId, driverInfo.getId()));
@@ -561,18 +560,39 @@
    @Override
    public DriverInfo getVerifyDetail(Integer memberId) {
        DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getMemberId, memberId)
                .eq(DriverInfo::getId, memberId)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (Objects.isNull(driverInfo)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 拼接图片前缀
        String imgPrefix = "";
        try {
            imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.OSS, Constants.DRIVER_FILES).getCode();
        } catch (Exception e) {
            // 未配置时忽略
        }
        driverInfo.setImgPrefix(imgPrefix);
        // 查询车辆类型名称和是否需要驾驶证
        if (driverInfo.getCarType() != null) {
            Category category = categoryMapper.selectById(driverInfo.getCarType());
            if (Objects.nonNull(category)) {
                driverInfo.setCarTypeName(category.getName());
                driverInfo.setNeedLicense(Constants.equalsInteger(Integer.valueOf(category.getOtherField()), Constants.ONE) ? Constants.ONE : Constants.ZERO);
            }
        }
        // 查询省市区信息
        if (driverInfo.getAreaId() != null) {
            Areas district = areasBiz.resolveArea(driverInfo.getAreaId());
            if (district != null) {
                driverInfo.setDistrictId(district.getId());
                driverInfo.setDistrictName(district.getName());
                driverInfo.setCityId(district.getCityId());
                driverInfo.setCityName(district.getCityName());
                driverInfo.setProvinceId(district.getProvinceId());
                driverInfo.setProvinceName(district.getProvinceName());
            }
        }
        // 查询照片列表
@@ -613,7 +633,7 @@
        // 审批结果:auditDTO.auditStatus 0=通过→auditStatus=1,1=拒绝→auditStatus=2
        Integer newAuditStatus;
        if (Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ZERO)) {
            newAuditStatus = Constants.ONE;  // 审批通过
            newAuditStatus = Constants.THREE;  // 审批通过
            // 审批通过时司机定级为必填
            if (auditDTO.getDriverLevel() == null || auditDTO.getDriverLevel() < 1 || auditDTO.getDriverLevel() > 5) {
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "审批通过时必须填写司机定级");
@@ -693,6 +713,17 @@
                } else if (Constants.equalsInteger(mf.getObjType(), 8)) {
                    driverInfo.getOtherImgList().add(mf);
                }
            }
        }
        // 查询审批人名称
        if (driverInfo.getAuditUser() != null) {
            try {
                SystemUser auditUser = systemUserService.findById(driverInfo.getAuditUser());
                if (auditUser != null) {
                    driverInfo.setAuditUserName(auditUser.getRealname());
                }
            } catch (Exception e) {
                // 审批人已删除等异常忽略
            }
        }
        return driverInfo;
@@ -790,8 +821,12 @@
        vo.setImgUrl(driver.getImgurl());
        vo.setCarCode(driver.getCarCode());
        vo.setScore(driver.getScore() != null ? driver.getScore().toPlainString() : "0");
        vo.setDriverLevel(driver.getDriverLevel());
        vo.setDriverLevelName(Constants.getDriverLevelName(driver.getDriverLevel()));
        vo.setAuditStatus(driver.getAuditStatus());
        vo.setAuditRemark(driver.getAuditRemark());
        vo.setBalance(driver.getBalance() != null ? driver.getBalance() : 0L);
        vo.setAcceptingStatus(driver.getAcceptingStatus());
        // 头像全路径
        if (StringUtils.isNotBlank(driver.getImgurl())) {
            String imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
@@ -799,7 +834,7 @@
            vo.setFullImgUrl(imgPrefix + driver.getImgurl());
        }
        // 今日预计佣金:revenue表中今天的收入记录金额之和
        // 今日预计佣金:今日接单的司机佣金 + 平台奖励金
        Date now = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(now);
@@ -809,22 +844,22 @@
        cal.set(Calendar.MILLISECOND, 0);
        Date todayStart = cal.getTime();
        QueryWrapper<Revenue> revenueWrapper = new QueryWrapper<>();
        revenueWrapper.lambda()
                .eq(Revenue::getMemberId, memberId)
                .eq(Revenue::getMemberType, Constants.ONE)
                .eq(Revenue::getOptType, Constants.ONE)
                .eq(Revenue::getDeleted, Constants.ZERO)
                .ge(Revenue::getCreateTime, todayStart);
        revenueWrapper.select("IFNULL(SUM(AMOUNT),0) as amount");
        Revenue sumResult = revenueMapper.selectOne(revenueWrapper);
        vo.setTodayCommission(sumResult != null && sumResult.getAmount() != null ? sumResult.getAmount() : 0L);
        QueryWrapper<Orders> commissionWrapper = new QueryWrapper<>();
        commissionWrapper.lambda()
                .eq(Orders::getAcceptDriver, driver.getId())
                .eq(Orders::getDeleted, Constants.ZERO)
                .ge(Orders::getAcceptTime, todayStart);
        commissionWrapper.select("IFNULL(SUM(DRIVER_FEE),0) as driverFee", "IFNULL(SUM(PLATFORM_REWARD_AMOUNT),0) as platformRewardAmount");
        Orders commissionResult = ordersMapper.selectOne(commissionWrapper);
        long driverFee = commissionResult != null && commissionResult.getDriverFee() != null ? commissionResult.getDriverFee() : 0L;
        long rewardAmount = commissionResult != null && commissionResult.getPlatformRewardAmount() != null ? commissionResult.getPlatformRewardAmount() : 0L;
        vo.setTodayCommission(driverFee + rewardAmount);
        // 今日接单数:今天完成的订单数(acceptDriver=司机主键,状态=已完成)
        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)
@@ -840,6 +875,54 @@
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getStatus, Constants.OrderStatus.delivering.getStatus()));
        vo.setWaitDeliverCount(waitDeliverCount.intValue());
        return vo;
    }
    @Override
    public com.doumee.dao.vo.DriverStatsVO getDriverStats(Integer memberId) {
        DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getId, memberId)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (driver == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在");
        }
        com.doumee.dao.vo.DriverStatsVO vo = new com.doumee.dao.vo.DriverStatsVO();
        // 累计佣金:type=0(完成订单) + vaildStatus=1(已入账)
        QueryWrapper<Revenue> totalWrapper = new QueryWrapper<>();
        totalWrapper.lambda()
                .eq(Revenue::getMemberId, memberId)
                .eq(Revenue::getMemberType, Constants.ONE)
                .eq(Revenue::getType, Constants.ZERO)
                .eq(Revenue::getVaildStatus, Constants.ONE)
                .eq(Revenue::getDeleted, Constants.ZERO);
        totalWrapper.select("IFNULL(SUM(AMOUNT),0) as amount");
        Revenue totalResult = revenueMapper.selectOne(totalWrapper);
        vo.setTotalCommission(totalResult != null && totalResult.getAmount() != null ? totalResult.getAmount() : 0L);
        // 待结算佣金:type=0(完成订单) + vaildStatus=0(入账中)
        QueryWrapper<Revenue> pendingWrapper = new QueryWrapper<>();
        pendingWrapper.lambda()
                .eq(Revenue::getMemberId, memberId)
                .eq(Revenue::getMemberType, Constants.ONE)
                .eq(Revenue::getType, Constants.ZERO)
                .eq(Revenue::getVaildStatus, Constants.ZERO)
                .eq(Revenue::getDeleted, Constants.ZERO);
        pendingWrapper.select("IFNULL(SUM(AMOUNT),0) as amount");
        Revenue pendingResult = revenueMapper.selectOne(pendingWrapper);
        vo.setPendingCommission(pendingResult != null && pendingResult.getAmount() != null ? pendingResult.getAmount() : 0L);
        // 订单总数
        Long totalOrderCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getAcceptDriver, driver.getId())
                .eq(Orders::getDeleted, Constants.ZERO));
        vo.setTotalOrderCount(totalOrderCount.intValue());
        // 钱包余额
        vo.setBalance(driver.getBalance() != null ? driver.getBalance() : 0L);
        return vo;
    }
@@ -876,10 +959,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());
@@ -894,6 +975,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.detail = '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")
@@ -993,13 +1077,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);
@@ -1034,10 +1120,13 @@
        wrapper.selectAll(Orders.class)
                .select("s1.name", Orders::getDepositShopName)
                .select("s1.address", Orders::getDepositShopAddress)
                .select("s1.telephone", Orders::getDepositShopLinkPhone)
                .select("s2.name", Orders::getTakeShopName)
                .select("s2.address", Orders::getTakeShopAddress)
                .select("s2.link_phone as takeShopLinkPhone")
                .select("s2.telephone", Orders::getTakeShopLinkPhone)
                .select("c2.other_field as c2OtherField")
                .select("c1.name as goodTypeName")
                .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")
@@ -1095,11 +1184,29 @@
        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.setDepositShopPhone(order.getDepositShopLinkPhone());
        if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.delivering.getStatus())
                || Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) {
            if (order.getTakeShopId() != null && StringUtils.isNotBlank(order.getTakeShopLinkPhone())) {
                vo.setTakeContactPhone(order.getTakeShopLinkPhone());
            } else {
                vo.setTakeContactPhone(order.getTakePhone());
            }
        }
        vo.setDriverFee(base.getDriverFee());
        vo.setUrgentAmount(base.getUrgentAmount());
        vo.setPlatformRewardAmount(order.getPlatformRewardAmount());
        vo.setIsValuable(base.getIsValuable());
        vo.setGoodLevelName(base.getGoodLevelName());
        vo.setGoodTypeName(order.getGoodTypeName());
        vo.setHasOversized(base.getHasOversized());
        vo.setDriverVerifyCode(base.getDriverVerifyCode());
        // 物品明细(转换类型)
        List<DriverOrderDetailVO.OrderItem> detailItems = new ArrayList<>();
@@ -1108,6 +1215,7 @@
                DriverOrderDetailVO.OrderItem item = new DriverOrderDetailVO.OrderItem();
                item.setName(src.getName());
                item.setQuantity(src.getQuantity());
                item.setIsOversized(src.getIsOversized());
                detailItems.add(item);
            }
        }
@@ -1116,6 +1224,11 @@
        // 详情特有字段
        vo.setStatus(order.getStatus());
        vo.setStatusDesc(getStatusDesc(order.getStatus()));
        vo.setCreateTime(order.getCreateTime());
        vo.setAcceptTime(order.getAcceptTime());
        vo.setDriverTakeTime(order.getDriverTakeTime());
        vo.setFinishTime(order.getFinishTime());
        vo.setRemark(order.getRemark());
        // 客户信息
        String customerInfo = "";
@@ -1128,52 +1241,27 @@
        vo.setCustomerInfo(customerInfo);
        // 导航经纬度(使用Orders自带坐标)
        if (Constants.equalsInteger(order.getStatus(), Constants.TWO)) {
        if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.accepted.getStatus())) {
            if (order.getDepositLgt() != null && order.getDepositLat() != null) {
                vo.setNavigateLat(order.getDepositLgt().doubleValue());
                vo.setNavigateLng(order.getDepositLat().doubleValue());
            }
        } else {
        } else if(Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.delivering.getStatus())){
            if (order.getTakeLgt() != null && order.getTakeLat() != null) {
                vo.setNavigateLat(order.getTakeLgt().doubleValue());
                vo.setNavigateLng(order.getTakeLat().doubleValue());
            }
        }
        // 异地寄存实时经纬度(按状态返回)
        if (Constants.ONE.equals(order.getType())) {
            // 司机自身经纬度
            vo.setDriverLng(driverLng);
            vo.setDriverLat(driverLat);
            // status=3(已接单):返回存件门店经纬度
            if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.accepted.getStatus())) {
                if (order.getDepositShopId() != null) {
                    ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
                    if (depositShop != null) {
                        vo.setDepositShopLng(depositShop.getLongitude());
                        vo.setDepositShopLat(depositShop.getLatitude());
                    }
                }
            }
            // status=4(配送中):返回取件点经纬度
            if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.delivering.getStatus())) {
                if (order.getTakeShopId() != null) {
                    ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
                    if (takeShop != null) {
                        vo.setTakeLng(takeShop.getLongitude());
                        vo.setTakeLat(takeShop.getLatitude());
                    }
                } else if (order.getTakeLgt() != null && order.getTakeLat() != null) {
                    vo.setTakeLng(order.getTakeLgt().doubleValue());
                    vo.setTakeLat(order.getTakeLat().doubleValue());
                }
            }
        }
        vo.setDepositShopLng(order.getDepositLgt().doubleValue());
        vo.setDepositShopLat(order.getDepositLat().doubleValue());
        vo.setTakeLng(order.getTakeLgt().doubleValue());
        vo.setTakeLat(order.getTakeLat().doubleValue());
        vo.setDriverLng(driverLng);
        vo.setDriverLat(driverLat);
        // 下单附件图片
        String imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                + systemDictDataBiz.queryByCode(Constants.OSS, Constants.MEMBER_FILES).getCode();
                + systemDictDataBiz.queryByCode(Constants.OSS, Constants.ORDERS_FILES).getCode();
        vo.setOrderImages(getFileUrls(orderId, Constants.FileType.ORDER_FILE.getKey(), imgPrefix));
        // 评价信息
@@ -1214,7 +1302,7 @@
        if (!Constants.ONE.equals(order.getType())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单可取消");
        }
        if (!Constants.TWO.equals(order.getStatus())) {
        if (!Constants.equalsInteger(Constants.OrderStatus.accepted.getStatus(), order.getStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许取消");
        }
        if (!driverId.equals(order.getAcceptDriver())) {
@@ -1247,6 +1335,7 @@
                .set(Orders::getAcceptDriver, null)
                .set(Orders::getAcceptTime, null)
                .set(Orders::getAcceptType, null)
                .set(Orders::getStatus, Constants.OrderStatus.deposited.getStatus())
                .eq(Orders::getId, orderId));
        // 5. 写入取消日志
@@ -1311,11 +1400,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));
@@ -1338,12 +1429,14 @@
        // 7. 通知会员:司机已抢单
        sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_PICKUP_GRABBED, orderId,
                "orderNo", order.getCode(),
                "driverName", driver.getName());
        // 通知存件门店:订单已抢单待取件
        if (order.getDepositShopId() != null) {
            sendShopNotice(order.getDepositShopId(), Constants.ShopOrderNotify.WAIT_PICKUP, orderId,
                    "orderNo", order.getCode());
                    "orderNo", order.getCode(),
                    "name", driver.getName());
        }
        // 通知司机:抢单成功
@@ -1405,6 +1498,7 @@
        // 4. 更新订单状态为派送中(4)
        ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                .set(Orders::getStatus, Constants.OrderStatus.delivering.getStatus())
                .set(Orders::getTakeTime, now)
                .set(Orders::getUpdateTime, now)
                .eq(Orders::getId, orderId));
@@ -1417,6 +1511,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);
@@ -1567,6 +1662,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);
@@ -1613,14 +1724,17 @@
        vo.setIsUrgent(order.getIsUrgent());
        vo.setDriverFee(order.getDriverFee());
        vo.setUrgentAmount(order.getUrgentAmount());
        vo.setPlatformRewardAmount(order.getPlatformRewardAmount());
        // 存件门店(使用Orders自带坐标)
        vo.setDepositShopName(order.getDepositShopName());
        vo.setDepositShopAddress(order.getDepositShopAddress());
        vo.setDepositLng(order.getDepositLgt() != null ? order.getDepositLgt().doubleValue() : null);
        vo.setDepositLat(order.getDepositLat() != null ? order.getDepositLat().doubleValue() : null);
        if (needDepositDist && driverLat != null && driverLng != null
                && order.getDepositLgt() != null && order.getDepositLat() != null) {
            double distKm = haversine(driverLat, driverLng,
                    order.getDepositLgt().doubleValue(), order.getDepositLat().doubleValue());
            double distKm = haversine(driverLat, driverLng, order.getDepositLat().doubleValue(),
                    order.getDepositLgt().doubleValue());
            vo.setDepositDistance(formatDistance(distKm));
        }
@@ -1628,28 +1742,60 @@
        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());
        }
        vo.setTakeLng(order.getTakeLgt() != null ? order.getTakeLgt().doubleValue() : null);
        vo.setTakeLat(order.getTakeLat() != null ? order.getTakeLat().doubleValue() : null);
        if (driverLat != null && driverLng != null
                && order.getTakeLgt() != null && order.getTakeLat() != null) {
            double takeDist = haversine(driverLat, driverLng,
                    order.getTakeLgt().doubleValue(), order.getTakeLat().doubleValue());
            double takeDist = haversine(driverLat, driverLng, order.getTakeLat().doubleValue(),
                    order.getTakeLgt().doubleValue());
            vo.setTakeDistance(formatDistance(takeDist));
        }
        // 贵重物品
        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());
        }
        // 订单状态
        vo.setStatus(order.getStatus());
        vo.setStatusDesc(getStatusDesc(order.getStatus()));
        vo.setCreateTime(order.getCreateTime());
        // 物品明细
        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);
@@ -1670,19 +1816,16 @@
            for (int i = 0; i < paramPairs.length - 1; i += 2) {
                templateParam.put(paramPairs[i], paramPairs[i + 1]);
            }
            boolean result = AliSmsService.sendSms(phone, smsNotify.getTemplateCode(),
            String error = AliSmsService.sendSms(phone, smsNotify.getTemplateCode(),
                    templateParam.toJSONString());
            if (result) {
                log.info("短信发送成功: phone={}, template={}", phone, smsNotify.name());
            } else {
                log.warn("短信发送失败: phone={}, template={}", phone, smsNotify.name());
            }
            // 存储短信记录
            Smsrecord record = new Smsrecord();
            record.setPhone(phone);
            record.setContent(content);
            record.setType(Constants.ONE); // 1=订单通知
            record.setStatus(result ? Constants.ONE : Constants.ZERO);
            record.setType(Constants.ONE);
            record.setStatus(error == null ? Constants.ONE : Constants.ZERO);
            if (error != null) {
                record.setRemark(error);
            }
            record.setCreateTime(new Date());
            record.setDeleted(Constants.ZERO);
            smsrecordMapper.insert(record);
@@ -1694,6 +1837,7 @@
                record.setContent(content);
                record.setType(Constants.ONE);
                record.setStatus(Constants.ZERO);
                record.setRemark(e.getMessage());
                record.setCreateTime(new Date());
                record.setDeleted(Constants.ZERO);
                smsrecordMapper.insert(record);
@@ -1701,4 +1845,197 @@
        }
    }
    @Override
    public void changePassword(Integer driverId, String oldPassword, String newPassword, String token) {
        if (StringUtils.isBlank(oldPassword)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "旧密码不能为空");
        }
        if (StringUtils.isBlank(newPassword)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "新密码不能为空");
        }
        // 校验新密码必须同时包含字母和数字
        boolean hasLetter = newPassword.chars().anyMatch(Character::isLetter);
        boolean hasDigit = newPassword.chars().anyMatch(Character::isDigit);
        if (!hasLetter || !hasDigit) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "密码必须同时包含字母和数字");
        }
        // 查询司机对应的会员
        DriverInfo driverInfo = driverInfoMapper.selectById(driverId);
        if (driverInfo == null || driverInfo.getMemberId() == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        Member member = memberMapper.selectById(driverInfo.getMemberId());
        if (member == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 校验旧密码
        String oldEncryptPwd = secure.encryptPassword(oldPassword, member.getSalt());
        if (!oldEncryptPwd.equals(member.getPassword())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "旧密码错误");
        }
        // 加密新密码并更新
        String salt = RandomStringUtils.randomAlphanumeric(6);
        String encryptPwd = secure.encryptPassword(newPassword, salt);
        memberMapper.update(new UpdateWrapper<Member>().lambda()
                .set(Member::getPassword, encryptPwd)
                .set(Member::getSalt, salt)
                .eq(Member::getId, member.getId()));
        // 清除token,强制重新登录
        if (StringUtils.isNotBlank(token)) {
            redisTemplate.delete(token);
        }
    }
    @Override
    public DriverActiveOrderCountVO getActiveOrderCount(Integer driverId) {
        // 已抢单(status=3)数量
        Long grabbed = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getAcceptDriver, driverId)
                .eq(Orders::getStatus, Constants.OrderStatus.accepted.getStatus())
                .eq(Orders::getDeleted, Constants.ZERO));
        // 派送中(status=4)数量
        Long delivering = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getAcceptDriver, driverId)
                .eq(Orders::getStatus, Constants.OrderStatus.delivering.getStatus())
                .eq(Orders::getDeleted, Constants.ZERO));
        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.arrived.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;
    }
    @Override
    public boolean checkDriverToken(String token) {
        if (StringUtils.isBlank(token) || !token.startsWith(Constants.ONE + "")) {
            return false;
        }
        String tokenRedis = (String) redisTemplate.opsForValue().get(Constants.REDIS_TOKEN_KEY + token);
        if (StringUtils.isBlank(tokenRedis)) {
            return false;
        }
        try {
            int lastIndex = token.lastIndexOf("_") + 1;
            Integer memberId = Integer.valueOf(token.substring(lastIndex));
            Member member = memberMapper.selectById(memberId);
            return member != null
                    && !Constants.ONE.equals(member.getDeleted())
                    && !Constants.ONE.equals(member.getStatus())
                    && Constants.ONE.equals(member.getUserType());
        } catch (Exception e) {
            return false;
        }
    }
}