
简介这是一套基于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) CallListOrder getPendingOrders(Query(userId) String userId); POST(order/accept) CallResult acceptOrder(Body AcceptRequest request); POST(order/report) CallResult reportLocation(Body LocationReport report); }逻辑说明getPendingOrders拉取待接单列表acceptOrder提交接单请求reportLocation上报位置。参数上userId用来过滤「这个接单人能看到的单」AcceptRequest里通常带orderId和timestamp。这里有个容易忽略的点接单接口必须做幂等否则用户手抖点两下就会重复接单。常见做法是在服务端用orderId userId做唯一约束或者用乐观锁版本号。2.3 两端如何对齐接口字段与状态码约定服务端和 Android 端最容易翻车的地方是字段对不齐。比如服务端返回createTime是时间戳Android 端按字符串解析直接崩。建议先找项目说明里的接口文档没有的话就对着 Controller 的返回体逐个核对。重点核对三类字段订单状态码、时间格式、坐标经纬度。坐标尤其要注意服务端存的是doubleAndroid 端定位回调给的是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?useUnicodetruecharacterEncodingutf8mb4 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?userId1001 # 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 id1;确认状态。如果第二步查不到检查派单半径是否覆盖了订单坐标如果第三步报错检查userId是否存在、订单是否已被别人接走。4. Android 端联调网络请求、定位上报与真机调试服务端验证通过后Android 端才有意义。这一步的重点是让 App 能连上你的服务端并且定位能正常上报。模拟器调试定位会有偏差建议用真机。4.1 改 BaseUrl 与网络权限Android 端第一步是改BaseUrl指向你电脑的 IP。注意不能用localhost因为手机和电脑是两个设备localhost在手机上指的是手机自己。要用电脑的局域网 IP比如192.168.1.100。!-- AndroidManifest.xml 里必须有网络和定位权限 -- uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.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?userId1001能返回数据说明网络通问题在 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 加characterEncodingutf8mb4建库语句用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:usesCleartextTraffictrue或者用 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.670.3*0.170.52后者约0.7*0.330.3*1.00.53会派给后者符合负载均衡预期。改完策略后别只看单次结果要跑一批数据对比。常见做法是写个单元测试构造 100 个订单和 10 个接单人统计两种策略下的平均接单距离和最大接单量用数据说话。我一般会把这个对比跑一遍再上线避免「感觉更均衡了」这种主观判断。从那以后我每次改派单策略都强制走一遍「构造数据 → 跑对比 → 看分布」的流程希望帮到你。本文还有配套的精品资源点击获取