一个校园里的包裹驿站,一天少说两三百个件,高峰期直接翻倍。过去靠站长喊名字、学生在货架里来回翻,效率低还容易拿错。我最近把一套 springboot 后端 + 微信小程序终端的智能包裹配送服务管理系统从需求到落地完整做了一遍,这里把设计思路、表结构、接口实现和踩过的坑全部整理出来。
这套系统的核心不是"造一个App",而是用小程序这个轻量载体解决末端包裹"最后一公里"的配送问题。用户在小程序里下单、查轨迹、收件评价;配送员在小程序里接单、更新位置、拍照签收;管理员在后端做订单调度和数据统计。整个链路覆盖了从用户发起到包裹签收的完整配送闭环,非常适合做校园快递代取、社区末端驿站、同城小件配送这类场景。
如果你正在做类似的毕业设计、个人项目,或者在微信小程序 + SpringBoot 这条技术路线上刚起步,这篇文章可以直接当作技术方案参考。我会把每一个关键设计背后的"为什么"讲清楚,不绕弯子,全部是可落地的干货。
1. 项目概述与整体设计思路
1.1 这个系统到底在解决什么问题
先想清楚业务痛点,后面写代码才有方向。末端包裹配送场景里,最疼的三个问题分别是:
- 包裹到了驿站但用户不方便取,需要别人代取代送;
- 配送员接到多个订单后,路线靠脑子记,送达状态靠微信聊天汇报,信息碎片化严重;
- 用户想知道自己的包裹到哪了,只能打电话问,体验很差。
这套智能包裹配送服务管理系统,本质上就是把"下单、派单、取件、送件、签收、评价"这些原本靠人肉沟通的流程,搬到了线上,并且用状态机和规则引擎把流程钉死。
我在设计时把"智能"分解成了三个可落地的点:
- 订单自动分配。新订单产生后,系统根据配送员当前任务量和所在片区,自动推荐接单人,不需要管理员手动派单。
- 状态机自动流转。订单从"待接单"到"已完成"的每一次变化,都有合法性校验,不懂业务的人乱调接口也改不了状态。
- 超时预警与统计看板。通过定时任务扫描超时未揽收、超时未送达的订单,推送提醒,同时在后台统计配送员完成率和时间段订单量。
注意,这里的"智能"不是算法层面的AI,而是业务规则 + 状态机 + 定时任务的组合。大多数实际项目需要的也就是这种"够用且不飘"的智能,别一上来就上推荐算法和路径规划模型,那是给自己挖坑。
1.2 为什么选SpringBoot + 微信小程序这套组合
这个选型我基本没犹豫。
后端用 SpringBoot,是因为它生态太成熟了。你要数据库操作有 MyBatis-Plus,要定时任务有 Quartz,要接口文档有 Swagger 那一套,要鉴权有 JWT,几乎每个环节都有现成方案,自己只需要专注业务逻辑。而且 SpringBoot 内置 Tomcat,打包成 jar 就能跑,部署门槛很低,配合 Docker 一条命令就能起环境。
前端小程序端的优势更直接。末端配送场景里,用户和配送员都不太可能为了代取个包裹专门下载一个 App,小程序扫码即用、用完即走,非常契合这类低频但刚需的应用场景。对开发者也友好,微信开发者工具里调试起来很方便,发布审核也有现成流程。
整体架构是前后端分离:微信小程序作为用户端和配送员端,SpringBoot 提供 RESTful API,MySQL 存业务数据,Redis 做缓存(比如热门地址、验证码、配送员在线状态),图片上传到 OSS,地图相关能力调用腾讯地图的 WebService API。之所以选择前后端分离,是因为用户端和配送员端虽然是两个角色,但都跑在小程序里,通过登录态区分角色即可,后端只需要维护一套 API 就能同时服务两端,省掉一套管理后台前端的工作量。
1.3 角色模型与核心业务闭环
这套系统里一共有三类角色:
- 用户:在小程序里下单寄包裹、查轨迹、确认签收、评价配送服务。
- 配送员:在小程序里接单、更新包裹位置、上传签收凭证。
- 管理员:在系统后台管理用户、审核配送员、查看数据统计。
一条完整的业务主流程是这样的:
用户打开小程序下单,填写取件地址、送达地址、包裹类型,系统生成订单后进入"待接单"状态;配送端刷新任务列表看到新订单,接单后状态变为"配送中";配送员到取件点揽收,系统记录揽收时间;配送途中配送员可手动上报轨迹点,用户在小程序端看到包裹移动路线;最后配送员拍照签收,订单变成"已完成",用户可以对这次配送打星评价。
这个闭环把信息流和实物流对齐了,每一步动作都会产生一条状态记录和一条轨迹记录,后续查历史、做统计都有据可依。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计与业务规则落地
2.1 五张核心表的结构设计
数据库设计是整个项目的地基,表字段没想清楚,后面写接口会到处别扭。我最终落地的核心表包括:用户表、包裹订单表、配送任务表、轨迹记录表、地址簿表。这里重点说包裹订单表和配送任务表。
包裹订单表(package_order)是主表,保存订单的业务信息:
sql复制CREATE TABLE `package_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`user_id` bigint(20) NOT NULL COMMENT '下单用户ID',
`sender_name` varchar(50) NOT NULL COMMENT '取件联系人',
`sender_phone` varchar(20) NOT NULL COMMENT '取件电话',
`sender_address` varchar(255) NOT NULL COMMENT '取件地址',
`sender_lng` decimal(10,6) DEFAULT NULL COMMENT '取件经度',
`sender_lat` decimal(10,6) DEFAULT NULL COMMENT '取件纬度',
`receiver_name` varchar(50) NOT NULL COMMENT '收件联系人',
`receiver_phone` varchar(20) NOT NULL COMMENT '收件电话',
`receiver_address` varchar(255) NOT NULL COMMENT '送达地址',
`receiver_lng` decimal(10,6) DEFAULT NULL COMMENT '送达经度',
`receiver_lat` decimal(10,6) DEFAULT NULL COMMENT '送达纬度',
`goods_name` varchar(100) DEFAULT NULL COMMENT '包裹描述',
`goods_type` tinyint(4) DEFAULT 0 COMMENT '包裹类型:0-普通 1-文件 2-生鲜 3-贵重',
`weight` decimal(5,2) DEFAULT 0.00 COMMENT '重量(kg)',
`status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '订单状态',
`amount` decimal(10,2) DEFAULT 0.00 COMMENT '配送费',
`expect_time` datetime DEFAULT NULL COMMENT '期望送达时间',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这里两个关键点:一是经纬度字段必须用 decimal(10,6),精度够用,别用 float,定位会偏;二是 user_id 和 status 一定要建索引,因为业务查询几乎都走这两个条件,不建索引数据量一大就卡。
用户表、配送任务表、轨迹表、地址簿表的核心字段可以这样设计:
| 表名 | 关键字段 | 设计说明 |
|---|---|---|
| user | id, openid, nickname, avatar, phone, role | openid 必须唯一索引,role 区分用户/配送员/管理员 |
| delivery_task | id, order_id, courier_id, status, accept_time, pickup_time, sign_time, sign_photo | 一个订单一条任务记录,保存每个关键动作的时间点 |
| delivery_track | id, order_id, lng, lat, location_desc, track_time | 每次位置上报追加一条记录,前端按时间排序连线 |
| address_book | id, user_id, name, phone, address, is_default | 用户常用地址簿,下单时可以快速选择 |
配送任务表单独拆出来,而不是在订单表上加一堆字段,是因为一个订单理论上可以经历"接单 -> 拒单 -> 转单 -> 再接单"的流程,如果所有信息都堆在订单表里,字段会越来越多,查询也越来越慢。拆成独立表后,每次接单就是一条新任务记录,订单表和任务表通过 order_id 关联,既清晰又便于追溯。
2.2 订单状态机:把流程钉死的关键
这套系统里最容易出 bug 的地方就是订单状态。如果前端把状态直接传给后端更新,那用户手一抖就能把一个"配送中"的订单改成"已完成",整个流程就乱了。
我采用的做法是在后端 service 层维护一个状态机,每次状态变更前先做一次合法性校验。订单状态我定义为 7 个:
| 状态码 | 含义 | 触发动作 |
|---|---|---|
| 0 | 待接单 | 用户提交订单 |
| 1 | 已接单 | 配送员接单 |
| 2 | 已揽收 | 配送员到达取件点确认取件 |
| 3 | 配送中 | 配送员开始配送并上报位置 |
| 4 | 已签收 | 配送员上传签收凭证 |
| 5 | 已完成 | 用户确认或系统自动确认 |
| 6 | 已取消/异常 | 用户取消或系统标记异常 |
状态流转规则必须在后端校验,不能相信前端传进来的值。我写了一个简单的状态流转校验方法:
java复制private static final Map<OrderStatus, Set<OrderStatus>> TRANSITION_MAP = new HashMap<>();
static {
TRANSITION_MAP.put(OrderStatus.PENDING, Set.of(OrderStatus.ACCEPTED, OrderStatus.CANCELED));
TRANSITION_MAP.put(OrderStatus.ACCEPTED, Set.of(OrderStatus.PICKED_UP, OrderStatus.CANCELED));
TRANSITION_MAP.put(OrderStatus.PICKED_UP, Set.of(OrderStatus.DELIVERING));
TRANSITION_MAP.put(OrderStatus.DELIVERING, Set.of(OrderStatus.SIGNED));
TRANSITION_MAP.put(OrderStatus.SIGNED, Set.of(OrderStatus.COMPLETED));
TRANSITION_MAP.put(OrderStatus.COMPLETED, Collections.emptySet());
TRANSITION_MAP.put(OrderStatus.CANCELED, Collections.emptySet());
}
public void verifyTransition(OrderStatus current, OrderStatus target) {
Set<OrderStatus> allowed = TRANSITION_MAP.get(current);
if (!allowed.contains(target)) {
throw new BusinessException("非法订单状态流转: " + current + " -> " + target);
}
}
每次更新订单状态前,先调 verifyTransition,不合法直接抛异常。这样即使前端有 bug,或者有人直接调接口乱传参数,状态也乱不了。这个设计我强烈建议保留,哪怕你觉得代码啰嗦。它能让后面所有业务逻辑省掉大量防御性判断。
2.3 自动分配与超时预警的策略落地
"智能配送"的第一个落点就是自动分配。我用的策略并不复杂:新订单产生后,系统先根据取件地址的经纬度找到所属片区的配送员(配送员注册时会设置自己负责的片区范围),然后按"当前任务数最少优先"的原则,选出一个候选配送员,把订单推送给他。说白了,就是"谁能干、谁最闲、谁先上"。
这里要注意一个细节:自动分配不是强制的,只是"推荐"。真实场景里配送员可能正在休假,所以我的设计是生成分配建议后发一条订阅消息给配送员,配送员在小程序里看到后手动接单。如果 5 分钟内没人接,订单回到公共池,所有配送员都能看到并抢单。这样处理既照顾了公平性,又避免了无人接单的尴尬。
超时预警用 Quartz 定时任务实现。我配置了两个 cron 任务:
- 每隔 5 分钟扫描一次"待接单超过 30 分钟"的订单,标记为"即将超时",推送提醒;
- 每隔 10 分钟扫描一次"已接单但超过 2 小时未揽收"的订单,给配送员推送超时提醒,同时抄送管理员。
这套定时任务代码不复杂,但价值很高。它让管理员不需要时刻盯着后台,系统会自动把异常单推到人面前。我做项目的时候发现,很多同学把精力花在花哨的界面上,忽略了这种"自动兜底"能力,其实这类功能才是业务方真正认可的"智能"。
3. 后端工程搭建与核心接口实现
3.1 工程结构:SpringBoot项目怎么分层不打架
后端工程我按标准的分层架构来组织,包结构如下:
text复制com.example.delivery
├── controller
│ ├── OrderController.java
│ ├── TaskController.java
│ └── UserController.java
├── service
│ ├── OrderService.java
│ ├── TaskService.java
│ └── UserService.java
├── mapper
│ ├── OrderMapper.java
│ └── UserMapper.java
├── entity
│ ├── Order.java
│ └── User.java
├── common
│ ├── Result.java
│ ├── BusinessException.java
│ └── GlobalExceptionHandler.java
└── config
├── JwtInterceptor.java
├── WebConfig.java
└── QuartzConfig.java
controller 层只做参数接收和结果返回,不写业务;service 层写业务逻辑;mapper 层只做数据库交互。这个分层没有技术含量,但能救命。我见过不少项目把所有内容堆在 controller 里,一个方法三五百行,后面加个需求得翻半天代码,那是给自己找罪受。
在 pom.xml 里,核心依赖就这几样:spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-j、lombok、jjwt、spring-boot-starter-data-redis、spring-boot-starter-quartz、hutool-all。hutool 是个工具库,生成订单号、日期处理、HttpUtil 调第三方接口都用得到,能省不少代码。
这里要特别说一下版本选择。我用的 Spring Boot 是 2.7.18,不是最新的 3.x。原因很简单:3.x 把 javax 包迁移成了 jakarta,很多老依赖不兼容,处理这些纯属浪费时间。后面我会专门讲版本坑。对于新手来说,Spring Boot 2.7.x + MyBatis-Plus 3.5.x + JDK 8 这个组合最稳,网上资料也最多,遇到问题一搜就有答案。
3.2 登录鉴权:JWT + 微信小程序登录的全流程
微信小程序不像 Web 端有 cookie 机制,登录态需要用 token 自己维护。标准做法是走微信官方的 code 换 session 流程:
- 小程序端调用 wx.login() 拿到临时 code;
- 小程序把 code 发给后端;
- 后端用 code 调微信接口换 openid 和 session_key;
- 后端拿着 openid 查用户表,查不到就自动注册;
- 后端签发 JWT 返回给小程序,后续所有请求在 header 里带 token。
登录核心代码大概是这样的:
java复制public LoginVO wxLogin(String code) {
// 1. 调微信接口换取 openid
String url = "https://api.weixin.qq.com/sns/jscode2session"
+ "?appid=" + appId
+ "&secret=" + appSecret
+ "&js_code=" + code
+ "&grant_type=authorization_code";
String result = HttpUtil.get(url);
JSONObject json = JSONUtil.parseObj(result);
String openid = json.getStr("openid");
if (StrUtil.isBlank(openid)) {
throw new BusinessException("微信登录失败: " + result);
}
// 2. 查用户,不存在则注册
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
.eq(User::getOpenid, openid));
if (user == null) {
user = new User();
user.setOpenid(openid);
user.setNickname("微信用户" + openid.substring(openid.length() - 6));
user.setRole(RoleEnum.USER.getCode());
userMapper.insert(user);
}
// 3. 签发 JWT,过期时间 7 天
String token = JwtUtil.createToken(user.getId(), user.getRole());
return new LoginVO(token, user);
}
拿到 token 之后,后端通过拦截器统一鉴权。我在 WebConfig 里注册拦截器,放行登录接口、订单查询接口、微信支付回调等,其他接口一律校验 token。
这里有一个很容易踩的坑:如果你在项目里集成了 Swagger,拦截器会把 Swagger 页面也拦了,导致接口文档打不开。解决方法是把 swagger 相关的路径加到白名单里。用 springdoc 的话是放行这些路径:
java复制registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/user/login",
"/api/order/query",
"/swagger-ui/**",
"/v3/api-docs/**"
);
这个"springboot jwt 放开 swagger"的问题,我在多个项目里都遇到过,属于高频坑,提前配好能省自己不少事。
3.3 订单下单、接单、签收接口的核心逻辑
接口设计上,我遵循一个原则:接口尽量按照"业务动词"来命名,一个接口只干一件事。核心接口清单如下:
| 接口路径 | 方法 | 说明 | 鉴权 |
|---|---|---|---|
| /api/user/login | POST | 微信登录 | 放行 |
| /api/order/create | POST | 用户下单 | 需登录 |
| /api/order/list | GET | 查询我的订单 | 需登录 |
| /api/order/track | GET | 查询订单轨迹 | 放行 |
| /api/task/list | GET | 配送员任务列表 | 需登录 |
| /api/task/accept | POST | 配送员接单 | 需登录 |
| /api/task/status | POST | 更新任务状态 | 需登录 |
| /api/task/report-location | POST | 上报实时位置 | 需登录 |
| /api/task/sign | POST | 拍照签收 | 需登录 |
用户下单接口是这个系统的入口,逻辑上要处理四件事:参数校验、运费计算、订单号生成、位置信息补全。运费计算我用的规则是"基础配送费 5 元 + 重量超出 1kg 每公斤加 2 元",距离因素暂时以基础费覆盖,后续可以接地图接口按公里数计价。订单号用 hutool 的 IdUtil.getSnowflakeNextIdStr() 生成,保证并发下不重复。
接单接口有个容易被忽略的校验点:配送员同时处理的任务数不能超过上限。我设置为同时最多 5 单,超过就提示"任务已满,请先完成部分订单"。这个限制看起来简单,但它防止了配送员贪多导致大量超时订单,是对整个系统的保护。
签收接口是用户最关心的环节,逻辑上要做的事比较多:更新订单状态为已签收、上传签收照片到 OSS、写入签收时间、追加一条轨迹记录、发送订阅消息通知用户。这里照片上传我用的是小程序端先传 OSS 拿回 URL,再在签收接口里传 URL,而不是把图片 base64 发给后端。这样后端不用处理大报文,接口响应速度也快。
java复制public void signOrder(Long orderId, String photoUrl) {
DeliveryTask task = taskService.getByOrderId(orderId);
if (task == null) {
throw new BusinessException("配送任务不存在");
}
// 校验状态:只有配送中才能签收
orderService.verifyTransition(OrderStatus.DELIVERING, OrderStatus.SIGNED);
task.setStatus(TaskStatus.SIGNED.getCode());
task.setSignTime(new Date());
task.setSignPhoto(photoUrl);
taskService.updateById(task);
orderService.updateStatus(orderId, OrderStatus.SIGNED);
// 追加轨迹记录
trackService.addTrack(orderId, null, null, "包裹已由配送员签收", new Date());
// 发送微信订阅消息通知用户
wxMessageService.sendSignNotify(orderId);
}
核心思路就是:先改任务表,再改订单表,最后追加轨迹和发通知。每一步都在一个事务里,要么全成功要么全失败,避免出现订单状态变了但任务表没更新的脏数据。用 Spring 的 @Transactional 注解搞定。
4. 微信小程序端关键功能实现
4.1 登录与用户身份绑定:别再用老的getUserInfo了
小程序端的登录流程,是承接后端登录接口的第一步。我用了 wx.login + 后端换取 token 的标准流程。不过这里有个坑要提醒:微信官方早就更新了用户信息授权策略,wx.getUserInfo 拿到的头像和昵称已经是"灰色头像 + 微信用户"的默认数据了,直接用会导致页面上全是默认头像,用户还以为自己没登录成功。
现在的做法是让用户主动填。小程序提供了头像昵称填写能力:用 button 组件的 open-type="chooseAvatar" 让用户选头像,用 input 组件的 type="nickname" 让用户填昵称。流程变长了一点,但这是官方要求,不合规的话审核都过不了。
手机号获取也要注意新规。现在不能直接用 getPhoneNumber 返回明文手机号了,返回的是一个动态令牌 code,需要后端拿着这个 code 调用接口解密换取真实手机号。我在后端写了一个专门处理手机号解密的接口,拿到手机号后存到用户表,作为配送员联系用户的主要方式。
登录这块踩过的最典型的坑是:真机调试时 request 请求发不出去,报"url not in domain list"。这是因为小程序要求所有请求域名必须配到微信公众平台后台的"request 合法域名"里。开发环境下可以在微信开发者工具右上角"详情 -> 本地设置"勾选"不校验合法域名",但上线前一定要把域名配好,否则正式版小程序所有接口全废。
4.2 下单页与地图选点:地理位置是核心业务数据
包裹配送业务绕不开地理位置。用户下单时必须选两个点:取件地址和送达地址。我在小程序端用 map 组件 + 腾讯地图的 POI 搜索来实现选点。
具体交互是:用户打开选点页面,map 组件展示当前位置,下方搜索框可以输入门牌号或小区名,调腾讯地图的"关键词输入提示"接口拿到候选地址列表,用户点选后地图 marker 定位到对应坐标,再把经纬度和结构化地址一起带回下单页。这样后端就能拿到 sender_lng、sender_lat、receiver_lng、receiver_lat 四个关键字段,后续做片区分配和轨迹展示都靠它们。
有个细节值得注意:下单页里我放了一个"配送方式"的单选框,选项是"立即配送"和"预约配送"。这个单选框用小程序原生 radio-group 实现就行,选中预约定时,弹出 picker 选择期望送达时间然后把值传给后端。功能不大,但业务上很有必要,因为很多人是晚上下单第二天才需要送。
地址簿功能也可以在这个环节一起做。用户选完地址后可以勾选"保存到常用地址",下次下单直接点地址簿里的记录,不用重复输入。这个功能对提升复购率很有帮助,实现也简单,就是 address_book 表的增删改查。
4.3 轨迹追踪:map组件 + polyline动态连线
用户最关心的就是"包裹到哪了"。轨迹展示这块,我用了 map 组件的 polyline 属性,把配送员上报的位置按时间顺序连成一条线。
后端轨迹查询接口返回一个坐标点数组,数据结构大概是:
json复制[
{ "lng": 116.397, "lat": 39.908, "time": "2025-01-10 10:00:00" },
{ "lng": 116.402, "lat": 39.910, "time": "2025-01-10 10:05:00" }
]
小程序端拿到数据后设置到 map 组件的 polyline 上,用 marker 标记起点和终点,用户一眼就能看出配送员的移动路线。地图组件核心代码:
javascript复制this.setData({
latitude: trackList[trackList.length - 1].lat,
longitude: trackList[trackList.length - 1].lng,
polyline: [{
points: trackList.map(item => ({ latitude: item.lat, longitude: item.lng })),
color: "#1989fa",
width: 4,
arrowLine: true
}],
markers: markers
});
轨迹数据怎么更新到用户端?我评估过两种方案:WebSocket 实时推送和定时轮询。WebSocket 看起来更"实时",但小程序端的 WebSocket 连接在切后台时会断开,重连逻辑写起来比较麻烦;最终我用户端采用了轮询方案,每 15 秒拉一次最新轨迹。配送员端更新位置频率很低(通常一个订单就上报几回),15 秒的延迟用户完全能接受。除非你要做"实时看到配送员移动"这种效果,否则轮询就是最稳妥的方案,别为了技术炫技给自己加负担。
4.4 配送员端工作台:任务列表与状态流转按钮
配送员端是小程序里复用同一套代码、按角色区分展示的。登录进来后根据 user.role 判断,如果是配送员身份,首页自动切换成工作台样式,展示"待接单"和"进行中"两类任务列表。
工作台页面的核心是任务卡片。每个卡片上显示取件地址、送达地址、期望时间、配送费,点击进入任务详情。任务详情页底部会根据任务状态动态显示操作按钮:待接单时显示"立即接单",已接单显示"确认揽收",揽收后显示"开始配送",配送中显示"拍照签收"。
这里有个很实用的小技巧:导航栏高度适配问题。小程序不同机型顶部状态栏高度不一样,如果写死一个 px 值,在刘海屏上顶部内容会被遮挡。我封装了一个工具函数:
javascript复制function getNavBarInfo() {
const menuRect = wx.getMenuButtonBoundingClientRect();
const systemInfo = wx.getSystemInfoSync();
return {
statusBarHeight: systemInfo.statusBarHeight,
navBarHeight: (menuRect.top - systemInfo.statusBarHeight) * 2 + menuRect.height
};
}
这个函数返回状态栏高度和自定义导航栏高度,页面里动态计算 padding-top 和顶部按钮位置,保证在任何机型上都不会出现遮挡。这个"微信小程序顶部导航栏高度"问题几乎是每个自定义导航栏项目都会遇到的,建议直接存下来复用。
配送员到了送达点后,点击"拍照签收"会调起 wx.chooseMedia 拍照或从相册选择,确认后调用后端签收接口。签收照片是后续纠纷处理的依据,必须做必填校验,别放开。
5. 常见问题与排查技巧实录
5.1 springboot版本太高导致的兼容性问题
这绝对是新手最容易踩的坑。Spring Boot 3.x 发布后,很多同学直接选了最新版,结果发现 MyBatis-Plus 启动报错、Swagger 页面打不开、项目根本跑不起来。
核心原因就一个:Spring Boot 3.x 把 javax 包名改成了 jakarta。很多老依赖还是按 javax 写的,自然不兼容。如果你确实要用 Spring Boot 3.x,必须确认以下依赖版本:MyBatis-Plus 要 3.5.3.1 及以上,Swagger 要换 springdoc-openapi 2.x 版本,连接池要用 HikariCP 自带版本。
但说实话,我建议非必要不上 3.x。这个业务系统用 Spring Boot 2.7.18 完全够用,性能和稳定性没有区别,但踩坑成本低一个量级。版本号就是生产力和幸福感,能稳就稳。
5.2 登录失败与真机调试的典型问题
登录失败是出现频率最高的问题,而大部分登录失败都不是代码逻辑错了,而是环境问题。常见的三种:
- 后端返回"微信登录失败",多半是 appid 和 secret 不匹配。检查小程序后台的 AppID 和后端配置是否一致,特别注意不要在代码里写死别人的 appid。
- 开发工具里能登录,真机登录失败,一般是请求域名没配白名单,或者手机和电脑不在同一网络。
- 有时候开发者工具会弹"paused in debugger",这是工具自带的调试暂停,不用慌,点继续执行就行,不是你的代码问题。
调试小程序请求还有一个好用的技巧:抓包。用微信开发者工具的 Network 面板就能看到每个请求的完整参数和响应,排查接口问题足够了,不用额外装抓包工具。如果接口返回了非预期结果,先把 Network 里的请求参数和后端日志对一下,90% 的问题都能定位。
5.3 开发完成后的上线检查清单
小程序开发完不是直接点上传就能发布的,上线前有几件事必须做,否则审核大概率被拒。
第一,域名必须是 HTTPS,并且在小程序后台把接口域名配置到 request 合法域名里。微信只认备案过的域名,IP 地址和 http 都不行,这个要在部署时提前准备。
第二,类目选择要谨慎。如果你的小程序被归到"快递"类目,可能需要提供快递业务经营许可证,个人开发者根本拿不到。实际操作中,很多校园代取项目会选"同城服务"或"便民服务"类目,用"跑腿"的定位来规避资质门槛。具体怎么选,建议上线前仔细看微信的类目说明,别等审核被拒了再改。
第三,隐私协议必须配置。小程序如果收集用户手机号、位置信息,必须在"小程序后台 -> 设置 -> 服务内容声明"里填写用户隐私保护指引,并在代码里做隐私授权弹窗。没配这个,审核会以"涉及用户隐私收集"为由驳回。
第四,如果你有小程序跳转小程序的需求,比如从包裹系统跳到一个优惠券小程序,需要在微信公众平台后台配置跳转白名单,不然会跳转失败。这个操作在"设置 -> 第三方设置 -> 小程序跳转"里配置,两边都要加。
6. 几个值得复盘的个人经验
最后再分享一点自己做这类项目的心得。整个系统做完后我复盘过,发现最有价值的不是某个接口写得多优雅,而是状态机设计和权限控制从一开始就钉死了。状态不乱,业务就不会乱;权限不松,数据就不会脏。刚开始写代码时我也觉得状态机校验啰嗦,后面越写越庆幸有这层防护,因为前端页面迭代速度快,经常有人误调接口,没有这层校验早就出事故了。
还有一个体会是,能跑就行和能维护完全是两码事。我见过很多项目接口文档缺失、命名随意,一个字段在 A 接口叫 status,在 B 接口叫 orderStatus,后面接手的人想死的心都有。坚持用统一的枚举和命名规范,短期看是慢了,长期看是在帮自己省时间。
如果你打算在这套系统上做二次开发,我建议先改状态机,再改页面,顺序反了会非常痛苦。这套模型也不只适合校园驿站,把配送员换成社区团长、把包裹换成生鲜订单,核心链路一样能跑通。希望这篇整理能帮你把关键路径一次性走对,少踩几个没必要的坑。
