1. 校园跑腿系统到底在解决什么问题
如果你在大学校园里生活过,大概率见过这样的场景:宿舍群里有人发“求代拿快递,东门菜鸟驿站,3 栋,有偿”,然后下面接龙一串“+1”;或者班委在群里统计“谁去打印店,帮忙带一份课件”。这种人工撮合的方式有几个明显的问题:需求方和接单方都靠刷屏寻找彼此、价格全凭口头约定、订单状态无法追踪、出现纠纷也没有凭证。本质上,校园跑腿是一个低频次、高即时性、强地理属性的需求,而微信群接龙完全不具备支撑这种匹配效率的机制。
校园跑腿系统要解决的,就是把这套原始撮合流程搬到线上,用订单状态机驱动整个业务流程:用户发布任务、跑腿员抢单/接单、系统记录状态变更、双方完成支付和评价。从技术角度看,它又是一个非常典型的“前后端分离 + 移动端跨平台 + 关系型数据库”的全栈项目,非常适合作为编程实战、毕业设计或简历项目。
这篇文章从业务模块、技术选型、数据库设计、核心接口实现、前端页面、部署联调和常见坑位几个维度展开。读完你不仅能理解这个系统是怎么跑起来的,还能直接对照源码把项目落地到本地,知道每一层该写什么、为什么这么写。
2. 核心业务模型与七大功能模块拆解
从业务上说,校园跑腿系统里存在三类角色:普通用户(发布需求)、跑腿员(接单完成)、平台管理员(审核与运营)。所有功能都围绕这三类角色的协作流转展开。标题中提到的“七大业务模块”具体到源码实践,一般会拆成下面这些维度,这也是多数校园跑腿项目的标准划分方式。
2.1 用户管理模块
包含注册、登录、身份认证、个人信息维护、余额与支付账户绑定。学生用户和跑腿员在注册环节通常走不同入口,跑腿员需要额外提交身份信息用于审核。这块的核心是安全设计:密码不能明文存储,统一走哈希加盐;JWT Token 用于维持登录态,接口层通过拦截器校验权限。
2.2 跑腿员管理模块
跑腿员不是简单注册就能接单,需要后台审核认证。模块里包括跑腿员资料审核、接单状态管理(空闲/忙碌/离线)、历史订单统计、接单排行榜。从业务设计角度,跑腿员的“忙碌状态”需要和并发抢单逻辑联动,否则会出现一个跑腿员同时接到多单的问题。
2.3 跑腿任务管理模块
这是系统的核心流程之一。用户发布任务时填写物品类型、取件地点、送达地点、期望完成时间、小费金额。任务创建后进入“待接单池”,跑腿员可以看到附近的任务列表。任务发布后还没有人接时,用户可以取消;被接单后,取消则需要跑腿员同意或管理员介入。看似简单的状态管理,落到数据库层面就是一张任务表加一个状态字段,但状态流转的逻辑必须严谨。
2.4 订单管理模块
任务被接单后升级为订单,订单模块管理整个履约链路:接单、取件、送达、确认完成、支付、退款/售后。这里最核心的是订单状态机:待接单 -> 已接单 -> 配送中 -> 已完成,中间穿插取消、申诉等分支状态。状态机设计得好,整个系统的流程控制就清晰;设计得乱,后面加了需求很容易让代码变成一团乱麻。
2.5 支付与结算模块
校园跑腿的小额交易有两个典型方案:一是接入微信/支付宝支付,二是平台余额账户。从源码实战的角度,很多项目会先实现余额支付逻辑,再预留第三方支付接口。跑腿员完成订单后获得报酬,平台按比例抽成或收取信息费,这部分涉及订单金额拆分、账户流水记录和提现审核。
2.6 评价与投诉模块
订单完成后双方互评,评价内容影响跑腿员的信用分和展示排序。投诉模块负责处理履约过程中的纠纷,管理员可以查看聊天记录、订单快照,进行退款或赔偿裁定。
2.7 公告与运营管理模块
管理员维护系统公告、跑腿员审核、用户封禁、订单数据统计。运营模块在毕业设计或实战项目中看起来不起眼,但在答辩和项目介绍里是很好的加分点,因为它体现了完整的后台管理闭环。
3. 技术选型分析:Spring Boot + Vue + uni-app + MySQL
之所以这套组合适合校园跑腿项目,不是因为每个技术选型都是最新的,而是因为它覆盖了当前企业级项目最主流的开发形态。
后端采用 Spring Boot + MyBatis Plus/MyBatis + MySQL,是 Java 生态里最成熟的一套组合。Spring Boot 负责提供 RESTful API 和自动配置,MyBatis 系负责数据库访问,MySQL 存储业务数据。认证机制使用 JWT + Spring Interceptor,权限控制通过自定义注解实现,这些都是 Java 后端岗位面试高频考点。
前端管理后台用 Vue + Element UI / Element Plus,适合快速搭建表格、表单、弹窗这类后台管理界面。Vue 的响应式数据和组件化开发方式,让订单列表、用户表格这类页面写起来很直观。如果你选择 Vue 3 + Vite,开发体验会比 Vue 2 + Webpack 更轻量,不过很多现成源码还在用 Vue 2,实际选型时看自己的熟悉度即可。
移动端采用 uni-app,这是一套 Vue 语法跨端框架,一套代码可以编译到微信小程序、H5 和 App。对于校园跑腿这种需要“用户随时发布任务、跑腿员随地接单”的场景,小程序是最合适的载体,uni-app 的价值在于保留 Vue 开发体验的同时,把多端适配问题封装掉。
MySQL 负责数据持久化,涉及用户表、任务表、订单表、账户流水表、评价表等。表结构设计是这个项目数据库部分的核心,下面的章节单独展开。
这套组合的逻辑在于:后端工程化能力强,适合业务规则复杂的订单流转;前端开发和移动端共用了 Vue 语法体系,学习成本被拉低;MySQL 足够支撑校园场景的并发量,不需要引入更重的分布式中间件。适合的才是最好的。
4. 数据库设计:核心表结构与关系说明
数据库设计决定了这个项目能支撑多少业务扩展。以下 6 张核心表是所有校园跑腿项目都必须具备的,建表时命名统一使用下划线风格,字段注释写清楚,这是良好工程习惯的体现。
4.1 用户表(user)
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` varchar(64) NOT NULL COMMENT '用户名', `password` varchar(128) NOT NULL COMMENT '密码(BCrypt加密)', `nickname` varchar(64) DEFAULT NULL COMMENT '昵称', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `role` tinyint(4) NOT NULL DEFAULT '0' COMMENT '角色:0-普通用户 1-跑腿员 2-管理员', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:0-禁用 1-正常', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';用户表是最基础的实体,跑腿员信息如果字段很多,可以单独拆一张 deliverer 表,用 user_id 关联。实际项目中更常见的做法是在 user 表加 role 字段区分身份,再在跑腿员专属表中补充认证资料、接单状态、评分等扩展字段。
4.2 跑腿任务表(task)
CREATE TABLE `task` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID', `user_id` bigint(20) NOT NULL COMMENT '发布人ID', `task_type` varchar(32) DEFAULT NULL COMMENT '任务类型:取快递/代购/打印等', `pickup_address` varchar(255) NOT NULL COMMENT '取件地点', `delivery_address` varchar(255) NOT NULL COMMENT '送达地点', `reward` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '跑腿费(元)', `remark` varchar(500) DEFAULT NULL COMMENT '备注说明', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-待接单 1-已接单 2-配送中 3-已完成 4-已取消', `deliverer_id` bigint(20) DEFAULT NULL COMMENT '接单跑腿员ID', `expect_time` datetime DEFAULT NULL COMMENT '期望完成时间', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_status` (`status`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跑腿任务表';任务表的状态字段是整个业务流程的核心。注意这里状态用 int 而不是 varchar,因为状态机用数字定义扩展更方便;建立 status 索引是为了支持“待接单任务列表”这类高频查询。
4.3 订单表(orders)
CREATE TABLE `orders` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID', `order_no` varchar(64) NOT NULL COMMENT '订单编号', `task_id` bigint(20) NOT NULL COMMENT '关联任务ID', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `deliverer_id` bigint(20) NOT NULL COMMENT '跑腿员ID', `amount` decimal(10,2) NOT NULL COMMENT '订单金额', `pay_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '支付状态:0-未支付 1-已支付 2-已退款', `order_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态:0-进行中 1-已完成 2-已取消 3-售后中', `finish_time` datetime DEFAULT NULL COMMENT '完成时间', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_task_id` (`task_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';订单表和任务表在业务上是一对一关系,为什么要拆两张表?因为任务描述的是跑腿服务本身,订单描述的是交易行为,两者关注点不同。订单需要记录支付金额、支付状态、完成时间,而任务关注地点和完成流程。拆开后各自的字段职责更清楚,也方便未来订单表扩展支付流水等字段。
4.4 钱包与流水表
用户要支付跑腿费、跑腿员要收到报酬,就需要一个账户体系。最少需要两张表:账户表(wallet)和流水表(wallet_transaction)。
账户表核心字段:user_id、balance(余额)、frozen_amount(冻结金额)、status。流水表核心字段:wallet_id、user_id、change_type(收入/支出/冻结/解冻)、change_amount、balance_after、biz_type、biz_id、create_time。
在支付场景里,最简单也最稳妥的流程是:用户发布任务时先冻结余额,任务完成后再实际扣款并计入跑腿员账户。引入“冻结”这个中间状态,可以避免提现后账户余额不足的纠纷。
4.5 评价表(review)
CREATE TABLE `review` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '评价ID', `order_id` bigint(20) NOT NULL COMMENT '订单ID', `from_user_id` bigint(20) NOT NULL COMMENT '评价人ID', `to_user_id` bigint(20) NOT NULL COMMENT '被评价人ID', `rating` tinyint(4) NOT NULL DEFAULT '5' COMMENT '评分1-5', `content` varchar(500) DEFAULT NULL COMMENT '评价内容', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), KEY `idx_to_user_id` (`to_user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评价表';评价数据用于计算跑腿员平均分,列表页按评分排序时直接对 to_user_id 做 AVG 聚合即可。数据量变大后可以把平均分冗余到跑腿员表中,避免每次联表统计,但毕业设计阶段无需过度优化。
5. 后端接口设计与状态机流转实现
5.1 任务状态机设计
后端最核心的逻辑不在 CRUD,而在于任务和订单的状态流转。设计状态机时先穷举状态,再定义合法流转路径:
状态:待接单(0) -> 已接单(1) -> 配送中(2) -> 已完成(3) \-> 已取消(4)合法的流转路径:
- 待接单 -> 已接单:跑腿员抢单
- 待接单 -> 已取消:用户取消
- 已接单 -> 配送中:跑腿员确认已取件
- 已接单 -> 已取消:跑腿员取消需要用户同意,或管理员强制取消
- 配送中 -> 已完成:跑腿员送达,用户确认或超时自动确认
- 已完成 -> 售后中:用户发起售后
状态机可以写在一个枚举类里,也可以用设计模式做成处理器,但最简单的做法是写一个 TaskStatusEnum 枚举 + 一个状态流转校验工具类,在 Service 方法里先校验再执行更新。
5.2 任务发布接口
以 Spring Boot 为例,任务发布接口的 Controller 只负责接收参数和返回结果,核心逻辑在 Service 层完成。下面是一个简化示例,可以作为源码阅读的索引。
// 文件路径:src/main/java/com/campus/runner/controller/TaskController.java @RestController @RequestMapping("/api/task") public class TaskController { @Resource private TaskService taskService; /** * 发布跑腿任务 */ @PostMapping("/publish") public Result<Long> publish(@RequestBody @Validated TaskPublishRequest request, @RequestAttribute("userId") Long userId) { Long taskId = taskService.publish(userId, request); return Result.success(taskId); } /** * 跑腿员接单 */ @PostMapping("/accept/{taskId}") public Result<Void> accept(@PathVariable Long taskId, @RequestAttribute("userId") Long userId) { taskService.accept(taskId, userId); return Result.success(); } /** * 获取待接单任务列表 */ @GetMapping("/pending") public Result<List<TaskVO>> pendingList(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { return Result.success(taskService.pendingList(page, size)); } }接单接口是这个系统里最需要小心的并发点。两个跑腿员同时点接单,必须保证只有一个人成功。方案是在任务表中执行条件更新:
// 文件路径:src/main/java/com/campus/runner/service/impl/TaskServiceImpl.java @Override @Transactional(rollbackFor = Exception.class) public void accept(Long taskId, Long delivererId) { // 方案一:乐观锁,先检查状态再更新 Task task = taskMapper.selectById(taskId); if (task == null || task.getStatus() != 0) { throw new BusinessException("任务不存在或已被接单"); } int rows = taskMapper.updateStatusWithCondition(taskId, 0, 1, delivererId); if (rows == 0) { throw new BusinessException("手慢了,任务已被其他人接走"); } }<!-- 文件路径:src/main/resources/mapper/TaskMapper.xml --> <update id="updateStatusWithCondition"> UPDATE task SET status = #{targetStatus}, deliverer_id = #{delivererId} WHERE id = #{taskId} AND status = #{currentStatus} </update>这一段就是典型的“乐观锁更新”:把状态检查放在 UPDATE 的 WHERE 条件里,数据库行锁保证同一时间只有一个事务能更新成功。
5.3 用户认证与拦截器配置
JWT 登录是前后端分离项目的标配。登录接口验证用户名密码后签发 Token,前端每次请求在 Header 中携带 Authorization,后端通过拦截器解析 Token 并把 userId 放到 Request 属性中。
// 文件路径:src/main/java/com/campus/runner/config/WebMvcConfig.java @Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 放行登录、注册等白名单接口 String uri = request.getRequestURI(); if (uri.contains("/api/auth/login") || uri.contains("/api/auth/register")) { return true; } // 从请求头获取 Token String token = request.getHeader("Authorization"); if (!StringUtils.hasText(token)) { throw new BusinessException("未登录,请先登录"); } // 解析 Token 得到 userId Long userId = JwtUtil.parseToken(token.replace("Bearer ", "")); request.setAttribute("userId", userId); return true; } }拦截器配置好之后,Controller 中通过@RequestAttribute("userId")拿到当前登录用户,不需要在每个接口里重复解析 Token。如果项目里有管理员接口,再在拦截器后面加一层管理员权限校验。
6. 前端与移动端页面实现要点
6.1 管理后台(Vue)
管理后台的核心页面包括登录页、用户管理、跑腿员审核、任务列表、订单列表、数据统计。以订单列表页为例,典型的 Vue + Element UI 代码结构如下:
<!-- 文件路径:src/views/order/OrderList.vue --> <template> <div class="order-list"> <el-card> <el-form :inline="true" :model="queryParams"> <el-form-item label="订单编号"> <el-input v-model="queryParams.orderNo" placeholder="请输入订单编号" clearable /> </el-form-item> <el-form-item label="订单状态"> <el-select v-model="queryParams.status" placeholder="请选择状态" clearable> <el-option label="进行中" :value="0" /> <el-option label="已完成" :value="1" /> <el-option label="已取消" :value="2" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">查询</el-button> <el-button @click="resetQuery">重置</el-button> </el-form-item> </el-form> <el-table :data="orderList" v-loading="loading" border stripe> <el-table-column prop="orderNo" label="订单编号" width="220" /> <el-table-column prop="userName" label="用户" width="120" /> <el-table-column prop="delivererName" label="跑腿员" width="120" /> <el-table-column prop="amount" label="金额" width="100" /> <el-table-column prop="orderStatus" label="状态" width="100"> <template #default="{ row }"> <el-tag :type="statusTagType(row.orderStatus)"> {{ statusText(row.orderStatus) }} </el-tag> </template> </el-table-column> <el-table-column prop="createTime" label="创建时间" width="180" /> <el-table-column label="操作" min-width="150"> <template #default="{ row }"> <el-button type="primary" link @click="handleDetail(row)">详情</el-button> </template> </el-table-column> </el-table> <el-pagination v-model:current-page="queryParams.page" v-model:page-size="queryParams.size" :total="total" layout="total, prev, pager, next" @change="fetchList" /> </el-card> </div> </template>这是很典型的后台 CRUD 页面结构:查询表单、数据表格、分页组件。在源码里阅读这类页面的关键是找到每个单元格绑定的数据字段,再回去对照后端接口返回的 VO 类,前后端字段对齐后页面自然就通了。
6.2 移动端(uni-app)
用户端和跑腿员端都基于 uni-app 开发,核心页面包括登录注册、发布任务、任务大厅、任务详情、我的订单、个人中心。以任务大厅为例,页面结构是列表加载模式,通过触底加载更多分页数据。以下是一段典型的 API 请求封装,移动端所有接口都通过它访问后端:
// 文件路径:src/utils/request.js const BASE_URL = 'http://localhost:8080/api' export function request(options) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'Authorization': uni.getStorageSync('token') || '' }, success: (res) => { if (res.data.code === 200) { resolve(res.data.data) } else { uni.showToast({ title: res.data.msg || '请求失败', icon: 'none' }) reject(res.data) } }, fail: (err) => { uni.showToast({ title: '网络异常,请检查后端服务是否启动', icon: 'none' }) reject(err) } }) }) }<!-- 文件路径:src/pages/task/TaskList.vue --> <template> <view class="task-list"> <view v-for="item in taskList" :key="item.id" class="task-card" @click="goDetail(item.id)"> <view class="task-title">{{ item.taskType }} · 跑腿费 ¥{{ item.reward }}</view> <view class="task-address"> <text class="label">取件:</text>{{ item.pickupAddress }} </view> <view class="task-address"> <text class="label">送达:</text>{{ item.deliveryAddress }} </view> <view class="task-footer"> <text class="time">{{ item.createTime }}</text> <text class="status" :class="'status-' + item.status">{{ statusText(item.status) }}</text> </view> </view> <view v-if="loading" class="loading-text">加载中...</view> <view v-if="finished" class="loading-text">没有更多了</view> </view> </template> <script setup> import { ref } from 'vue' import { request } from '@/utils/request.js' import { onReachBottom } from '@dcloudio/uni-app' const taskList = ref([]) const page = ref(1) const size = ref(10) const loading = ref(false) const finished = ref(false) async function fetchTaskList() { if (loading.value || finished.value) return loading.value = true try { const data = await request({ url: '/task/pending', data: { page: page.value, size: size.value } }) taskList.value = [...taskList.value, ...data.records] if (taskList.value.length >= data.total) { finished.value = true } page.value++ } finally { loading.value = false } } onReachBottom(() => { fetchTaskList() }) fetchTaskList() </script>发布任务页的核心是表单提交,需要注意金额校验,前端限制最小跑腿费不能为负数,同时后端也要做同样的校验。前后端双重校验是生产级项目的基本要求。
7. 本地环境搭建与前后端联调完整步骤
7.1 环境准备
- JDK 1.8 或 17(视后端源码版本而定,Spring Boot 2.x 用 JDK 8 即可,Spring Boot 3.x 需要 JDK 17)
- Maven 3.6+
- MySQL 5.7 或 8.0
- Node.js 14+ 和 npm
- HBuilderX 或微信开发者工具(运行 uni-app)
- IDEA 或 VS Code
版本细节以你拿到的源码 README 为准,不要盲目使用最新版,老项目用新依赖经常出现版本兼容问题。
7.2 初始化数据库
先创建数据库,再导入项目提供的 SQL 脚本。没有现成 SQL 时,可以直接使用第 4 节的核心建表语句作为基础,再按需补充。
mysql -u root -pCREATE DATABASE IF NOT EXISTS campus_runner DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE campus_runner; source /path/to/your/sql/campus_runner.sql;导入后检查一下核心表是否创建成功:
SHOW TABLES;7.3 启动后端服务
修改后端配置文件中的数据库连接信息:
# 文件路径:src/main/resources/application.yml(或 application.properties) spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_runner?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你的数据库密码 server: port: 8080 jwt: secret: your-jwt-secret-key expire: 604800然后启动后端:
mvn spring-boot:run启动成功后,控制台出现 “Started Application in x.xxx seconds” 说明后端服务正常。
7.4 启动 Vue 管理后台
cd campus-runner-admin npm install npm run serve启动后访问http://localhost:3000,使用管理员账号登录。如果后端接口地址不是 8080,注意修改前端的代理配置或 request 工具类中的 BASE_URL。
7.5 启动 uni-app 移动端
用 HBuilderX 打开移动端项目目录,或者直接用命令行:
cd campus-runner-mobile npm install npm run dev:mp-weixin然后用微信开发者工具打开生成的dist/dev/mp-weixin目录。移动端访问后端接口时,不能使用localhost,因为小程序运行在手机或模拟器环境。需要把 request.js 中的BASE_URL改为你电脑在局域网中的 IP,例如http://192.168.1.100:8080/api。
这一步是联调时最容易卡住的地方。后端监听的是本机 8080,微信开发者工具模拟器访问 localhost 指向的是开发者工具所在电脑,如果你的电脑就是后端所在机器,理论上能通。但真机预览时必须使用局域网 IP,且后端需要开启允许跨域或正确配置 CORS。
7.6 验证完整业务链路
用两个账号分别模拟用户和跑腿员:
- 用户登录,发布一个跑腿任务,填写取件地和送达地。
- 跑腿员登录,在任务大厅看到该任务,点击接单。
- 任务状态从“待接单”变为“已接单”。
- 跑腿员点击“确认取件”,状态变为“配送中”。
- 跑腿员点击“确认送达”,用户确认后订单完成。
- 用户在评价页给跑腿员打分,跑腿员个人页评分更新。
如果以上链路全部走通,说明核心业务闭环已经正常运转。
8. 常见问题与排查思路
前端和后端联调阶段最容易出现的问题集中在接口不通、跨域、状态不一致这几个方向。下面整理了一张排查表,实际项目里遇到问题时按表操作效率高很多。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 后端启动失败,提示数据库连接失败 | 数据库未创建、账号密码错误、服务未启动 | 检查 MySQL 服务状态,确认连接串中的账号密码 | 创建数据库并导入 SQL,核对 application.yml 配置 |
| 前端请求接口提示 404 | 后端接口路径有变更,前端 BASE_URL 错误 | 打开浏览器开发者工具查看 Network 请求地址 | 统一前后端接口文档,修改 BASE_URL |
| 小程序真机无法访问后端接口 | 使用了 localhost 地址 | 在电脑上执行ipconfig查看局域网 IP | 将 BASE_URL 改为局域网 IP,确保手机和电脑同一网络 |
| 跨域请求被拦截 | 后端未配置 CORS | 查看浏览器 Console 中的 CORS 报错 | 后端配置跨域过滤器或使用代理 |
| 用户发布任务后任务列表看不到 | 状态过滤条件错误,数据库字段不一致 | 查询任务表数据,检查 SQL 条件和返回 VO | 对齐状态枚举值,修正查询条件 |
| 接单时提示已被接走 | 并发问题,或状态字段被其他逻辑修改 | 观察数据库 task 表 status 字段 | 使用条件更新,保证状态变更原子性 |
| 支付扣款成功但余额没变化 | 事务未提交,或钱包表与流水表未在同一事务 | 查看 Service 是否有 @Transactional 注解 | 在涉及金额的方法上加事务注解 |
| 登录后接口仍提示未认证 | Token 未正确携带或拦截器放行逻辑错误 | 检查请求头 Authorization 是否传值 | 在前端 request 封装中添加 Token Header |
9. 最佳实践与工程化建议
从能跑通到跑得稳,中间还差一段工程化意识的距离。整理几条对这个项目最有价值的建议。
第一,接口返回结构要统一。建议所有接口统一返回Result包装类,包含 code、msg、data 三个字段,前端根据 code 判断是否成功。不要有的接口直接返回对象、有的返回 Map、有的返回 null,后期维护会非常痛苦。
第二,金额计算必须保证精度。Java 里做金额运算要用BigDecimal,不要用 double 或 float。涉及钱包扣款、冻结、结算之类的操作,代码里要加事务注解,让多个数据库操作在同一次事务内提交或回滚。这是答辩时经常被追问的高频问题。
第三,状态流转要集中管理。如果任务状态下方的判断逻辑散落在各种 Service 方法里,后续新增需求极易引发状态错乱。建议把状态机校验封装成独立类,集中维护合法流转路径,每个 Service 方法在更新状态前先调用校验逻辑。
第四,权限边界要清晰。普通用户不能调用跑腿员审核接口,跑腿员不能调用用户余额充值接口。权限用 Spring Interceptor + 自定义注解结合实现,Controller 上打@RequireRole("ADMIN")这类注解即可完成方法级权限控制。
第五,日志和异常处理要提前做。后端全局异常处理器把业务异常和系统异常分开,业务异常返回友好提示,系统异常记录完整堆栈。关键订单节点打印日志,方便排查“用户说没扣款、实际扣了但没入账”这类扯皮问题。
第六,不要把支付功能做成 mock 就不管了。即使是毕设项目,也要把“支付回调接口”和“订单超时未支付自动取消”这两个逻辑预留出来。支付回调是第三方支付接入的必经之路,超时取消可以用定时任务扫描实现,这两块设计做到位,项目整体完整度会提升一个档次。
10. 总结
校园跑腿系统这个选题的价值在于:它是一个完全真实、可感知、边界明确的业务系统,同时又涵盖了后端接口设计、前端管理界面、移动端跨端开发、关系型数据库建模这几大主流技术方向。比起单纯学习某个技术点,把它串成一个完整项目对开发能力的提升更明显。
从学习路径上看,建议按下面的顺序推进:先看数据库表结构和状态机设计,理解数据如何流转;再启用后端服务,用 Postman 把几个核心接口调通;然后启动管理后台和数据维护流程;最后用两个手机号分别扮演用户和跑腿员,完整走一遍任务发布、接单、完成、评价的链路。每走通一个环节,你对前后端如何配合、数据如何落库、状态如何流转的理解都会加深一层。
项目跑通之后,可以继续在三个方向上做扩展:一是接入真实的小程序支付,理解支付回调的完整流程;二是加入消息推送,让用户实时感知订单状态变化;三是加一个简单的推荐排序逻辑,比如根据跑腿员评分和距离决定任务分配优先级。这几个方向任何一个独立拿出来,都值得作为工程进阶的下一站。
最后提醒一句:拿到任何一套源码,先不要急着改业务代码。把项目启动起来、把数据流走通、把表关系画清楚,然后再动手改功能。跑的起来才是学习的前提,改得动才是收获的开始。