Commit 5ca65d380e55671e04479855cada58a8b3328387

Authored by shaofan
1 parent 66413a4f

重构开放平台门店同步逻辑,支持外部指定配送费

src/main/java/com/diligrp/rider/controller/OpenStoreController.java
... ... @@ -43,7 +43,7 @@ public class OpenStoreController {
43 43 @RequestParam(required = false) Long cityId,
44 44 @RequestParam(defaultValue = "1") int page) {
45 45 String appKey = request.getHeader("X-App-Key");
46   - return Result.success(merchantService.storeList(cityId, null, page));
  46 + return Result.success(merchantService.storeListByApp(appKey, cityId, page));
47 47 }
48 48  
49 49 /**
... ...
src/main/java/com/diligrp/rider/dto/DeliveryOrderCreateDTO.java
1 1 package com.diligrp.rider.dto;
2 2  
  3 +import jakarta.validation.constraints.DecimalMin;
3 4 import jakarta.validation.constraints.NotBlank;
4   -import jakarta.validation.constraints.NotNull;
5 5 import lombok.Data;
6 6  
7 7 import java.math.BigDecimal;
... ... @@ -29,10 +29,10 @@ public class DeliveryOrderCreateDTO {
29 29 /** 发货门店地址 */
30 30 private String storeAddr;
31 31  
32   - /** 发货门店经度(extStoreId 为空时必填) */
  32 + /** 发货门店经度(outStoreId 无法补齐时必填) */
33 33 private String storeLng;
34 34  
35   - /** 发货门店纬度(extStoreId 为空时必填) */
  35 + /** 发货门店纬度(outStoreId 无法补齐时必填) */
36 36 private String storeLat;
37 37  
38 38 /** 收件人姓名 */
... ... @@ -59,6 +59,10 @@ public class DeliveryOrderCreateDTO {
59 59 @NotBlank(message = "外部订单号不能为空")
60 60 private String outOrderNo;
61 61  
  62 + /** 外部指定配送费;不传则按系统规则计算 */
  63 + @DecimalMin(value = "0.00", message = "配送费不能小于0")
  64 + private BigDecimal deliveryFee;
  65 +
62 66 /** 货物重量(kg),用于重量计费,0=不计重 */
63 67 private BigDecimal weight = BigDecimal.ZERO;
64 68  
... ...
src/main/java/com/diligrp/rider/entity/ExtStore.java deleted 100644 → 0
1   -package com.diligrp.rider.entity;
2   -
3   -import com.baomidou.mybatisplus.annotation.*;
4   -import lombok.Data;
5   -
6   -/**
7   - * 外部门店表
8   - * 接入方通过 API 注册/同步自己系统的门店到配送中台
9   - * 与 AppKey 绑定,一个应用可有多个门店
10   - */
11   -@Data
12   -@TableName("ext_store")
13   -public class ExtStore {
14   -
15   - @TableId(type = IdType.AUTO)
16   - private Long id;
17   -
18   - /** 所属应用 AppKey */
19   - private String appKey;
20   -
21   - /** 接入方自己系统的门店ID(外部系统的原始ID,用于对账) */
22   - private String outStoreId;
23   -
24   - /** 门店名称 */
25   - private String name;
26   -
27   - /** 门店地址 */
28   - private String address;
29   -
30   - /** 经度 */
31   - private String lng;
32   -
33   - /** 纬度 */
34   - private String lat;
35   -
36   - /** 所属城市ID */
37   - private Long cityId;
38   -
39   - /** 门店联系电话 */
40   - private String phone;
41   -
42   - /** 营业状态:0=关闭 1=营业 */
43   - private Integer status;
44   -
45   - /** 备注 */
46   - private String remark;
47   -
48   - private Long createTime;
49   -
50   - private Long updateTime;
51   -}
src/main/java/com/diligrp/rider/entity/Orders.java
... ... @@ -123,9 +123,6 @@ public class Orders {
123 123 /** 货物整单备注(如:放门口、不要辣) */
124 124 private String itemRemark;
125 125  
126   - /** 关联外部门店ID(接入方在平台注册的门店) */
127   - private Long extStoreId;
128   -
129 126 /** 是否删除 */
130 127 @TableLogic
131 128 private Integer isDel;
... ...
src/main/java/com/diligrp/rider/mapper/ExtStoreMapper.java deleted 100644 → 0
1   -package com.diligrp.rider.mapper;
2   -
3   -import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4   -import com.diligrp.rider.entity.ExtStore;
5   -import org.apache.ibatis.annotations.Mapper;
6   -
7   -@Mapper
8   -public interface ExtStoreMapper extends BaseMapper<ExtStore> {
9   -}
src/main/java/com/diligrp/rider/service/ExtStoreService.java deleted 100644 → 0
1   -package com.diligrp.rider.service;
2   -
3   -import com.diligrp.rider.entity.ExtStore;
4   -
5   -import java.util.List;
6   -
7   -public interface ExtStoreService {
8   - /** 同步门店(新增或更新,以 appKey+outStoreId 为唯一键) */
9   - ExtStore syncStore(String appKey, ExtStore store);
10   - /** 查询某应用下的门店列表 */
11   - List<ExtStore> listByApp(String appKey);
12   - /** 获取单个门店(appKey为null时不校验归属) */
13   - ExtStore getById(Long id, String appKey);
14   - /** 设置门店状态 */
15   - void setStatus(Long id, String appKey, int status);
16   - /** 删除门店 */
17   - void delete(Long id, String appKey);
18   - /** 平台管理端:查看所有门店(可按 appKey/cityId 过滤) */
19   - List<ExtStore> listAll(String appKey, Long cityId, int page);
20   -}
src/main/java/com/diligrp/rider/service/MerchantService.java
... ... @@ -12,6 +12,7 @@ public interface MerchantService {
12 12 void editStore(MerchantStoreDTO dto);
13 13 void editStore(MerchantStoreDTO dto, Long scopedCityId);
14 14 List<MerchantStore> storeList(Long cityId, String keyword, int page);
  15 + List<MerchantStore> storeListByApp(String appKey, Long cityId, int page);
15 16 MerchantStore getStore(Long storeId);
16 17 MerchantStore getStore(Long storeId, Long scopedCityId);
17 18 void setOperatingState(Long storeId, int state);
... ...
src/main/java/com/diligrp/rider/service/impl/DeliveryOrderServiceImpl.java
... ... @@ -27,6 +27,7 @@ import org.springframework.stereotype.Service;
27 27 import org.springframework.transaction.annotation.Transactional;
28 28  
29 29 import java.math.BigDecimal;
  30 +import java.math.RoundingMode;
30 31 import java.time.LocalDate;
31 32 import java.time.format.DateTimeFormatter;
32 33 import java.util.List;
... ... @@ -97,7 +98,10 @@ public class DeliveryOrderServiceImpl implements DeliveryOrderService {
97 98 feeDTO.setWeight(dto.getWeight());
98 99 feeDTO.setPieces(calcPieces(dto.getItems()));
99 100 feeDTO.setServiceTime(dto.getServiceTime());
100   - DeliveryFeeResultVO fee = deliveryFeeService.calcFee(feeDTO);
  101 + DeliveryFeeResultVO computedFee = deliveryFeeService.calcFee(feeDTO);
  102 + DeliveryFeeResultVO fee = dto.getDeliveryFee() != null
  103 + ? buildExternalFee(dto.getDeliveryFee(), computedFee)
  104 + : computedFee;
101 105  
102 106 // 4. 生成订单号
103 107 String orderNo = generateOrderNo();
... ... @@ -111,11 +115,16 @@ public class DeliveryOrderServiceImpl implements DeliveryOrderService {
111 115 extra.put("weight", dto.getWeight());
112 116 extra.put("pieces", feeDTO.getPieces());
113 117 extra.put("remark", dto.getRemark());
  118 + extra.put("feeSource", dto.getDeliveryFee() != null ? "external" : "computed");
  119 + if (dto.getDeliveryFee() != null) {
  120 + extra.put("externalDeliveryFee", fee.getTotalFee());
  121 + }
114 122 extra.put("computed", Map.of(
115   - "money_basic", fee.getMoneyBasic(),
116   - "money_distance", fee.getMoneyDistance(),
117   - "money_piece", fee.getMoneyPiece(),
118   - "money_time", fee.getMoneyTime()
  123 + "money_basic", computedFee.getMoneyBasic(),
  124 + "money_distance", computedFee.getMoneyDistance(),
  125 + "money_piece", computedFee.getMoneyPiece(),
  126 + "money_time", computedFee.getMoneyTime(),
  127 + "total_fee", computedFee.getTotalFee()
119 128 ));
120 129 String extraJson;
121 130 try {
... ... @@ -162,7 +171,6 @@ public class DeliveryOrderServiceImpl implements DeliveryOrderService {
162 171 } catch (Exception ignored) {}
163 172 }
164 173 order.setItemRemark(dto.getItemRemark());
165   - order.setExtStoreId(null); // 改为通过 outStoreId 关联,不存具体ID
166 174 order.setRiderId(0L);
167 175 order.setOldRiderId(0L);
168 176 order.setIsTrans(0);
... ... @@ -237,6 +245,24 @@ public class DeliveryOrderServiceImpl implements DeliveryOrderService {
237 245 }
238 246 }
239 247  
  248 + private DeliveryFeeResultVO buildExternalFee(BigDecimal deliveryFee, DeliveryFeeResultVO computedFee) {
  249 + BigDecimal actualFee = deliveryFee.setScale(2, RoundingMode.HALF_UP);
  250 + DeliveryFeeResultVO feeVO = new DeliveryFeeResultVO();
  251 + feeVO.setMoneyBasic(actualFee);
  252 + feeVO.setMoneyDistance(BigDecimal.ZERO);
  253 + feeVO.setMoneyWeight(BigDecimal.ZERO);
  254 + feeVO.setMoneyPiece(BigDecimal.ZERO);
  255 + feeVO.setMoneyTime(BigDecimal.ZERO);
  256 + feeVO.setTotalFee(actualFee);
  257 + feeVO.setDistance(computedFee.getDistance());
  258 + feeVO.setEstimatedMinutes(computedFee.getEstimatedMinutes());
  259 + feeVO.setWeight(computedFee.getWeight());
  260 + feeVO.setPieces(computedFee.getPieces());
  261 + feeVO.setMinFee(computedFee.getMinFee());
  262 + feeVO.setMinFeeApplied(0);
  263 + return feeVO;
  264 + }
  265 +
240 266 private DeliveryFeeResultVO buildFeeVO(Orders order) {
241 267 DeliveryFeeResultVO feeVO = new DeliveryFeeResultVO();
242 268 feeVO.setMoneyBasic(order.getMoneyDelivery());
... ...
src/main/java/com/diligrp/rider/service/impl/ExtStoreServiceImpl.java deleted 100644 → 0
1   -package com.diligrp.rider.service.impl;
2   -
3   -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4   -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
5   -import com.diligrp.rider.common.exception.BizException;
6   -import com.diligrp.rider.entity.ExtStore;
7   -import com.diligrp.rider.mapper.ExtStoreMapper;
8   -import com.diligrp.rider.service.ExtStoreService;
9   -import lombok.RequiredArgsConstructor;
10   -import org.springframework.stereotype.Service;
11   -
12   -import java.util.List;
13   -
14   -@Service
15   -@RequiredArgsConstructor
16   -public class ExtStoreServiceImpl implements ExtStoreService {
17   -
18   - private final ExtStoreMapper extStoreMapper;
19   -
20   - @Override
21   - public ExtStore syncStore(String appKey, ExtStore store) {
22   - store.setAppKey(appKey);
23   - long now = System.currentTimeMillis() / 1000;
24   -
25   - // 根据 appKey + outStoreId 判断是新增还是更新
26   - ExtStore existing = extStoreMapper.selectOne(new LambdaQueryWrapper<ExtStore>()
27   - .eq(ExtStore::getAppKey, appKey)
28   - .eq(ExtStore::getOutStoreId, store.getOutStoreId())
29   - .last("LIMIT 1"));
30   -
31   - if (existing == null) {
32   - store.setStatus(store.getStatus() != null ? store.getStatus() : 1);
33   - store.setCreateTime(now);
34   - store.setUpdateTime(now);
35   - extStoreMapper.insert(store);
36   - } else {
37   - store.setId(existing.getId());
38   - store.setUpdateTime(now);
39   - extStoreMapper.updateById(store);
40   - }
41   - return extStoreMapper.selectById(store.getId());
42   - }
43   -
44   - @Override
45   - public List<ExtStore> listByApp(String appKey) {
46   - return extStoreMapper.selectList(new LambdaQueryWrapper<ExtStore>()
47   - .eq(ExtStore::getAppKey, appKey)
48   - .eq(ExtStore::getStatus, 1)
49   - .orderByDesc(ExtStore::getId));
50   - }
51   -
52   - @Override
53   - public ExtStore getById(Long id, String appKey) {
54   - ExtStore store = extStoreMapper.selectById(id);
55   - if (store == null) throw new BizException("门店不存在");
56   - if (appKey != null && !appKey.equals(store.getAppKey())) throw new BizException("无权访问该门店");
57   - return store;
58   - }
59   -
60   - @Override
61   - public void setStatus(Long id, String appKey, int status) {
62   - ExtStore store = getById(id, appKey);
63   - extStoreMapper.update(null, new LambdaUpdateWrapper<ExtStore>()
64   - .eq(ExtStore::getId, id)
65   - .set(ExtStore::getStatus, status)
66   - .set(ExtStore::getUpdateTime, System.currentTimeMillis() / 1000));
67   - }
68   -
69   - @Override
70   - public void delete(Long id, String appKey) {
71   - getById(id, appKey);
72   - extStoreMapper.deleteById(id);
73   - }
74   -
75   - // 平台管理端:不限 appKey 查看所有门店
76   - @Override
77   - public List<ExtStore> listAll(String appKey, Long cityId, int page) {
78   - LambdaQueryWrapper<ExtStore> wrapper = new LambdaQueryWrapper<ExtStore>()
79   - .orderByDesc(ExtStore::getId);
80   - if (appKey != null && !appKey.isBlank()) wrapper.eq(ExtStore::getAppKey, appKey);
81   - if (cityId != null && cityId > 0) wrapper.eq(ExtStore::getCityId, cityId);
82   - int offset = (page - 1) * 20;
83   - wrapper.last("LIMIT " + offset + ",20");
84   - return extStoreMapper.selectList(wrapper);
85   - }
86   -}
src/main/java/com/diligrp/rider/service/impl/MerchantServiceImpl.java
... ... @@ -6,10 +6,12 @@ import com.diligrp.rider.common.exception.BizException;
6 6 import com.diligrp.rider.dto.MerchantStoreDTO;
7 7 import com.diligrp.rider.entity.MerchantStore;
8 8 import com.diligrp.rider.entity.MerchantUsers;
  9 +import com.diligrp.rider.entity.OpenApp;
9 10 import com.diligrp.rider.mapper.MerchantStoreMapper;
10 11 import com.diligrp.rider.mapper.MerchantUsersMapper;
11 12 import com.diligrp.rider.service.CityService;
12 13 import com.diligrp.rider.service.MerchantService;
  14 +import com.diligrp.rider.service.OpenAppService;
13 15 import lombok.RequiredArgsConstructor;
14 16 import org.springframework.stereotype.Service;
15 17 import org.springframework.transaction.annotation.Transactional;
... ... @@ -24,6 +26,7 @@ public class MerchantServiceImpl implements MerchantService {
24 26 private final MerchantStoreMapper storeMapper;
25 27 private final MerchantUsersMapper usersMapper;
26 28 private final CityService cityService;
  29 + private final OpenAppService openAppService;
27 30  
28 31 private static final int PAGE_SIZE = 20;
29 32  
... ... @@ -95,6 +98,21 @@ public class MerchantServiceImpl implements MerchantService {
95 98 }
96 99  
97 100 @Override
  101 + public List<MerchantStore> storeListByApp(String appKey, Long cityId, int page) {
  102 + if (appKey == null || appKey.isBlank()) throw new BizException("AppKey不能为空");
  103 + LambdaQueryWrapper<MerchantStore> wrapper = new LambdaQueryWrapper<MerchantStore>()
  104 + .eq(MerchantStore::getAppKey, appKey)
  105 + .eq(MerchantStore::getIsDel, 0)
  106 + .orderByAsc(MerchantStore::getListOrder)
  107 + .orderByDesc(MerchantStore::getId);
  108 + if (cityId != null && cityId > 0) wrapper.eq(MerchantStore::getCityId, cityId);
  109 + int currentPage = Math.max(page, 1);
  110 + int offset = (currentPage - 1) * PAGE_SIZE;
  111 + wrapper.last("LIMIT " + offset + "," + PAGE_SIZE);
  112 + return storeMapper.selectList(wrapper);
  113 + }
  114 +
  115 + @Override
98 116 public MerchantStore getStore(Long storeId) {
99 117 return getStore(storeId, null);
100 118 }
... ... @@ -157,7 +175,12 @@ public class MerchantServiceImpl implements MerchantService {
157 175 public MerchantStore syncStore(String appKey, MerchantStoreDTO dto) {
158 176 if (appKey == null || appKey.isBlank()) throw new BizException("AppKey不能为空");
159 177 if (dto.getOutStoreId() == null || dto.getOutStoreId().isBlank()) throw new BizException("外部门店编号不能为空");
160   - ensureCityExists(dto.getCityId());
  178 +
  179 + OpenApp app = openAppService.getByAppKey(appKey);
  180 + if (app == null) throw new BizException("开放平台应用不存在");
  181 + if (app.getCityId() == null || app.getCityId() < 1) throw new BizException("该应用未绑定城市/租户,请联系平台管理员");
  182 + dto.setCityId(app.getCityId());
  183 + ensureCityExists(app.getCityId());
161 184  
162 185 long now = System.currentTimeMillis() / 1000;
163 186 MerchantStore existing = getByOutStoreId(appKey, dto.getOutStoreId());
... ...
src/main/resources/schema.sql
... ... @@ -348,9 +348,11 @@ CREATE TABLE `merchant_store` (
348 348 `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
349 349 `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除',
350 350 `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
  351 + `sync_app_key` VARCHAR(32) GENERATED ALWAYS AS (NULLIF(`app_key`, '')) STORED COMMENT '外部同步唯一键AppKey',
  352 + `sync_out_store_id` VARCHAR(64) GENERATED ALWAYS AS (NULLIF(`out_store_id`, '')) STORED COMMENT '外部同步唯一键门店ID',
351 353 PRIMARY KEY (`id`),
352 354 KEY `idx_city_id` (`city_id`),
353   - KEY `idx_app_out_store` (`app_key`, `out_store_id`),
  355 + UNIQUE KEY `uk_app_out_store` (`sync_app_key`, `sync_out_store_id`, `is_del`),
354 356 KEY `idx_order_del` (`list_order`, `is_del`)
355 357 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家店铺表';
356 358  
... ... @@ -504,30 +506,9 @@ INSERT INTO `orders_refund_reason` (`name`, `role`, `list_order`) VALUES
504 506 ('无法完成配送', 2, 2),
505 507 ('其他原因', 2, 99);
506 508  
507   --- 外部门店表(接入方通过开放平台同步自己系统的门店)
508   -CREATE TABLE `ext_store` (
509   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
510   - `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '所属应用AppKey',
511   - `out_store_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '接入方门店原始ID',
512   - `name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '门店名称',
513   - `address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '门店地址',
514   - `lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '经度',
515   - `lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '纬度',
516   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属城市ID',
517   - `phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '联系电话',
518   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=关闭 1=营业',
519   - `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
520   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
521   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
522   - PRIMARY KEY (`id`),
523   - UNIQUE KEY `uk_app_store` (`app_key`, `out_store_id`),
524   - KEY `idx_city_id` (`city_id`)
525   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外部门店表';
526   -
527 509 -- orders 表补充货物快照字段
528 510 ALTER TABLE `orders` ADD COLUMN `items_json` TEXT COMMENT '货物清单快照JSON' AFTER `callback_url`;
529 511 ALTER TABLE `orders` ADD COLUMN `item_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '整单货物备注' AFTER `items_json`;
530   -ALTER TABLE `orders` ADD COLUMN `ext_store_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联外部门店ID' AFTER `item_remark`;
531 512  
532 513 -- orders 表补充调度字段
533 514 ALTER TABLE `orders` ADD COLUMN `dispatch_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '系统派单时间' AFTER `trans_time`;
... ...