package com.doumee.service.business.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 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.core.constants.Constants; import com.doumee.core.constants.ResponseStatus; import com.doumee.core.exception.BusinessException; import com.doumee.core.model.PageData; import com.doumee.core.model.PageWrap; import com.doumee.config.jwt.JwtTokenUtil; import com.doumee.core.utils.Secure; import com.doumee.core.utils.Utils; import com.doumee.dao.business.DriverInfoMapper; import com.doumee.dao.business.MemberMapper; import com.doumee.dao.business.MultifileMapper; import com.doumee.dao.business.SmsrecordMapper; import com.doumee.dao.business.CategoryMapper; import com.doumee.dao.business.OrdersMapper; import com.doumee.dao.business.OrdersDetailMapper; import com.doumee.dao.business.RevenueMapper; 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.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.vo.AccountResponse; import com.doumee.dao.vo.DriverCenterVO; import com.doumee.dao.vo.DriverGrabOrderVO; import com.doumee.dao.vo.DriverOrderDetailVO; import com.doumee.dao.dto.AuditDTO; import com.doumee.dao.dto.ChangeStatusDTO; import com.doumee.dao.dto.DriverLoginRequest; import com.doumee.dao.dto.DriverRegisterRequest; import com.doumee.dao.dto.DriverVerifyRequest; import com.doumee.dao.dto.DriverActiveOrderDTO; import com.doumee.dao.dto.DriverGrabOrderDTO; import com.doumee.core.utils.aliyun.AliSmsService; import com.doumee.service.business.DriverInfoService; import com.alibaba.fastjson.JSONObject; import com.github.yulichang.wrapper.MPJLambdaWrapper; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.util.*; import java.util.stream.Collectors; /** * 司机注册信息Service实现 * @author rk * @date 2026/04/08 */ @Service public class DriverInfoServiceImpl implements DriverInfoService { @Autowired private DriverInfoMapper driverInfoMapper; @Autowired private MemberMapper memberMapper; @Autowired private SmsrecordMapper smsrecordMapper; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private Secure secure; @Autowired private RedisTemplate redisTemplate; @Autowired private MultifileMapper multifileMapper; @Autowired private CategoryMapper categoryMapper; @Autowired private OrdersMapper ordersMapper; @Autowired private RevenueMapper revenueMapper; @Autowired private OrdersDetailMapper ordersDetailMapper; @Autowired private SystemDictDataBiz systemDictDataBiz; @Autowired private OrderLogMapper orderLogMapper; @Autowired private OperationConfigBiz operationConfigBiz; @Override public Integer create(DriverInfo driverInfo) { driverInfoMapper.insert(driverInfo); return driverInfo.getId(); } @Override public void deleteById(Integer id) { driverInfoMapper.deleteById(id); } @Override public void delete(DriverInfo driverInfo) { UpdateWrapper deleteWrapper = new UpdateWrapper<>(driverInfo); driverInfoMapper.delete(deleteWrapper); } @Override public void deleteByIdInBatch(List ids) { if (CollectionUtils.isEmpty(ids)) { return; } driverInfoMapper.deleteBatchIds(ids); } @Override public void updateById(DriverInfo driverInfo) { driverInfoMapper.updateById(driverInfo); } @Override public void updateByIdInBatch(List driverInfos) { if (CollectionUtils.isEmpty(driverInfos)) { return; } for (DriverInfo driverInfo : driverInfos) { this.updateById(driverInfo); } } @Override public DriverInfo findById(Integer id) { DriverInfo driverInfo = driverInfoMapper.selectById(id); if (Objects.isNull(driverInfo)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } return driverInfo; } @Override public DriverInfo findOne(DriverInfo driverInfo) { QueryWrapper wrapper = new QueryWrapper<>(driverInfo); return driverInfoMapper.selectOne(wrapper); } @Override public List findList(DriverInfo driverInfo) { QueryWrapper wrapper = new QueryWrapper<>(driverInfo); return driverInfoMapper.selectList(wrapper); } @Override public PageData findPage(PageWrap pageWrap) { IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); MPJLambdaWrapper queryWrapper = new MPJLambdaWrapper<>(); Utils.MP.blankToNull(pageWrap.getModel()); pageWrap.getModel().setDeleted(Constants.ZERO); // 司机姓名/手机号(关键字模糊查询) if (StringUtils.isNotBlank(pageWrap.getModel().getKeyword())) { queryWrapper.and(w -> w .like(DriverInfo::getName, pageWrap.getModel().getKeyword()) .or() .like(DriverInfo::getTelephone, pageWrap.getModel().getKeyword())); } // 车牌号 if (StringUtils.isNotBlank(pageWrap.getModel().getCarCode())) { queryWrapper.like(DriverInfo::getCarCode, pageWrap.getModel().getCarCode()); } // 状态 if (pageWrap.getModel().getStatus() != null) { queryWrapper.eq(DriverInfo::getStatus, pageWrap.getModel().getStatus()); } // 审批状态 if (pageWrap.getModel().getAuditStatus() != null) { queryWrapper.eq(DriverInfo::getAuditStatus, pageWrap.getModel().getAuditStatus()); } // 创建日期范围 if (pageWrap.getModel().getCreateTimeStart() != null) { queryWrapper.ge(DriverInfo::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateTimeStart())); } if (pageWrap.getModel().getCreateTimeEnd() != null) { queryWrapper.le(DriverInfo::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateTimeEnd())); } // 子查询:余额 queryWrapper.selectAll(DriverInfo.class) .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()); } } PageData pageData = PageData.from(driverInfoMapper.selectPage(page, queryWrapper)); for (DriverInfo d : pageData.getRecords()) { d.setGender(Constants.getGenderByIdCard(d.getIdcard())); } return pageData; } @Override public long count(DriverInfo driverInfo) { QueryWrapper wrapper = new QueryWrapper<>(driverInfo); return driverInfoMapper.selectCount(wrapper); } /***************司机验证码登录业务*************/ @Override public void sendRegisterCode(String telephone) { if (StringUtils.isBlank(telephone)) { throw new BusinessException(ResponseStatus.BAD_REQUEST); } // 生成6位数字验证码 String code = RandomStringUtils.randomNumeric(6); // 发送短信 String templateParam = "{\"code\":\"" + code + "\"}"; AliSmsService.sendSms(telephone, "SMS_491325122", templateParam); // 保存短信记录 Smsrecord smsrecord = new Smsrecord(); smsrecord.setDeleted(Constants.ZERO); smsrecord.setCreateTime(new Date()); smsrecord.setUpdateTime(new Date()); smsrecord.setPhone(telephone); smsrecord.setContent("司机验证码登录:" + code); smsrecord.setType(Constants.ZERO); smsrecord.setCode(code); smsrecord.setStatus(Constants.ZERO); // 有效期15分钟 Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); calendar.add(Calendar.MINUTE, 15); smsrecord.setValidDate(calendar.getTime()); smsrecordMapper.insert(smsrecord); } @Override @Transactional public AccountResponse register(DriverRegisterRequest request) { String telephone = request.getTelephone(); String code = request.getCode(); if (StringUtils.isAnyBlank(telephone, code)) { throw new BusinessException(ResponseStatus.BAD_REQUEST); } // 1. 验证短信验证码 QueryWrapper smsWrapper = new QueryWrapper<>(); smsWrapper.lambda().eq(Smsrecord::getPhone, telephone) .eq(Smsrecord::getCode, code) .eq(Smsrecord::getType, Constants.ZERO) .eq(Smsrecord::getStatus, Constants.ZERO) .ge(Smsrecord::getValidDate, new Date()) .orderByDesc(Smsrecord::getCreateTime) .last("limit 1"); Smsrecord smsrecord = smsrecordMapper.selectOne(smsWrapper); if (Objects.isNull(smsrecord)) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "验证码错误或已过期"); } // 标记验证码已使用 smsrecord.setStatus(Constants.ONE); smsrecord.setUpdateTime(new Date()); smsrecordMapper.updateById(smsrecord); // 2. 查询是否已有司机账号 Member existMember = memberMapper.selectOne(new QueryWrapper().lambda() .eq(Member::getTelephone, telephone) .eq(Member::getUserType, Constants.ONE) .ne(Member::getStatus, Constants.TWO) .last("limit 1")); Date now = new Date(); Member member; if (Objects.nonNull(existMember)) { // 已有司机账号,直接登录 if (!Constants.equalsInteger(existMember.getStatus(), Constants.ZERO)) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "账号已停用,请联系管理员"); } // 更新登录信息 memberMapper.update(new UpdateWrapper().lambda() .set(Member::getLoginTime, now) .setSql("login_times = login_times + 1") .eq(Member::getId, existMember.getId())); // 更新内存中的登录信息 existMember.setLoginTime(now); existMember.setLoginTimes(existMember.getLoginTimes() + 1); member = existMember; } else { // 没有司机账号,注册新用户,默认密码为手机号后六位 String salt = RandomStringUtils.randomAlphanumeric(6); String defaultPassword = telephone.substring(telephone.length() - 6); member = new Member(); member.setDeleted(Constants.ZERO); member.setCreateTime(now); member.setUpdateTime(now); member.setTelephone(telephone); member.setNickName(telephone.substring(0, 3) + "****" + telephone.substring(7)); member.setName(telephone); member.setUserType(Constants.ONE); member.setBusinessStatus(Constants.ZERO); member.setPassword(secure.encryptPassword(defaultPassword, salt)); member.setSalt(salt); member.setAmount(Constants.ZERO.longValue()); member.setTotalAmount(Constants.ZERO.longValue()); member.setStatus(Constants.ZERO); member.setLoginTime(now); member.setLoginTimes(Constants.ONE.longValue()); member.setReceiveNum(Constants.ZERO.longValue()); member.setPublishNum(Constants.ZERO.longValue()); member.setAutoReceiveStatus(Constants.ONE); member.setUseIdentity(Constants.ZERO); memberMapper.insert(member); // 创建司机基础信息 DriverInfo driverInfo = new DriverInfo(); driverInfo.setDeleted(Constants.ZERO); driverInfo.setCreateTime(now); driverInfo.setUpdateTime(now); driverInfo.setTelephone(telephone); driverInfo.setMemberId(member.getId()); driverInfo.setStatus(Constants.ZERO); driverInfo.setAuditStatus(null); driverInfoMapper.insert(driverInfo); } // 3. 生成token返回 AccountResponse accountResponse = new AccountResponse(); accountResponse.setToken(JwtTokenUtil.generateTokenForRedis(member.getId(), Constants.ONE, JSONObject.toJSONString(member), redisTemplate)); accountResponse.setMember(member); return accountResponse; } @Override public AccountResponse login(DriverLoginRequest request) { String telephone = request.getTelephone(); String password = request.getPassword(); if (StringUtils.isAnyBlank(telephone, password)) { throw new BusinessException(ResponseStatus.BAD_REQUEST); } // 查询司机会员 Member member = memberMapper.selectOne(new QueryWrapper().lambda() .eq(Member::getTelephone, telephone) .eq(Member::getUserType, Constants.ONE) .ne(Member::getStatus, Constants.TWO) .last("limit 1")); if (Objects.isNull(member)) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "账号不存在"); } if (!Constants.equalsInteger(member.getStatus(), Constants.ZERO)) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "账号已停用,请联系管理员"); } // 校验密码 String encryptPwd = secure.encryptPassword(password, member.getSalt()); if (!encryptPwd.equals(member.getPassword())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "密码错误"); } // 更新登录信息 memberMapper.update(new UpdateWrapper().lambda() .set(Member::getLoginTime, new Date()) .setSql("login_times = login_times + 1") .eq(Member::getId, member.getId())); AccountResponse accountResponse = new AccountResponse(); accountResponse.setToken(JwtTokenUtil.generateTokenForRedis(member.getId(), Constants.ONE, JSONObject.toJSONString(member), redisTemplate)); accountResponse.setMember(member); return accountResponse; } /***************司机实名认证业务*************/ @Override @Transactional public void submitVerify(Integer memberId, DriverVerifyRequest request) { // 参数基础校验 if (StringUtils.isAnyBlank(request.getName(), request.getIdcard(), request.getLivePlace(), request.getCarCode(), request.getIdcardImg(), request.getIdcardImgBack())) { throw new BusinessException(ResponseStatus.BAD_REQUEST); } if (request.getMaritalStatus() == null || request.getCarType() == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST); } // 身份证号格式校验(18位,最后一位可为X) if (!request.getIdcard().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$")) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "身份证号格式不正确"); } // 查询司机信息 DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper().lambda() .eq(DriverInfo::getMemberId, memberId) .eq(DriverInfo::getDeleted, Constants.ZERO) .last("limit 1")); if (Objects.isNull(driverInfo)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } // 状态校验:auditStatus=null(未提交)或auditStatus=2(审批驳回)可提交认证 if (driverInfo.getAuditStatus() != null && !Constants.equalsInteger(driverInfo.getAuditStatus(), Constants.TWO)) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前状态不允许提交认证"); } // 根据车辆类型判断是否需要驾驶证 Category category = categoryMapper.selectById(request.getCarType()); if (Objects.isNull(category) || !Constants.equalsInteger(category.getType(), Constants.ONE)) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "车辆类型不存在"); } boolean needLicense = Constants.equalsInteger(Integer.valueOf(category.getOtherField()), Constants.ONE); if (needLicense) { if (request.getCardStartDate() == null || request.getCardEndDate() == null) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "该车辆类型需要填写驾驶证有效期"); } if (request.getLicenseImgUrls() == null || request.getLicenseImgUrls().isEmpty()) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "该车辆类型需要上传驾驶证照片"); } } // 车辆照片必填 if (request.getCarImgUrls() == null || request.getCarImgUrls().isEmpty()) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "请上传车辆照片"); } // 照片数量校验 if (request.getCarImgUrls().size() > 3) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "车辆照片最多上传3张"); } if (request.getLicenseImgUrls() != null && request.getLicenseImgUrls().size() > 3) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "驾驶证照片最多上传3张"); } if (request.getOtherImgUrls() != null && request.getOtherImgUrls().size() > 3) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "其他资料照片最多上传3张"); } // 更新司机信息 Date now = new Date(); driverInfoMapper.update(new UpdateWrapper().lambda() .set(DriverInfo::getName, request.getName()) .set(DriverInfo::getIdcard, request.getIdcard()) .set(DriverInfo::getMaritalStatus, request.getMaritalStatus()) .set(DriverInfo::getAreaId, request.getAreaId()) .set(DriverInfo::getLivePlace, request.getLivePlace()) .set(DriverInfo::getCarCode, request.getCarCode()) .set(DriverInfo::getCarType, request.getCarType()) .set(DriverInfo::getCarColor, request.getCarColor()) .set(DriverInfo::getCardStartDate, request.getCardStartDate()) .set(DriverInfo::getCardEndDate, request.getCardEndDate()) .set(DriverInfo::getIdcardImg, request.getIdcardImg()) .set(DriverInfo::getIdcardImgBack, request.getIdcardImgBack()) .set(DriverInfo::getAuditStatus, Constants.ZERO) .set(DriverInfo::getUpdateTime, now) .set(DriverInfo::getAuditRemark, null) .set(DriverInfo::getAuditTime, null) .eq(DriverInfo::getId, driverInfo.getId())); // 删除旧的照片记录 multifileMapper.delete(new QueryWrapper().lambda() .eq(Multifile::getObjId, driverInfo.getId()) .in(Multifile::getObjType, 6, 7, 8)); // 保存车辆照片 objType=6 saveMultifileList(driverInfo.getId(), 6, request.getCarImgUrls(), now); // 保存驾驶证照片 objType=7 if (!CollectionUtils.isEmpty(request.getLicenseImgUrls())) { saveMultifileList(driverInfo.getId(), 7, request.getLicenseImgUrls(), now); } // 保存其他资料照片 objType=8 if (!CollectionUtils.isEmpty(request.getOtherImgUrls())) { saveMultifileList(driverInfo.getId(), 8, request.getOtherImgUrls(), now); } // 更新会员司机认证状态为认证中 memberMapper.update(new UpdateWrapper().lambda() .set(Member::getBusinessStatus, Constants.ONE) .set(Member::getUpdateTime, now) .eq(Member::getId, memberId)); } @Override public DriverInfo getVerifyDetail(Integer memberId) { DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper().lambda() .eq(DriverInfo::getMemberId, memberId) .eq(DriverInfo::getDeleted, Constants.ZERO) .last("limit 1")); if (Objects.isNull(driverInfo)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } // 查询车辆类型名称和是否需要驾驶证 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); } } // 查询照片列表 List multifileList = multifileMapper.selectList(new QueryWrapper().lambda() .eq(Multifile::getObjId, driverInfo.getId()) .in(Multifile::getObjType, 6, 7, 8) .orderByAsc(Multifile::getSortnum)); if (!CollectionUtils.isEmpty(multifileList)) { for (Multifile mf : multifileList) { if (Constants.equalsInteger(mf.getObjType(), 6)) { driverInfo.getCarImgList().add(mf); } else if (Constants.equalsInteger(mf.getObjType(), 7)) { driverInfo.getLicenseImgList().add(mf); } else if (Constants.equalsInteger(mf.getObjType(), 8)) { driverInfo.getOtherImgList().add(mf); } } } return driverInfo; } @Override @Transactional public void auditVerify(AuditDTO auditDTO) { if (Objects.isNull(auditDTO.getId()) || Objects.isNull(auditDTO.getAuditStatus())) { throw new BusinessException(ResponseStatus.BAD_REQUEST); } DriverInfo driverInfo = driverInfoMapper.selectById(auditDTO.getId()); if (Objects.isNull(driverInfo)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } // 只有审批状态为0(待审批)且已填写认证信息才能审批 if (!Constants.equalsInteger(driverInfo.getAuditStatus(), Constants.ZERO) || StringUtils.isBlank(driverInfo.getIdcard())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前状态不允许审批"); } Date now = new Date(); // 审批结果:auditDTO.auditStatus 0=通过→auditStatus=1,1=拒绝→auditStatus=2 Integer newAuditStatus; if (Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ZERO)) { newAuditStatus = Constants.ONE; // 审批通过 // 审批通过时司机定级为必填 if (auditDTO.getDriverLevel() == null || auditDTO.getDriverLevel() < 1 || auditDTO.getDriverLevel() > 5) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "审批通过时必须填写司机定级"); } } else if (Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ONE)) { newAuditStatus = Constants.TWO; // 审批驳回 } else { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "审批状态参数错误"); } // 更新司机审批状态 driverInfoMapper.update(new UpdateWrapper().lambda() .set(DriverInfo::getAuditStatus, newAuditStatus) .set(DriverInfo::getAuditTime, now) .set(DriverInfo::getAuditRemark, auditDTO.getAuditRemark()) .set(DriverInfo::getAuditUser, auditDTO.getAuditUser()) .set(auditDTO.getDriverLevel() != null, DriverInfo::getDriverLevel, auditDTO.getDriverLevel()) .set(DriverInfo::getUpdateTime, now) .eq(DriverInfo::getId, auditDTO.getId())); // 更新会员司机认证状态:通过=2,驳回=3 Integer driverStatus = Constants.equalsInteger(newAuditStatus, Constants.ONE) ? Constants.TWO : Constants.THREE; memberMapper.update(new UpdateWrapper().lambda() .set(Member::getBusinessStatus, driverStatus) .set(Member::getUpdateTime, now) .eq(Member::getId, driverInfo.getMemberId())); } @Override public DriverInfo getDetail(Integer id) { DriverInfo driverInfo = driverInfoMapper.selectById(id); if (Objects.isNull(driverInfo) || Constants.equalsInteger(driverInfo.getDeleted(), Constants.ONE)) { 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); driverInfo.setGender(Constants.getGenderByIdCard(driverInfo.getIdcard())); // 查询车辆类型名称和是否需要驾驶证 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); } } // 查询照片列表:objType=6车辆照片、7驾驶证照片、8其他资料照片 List multifileList = multifileMapper.selectList(new QueryWrapper().lambda() .eq(Multifile::getObjId, driverInfo.getId()) .in(Multifile::getObjType, 6, 7, 8) .orderByAsc(Multifile::getSortnum)); if (!CollectionUtils.isEmpty(multifileList)) { for (Multifile mf : multifileList) { mf.setFileurlFull(imgPrefix + mf.getFileurl()); if (Constants.equalsInteger(mf.getObjType(), 6)) { driverInfo.getCarImgList().add(mf); } else if (Constants.equalsInteger(mf.getObjType(), 7)) { driverInfo.getLicenseImgList().add(mf); } else if (Constants.equalsInteger(mf.getObjType(), 8)) { driverInfo.getOtherImgList().add(mf); } } } return driverInfo; } @Override public void changeStatus(ChangeStatusDTO dto) { if (dto.getId() == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "主键不能为空"); } if (dto.getStatus() == null || (dto.getStatus() != 0 && dto.getStatus() != 1)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "状态参数错误,0=启用;1=禁用"); } DriverInfo driverInfo = driverInfoMapper.selectById(dto.getId()); if (Objects.isNull(driverInfo) || Constants.equalsInteger(driverInfo.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } driverInfo.setStatus(dto.getStatus()); driverInfo.setUpdateTime(new Date()); driverInfoMapper.updateById(driverInfo); // 联动修改会员状态(member主键与driver_info主键一致) memberMapper.update(new UpdateWrapper().lambda() .set(Member::getStatus, dto.getStatus()) .set(Member::getUpdateTime, new Date()) .eq(Member::getId, driverInfo.getId())); } /** * 批量保存附件记录 */ private void saveMultifileList(Integer objId, Integer objType, List urls, Date now) { int sortNum = 0; for (String url : urls) { Multifile multifile = new Multifile(); multifile.setObjId(objId); multifile.setObjType(objType); multifile.setType(Constants.ZERO); multifile.setFileurl(url); multifile.setIsdeleted(Constants.ZERO); multifile.setCreateDate(now); multifile.setSortnum(sortNum++); multifileMapper.insert(multifile); } } @Override public void updateAcceptingStatus(Integer memberId, Integer status) { if (!Constants.ZERO.equals(status) && !Constants.ONE.equals(status)) { throw new BusinessException(ResponseStatus.BAD_REQUEST); } DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper().lambda() .eq(DriverInfo::getMemberId, memberId) .eq(DriverInfo::getDeleted, Constants.ZERO) .last("limit 1")); if (driver == null) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在"); } if (!Constants.THREE.equals(driver.getAuditStatus())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "请先完成押金支付"); } driverInfoMapper.update(new UpdateWrapper().lambda() .set(DriverInfo::getAcceptingStatus, status) .eq(DriverInfo::getId, driver.getId())); } @Override public void updateLocation(Integer memberId, Double longitude, Double latitude) { if (longitude == null || latitude == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "经纬度不能为空"); } DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper().lambda() .eq(DriverInfo::getMemberId, memberId) .eq(DriverInfo::getDeleted, Constants.ZERO) .last("limit 1")); if (driver == null) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在"); } driverInfoMapper.update(new UpdateWrapper().lambda() .set(DriverInfo::getLongitude, longitude) .set(DriverInfo::getLatitude, latitude) .eq(DriverInfo::getId, driver.getId())); } @Override public DriverCenterVO getDriverCenterInfo(Integer memberId) { DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper().lambda() .eq(DriverInfo::getMemberId, memberId) .eq(DriverInfo::getDeleted, Constants.ZERO) .last("limit 1")); if (driver == null) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在"); } DriverCenterVO vo = new DriverCenterVO(); vo.setName(driver.getName()); vo.setImgUrl(driver.getImgurl()); vo.setCarCode(driver.getCarCode()); vo.setScore(driver.getScore() != null ? driver.getScore().toPlainString() : "0"); vo.setBalance(driver.getBalance() != null ? driver.getBalance() : 0L); // 头像全路径 if (StringUtils.isNotBlank(driver.getImgurl())) { String imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode() + systemDictDataBiz.queryByCode(Constants.OSS, Constants.MEMBER_FILES).getCode(); vo.setFullImgUrl(imgPrefix + driver.getImgurl()); } // 今日预计佣金:revenue表中今天的收入记录金额之和 Date now = new Date(); Calendar cal = Calendar.getInstance(); cal.setTime(now); 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(); QueryWrapper 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); // 今日接单数:今天完成的订单数(acceptDriver=司机主键,状态=已完成) Long todayOrderCount = ordersMapper.selectCount(new QueryWrapper().lambda() .eq(Orders::getAcceptDriver, driver.getId()) .eq(Orders::getDeleted, Constants.ZERO) .ge(Orders::getFinishTime, todayStart)); vo.setTodayOrderCount(todayOrderCount.intValue()); // 待取货(已接单=3) Long waitPickCount = ordersMapper.selectCount(new QueryWrapper().lambda() .eq(Orders::getAcceptDriver, driver.getId()) .eq(Orders::getDeleted, Constants.ZERO) .eq(Orders::getStatus, Constants.OrderStatus.accepted.getStatus())); vo.setWaitPickCount(waitPickCount.intValue()); // 待配送(派送中=4) Long waitDeliverCount = ordersMapper.selectCount(new QueryWrapper().lambda() .eq(Orders::getAcceptDriver, driver.getId()) .eq(Orders::getDeleted, Constants.ZERO) .eq(Orders::getStatus, Constants.OrderStatus.delivering.getStatus())); vo.setWaitDeliverCount(waitDeliverCount.intValue()); return vo; } @Override public PageData grabOrderHall(Integer memberId, PageWrap pageWrap) { DriverGrabOrderDTO dto = pageWrap.getModel(); // 1. 获取司机定位 DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper().lambda() .eq(DriverInfo::getMemberId, memberId) .eq(DriverInfo::getDeleted, Constants.ZERO) .last("limit 1")); if (driver == null || driver.getLatitude() == null || driver.getLongitude() == null) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "司机位置信息缺失,请先开启定位"); } // 校验司机已支付押金 if (!Constants.THREE.equals(driver.getAuditStatus())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "请先完成押金支付"); } double driverLat = driver.getLatitude(); double driverLng = driver.getLongitude(); // 2. 预查物品等级对应的物品分类ID List goodTypeIds = null; if (dto != null && dto.getGradeId() != null) { List cats = categoryMapper.selectList(new QueryWrapper().lambda() .eq(Category::getDeleted, Constants.ZERO) .eq(Category::getType, Constants.TWO) .eq(Category::getRelationId, dto.getGradeId())); if (cats.isEmpty()) { return emptyPage(pageWrap); } 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))))"; // 4. 构造MPJ查询 IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); MPJLambdaWrapper 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") // 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") .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"); // 核心条件 wrapper.eq(Orders::getType, Constants.ONE) .eq(Orders::getStatus, Constants.TWO) .eq(Orders::getDeleted, Constants.ZERO); // 司机级别 >= 订单物品级别 if (driver.getDriverLevel() != null) { wrapper.apply("t.GOOD_LEVEL <= {0}", driver.getDriverLevel()); } // 加急 OR 在配送范围内 wrapper.and(w -> w .eq(Orders::getIsUrgent, Constants.ONE) .or() .apply(depositDist + " <= s1.delivery_area")); // 用户距离过滤 if (dto != null && dto.getDistance() != null && dto.getDistance() > 0) { double maxKm = dto.getDistance() / 1000.0; wrapper.apply(depositDist + " <= {0}", maxKm); } // 物品等级过滤 if (goodTypeIds != null && !goodTypeIds.isEmpty()) { wrapper.in(Orders::getGoodType, goodTypeIds); } // 排序 Integer sortType = (dto != null) ? dto.getSortType() : null; if (sortType != null && sortType == Constants.TWO) { wrapper.last("ORDER BY " + depositDist + " ASC"); } else { wrapper.orderByDesc(Orders::getCreateTime); } IPage result = ordersMapper.selectJoinPage(page, Orders.class, wrapper); // 5. 批量查询物品明细 List orderIds = result.getRecords().stream() .map(Orders::getId).collect(Collectors.toList()); Map> detailMap = new HashMap<>(); if (!orderIds.isEmpty()) { List allDetails = ordersDetailMapper.selectList( new QueryWrapper().lambda() .in(OrdersDetail::getOrderId, orderIds)); for (OrdersDetail d : allDetails) { detailMap.computeIfAbsent(d.getOrderId(), k -> new ArrayList<>()).add(d); } } // 6. 构建VO(使用共用方法) Date now = new Date(); List voList = new ArrayList<>(); for (Orders order : result.getRecords()) { voList.add(buildDriverOrderVO(order, driverLat, driverLng, true, now, detailMap)); } // 7. 手动分页 PageData pageData = new PageData<>(result.getCurrent(), result.getSize()); pageData.setTotal(result.getTotal()); pageData.setRecords(voList); return pageData; } @Override public List activeOrders(Integer memberId, DriverActiveOrderDTO dto) { if (dto == null || dto.getStatus() == null || (!Constants.equalsInteger(dto.getStatus(), Constants.THREE) && !Constants.equalsInteger(dto.getStatus(), Constants.FOUR))) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单状态只能为3(已抢单)或4(派送中)"); } // 获取司机信息 DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper().lambda() .eq(DriverInfo::getMemberId, memberId) .eq(DriverInfo::getDeleted, Constants.ZERO) .last("limit 1")); if (driver == null) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在"); } boolean needDepositDist = Constants.equalsInteger(dto.getStatus(), Constants.THREE); Double driverLat = driver.getLatitude(); Double driverLng = driver.getLongitude(); // MPJ查询 MPJLambdaWrapper 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") .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(Orders::getDeleted, Constants.ZERO) .orderByAsc(Orders::getAcceptTime); List ordersList = ordersMapper.selectJoinList(Orders.class, wrapper); // 批量查物品明细 List orderIds = ordersList.stream().map(Orders::getId).collect(Collectors.toList()); Map> detailMap = new HashMap<>(); if (!orderIds.isEmpty()) { List allDetails = ordersDetailMapper.selectList( new QueryWrapper().lambda() .in(OrdersDetail::getOrderId, orderIds)); for (OrdersDetail d : allDetails) { detailMap.computeIfAbsent(d.getOrderId(), k -> new ArrayList<>()).add(d); } } // 构建VO(使用共用方法) Date now = new Date(); List voList = new ArrayList<>(); for (Orders order : ordersList) { voList.add(buildDriverOrderVO(order, driverLat, driverLng, needDepositDist, now, detailMap)); } return voList; } @Override public DriverOrderDetailVO driverOrderDetail(Integer driverId, Integer orderId) { // 查询订单(MPJ JOIN 门店名称+分类,距离计算使用Orders自带坐标) MPJLambdaWrapper 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") .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::getId, orderId) .eq(Orders::getDeleted, Constants.ZERO); Orders order = ordersMapper.selectJoinOne(Orders.class, wrapper); if (order == null) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); } // 权限校验:抢单大厅可见(status=2, type=1) 或 已抢单/派送中(acceptDriver=driverId) boolean canView = false; if (Constants.equalsInteger(order.getStatus(), Constants.TWO) && Constants.equalsInteger(order.getType(), Constants.ONE)) { canView = true; } if (driverId != null && driverId.equals(order.getAcceptDriver())) { canView = true; } if (!canView) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权查看该订单"); } // 获取司机位置(用于距离计算) Double driverLat = null; Double driverLng = null; if (driverId != null) { DriverInfo driver = driverInfoMapper.selectById(driverId); if (driver != null) { driverLat = driver.getLatitude(); driverLng = driver.getLongitude(); } } // 物品明细 List details = ordersDetailMapper.selectList( new QueryWrapper().lambda() .eq(OrdersDetail::getOrderId, orderId)); Map> detailMap = new HashMap<>(); for (OrdersDetail d : details) { detailMap.computeIfAbsent(d.getOrderId(), k -> new ArrayList<>()).add(d); } // 使用共用方法构建基础VO字段 Date now = new Date(); DriverGrabOrderVO base = buildDriverOrderVO(order, driverLat, driverLng, true, now, detailMap); // 构建详情VO DriverOrderDetailVO vo = new DriverOrderDetailVO(); vo.setId(base.getId()); vo.setCode(base.getCode()); vo.setRemainMinutes(base.getRemainMinutes()); vo.setIsUrgent(base.getIsUrgent()); vo.setDepositShopName(base.getDepositShopName()); vo.setDepositShopAddress(base.getDepositShopAddress()); vo.setDepositDistance(base.getDepositDistance()); vo.setTakeName(base.getTakeName()); vo.setTakeDistance(base.getTakeDistance()); vo.setContactPhone(base.getContactPhone()); vo.setDriverFee(base.getDriverFee()); vo.setUrgentAmount(base.getUrgentAmount()); vo.setIsValuable(base.getIsValuable()); // 物品明细(转换类型) List detailItems = new ArrayList<>(); if (base.getItems() != null) { for (DriverGrabOrderVO.OrderItem src : base.getItems()) { DriverOrderDetailVO.OrderItem item = new DriverOrderDetailVO.OrderItem(); item.setName(src.getName()); item.setQuantity(src.getQuantity()); detailItems.add(item); } } vo.setItems(detailItems); // 详情特有字段 vo.setStatus(order.getStatus()); vo.setStatusDesc(getStatusDesc(order.getStatus())); // 客户信息 String customerInfo = ""; if (StringUtils.isNotBlank(order.getTakeUser())) { customerInfo = order.getTakeUser(); } if (StringUtils.isNotBlank(order.getTakePhone()) && order.getTakePhone().length() >= 4) { customerInfo += "(手机尾号" + order.getTakePhone().substring(order.getTakePhone().length() - 4) + ")"; } vo.setCustomerInfo(customerInfo); // 导航经纬度(使用Orders自带坐标) if (Constants.equalsInteger(order.getStatus(), Constants.TWO)) { if (order.getDepositLgt() != null && order.getDepositLat() != null) { vo.setNavigateLat(order.getDepositLgt().doubleValue()); vo.setNavigateLng(order.getDepositLat().doubleValue()); } } else { if (order.getTakeLgt() != null && order.getTakeLat() != null) { vo.setNavigateLat(order.getTakeLgt().doubleValue()); vo.setNavigateLng(order.getTakeLat().doubleValue()); } } // 下单附件图片 String imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode() + systemDictDataBiz.queryByCode(Constants.OSS, Constants.MEMBER_FILES).getCode(); vo.setOrderImages(getFileUrls(orderId, Constants.FileType.ORDER_FILE.getKey(), imgPrefix)); return vo; } @Override @Transactional(rollbackFor = Exception.class) public void cancelOrder(Integer driverId, Integer orderId, String reason) { // 1. 查询司机信息 DriverInfo driver = driverInfoMapper.selectById(driverId); if (driver == null) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在"); } // 2. 校验订单 Orders order = ordersMapper.selectById(orderId); if (order == null || Constants.ONE.equals(order.getDeleted())) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); } if (!Constants.ONE.equals(order.getType())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单可取消"); } if (!Constants.TWO.equals(order.getStatus())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许取消"); } if (!driverId.equals(order.getAcceptDriver())) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权操作该订单"); } // 3. 每日取消次数限制 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().lambda() .eq(OrderLog::getOptUserId, driver.getMemberId()) .eq(OrderLog::getObjType, Constants.ORDER_LOG_CANCEL) .eq(OrderLog::getOptUserType, Constants.ONE) .ge(OrderLog::getCreateTime, todayStart)); if (todayCancelCount != null && todayCancelCount >= limit) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "今日取消次数已达上限"); } // 4. 重置订单司机字段(保持status=2,释放回抢单大厅) ordersMapper.update(new UpdateWrapper().lambda() .set(Orders::getAcceptDriver, null) .set(Orders::getAcceptTime, null) .set(Orders::getAcceptType, null) .eq(Orders::getId, orderId)); // 5. 写入取消日志 OrderLog log = new OrderLog(); log.setOrderId(orderId); log.setTitle("司机取消订单"); log.setLogInfo(StringUtils.isNotBlank(reason) ? reason : "司机取消接单"); log.setObjType(Constants.ORDER_LOG_CANCEL); log.setOptUserId(driver.getMemberId()); log.setOptUserType(Constants.ONE); log.setOrderStatus(order.getStatus()); log.setCreateTime(new Date()); log.setDeleted(Constants.ZERO); orderLogMapper.insert(log); } private List getFileUrls(Integer orderId, int objType, String prefix) { List files = multifileMapper.selectList( new QueryWrapper().lambda() .eq(Multifile::getObjId, orderId) .eq(Multifile::getObjType, objType) .eq(Multifile::getIsdeleted, Constants.ZERO) .orderByAsc(Multifile::getSortnum)); List urls = new ArrayList<>(); if (files != null) { for (Multifile f : files) { if (StringUtils.isNotBlank(f.getFileurl())) { urls.add(prefix + f.getFileurl()); } } } return urls; } private String getStatusDesc(Integer status) { if (status == null) return ""; switch (status) { case 0: return "待支付"; case 1: return "待寄存"; case 2: return "已寄存"; case 3: return "已接单"; case 4: return "派送中"; case 5: return "待取件"; case 7: return "已完成"; case 96: return "订单关闭"; case 98: return "取消中"; case 99: return "已取消"; default: return ""; } } private String formatDistance(double km) { if (km < 1) { return Math.round(km * 1000) + "m"; } return String.format("%.1fkm", km); } private double haversine(double lat1, double lng1, double lat2, double lng2) { double R = 6371; 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 R * c; } private PageData emptyPage(PageWrap pageWrap) { PageData pd = new PageData<>(pageWrap.getPage(), pageWrap.getCapacity()); pd.setTotal(0); pd.setRecords(new ArrayList<>()); return pd; } /** * 构建司机端订单列表共用VO(使用Orders自带坐标计算距离) * * @param order 订单实体(MPJ已填充depositShopName等关联字段) * @param driverLat 司机纬度 * @param driverLng 司机经度 * @param needDepositDist 是否需要计算距存件门店距离 * @param now 当前时间 * @param detailMap 订单物品明细Map(orderId → detailList) * @return DriverGrabOrderVO */ private DriverGrabOrderVO buildDriverOrderVO(Orders order, Double driverLat, Double driverLng, boolean needDepositDist, Date now, Map> detailMap) { DriverGrabOrderVO vo = new DriverGrabOrderVO(); vo.setId(order.getId()); vo.setCode(order.getCode()); // 剩余时长 if (order.getEstimatedDeliveryTime() != null) { long diffMs = order.getEstimatedDeliveryTime().getTime() - now.getTime(); vo.setRemainMinutes(diffMs > 0 ? diffMs / (60 * 1000) : 0L); } else { vo.setRemainMinutes(0L); } vo.setIsUrgent(order.getIsUrgent()); vo.setDriverFee(order.getDriverFee()); vo.setUrgentAmount(order.getUrgentAmount()); // 存件门店(使用Orders自带坐标) vo.setDepositShopName(order.getDepositShopName()); vo.setDepositShopAddress(order.getDepositShopAddress()); if (needDepositDist && driverLat != null && driverLng != null && order.getDepositLgt() != null && order.getDepositLat() != null) { double distKm = haversine(driverLat, driverLng, order.getDepositLgt().doubleValue(), order.getDepositLat().doubleValue()); vo.setDepositDistance(formatDistance(distKm)); } // 取件信息 + 联系电话(使用Orders自带坐标) boolean hasTakeShop = order.getTakeShopId() != null && StringUtils.isNotBlank(order.getTakeShopName()); if (hasTakeShop) { vo.setTakeName(order.getTakeShopName()); vo.setContactPhone(order.getTakeShopLinkPhone()); } else { vo.setTakeName(order.getTakeLocation()); vo.setContactPhone(order.getTakePhone()); } if (driverLat != null && driverLng != null && order.getTakeLgt() != null && order.getTakeLat() != null) { double takeDist = haversine(driverLat, driverLng, order.getTakeLgt().doubleValue(), order.getTakeLat().doubleValue()); vo.setTakeDistance(formatDistance(takeDist)); } // 贵重物品 vo.setIsValuable("1".equals(order.getC2OtherField())); // 物品明细 List details = detailMap.getOrDefault(order.getId(), Collections.emptyList()); List items = new ArrayList<>(); for (OrdersDetail detail : details) { DriverGrabOrderVO.OrderItem item = new DriverGrabOrderVO.OrderItem(); item.setName(detail.getLuggageName()); item.setQuantity(detail.getNum()); items.add(item); } vo.setItems(items); return vo; } }