简介:这是一套基于Java开发的派单系统平台完整源码,面向希望学习全栈开发或搭建服务行业订单调度系统的开发者,涵盖后台任务分配、状态跟踪与Android客户端下单接单等核心流程,适合具备一定Java与Android基础的中高级学习者参考。压缩包共11019个文件,约65.36MB,以js、png、h、svg、css等前端资源为主,另有404个java源文件、301个xml布局、236个json配置及gradle构建脚本,并包含源码必读.txt阅读指南,便于快速理解项目结构与关键组件。目前已有309人学习下载。项目借鉴upWork式工作流,涉及Spring Boot、MyBatis、MySQL、Retrofit、OkHttp及SSL/TLS安全通信等知识点,前后端通过RESTful API与JSON交互,是理解Java服务端与Android客户端协同开发的实用案例。
1. 一套 Java 派单系统源码,为什么值得后端和 Android 开发者各拆一遍
派单这件事,听起来像外卖骑手的专属,其实任何「把任务分给合适的人」的场景都算:运维工单、售后上门、地推任务、巡检打卡。市面上讲派单系统的文章大多停在「抢单 + 派单」两个按钮,真到落地就卡在并发、状态机、位置上报这几处。这份 Java 派单系统平台源码完整版,带 Android 端完整源码和项目说明,价值不在于代码量,而在于它把「服务端调度 + 移动端执行」这条链路完整跑通了——后端负责订单池、派单策略、状态流转,Android 端负责接单、上报位置、回传结果。适合两类人:一是想找一个能跑通的服务端 + 移动端联调案例的 Java 开发者,二是想拿真实业务练 Android 网络层和定位的移动端开发者。下面按「先看清结构、再动手跑通、最后避坑」的顺序拆。
2. 先看清工程结构:服务端分层与 Android 端模块怎么对应
拿到一份源码,最忌讳上来就点运行。先花二十分钟把目录结构和依赖关系摸清楚,后面能省掉大量「找不到类」的时间。这套派单系统的服务端是典型的分层结构,Android 端则围绕网络请求和定位两条主线组织。
2.1 服务端分层:Controller / Service / Mapper 各管什么
服务端常见做法是 Spring Boot + MyBatis 的组合,分层大致是 Controller 接请求、Service 写业务、Mapper 落库。派单系统的业务重心在 Service 层,因为「谁该接这一单」的判断逻辑全在这里。你可以先找这几个关键类:订单相关的 Controller、派单策略的 Service、订单状态的枚举。状态枚举尤其重要,它决定了整个系统的流转规则。
// 订单状态枚举,派单系统的核心契约 public enum OrderStatus { PENDING(0, "待派单"), // 刚创建,还没分配 DISPATCHED(1, "已派单"), // 已指定接单人,等待接单 ACCEPTED(2, "已接单"), // 接单人确认 IN_PROGRESS(3, "进行中"), // 上门/执行中 FINISHED(4, "已完成"), // 结果已回传 CANCELLED(5, "已取消"); // 异常终止 private final int code; private final String desc; OrderStatus(int code, String desc) { this.code = code; this.desc = desc; } public int getCode() { return code; } }这段枚举是整个系统的「黑匣子」入口。参数说明:code是落库的整型值,desc是给前端展示的中文。逻辑上,任何状态变更都必须走PENDING → DISPATCHED → ACCEPTED → IN_PROGRESS → FINISHED这条主链,CANCELLED是旁路。你排查问题时,第一件事就是看数据库里status字段的值是否符合这条链——如果出现PENDING直接跳到FINISHED,说明某处状态校验被绕过了。
2.2 Android 端模块:网络层、定位层、UI 层怎么拆
Android 端一般按network、location、ui三个包组织。network里是 Retrofit 或 OkHttp 的接口定义,location里封装定位回调,ui里是接单列表和详情页。派单场景对移动端有两个硬要求:一是长连接或轮询要及时收到新单推送,二是定位要持续上报。先看网络接口的定义方式:
// Retrofit 接口定义,注意派单相关的三个端点 public interface OrderApi { @GET("order/pending") Call<List<Order>> getPendingOrders(@Query("userId") String userId); @POST("order/accept") Call<Result> acceptOrder(@Body AcceptRequest request); @POST("order/report") Call<Result> reportLocation(@Body LocationReport report); }逻辑说明:getPendingOrders拉取待接单列表,acceptOrder提交接单请求,reportLocation上报位置。参数上,userId用来过滤「这个接单人能看到的单」,AcceptRequest里通常带orderId和timestamp。这里有个容易忽略的点:接单接口必须做幂等,否则用户手抖点两下就会重复接单。常见做法是在服务端用orderId + userId做唯一约束,或者用乐观锁版本号。
2.3 两端如何对齐:接口字段与状态码约定
服务端和 Android 端最容易翻车的地方是字段对不齐。比如服务端返回createTime是时间戳,Android 端按字符串解析,直接崩。建议先找项目说明里的接口文档,没有的话就对着 Controller 的返回体逐个核对。重点核对三类字段:订单状态码、时间格式、坐标经纬度。坐标尤其要注意,服务端存的是double,Android 端定位回调给的是Location.getLatitude(),精度和正负号都要一致,否则会出现「派单到河对岸」的玄学问题。
3. 把服务端跑起来:数据库、配置、派单策略三步走
结构看清了,接下来动手。服务端跑通的核心是三件事:建库导数据、改配置、验证派单逻辑。这一步做完,你就能用 Postman 或 curl 模拟一次完整派单。
3.1 建库与初始化:SQL 脚本执行顺序
项目说明里一般会带sql目录,里面是建表语句和初始数据。执行顺序不能乱:先建库,再建表,最后导初始数据。常见做法是用 Navicat 或命令行source导入。注意字符集,派单系统里中文地址多,建库时用utf8mb4,否则地址里的特殊字符会变问号。
# 命令行导入,注意先切到 sql 文件所在目录 mysql -u root -p -e "CREATE DATABASE dispatch DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p dispatch < schema.sql mysql -u root -p dispatch < init_data.sql参数说明:-u root是用户名,-p会提示输密码,dispatch是库名。逻辑上,schema.sql建表,init_data.sql灌入测试用的接单人和订单。执行完用SHOW TABLES;确认表都在,重点看order、user、dispatch_record这三张表。如果init_data.sql报外键错误,说明导入顺序反了,先导主表再导从表。
3.2 配置文件:数据库连接与派单参数
Spring Boot 的配置在application.yml或application.properties。派单系统除了数据库,通常还有几个业务参数:派单半径、超时未接单自动重派时间、最大重派次数。这些参数直接决定系统行为,改之前先理解含义。
spring: datasource: url: jdbc:mysql://localhost:3306/dispatch?useUnicode=true&characterEncoding=utf8mb4 username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver dispatch: radius: 3000 # 派单半径,单位米 timeout: 60 # 超时未接单秒数,超过则重派 max-retry: 3 # 最大重派次数参数说明:radius决定「多远范围内的接单人会收到这单」,设太大接单人跑不过来,设太小可能没人接;timeout是接单倒计时,到点没接就换人;max-retry防止一单无限重派。逻辑上,这三个参数配合使用:先按半径筛人,派给最近的一个,超时没接就派下一个,重派超过max-retry次就转人工。调参时建议先把radius调大、timeout调长,方便观察流程,稳定后再收紧。
3.3 验证派单:用 curl 模拟一次完整流转
服务端起来后,别急着开 Android 端,先用 curl 把接口跑一遍。这样能把「服务端问题」和「移动端问题」隔离开,是排查效率最高的做法。
# 1. 创建订单 curl -X POST http://localhost:8080/order/create \ -H "Content-Type: application/json" \ -d '{"address":"测试地址","longitude":116.40,"latitude":39.90}' # 2. 查询待接单列表(模拟接单人视角) curl "http://localhost:8080/order/pending?userId=1001" # 3. 接单 curl -X POST http://localhost:8080/order/accept \ -H "Content-Type: application/json" \ -d '{"orderId":1,"userId":1001}' # 4. 上报位置 curl -X POST http://localhost:8080/order/report \ -H "Content-Type: application/json" \ -d '{"orderId":1,"longitude":116.41,"latitude":39.91}'逻辑说明:这四步对应订单的完整生命周期。第一步创建后状态是PENDING,第二步应该能查到这单,第三步接单后变ACCEPTED,第四步上报位置后变IN_PROGRESS。每步之后都去数据库SELECT status FROM order WHERE id=1;确认状态。如果第二步查不到,检查派单半径是否覆盖了订单坐标;如果第三步报错,检查userId是否存在、订单是否已被别人接走。
4. Android 端联调:网络请求、定位上报与真机调试
服务端验证通过后,Android 端才有意义。这一步的重点是让 App 能连上你的服务端,并且定位能正常上报。模拟器调试定位会有偏差,建议用真机。
4.1 改 BaseUrl 与网络权限
Android 端第一步是改BaseUrl,指向你电脑的 IP。注意不能用localhost,因为手机和电脑是两个设备,localhost在手机上指的是手机自己。要用电脑的局域网 IP,比如192.168.1.100。
<!-- AndroidManifest.xml 里必须有网络和定位权限 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />// Retrofit 初始化,BaseUrl 换成你电脑的局域网 IP Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://192.168.1.100:8080/") // 注意结尾斜杠 .addConverterFactory(GsonConverterFactory.create()) .build();参数说明:baseUrl结尾的斜杠不能少,否则 Retrofit 拼接路径会出错。逻辑上,手机和电脑要在同一个局域网,电脑防火墙要放行 8080 端口。如果请求超时,先在手机浏览器访问http://192.168.1.100:8080/order/pending?userId=1001,能返回数据说明网络通,问题在 App 代码;访问不了就是网络或防火墙问题。
4.2 定位上报:权限申请与回调处理
Android 6.0 以后定位权限要动态申请,直接调定位接口会静默失败。常见做法是在接单页面onCreate里检查权限,没有就弹窗申请。
// 动态申请定位权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1001); } // 定位回调里上报 LocationCallback callback = new LocationCallback() { @Override public void onLocationResult(LocationResult result) { Location loc = result.getLastLocation(); if (loc != null) { LocationReport report = new LocationReport(); report.setOrderId(currentOrderId); report.setLongitude(loc.getLongitude()); report.setLatitude(loc.getLatitude()); api.reportLocation(report).enqueue(...); // 异步上报 } } };逻辑说明:权限申请用requestCode区分,回调里判断grantCode是否授权。定位回调里先判空,再组装上报体。参数上,orderId是当前正在执行的订单,longitude和latitude直接取定位结果。注意上报频率,太频繁耗电,太稀疏服务端看不到轨迹,常见做法是每 10 到 30 秒上报一次,或者位置变化超过 50 米才上报。
4.3 真机调试:日志与断点怎么配合
真机调试时,Log.d和断点要配合用。网络请求失败先看 Logcat 里的okhttp日志,定位失败看LocationManager相关日志。如果 App 闪退,看AndroidRuntime的堆栈。一个血泪经验:真机连电脑调试时,USB 调试授权弹窗如果没点「始终允许」,重新插拔后会断连,日志就断了。建议在开发者选项里打开「USB 调试」和「保持唤醒」。
5. 避坑与排查:派单系统最容易翻车的五个地方
这套源码跑通不难,难的是跑稳。下面五条是我拆类似项目时踩过的坑,每条按「现象 → 原因 → 解决」写,你对照排查能省不少时间。
5.1 现象:接单人收到重复订单推送
原因:服务端推送和 Android 端轮询同时生效,或者接单接口没做幂等,用户重复点击。解决:服务端用orderId + userId做唯一约束,接单接口加乐观锁;Android 端接单按钮点击后立即置灰,收到成功回调再恢复。
5.2 现象:订单状态卡在「已派单」不动
原因:接单人没在timeout时间内接单,但重派逻辑没触发。常见是定时任务没启动,或者max-retry设成了 0。解决:检查定时任务类是否被 Spring 扫描到,@Scheduled注解是否生效;确认max-retry大于 0。
5.3 现象:Android 端定位一直返回 null
原因:权限没授权,或者模拟器没有定位数据。解决:真机测试,确认权限弹窗点了允许;模拟器的话在 Extended Controls 里手动设置经纬度。另外检查LocationRequest的优先级,派单场景用PRIORITY_HIGH_ACCURACY。
5.4 现象:服务端返回中文乱码
原因:数据库连接没指定字符集,或者建库时用了latin1。解决:JDBC URL 加characterEncoding=utf8mb4,建库语句用DEFAULT CHARACTER SET utf8mb4。已经建好的库可以用ALTER DATABASE dispatch CHARACTER SET utf8mb4;改。
5.5 现象:Android 端请求服务端报Cleartext HTTP traffic not permitted
原因:Android 9 以后默认禁止明文 HTTP。解决:在AndroidManifest.xml的application标签加android:usesCleartextTraffic="true",或者用 HTTPS。开发阶段用前者,上线前换 HTTPS。
6. 进阶:把派单策略从「最近优先」改成「负载均衡」
源码自带的派单策略通常是「最近优先」,实现简单但有个问题:热门区域的接单人会被压垮,冷门区域的人闲着。进阶做法是引入负载均衡,综合距离和当前接单量打分。下面是一个可落地的打分函数,你可以直接替换掉原来的策略类。
/** * 综合评分:距离越近分越高,当前单量越多分越低 * @param distance 接单人到订单的距离(米) * @param currentLoad 接单人当前进行中的订单数 * @return 综合得分,越高越优先 */ public double score(double distance, int currentLoad) { double distanceScore = 1.0 / (1.0 + distance / 1000.0); // 距离归一化 double loadScore = 1.0 / (1.0 + currentLoad); // 负载归一化 return distanceScore * 0.7 + loadScore * 0.3; // 权重可调 }参数说明:distance单位米,除以 1000 转成公里再归一化;currentLoad是接单人手上未完成的单量;两个权重0.7和0.3分别代表距离和负载的重要程度,业务上如果更看重响应速度就把距离权重调高,更看重公平就把负载权重调高。逻辑上,这个函数对每个候选接单人算分,取最高分的派单。验证方法:造两个接单人,一个距离 500 米但手上有 5 单,一个距离 2000 米但手上 0 单,看系统派给谁——按上面的权重,前者得分约0.7*0.67+0.3*0.17=0.52,后者约0.7*0.33+0.3*1.0=0.53,会派给后者,符合负载均衡预期。
改完策略后,别只看单次结果,要跑一批数据对比。常见做法是写个单元测试,构造 100 个订单和 10 个接单人,统计两种策略下的平均接单距离和最大接单量,用数据说话。我一般会把这个对比跑一遍再上线,避免「感觉更均衡了」这种主观判断。从那以后我每次改派单策略,都强制走一遍「构造数据 → 跑对比 → 看分布」的流程,希望帮到你。
本文还有配套的精品资源,点击获取