Commit 548c125fc08f22c28c8ebb13f55de6f29cccbbb9

Authored by shaofan
1 parent 5ca65d38

新增骑手到店上报功能,包含到店时间、经纬度及距离字段,完善订单处理逻辑以支持该功能

src/main/java/com/diligrp/rider/controller/RiderOrderController.java
... ... @@ -23,6 +23,8 @@ public class RiderOrderController {
23 23 static class OrderCompleteReq { private Long orderId; private String thumbs; }
24 24 @Data
25 25 static class OrderStartReq { private Long orderId; private String code; }
  26 + @Data
  27 + static class OrderArriveShopReq { private Long orderId; private String lng; private String lat; }
26 28  
27 29 /**
28 30 * 订单列表
... ... @@ -70,6 +72,14 @@ public class RiderOrderController {
70 72 return Result.success();
71 73 }
72 74  
  75 + /** 到店上报 */
  76 + @PostMapping("/arrive-shop")
  77 + public Result<Void> arriveShop(@RequestBody OrderArriveShopReq req, HttpServletRequest request) {
  78 + Long riderId = (Long) request.getAttribute("riderId");
  79 + orderService.arriveShop(riderId, req.getOrderId(), req.getLng(), req.getLat());
  80 + return Result.success();
  81 + }
  82 +
73 83 /** 完成订单,上传照片 */
74 84 @PostMapping("/complete")
75 85 public Result<Void> complete(@RequestBody OrderCompleteReq req, HttpServletRequest request) {
... ...
src/main/java/com/diligrp/rider/entity/Orders.java
... ... @@ -139,6 +139,18 @@ public class Orders {
139 139 /** 取件时间 */
140 140 private Long pickTime;
141 141  
  142 + /** 骑手到店时间 */
  143 + private Long arriveShopTime;
  144 +
  145 + /** 骑手到店经度 */
  146 + private String arriveShopLng;
  147 +
  148 + /** 骑手到店纬度 */
  149 + private String arriveShopLat;
  150 +
  151 + /** 骑手到店距离门店距离,单位米 */
  152 + private BigDecimal arriveShopDistance;
  153 +
142 154 /** 完成时间 */
143 155 private Long completeTime;
144 156  
... ...
src/main/java/com/diligrp/rider/service/RiderOrderService.java
... ... @@ -17,6 +17,8 @@ public interface RiderOrderService {
17 17 void grap(Long riderId, Long cityId, Long orderId);
18 18 /** 开始服务(取件),输入完成码 */
19 19 void start(Long riderId, Long orderId, String code);
  20 + /** 到店上报 */
  21 + void arriveShop(Long riderId, Long orderId, String lng, String lat);
20 22 /** 完成订单,上传照片 */
21 23 void complete(Long riderId, Long orderId, String thumbsJson);
22 24 /** 骑手申请转单 */
... ...
src/main/java/com/diligrp/rider/service/impl/AdminRiderServiceImpl.java
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.auth.AdminScopeGuard;
6   -import com.diligrp.rider.common.exception.BizException;
7   -import com.diligrp.rider.dto.AdminRiderAddDTO;
8   -import com.diligrp.rider.entity.Orders;
9   -import com.diligrp.rider.entity.Rider;
10   -import com.diligrp.rider.entity.RiderLevel;
11   -import com.diligrp.rider.mapper.OrdersMapper;
12   -import com.diligrp.rider.mapper.RiderBalanceMapper;
13   -import com.diligrp.rider.mapper.RiderLevelMapper;
14   -import com.diligrp.rider.mapper.RiderMapper;
15   -import com.diligrp.rider.entity.*;
16   -import com.diligrp.rider.mapper.*;
17   -import com.diligrp.rider.service.AdminRiderService;
18   -import com.diligrp.rider.service.DeliveryOrderService;
19   -import com.diligrp.rider.service.RiderHoldLimitService;
20   -import com.diligrp.rider.service.WebhookService;
21   -import com.diligrp.rider.vo.DeliveryOrderCreateVO;
22   -import com.fasterxml.jackson.databind.ObjectMapper;
23   -import lombok.RequiredArgsConstructor;
24   -import lombok.extern.slf4j.Slf4j;
25   -import org.springframework.stereotype.Service;
26   -import org.springframework.transaction.annotation.Transactional;
27   -import org.springframework.util.DigestUtils;
28   -
29   -import java.math.BigDecimal;
30   -import java.nio.charset.StandardCharsets;
31   -import java.util.HashMap;
32   -import java.util.List;
33   -import java.util.Map;
34   -import java.util.Set;
35   -import java.util.stream.Collectors;
36   -
37   -@Slf4j
38   -@Service
39   -@RequiredArgsConstructor
40   -public class AdminRiderServiceImpl implements AdminRiderService {
41   -
42   - private final RiderMapper riderMapper;
43   - private final RiderLevelMapper riderLevelMapper;
44   - private final OrdersMapper ordersMapper;
45   - private final RiderBalanceMapper balanceMapper;
46   - private final AdminScopeGuard adminScopeGuard;
47   - private final DeliveryOrderService deliveryOrderService;
48   - private final RiderHoldLimitService riderHoldLimitService;
49   - private final WebhookService webhookService;
50   - private final ObjectMapper objectMapper;
51   -
52   - @Override
53   - public void add(AdminRiderAddDTO dto, Long cityId) {
54   - if (cityId == null || cityId < 1) {
55   - throw new BizException("城市不能为空");
56   - }
57   - Long exists = riderMapper.selectCount(new LambdaQueryWrapper<Rider>()
58   - .eq(Rider::getMobile, dto.getMobile()));
59   - if (exists > 0) {
60   - throw new BizException("该手机号已存在");
61   - }
62   -
63   - Rider rider = new Rider();
64   - rider.setUserNickname(dto.getUserNickname());
65   - rider.setMobile(dto.getMobile());
66   - rider.setIdNo(dto.getIdNo());
67   - rider.setUserPass(encryptPass(dto.getPassword()));
68   - rider.setCityId(cityId);
69   - rider.setUserStatus(1);
70   - rider.setStatus(1);
71   - rider.setType(1);
72   - rider.setIsRest(0);
73   - rider.setBalance(BigDecimal.ZERO);
74   - rider.setFrozenBalance(BigDecimal.ZERO);
75   - rider.setUserLogin("phone_" + System.currentTimeMillis());
76   - rider.setCreateTime(System.currentTimeMillis() / 1000);
77   - riderMapper.insert(rider);
78   - }
79   -
80   - @Override
81   - public List<Rider> list(String keyword, Integer userStatus, Long cityId) {
82   - LambdaQueryWrapper<Rider> wrapper = new LambdaQueryWrapper<Rider>()
83   - .orderByDesc(Rider::getId);
84   - if (cityId != null) {
85   - wrapper.eq(Rider::getCityId, cityId);
86   - }
87   - if (userStatus != null) {
88   - wrapper.eq(Rider::getUserStatus, userStatus);
89   - }
90   - if (keyword != null && !keyword.isBlank()) {
91   - wrapper.and(w -> w.like(Rider::getUserNickname, keyword)
92   - .or()
93   - .like(Rider::getMobile, keyword));
94   - }
95   - List<Rider> riders = riderMapper.selectList(wrapper);
96   - enrichLevelName(riders);
97   - return riders;
98   - }
99   -
100   - @Override
101   - public List<Rider> designateCandidates(Long orderId, Long cityId) {
102   - Orders order = ordersMapper.selectById(orderId);
103   - if (order == null) throw new BizException("订单不存在");
104   - if (cityId != null && !cityId.equals(order.getCityId())) {
105   - throw new BizException("只能查看当前租户订单的骑手");
106   - }
107   -
108   - LambdaQueryWrapper<Rider> wrapper = new LambdaQueryWrapper<Rider>()
109   - .eq(Rider::getCityId, order.getCityId())
110   - .eq(Rider::getUserStatus, 1)
111   - .eq(Rider::getStatus, 1)
112   - .orderByAsc(Rider::getIsRest)
113   - .orderByDesc(Rider::getId);
114   - if (order.getIsTrans() != null && order.getIsTrans() == 1
115   - && order.getOldRiderId() != null && order.getOldRiderId() > 0) {
116   - wrapper.ne(Rider::getId, order.getOldRiderId());
117   - }
118   -
119   - List<Rider> riders = riderMapper.selectList(wrapper);
120   - enrichLevelName(riders);
121   - return riders;
122   - }
123   -
124   - @Override
125   - public void setStatus(Long riderId, int status, Long cityId) {
126   - Rider rider = riderMapper.selectById(riderId);
127   - if (rider == null) throw new BizException("骑手不存在");
128   - adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
129   - riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
130   - .eq(Rider::getId, riderId)
131   - .set(Rider::getUserStatus, status));
132   - }
133   -
134   - @Override
135   - public void setLevel(Long riderId, Long levelId, Long cityId) {
136   - Rider rider = riderMapper.selectById(riderId);
137   - if (rider == null) throw new BizException("骑手不存在");
138   - if (cityId != null && !cityId.equals(rider.getCityId())) {
139   - throw new BizException("只能操作当前城市骑手");
140   - }
141   - if (levelId != null && levelId > 0) {
142   - RiderLevel level = riderLevelMapper.selectById(levelId);
143   - if (level == null) throw new BizException("等级不存在");
144   - if (!rider.getCityId().equals(level.getCityId())) {
145   - throw new BizException("等级与骑手城市不匹配");
146   - }
147   - }
148   - riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
149   - .eq(Rider::getId, riderId)
150   - .set(Rider::getLevelId, levelId != null && levelId > 0 ? levelId : null));
151   - }
152   -
153   - @Override
154   - public void setEnableStatus(Long riderId, int status, Long cityId) {
155   - Rider rider = riderMapper.selectById(riderId);
156   - if (rider == null) throw new BizException("骑手不存在");
157   - adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
158   - riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
159   - .eq(Rider::getId, riderId)
160   - .set(Rider::getStatus, status));
161   - }
162   -
163   - @Override
164   - @Transactional
165   - public void setType(Long riderId, int type, Long cityId) {
166   - Rider rider = riderMapper.selectById(riderId);
167   - if (rider == null) throw new BizException("骑手不存在");
168   - adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
169   - if (type == 2) {
170   - if (rider.getBalance() != null && rider.getBalance().compareTo(BigDecimal.ZERO) > 0) {
171   - throw new BizException("变更为全职前要保证余额为0");
172   - }
173   - if (rider.getFrozenBalance() != null && rider.getFrozenBalance().compareTo(BigDecimal.ZERO) > 0) {
174   - throw new BizException("变更为全职前要保证无提现冻结余额");
175   - }
176   - }
177   - LambdaUpdateWrapper<Rider> wrapper = new LambdaUpdateWrapper<Rider>()
178   - .eq(Rider::getId, riderId)
179   - .set(Rider::getType, type);
180   - if (type == 1) {
181   - wrapper.set(Rider::getBalance, BigDecimal.ZERO);
182   - }
183   - riderMapper.update(null, wrapper);
184   - }
185   -
186   - @Override
187   - public void setHoldOrderLimit(Long riderId, int holdOrderLimit, Long cityId) {
188   - if (holdOrderLimit < 0) throw new BizException("个人持单上限不能小于0");
189   - Rider rider = riderMapper.selectById(riderId);
190   - if (rider == null) throw new BizException("骑手不存在");
191   - adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
192   - riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
193   - .eq(Rider::getId, riderId)
194   - .set(Rider::getHoldOrderLimit, holdOrderLimit));
195   - }
196   -
197   - @Override
198   - @Transactional
199   - public void designate(Long orderId, Long riderId, Long cityId) {
200   - Orders order = ordersMapper.selectById(orderId);
201   - if (order == null) throw new BizException("订单不存在");
202   - adminScopeGuard.assertCityAccessible(cityId, order.getCityId());
203   - Rider rider = riderMapper.selectById(riderId);
204   - if (rider == null) throw new BizException("骑手不存在");
205   - if (!order.getCityId().equals(rider.getCityId())) {
206   - throw new BizException("骑手与订单城市不匹配");
207   - }
208   - if (order.getStatus() == 1) throw new BizException("订单未支付,无法指派");
209   - if (order.getStatus() == 10) throw new BizException("订单已取消,无法指派");
210   - if (order.getStatus() != 2) throw new BizException("订单已服务中,无法指派");
211   - if (order.getIsTrans() == 1 && riderId.equals(order.getOldRiderId())) {
212   - throw new BizException("此订单为该骑手转单订单,无法指派给该骑手");
213   - }
214   - Integer holdOrderLimit = riderHoldLimitService.resolveHoldOrderLimit(rider);
215   - riderHoldLimitService.assertWithinLimit(riderId, holdOrderLimit, "该骑手当前持单量已达个人上限,无法指派");
216   -
217   - long now = System.currentTimeMillis() / 1000;
218   - LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>()
219   - .eq(Orders::getId, orderId)
220   - .eq(Orders::getStatus, 2)
221   - .eq(Orders::getRiderId, 0)
222   - .set(Orders::getRiderId, riderId)
223   - .set(Orders::getStatus, 3)
224   - .set(Orders::getGrapTime, now);
225   - if (order.getOldRiderId() == null || order.getOldRiderId() == 0) {
226   - wrapper.set(Orders::getOldRiderId, riderId);
227   - }
228   - int updated = ordersMapper.update(null, wrapper);
229   - if (updated == 0) throw new BizException("指派失败,请重试");
230   - notifyOrderEvent(orderId, "order.dispatched");
231   - }
232   -
233   - @Override
234   - @Transactional
235   - public void setTrans(Long orderId, int trans, Long cityId) {
236   - Orders order = ordersMapper.selectById(orderId);
237   - if (order == null) throw new BizException("订单不存在");
238   - adminScopeGuard.assertCityAccessible(cityId, order.getCityId());
239   - if (order.getStatus() != 4) throw new BizException("订单状态错误,无法操作");
240   - if (order.getIsTrans() != 2) throw new BizException("订单未申请转单,无法操作");
241   -
242   - LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>()
243   - .eq(Orders::getId, orderId)
244   - .eq(Orders::getIsTrans, 2)
245   - .set(Orders::getIsTrans, trans);
246   -
247   - if (trans == 1) {
248   - wrapper.set(Orders::getStatus, 2)
249   - .set(Orders::getGrapTime, 0L)
250   - .set(Orders::getPickTime, 0L)
251   - .set(Orders::getRiderId, 0L)
252   - .set(Orders::getIsIncome, 0)
253   - .set(Orders::getRiderIncome, BigDecimal.ZERO)
254   - .set(Orders::getSubstationIncome, BigDecimal.ZERO);
255   - }
256   - int updated = ordersMapper.update(null, wrapper);
257   - if (updated == 0) throw new BizException("操作失败,请重试");
258   - if (trans == 1) {
259   - notifyOrderEvent(orderId, "order.transferred");
260   - }
261   - }
262   -
263   - @Override
264   - public DeliveryOrderCreateVO deliveryDetail(Long orderId, Long cityId) {
265   - Orders order = ordersMapper.selectById(orderId);
266   - if (order == null) throw new BizException("订单不存在");
267   - adminScopeGuard.assertCityAccessible(cityId, order.getCityId());
268   - return deliveryOrderService.getAdminDetail(order);
269   - }
270   -
271   - @Override
272   - public void cancelDeliveryOrder(Long orderId, Long cityId) {
273   - Orders order = ordersMapper.selectById(orderId);
274   - if (order == null) throw new BizException("订单不存在");
275   - adminScopeGuard.assertCityAccessible(cityId, order.getCityId());
276   - deliveryOrderService.cancelByAdmin(order);
277   - }
278   -
279   - private void notifyOrderEvent(Long orderId, String event) {
280   - try {
281   - Orders order = ordersMapper.selectById(orderId);
282   - if (order == null || order.getAppKey() == null || order.getAppKey().isBlank()) return;
283   - Map<String, Object> payload = new HashMap<>();
284   - payload.put("event", event);
285   - payload.put("outOrderNo", order.getOutOrderNo());
286   - payload.put("deliveryOrderId", order.getId());
287   - payload.put("orderNo", order.getOrderNo());
288   - payload.put("status", order.getStatus());
289   - payload.put("riderId", order.getRiderId());
290   - payload.put("timestamp", System.currentTimeMillis() / 1000);
291   - webhookService.sendOrderEvent(order, event, objectMapper.writeValueAsString(payload));
292   - } catch (Exception e) {
293   - log.warn("后台订单事件通知失败 orderId={} event={}", orderId, event, e);
294   - }
295   - }
296   -
297   - private String encryptPass(String pass) {
298   - return DigestUtils.md5DigestAsHex(pass.getBytes(StandardCharsets.UTF_8));
299   - }
300   -
301   - private void enrichLevelName(List<Rider> riders) {
302   - if (riders == null || riders.isEmpty()) return;
303   -
304   - Set<Long> cityIds = riders.stream()
305   - .map(Rider::getCityId)
306   - .filter(id -> id != null && id > 0)
307   - .collect(Collectors.toSet());
308   - if (cityIds.isEmpty()) return;
309   -
310   - List<RiderLevel> levels = riderLevelMapper.selectList(new LambdaQueryWrapper<RiderLevel>()
311   - .in(RiderLevel::getCityId, cityIds));
312   - Map<Long, String> levelNameMap = new HashMap<>();
313   - Map<Long, String> defaultLevelNameMap = new HashMap<>();
314   - for (RiderLevel level : levels) {
315   - levelNameMap.put(level.getId(), level.getName());
316   - if (level.getIsDefault() != null && level.getIsDefault() == 1) {
317   - defaultLevelNameMap.put(level.getCityId(), level.getName());
318   - }
319   - }
320   -
321   - for (Rider rider : riders) {
322   - if (rider.getLevelId() != null && rider.getLevelId() > 0) {
323   - rider.setLevelName(levelNameMap.getOrDefault(rider.getLevelId(), "未知等级"));
324   - } else {
325   - rider.setLevelName(defaultLevelNameMap.getOrDefault(rider.getCityId(), "默认等级"));
326   - }
327   - }
328   - }
329   -}
  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.auth.AdminScopeGuard;
  6 +import com.diligrp.rider.common.exception.BizException;
  7 +import com.diligrp.rider.dto.AdminRiderAddDTO;
  8 +import com.diligrp.rider.entity.Orders;
  9 +import com.diligrp.rider.entity.Rider;
  10 +import com.diligrp.rider.entity.RiderLevel;
  11 +import com.diligrp.rider.mapper.OrdersMapper;
  12 +import com.diligrp.rider.mapper.RiderBalanceMapper;
  13 +import com.diligrp.rider.mapper.RiderLevelMapper;
  14 +import com.diligrp.rider.mapper.RiderMapper;
  15 +import com.diligrp.rider.entity.*;
  16 +import com.diligrp.rider.mapper.*;
  17 +import com.diligrp.rider.service.AdminRiderService;
  18 +import com.diligrp.rider.service.DeliveryOrderService;
  19 +import com.diligrp.rider.service.RiderHoldLimitService;
  20 +import com.diligrp.rider.service.WebhookService;
  21 +import com.diligrp.rider.vo.DeliveryOrderCreateVO;
  22 +import com.fasterxml.jackson.databind.ObjectMapper;
  23 +import lombok.RequiredArgsConstructor;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.springframework.stereotype.Service;
  26 +import org.springframework.transaction.annotation.Transactional;
  27 +import org.springframework.util.DigestUtils;
  28 +
  29 +import java.math.BigDecimal;
  30 +import java.nio.charset.StandardCharsets;
  31 +import java.util.HashMap;
  32 +import java.util.List;
  33 +import java.util.Map;
  34 +import java.util.Set;
  35 +import java.util.stream.Collectors;
  36 +
  37 +@Slf4j
  38 +@Service
  39 +@RequiredArgsConstructor
  40 +public class AdminRiderServiceImpl implements AdminRiderService {
  41 +
  42 + private final RiderMapper riderMapper;
  43 + private final RiderLevelMapper riderLevelMapper;
  44 + private final OrdersMapper ordersMapper;
  45 + private final RiderBalanceMapper balanceMapper;
  46 + private final AdminScopeGuard adminScopeGuard;
  47 + private final DeliveryOrderService deliveryOrderService;
  48 + private final RiderHoldLimitService riderHoldLimitService;
  49 + private final WebhookService webhookService;
  50 + private final ObjectMapper objectMapper;
  51 +
  52 + @Override
  53 + public void add(AdminRiderAddDTO dto, Long cityId) {
  54 + if (cityId == null || cityId < 1) {
  55 + throw new BizException("城市不能为空");
  56 + }
  57 + Long exists = riderMapper.selectCount(new LambdaQueryWrapper<Rider>()
  58 + .eq(Rider::getMobile, dto.getMobile()));
  59 + if (exists > 0) {
  60 + throw new BizException("该手机号已存在");
  61 + }
  62 +
  63 + Rider rider = new Rider();
  64 + rider.setUserNickname(dto.getUserNickname());
  65 + rider.setMobile(dto.getMobile());
  66 + rider.setIdNo(dto.getIdNo());
  67 + rider.setUserPass(encryptPass(dto.getPassword()));
  68 + rider.setCityId(cityId);
  69 + rider.setUserStatus(1);
  70 + rider.setStatus(1);
  71 + rider.setType(1);
  72 + rider.setIsRest(0);
  73 + rider.setBalance(BigDecimal.ZERO);
  74 + rider.setFrozenBalance(BigDecimal.ZERO);
  75 + rider.setUserLogin("phone_" + System.currentTimeMillis());
  76 + rider.setCreateTime(System.currentTimeMillis() / 1000);
  77 + riderMapper.insert(rider);
  78 + }
  79 +
  80 + @Override
  81 + public List<Rider> list(String keyword, Integer userStatus, Long cityId) {
  82 + LambdaQueryWrapper<Rider> wrapper = new LambdaQueryWrapper<Rider>()
  83 + .orderByDesc(Rider::getId);
  84 + if (cityId != null) {
  85 + wrapper.eq(Rider::getCityId, cityId);
  86 + }
  87 + if (userStatus != null) {
  88 + wrapper.eq(Rider::getUserStatus, userStatus);
  89 + }
  90 + if (keyword != null && !keyword.isBlank()) {
  91 + wrapper.and(w -> w.like(Rider::getUserNickname, keyword)
  92 + .or()
  93 + .like(Rider::getMobile, keyword));
  94 + }
  95 + List<Rider> riders = riderMapper.selectList(wrapper);
  96 + enrichLevelName(riders);
  97 + return riders;
  98 + }
  99 +
  100 + @Override
  101 + public List<Rider> designateCandidates(Long orderId, Long cityId) {
  102 + Orders order = ordersMapper.selectById(orderId);
  103 + if (order == null) throw new BizException("订单不存在");
  104 + if (cityId != null && !cityId.equals(order.getCityId())) {
  105 + throw new BizException("只能查看当前租户订单的骑手");
  106 + }
  107 +
  108 + LambdaQueryWrapper<Rider> wrapper = new LambdaQueryWrapper<Rider>()
  109 + .eq(Rider::getCityId, order.getCityId())
  110 + .eq(Rider::getUserStatus, 1)
  111 + .eq(Rider::getStatus, 1)
  112 + .orderByAsc(Rider::getIsRest)
  113 + .orderByDesc(Rider::getId);
  114 + if (order.getIsTrans() != null && order.getIsTrans() == 1
  115 + && order.getOldRiderId() != null && order.getOldRiderId() > 0) {
  116 + wrapper.ne(Rider::getId, order.getOldRiderId());
  117 + }
  118 +
  119 + List<Rider> riders = riderMapper.selectList(wrapper);
  120 + enrichLevelName(riders);
  121 + return riders;
  122 + }
  123 +
  124 + @Override
  125 + public void setStatus(Long riderId, int status, Long cityId) {
  126 + Rider rider = riderMapper.selectById(riderId);
  127 + if (rider == null) throw new BizException("骑手不存在");
  128 + adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
  129 + riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
  130 + .eq(Rider::getId, riderId)
  131 + .set(Rider::getUserStatus, status));
  132 + }
  133 +
  134 + @Override
  135 + public void setLevel(Long riderId, Long levelId, Long cityId) {
  136 + Rider rider = riderMapper.selectById(riderId);
  137 + if (rider == null) throw new BizException("骑手不存在");
  138 + if (cityId != null && !cityId.equals(rider.getCityId())) {
  139 + throw new BizException("只能操作当前城市骑手");
  140 + }
  141 + if (levelId != null && levelId > 0) {
  142 + RiderLevel level = riderLevelMapper.selectById(levelId);
  143 + if (level == null) throw new BizException("等级不存在");
  144 + if (!rider.getCityId().equals(level.getCityId())) {
  145 + throw new BizException("等级与骑手城市不匹配");
  146 + }
  147 + }
  148 + riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
  149 + .eq(Rider::getId, riderId)
  150 + .set(Rider::getLevelId, levelId != null && levelId > 0 ? levelId : null));
  151 + }
  152 +
  153 + @Override
  154 + public void setEnableStatus(Long riderId, int status, Long cityId) {
  155 + Rider rider = riderMapper.selectById(riderId);
  156 + if (rider == null) throw new BizException("骑手不存在");
  157 + adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
  158 + riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
  159 + .eq(Rider::getId, riderId)
  160 + .set(Rider::getStatus, status));
  161 + }
  162 +
  163 + @Override
  164 + @Transactional
  165 + public void setType(Long riderId, int type, Long cityId) {
  166 + Rider rider = riderMapper.selectById(riderId);
  167 + if (rider == null) throw new BizException("骑手不存在");
  168 + adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
  169 + if (type == 2) {
  170 + if (rider.getBalance() != null && rider.getBalance().compareTo(BigDecimal.ZERO) > 0) {
  171 + throw new BizException("变更为全职前要保证余额为0");
  172 + }
  173 + if (rider.getFrozenBalance() != null && rider.getFrozenBalance().compareTo(BigDecimal.ZERO) > 0) {
  174 + throw new BizException("变更为全职前要保证无提现冻结余额");
  175 + }
  176 + }
  177 + LambdaUpdateWrapper<Rider> wrapper = new LambdaUpdateWrapper<Rider>()
  178 + .eq(Rider::getId, riderId)
  179 + .set(Rider::getType, type);
  180 + if (type == 1) {
  181 + wrapper.set(Rider::getBalance, BigDecimal.ZERO);
  182 + }
  183 + riderMapper.update(null, wrapper);
  184 + }
  185 +
  186 + @Override
  187 + public void setHoldOrderLimit(Long riderId, int holdOrderLimit, Long cityId) {
  188 + if (holdOrderLimit < 0) throw new BizException("个人持单上限不能小于0");
  189 + Rider rider = riderMapper.selectById(riderId);
  190 + if (rider == null) throw new BizException("骑手不存在");
  191 + adminScopeGuard.assertCityAccessible(cityId, rider.getCityId());
  192 + riderMapper.update(null, new LambdaUpdateWrapper<Rider>()
  193 + .eq(Rider::getId, riderId)
  194 + .set(Rider::getHoldOrderLimit, holdOrderLimit));
  195 + }
  196 +
  197 + @Override
  198 + @Transactional
  199 + public void designate(Long orderId, Long riderId, Long cityId) {
  200 + Orders order = ordersMapper.selectById(orderId);
  201 + if (order == null) throw new BizException("订单不存在");
  202 + adminScopeGuard.assertCityAccessible(cityId, order.getCityId());
  203 + Rider rider = riderMapper.selectById(riderId);
  204 + if (rider == null) throw new BizException("骑手不存在");
  205 + if (!order.getCityId().equals(rider.getCityId())) {
  206 + throw new BizException("骑手与订单城市不匹配");
  207 + }
  208 + if (order.getStatus() == 1) throw new BizException("订单未支付,无法指派");
  209 + if (order.getStatus() == 10) throw new BizException("订单已取消,无法指派");
  210 + if (order.getStatus() != 2) throw new BizException("订单已服务中,无法指派");
  211 + if (order.getIsTrans() == 1 && riderId.equals(order.getOldRiderId())) {
  212 + throw new BizException("此订单为该骑手转单订单,无法指派给该骑手");
  213 + }
  214 + Integer holdOrderLimit = riderHoldLimitService.resolveHoldOrderLimit(rider);
  215 + riderHoldLimitService.assertWithinLimit(riderId, holdOrderLimit, "该骑手当前持单量已达个人上限,无法指派");
  216 +
  217 + long now = System.currentTimeMillis() / 1000;
  218 + LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>()
  219 + .eq(Orders::getId, orderId)
  220 + .eq(Orders::getStatus, 2)
  221 + .eq(Orders::getRiderId, 0)
  222 + .set(Orders::getRiderId, riderId)
  223 + .set(Orders::getStatus, 3)
  224 + .set(Orders::getGrapTime, now);
  225 + if (order.getOldRiderId() == null || order.getOldRiderId() == 0) {
  226 + wrapper.set(Orders::getOldRiderId, riderId);
  227 + }
  228 + int updated = ordersMapper.update(null, wrapper);
  229 + if (updated == 0) throw new BizException("指派失败,请重试");
  230 + notifyOrderEvent(orderId, "order.dispatched");
  231 + }
  232 +
  233 + @Override
  234 + @Transactional
  235 + public void setTrans(Long orderId, int trans, Long cityId) {
  236 + Orders order = ordersMapper.selectById(orderId);
  237 + if (order == null) throw new BizException("订单不存在");
  238 + adminScopeGuard.assertCityAccessible(cityId, order.getCityId());
  239 + if (order.getStatus() != 4) throw new BizException("订单状态错误,无法操作");
  240 + if (order.getIsTrans() != 2) throw new BizException("订单未申请转单,无法操作");
  241 +
  242 + LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>()
  243 + .eq(Orders::getId, orderId)
  244 + .eq(Orders::getIsTrans, 2)
  245 + .set(Orders::getIsTrans, trans);
  246 +
  247 + if (trans == 1) {
  248 + wrapper.set(Orders::getStatus, 2)
  249 + .set(Orders::getGrapTime, 0L)
  250 + .set(Orders::getPickTime, 0L)
  251 + .set(Orders::getArriveShopTime, 0L)
  252 + .set(Orders::getArriveShopLng, "")
  253 + .set(Orders::getArriveShopLat, "")
  254 + .set(Orders::getArriveShopDistance, BigDecimal.ZERO)
  255 + .set(Orders::getRiderId, 0L)
  256 + .set(Orders::getIsIncome, 0)
  257 + .set(Orders::getRiderIncome, BigDecimal.ZERO)
  258 + .set(Orders::getSubstationIncome, BigDecimal.ZERO);
  259 + }
  260 + int updated = ordersMapper.update(null, wrapper);
  261 + if (updated == 0) throw new BizException("操作失败,请重试");
  262 + if (trans == 1) {
  263 + notifyOrderEvent(orderId, "order.transferred");
  264 + }
  265 + }
  266 +
  267 + @Override
  268 + public DeliveryOrderCreateVO deliveryDetail(Long orderId, Long cityId) {
  269 + Orders order = ordersMapper.selectById(orderId);
  270 + if (order == null) throw new BizException("订单不存在");
  271 + adminScopeGuard.assertCityAccessible(cityId, order.getCityId());
  272 + return deliveryOrderService.getAdminDetail(order);
  273 + }
  274 +
  275 + @Override
  276 + public void cancelDeliveryOrder(Long orderId, Long cityId) {
  277 + Orders order = ordersMapper.selectById(orderId);
  278 + if (order == null) throw new BizException("订单不存在");
  279 + adminScopeGuard.assertCityAccessible(cityId, order.getCityId());
  280 + deliveryOrderService.cancelByAdmin(order);
  281 + }
  282 +
  283 + private void notifyOrderEvent(Long orderId, String event) {
  284 + try {
  285 + Orders order = ordersMapper.selectById(orderId);
  286 + if (order == null || order.getAppKey() == null || order.getAppKey().isBlank()) return;
  287 + Map<String, Object> payload = new HashMap<>();
  288 + payload.put("event", event);
  289 + payload.put("outOrderNo", order.getOutOrderNo());
  290 + payload.put("deliveryOrderId", order.getId());
  291 + payload.put("orderNo", order.getOrderNo());
  292 + payload.put("status", order.getStatus());
  293 + payload.put("riderId", order.getRiderId());
  294 + payload.put("timestamp", System.currentTimeMillis() / 1000);
  295 + webhookService.sendOrderEvent(order, event, objectMapper.writeValueAsString(payload));
  296 + } catch (Exception e) {
  297 + log.warn("后台订单事件通知失败 orderId={} event={}", orderId, event, e);
  298 + }
  299 + }
  300 +
  301 + private String encryptPass(String pass) {
  302 + return DigestUtils.md5DigestAsHex(pass.getBytes(StandardCharsets.UTF_8));
  303 + }
  304 +
  305 + private void enrichLevelName(List<Rider> riders) {
  306 + if (riders == null || riders.isEmpty()) return;
  307 +
  308 + Set<Long> cityIds = riders.stream()
  309 + .map(Rider::getCityId)
  310 + .filter(id -> id != null && id > 0)
  311 + .collect(Collectors.toSet());
  312 + if (cityIds.isEmpty()) return;
  313 +
  314 + List<RiderLevel> levels = riderLevelMapper.selectList(new LambdaQueryWrapper<RiderLevel>()
  315 + .in(RiderLevel::getCityId, cityIds));
  316 + Map<Long, String> levelNameMap = new HashMap<>();
  317 + Map<Long, String> defaultLevelNameMap = new HashMap<>();
  318 + for (RiderLevel level : levels) {
  319 + levelNameMap.put(level.getId(), level.getName());
  320 + if (level.getIsDefault() != null && level.getIsDefault() == 1) {
  321 + defaultLevelNameMap.put(level.getCityId(), level.getName());
  322 + }
  323 + }
  324 +
  325 + for (Rider rider : riders) {
  326 + if (rider.getLevelId() != null && rider.getLevelId() > 0) {
  327 + rider.setLevelName(levelNameMap.getOrDefault(rider.getLevelId(), "未知等级"));
  328 + } else {
  329 + rider.setLevelName(defaultLevelNameMap.getOrDefault(rider.getCityId(), "默认等级"));
  330 + }
  331 + }
  332 + }
  333 +}
... ...
src/main/java/com/diligrp/rider/service/impl/DeliveryOrderServiceImpl.java
... ... @@ -387,6 +387,14 @@ public class DeliveryOrderServiceImpl implements DeliveryOrderService {
387 387 vo.setDistance(fee.getDistance());
388 388 vo.setEstimatedMinutes(fee.getEstimatedMinutes());
389 389 vo.setStatus(order.getStatus());
  390 + vo.setRiderId(order.getRiderId());
  391 + vo.setGrapTime(order.getGrapTime());
  392 + vo.setArriveShopTime(order.getArriveShopTime());
  393 + vo.setArriveShopLng(order.getArriveShopLng());
  394 + vo.setArriveShopLat(order.getArriveShopLat());
  395 + vo.setArriveShopDistance(order.getArriveShopDistance());
  396 + vo.setPickTime(order.getPickTime());
  397 + vo.setCompleteTime(order.getCompleteTime());
390 398 return vo;
391 399 }
392 400 }
... ...
src/main/java/com/diligrp/rider/service/impl/RiderOrderServiceImpl.java
... ... @@ -23,6 +23,7 @@ import org.springframework.stereotype.Service;
23 23 import org.springframework.transaction.annotation.Transactional;
24 24  
25 25 import java.math.BigDecimal;
  26 +import java.math.RoundingMode;
26 27 import java.time.LocalDate;
27 28 import java.time.ZoneId;
28 29 import java.time.format.DateTimeFormatter;
... ... @@ -185,6 +186,9 @@ public class RiderOrderServiceImpl implements RiderOrderService {
185 186 if (order == null) throw new BizException(1004, "订单信息错误");
186 187 if (!riderId.equals(order.getRiderId())) throw new BizException(1005, "订单信息错误");
187 188 if (order.getStatus() != 3) throw new BizException(1006, "订单状态错误");
  189 + if (order.getArriveShopTime() == null || order.getArriveShopTime() <= 0) {
  190 + throw new BizException(1007, "请先上报到店");
  191 + }
188 192 // @TODO 注释
189 193 // if (!code.equals(order.getCode())) throw new BizException(1007, "完成码错误");
190 194  
... ... @@ -202,6 +206,39 @@ public class RiderOrderServiceImpl implements RiderOrderService {
202 206  
203 207 @Override
204 208 @Transactional
  209 + public void arriveShop(Long riderId, Long orderId, String lng, String lat) {
  210 + if (lng == null || lng.isBlank() || lat == null || lat.isBlank()) {
  211 + throw new BizException(1001, "请上传到店位置");
  212 + }
  213 +
  214 + double arriveLng = parseCoordinate(lng, "经度格式错误");
  215 + double arriveLat = parseCoordinate(lat, "纬度格式错误");
  216 + Orders order = ordersMapper.selectById(orderId);
  217 + if (order == null) throw new BizException(1004, "订单信息错误");
  218 + if (!riderId.equals(order.getRiderId())) throw new BizException(1005, "订单信息错误");
  219 + if (order.getStatus() != 3) throw new BizException(1006, "订单状态错误");
  220 + if (order.getArriveShopTime() != null && order.getArriveShopTime() > 0) {
  221 + throw new BizException(1007, "已上报到店,请勿重复操作");
  222 + }
  223 +
  224 + BigDecimal distanceMeters = calcDistanceMeters(arriveLat, arriveLng, order.getFLat(), order.getFLng());
  225 + long now = System.currentTimeMillis() / 1000;
  226 + int updated = ordersMapper.update(null, new LambdaUpdateWrapper<Orders>()
  227 + .eq(Orders::getId, orderId)
  228 + .eq(Orders::getRiderId, riderId)
  229 + .eq(Orders::getStatus, 3)
  230 + .eq(Orders::getArriveShopTime, 0L)
  231 + .set(Orders::getArriveShopTime, now)
  232 + .set(Orders::getArriveShopLng, lng)
  233 + .set(Orders::getArriveShopLat, lat)
  234 + .set(Orders::getArriveShopDistance, distanceMeters));
  235 + if (updated == 0) throw new BizException(980, "操作失败");
  236 +
  237 + notifyOrderEvent(orderId, "order.arrived_shop");
  238 + }
  239 +
  240 + @Override
  241 + @Transactional
205 242 public void complete(Long riderId, Long orderId, String thumbsJson) {
206 243 if (thumbsJson == null || thumbsJson.isBlank()) throw new BizException(1001, "请上传照片");
207 244  
... ... @@ -255,6 +292,12 @@ public class RiderOrderServiceImpl implements RiderOrderService {
255 292 payload.put("orderNo", order.getOrderNo());
256 293 payload.put("status", order.getStatus());
257 294 payload.put("riderId", order.getRiderId());
  295 + payload.put("arriveShopTime", order.getArriveShopTime());
  296 + payload.put("arriveShopLng", order.getArriveShopLng());
  297 + payload.put("arriveShopLat", order.getArriveShopLat());
  298 + payload.put("arriveShopDistance", order.getArriveShopDistance());
  299 + payload.put("pickTime", order.getPickTime());
  300 + payload.put("completeTime", order.getCompleteTime());
258 301 payload.put("timestamp", System.currentTimeMillis() / 1000);
259 302 webhookService.sendOrderEvent(order, event, objectMapper.writeValueAsString(payload));
260 303 } catch (Exception e) {
... ... @@ -324,6 +367,38 @@ public class RiderOrderServiceImpl implements RiderOrderService {
324 367 return 0;
325 368 }
326 369  
  370 + private double parseCoordinate(String value, String message) {
  371 + try {
  372 + return Double.parseDouble(value);
  373 + } catch (Exception e) {
  374 + throw new BizException(1002, message);
  375 + }
  376 + }
  377 +
  378 + private BigDecimal calcDistanceMeters(double arriveLat, double arriveLng, String shopLat, String shopLng) {
  379 + try {
  380 + if (shopLat == null || shopLat.isBlank() || shopLng == null || shopLng.isBlank()) {
  381 + return BigDecimal.ZERO;
  382 + }
  383 + double targetLat = Double.parseDouble(shopLat);
  384 + double targetLng = Double.parseDouble(shopLng);
  385 + double distance = haversineMeters(arriveLat, arriveLng, targetLat, targetLng);
  386 + return BigDecimal.valueOf(distance).setScale(2, RoundingMode.HALF_UP);
  387 + } catch (Exception e) {
  388 + return BigDecimal.ZERO;
  389 + }
  390 + }
  391 +
  392 + private double haversineMeters(double lat1, double lng1, double lat2, double lng2) {
  393 + double earthRadiusMeters = 6371000.0;
  394 + double dLat = Math.toRadians(lat2 - lat1);
  395 + double dLng = Math.toRadians(lng2 - lng1);
  396 + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
  397 + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
  398 + * Math.sin(dLng / 2) * Math.sin(dLng / 2);
  399 + return earthRadiusMeters * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  400 + }
  401 +
327 402 private OrderVO toVO(Orders o, Long riderId) {
328 403 OrderVO vo = new OrderVO();
329 404 vo.setId(o.getId());
... ... @@ -345,6 +420,10 @@ public class RiderOrderServiceImpl implements RiderOrderService {
345 420 vo.setAddTime(o.getAddTime() != null ? formatTime(o.getAddTime()) : null);
346 421 vo.setGrapTime(o.getGrapTime() != null && o.getGrapTime() > 0 ? formatTime(o.getGrapTime()) : null);
347 422 vo.setPickTime(o.getPickTime() != null && o.getPickTime() > 0 ? formatTime(o.getPickTime()) : null);
  423 + vo.setArriveShopTime(o.getArriveShopTime() != null && o.getArriveShopTime() > 0 ? formatTime(o.getArriveShopTime()) : null);
  424 + vo.setArriveShopLng(o.getArriveShopLng());
  425 + vo.setArriveShopLat(o.getArriveShopLat());
  426 + vo.setArriveShopDistance(o.getArriveShopDistance());
348 427 vo.setCompleteTime(o.getCompleteTime() != null && o.getCompleteTime() > 0 ? formatTime(o.getCompleteTime()) : null);
349 428 vo.setTransTime(o.getTransTime() != null && o.getTransTime() > 0 ? formatTime(o.getTransTime()) : null);
350 429 // 收入
... ... @@ -422,6 +501,10 @@ public class RiderOrderServiceImpl implements RiderOrderService {
422 501 .set(Orders::getStatus, 2) // 回到待接单
423 502 .set(Orders::getRiderId, 0L) // 清空骑手
424 503 .set(Orders::getGrapTime, 0L)
  504 + .set(Orders::getArriveShopTime, 0L)
  505 + .set(Orders::getArriveShopLng, "")
  506 + .set(Orders::getArriveShopLat, "")
  507 + .set(Orders::getArriveShopDistance, BigDecimal.ZERO)
425 508 .set(Orders::getIsIncome, 0)
426 509 .set(Orders::getRiderIncome, BigDecimal.ZERO)
427 510 .set(Orders::getSubstationIncome, BigDecimal.ZERO)
... ...
src/main/java/com/diligrp/rider/vo/DeliveryOrderCreateVO.java
... ... @@ -29,4 +29,20 @@ public class DeliveryOrderCreateVO {
29 29 private Integer estimatedMinutes;
30 30 /** 订单状态:2=待接单 */
31 31 private Integer status;
  32 + /** 骑手ID */
  33 + private Long riderId;
  34 + /** 接单时间 */
  35 + private Long grapTime;
  36 + /** 骑手到店时间 */
  37 + private Long arriveShopTime;
  38 + /** 骑手到店经度 */
  39 + private String arriveShopLng;
  40 + /** 骑手到店纬度 */
  41 + private String arriveShopLat;
  42 + /** 骑手到店距离门店距离,单位米 */
  43 + private BigDecimal arriveShopDistance;
  44 + /** 取件时间 */
  45 + private Long pickTime;
  46 + /** 完成时间 */
  47 + private Long completeTime;
32 48 }
... ...
src/main/java/com/diligrp/rider/vo/OrderVO.java
... ... @@ -38,6 +38,10 @@ public class OrderVO {
38 38 private String addTime;
39 39 private String grapTime;
40 40 private String pickTime;
  41 + private String arriveShopTime;
  42 + private String arriveShopLng;
  43 + private String arriveShopLat;
  44 + private BigDecimal arriveShopDistance;
41 45 private String completeTime;
42 46 private String transTime;
43 47 private Object extra;
... ...
src/main/resources/20260513-arrive-shop.sql 0 → 100644
  1 +-- 骑手到店节点字段
  2 +ALTER TABLE `orders`
  3 + ADD COLUMN `arrive_shop_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手到店时间' AFTER `pick_time`,
  4 + ADD COLUMN `arrive_shop_lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '骑手到店经度' AFTER `arrive_shop_time`,
  5 + ADD COLUMN `arrive_shop_lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '骑手到店纬度' AFTER `arrive_shop_lng`,
  6 + ADD COLUMN `arrive_shop_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '骑手到店距离门店距离,单位米' AFTER `arrive_shop_lat`;
  7 +
  8 +-- 示例开放平台应用订阅新增到店事件。
  9 +-- 如生产环境已有 open_app 数据,请按实际 app_key 更新 webhook_events。
  10 +UPDATE `open_app`
  11 +SET `webhook_events` = '["order.created","order.accepted","order.arrived_shop","order.picking","order.completed","order.cancelled"]'
  12 +WHERE `app_key` = 'TESTAPPKEY00001';
... ...
src/main/resources/data-init.sql
1   --- 外卖配送中台 初始化数据脚本
2   --- 执行前请确保已执行 schema.sql 建表
3   --- 执行方式:mysql -u root -p dili_rider < data-init.sql
4   -
5   -USE dili_rider;
6   -
7   --- ============================================================
8   --- 1. 城市数据(省+市两级)
9   --- ============================================================
10   -INSERT INTO `city` (`id`, `pid`, `name`, `area_code`, `status`, `rate`, `list_order`, `config`) VALUES
11   -(1, 0, '广东省', '44000000', 0, 0.00, 1, NULL),
12   -(2, 1, '广州市', '44010000', 1, 10.00, 1,
13   -'{
14   - "type": [6],
15   - "type6": {
16   - "feeMode": 2,
17   - "fixMoney": 0,
18   - "distanceSwitch": 1,
19   - "distanceBasic": 3,
20   - "distanceBasicMoney": 4.00,
21   - "distanceMoreMoney": 1.50,
22   - "distanceMode": 1,
23   - "distanceType": 1,
24   - "weightSwitch": 0,
25   - "weightBasic": 0,
26   - "weightBasicMoney": 0,
27   - "weightMoreMoney": 0,
28   - "weightType": 1,
29   - "times": [
30   - {"start": 0, "end": 480, "isOpen": 0, "money": 0},
31   - {"start": 480, "end": 1320, "isOpen": 1, "money": 0},
32   - {"start": 1320, "end": 1440, "isOpen": 1, "money": 2}
33   - ]
34   - },
35   - "distanceBasic": 3,
36   - "distanceBasicTime": 30,
37   - "distanceMoreTime": 10
38   -}'),
39   -(3, 1, '深圳市', '44030000', 1, 10.00, 2,
40   -'{
41   - "type": [6],
42   - "type6": {
43   - "feeMode": 1,
44   - "fixMoney": 5.00,
45   - "distanceSwitch": 0,
46   - "distanceBasic": 0,
47   - "distanceBasicMoney": 0,
48   - "distanceMoreMoney": 0,
49   - "distanceMode": 1,
50   - "distanceType": 1,
51   - "weightSwitch": 0,
52   - "weightBasic": 0,
53   - "weightBasicMoney": 0,
54   - "weightMoreMoney": 0,
55   - "weightType": 1,
56   - "times": []
57   - },
58   - "distanceBasic": 3,
59   - "distanceBasicTime": 25,
60   - "distanceMoreTime": 8
61   -}');
62   -
63   --- ============================================================
64   --- 2. 分站管理员(每个已开通城市一个)
65   --- 默认密码均为 admin123(MD5: 0192023a7bbd73250516f069df18b500)
66   --- ============================================================
67   -INSERT INTO `sys_role` (`code`, `name`, `role_scope`, `status`, `create_time`) VALUES
68   -('platform_admin', '平台管理员', 'PLATFORM', 1, UNIX_TIMESTAMP()),
69   -('substation_admin', '分站管理员', 'SUBSTATION', 1, UNIX_TIMESTAMP());
70   -
71   -INSERT INTO `sys_menu` (`code`, `name`, `type`, `path`, `icon`, `parent_id`, `menu_scope`, `list_order`, `visible`, `status`, `create_time`) VALUES
72   -('dashboard', '工作台', 'MENU', '/dashboard', 'HomeOutlined', 0, 'BOTH', 10, 1, 1, UNIX_TIMESTAMP()),
73   -('city.manage', '租户管理', 'MENU', '/city', 'GlobalOutlined', 0, 'PLATFORM', 20, 1, 1, UNIX_TIMESTAMP()),
74   -('substation.manage', '分站管理', 'MENU', '/substation', 'ApartmentOutlined', 0, 'PLATFORM', 30, 1, 1, UNIX_TIMESTAMP()),
75   -('merchant.root', '商家管理', 'DIR', '', 'ShopOutlined', 0, 'BOTH', 40, 1, 1, UNIX_TIMESTAMP()),
76   -('merchant.enter', '入驻申请', 'MENU', '/merchant/enter', '', 4, 'BOTH', 41, 1, 1, UNIX_TIMESTAMP()),
77   -('merchant.store', '店铺管理', 'MENU', '/merchant/store', '', 4, 'BOTH', 42, 1, 1, UNIX_TIMESTAMP()),
78   -('rider.list', '骑手管理', 'MENU', '/rider', 'UserOutlined', 0, 'BOTH', 50, 1, 1, UNIX_TIMESTAMP()),
79   -('rider.evaluate', '骑手评价', 'MENU', '/rider/evaluate', 'StarOutlined', 0, 'BOTH', 60, 1, 1, UNIX_TIMESTAMP()),
80   -('order.root', '订单管理', 'DIR', '', 'UnorderedListOutlined', 0, 'BOTH', 70, 1, 1, UNIX_TIMESTAMP()),
81   -('order.list', '订单列表', 'MENU', '/order', '', 9, 'BOTH', 71, 1, 1, UNIX_TIMESTAMP()),
82   -('order.refund', '退款管理', 'MENU', '/refund', '', 9, 'BOTH', 72, 1, 1, UNIX_TIMESTAMP()),
83   -('order.delivery', '配送订单', 'MENU', '/delivery/order', '', 9, 'BOTH', 73, 1, 1, UNIX_TIMESTAMP()),
84   -('config.root', '配置中心', 'DIR', '', 'ControlOutlined', 0, 'BOTH', 80, 1, 1, UNIX_TIMESTAMP()),
85   -('fee.plan', '配送费配置', 'MENU', '/config/fee-plan', '', 13, 'BOTH', 81, 1, 1, UNIX_TIMESTAMP()),
86   -('dispatch.rule', '调度配置', 'MENU', '/dispatch/rule', '', 13, 'BOTH', 82, 1, 1, UNIX_TIMESTAMP()),
87   -('open.root', '开放平台', 'DIR', '', 'ApiOutlined', 0, 'PLATFORM', 90, 1, 1, UNIX_TIMESTAMP()),
88   -('open.app', '应用管理', 'MENU', '/open', '', 16, 'PLATFORM', 91, 1, 1, UNIX_TIMESTAMP()),
89   -('open.mock_delivery', '模拟推单', 'MENU', '/open/mock-delivery', '', 16, 'PLATFORM', 92, 1, 1, UNIX_TIMESTAMP()),
90   -('system.root', '系统管理', 'DIR', '', 'ControlOutlined', 0, 'PLATFORM', 100, 1, 1, UNIX_TIMESTAMP()),
91   -('system.menu', '菜单管理', 'MENU', '/system/menu', '', 19, 'PLATFORM', 101, 1, 1, UNIX_TIMESTAMP()),
92   -('system.role_menu', '角色菜单', 'MENU', '/system/role-menu', '', 19, 'PLATFORM', 102, 1, 1, UNIX_TIMESTAMP()),
93   -('admin.user', '平台账号', 'MENU', '/admin-user', '', 19, 'PLATFORM', 103, 1, 1, UNIX_TIMESTAMP()),
94   -('rider.withdraw', '骑手提现审核', 'MENU', '/rider/withdraw', 'WalletOutlined', 0, 'BOTH', 56, 1, 1, UNIX_TIMESTAMP());
95   -
96   -INSERT INTO `sys_role_menu` (`role_id`, `menu_id`, `create_time`)
97   -SELECT 1, id, UNIX_TIMESTAMP() FROM `sys_menu` WHERE `menu_scope` IN ('PLATFORM', 'BOTH');
98   -
99   -INSERT INTO `sys_role_menu` (`role_id`, `menu_id`, `create_time`)
100   -SELECT 2, id, UNIX_TIMESTAMP() FROM `sys_menu` WHERE `menu_scope` IN ('SUBSTATION', 'BOTH');
101   -
102   -INSERT INTO `substation` (`city_id`, `user_login`, `user_nickname`, `user_pass`, `mobile`, `user_status`, `role_id`, `create_time`) VALUES
103   -(2, 'gz_admin', '广州分站管理员', '0192023a7bbd73250516f069df18b500', '13800000001', 1, 2, UNIX_TIMESTAMP()),
104   -(3, 'sz_admin', '深圳分站管理员', '0192023a7bbd73250516f069df18b500', '13800000002', 1, 2, UNIX_TIMESTAMP());
105   -
106   --- ============================================================
107   --- 3. 骑手等级配置(广州)
108   --- ============================================================
109   -INSERT INTO `rider_level` (`city_id`, `level_id`, `name`, `is_default`, `trans_nums`,
110   - `run_fee_mode`, `run_fix_money`, `run_rate`, `distance_basic`, `distance_basic_money`,
111   - `distance_more_money`, `distance_max_money`, `work_fee_mode`, `work_fix_money`, `work_rate`) VALUES
112   --- 普通骑手:按比例拿配送费的70%
113   -(2, 1, '普通骑手', 1, 3,
114   - 2, 0.00, 70.00, 0, 0.00, 0.00, 0.00,
115   - 1, 5.00, 0.00),
116   --- 资深骑手:按比例拿配送费的80%
117   -(2, 2, '资深骑手', 0, 5,
118   - 2, 0.00, 80.00, 0, 0.00, 0.00, 0.00,
119   - 1, 6.00, 0.00);
120   -
121   --- 深圳骑手等级
122   -INSERT INTO `rider_level` (`city_id`, `level_id`, `name`, `is_default`, `trans_nums`,
123   - `run_fee_mode`, `run_fix_money`, `run_rate`, `distance_basic`, `distance_basic_money`,
124   - `distance_more_money`, `distance_max_money`, `work_fee_mode`, `work_fix_money`, `work_rate`) VALUES
125   -(3, 1, '普通骑手', 1, 3,
126   - 2, 0.00, 70.00, 0, 0.00, 0.00, 0.00,
127   - 1, 5.00, 0.00);
128   -
129   --- ============================================================
130   --- 4. 示例骑手账号
131   --- 默认密码均为 test1234(MD5: 16d7a4fca7442dda3ad93c9a726597e4)
132   --- ============================================================
133   -INSERT INTO `rider` (`mobile`, `user_login`, `user_nickname`, `user_pass`, `city_id`, `level_id`,
134   - `type`, `user_status`, `balance`, `is_rest`, `create_time`) VALUES
135   -('13900000001', 'phone_rider001', '张骑手', '16d7a4fca7442dda3ad93c9a726597e4', 2, 1, 1, 1, 0.00, 0, UNIX_TIMESTAMP()),
136   -('13900000002', 'phone_rider002', '李骑手', '16d7a4fca7442dda3ad93c9a726597e4', 2, 1, 2, 1, 0.00, 0, UNIX_TIMESTAMP());
137   -
138   --- ============================================================
139   --- 5. 示例商家店铺
140   --- ============================================================
141   -INSERT INTO `merchant_store` (`name`, `thumb`, `city_id`, `address`, `lng`, `lat`,
142   - `operating_state`, `automatic_order`, `shipping_type`, `free_shipping`, `up_to_send`,
143   - `open_date`, `open_time`, `about`, `list_order`, `is_del`, `add_time`) VALUES
144   -('测试餐厅', '', 2, '广州市天河区测试路1号', '113.330010', '23.132891',
145   - 1, 1, 1, 30.00, 15.00,
146   - '[1,2,3,4,5,6,7]', '["09:00","22:00"]', '测试店铺,仅供开发调试', 1, 0, UNIX_TIMESTAMP());
147   -
148   --- 创建商家账号
149   -INSERT INTO `merchant_users` (`store_id`, `mobile`, `user_nickname`, `user_status`, `type`, `create_time`)
150   -VALUES (LAST_INSERT_ID(), '13700000001', '测试餐厅老板', 1, 1, UNIX_TIMESTAMP());
151   -
152   --- ============================================================
153   --- 6. 开放平台示例应用
154   --- ============================================================
155   -INSERT INTO `open_app` (`app_name`, `app_key`, `app_secret`, `store_id`, `status`,
156   - `webhook_url`, `webhook_events`, `remark`, `create_time`) VALUES
157   -('内部电商系统', 'TESTAPPKEY00001', 'testsecret0000000000000000000000000000000000000000000000000001', 0, 1,
158   - '', '["order.paid","order.completed","order.cancelled"]', '用于测试的内部应用', UNIX_TIMESTAMP());
159   -
160   --- ============================================================
161   --- 完成提示
162   --- ============================================================
163   -SELECT '初始化完成!' AS 提示;
164   -SELECT '骑手登录账号: 13900000001 / 13900000002,密码: test1234' AS 骑手账号;
165   -SELECT '分站管理员: gz_admin / sz_admin,密码: admin123' AS 分站账号;
166   -SELECT '商家手机号: 13700000001' AS 商家账号;
167   -SELECT '开放平台 AppKey: TESTAPPKEY00001' AS 开放平台;
168   -
169   --- ============================================================
170   --- 7. 超级管理员账号
171   --- 默认密码:admin123(MD5: 0192023a7bbd73250516f069df18b500)
172   --- ============================================================
173   -INSERT INTO `admin_user` (`user_login`, `user_pass`, `user_nickname`, `user_status`, `role_id`, `create_time`) VALUES
174   -('admin', '0192023a7bbd73250516f069df18b500', '超级管理员', 1, 1, UNIX_TIMESTAMP());
175   -
176   -SELECT '超级管理员: admin / admin123(role=admin)' AS 超管账号;
177   -SELECT '系统管理菜单: /system/menu /system/role-menu /admin-user' AS 系统管理;
  1 +-- 外卖配送中台 初始化数据脚本
  2 +-- 执行前请确保已执行 schema.sql 建表
  3 +-- 执行方式:mysql -u root -p dili_rider < data-init.sql
  4 +
  5 +USE dili_rider;
  6 +
  7 +-- ============================================================
  8 +-- 1. 城市数据(省+市两级)
  9 +-- ============================================================
  10 +INSERT INTO `city` (`id`, `pid`, `name`, `area_code`, `status`, `rate`, `list_order`, `config`) VALUES
  11 +(1, 0, '广东省', '44000000', 0, 0.00, 1, NULL),
  12 +(2, 1, '广州市', '44010000', 1, 10.00, 1,
  13 +'{
  14 + "type": [6],
  15 + "type6": {
  16 + "feeMode": 2,
  17 + "fixMoney": 0,
  18 + "distanceSwitch": 1,
  19 + "distanceBasic": 3,
  20 + "distanceBasicMoney": 4.00,
  21 + "distanceMoreMoney": 1.50,
  22 + "distanceMode": 1,
  23 + "distanceType": 1,
  24 + "weightSwitch": 0,
  25 + "weightBasic": 0,
  26 + "weightBasicMoney": 0,
  27 + "weightMoreMoney": 0,
  28 + "weightType": 1,
  29 + "times": [
  30 + {"start": 0, "end": 480, "isOpen": 0, "money": 0},
  31 + {"start": 480, "end": 1320, "isOpen": 1, "money": 0},
  32 + {"start": 1320, "end": 1440, "isOpen": 1, "money": 2}
  33 + ]
  34 + },
  35 + "distanceBasic": 3,
  36 + "distanceBasicTime": 30,
  37 + "distanceMoreTime": 10
  38 +}'),
  39 +(3, 1, '深圳市', '44030000', 1, 10.00, 2,
  40 +'{
  41 + "type": [6],
  42 + "type6": {
  43 + "feeMode": 1,
  44 + "fixMoney": 5.00,
  45 + "distanceSwitch": 0,
  46 + "distanceBasic": 0,
  47 + "distanceBasicMoney": 0,
  48 + "distanceMoreMoney": 0,
  49 + "distanceMode": 1,
  50 + "distanceType": 1,
  51 + "weightSwitch": 0,
  52 + "weightBasic": 0,
  53 + "weightBasicMoney": 0,
  54 + "weightMoreMoney": 0,
  55 + "weightType": 1,
  56 + "times": []
  57 + },
  58 + "distanceBasic": 3,
  59 + "distanceBasicTime": 25,
  60 + "distanceMoreTime": 8
  61 +}');
  62 +
  63 +-- ============================================================
  64 +-- 2. 分站管理员(每个已开通城市一个)
  65 +-- 默认密码均为 admin123(MD5: 0192023a7bbd73250516f069df18b500)
  66 +-- ============================================================
  67 +INSERT INTO `sys_role` (`code`, `name`, `role_scope`, `status`, `create_time`) VALUES
  68 +('platform_admin', '平台管理员', 'PLATFORM', 1, UNIX_TIMESTAMP()),
  69 +('substation_admin', '分站管理员', 'SUBSTATION', 1, UNIX_TIMESTAMP());
  70 +
  71 +INSERT INTO `sys_menu` (`code`, `name`, `type`, `path`, `icon`, `parent_id`, `menu_scope`, `list_order`, `visible`, `status`, `create_time`) VALUES
  72 +('dashboard', '工作台', 'MENU', '/dashboard', 'HomeOutlined', 0, 'BOTH', 10, 1, 1, UNIX_TIMESTAMP()),
  73 +('city.manage', '租户管理', 'MENU', '/city', 'GlobalOutlined', 0, 'PLATFORM', 20, 1, 1, UNIX_TIMESTAMP()),
  74 +('substation.manage', '分站管理', 'MENU', '/substation', 'ApartmentOutlined', 0, 'PLATFORM', 30, 1, 1, UNIX_TIMESTAMP()),
  75 +('merchant.root', '商家管理', 'DIR', '', 'ShopOutlined', 0, 'BOTH', 40, 1, 1, UNIX_TIMESTAMP()),
  76 +('merchant.enter', '入驻申请', 'MENU', '/merchant/enter', '', 4, 'BOTH', 41, 1, 1, UNIX_TIMESTAMP()),
  77 +('merchant.store', '店铺管理', 'MENU', '/merchant/store', '', 4, 'BOTH', 42, 1, 1, UNIX_TIMESTAMP()),
  78 +('rider.list', '骑手管理', 'MENU', '/rider', 'UserOutlined', 0, 'BOTH', 50, 1, 1, UNIX_TIMESTAMP()),
  79 +('rider.evaluate', '骑手评价', 'MENU', '/rider/evaluate', 'StarOutlined', 0, 'BOTH', 60, 1, 1, UNIX_TIMESTAMP()),
  80 +('order.root', '订单管理', 'DIR', '', 'UnorderedListOutlined', 0, 'BOTH', 70, 1, 1, UNIX_TIMESTAMP()),
  81 +('order.list', '订单列表', 'MENU', '/order', '', 9, 'BOTH', 71, 1, 1, UNIX_TIMESTAMP()),
  82 +('order.refund', '退款管理', 'MENU', '/refund', '', 9, 'BOTH', 72, 1, 1, UNIX_TIMESTAMP()),
  83 +('order.delivery', '配送订单', 'MENU', '/delivery/order', '', 9, 'BOTH', 73, 1, 1, UNIX_TIMESTAMP()),
  84 +('config.root', '配置中心', 'DIR', '', 'ControlOutlined', 0, 'BOTH', 80, 1, 1, UNIX_TIMESTAMP()),
  85 +('fee.plan', '配送费配置', 'MENU', '/config/fee-plan', '', 13, 'BOTH', 81, 1, 1, UNIX_TIMESTAMP()),
  86 +('dispatch.rule', '调度配置', 'MENU', '/dispatch/rule', '', 13, 'BOTH', 82, 1, 1, UNIX_TIMESTAMP()),
  87 +('open.root', '开放平台', 'DIR', '', 'ApiOutlined', 0, 'PLATFORM', 90, 1, 1, UNIX_TIMESTAMP()),
  88 +('open.app', '应用管理', 'MENU', '/open', '', 16, 'PLATFORM', 91, 1, 1, UNIX_TIMESTAMP()),
  89 +('open.mock_delivery', '模拟推单', 'MENU', '/open/mock-delivery', '', 16, 'PLATFORM', 92, 1, 1, UNIX_TIMESTAMP()),
  90 +('system.root', '系统管理', 'DIR', '', 'ControlOutlined', 0, 'PLATFORM', 100, 1, 1, UNIX_TIMESTAMP()),
  91 +('system.menu', '菜单管理', 'MENU', '/system/menu', '', 19, 'PLATFORM', 101, 1, 1, UNIX_TIMESTAMP()),
  92 +('system.role_menu', '角色菜单', 'MENU', '/system/role-menu', '', 19, 'PLATFORM', 102, 1, 1, UNIX_TIMESTAMP()),
  93 +('admin.user', '平台账号', 'MENU', '/admin-user', '', 19, 'PLATFORM', 103, 1, 1, UNIX_TIMESTAMP()),
  94 +('rider.withdraw', '骑手提现审核', 'MENU', '/rider/withdraw', 'WalletOutlined', 0, 'BOTH', 56, 1, 1, UNIX_TIMESTAMP());
  95 +
  96 +INSERT INTO `sys_role_menu` (`role_id`, `menu_id`, `create_time`)
  97 +SELECT 1, id, UNIX_TIMESTAMP() FROM `sys_menu` WHERE `menu_scope` IN ('PLATFORM', 'BOTH');
  98 +
  99 +INSERT INTO `sys_role_menu` (`role_id`, `menu_id`, `create_time`)
  100 +SELECT 2, id, UNIX_TIMESTAMP() FROM `sys_menu` WHERE `menu_scope` IN ('SUBSTATION', 'BOTH');
  101 +
  102 +INSERT INTO `substation` (`city_id`, `user_login`, `user_nickname`, `user_pass`, `mobile`, `user_status`, `role_id`, `create_time`) VALUES
  103 +(2, 'gz_admin', '广州分站管理员', '0192023a7bbd73250516f069df18b500', '13800000001', 1, 2, UNIX_TIMESTAMP()),
  104 +(3, 'sz_admin', '深圳分站管理员', '0192023a7bbd73250516f069df18b500', '13800000002', 1, 2, UNIX_TIMESTAMP());
  105 +
  106 +-- ============================================================
  107 +-- 3. 骑手等级配置(广州)
  108 +-- ============================================================
  109 +INSERT INTO `rider_level` (`city_id`, `level_id`, `name`, `is_default`, `trans_nums`,
  110 + `run_fee_mode`, `run_fix_money`, `run_rate`, `distance_basic`, `distance_basic_money`,
  111 + `distance_more_money`, `distance_max_money`, `work_fee_mode`, `work_fix_money`, `work_rate`) VALUES
  112 +-- 普通骑手:按比例拿配送费的70%
  113 +(2, 1, '普通骑手', 1, 3,
  114 + 2, 0.00, 70.00, 0, 0.00, 0.00, 0.00,
  115 + 1, 5.00, 0.00),
  116 +-- 资深骑手:按比例拿配送费的80%
  117 +(2, 2, '资深骑手', 0, 5,
  118 + 2, 0.00, 80.00, 0, 0.00, 0.00, 0.00,
  119 + 1, 6.00, 0.00);
  120 +
  121 +-- 深圳骑手等级
  122 +INSERT INTO `rider_level` (`city_id`, `level_id`, `name`, `is_default`, `trans_nums`,
  123 + `run_fee_mode`, `run_fix_money`, `run_rate`, `distance_basic`, `distance_basic_money`,
  124 + `distance_more_money`, `distance_max_money`, `work_fee_mode`, `work_fix_money`, `work_rate`) VALUES
  125 +(3, 1, '普通骑手', 1, 3,
  126 + 2, 0.00, 70.00, 0, 0.00, 0.00, 0.00,
  127 + 1, 5.00, 0.00);
  128 +
  129 +-- ============================================================
  130 +-- 4. 示例骑手账号
  131 +-- 默认密码均为 test1234(MD5: 16d7a4fca7442dda3ad93c9a726597e4)
  132 +-- ============================================================
  133 +INSERT INTO `rider` (`mobile`, `user_login`, `user_nickname`, `user_pass`, `city_id`, `level_id`,
  134 + `type`, `user_status`, `balance`, `is_rest`, `create_time`) VALUES
  135 +('13900000001', 'phone_rider001', '张骑手', '16d7a4fca7442dda3ad93c9a726597e4', 2, 1, 1, 1, 0.00, 0, UNIX_TIMESTAMP()),
  136 +('13900000002', 'phone_rider002', '李骑手', '16d7a4fca7442dda3ad93c9a726597e4', 2, 1, 2, 1, 0.00, 0, UNIX_TIMESTAMP());
  137 +
  138 +-- ============================================================
  139 +-- 5. 示例商家店铺
  140 +-- ============================================================
  141 +INSERT INTO `merchant_store` (`name`, `thumb`, `city_id`, `address`, `lng`, `lat`,
  142 + `operating_state`, `automatic_order`, `shipping_type`, `free_shipping`, `up_to_send`,
  143 + `open_date`, `open_time`, `about`, `list_order`, `is_del`, `add_time`) VALUES
  144 +('测试餐厅', '', 2, '广州市天河区测试路1号', '113.330010', '23.132891',
  145 + 1, 1, 1, 30.00, 15.00,
  146 + '[1,2,3,4,5,6,7]', '["09:00","22:00"]', '测试店铺,仅供开发调试', 1, 0, UNIX_TIMESTAMP());
  147 +
  148 +-- 创建商家账号
  149 +INSERT INTO `merchant_users` (`store_id`, `mobile`, `user_nickname`, `user_status`, `type`, `create_time`)
  150 +VALUES (LAST_INSERT_ID(), '13700000001', '测试餐厅老板', 1, 1, UNIX_TIMESTAMP());
  151 +
  152 +-- ============================================================
  153 +-- 6. 开放平台示例应用
  154 +-- ============================================================
  155 +INSERT INTO `open_app` (`app_name`, `app_key`, `app_secret`, `store_id`, `status`,
  156 + `webhook_url`, `webhook_events`, `remark`, `create_time`) VALUES
  157 +('内部电商系统', 'TESTAPPKEY00001', 'testsecret0000000000000000000000000000000000000000000000000001', 0, 1,
  158 + '', '["order.created","order.accepted","order.arrived_shop","order.picking","order.completed","order.cancelled"]', '用于测试的内部应用', UNIX_TIMESTAMP());
  159 +
  160 +-- ============================================================
  161 +-- 完成提示
  162 +-- ============================================================
  163 +SELECT '初始化完成!' AS 提示;
  164 +SELECT '骑手登录账号: 13900000001 / 13900000002,密码: test1234' AS 骑手账号;
  165 +SELECT '分站管理员: gz_admin / sz_admin,密码: admin123' AS 分站账号;
  166 +SELECT '商家手机号: 13700000001' AS 商家账号;
  167 +SELECT '开放平台 AppKey: TESTAPPKEY00001' AS 开放平台;
  168 +
  169 +-- ============================================================
  170 +-- 7. 超级管理员账号
  171 +-- 默认密码:admin123(MD5: 0192023a7bbd73250516f069df18b500)
  172 +-- ============================================================
  173 +INSERT INTO `admin_user` (`user_login`, `user_pass`, `user_nickname`, `user_status`, `role_id`, `create_time`) VALUES
  174 +('admin', '0192023a7bbd73250516f069df18b500', '超级管理员', 1, 1, UNIX_TIMESTAMP());
  175 +
  176 +SELECT '超级管理员: admin / admin123(role=admin)' AS 超管账号;
  177 +SELECT '系统管理菜单: /system/menu /system/role-menu /admin-user' AS 系统管理;
... ...
src/main/resources/schema.sql
1   --- 外卖骑手配送模块 数据库建表脚本
2   --- 数据库:dili_rider
3   -
4   -CREATE DATABASE IF NOT EXISTS dili_rider DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
5   -USE dili_rider;
6   -
7   --- 骑手信息表
8   -CREATE TABLE `rider` (
9   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '骑手ID',
10   - `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号',
11   - `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录名',
12   - `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
13   - `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)',
14   - `avatar` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像',
15   - `avatar_thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像缩略图',
16   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
17   - `level_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '等级ID',
18   - `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=兼职 2=全职',
19   - `user_status` TINYINT NOT NULL DEFAULT 2 COMMENT '审核状态:0=拒绝 1=通过 2=待审核',
20   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '账号状态:0=禁用 1=正常',
21   - `balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '余额(兼职用)',
22   - `frozen_balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额(提现审核中)',
23   - `is_rest` TINYINT NOT NULL DEFAULT 0 COMMENT '是否休息:0=否 1=是',
24   - `hold_order_limit` INT NOT NULL DEFAULT 0 COMMENT '个人持单上限,0=不限制',
25   - `id_no` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '身份证号',
26   - `thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '手持身份证照片',
27   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '注册时间',
28   - `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=正常 1=已删除',
29   - PRIMARY KEY (`id`),
30   - UNIQUE KEY `uk_mobile` (`mobile`),
31   - KEY `idx_city_id` (`city_id`)
32   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手信息表';
33   -
34   --- 骑手等级配置表
35   -CREATE TABLE `rider_level` (
36   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
37   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
38   - `level_id` INT NOT NULL DEFAULT 0 COMMENT '等级编号',
39   - `name` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '等级名称',
40   - `is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认',
41   - `trans_nums` INT NOT NULL DEFAULT 0 COMMENT '每日转单次数上限',
42   - `run_fee_mode` TINYINT NOT NULL DEFAULT 1 COMMENT '跑腿收入模式:1=固定 2=比例 3=距离',
43   - `run_fix_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '跑腿固定金额',
44   - `run_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '跑腿比例(%)',
45   - `distance_basic` INT NOT NULL DEFAULT 0 COMMENT '起始距离(米)',
46   - `distance_basic_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '基础配送费',
47   - `distance_more_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '超出每公里费',
48   - `distance_max_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '最高配送费上限',
49   - `work_fee_mode` TINYINT NOT NULL DEFAULT 1 COMMENT '办事收入模式:1=固定 2=比例',
50   - `work_fix_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '办事固定金额',
51   - `work_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '办事比例(%)',
52   - PRIMARY KEY (`id`),
53   - UNIQUE KEY `uk_city_level` (`city_id`, `level_id`),
54   - KEY `idx_city_default` (`city_id`, `is_default`)
55   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手等级配置表';
56   -
57   --- 骑手实时位置表
58   -CREATE TABLE `rider_location` (
59   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
60   - `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
61   - `lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '经度',
62   - `lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '纬度',
63   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间',
64   - PRIMARY KEY (`id`),
65   - UNIQUE KEY `uk_uid` (`uid`)
66   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手实时位置表';
67   -
68   --- 骑手余额流水表
69   -CREATE TABLE `rider_balance` (
70   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
71   - `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
72   - `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=收入 2=提现',
73   - `action` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '动作标识',
74   - `action_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联ID',
75   - `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号',
76   - `nums` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '变动金额',
77   - `total` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '变动后余额',
78   - `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '记录时间',
79   - PRIMARY KEY (`id`),
80   - KEY `idx_uid` (`uid`),
81   - KEY `idx_action_id` (`action_id`)
82   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手余额流水表';
83   -
84   --- 骑手提现申请表
85   -CREATE TABLE `rider_withdraw_apply` (
86   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
87   - `withdraw_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '提现单号',
88   - `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID',
89   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
90   - `amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '提现金额',
91   - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=待审核 1=审核通过待打款 2=审核拒绝 3=已打款 4=打款失败',
92   - `account_type` TINYINT NOT NULL DEFAULT 1 COMMENT '收款账户类型:1=银行卡 2=支付宝 3=微信',
93   - `account_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收款人',
94   - `bank_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '开户行',
95   - `bank_branch` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '开户支行',
96   - `account_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '收款账号',
97   - `apply_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '申请备注',
98   - `audit_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '审核/打款备注',
99   - `auditor_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核人ID',
100   - `auditor_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '审核人名称',
101   - `apply_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请时间',
102   - `audit_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核时间',
103   - `pay_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '打款时间',
104   - `transfer_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '打款流水号',
105   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
106   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
107   - PRIMARY KEY (`id`),
108   - UNIQUE KEY `uk_withdraw_no` (`withdraw_no`),
109   - KEY `idx_rider_status` (`rider_id`, `status`),
110   - KEY `idx_city_status_time` (`city_id`, `status`, `apply_time`)
111   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手提现申请表';
112   -
113   --- 骑手订单统计表
114   -CREATE TABLE `rider_order_count` (
115   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
116   - `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
117   - `count_date` INT NOT NULL COMMENT '统计日期yyyyMMdd',
118   - `orders` INT NOT NULL DEFAULT 0 COMMENT '完成订单数',
119   - `transfers` INT NOT NULL DEFAULT 0 COMMENT '转单数',
120   - `distance` BIGINT NOT NULL DEFAULT 0 COMMENT '配送距离(米)',
121   - PRIMARY KEY (`id`),
122   - UNIQUE KEY `uk_uid_date` (`uid`, `count_date`)
123   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手订单统计表';
124   -
125   --- 骑手拒单记录表
126   -CREATE TABLE `rider_orders_refuse` (
127   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
128   - `rider_id` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
129   - `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
130   - `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '拒单时间',
131   - PRIMARY KEY (`id`),
132   - KEY `idx_rider_id` (`rider_id`),
133   - KEY `idx_oid` (`oid`)
134   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手拒单记录表';
135   -
136   --- 订单主表
137   -CREATE TABLE `orders` (
138   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单ID',
139   - `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号',
140   - `uid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户ID',
141   - `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID',
142   - `old_rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '原始骑手ID',
143   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
144   - `type` TINYINT NOT NULL DEFAULT 6 COMMENT '订单类型:6=外卖配送',
145   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1待支付 2已支付 3已接单 4服务中 6已完成 7退款申请 8退款成功 9退款拒绝 10已取消',
146   - `pay_type` TINYINT NOT NULL DEFAULT 0 COMMENT '支付类型:1=支付宝 2=微信',
147   - `money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额',
148   - `money_delivery` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '配送费',
149   - `money_total` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '实付总金额',
150   - `rider_income` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '骑手收入',
151   - `substation_income` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '站点收入',
152   - `is_income` TINYINT NOT NULL DEFAULT 0 COMMENT '结算状态:0=未结算 1=待结算 2=已结算',
153   - `is_trans` TINYINT NOT NULL DEFAULT 0 COMMENT '转单状态:0=未转 1=通过 2=申请中 3=拒绝',
154   - `code` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '完成码',
155   - `f_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '起点名称',
156   - `f_addr` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '起点地址',
157   - `f_lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '起点经度',
158   - `f_lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '起点纬度',
159   - `t_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '终点名称',
160   - `t_addr` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '终点地址',
161   - `t_lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '终点经度',
162   - `t_lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '终点纬度',
163   - `recip_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收件人姓名',
164   - `recip_phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '收件人电话',
165   - `extra` TEXT COMMENT '附加信息JSON(距离、重量等)',
166   - `thumbs` TEXT COMMENT '取件照片JSON数组',
167   - `store_oid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺订单ID',
168   - `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除',
169   - `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '下单时间',
170   - `pay_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付时间',
171   - `grap_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '接单时间',
172   - `pick_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '取件时间',
173   - `complete_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '完成时间',
174   - `trans_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '转单时间',
175   - PRIMARY KEY (`id`),
176   - UNIQUE KEY `uk_order_no` (`order_no`),
177   - KEY `idx_rider_id` (`rider_id`),
178   - KEY `idx_uid` (`uid`),
179   - KEY `idx_city_status` (`city_id`, `status`),
180   - KEY `idx_old_rider_trans` (`old_rider_id`, `is_trans`, `trans_time`)
181   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';
182   -
183   --- 城市表(配送中台核心配置)
184   -CREATE TABLE `city` (
185   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '城市ID',
186   - `pid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父级ID,0=省级',
187   - `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '城市名称',
188   - `area_code` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '行政区划码',
189   - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=未开通 1=已开通',
190   - `rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '平台抽成比例(%)',
191   - `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
192   - PRIMARY KEY (`id`),
193   - KEY `idx_pid_order` (`pid`, `list_order`),
194   - KEY `idx_area_code` (`area_code`)
195   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='城市表';
196   -
197   --- 配送计价方案主表
198   -CREATE TABLE `delivery_fee_plan` (
199   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
200   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '租户ID',
201   - `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '方案名称',
202   - `is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认方案',
203   - `min_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '保底费用',
204   - `distance_basic` DECIMAL(10,2) NOT NULL DEFAULT 3.00 COMMENT '预计送达基础距离(km)',
205   - `distance_basic_time` INT NOT NULL DEFAULT 30 COMMENT '预计送达基础时间(分钟)',
206   - `distance_more_time` INT NOT NULL DEFAULT 10 COMMENT '预计送达超出每km增加时间(分钟)',
207   - `rider_distance` DECIMAL(10,2) NOT NULL DEFAULT 3.00 COMMENT '附近骑手展示范围(km)',
208   - `rider_time` INT NOT NULL DEFAULT 0 COMMENT '预计接单时间(分钟)',
209   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用',
210   - `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
211   - `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
212   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
213   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
214   - PRIMARY KEY (`id`),
215   - KEY `idx_city_default` (`city_id`, `is_default`),
216   - KEY `idx_city_status` (`city_id`, `status`)
217   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价方案主表';
218   -
219   --- 配送计价维度主配置表
220   -CREATE TABLE `delivery_fee_plan_dimension` (
221   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
222   - `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID',
223   - `dimension_type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '维度类型:base/distance/weight/piece/time',
224   - `enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '是否启用',
225   - `base_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '基础费',
226   - `start_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起步里程(km)',
227   - `start_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起步费用',
228   - `first_weight` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '首重(kg)',
229   - `first_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '首重费用',
230   - `unit_weight_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '续重单价',
231   - `cap_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '封顶费用',
232   - `extra_json` TEXT COMMENT '扩展配置',
233   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
234   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
235   - PRIMARY KEY (`id`),
236   - UNIQUE KEY `uk_plan_dimension` (`plan_id`, `dimension_type`)
237   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价维度主配置表';
238   -
239   --- 里程阶梯表
240   -CREATE TABLE `delivery_fee_plan_distance_step` (
241   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
242   - `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID',
243   - `end_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '结束里程(km)',
244   - `unit_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '每档里程(km)',
245   - `unit_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '每档加价',
246   - `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
247   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
248   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
249   - PRIMARY KEY (`id`),
250   - KEY `idx_plan_order` (`plan_id`, `list_order`)
251   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价里程阶梯表';
252   -
253   --- 件数区间表
254   -CREATE TABLE `delivery_fee_plan_piece_rule` (
255   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
256   - `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID',
257   - `start_piece` INT NOT NULL DEFAULT 0 COMMENT '起始件数',
258   - `end_piece` INT NOT NULL DEFAULT 0 COMMENT '结束件数',
259   - `fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '费用',
260   - `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
261   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
262   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
263   - PRIMARY KEY (`id`),
264   - KEY `idx_plan_order` (`plan_id`, `list_order`)
265   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价件数区间表';
266   -
267   --- 时段附加费表
268   -CREATE TABLE `delivery_fee_plan_time_rule` (
269   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
270   - `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID',
271   - `start_minute` INT NOT NULL DEFAULT 0 COMMENT '开始分钟',
272   - `end_minute` INT NOT NULL DEFAULT 0 COMMENT '结束分钟',
273   - `fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '附加费',
274   - `enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用',
275   - `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
276   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
277   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
278   - PRIMARY KEY (`id`),
279   - KEY `idx_plan_order` (`plan_id`, `list_order`)
280   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价时段附加费表';
281   -
282   --- 分站管理员表(每城市一个,管理本城市骑手和订单)
283   -CREATE TABLE `substation` (
284   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分站ID',
285   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '管理城市ID',
286   - `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录账号',
287   - `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
288   - `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)',
289   - `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号',
290   - `avatar` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像',
291   - `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
292   - `role_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '菜单角色ID',
293   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
294   - PRIMARY KEY (`id`),
295   - UNIQUE KEY `uk_user_login` (`user_login`)
296   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分站管理员表(一个城市/租户下可有多个管理员)';
297   -
298   --- 商家入驻申请表
299   -CREATE TABLE `merchant_enter` (
300   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
301   - `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '联系人姓名',
302   - `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号',
303   - `store_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '店铺名称',
304   - `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=商家入驻 2=骑手入驻 3=商务合作',
305   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
306   - `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
307   - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=未处理 1=已通过 -1=已拒绝',
308   - `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请时间',
309   - PRIMARY KEY (`id`),
310   - KEY `idx_status_type` (`status`, `type`),
311   - KEY `idx_city_id` (`city_id`)
312   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家入驻申请表';
313   -
314   --- 商家账号表
315   -CREATE TABLE `merchant_users` (
316   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
317   - `store_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺ID',
318   - `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号(登录账号)',
319   - `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
320   - `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
321   - `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=商家',
322   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
323   - PRIMARY KEY (`id`),
324   - UNIQUE KEY `uk_mobile` (`mobile`),
325   - KEY `idx_store_id` (`store_id`)
326   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家账号表';
327   -
328   --- 商家店铺表
329   -CREATE TABLE `merchant_store` (
330   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '店铺ID',
331   - `name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '店铺名称',
332   - `thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封面图',
333   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属城市ID',
334   - `address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '店铺地址',
335   - `lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '经度',
336   - `lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '纬度',
337   - `operating_state` TINYINT NOT NULL DEFAULT 1 COMMENT '营业状态:0=打烊 1=营业',
338   - `automatic_order` TINYINT NOT NULL DEFAULT 0 COMMENT '自动接单:0=否 1=是',
339   - `shipping_type` TINYINT NOT NULL DEFAULT 1 COMMENT '配送类型:1=外卖配送 2=到店自提',
340   - `free_shipping` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '免运费门槛,0=不免',
341   - `up_to_send` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起送金额,0=不限',
342   - `open_date` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '营业日期JSON,如[1,2,3,4,5]',
343   - `open_time` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '营业时间JSON,如["09:00","22:00"]',
344   - `about` TEXT COMMENT '店铺简介',
345   - `account_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联账号ID',
346   - `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '接入方AppKey,为空=平台自建',
347   - `out_store_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '接入方门店编号,用于推单时自动匹配',
348   - `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
349   - `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除',
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',
353   - PRIMARY KEY (`id`),
354   - KEY `idx_city_id` (`city_id`),
355   - UNIQUE KEY `uk_app_out_store` (`sync_app_key`, `sync_out_store_id`, `is_del`),
356   - KEY `idx_order_del` (`list_order`, `is_del`)
357   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家店铺表';
358   -
359   --- 开放平台应用表
360   -CREATE TABLE `open_app` (
361   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
362   - `app_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '应用名称',
363   - `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'AppKey',
364   - `app_secret` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'AppSecret',
365   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联城市/租户ID(必填,租户隔离核心字段)',
366   - `store_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺ID,0=不限制',
367   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
368   - `webhook_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Webhook回调地址',
369   - `webhook_events` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '订阅事件JSON数组',
370   - `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
371   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
372   - PRIMARY KEY (`id`),
373   - UNIQUE KEY `uk_app_key` (`app_key`),
374   - KEY `idx_city_id` (`city_id`)
375   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放平台应用表';
376   -
377   --- Webhook 推送日志表
378   -CREATE TABLE `webhook_log` (
379   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
380   - `app_id` BIGINT UNSIGNED NOT NULL COMMENT '应用ID',
381   - `event` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '事件类型',
382   - `biz_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '业务ID',
383   - `url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '推送URL',
384   - `payload` TEXT COMMENT '推送内容JSON',
385   - `response_code` INT NOT NULL DEFAULT 0 COMMENT 'HTTP响应码',
386   - `response_body` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '响应内容',
387   - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=失败 1=成功',
388   - `retry_count` INT NOT NULL DEFAULT 0 COMMENT '重试次数',
389   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
390   - PRIMARY KEY (`id`),
391   - KEY `idx_app_event` (`app_id`, `event`),
392   - KEY `idx_biz_id` (`biz_id`),
393   - KEY `idx_status` (`status`)
394   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Webhook推送日志表';
395   -
396   --- 超级管理员表
397   -CREATE TABLE `admin_user` (
398   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
399   - `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录账号',
400   - `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)',
401   - `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
402   - `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
403   - `role_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '菜单角色ID',
404   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
405   - PRIMARY KEY (`id`),
406   - UNIQUE KEY `uk_user_login` (`user_login`)
407   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='超级管理员表';
408   -
409   -CREATE TABLE `sys_role` (
410   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
411   - `code` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色编码',
412   - `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色名称',
413   - `role_scope` VARCHAR(32) NOT NULL DEFAULT 'PLATFORM' COMMENT '角色归属:PLATFORM/SUBSTATION',
414   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属租户ID:0=平台全局角色,>0=分站租户专属角色',
415   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
416   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
417   - PRIMARY KEY (`id`),
418   - UNIQUE KEY `uk_role_code` (`code`, `city_id`),
419   - KEY `idx_city_scope` (`city_id`, `role_scope`)
420   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台菜单角色表';
421   -
422   -CREATE TABLE `sys_menu` (
423   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
424   - `code` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '菜单编码',
425   - `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '菜单名称',
426   - `type` VARCHAR(16) NOT NULL DEFAULT 'MENU' COMMENT '类型:DIR/MENU',
427   - `path` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '前端路由路径',
428   - `icon` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '前端图标名',
429   - `parent_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父级菜单ID',
430   - `menu_scope` VARCHAR(32) NOT NULL DEFAULT 'BOTH' COMMENT '菜单归属:PLATFORM/SUBSTATION/BOTH',
431   - `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
432   - `visible` TINYINT NOT NULL DEFAULT 1 COMMENT '是否显示:0=否 1=是',
433   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
434   - KEY `idx_scope_parent` (`menu_scope`, `parent_id`, `list_order`),
435   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
436   - PRIMARY KEY (`id`),
437   - UNIQUE KEY `uk_menu_code` (`code`),
438   - KEY `idx_parent_order` (`parent_id`, `list_order`)
439   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台菜单表';
440   -
441   -CREATE TABLE `sys_role_menu` (
442   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
443   - `role_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '角色ID',
444   - `menu_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '菜单ID',
445   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
446   - PRIMARY KEY (`id`),
447   - UNIQUE KEY `uk_role_menu` (`role_id`, `menu_id`)
448   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台角色菜单关系表';
449   -
450   --- orders 表补充字段(如已有 orders 表,执行以下 ALTER)
451   -ALTER TABLE `orders` ADD COLUMN `out_order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '外部系统订单号' AFTER `order_no`;
452   -ALTER TABLE `orders` ADD COLUMN `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '接入方AppKey' AFTER `out_order_no`;
453   -ALTER TABLE `orders` ADD COLUMN `callback_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '状态回调地址' AFTER `app_key`;
454   -ALTER TABLE `orders` ADD INDEX `idx_app_out_order` (`app_key`, `out_order_no`);
455   -
456   --- 骑手评价表
457   -CREATE TABLE `rider_evaluate` (
458   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
459   - `uid` BIGINT UNSIGNED NOT NULL COMMENT '评价用户ID',
460   - `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
461   - `rid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
462   - `content` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '评价内容',
463   - `star` TINYINT NOT NULL DEFAULT 5 COMMENT '星级1-5',
464   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
465   - `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
466   - PRIMARY KEY (`id`),
467   - UNIQUE KEY `uk_uid_oid` (`uid`, `oid`),
468   - KEY `idx_rid` (`rid`)
469   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手评价表';
470   -
471   --- 退款原因配置表
472   -CREATE TABLE `orders_refund_reason` (
473   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
474   - `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '原因描述',
475   - `role` TINYINT NOT NULL DEFAULT 1 COMMENT '1=用户 2=骑手',
476   - `list_order` INT NOT NULL DEFAULT 0,
477   - PRIMARY KEY (`id`)
478   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款原因配置表';
479   -
480   --- 退款申请记录表
481   -CREATE TABLE `orders_refund_record` (
482   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
483   - `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
484   - `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号',
485   - `uid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请人ID',
486   - `role` TINYINT NOT NULL DEFAULT 1 COMMENT '1=用户 2=骑手',
487   - `reason_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
488   - `reason` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '退款原因',
489   - `money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '退款金额',
490   - `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=待处理 1=通过 2=拒绝',
491   - `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '处理备注',
492   - `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
493   - `handle_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
494   - PRIMARY KEY (`id`),
495   - KEY `idx_oid` (`oid`),
496   - KEY `idx_status` (`status`)
497   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款申请记录表';
498   -
499   --- 退款原因初始数据
500   -INSERT INTO `orders_refund_reason` (`name`, `role`, `list_order`) VALUES
501   -('骑手长时间未接单', 1, 1),
502   -('骑手态度恶劣', 1, 2),
503   -('物品损坏', 1, 3),
504   -('其他原因', 1, 99),
505   -('用户恶意单', 2, 1),
506   -('无法完成配送', 2, 2),
507   -('其他原因', 2, 99);
508   -
509   --- orders 表补充货物快照字段
510   -ALTER TABLE `orders` ADD COLUMN `items_json` TEXT COMMENT '货物清单快照JSON' AFTER `callback_url`;
511   -ALTER TABLE `orders` ADD COLUMN `item_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '整单货物备注' AFTER `items_json`;
512   -
513   --- orders 表补充调度字段
514   -ALTER TABLE `orders` ADD COLUMN `dispatch_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '系统派单时间' AFTER `trans_time`;
515   -ALTER TABLE `orders` ADD COLUMN `dispatch_rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '系统指派骑手ID' AFTER `dispatch_time`;
516   -
517   --- rider 表补充个人持单上限与评分统计字段
518   -ALTER TABLE `rider` ADD COLUMN `hold_order_limit` INT NOT NULL DEFAULT 0 COMMENT '个人持单上限,0=不限制' AFTER `is_rest`;
519   -ALTER TABLE `rider` ADD COLUMN `star_total` INT NOT NULL DEFAULT 0 COMMENT '评分总分' AFTER `thumb`;
520   -ALTER TABLE `rider` ADD COLUMN `star_count` INT NOT NULL DEFAULT 0 COMMENT '评分次数' AFTER `star_total`;
521   -
522   --- 调度规则模板表
523   -CREATE TABLE `dispatch_rule_template` (
524   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
525   - `city_id` BIGINT UNSIGNED NOT NULL COMMENT '城市ID',
526   - `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '模板名称',
527   - `is_active` TINYINT NOT NULL DEFAULT 0 COMMENT '是否当前生效:0=否 1=是',
528   - `grab_enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '抢单模式启用',
529   - `grab_timeout` INT NOT NULL DEFAULT 30 COMMENT '抢单超时分钟数,超时后转自动派单',
530   - `grab_scope` TINYINT NOT NULL DEFAULT 1 COMMENT '抢单可见范围:1=订单所属区域骑手 2=全部自营骑手',
531   - `grab_max_per_rider` INT NOT NULL DEFAULT 3 COMMENT '单人最大同时持单量',
532   - `auto_dispatch` TINYINT NOT NULL DEFAULT 1 COMMENT '同步开启自动派单:0=否 1=是',
533   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
534   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
535   - PRIMARY KEY (`id`),
536   - KEY `idx_city_active` (`city_id`, `is_active`)
537   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='调度规则模板表';
538   -
539   --- 派单优先级条件表
540   -CREATE TABLE `dispatch_rule_condition` (
541   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
542   - `template_id` BIGINT UNSIGNED NOT NULL COMMENT '所属模板ID',
543   - `condition_type` VARCHAR(32) NOT NULL COMMENT '条件类型',
544   - `enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用',
545   - `threshold_value` DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '阈值',
546   - `sort_order` INT NOT NULL DEFAULT 0 COMMENT '优先级排序(小的优先)',
547   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
548   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
549   - PRIMARY KEY (`id`),
550   - UNIQUE KEY `uk_template_type` (`template_id`, `condition_type`),
551   - KEY `idx_template_order` (`template_id`, `sort_order`)
552   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='派单优先级条件表';
553   -
554   --- 骑手消息表
555   -CREATE TABLE `rider_message` (
556   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '消息ID',
557   - `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID(多租户隔离)',
558   - `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID,0表示全员消息',
559   - `type` TINYINT NOT NULL DEFAULT 1 COMMENT '消息类型:1=订单消息 2=系统通知',
560   - `title` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '消息标题',
561   - `content` TEXT NOT NULL COMMENT '消息内容',
562   - `biz_type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '业务类型:order_assigned/order_timeout/system_notice',
563   - `biz_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联业务ID(如订单ID)',
564   - `extra_data` TEXT COMMENT '扩展数据(JSON格式)',
565   - `is_read` TINYINT NOT NULL DEFAULT 0 COMMENT '已读状态:0=未读 1=已读',
566   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
567   - `read_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '阅读时间',
568   - PRIMARY KEY (`id`),
569   - KEY `idx_rider_type` (`rider_id`, `type`, `create_time`),
570   - KEY `idx_city_create` (`city_id`, `create_time`),
571   - KEY `idx_biz` (`biz_type`, `biz_id`)
572   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手消息表';
573   -
574   --- 消息模板表
575   -CREATE TABLE `rider_message_template` (
576   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
577   - `biz_type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '业务类型标识',
578   - `type` TINYINT NOT NULL DEFAULT 1 COMMENT '消息类型:1=订单 2=系统',
579   - `title_template` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '标题模板,支持 #{key} 占位符',
580   - `content_template` TEXT NOT NULL COMMENT '内容模板,支持 #{key} 占位符',
581   - `is_push` TINYINT NOT NULL DEFAULT 1 COMMENT '是否实时推送:0=否 1=是',
582   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用',
583   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
584   - PRIMARY KEY (`id`),
585   - UNIQUE KEY `uk_biz_type` (`biz_type`)
586   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息模板表';
587   -
588   --- 初始化模板数据
589   -INSERT INTO `rider_message_template` (`biz_type`, `type`, `title_template`, `content_template`, `is_push`) VALUES
590   -('order_assigned', 1, '新订单', '您有新的配送订单 #{orderNo},请及时处理', 1),
591   -('order_timeout', 1, '订单超时提醒', '订单 #{orderNo} 即将超时,请尽快送达', 1),
592   -('order_cancelled', 1, '订单取消', '订单 #{orderNo} 已被取消', 1),
593   -('system_notice', 2, '系统通知', '#{content}', 1),
594   -('level_upgrade', 2, '等级提升', '恭喜您,等级已提升至 #{levelName}', 1),
595   -('balance_change', 2, '余额变动', '您的余额发生变动,当前余额:#{balance} 元', 0);
596   -
597   --- 骑手设备推送绑定表(极光推送)
598   -CREATE TABLE `rider_device` (
599   - `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
600   - `rider_id` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
601   - `registration_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'JPush 设备注册ID',
602   - `platform` TINYINT NOT NULL DEFAULT 0 COMMENT '平台:1=Android 2=iOS',
603   - `device_info` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '设备信息(型号/系统版本)',
604   - `app_version` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'App 版本',
605   - `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=已解绑 1=正常',
606   - `last_active_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '最近活跃时间',
607   - `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
608   - `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
609   - PRIMARY KEY (`id`),
610   - UNIQUE KEY `uk_registration_id` (`registration_id`),
611   - KEY `idx_rider_status` (`rider_id`, `status`)
612   -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手设备推送绑定表';
  1 +-- 外卖骑手配送模块 数据库建表脚本
  2 +-- 数据库:dili_rider
  3 +
  4 +CREATE DATABASE IF NOT EXISTS dili_rider DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  5 +USE dili_rider;
  6 +
  7 +-- 骑手信息表
  8 +CREATE TABLE `rider` (
  9 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '骑手ID',
  10 + `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号',
  11 + `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录名',
  12 + `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
  13 + `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)',
  14 + `avatar` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像',
  15 + `avatar_thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像缩略图',
  16 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
  17 + `level_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '等级ID',
  18 + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=兼职 2=全职',
  19 + `user_status` TINYINT NOT NULL DEFAULT 2 COMMENT '审核状态:0=拒绝 1=通过 2=待审核',
  20 + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '账号状态:0=禁用 1=正常',
  21 + `balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '余额(兼职用)',
  22 + `frozen_balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额(提现审核中)',
  23 + `is_rest` TINYINT NOT NULL DEFAULT 0 COMMENT '是否休息:0=否 1=是',
  24 + `hold_order_limit` INT NOT NULL DEFAULT 0 COMMENT '个人持单上限,0=不限制',
  25 + `id_no` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '身份证号',
  26 + `thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '手持身份证照片',
  27 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '注册时间',
  28 + `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=正常 1=已删除',
  29 + PRIMARY KEY (`id`),
  30 + UNIQUE KEY `uk_mobile` (`mobile`),
  31 + KEY `idx_city_id` (`city_id`)
  32 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手信息表';
  33 +
  34 +-- 骑手等级配置表
  35 +CREATE TABLE `rider_level` (
  36 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  37 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
  38 + `level_id` INT NOT NULL DEFAULT 0 COMMENT '等级编号',
  39 + `name` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '等级名称',
  40 + `is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认',
  41 + `trans_nums` INT NOT NULL DEFAULT 0 COMMENT '每日转单次数上限',
  42 + `run_fee_mode` TINYINT NOT NULL DEFAULT 1 COMMENT '跑腿收入模式:1=固定 2=比例 3=距离',
  43 + `run_fix_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '跑腿固定金额',
  44 + `run_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '跑腿比例(%)',
  45 + `distance_basic` INT NOT NULL DEFAULT 0 COMMENT '起始距离(米)',
  46 + `distance_basic_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '基础配送费',
  47 + `distance_more_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '超出每公里费',
  48 + `distance_max_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '最高配送费上限',
  49 + `work_fee_mode` TINYINT NOT NULL DEFAULT 1 COMMENT '办事收入模式:1=固定 2=比例',
  50 + `work_fix_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '办事固定金额',
  51 + `work_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '办事比例(%)',
  52 + PRIMARY KEY (`id`),
  53 + UNIQUE KEY `uk_city_level` (`city_id`, `level_id`),
  54 + KEY `idx_city_default` (`city_id`, `is_default`)
  55 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手等级配置表';
  56 +
  57 +-- 骑手实时位置表
  58 +CREATE TABLE `rider_location` (
  59 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  60 + `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
  61 + `lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '经度',
  62 + `lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '纬度',
  63 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间',
  64 + PRIMARY KEY (`id`),
  65 + UNIQUE KEY `uk_uid` (`uid`)
  66 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手实时位置表';
  67 +
  68 +-- 骑手余额流水表
  69 +CREATE TABLE `rider_balance` (
  70 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  71 + `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
  72 + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=收入 2=提现',
  73 + `action` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '动作标识',
  74 + `action_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联ID',
  75 + `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号',
  76 + `nums` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '变动金额',
  77 + `total` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '变动后余额',
  78 + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '记录时间',
  79 + PRIMARY KEY (`id`),
  80 + KEY `idx_uid` (`uid`),
  81 + KEY `idx_action_id` (`action_id`)
  82 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手余额流水表';
  83 +
  84 +-- 骑手提现申请表
  85 +CREATE TABLE `rider_withdraw_apply` (
  86 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  87 + `withdraw_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '提现单号',
  88 + `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID',
  89 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
  90 + `amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '提现金额',
  91 + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=待审核 1=审核通过待打款 2=审核拒绝 3=已打款 4=打款失败',
  92 + `account_type` TINYINT NOT NULL DEFAULT 1 COMMENT '收款账户类型:1=银行卡 2=支付宝 3=微信',
  93 + `account_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收款人',
  94 + `bank_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '开户行',
  95 + `bank_branch` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '开户支行',
  96 + `account_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '收款账号',
  97 + `apply_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '申请备注',
  98 + `audit_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '审核/打款备注',
  99 + `auditor_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核人ID',
  100 + `auditor_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '审核人名称',
  101 + `apply_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请时间',
  102 + `audit_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核时间',
  103 + `pay_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '打款时间',
  104 + `transfer_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '打款流水号',
  105 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  106 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  107 + PRIMARY KEY (`id`),
  108 + UNIQUE KEY `uk_withdraw_no` (`withdraw_no`),
  109 + KEY `idx_rider_status` (`rider_id`, `status`),
  110 + KEY `idx_city_status_time` (`city_id`, `status`, `apply_time`)
  111 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手提现申请表';
  112 +
  113 +-- 骑手订单统计表
  114 +CREATE TABLE `rider_order_count` (
  115 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  116 + `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
  117 + `count_date` INT NOT NULL COMMENT '统计日期yyyyMMdd',
  118 + `orders` INT NOT NULL DEFAULT 0 COMMENT '完成订单数',
  119 + `transfers` INT NOT NULL DEFAULT 0 COMMENT '转单数',
  120 + `distance` BIGINT NOT NULL DEFAULT 0 COMMENT '配送距离(米)',
  121 + PRIMARY KEY (`id`),
  122 + UNIQUE KEY `uk_uid_date` (`uid`, `count_date`)
  123 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手订单统计表';
  124 +
  125 +-- 骑手拒单记录表
  126 +CREATE TABLE `rider_orders_refuse` (
  127 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  128 + `rider_id` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
  129 + `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
  130 + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '拒单时间',
  131 + PRIMARY KEY (`id`),
  132 + KEY `idx_rider_id` (`rider_id`),
  133 + KEY `idx_oid` (`oid`)
  134 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手拒单记录表';
  135 +
  136 +-- 订单主表
  137 +CREATE TABLE `orders` (
  138 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单ID',
  139 + `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号',
  140 + `uid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户ID',
  141 + `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID',
  142 + `old_rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '原始骑手ID',
  143 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
  144 + `type` TINYINT NOT NULL DEFAULT 6 COMMENT '订单类型:6=外卖配送',
  145 + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1待支付 2已支付 3已接单 4服务中 6已完成 7退款申请 8退款成功 9退款拒绝 10已取消',
  146 + `pay_type` TINYINT NOT NULL DEFAULT 0 COMMENT '支付类型:1=支付宝 2=微信',
  147 + `money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额',
  148 + `money_delivery` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '配送费',
  149 + `money_total` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '实付总金额',
  150 + `rider_income` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '骑手收入',
  151 + `substation_income` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '站点收入',
  152 + `is_income` TINYINT NOT NULL DEFAULT 0 COMMENT '结算状态:0=未结算 1=待结算 2=已结算',
  153 + `is_trans` TINYINT NOT NULL DEFAULT 0 COMMENT '转单状态:0=未转 1=通过 2=申请中 3=拒绝',
  154 + `code` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '完成码',
  155 + `f_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '起点名称',
  156 + `f_addr` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '起点地址',
  157 + `f_lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '起点经度',
  158 + `f_lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '起点纬度',
  159 + `t_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '终点名称',
  160 + `t_addr` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '终点地址',
  161 + `t_lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '终点经度',
  162 + `t_lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '终点纬度',
  163 + `recip_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收件人姓名',
  164 + `recip_phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '收件人电话',
  165 + `extra` TEXT COMMENT '附加信息JSON(距离、重量等)',
  166 + `thumbs` TEXT COMMENT '取件照片JSON数组',
  167 + `store_oid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺订单ID',
  168 + `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除',
  169 + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '下单时间',
  170 + `pay_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付时间',
  171 + `grap_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '接单时间',
  172 + `pick_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '取件时间',
  173 + `arrive_shop_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手到店时间',
  174 + `arrive_shop_lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '骑手到店经度',
  175 + `arrive_shop_lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '骑手到店纬度',
  176 + `arrive_shop_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '骑手到店距离门店距离,单位米',
  177 + `complete_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '完成时间',
  178 + `trans_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '转单时间',
  179 + PRIMARY KEY (`id`),
  180 + UNIQUE KEY `uk_order_no` (`order_no`),
  181 + KEY `idx_rider_id` (`rider_id`),
  182 + KEY `idx_uid` (`uid`),
  183 + KEY `idx_city_status` (`city_id`, `status`),
  184 + KEY `idx_old_rider_trans` (`old_rider_id`, `is_trans`, `trans_time`)
  185 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';
  186 +
  187 +-- 城市表(配送中台核心配置)
  188 +CREATE TABLE `city` (
  189 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '城市ID',
  190 + `pid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父级ID,0=省级',
  191 + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '城市名称',
  192 + `area_code` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '行政区划码',
  193 + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=未开通 1=已开通',
  194 + `rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '平台抽成比例(%)',
  195 + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
  196 + PRIMARY KEY (`id`),
  197 + KEY `idx_pid_order` (`pid`, `list_order`),
  198 + KEY `idx_area_code` (`area_code`)
  199 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='城市表';
  200 +
  201 +-- 配送计价方案主表
  202 +CREATE TABLE `delivery_fee_plan` (
  203 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  204 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '租户ID',
  205 + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '方案名称',
  206 + `is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认方案',
  207 + `min_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '保底费用',
  208 + `distance_basic` DECIMAL(10,2) NOT NULL DEFAULT 3.00 COMMENT '预计送达基础距离(km)',
  209 + `distance_basic_time` INT NOT NULL DEFAULT 30 COMMENT '预计送达基础时间(分钟)',
  210 + `distance_more_time` INT NOT NULL DEFAULT 10 COMMENT '预计送达超出每km增加时间(分钟)',
  211 + `rider_distance` DECIMAL(10,2) NOT NULL DEFAULT 3.00 COMMENT '附近骑手展示范围(km)',
  212 + `rider_time` INT NOT NULL DEFAULT 0 COMMENT '预计接单时间(分钟)',
  213 + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用',
  214 + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
  215 + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
  216 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  217 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  218 + PRIMARY KEY (`id`),
  219 + KEY `idx_city_default` (`city_id`, `is_default`),
  220 + KEY `idx_city_status` (`city_id`, `status`)
  221 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价方案主表';
  222 +
  223 +-- 配送计价维度主配置表
  224 +CREATE TABLE `delivery_fee_plan_dimension` (
  225 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  226 + `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID',
  227 + `dimension_type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '维度类型:base/distance/weight/piece/time',
  228 + `enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '是否启用',
  229 + `base_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '基础费',
  230 + `start_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起步里程(km)',
  231 + `start_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起步费用',
  232 + `first_weight` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '首重(kg)',
  233 + `first_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '首重费用',
  234 + `unit_weight_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '续重单价',
  235 + `cap_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '封顶费用',
  236 + `extra_json` TEXT COMMENT '扩展配置',
  237 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  238 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  239 + PRIMARY KEY (`id`),
  240 + UNIQUE KEY `uk_plan_dimension` (`plan_id`, `dimension_type`)
  241 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价维度主配置表';
  242 +
  243 +-- 里程阶梯表
  244 +CREATE TABLE `delivery_fee_plan_distance_step` (
  245 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  246 + `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID',
  247 + `end_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '结束里程(km)',
  248 + `unit_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '每档里程(km)',
  249 + `unit_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '每档加价',
  250 + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
  251 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  252 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  253 + PRIMARY KEY (`id`),
  254 + KEY `idx_plan_order` (`plan_id`, `list_order`)
  255 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价里程阶梯表';
  256 +
  257 +-- 件数区间表
  258 +CREATE TABLE `delivery_fee_plan_piece_rule` (
  259 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  260 + `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID',
  261 + `start_piece` INT NOT NULL DEFAULT 0 COMMENT '起始件数',
  262 + `end_piece` INT NOT NULL DEFAULT 0 COMMENT '结束件数',
  263 + `fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '费用',
  264 + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
  265 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  266 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  267 + PRIMARY KEY (`id`),
  268 + KEY `idx_plan_order` (`plan_id`, `list_order`)
  269 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价件数区间表';
  270 +
  271 +-- 时段附加费表
  272 +CREATE TABLE `delivery_fee_plan_time_rule` (
  273 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  274 + `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID',
  275 + `start_minute` INT NOT NULL DEFAULT 0 COMMENT '开始分钟',
  276 + `end_minute` INT NOT NULL DEFAULT 0 COMMENT '结束分钟',
  277 + `fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '附加费',
  278 + `enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用',
  279 + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
  280 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  281 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  282 + PRIMARY KEY (`id`),
  283 + KEY `idx_plan_order` (`plan_id`, `list_order`)
  284 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价时段附加费表';
  285 +
  286 +-- 分站管理员表(每城市一个,管理本城市骑手和订单)
  287 +CREATE TABLE `substation` (
  288 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分站ID',
  289 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '管理城市ID',
  290 + `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录账号',
  291 + `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
  292 + `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)',
  293 + `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号',
  294 + `avatar` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像',
  295 + `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
  296 + `role_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '菜单角色ID',
  297 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
  298 + PRIMARY KEY (`id`),
  299 + UNIQUE KEY `uk_user_login` (`user_login`)
  300 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分站管理员表(一个城市/租户下可有多个管理员)';
  301 +
  302 +-- 商家入驻申请表
  303 +CREATE TABLE `merchant_enter` (
  304 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  305 + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '联系人姓名',
  306 + `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号',
  307 + `store_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '店铺名称',
  308 + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=商家入驻 2=骑手入驻 3=商务合作',
  309 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID',
  310 + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
  311 + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=未处理 1=已通过 -1=已拒绝',
  312 + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请时间',
  313 + PRIMARY KEY (`id`),
  314 + KEY `idx_status_type` (`status`, `type`),
  315 + KEY `idx_city_id` (`city_id`)
  316 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家入驻申请表';
  317 +
  318 +-- 商家账号表
  319 +CREATE TABLE `merchant_users` (
  320 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  321 + `store_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺ID',
  322 + `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号(登录账号)',
  323 + `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
  324 + `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
  325 + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=商家',
  326 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
  327 + PRIMARY KEY (`id`),
  328 + UNIQUE KEY `uk_mobile` (`mobile`),
  329 + KEY `idx_store_id` (`store_id`)
  330 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家账号表';
  331 +
  332 +-- 商家店铺表
  333 +CREATE TABLE `merchant_store` (
  334 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '店铺ID',
  335 + `name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '店铺名称',
  336 + `thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封面图',
  337 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属城市ID',
  338 + `address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '店铺地址',
  339 + `lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '经度',
  340 + `lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '纬度',
  341 + `operating_state` TINYINT NOT NULL DEFAULT 1 COMMENT '营业状态:0=打烊 1=营业',
  342 + `automatic_order` TINYINT NOT NULL DEFAULT 0 COMMENT '自动接单:0=否 1=是',
  343 + `shipping_type` TINYINT NOT NULL DEFAULT 1 COMMENT '配送类型:1=外卖配送 2=到店自提',
  344 + `free_shipping` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '免运费门槛,0=不免',
  345 + `up_to_send` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起送金额,0=不限',
  346 + `open_date` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '营业日期JSON,如[1,2,3,4,5]',
  347 + `open_time` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '营业时间JSON,如["09:00","22:00"]',
  348 + `about` TEXT COMMENT '店铺简介',
  349 + `account_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联账号ID',
  350 + `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '接入方AppKey,为空=平台自建',
  351 + `out_store_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '接入方门店编号,用于推单时自动匹配',
  352 + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
  353 + `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除',
  354 + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
  355 + `sync_app_key` VARCHAR(32) GENERATED ALWAYS AS (NULLIF(`app_key`, '')) STORED COMMENT '外部同步唯一键AppKey',
  356 + `sync_out_store_id` VARCHAR(64) GENERATED ALWAYS AS (NULLIF(`out_store_id`, '')) STORED COMMENT '外部同步唯一键门店ID',
  357 + PRIMARY KEY (`id`),
  358 + KEY `idx_city_id` (`city_id`),
  359 + UNIQUE KEY `uk_app_out_store` (`sync_app_key`, `sync_out_store_id`, `is_del`),
  360 + KEY `idx_order_del` (`list_order`, `is_del`)
  361 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家店铺表';
  362 +
  363 +-- 开放平台应用表
  364 +CREATE TABLE `open_app` (
  365 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  366 + `app_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '应用名称',
  367 + `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'AppKey',
  368 + `app_secret` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'AppSecret',
  369 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联城市/租户ID(必填,租户隔离核心字段)',
  370 + `store_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺ID,0=不限制',
  371 + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
  372 + `webhook_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Webhook回调地址',
  373 + `webhook_events` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '订阅事件JSON数组',
  374 + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
  375 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  376 + PRIMARY KEY (`id`),
  377 + UNIQUE KEY `uk_app_key` (`app_key`),
  378 + KEY `idx_city_id` (`city_id`)
  379 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放平台应用表';
  380 +
  381 +-- Webhook 推送日志表
  382 +CREATE TABLE `webhook_log` (
  383 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  384 + `app_id` BIGINT UNSIGNED NOT NULL COMMENT '应用ID',
  385 + `event` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '事件类型',
  386 + `biz_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '业务ID',
  387 + `url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '推送URL',
  388 + `payload` TEXT COMMENT '推送内容JSON',
  389 + `response_code` INT NOT NULL DEFAULT 0 COMMENT 'HTTP响应码',
  390 + `response_body` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '响应内容',
  391 + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=失败 1=成功',
  392 + `retry_count` INT NOT NULL DEFAULT 0 COMMENT '重试次数',
  393 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  394 + PRIMARY KEY (`id`),
  395 + KEY `idx_app_event` (`app_id`, `event`),
  396 + KEY `idx_biz_id` (`biz_id`),
  397 + KEY `idx_status` (`status`)
  398 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Webhook推送日志表';
  399 +
  400 +-- 超级管理员表
  401 +CREATE TABLE `admin_user` (
  402 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  403 + `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录账号',
  404 + `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)',
  405 + `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称',
  406 + `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
  407 + `role_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '菜单角色ID',
  408 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  409 + PRIMARY KEY (`id`),
  410 + UNIQUE KEY `uk_user_login` (`user_login`)
  411 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='超级管理员表';
  412 +
  413 +CREATE TABLE `sys_role` (
  414 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  415 + `code` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色编码',
  416 + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '角色名称',
  417 + `role_scope` VARCHAR(32) NOT NULL DEFAULT 'PLATFORM' COMMENT '角色归属:PLATFORM/SUBSTATION',
  418 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属租户ID:0=平台全局角色,>0=分站租户专属角色',
  419 + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
  420 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  421 + PRIMARY KEY (`id`),
  422 + UNIQUE KEY `uk_role_code` (`code`, `city_id`),
  423 + KEY `idx_city_scope` (`city_id`, `role_scope`)
  424 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台菜单角色表';
  425 +
  426 +CREATE TABLE `sys_menu` (
  427 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  428 + `code` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '菜单编码',
  429 + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '菜单名称',
  430 + `type` VARCHAR(16) NOT NULL DEFAULT 'MENU' COMMENT '类型:DIR/MENU',
  431 + `path` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '前端路由路径',
  432 + `icon` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '前端图标名',
  433 + `parent_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父级菜单ID',
  434 + `menu_scope` VARCHAR(32) NOT NULL DEFAULT 'BOTH' COMMENT '菜单归属:PLATFORM/SUBSTATION/BOTH',
  435 + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序',
  436 + `visible` TINYINT NOT NULL DEFAULT 1 COMMENT '是否显示:0=否 1=是',
  437 + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常',
  438 + KEY `idx_scope_parent` (`menu_scope`, `parent_id`, `list_order`),
  439 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  440 + PRIMARY KEY (`id`),
  441 + UNIQUE KEY `uk_menu_code` (`code`),
  442 + KEY `idx_parent_order` (`parent_id`, `list_order`)
  443 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台菜单表';
  444 +
  445 +CREATE TABLE `sys_role_menu` (
  446 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  447 + `role_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '角色ID',
  448 + `menu_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '菜单ID',
  449 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  450 + PRIMARY KEY (`id`),
  451 + UNIQUE KEY `uk_role_menu` (`role_id`, `menu_id`)
  452 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台角色菜单关系表';
  453 +
  454 +-- orders 表补充字段(如已有 orders 表,执行以下 ALTER)
  455 +ALTER TABLE `orders` ADD COLUMN `out_order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '外部系统订单号' AFTER `order_no`;
  456 +ALTER TABLE `orders` ADD COLUMN `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '接入方AppKey' AFTER `out_order_no`;
  457 +ALTER TABLE `orders` ADD COLUMN `callback_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '状态回调地址' AFTER `app_key`;
  458 +ALTER TABLE `orders` ADD INDEX `idx_app_out_order` (`app_key`, `out_order_no`);
  459 +
  460 +-- 骑手评价表
  461 +CREATE TABLE `rider_evaluate` (
  462 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  463 + `uid` BIGINT UNSIGNED NOT NULL COMMENT '评价用户ID',
  464 + `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
  465 + `rid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
  466 + `content` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '评价内容',
  467 + `star` TINYINT NOT NULL DEFAULT 5 COMMENT '星级1-5',
  468 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  469 + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  470 + PRIMARY KEY (`id`),
  471 + UNIQUE KEY `uk_uid_oid` (`uid`, `oid`),
  472 + KEY `idx_rid` (`rid`)
  473 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手评价表';
  474 +
  475 +-- 退款原因配置表
  476 +CREATE TABLE `orders_refund_reason` (
  477 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  478 + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '原因描述',
  479 + `role` TINYINT NOT NULL DEFAULT 1 COMMENT '1=用户 2=骑手',
  480 + `list_order` INT NOT NULL DEFAULT 0,
  481 + PRIMARY KEY (`id`)
  482 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款原因配置表';
  483 +
  484 +-- 退款申请记录表
  485 +CREATE TABLE `orders_refund_record` (
  486 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  487 + `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
  488 + `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号',
  489 + `uid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请人ID',
  490 + `role` TINYINT NOT NULL DEFAULT 1 COMMENT '1=用户 2=骑手',
  491 + `reason_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  492 + `reason` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '退款原因',
  493 + `money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '退款金额',
  494 + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=待处理 1=通过 2=拒绝',
  495 + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '处理备注',
  496 + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  497 + `handle_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  498 + PRIMARY KEY (`id`),
  499 + KEY `idx_oid` (`oid`),
  500 + KEY `idx_status` (`status`)
  501 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款申请记录表';
  502 +
  503 +-- 退款原因初始数据
  504 +INSERT INTO `orders_refund_reason` (`name`, `role`, `list_order`) VALUES
  505 +('骑手长时间未接单', 1, 1),
  506 +('骑手态度恶劣', 1, 2),
  507 +('物品损坏', 1, 3),
  508 +('其他原因', 1, 99),
  509 +('用户恶意单', 2, 1),
  510 +('无法完成配送', 2, 2),
  511 +('其他原因', 2, 99);
  512 +
  513 +-- orders 表补充货物快照字段
  514 +ALTER TABLE `orders` ADD COLUMN `items_json` TEXT COMMENT '货物清单快照JSON' AFTER `callback_url`;
  515 +ALTER TABLE `orders` ADD COLUMN `item_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '整单货物备注' AFTER `items_json`;
  516 +
  517 +-- orders 表补充调度字段
  518 +ALTER TABLE `orders` ADD COLUMN `dispatch_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '系统派单时间' AFTER `trans_time`;
  519 +ALTER TABLE `orders` ADD COLUMN `dispatch_rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '系统指派骑手ID' AFTER `dispatch_time`;
  520 +
  521 +-- rider 表补充个人持单上限与评分统计字段
  522 +ALTER TABLE `rider` ADD COLUMN `hold_order_limit` INT NOT NULL DEFAULT 0 COMMENT '个人持单上限,0=不限制' AFTER `is_rest`;
  523 +ALTER TABLE `rider` ADD COLUMN `star_total` INT NOT NULL DEFAULT 0 COMMENT '评分总分' AFTER `thumb`;
  524 +ALTER TABLE `rider` ADD COLUMN `star_count` INT NOT NULL DEFAULT 0 COMMENT '评分次数' AFTER `star_total`;
  525 +
  526 +-- 调度规则模板表
  527 +CREATE TABLE `dispatch_rule_template` (
  528 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  529 + `city_id` BIGINT UNSIGNED NOT NULL COMMENT '城市ID',
  530 + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '模板名称',
  531 + `is_active` TINYINT NOT NULL DEFAULT 0 COMMENT '是否当前生效:0=否 1=是',
  532 + `grab_enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '抢单模式启用',
  533 + `grab_timeout` INT NOT NULL DEFAULT 30 COMMENT '抢单超时分钟数,超时后转自动派单',
  534 + `grab_scope` TINYINT NOT NULL DEFAULT 1 COMMENT '抢单可见范围:1=订单所属区域骑手 2=全部自营骑手',
  535 + `grab_max_per_rider` INT NOT NULL DEFAULT 3 COMMENT '单人最大同时持单量',
  536 + `auto_dispatch` TINYINT NOT NULL DEFAULT 1 COMMENT '同步开启自动派单:0=否 1=是',
  537 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  538 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  539 + PRIMARY KEY (`id`),
  540 + KEY `idx_city_active` (`city_id`, `is_active`)
  541 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='调度规则模板表';
  542 +
  543 +-- 派单优先级条件表
  544 +CREATE TABLE `dispatch_rule_condition` (
  545 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  546 + `template_id` BIGINT UNSIGNED NOT NULL COMMENT '所属模板ID',
  547 + `condition_type` VARCHAR(32) NOT NULL COMMENT '条件类型',
  548 + `enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用',
  549 + `threshold_value` DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '阈值',
  550 + `sort_order` INT NOT NULL DEFAULT 0 COMMENT '优先级排序(小的优先)',
  551 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  552 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  553 + PRIMARY KEY (`id`),
  554 + UNIQUE KEY `uk_template_type` (`template_id`, `condition_type`),
  555 + KEY `idx_template_order` (`template_id`, `sort_order`)
  556 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='派单优先级条件表';
  557 +
  558 +-- 骑手消息表
  559 +CREATE TABLE `rider_message` (
  560 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '消息ID',
  561 + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID(多租户隔离)',
  562 + `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID,0表示全员消息',
  563 + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '消息类型:1=订单消息 2=系统通知',
  564 + `title` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '消息标题',
  565 + `content` TEXT NOT NULL COMMENT '消息内容',
  566 + `biz_type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '业务类型:order_assigned/order_timeout/system_notice',
  567 + `biz_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联业务ID(如订单ID)',
  568 + `extra_data` TEXT COMMENT '扩展数据(JSON格式)',
  569 + `is_read` TINYINT NOT NULL DEFAULT 0 COMMENT '已读状态:0=未读 1=已读',
  570 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
  571 + `read_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '阅读时间',
  572 + PRIMARY KEY (`id`),
  573 + KEY `idx_rider_type` (`rider_id`, `type`, `create_time`),
  574 + KEY `idx_city_create` (`city_id`, `create_time`),
  575 + KEY `idx_biz` (`biz_type`, `biz_id`)
  576 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手消息表';
  577 +
  578 +-- 消息模板表
  579 +CREATE TABLE `rider_message_template` (
  580 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  581 + `biz_type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '业务类型标识',
  582 + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '消息类型:1=订单 2=系统',
  583 + `title_template` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '标题模板,支持 #{key} 占位符',
  584 + `content_template` TEXT NOT NULL COMMENT '内容模板,支持 #{key} 占位符',
  585 + `is_push` TINYINT NOT NULL DEFAULT 1 COMMENT '是否实时推送:0=否 1=是',
  586 + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用',
  587 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  588 + PRIMARY KEY (`id`),
  589 + UNIQUE KEY `uk_biz_type` (`biz_type`)
  590 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息模板表';
  591 +
  592 +-- 初始化模板数据
  593 +INSERT INTO `rider_message_template` (`biz_type`, `type`, `title_template`, `content_template`, `is_push`) VALUES
  594 +('order_assigned', 1, '新订单', '您有新的配送订单 #{orderNo},请及时处理', 1),
  595 +('order_timeout', 1, '订单超时提醒', '订单 #{orderNo} 即将超时,请尽快送达', 1),
  596 +('order_cancelled', 1, '订单取消', '订单 #{orderNo} 已被取消', 1),
  597 +('system_notice', 2, '系统通知', '#{content}', 1),
  598 +('level_upgrade', 2, '等级提升', '恭喜您,等级已提升至 #{levelName}', 1),
  599 +('balance_change', 2, '余额变动', '您的余额发生变动,当前余额:#{balance} 元', 0);
  600 +
  601 +-- 骑手设备推送绑定表(极光推送)
  602 +CREATE TABLE `rider_device` (
  603 + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  604 + `rider_id` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID',
  605 + `registration_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'JPush 设备注册ID',
  606 + `platform` TINYINT NOT NULL DEFAULT 0 COMMENT '平台:1=Android 2=iOS',
  607 + `device_info` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '设备信息(型号/系统版本)',
  608 + `app_version` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'App 版本',
  609 + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=已解绑 1=正常',
  610 + `last_active_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '最近活跃时间',
  611 + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  612 + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  613 + PRIMARY KEY (`id`),
  614 + UNIQUE KEY `uk_registration_id` (`registration_id`),
  615 + KEY `idx_rider_status` (`rider_id`, `status`)
  616 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手设备推送绑定表';
... ...