rk
2026-04-17 21bd711a3756850299b443848181ee60708c6377
代码生成
已添加7个文件
已修改38个文件
已重命名1个文件
1611 ■■■■■ 文件已修改
server/services/db/db_change.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/jwt/JwtTokenUtil.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/jwt/WebMvcConfig.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/annotation/LoginDriverRequired.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/utils/Tencent/MapUtil.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/utils/aliyun/AliSmsService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Category.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/DriverInfo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Orders.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/PricingRule.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/CalculateLocalPriceDTO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/CalculateRemotePriceDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/RevenueQueryDTO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/SameCityCheckDTO.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/ShopApplyDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/ShopNearbyDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/AccountResponse.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/DriverCenterVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/EstimatedDeliveryResultVO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/RevenueSummaryVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/ShopNearbyVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/ShopWebDetailVO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/UserCenterVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/AreasService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/DriverInfoService.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/OrdersService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/RevenueService.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/ShopInfoService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/AreasServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/BannerServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/CategoryServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java 562 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/RevenueServiceImpl.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/ShopInfoServiceImpl.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/SmsrecordServiceImpl.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/WithdrawalOrdersServiceImpl.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/resources/application-dev.yml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/ApiController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/ConfigApi.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/DriverInfoApi.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/RevenueApi.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/ShopInfoApi.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/WalletApi.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/db/db_change.sql
@@ -5,6 +5,19 @@
-- ============================================================
-- 2026/04/17 è®¢å•预计送达时间字段
-- ============================================================
ALTER TABLE `orders` ADD COLUMN `ESTIMATED_DELIVERY_TIME` DATETIME DEFAULT NULL COMMENT '预计送达时间' AFTER `CODE`;
-- ============================================================
-- 2026/04/17 å¸æœºå®žæ—¶å®šä½å­—段
-- ============================================================
ALTER TABLE `driver_info` ADD COLUMN `LONGITUDE` DOUBLE DEFAULT NULL COMMENT '定位经度' AFTER `ACCEPTING_STATUS`;
ALTER TABLE `driver_info` ADD COLUMN `LATITUDE` DOUBLE DEFAULT NULL COMMENT '定位纬度' AFTER `LONGITUDE`;
-- ============================================================
-- 2026/04/16 è®¢å•结算功能:结算时间字段 + é—¨åº—/司机余额字段
-- ============================================================
ALTER TABLE `orders` ADD COLUMN `SETTLEMENT_TIME` DATETIME DEFAULT NULL COMMENT '结算时间' AFTER `SETTLEMENT_STATUS`;
@@ -14,6 +27,7 @@
ALTER TABLE `driver_info` ADD COLUMN `BALANCE` BIGINT DEFAULT 0 COMMENT '当前余额(分)' AFTER `SCORE`;
ALTER TABLE `driver_info` ADD COLUMN `TOTAL_BALANCE` BIGINT DEFAULT 0 COMMENT '历史总金额(分)' AFTER `BALANCE`;
ALTER TABLE `driver_info` ADD COLUMN `ACCEPTING_STATUS` INT DEFAULT 0 COMMENT '是否接单中:0=未接单;1=接单中' AFTER `TOTAL_BALANCE`;
-- ============================================================
server/services/src/main/java/com/doumee/config/jwt/JwtTokenUtil.java
@@ -29,11 +29,10 @@
    public static final String MEMBER_ID = "MEMBER_ID";
    public static final String MEMBER_INFO = "MEMBER_INFO";
    public static final String SHOP_ID = "SHOP_ID";
    public static final String SHOP_INFO = "SHOP_INFO";
    public static final String DRIVER_ID = "DRIVER_ID";
    @Resource
    private RedisTemplate<String,Object> redisTemplate;
server/services/src/main/java/com/doumee/config/jwt/WebMvcConfig.java
@@ -1,6 +1,7 @@
package com.doumee.config.jwt;
import com.alibaba.fastjson.JSONObject;
import com.doumee.core.annotation.LoginDriverRequired;
import com.doumee.core.annotation.LoginRequired;
import com.doumee.core.annotation.LoginShopRequired;
import com.doumee.core.constants.Constants;
@@ -87,6 +88,22 @@
                    } else {
                        throw new BusinessException(ResponseStatus.BE_OVERDUE.getCode(),"未登录");
                    }
                }else if (beanType.isAnnotationPresent(LoginDriverRequired.class)) {
                    //获取token
                    String token = request.getHeader(JwtTokenUtil.HEADER_KEY);  // ä»Ž http è¯·æ±‚头中取出 token
                    if (StringUtils.isNotBlank(token)) {
                        checkDriverLogin(request,response);
                    } else {
                        throw new BusinessException(ResponseStatus.BE_OVERDUE.getCode(),"未登录");
                    }
                }else if (handlerMethod.hasMethodAnnotation(LoginDriverRequired.class)){
                    //获取token
                    String token = request.getHeader(JwtTokenUtil.HEADER_KEY);  // ä»Ž http è¯·æ±‚头中取出 token
                    if (StringUtils.isNotBlank(token)) {
                        checkDriverLogin(request,response);
                    } else {
                        throw new BusinessException(ResponseStatus.BE_OVERDUE.getCode(),"未登录");
                    }
                }
                return true;
            }
@@ -99,11 +116,10 @@
    public Boolean checkMemberLogin(HttpServletRequest request, HttpServletResponse response){
        String token = request.getHeader(JwtTokenUtil.HEADER_KEY);
        try {
            if(!token.startsWith(Constants.ZERO+"")){
                throw new BusinessException(ResponseStatus.TOKEN_EXCEED_TIME);
            }
            String tokenRedis = (String) redisTemplate.opsForValue().get(token);
            String tokenRedis = (String) redisTemplate.opsForValue().get(Constants.REDIS_TOKEN_KEY+token);
            if(StringUtils.isBlank(tokenRedis)){
                throw new BusinessException(ResponseStatus.BE_OVERDUE);
            }
@@ -117,7 +133,6 @@
            }
            Integer count = dao.queryForObject("select count(1) from member where id  = ?", Integer.class, member.getId());
            if (count != null && count > 0) {
                request.setAttribute(JwtTokenUtil.MEMBER_INFO, JSONObject.toJSONString(member));
                request.setAttribute(JwtTokenUtil.MEMBER_ID, member.getId());
                return true;
            }else{
@@ -135,7 +150,7 @@
            if(!token.startsWith(Constants.TWO+"")){
                throw new BusinessException(ResponseStatus.TOKEN_EXCEED_TIME);
            }
            String tokenRedis = (String) redisTemplate.opsForValue().get(token);
            String tokenRedis = (String) redisTemplate.opsForValue().get(Constants.REDIS_TOKEN_KEY+token);
            if(StringUtils.isBlank(tokenRedis)){
                throw new BusinessException(ResponseStatus.BE_OVERDUE);
            }
@@ -159,7 +174,6 @@
            }
            Integer count = dao.queryForObject("select count(1) from shop where id  = ?", Integer.class, shopId);
            if (count != null && count > 0) {
                request.setAttribute(JwtTokenUtil.SHOP_INFO, JSONObject.toJSONString(shop));
                request.setAttribute(JwtTokenUtil.SHOP_ID, shop.getId());
                return true;
            }else{
@@ -170,6 +184,37 @@
        }
    }
    public Boolean checkDriverLogin(HttpServletRequest request, HttpServletResponse response){
        String token = request.getHeader(JwtTokenUtil.HEADER_KEY);
        try {
            if(!token.startsWith(Constants.ONE+"")){
                throw new BusinessException(ResponseStatus.TOKEN_EXCEED_TIME);
            }
            String tokenRedis = (String) redisTemplate.opsForValue().get(Constants.REDIS_TOKEN_KEY+token);
            if(StringUtils.isBlank(tokenRedis)){
                throw new BusinessException(ResponseStatus.BE_OVERDUE);
            }
            Integer memberId = getTokenId(token);
            Integer isDeleted = dao.queryForObject(" select COALESCE(DELETED,1)  from member where user_type = 1 and   id  = ?", Integer.class, memberId);
            if(isDeleted== Constants.ONE){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"司机信息已删除,请联系管理员");
            }
            Integer isForbidden = dao.queryForObject(" select COALESCE(STATUS,0)  from member where user_type = 1 and  id  = ?", Integer.class, memberId);
            if(isForbidden == Constants.ONE){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"司机信息已禁用,请联系管理员");
            }
            Integer count = dao.queryForObject("select count(1) from member where  user_type = 1  and id  = ?", Integer.class, memberId);
            if (count != null && count > 0) {
                request.setAttribute(JwtTokenUtil.DRIVER_ID, memberId);
                return true;
            }else{
                throw new BusinessException(ResponseStatus.BE_OVERDUE.getCode(),"司机信息出错");
            }
        } catch (IllegalArgumentException | JwtException e) {
            throw new BusinessException(ResponseStatus.BE_OVERDUE);
        }
    }
    public Integer getTokenId(String token){
        try {
server/services/src/main/java/com/doumee/core/annotation/LoginDriverRequired.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
package com.doumee.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginDriverRequired {}
server/services/src/main/java/com/doumee/core/utils/Tencent/MapUtil.java
@@ -28,6 +28,9 @@
    /** è·ç¦»çŸ©é˜µAPI */
    public static final String MATRIX_URL = "https://apis.map.qq.com/ws/distance/v1/matrix";
    /** é€†åœ°ç†è§£æž */
    public static final String GEO_URL = "https://apis.map.qq.com/ws/geocoder/v1/";
    /** æ”¯æŒçš„æ¨¡å¼ */
    private static final List<String> SUPPORTED_MODES = Arrays.asList("driving", "bicycling");
@@ -121,4 +124,59 @@
                .map(row -> ((JSONObject) row).getJSONArray("elements").getJSONObject(0))
                .collect(Collectors.toList());
    }
    /**
     * é€†åœ°ç†è§£æž - æ ¹æ®ç»çº¬åº¦èŽ·å–åœ°å€ä¿¡æ¯
     *
     * @param lat çº¬åº¦
     * @param lng ç»åº¦
     * @return result.ad_info ä¸­çš„ adcode(区划码)、city(城市)、district(区) ç­‰ä¿¡æ¯
     */
    public static JSONObject reverseGeocode(double lat, double lng) {
        try {
            String url = GEO_URL
                    + "?key=" + tencentKey
                    + "&location=" + lat + "," + lng;
            log.info("腾讯地图逆地理解析请求: location={},{}", lat, lng);
            JSONObject json = new Http().build(url)
                    .setConnectTimeout(5000)
                    .setReadTimeout(10000)
                    .get()
                    .toJSONObject();
            log.info("腾讯地图逆地理解析响应: {}", json);
            if (json.getIntValue("status") != 0) {
                throw new RuntimeException("腾讯地图逆地理解析失败: " + json.getString("message"));
            }
            return json.getJSONObject("result");
        } catch (IOException e) {
            log.error("腾讯地图逆地理解析异常", e);
            throw new RuntimeException("腾讯地图逆地理解析异常", e);
        }
    }
    /**
     * åˆ¤æ–­ä¸¤ä¸ªç»çº¬åº¦æ˜¯å¦åœ¨åŒä¸€ä¸ªåŸŽå¸‚
     *
     * @param lat1 ç¬¬ä¸€ä¸ªç‚¹çº¬åº¦
     * @param lng1 ç¬¬ä¸€ä¸ªç‚¹ç»åº¦
     * @param lat2 ç¬¬äºŒä¸ªç‚¹çº¬åº¦
     * @param lng2 ç¬¬äºŒä¸ªç‚¹ç»åº¦
     * @return true=同城,false=不同城
     */
    public static boolean isSameCity(double lat1, double lng1, double lat2, double lng2) {
        JSONObject result1 = reverseGeocode(lat1, lng1);
        JSONObject result2 = reverseGeocode(lat2, lng2);
        String city1 = result1.getJSONObject("ad_info").getString("city");
        String city2 = result2.getJSONObject("ad_info").getString("city");
        log.info("判断同城: ({},{}) => city={}, ({},{}) => city={}", lat1, lng1, city1, lat2, lng2, city2);
        return city1 != null && city1.equals(city2);
    }
}
server/services/src/main/java/com/doumee/core/utils/aliyun/AliSmsService.java
ÎļþÃû´Ó server/services/src/main/java/com/doumee/service/business/AliSmsService.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.doumee.service.business;
package com.doumee.core.utils.aliyun;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
server/services/src/main/java/com/doumee/dao/business/model/Category.java
@@ -79,7 +79,7 @@
    @ExcelColumn(name="排序码(升序)")
    private Integer sortnum;
    @ApiModelProperty(value = "附属字段1 (type=1:是否需要上传驾驶证:0=不需要;1=需要;) ")
    @ApiModelProperty(value = "附属字段1 (type=1:是否需要上传驾驶证:0=不需要;1=需要;type=3:是否贵重物品:0=否;1=是;type=4:尺寸描述;) ")
    @ExcelColumn(name="内容")
    private String otherField;
server/services/src/main/java/com/doumee/dao/business/model/DriverInfo.java
@@ -141,6 +141,15 @@
    @ApiModelProperty(value = "历史总金额(分)")
    private Long totalBalance;
    @ApiModelProperty(value = "是否接单中:0=已下线;1=接单中", example = "0")
    private Integer acceptingStatus;
    @ApiModelProperty(value = "定位经度", example = "116.404")
    private Double longitude;
    @ApiModelProperty(value = "定位纬度", example = "39.915")
    private Double latitude;
    @ApiModelProperty(value = "车辆照片列表")
    @TableField(exist = false)
    private List<Multifile> carImgList = new ArrayList<>();
server/services/src/main/java/com/doumee/dao/business/model/Orders.java
@@ -284,6 +284,10 @@
    @ExcelColumn(name = "订单编号")
    private String code;
    @ApiModelProperty(value = "预计送达时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date estimatedDeliveryTime;
    @ApiModelProperty(value = "司机薪酬(分)")
    @ExcelColumn(name = "司机薪酬")
    private Long driverFee;
@@ -397,4 +401,12 @@
    @ApiModelProperty(value = "取件门店地址(关联查询)")
    private String takeShopAddress;
    @TableField(exist = false)
    @ApiModelProperty(value = "取件门店联系电话(关联查询)")
    private String takeShopLinkPhone;
    @TableField(exist = false)
    @ApiModelProperty(value = "物品等级贵重标识(关联查询:0=否;1=是)")
    private String c2OtherField;
}
server/services/src/main/java/com/doumee/dao/business/model/PricingRule.java
@@ -48,7 +48,7 @@
    @ApiModelProperty(value = "城市主键(area_id)", example = "1")
    private Integer cityId;
    @ApiModelProperty(value = "类型:0=就地存取规则;1=异地存取规则;2=预计失效;3=门店注册押金;4=分成比例", example = "0")
    @ApiModelProperty(value = "类型:0=就地存取规则;1=异地存取规则;2=预计时效;3=门店注册押金;4=分成比例", example = "0")
    @ExcelColumn(name = "类型", index = 2, width = 10, valueMapping = "0=就地存取规则;1=异地存取规则;2=预计失效;3=门店注册押金;4=分成比例;")
    private Integer type;
server/services/src/main/java/com/doumee/dao/dto/CalculateLocalPriceDTO.java
@@ -8,6 +8,7 @@
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
@@ -24,17 +25,18 @@
    @NotNull(message = "城市不能为空")
    private Integer cityId;
    @ApiModelProperty(value = "预计存放天数", required = true)
    @NotNull(message = "预计存放天数不能为空")
    private Integer estimatedDepositDays;
    @ApiModelProperty(value = "预计存放开始时间", required = true)
    @NotNull(message = "预计存放开始时间不能为空")
    private Date depositStartTime;
    @ApiModelProperty(value = "预计存放结束时间", required = true)
    @NotNull(message = "预计存放结束时间不能为空")
    private Date depositEndTime;
    @ApiModelProperty(value = "物品列表", required = true)
    @NotEmpty(message = "物品列表不能为空")
    @Valid
    private List<OrderItemDTO> items;
    @ApiModelProperty(value = "是否保价")
    private Boolean insured;
    @ApiModelProperty(value = "保价金额(元)")
    private BigDecimal declaredAmount;
server/services/src/main/java/com/doumee/dao/dto/CalculateRemotePriceDTO.java
@@ -45,9 +45,6 @@
    @Valid
    private List<OrderItemDTO> items;
    @ApiModelProperty(value = "是否保价")
    private Boolean insured;
    @ApiModelProperty(value = "保价金额(元)")
    private BigDecimal declaredAmount;
server/services/src/main/java/com/doumee/dao/dto/RevenueQueryDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.doumee.dao.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("流水查询条件")
public class RevenueQueryDTO {
    @ApiModelProperty(value = "收支类型:1=收入;-1=支出")
    private Integer optType;
    @ApiModelProperty(value = "变动类型:0=完成订单;1=提现支出;2=提现退回;3=平台奖励")
    private Integer type;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "开始时间")
    private Date startTime;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "结束时间")
    private Date endTime;
}
server/services/src/main/java/com/doumee/dao/dto/SameCityCheckDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.doumee.dao.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
 * æ ¡éªŒä¸¤ä¸ªç»çº¬åº¦æ˜¯å¦åŒåŸŽè¯·æ±‚
 * @author rk
 * @date 2026/04/17
 */
@Data
@ApiModel("校验同城请求")
public class SameCityCheckDTO {
    @NotNull(message = "第一个点纬度不能为空")
    @ApiModelProperty(value = "第一个点纬度", example = "39.915", required = true)
    private Double lat1;
    @NotNull(message = "第一个点经度不能为空")
    @ApiModelProperty(value = "第一个点经度", example = "116.404", required = true)
    private Double lng1;
    @NotNull(message = "第二个点纬度不能为空")
    @ApiModelProperty(value = "第二个点纬度", example = "31.230", required = true)
    private Double lat2;
    @NotNull(message = "第二个点经度不能为空")
    @ApiModelProperty(value = "第二个点经度", example = "121.473", required = true)
    private Double lng2;
}
server/services/src/main/java/com/doumee/dao/dto/ShopApplyDTO.java
@@ -19,6 +19,9 @@
@ApiModel("门店入驻申请请求")
public class ShopApplyDTO implements Serializable {
    @ApiModelProperty(value = "登录用户主键", hidden = true, example = "0")
    private Integer memberId;
    @ApiModelProperty(value = "企业类型:0=个人;1=企业", required = true, example = "0")
    @NotNull(message = "企业类型不能为空")
    private Integer companyType;
server/services/src/main/java/com/doumee/dao/dto/ShopNearbyDTO.java
@@ -36,4 +36,7 @@
    @ApiModelProperty(value = "门店名称(模糊查询)", example = "XX门店")
    private String name;
    @ApiModelProperty(value = "城市主键", example = "1")
    private Integer cityId;
}
server/services/src/main/java/com/doumee/dao/vo/AccountResponse.java
@@ -27,15 +27,6 @@
    @ApiModelProperty(value = "用户信息")
    private Member member;
    @ApiModelProperty(value = "我注册的门店主键(门店用户时返回)")
    private Integer shopId;
    @ApiModelProperty(value = "我注册的门店审核状态(门店用户时返回): 0=待审核 1=已通过 2=已驳回 3=已缴纳保证金")
    private Integer shopAuditStatus;
    @ApiModelProperty(value = "门店token")
    private String loginShopToken;
server/services/src/main/java/com/doumee/dao/vo/DriverCenterVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * å¸æœºç«¯é¦–页信息
 * @author rk
 * @date 2026/04/17
 */
@Data
@ApiModel("司机端首页信息")
public class DriverCenterVO {
    @ApiModelProperty(value = "司机姓名")
    private String name;
    @ApiModelProperty(value = "司机头像")
    private String imgUrl;
    @ApiModelProperty(value = "司机头像全路径")
    private String fullImgUrl;
    @ApiModelProperty(value = "车牌号")
    private String carCode;
    @ApiModelProperty(value = "服务评分")
    private String score;
    @ApiModelProperty(value = "今日预计佣金(分)")
    private Long todayCommission;
    @ApiModelProperty(value = "今日接单数")
    private Integer todayOrderCount;
    @ApiModelProperty(value = "我的余额(分)")
    private Long balance;
    @ApiModelProperty(value = "待取货订单数量")
    private Integer waitPickCount;
    @ApiModelProperty(value = "待配送订单数量")
    private Integer waitDeliverCount;
}
server/services/src/main/java/com/doumee/dao/vo/EstimatedDeliveryResultVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
 * é¢„计送达时效结果
 * @author rk
 * @date 2026/04/17
 */
@Data
@ApiModel("预计送达时效结果")
public class EstimatedDeliveryResultVO {
    @ApiModelProperty(value = "实际距离(公里)")
    private BigDecimal distanceKm;
    @ApiModelProperty(value = "标速达时效(小时)")
    private BigDecimal standardHours;
    @ApiModelProperty(value = "极速达时效(小时)")
    private BigDecimal expressHours;
}
server/services/src/main/java/com/doumee/dao/vo/RevenueSummaryVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.doumee.dao.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("流水收支统计")
public class RevenueSummaryVO {
    @ApiModelProperty(value = "收入总额(分)")
    private Long totalIncome;
    @ApiModelProperty(value = "支出总额(分)")
    private Long totalExpense;
}
server/services/src/main/java/com/doumee/dao/vo/ShopNearbyVO.java
@@ -36,4 +36,10 @@
    @ApiModelProperty(value = "门店评分")
    private BigDecimal score;
    @ApiModelProperty(value = "门店纬度")
    private Double latitude;
    @ApiModelProperty(value = "门店经度")
    private Double longitude;
}
server/services/src/main/java/com/doumee/dao/vo/ShopWebDetailVO.java
@@ -33,4 +33,16 @@
    @ApiModelProperty(value = "距离(如 500m、1.2km),无经纬度入参时为空")
    private String distanceText;
    @ApiModelProperty(value = "寄存类型(逗号分隔的category主键)")
    private String depositTypes;
    @ApiModelProperty(value = "收费标准")
    private String feeStandard;
    @ApiModelProperty(value = "门店纬度")
    private Double latitude;
    @ApiModelProperty(value = "门店经度")
    private Double longitude;
}
server/services/src/main/java/com/doumee/dao/vo/UserCenterVO.java
@@ -12,6 +12,9 @@
@Data
public class UserCenterVO {
    @ApiModelProperty(value = "openid")
    private String openid;
    @ApiModelProperty(value = "会员头像全路径")
    private String fullCoverImage;
@@ -36,4 +39,18 @@
    @ApiModelProperty(value = "待收货订单数量")
    private Integer waitReceiveCount;
    @ApiModelProperty(value = "退款中订单数量")
    private Integer refundingCount;
    @ApiModelProperty(value = "我注册的门店主键(门店用户时返回)")
    private Integer shopId;
    @ApiModelProperty(value = "我注册的门店审核状态(门店用户时返回): 0=待审核 1=已通过 2=已驳回 3=已缴纳保证金")
    private Integer shopAuditStatus;
    @ApiModelProperty(value = "我当前绑定的门店")
    private String bindShopId;
}
server/services/src/main/java/com/doumee/service/business/AreasService.java
@@ -140,4 +140,12 @@
     */
    List<Areas> getOpenCityList();
    /**
     * æ ¹æ®åŸŽå¸‚名称查询已开通的城市信息
     *
     * @param cityName åŸŽå¸‚名称
     * @return åŸŽå¸‚信息,未开通则返回null
     */
    Areas getOpenedCityByName(String cityName);
}
server/services/src/main/java/com/doumee/service/business/DriverInfoService.java
@@ -2,6 +2,8 @@
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.dto.DriverActiveOrderDTO;
import com.doumee.dao.dto.DriverGrabOrderDTO;
import com.doumee.dao.business.model.DriverInfo;
import com.doumee.dao.dto.DriverLoginRequest;
import com.doumee.dao.dto.DriverRegisterRequest;
@@ -161,4 +163,56 @@
     */
    void changeStatus(com.doumee.dao.dto.ChangeStatusDTO dto);
    /**
     * åˆ‡æ¢å¸æœºæŽ¥å•状态
     *
     * @param memberId ä¼šå‘˜ä¸»é”®
     * @param status   æŽ¥å•状态:0=未接单;1=接单中
     */
    void updateAcceptingStatus(Integer memberId, Integer status);
    /**
     * æ›´æ–°å¸æœºå®žæ—¶å®šä½
     *
     * @param memberId  ä¼šå‘˜ä¸»é”®
     * @param longitude ç»åº¦
     * @param latitude  çº¬åº¦
     */
    void updateLocation(Integer memberId, Double longitude, Double latitude);
    /**
     * èŽ·å–å¸æœºç«¯é¦–é¡µä¿¡æ¯
     *
     * @param memberId ä¼šå‘˜ä¸»é”®
     * @return DriverCenterVO
     */
    com.doumee.dao.vo.DriverCenterVO getDriverCenterInfo(Integer memberId);
    /**
     * å¸æœºæŠ¢å•大厅 - åˆ†é¡µæŸ¥è¯¢å¯æŠ¢è®¢å•
     *
     * @param memberId  å½“前登录会员主键
     * @param pageWrap  åˆ†é¡µè¯·æ±‚
     * @return PageData<DriverGrabOrderVO>
     */
    com.doumee.core.model.PageData<com.doumee.dao.vo.DriverGrabOrderVO> grabOrderHall(Integer memberId, PageWrap<DriverGrabOrderDTO> pageWrap);
    /**
     * å¸æœºè¿›è¡Œä¸­è®¢å•列表
     *
     * @param memberId ä¼šå‘˜ä¸»é”®
     * @param dto      æŸ¥è¯¢æ¡ä»¶ï¼ˆstatus: 3=已抢单, 4=派送中)
     * @return è®¢å•列表
     */
    java.util.List<com.doumee.dao.vo.DriverGrabOrderVO> activeOrders(Integer memberId, DriverActiveOrderDTO dto);
    /**
     * å¸æœºç«¯è®¢å•详情
     *
     * @param driverId å¸æœºä¸»é”®
     * @param orderId  è®¢å•主键
     * @return DriverOrderDetailVO
     */
    com.doumee.dao.vo.DriverOrderDetailVO driverOrderDetail(Integer driverId, Integer orderId);
}
server/services/src/main/java/com/doumee/service/business/OrdersService.java
@@ -17,6 +17,7 @@
import com.doumee.dao.vo.OverdueFeeVO;
import com.doumee.dao.vo.PayResponse;
import com.doumee.dao.vo.PriceCalculateVO;
import com.doumee.dao.vo.EstimatedDeliveryResultVO;
import java.math.BigDecimal;
import java.util.List;
@@ -355,4 +356,19 @@
     */
    PageData<MyOrderVO> findShopOrderPage(PageWrap<MyOrderDTO> pageWrap, Integer shopId);
    /**
     * è®¡ç®—两地之间的预计送达时效(标速达 + æžé€Ÿè¾¾ï¼‰
     * é€šè¿‡è…¾è®¯åœ°å›¾API计算实际距离,再根据pricing_rule type=2计算时效
     *
     * @param cityId    åŸŽå¸‚主键
     * @param fromLat   èµ·ç‚¹çº¬åº¦
     * @param fromLng   èµ·ç‚¹ç»åº¦
     * @param toLat     ç»ˆç‚¹çº¬åº¦
     * @param toLng     ç»ˆç‚¹ç»åº¦
     * @return é¢„计送达时效结果
     */
    EstimatedDeliveryResultVO calculateEstimatedDelivery(Integer cityId,
                                                         Double fromLat, Double fromLng,
                                                         Double toLat, Double toLng);
}
server/services/src/main/java/com/doumee/service/business/RevenueService.java
@@ -3,7 +3,9 @@
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.Revenue;
import com.doumee.dao.dto.RevenueQueryDTO;
import com.doumee.dao.vo.RevenueStatisticsVO;
import com.doumee.dao.vo.RevenueSummaryVO;
import java.util.List;
@@ -113,4 +115,40 @@
     */
    RevenueStatisticsVO getDriverRevenueStatistics(Integer memberId);
    /**
     * å¸æœºæµæ°´åˆ†é¡µæŸ¥è¯¢
     *
     * @param pageWrap åˆ†é¡µå‚æ•°
     * @param memberId ä¼šå‘˜ä¸»é”®
     * @return åˆ†é¡µç»“æžœ
     */
    PageData<Revenue> findDriverRevenuePage(PageWrap<RevenueQueryDTO> pageWrap, Integer memberId);
    /**
     * é—¨åº—流水分页查询
     *
     * @param pageWrap åˆ†é¡µå‚æ•°
     * @param shopId   é—¨åº—主键
     * @return åˆ†é¡µç»“æžœ
     */
    PageData<Revenue> findShopRevenuePage(PageWrap<RevenueQueryDTO> pageWrap, Integer shopId);
    /**
     * å¸æœºæµæ°´æ”¶æ”¯ç»Ÿè®¡
     *
     * @param queryDTO æŸ¥è¯¢æ¡ä»¶
     * @param memberId ä¼šå‘˜ä¸»é”®
     * @return æ”¶æ”¯æ±‡æ€»
     */
    RevenueSummaryVO getDriverRevenueSummary(RevenueQueryDTO queryDTO, Integer memberId);
    /**
     * é—¨åº—流水收支统计
     *
     * @param queryDTO æŸ¥è¯¢æ¡ä»¶
     * @param shopId   é—¨åº—主键
     * @return æ”¶æ”¯æ±‡æ€»
     */
    RevenueSummaryVO getShopRevenueSummary(RevenueQueryDTO queryDTO, Integer shopId);
}
server/services/src/main/java/com/doumee/service/business/ShopInfoService.java
@@ -109,7 +109,7 @@
     * @param request ç”³è¯·è¯·æ±‚
     * @param member å½“前登录会员
     */
    void applyShop(ShopApplyDTO request, Member member);
    void applyShop(ShopApplyDTO request);
    /**
     * æŸ¥è¯¢é—¨åº—详情(含附件)
server/services/src/main/java/com/doumee/service/business/impl/AreasServiceImpl.java
@@ -537,4 +537,19 @@
        return list;
    }
    @Override
    public Areas getOpenedCityByName(String cityName) {
        if (StringUtils.isBlank(cityName)) {
            return null;
        }
        QueryWrapper<Areas> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(Areas::getType, Constants.ONE)
                .eq(Areas::getStatus, Constants.ONE)
                .eq(Areas::getIsdeleted, Constants.ZERO)
                .eq(Areas::getName, cityName)
                .last("limit 1");
        return areasMapper.selectOne(qw);
    }
}
server/services/src/main/java/com/doumee/service/business/impl/BannerServiceImpl.java
@@ -229,6 +229,6 @@
    private String getBannerPath() {
        return systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.BANNER_FILES).getCode();
                + systemDictDataBiz.queryByCode(Constants.OSS, Constants.BANNER_FILES).getCode();
    }
}
server/services/src/main/java/com/doumee/service/business/impl/CategoryServiceImpl.java
@@ -140,7 +140,7 @@
        }
        if(StringUtils.isNotBlank(category.getIcon())){
            String path  = systemDictDataBiz.queryByCode(Constants.OSS,Constants.RESOURCE_PATH).getCode()
                    +systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.CATEGORY_FILES).getCode();
                    +systemDictDataBiz.queryByCode(Constants.OSS,Constants.CATEGORY_FILES).getCode();
            category.setIconFull(path + category.getIcon());
        }
        return category;
@@ -209,7 +209,7 @@
        PageData<Category> result =PageData.from(categoryMapper.selectJoinPage(page, Category.class,queryWrapper));
        if(result!=null && result.getRecords()!=null){
            String path  = systemDictDataBiz.queryByCode(Constants.OSS,Constants.RESOURCE_PATH).getCode()
                    +systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.CATEGORY_FILES).getCode();
                    +systemDictDataBiz.queryByCode(Constants.OSS,Constants.CATEGORY_FILES).getCode();
            for(Category cate : result.getRecords()){
                try {
                    if(StringUtils.isNotBlank(cate.getIcon())){
@@ -237,7 +237,7 @@
        );
        if(com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(categoryList)){
            String path  = systemDictDataBiz.queryByCode(Constants.OSS,Constants.RESOURCE_PATH).getCode()
                    +systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.CATEGORY_FILES).getCode();
                    +systemDictDataBiz.queryByCode(Constants.OSS,Constants.CATEGORY_FILES).getCode();
            for (Category category:categoryList) {
                if(StringUtils.isNotBlank(category.getIcon())){
                    category.setIconFull(path + category.getIcon());
@@ -276,7 +276,7 @@
        // 3. æ‹¼æŽ¥å›¾æ ‡å…¨è·¯å¾„
        if (!CollectionUtils.isEmpty(sizeList)) {
            String path = systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.CATEGORY_FILES).getCode();
                    + systemDictDataBiz.queryByCode(Constants.OSS, Constants.CATEGORY_FILES).getCode();
            for (Category cate : sizeList) {
                if (StringUtils.isNotBlank(cate.getIcon())) {
                    cate.setIconFull(path + cate.getIcon());
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java
@@ -17,19 +17,30 @@
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.dao.business.model.Category;
import com.doumee.dao.business.model.DriverInfo;
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.service.business.AliSmsService;
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;
@@ -41,10 +52,8 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
/**
 * å¸æœºæ³¨å†Œä¿¡æ¯Service实现
@@ -77,6 +86,15 @@
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private OrdersMapper ordersMapper;
    @Autowired
    private RevenueMapper revenueMapper;
    @Autowired
    private OrdersDetailMapper ordersDetailMapper;
    @Autowired
    private SystemDictDataBiz systemDictDataBiz;
@@ -289,7 +307,7 @@
            member.setCreateTime(now);
            member.setUpdateTime(now);
            member.setTelephone(telephone);
            member.setNickName(telephone);
            member.setNickName(telephone.substring(0, 3) + "****" + telephone.substring(7));
            member.setName(telephone);
            member.setUserType(Constants.ONE);
            member.setBusinessStatus(Constants.ZERO);
@@ -556,7 +574,7 @@
        String imgPrefix = "";
        try {
            imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.DRIVER_FILES).getCode();
                    + systemDictDataBiz.queryByCode(Constants.OSS, Constants.DRIVER_FILES).getCode();
        } catch (Exception e) {
            // æœªé…ç½®æ—¶å¿½ç•¥
        }
@@ -630,4 +648,534 @@
        }
    }
    @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<DriverInfo>().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<DriverInfo>().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<DriverInfo>().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<DriverInfo>().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<DriverInfo>().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<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);
        // ä»Šæ—¥æŽ¥å•数:今天完成的订单数(acceptDriver=司机主键,状态=已完成)
        Long todayOrderCount = ordersMapper.selectCount(new QueryWrapper<Orders>().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<Orders>().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<Orders>().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<DriverGrabOrderVO> grabOrderHall(Integer memberId, PageWrap<DriverGrabOrderDTO> pageWrap) {
        DriverGrabOrderDTO dto = pageWrap.getModel();
        // 1. èŽ·å–å¸æœºå®šä½
        DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().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(), "司机位置信息缺失,请先开启定位");
        }
        double driverLat = driver.getLatitude();
        double driverLng = driver.getLongitude();
        // 2. é¢„查物品等级对应的物品分类ID
        List<Integer> goodTypeIds = null;
        if (dto != null && dto.getGradeId() != null) {
            List<Category> cats = categoryMapper.selectList(new QueryWrapper<Category>().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<Orders> page = 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")
                // 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);
        // åŠ æ€¥ 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<Orders> result = ordersMapper.selectJoinPage(page, Orders.class, wrapper);
        // 5. æ‰¹é‡æŸ¥è¯¢ç‰©å“æ˜Žç»†
        List<Integer> orderIds = result.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);
            }
        }
        // 6. æž„建VO(使用共用方法)
        Date now = new Date();
        List<DriverGrabOrderVO> voList = new ArrayList<>();
        for (Orders order : result.getRecords()) {
            voList.add(buildDriverOrderVO(order, driverLat, driverLng, true, now, detailMap));
        }
        // 7. æ‰‹åŠ¨åˆ†é¡µ
        PageData<DriverGrabOrderVO> pageData = new PageData<>(result.getCurrent(), result.getSize());
        pageData.setTotal(result.getTotal());
        pageData.setRecords(voList);
        return pageData;
    }
    @Override
    public List<DriverGrabOrderVO> 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<DriverInfo>().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<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")
                .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<Orders> ordersList = ordersMapper.selectJoinList(Orders.class, wrapper);
        // æ‰¹é‡æŸ¥ç‰©å“æ˜Žç»†
        List<Integer> orderIds = ordersList.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);
            }
        }
        // æž„建VO(使用共用方法)
        Date now = new Date();
        List<DriverGrabOrderVO> 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<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")
                .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<OrdersDetail> details = ordersDetailMapper.selectList(
                new QueryWrapper<OrdersDetail>().lambda()
                        .eq(OrdersDetail::getOrderId, orderId));
        Map<Integer, List<OrdersDetail>> 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<DriverOrderDetailVO.OrderItem> 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;
    }
    private List<String> getFileUrls(Integer orderId, int objType, String prefix) {
        List<Multifile> files = multifileMapper.selectList(
                new QueryWrapper<Multifile>().lambda()
                        .eq(Multifile::getObjId, orderId)
                        .eq(Multifile::getObjType, objType)
                        .eq(Multifile::getIsdeleted, Constants.ZERO)
                        .orderByAsc(Multifile::getSortnum));
        List<String> 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 <T> PageData<T> emptyPage(PageWrap<?> pageWrap) {
        PageData<T> 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<Integer, List<OrdersDetail>> 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<OrdersDetail> details = detailMap.getOrDefault(order.getId(), Collections.emptyList());
        List<DriverGrabOrderVO.OrderItem> 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;
    }
}
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java
@@ -286,8 +286,6 @@
            String token = JwtTokenUtil.generateTokenForRedis(member.getId(), Constants.ZERO, JSONObject.toJSONString(member), redisTemplate);
            accountResponse.setToken(token);
            accountResponse.setMember(member);
            // é—¨åº—用户身份时,返回申请的门店状态
            fillShopInfo(accountResponse, member);
            return accountResponse;
        } catch (WxErrorException e) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "微信登录异常!请联系管理员");
@@ -341,8 +339,6 @@
            AccountResponse accountResponse = new AccountResponse();
            accountResponse.setToken(token);
            accountResponse.setMember(member);
            // é—¨åº—用户身份时,返回申请的门店状态
            fillShopInfo(accountResponse, member);
            return accountResponse;
        } catch (Exception e) {
            e.printStackTrace();
@@ -353,15 +349,25 @@
    /**
     * é—¨åº—用户身份时,填充门店审核状态
     */
    private void fillShopInfo(AccountResponse response, Member member) {
    private void fillShopInfo(UserCenterVO userCenterVO, Member member) {
        if (Constants.TWO.equals(member.getUserType())) {
            ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                    .eq(ShopInfo::getRegionMemberId, member.getId())
                    .eq(ShopInfo::getDeleted, Constants.ZERO)
                    .last("limit 1"));
            if (shopInfo != null) {
                response.setShopId(shopInfo.getId());
                response.setShopAuditStatus(shopInfo.getAuditStatus());
                userCenterVO.setShopId(shopInfo.getId());
                userCenterVO.setShopAuditStatus(shopInfo.getAuditStatus());
            }
        }
        // æ ¹æ®openid查询当前绑定的门店
        if (StringUtils.isNotBlank(member.getOpenid())) {
            ShopInfo bindShop = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                    .eq(ShopInfo::getOpenid, member.getOpenid())
                    .eq(ShopInfo::getDeleted, Constants.ZERO)
                    .last("limit 1"));
            if (bindShop != null) {
                userCenterVO.setBindShopId(String.valueOf(bindShop.getId()));
            }
        }
    }
@@ -398,6 +404,7 @@
        userCenterVO.setName(member.getName());
        userCenterVO.setTelephone(member.getTelephone());
        userCenterVO.setCoverImage(member.getCoverImage());
        userCenterVO.setOpenid(member.getOpenid());
        if(StringUtils.isNotBlank(member.getCoverImage())){
            String path  = systemDictDataBiz.queryByCode(Constants.OSS,Constants.RESOURCE_PATH).getCode()
                    +systemDictDataBiz.queryByCode(Constants.OSS,Constants.MEMBER_FILES).getCode();
@@ -417,6 +424,14 @@
                .eq(Orders::getDeleted, Constants.ZERO)
                .in(Orders::getStatus, Arrays.stream(waitReceiveStatuses).boxed().collect(Collectors.toList())));
        userCenterVO.setWaitReceiveCount(waitReceiveCount.intValue());
        // é€€æ¬¾ä¸­è®¢å•数量
        Long refundingCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getMemberId, memberId)
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getStatus, 98));
        userCenterVO.setRefundingCount(refundingCount.intValue());
        // é—¨åº—用户身份时,返回申请的门店状态
        fillShopInfo(userCenterVO, member);
        return userCenterVO;
    }
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java
@@ -33,6 +33,7 @@
import com.doumee.service.business.OrderLogService;
import com.doumee.service.business.OrdersService;
import com.doumee.service.business.AreasService;
import com.doumee.service.business.PricingRuleService;
import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.exception.WxPayException;
@@ -111,6 +112,10 @@
    @Autowired
    private PricingRuleMapper pricingRuleMapper;
    @Autowired
    private PricingRuleService pricingRuleService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
@@ -303,9 +308,9 @@
     */
    @Override
    public PriceCalculateVO calculateLocalPrice(CalculateLocalPriceDTO dto) {
        // å¤©æ•°æ ¡éªŒï¼Œæœ€å°‘1天
        int days = dto.getEstimatedDepositDays() != null && dto.getEstimatedDepositDays() > 0
                ? dto.getEstimatedDepositDays() : 1;
        // æ ¹æ®å¼€å§‹å’Œç»“束时间计算天数,最少1天
        long diffMs = dto.getDepositEndTime().getTime() - dto.getDepositStartTime().getTime();
        int days = (int) Math.max(1, (diffMs / (1000 * 60 * 60 * 24)) + 1);
        // æ”¶é›†æ‰€æœ‰ç‰©å“ç±»åž‹ID
        List<Integer> categoryIds = new ArrayList<>();
@@ -364,9 +369,9 @@
            itemPriceTotal += subtotal;
        }
        // ä¿ä»·è´¹ç”¨ï¼šæŠ¥ä»·é‡‘额 Ã— ä¿ä»·è´¹çއ(字典 INSURANCE_RATE),元→分
        // ä¿ä»·è´¹ç”¨ï¼šæŠ¥ä»·é‡‘额 Ã— ä¿ä»·è´¹çއ(字典 INSURANCE_RATE),元→分(保价金额>0时计费)
        long insuranceFeeFen = 0L;
        if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) {
        if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) {
            BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount());
            insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue();
        }
@@ -504,9 +509,9 @@
            itemPriceTotal += subtotal;
        }
        // 4. ä¿ä»·è´¹ç”¨ï¼šæŠ¥ä»·é‡‘额 Ã— ä¿ä»·è´¹çއ(字典 INSURANCE_RATE),元→分
        // 4. ä¿ä»·è´¹ç”¨ï¼šæŠ¥ä»·é‡‘额 Ã— ä¿ä»·è´¹çއ(字典 INSURANCE_RATE),元→分(保价金额>0时计费)
        long insuranceFeeFen = 0L;
        if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) {
        if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) {
            BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount());
            insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue();
        }
@@ -633,6 +638,11 @@
                takeLgt = BigDecimal.valueOf(takeShop.getLongitude());
                takeLocationValue = takeShop.getAddress();
            } else if (dto.getTakeLat() != null && dto.getTakeLgt() != null && StringUtils.isNotBlank(dto.getTakeLocation())) {
                // æ— å–件门店,校验存件点与自选取件点是否在同一城市
                if (MapUtil.isSameCity(depositShop.getLatitude(), depositShop.getLongitude(),
                        dto.getTakeLat().doubleValue(), dto.getTakeLgt().doubleValue())) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存订单存取点不能在同一城市,如需同城寄存请选择就近门店");
                }
                takeLat = dto.getTakeLat();
                takeLgt = dto.getTakeLgt();
                takeLocationValue = dto.getTakeLocation();
@@ -647,15 +657,12 @@
        // ========== 3. è®¡ç®—费用 ==========
        PriceCalculateVO priceResult;
        if (Constants.ZERO.equals(dto.getType())) {
            // å°±åœ°å¯„存:计算天数
            long diffMs = takeTime.getTime() - depositTime.getTime();
            int days = (int) Math.max(1, (diffMs / (1000 * 60 * 60 * 24)) + 1);
            // å°±åœ°å¯„å­˜
            CalculateLocalPriceDTO priceDTO = new CalculateLocalPriceDTO();
            priceDTO.setCityId(dto.getCityId());
            priceDTO.setEstimatedDepositDays(days);
            priceDTO.setDepositStartTime(depositTime);
            priceDTO.setDepositEndTime(takeTime);
            priceDTO.setItems(dto.getItems());
            priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0);
            priceDTO.setDeclaredAmount(dto.getDeclaredAmount());
            priceResult = calculateLocalPrice(priceDTO);
        } else {
@@ -667,7 +674,6 @@
            priceDTO.setToLat(takeLat);
            priceDTO.setToLgt(takeLgt);
            priceDTO.setItems(dto.getItems());
            priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0);
            priceDTO.setDeclaredAmount(dto.getDeclaredAmount());
            priceDTO.setUrgent(Constants.ONE.equals(dto.getIsUrgent()));
            priceResult = calculateRemotePrice(priceDTO);
@@ -1714,6 +1720,24 @@
        order.setUpdateTime(now);
        // ç”Ÿæˆä¼šå‘˜æ ¸é”€ç 
        order.setMemberVerifyCode(generateVerifyCode());
        // å¼‚地寄存:计算预计送达时间
        if (Constants.ONE.equals(order.getType())
                && order.getDepositLat() != null && order.getDepositLgt() != null
                && order.getTakeLat() != null && order.getTakeLgt() != null) {
            EstimatedDeliveryResultVO deliveryResult = calculateEstimatedDelivery(
                    Integer.valueOf(order.getCityId()),
                    order.getDepositLat().doubleValue(), order.getDepositLgt().doubleValue(),
                    order.getTakeLat().doubleValue(), order.getTakeLgt().doubleValue());
            // isUrgent: 0=标速达; 1=极速达
            BigDecimal hours = Constants.ONE.equals(order.getIsUrgent())
                    ? deliveryResult.getExpressHours()
                    : deliveryResult.getStandardHours();
            if (hours != null) {
                long millis = hours.multiply(new BigDecimal("3600000"))
                        .setScale(0, RoundingMode.HALF_UP).longValue();
                order.setEstimatedDeliveryTime(new Date(now.getTime() + millis));
            }
        }
        ordersMapper.updateById(order);
    }
@@ -2857,4 +2881,29 @@
        return Math.max(days, 0);
    }
    @Override
    public EstimatedDeliveryResultVO calculateEstimatedDelivery(Integer cityId,
                                                                Double fromLat, Double fromLng,
                                                                Double toLat, Double toLng) {
        // è…¾è®¯åœ°å›¾è·ç¦»çŸ©é˜µAPI计算实际距离
        String from = fromLat + "," + fromLng;
        String to = toLat + "," + toLng;
        JSONObject distanceResult = MapUtil.distanceSingle("driving", from, to);
        // èŽ·å–è·ç¦»ï¼ˆç±³ï¼‰ï¼Œè½¬å…¬é‡Œ
        int distanceMeters = distanceResult.getIntValue("distance");
        BigDecimal distanceKm = new BigDecimal(distanceMeters)
                .divide(new BigDecimal("1000"), 2, RoundingMode.HALF_UP);
        // æ ¹æ®pricing_rule type=2 è®¡ç®— æ ‡é€Ÿè¾¾(1) å’Œ æžé€Ÿè¾¾(2) æ—¶æ•ˆ
        BigDecimal standardTime = pricingRuleService.calculateEstimatedTime(cityId, 1, distanceKm);
        BigDecimal expressTime = pricingRuleService.calculateEstimatedTime(cityId, 2, distanceKm);
        EstimatedDeliveryResultVO vo = new EstimatedDeliveryResultVO();
        vo.setDistanceKm(distanceKm);
        vo.setStandardHours(standardTime);
        vo.setExpressHours(expressTime);
        return vo;
    }
}
server/services/src/main/java/com/doumee/service/business/impl/RevenueServiceImpl.java
@@ -18,7 +18,9 @@
import com.doumee.dao.business.model.ShopInfo;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.model.WithdrawalOrders;
import com.doumee.dao.dto.RevenueQueryDTO;
import com.doumee.dao.vo.RevenueStatisticsVO;
import com.doumee.dao.vo.RevenueSummaryVO;
import com.doumee.service.business.RevenueService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -235,4 +237,118 @@
        return vo;
    }
    @Override
    public PageData<Revenue> findDriverRevenuePage(PageWrap<RevenueQueryDTO> pageWrap, Integer memberId) {
        IPage<Revenue> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        QueryWrapper<Revenue> qw = buildRevenueQueryWrapper(pageWrap.getModel(), memberId);
        PageData<Revenue> result = PageData.from(revenueMapper.selectPage(page, qw));
        if (result != null && result.getRecords() != null) {
            for (Revenue model : result.getRecords()) {
                model.setAmountInfo(Constants.getFormatMoney(model.getAmount()));
            }
        }
        return result;
    }
    @Override
    public PageData<Revenue> findShopRevenuePage(PageWrap<RevenueQueryDTO> pageWrap, Integer shopId) {
        ShopInfo shop = shopInfoMapper.selectById(shopId);
        if (shop == null || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        IPage<Revenue> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        QueryWrapper<Revenue> qw = buildRevenueQueryWrapper(pageWrap.getModel(), shop.getRegionMemberId());
        PageData<Revenue> result = PageData.from(revenueMapper.selectPage(page, qw));
        if (result != null && result.getRecords() != null) {
            for (Revenue model : result.getRecords()) {
                model.setAmountInfo(Constants.getFormatMoney(model.getAmount()));
            }
        }
        return result;
    }
    @Override
    public RevenueSummaryVO getDriverRevenueSummary(RevenueQueryDTO queryDTO, Integer memberId) {
        return buildRevenueSummary(queryDTO, memberId);
    }
    @Override
    public RevenueSummaryVO getShopRevenueSummary(RevenueQueryDTO queryDTO, Integer shopId) {
        ShopInfo shop = shopInfoMapper.selectById(shopId);
        if (shop == null || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        return buildRevenueSummary(queryDTO, shop.getRegionMemberId());
    }
    private QueryWrapper<Revenue> buildRevenueQueryWrapper(RevenueQueryDTO query, Integer memberId) {
        QueryWrapper<Revenue> qw = new QueryWrapper<>();
        qw.lambda().eq(Revenue::getDeleted, Constants.ZERO);
        qw.lambda().eq(Revenue::getMemberId, memberId);
        if (query != null) {
            if (query.getOptType() != null) {
                qw.lambda().eq(Revenue::getOptType, query.getOptType());
            }
            if (query.getType() != null) {
                qw.lambda().eq(Revenue::getType, query.getType());
            }
            if (query.getStartTime() != null) {
                qw.lambda().ge(Revenue::getCreateTime, query.getStartTime());
            }
            if (query.getEndTime() != null) {
                qw.lambda().le(Revenue::getCreateTime, Utils.Date.getEnd(query.getEndTime()));
            }
        }
        qw.lambda().orderByDesc(Revenue::getCreateTime);
        return qw;
    }
    private RevenueSummaryVO buildRevenueSummary(RevenueQueryDTO queryDTO, Integer memberId) {
        // æ”¶å…¥
        QueryWrapper<Revenue> incomeQw = new QueryWrapper<>();
        incomeQw.eq("DELETED", Constants.ZERO);
        incomeQw.eq("MEMBER_ID", memberId);
        incomeQw.eq("OPT_TYPE", Constants.ONE);
        if (queryDTO != null) {
            if (queryDTO.getType() != null) {
                incomeQw.eq("TYPE", queryDTO.getType());
            }
            if (queryDTO.getStartTime() != null) {
                incomeQw.ge("CREATE_TIME", queryDTO.getStartTime());
            }
            if (queryDTO.getEndTime() != null) {
                incomeQw.le("CREATE_TIME", Utils.Date.getEnd(queryDTO.getEndTime()));
            }
        }
        incomeQw.select("IFNULL(SUM(AMOUNT), 0) as amount");
        Map<String, Object> incomeResult = revenueMapper.selectMaps(incomeQw).stream().findFirst().orElse(null);
        long totalIncome = incomeResult != null && incomeResult.get("amount") != null
                ? Long.parseLong(incomeResult.get("amount").toString()) : 0L;
        // æ”¯å‡º
        QueryWrapper<Revenue> expenseQw = new QueryWrapper<>();
        expenseQw.eq("DELETED", Constants.ZERO);
        expenseQw.eq("MEMBER_ID", memberId);
        expenseQw.eq("OPT_TYPE", -Constants.ONE);
        if (queryDTO != null) {
            if (queryDTO.getType() != null) {
                expenseQw.eq("TYPE", queryDTO.getType());
            }
            if (queryDTO.getStartTime() != null) {
                expenseQw.ge("CREATE_TIME", queryDTO.getStartTime());
            }
            if (queryDTO.getEndTime() != null) {
                expenseQw.le("CREATE_TIME", Utils.Date.getEnd(queryDTO.getEndTime()));
            }
        }
        expenseQw.select("IFNULL(SUM(AMOUNT), 0) as amount");
        Map<String, Object> expenseResult = revenueMapper.selectMaps(expenseQw).stream().findFirst().orElse(null);
        long totalExpense = expenseResult != null && expenseResult.get("amount") != null
                ? Long.parseLong(expenseResult.get("amount").toString()) : 0L;
        RevenueSummaryVO vo = new RevenueSummaryVO();
        vo.setTotalIncome(totalIncome);
        vo.setTotalExpense(totalExpense);
        return vo;
    }
}
server/services/src/main/java/com/doumee/service/business/impl/ShopInfoServiceImpl.java
@@ -236,8 +236,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void applyShop(ShopApplyDTO request, Member member) {
        Integer memberId = member.getId();
    public void applyShop(ShopApplyDTO request) {
        Member member = memberMapper.selectById(request.getMemberId());
        // 1. æ ¡éªŒé—¨åº—手机号唯一性(shop_info.telephone)
        checkTelephoneUnique(request.getTelephone(), null);
@@ -275,7 +275,7 @@
        // 3. æŸ¥è¯¢è¯¥ä¼šå‘˜æ˜¯å¦å·²æœ‰é—¨åº—记录
        QueryWrapper<ShopInfo> existQw = new QueryWrapper<>();
        existQw.lambda()
                .eq(ShopInfo::getRegionMemberId, memberId)
                .eq(ShopInfo::getRegionMemberId, member.getId())
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .last("limit 1");
        ShopInfo existing = shopInfoMapper.selectOne(existQw);
@@ -317,7 +317,6 @@
            existing.setAliAccount(request.getAliAccount());
            existing.setAuditStatus(Constants.ZERO);
            existing.setUpdateTime(now);
            existing.setUpdateUser(memberId);
            existing.setAuditRemark(null);
            existing.setAuditTime(null);
            existing.setAuditUserId(null);
@@ -351,8 +350,7 @@
            shopInfo.setDeleted(Constants.ZERO);
            shopInfo.setCreateTime(now);
            shopInfo.setUpdateTime(now);
            shopInfo.setCreateUser(memberId);
            shopInfo.setRegionMemberId(memberId);
            shopInfo.setRegionMemberId(member.getId());
            shopInfoMapper.insert(shopInfo);
            shopId = shopInfo.getId();
        }
@@ -662,7 +660,7 @@
        String imgPrefix = "";
        try {
            imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode();
                    + systemDictDataBiz.queryByCode(Constants.OSS, Constants.SHOP_FILES).getCode();
        } catch (Exception e) {
            // æœªé…ç½®æ—¶å¿½ç•¥
        }
@@ -720,7 +718,7 @@
                String memberPrefix = "";
                try {
                    memberPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                            + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.MEMBER_FILES).getCode();
                            + systemDictDataBiz.queryByCode(Constants.OSS, Constants.MEMBER_FILES).getCode();
                } catch (Exception e) {
                    // æœªé…ç½®æ—¶å¿½ç•¥
                }
@@ -761,6 +759,12 @@
            qw.lambda().like(ShopInfo::getName, dto.getName());
        }
        // åŸŽå¸‚筛选(areaId是区县,需匹配其parentId等于城市ID)
        if (dto.getCityId() != null) {
            qw.inSql("AREA_ID",
                    "SELECT id FROM areas WHERE parent_id = " + dto.getCityId() + " AND isdeleted = 0");
        }
        // è·ç¦»ç­›é€‰ï¼ˆå•位:米 â†’ è½¬æ¢ä¸ºkm比较)
        if (distanceMeter != null && distanceMeter > 0) {
            double maxKm = distanceMeter / 1000.0;
@@ -793,6 +797,8 @@
            vo.setShopHours(shop.getShopHours());
            vo.setAddress(shop.getAddress());
            vo.setScore(shop.getScore());
            vo.setLatitude(shop.getLatitude());
            vo.setLongitude(shop.getLongitude());
            // é—¨å¤´ç…§ç¬¬ä¸€å¼ 
            vo.setCoverImg(getFirstImage(shop.getId(), Constants.FileType.STORE_FRONT.getKey(), imgPrefix));
            // è·ç¦»
@@ -823,6 +829,10 @@
        vo.setName(shop.getName());
        vo.setAddress(shop.getAddress());
        vo.setContent(shop.getContent());
        vo.setDepositTypes(shop.getDepositTypes());
        vo.setFeeStandard(shop.getFeeStandard());
        vo.setLatitude(shop.getLatitude());
        vo.setLongitude(shop.getLongitude());
        // é—¨å¤´ç…§ + å†…部照 å…¨è·¯å¾„集合
        String imgPrefix = getShopPrefix();
@@ -911,7 +921,7 @@
    private String getShopPrefix() {
        try {
            return systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode();
                    + systemDictDataBiz.queryByCode(Constants.OSS, Constants.SHOP_FILES).getCode();
        } catch (Exception e) {
            return "";
        }
@@ -965,7 +975,7 @@
        vo.setCoverImg(shop.getCoverImg());
        if (StringUtils.isNotBlank(shop.getCoverImg())) {
            String path = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode();
                    + systemDictDataBiz.queryByCode(Constants.OSS, Constants.SHOP_FILES).getCode();
            vo.setFullCoverImg(path + shop.getCoverImg());
        }
        vo.setHasMessage(false);
@@ -1055,7 +1065,6 @@
        if (shop == null) {
            return null;
        }
        // åˆ›å»ºtoken(generateTokenForRedis å·²è‡ªåŠ¨æ¸…é™¤è¯¥ç”¨æˆ·æ—§token,保证唯一有效)
        String token = JwtTokenUtil.generateTokenForRedis(shop.getId(), Constants.TWO, JSONObject.toJSONString(shop), redisTemplate);
server/services/src/main/java/com/doumee/service/business/impl/SmsrecordServiceImpl.java
@@ -1,7 +1,5 @@
package com.doumee.service.business.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonResponse;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -14,7 +12,7 @@
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.Strings;
import com.doumee.core.utils.Utils;
import com.doumee.core.utils.aliyun.ALiYunSmSUtil;
import com.doumee.core.utils.aliyun.AliSmsService;
import com.doumee.dao.business.SmsrecordMapper;
import com.doumee.dao.business.model.Smsrecord;
import com.doumee.service.business.SmsrecordService;
@@ -179,15 +177,10 @@
        );
        String digits = Strings.randomNumeric(4);
        //发送验证码
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("code",digits);
        CommonResponse response = ALiYunSmSUtil.sendMessage(phone,map);
        if(response.getHttpResponse().isSuccess()){
            JSONObject data = JSONObject.parseObject(response.getData());
            String returnCode = data.getString("Code");
            if(!returnCode.equals("OK")){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"短信发送失败:" + data.getString("Message"));
            }
        String templateParam = "{\"code\":\"" + digits + "\"}";
        Boolean result = AliSmsService.sendSms(phone, "SMS_491325122", templateParam);
        if (!result) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "短信发送失败,请稍后重试");
        }
        //存储短信验证码
        Smsrecord smsrecord = new Smsrecord();
server/services/src/main/java/com/doumee/service/business/impl/WithdrawalOrdersServiceImpl.java
@@ -258,6 +258,22 @@
        update.setUpdateTime(now);
        withdrawalOrdersMapper.updateById(update);
        // æ›´æ–°å…³è”的提现 Revenue è®°å½•状态
        // é€šè¿‡ objId + objType=1(提现业务) + type=1(提现支出) æ‰¾åˆ°åŽŸå§‹æçŽ°æ”¯å‡ºè®°å½•
        Revenue withdrawalRevenue = revenueMapper.selectOne(new QueryWrapper<Revenue>().lambda()
                .eq(Revenue::getObjId, order.getId())
                .eq(Revenue::getObjType, Constants.ONE)
                .eq(Revenue::getType, Constants.ONE)
                .eq(Revenue::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (withdrawalRevenue != null) {
            Integer revenueStatus = Constants.ONE.equals(dto.getStatus()) ? Constants.ZERO : Constants.ONE; // é€šè¿‡=0成功, é©³å›ž=1失败
            revenueMapper.update(new UpdateWrapper<Revenue>().lambda()
                    .set(Revenue::getStatus, revenueStatus)
                    .set(Revenue::getUpdateTime, now)
                    .eq(Revenue::getId, withdrawalRevenue.getId()));
        }
        // é©³å›žæ—¶é€€å›žä½™é¢
        if (Constants.TWO.equals(dto.getStatus())) {
            Long amountFen = order.getAmount() != null ? order.getAmount() : 0L;
@@ -295,7 +311,8 @@
                revenue.setVaildStatus(Constants.ONE); // å·²å…¥è´¦
                revenue.setObjId(order.getId());
                revenue.setObjType(Constants.ONE); // 1=提现业务
                revenue.setStatus(Constants.ZERO);
                revenue.setOrderNo(order.getOutBillNo());
                revenue.setStatus(Constants.ZERO); // 0=成功
                revenue.setDeleted(Constants.ZERO);
                revenue.setCreateTime(now);
                revenueMapper.insert(revenue);
@@ -390,7 +407,8 @@
        revenue.setVaildStatus(Constants.ONE);
        revenue.setObjId(order.getId());
        revenue.setObjType(Constants.ONE);
        revenue.setStatus(Constants.ZERO);
        revenue.setOrderNo(billNo);
        revenue.setStatus(Constants.TWO); // 2=处理中
        revenue.setDeleted(Constants.ZERO);
        revenue.setCreateTime(now);
        revenueMapper.insert(revenue);
@@ -440,7 +458,8 @@
        revenue.setVaildStatus(Constants.ONE); // å·²å…¥è´¦
        revenue.setObjId(order.getId());
        revenue.setObjType(Constants.ONE); // 1=提现业务
        revenue.setStatus(Constants.ZERO);
        revenue.setOrderNo(billNo);
        revenue.setStatus(Constants.TWO); // 2=处理中
        revenue.setDeleted(Constants.ZERO);
        revenue.setCreateTime(now);
        revenueMapper.insert(revenue);
server/services/src/main/resources/application-dev.yml
@@ -87,8 +87,14 @@
    appSecret: 3462fa186da7cb06c544df8d8664b63a
    mchId: 1229817002
    mchKey: u4TSNtv0wFP7WRfnxBgijYOtRhS9FvlM
    notifyUrl: https://test.doumee.cn/dmmall_web_api/web/api/wxPayNotify
    notifyUrl: http://xiaopiqiu2.natapp1.cc/web/api/wxPayNotify
    keyPath: D:\DouMee\dmkjWxcert\apiclient_cert.p12
#    appId: wx6264b4f3a697cbe8
#    appSecret: 23734577e8978138c946b727f0394027
#    mchId: 1629568742
#    mchKey: NJTLJSTZYXZRGScaiwubuzichanbu666
#    notifyUrl: https://test.doumee.cn/dmmall_web_api/web/api/wxPayNotify
#    keyPath: D:\DouMee\gtxljcWxcert\apiclient_cert.p12
alipay:
  pay:
server/web/src/main/java/com/doumee/api/web/ApiController.java
@@ -42,16 +42,8 @@
        return obj != null ? (Integer) obj : null;
    }
    /**
     * èŽ·å–ç™»å½•ç”¨æˆ·å¯¹è±¡ä¿¡æ¯
     * @return
     */
    protected Member getMemberResponse(){
        Object obj = this.getRequest().getAttribute(JwtTokenUtil.MEMBER_INFO);
        if(obj != null){
            return JSONObject.parseObject(obj.toString(),Member.class);
        }
        return null;
    protected Integer getDriverId() {
        Object obj = this.getRequest().getAttribute(JwtTokenUtil.DRIVER_ID);
        return obj != null ? (Integer) obj : null;
    }
}
server/web/src/main/java/com/doumee/api/web/ConfigApi.java
@@ -3,12 +3,13 @@
import com.doumee.core.annotation.LoginRequired;
import com.doumee.core.annotation.trace.Trace;
import com.doumee.core.model.ApiResponse;
import com.doumee.core.utils.Tencent.MapUtil;
import com.doumee.dao.business.model.Areas;
import com.doumee.dao.business.model.Banner;
import com.doumee.dao.business.model.Category;
import com.doumee.dao.dto.CalculateLocalPriceDTO;
import com.doumee.dao.dto.CalculateRemotePriceDTO;
import com.doumee.dao.vo.AccountResponse;
import com.doumee.dao.dto.SameCityCheckDTO;
import com.doumee.dao.vo.PriceCalculateVO;
import com.doumee.dao.vo.PlatformAboutVO;
import com.doumee.service.business.*;
@@ -17,7 +18,6 @@
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -118,10 +118,20 @@
        return ApiResponse.success("操作成功", ordersService.calculateRemotePrice(dto));
    }
    @ApiOperation(value = "校验两个经纬度是否同城", notes = "传入两组经纬度,返回是否在同一城市")
    @PostMapping("/isSameCity")
    public ApiResponse<Boolean> isSameCity(@RequestBody SameCityCheckDTO dto) {
        return ApiResponse.success("操作成功", MapUtil.isSameCity(dto.getLat1(), dto.getLng1(), dto.getLat2(), dto.getLng2()));
    }
    @ApiOperation(value = "根据城市名称查询城市信息", notes = "仅返回已开通的城市,未开通返回空")
    @GetMapping("/getCityByName")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query", dataType = "String", name = "cityName", value = "城市名称", required = true)
    })
    public ApiResponse<Areas> getCityByName(@RequestParam String cityName) {
        return ApiResponse.success("查询成功", areasService.getOpenedCityByName(cityName));
    }
}
server/web/src/main/java/com/doumee/api/web/DriverInfoApi.java
@@ -1,12 +1,20 @@
package com.doumee.api.web;
import com.doumee.core.annotation.LoginDriverRequired;
import com.doumee.core.annotation.LoginRequired;
import com.doumee.core.annotation.trace.Trace;
import com.doumee.core.model.ApiResponse;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.dto.DriverActiveOrderDTO;
import com.doumee.dao.dto.DriverGrabOrderDTO;
import com.doumee.dao.dto.DriverLoginRequest;
import com.doumee.dao.dto.DriverRegisterRequest;
import com.doumee.dao.dto.DriverVerifyRequest;
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.service.business.DriverInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
@@ -15,6 +23,9 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
 * å¸æœºéªŒè¯ç ç™»å½•接口
@@ -56,7 +67,7 @@
        return ApiResponse.success("登录成功", driverInfoService.login(request));
    }
    @LoginRequired
    @LoginDriverRequired
    @Trace
    @ApiOperation(value = "提交实名认证", notes = "初次提交或驳回后修改")
    @PostMapping("/submitVerify")
@@ -64,11 +75,11 @@
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse submitVerify(@RequestBody DriverVerifyRequest request) {
        driverInfoService.submitVerify(this.getMemberId(), request);
        driverInfoService.submitVerify(this.getDriverId(), request);
        return ApiResponse.success("提交成功");
    }
    @LoginRequired
    @LoginDriverRequired
    @Trace
    @ApiOperation(value = "查询实名认证详情", notes = "司机端查询")
    @GetMapping("/verifyDetail")
@@ -76,7 +87,79 @@
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse verifyDetail() {
        return ApiResponse.success(driverInfoService.getVerifyDetail(this.getMemberId()));
        return ApiResponse.success(driverInfoService.getVerifyDetail(this.getDriverId()));
    }
    @LoginDriverRequired
    @Trace
    @ApiOperation(value = "切换接单状态", notes = "0=未接单;1=接单中")
    @GetMapping("/updateAcceptingStatus")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
            @ApiImplicitParam(paramType = "query", dataType = "int", name = "status", value = "接单状态:0=未接单;1=接单中", required = true)
    })
    public ApiResponse updateAcceptingStatus(@RequestParam Integer status) {
        driverInfoService.updateAcceptingStatus(this.getDriverId(), status);
        return ApiResponse.success("操作成功");
    }
    @LoginDriverRequired
    @Trace
    @ApiOperation(value = "更新实时定位", notes = "司机端上报当前位置经纬度")
    @GetMapping("/updateLocation")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
            @ApiImplicitParam(paramType = "query", dataType = "Double", name = "longitude", value = "经度", required = true),
            @ApiImplicitParam(paramType = "query", dataType = "Double", name = "latitude", value = "纬度", required = true)
    })
    public ApiResponse updateLocation(@RequestParam Double longitude, @RequestParam Double latitude) {
        driverInfoService.updateLocation(this.getDriverId(), longitude, latitude);
        return ApiResponse.success("操作成功");
    }
    @LoginDriverRequired
    @Trace
    @ApiOperation(value = "司机端首页信息", notes = "姓名、头像、车牌号、评分、今日佣金、今日接单数、余额")
    @GetMapping("/centerInfo")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse<DriverCenterVO> centerInfo() {
        return ApiResponse.success("操作成功", driverInfoService.getDriverCenterInfo(this.getDriverId()));
    }
    @LoginDriverRequired
    @Trace
    @ApiOperation(value = "司机抢单大厅", notes = "分页查询可抢的异地寄存订单")
    @PostMapping("/grabOrderHall")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse<PageData<DriverGrabOrderVO>> grabOrderHall(@RequestBody PageWrap<DriverGrabOrderDTO> pageWrap) {
        return ApiResponse.success("操作成功", driverInfoService.grabOrderHall(this.getDriverId(), pageWrap));
    }
    @LoginDriverRequired
    @Trace
    @ApiOperation(value = "司机进行中订单", notes = "查询已抢单(status=3)或派送中(status=4)的订单列表")
    @PostMapping("/activeOrders")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse<List<DriverGrabOrderVO>> activeOrders(@RequestBody @Valid DriverActiveOrderDTO dto) {
        return ApiResponse.success("操作成功", driverInfoService.activeOrders(this.getDriverId(), dto));
    }
    @LoginDriverRequired
    @Trace
    @ApiOperation(value = "司机端订单详情", notes = "根据订单主键查询详情")
    @GetMapping("/orderDetail")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
            @ApiImplicitParam(paramType = "query", dataType = "Integer", name = "orderId", value = "订单主键", required = true)
    })
    public ApiResponse<DriverOrderDetailVO> orderDetail(@RequestParam Integer orderId) {
        return ApiResponse.success("操作成功", driverInfoService.driverOrderDetail(this.getDriverId(), orderId));
    }
}
server/web/src/main/java/com/doumee/api/web/RevenueApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package com.doumee.api.web;
import com.doumee.core.annotation.LoginRequired;
import com.doumee.core.annotation.LoginShopRequired;
import com.doumee.core.annotation.trace.Trace;
import com.doumee.core.model.ApiResponse;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.Revenue;
import com.doumee.dao.dto.RevenueQueryDTO;
import com.doumee.dao.vo.RevenueSummaryVO;
import com.doumee.service.business.RevenueService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@Api(tags = "交易流水")
@Trace(exclude = true)
@RestController
@RequestMapping("/web/revenue")
@Slf4j
public class RevenueApi extends ApiController {
    @Autowired
    private RevenueService revenueService;
    @LoginRequired
    @ApiOperation(value = "司机流水分页", notes = "小程序端")
    @PostMapping("/driverPage")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<PageData<Revenue>> driverPage(@RequestBody @Validated PageWrap<RevenueQueryDTO> pageWrap) {
        return ApiResponse.success("查询成功", revenueService.findDriverRevenuePage(pageWrap, getMemberId()));
    }
    @LoginRequired
    @ApiOperation(value = "司机流水收支统计", notes = "根据查询条件统计收入与支出")
    @PostMapping("/driverSummary")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<RevenueSummaryVO> driverSummary(@RequestBody RevenueQueryDTO queryDTO) {
        return ApiResponse.success("查询成功", revenueService.getDriverRevenueSummary(queryDTO, getMemberId()));
    }
    @LoginShopRequired
    @ApiOperation(value = "门店流水分页", notes = "小程序端")
    @PostMapping("/shopPage")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "门店token值", required = true),
    })
    public ApiResponse<PageData<Revenue>> shopPage(@RequestBody @Validated PageWrap<RevenueQueryDTO> pageWrap) {
        return ApiResponse.success("查询成功", revenueService.findShopRevenuePage(pageWrap, getShopId()));
    }
    @LoginShopRequired
    @ApiOperation(value = "门店流水收支统计", notes = "根据查询条件统计收入与支出")
    @PostMapping("/shopSummary")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "门店token值", required = true),
    })
    public ApiResponse<RevenueSummaryVO> shopSummary(@RequestBody RevenueQueryDTO queryDTO) {
        return ApiResponse.success("查询成功", revenueService.getShopRevenueSummary(queryDTO, getShopId()));
    }
}
server/web/src/main/java/com/doumee/api/web/ShopInfoApi.java
@@ -45,8 +45,8 @@
    @ApiOperation("门店入驻申请/修改")
    @PostMapping("/apply")
    public ApiResponse apply(@RequestBody @Validated ShopApplyDTO request) {
        Member member = this.getMemberResponse();
        shopInfoService.applyShop(request, member);
        request.setMemberId(getMemberId());
        shopInfoService.applyShop(request);
        return ApiResponse.success("操作成功");
    }
server/web/src/main/java/com/doumee/api/web/WalletApi.java
@@ -5,6 +5,7 @@
import com.doumee.core.model.ApiResponse;
import com.doumee.dao.dto.WithdrawalDTO;
import com.doumee.dao.vo.RevenueStatisticsVO;
import com.doumee.dao.business.model.WithdrawalOrders;
import com.doumee.service.business.RevenueService;
import com.doumee.service.business.WithdrawalOrdersService;
import io.swagger.annotations.Api;
@@ -14,6 +15,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -80,4 +82,15 @@
        return ApiResponse.success("查询成功", revenueService.getDriverRevenueStatistics(getMemberId()));
    }
    @LoginRequired
    @ApiOperation(value = "提现详情", notes = "根据提现主键查询详情(含审批人信息)")
    @GetMapping("/detail/{id}")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
            @ApiImplicitParam(paramType = "path", dataType = "Integer", name = "id", value = "提现记录主键", required = true)
    })
    public ApiResponse<WithdrawalOrders> detail(@PathVariable Integer id) {
        return ApiResponse.success("查询成功", withdrawalOrdersService.findById(id));
    }
}
server/web/src/main/resources/application.yml
@@ -12,7 +12,7 @@
spring:
  profiles:
    active: pro
    active: dev
  # JSON返回配置
  jackson:
    # é»˜è®¤æ—¶åŒº