
之前不少同学在准备毕业设计时都会选“微信小程序 校园业务”这类题目。但真动手时会发现前端小程序要处理登录、页面状态、接口联调后端要考虑鉴权、订单状态流转、数据库设计资料又零散。本文围绕“基于微信小程序的校园跑腿系统”从需求分析、表结构设计、小程序端逻辑、后端接口实现、联调验证到常见报错排查完整拆解项目结构可以直接作为毕业设计或课程设计的底座代码思路也能迁移到同类型系统中。1. 系统背景与技术选型1.1 校园跑腿系统解决什么问题校园跑腿的业务场景很典型同学A在宿舍不想下楼拿快递同学B刚好有空且愿意跑一趟双方通过平台完成“发单—接单—送达—确认”的闭环。比起普通电商系统它更强调即时性和LBS属性。从毕业设计角度看一个完整的校园跑腿系统应当包含用户侧微信授权登录、发布跑腿订单、浏览待接单列表、确认完成、订单评价。接单侧抢单/接单、查看订单详情、标记取件/送达。管理侧用户管理、订单管理、分类管理、数据统计。这类系统的价值在于业务链路清晰技术点覆盖广适合作为毕业设计、课程设计、实训项目的原型也方便做功能扩展和二次开发。1.2 技术选型与系统架构系统采用前后端分离架构端技术选型作用小程序端微信小程序原生框架WXML WXSS JS用户交互、登录、发单、接单后端服务Spring Boot MyBatis提供 RESTful API、业务处理、鉴权数据库MySQL 5.7 / 8.0用户、订单、分类等数据存储云开发可选微信云开发适合不想维护后端的快速实现后端技术栈以 Java 为基础也可以换成 Node.js、Python Flask/Django、PHP 等本文讲解以 Spring Boot MyBatis 为主因为课程设计和毕业设计中这一套最常用。整体流程如下微信小程序 (WXML/JS) ↓ HTTPS JSON Spring Boot 后端 (Controller → Service → Mapper) ↓ JDBC MySQL 数据库开发者需要掌握的核心点包括微信小程序登录流程。自定义 Token 鉴权方案。订单状态机设计。前后端接口联调与真机调试。2. 功能模块与数据库设计2.1 功能模块拆分按照“电商 任务派发”的思路功能模块分为三类角色普通用户登录、发布订单、查看自己的订单、确认完成。跑腿者浏览大厅订单、接单、更新任务状态。管理员后台管理查看用户列表、订单列表、订单分类统计。小程序端页面至少包含pages/index/index 首页发单入口 订单大厅 pages/order/detail 订单详情 pages/my/orders 我的订单 pages/my/index 个人中心 pages/publish/publish 发布跑腿订单后端接口按照资源维度拆分POST /api/user/login 微信登录 GET /api/user/info 获取用户信息 POST /api/order/publish 发布订单 GET /api/order/list 订单列表分页 状态筛选 POST /api/order/accept 接单 POST /api/order/complete 确认完成 POST /api/order/cancel 取消订单2.2 数据库表结构设计一个可运行的跑腿系统只需要三类核心表用户表、订单表、分类表。下面给出 SQL 设计中间省略部分非必要字段保留了扩展字段的位置。-- 用户表 CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信用户唯一标识, nickname varchar(64) DEFAULT COMMENT 昵称, avatar varchar(255) DEFAULT COMMENT 头像URL, phone varchar(20) DEFAULT COMMENT 手机号, student_no varchar(32) DEFAULT COMMENT 学号, dormitory varchar(64) DEFAULT COMMENT 宿舍信息, role tinyint(4) DEFAULT 1 COMMENT 角色1-普通用户 2-跑腿者 3-管理员, balance decimal(10,2) DEFAULT 0.00 COMMENT 账户余额, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;-- 订单表 CREATE TABLE orders ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id int(11) NOT NULL COMMENT 发布人ID, type_id int(11) DEFAULT 1 COMMENT 订单类型取快递/代购/代办, title varchar(100) NOT NULL COMMENT 标题, description varchar(500) DEFAULT COMMENT 详细描述, pickup_address varchar(255) DEFAULT COMMENT 取件地址, delivery_address varchar(255) DEFAULT COMMENT 送达地址, reward decimal(10,2) DEFAULT 0.00 COMMENT 跑腿费/悬赏金, status tinyint(4) DEFAULT 0 COMMENT 状态0-待接单 1-进行中 2-已完成 3-已取消, accept_id int(11) DEFAULT NULL COMMENT 接单用户ID, create_time datetime DEFAULT CURRENT_TIMESTAMP, accept_time datetime DEFAULT NULL COMMENT 接单时间, finish_time datetime DEFAULT NULL COMMENT 完成时间, PRIMARY KEY (id), KEY idx_status (status), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT跑腿订单表;-- 订单分类表 CREATE TABLE order_type ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(32) NOT NULL COMMENT 分类名称, icon varchar(255) DEFAULT COMMENT 图标, sort int(11) DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单分类表;2.3 核心表关系与设计说明用户表与订单表是一对多关系一个用户可发布多个订单。订单表中的accept_id表示接单人它同样是用户表 id 的外键逻辑但一个订单只能有一个接单人。这里的status字段是整个系统的核心状态机0 待接单发布成功后的初始状态。1 进行中跑腿者接单后订单进入履约状态。2 已完成用户确认后闭环。3 已取消发布人取消或后台取消。在业务层我们需要保证状态只能按合法路径流转例如“待接单 - 进行中 - 已完成”不能从“待接单”直接跳到“已完成”也不能让非接单人修改订单状态。状态字段的约束既可以通过后端代码控制也可以通过数据库触发器等兜底但在课程设计阶段优先在 Service 层实现状态校验。3. 微信小程序端开发3.1 小程序项目结构小程序端采用原生框架建议按照页面和公共模块拆分目录miniprogram/ ├── app.js // 小程序入口处理全局登录态 ├── app.json // 全局配置 ├── app.wxss // 全局样式 ├── utils/ │ └── request.js // 封装 wx.request ├── pages/ │ ├── index/ // 首页/订单大厅 │ ├── publish/ // 发布订单 │ ├── order/ // 订单详情 │ └── my/ // 个人中心 └── components/ // 公共组件3.2 微信登录与用户身份获取微信小程序登录推荐使用wx.login获取临时 code然后把 code 发送到后端由后端调用微信接口换取openid和session_key。不建议在开发者工具中调试时直接模拟 getUserProfile 来跳过登录因为真机上线后登录凭证必须走正规流程。小程序端登录核心代码// 文件路径utils/auth.js function login() { return new Promise((resolve, reject) { wx.login({ success(res) { if (res.code) { wx.request({ url: https://你的域名/api/user/login, method: POST, data: { code: res.code }, success(loginRes) { const data loginRes.data || {}; if (data.code 0 data.data.token) { wx.setStorageSync(token, data.data.token); wx.setStorageSync(userInfo, data.data.userInfo); resolve(data.data); } else { reject(new Error(data.msg || 登录失败)); } }, fail: reject }); } else { reject(new Error(wx.login 获取 code 失败)); } }, fail: reject }); }); } module.exports { login };后端收到 code 后需要调用微信接口GET https://api.weixin.qq.com/sns/jscode2session ?appidAPPID secretSECRET js_codeCODE grant_typeauthorization_code微信返回结构{ openid: oXXXX, session_key: tXXXX, unionid: uXXXX, errcode: 0, errmsg: }后端拿到openid后查询用户表如果不存在则自动注册。注意appid 和 secret 只能保存在后端不能出现在小程序代码中否则会泄露密钥。3.3 首页订单列表首页展示待接单的订单大厅核心页面是pages/index/index.js。分页加载是必须的否则订单量增长后页面会卡顿。下面给出列表请求的核心逻辑// 文件路径pages/index/index.js Page({ data: { orderList: [], page: 1, pageSize: 10, hasMore: true, loading: false }, onLoad() { this.loadOrders(); }, async loadOrders() { if (this.data.loading || !this.data.hasMore) return; this.setData({ loading: true }); const token wx.getStorageSync(token); const res await new Promise((resolve) { wx.request({ url: https://你的域名/api/order/list, method: GET, header: { Authorization: Bearer token }, data: { page: this.data.page, pageSize: this.data.pageSize, status: 0 }, success: resolve, fail: () resolve({ data: { code: -1 } }) }); }); const data res.data; if (data.code 0) { const list data.data.list || []; this.setData({ orderList: this.data.orderList.concat(list), page: this.data.page 1, hasMore: list.length this.data.pageSize, loading: false }); } else { this.setData({ loading: false }); wx.showToast({ title: data.msg || 加载失败, icon: none }); } }, onReachBottom() { this.loadOrders(); } });onReachBottom是页面滚动到底部的回调配合hasMore实现简单的分页避免一次性加载过多数据。3.4 发布订单页面发布订单页面需要收集订单类型、标题、详细描述、取件地址、送达地址和跑腿费。表单校验不能只在小程序端做后端也要做二次校验否则接口被直接调用时可以绕过前端规则。// 文件路径pages/publish/publish.js Page({ data: { typeList: [], form: { typeId: 1, title: , description: , pickupAddress: , deliveryAddress: , reward: } }, onLoad() { this.loadTypes(); }, loadTypes() { const token wx.getStorageSync(token); wx.request({ url: https://你的域名/api/order/typeList, header: { Authorization: Bearer token }, success: (res) { if (res.data.code 0) { this.setData({ typeList: res.data.data || [] }); } } }); }, handleSubmit() { const form this.data.form; if (!form.title.trim()) { wx.showToast({ title: 请填写标题, icon: none }); return; } if (!form.pickupAddress.trim() || !form.deliveryAddress.trim()) { wx.showToast({ title: 请完善取件和送达地址, icon: none }); return; } const token wx.getStorageSync(token); wx.request({ url: https://你的域名/api/order/publish, method: POST, header: { Authorization: Bearer token, Content-Type: application/json }, data: form, success: (res) { if (res.data.code 0) { wx.showToast({ title: 发布成功, icon: success }); setTimeout(() { wx.switchTab({ url: /pages/index/index }); }, 1500); } else { wx.showToast({ title: res.data.msg || 发布失败, icon: none }); } } }); } });此处wx.switchTab是因为订单大厅通常配置在 TabBar 中普通页面间跳转则使用wx.navigateTo两者不能混用。4. 后端接口设计与实现4.1 后端项目结构后端采用 Spring Boot MyBatis目录结构如下src/main/java/com/example/running/ ├── RunningApplication.java ├── controller/ │ ├── UserController.java │ └── OrderController.java ├── service/ │ ├── UserService.java │ └── OrderService.java ├── mapper/ │ ├── UserMapper.java │ └── OrderMapper.java ├── entity/ │ ├── User.java │ └── Order.java ├── common/ │ ├── Result.java │ └── TokenUtils.java └── config/ └── WebConfig.java4.2 统一返回结果前后端分离接口需要统一的返回格式便于小程序端统一处理错误码。一般定义如下public class ResultT { private int code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(0); result.setMsg(success); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT result new Result(); result.setCode(500); result.setMsg(msg); return result; } // getter / setter 省略 }4.3 用户登录接口后端登录接口需要调用微信 API同时负责用户注册逻辑。核心接口RestController RequestMapping(/api/user) public class UserController { Resource private UserService userService; PostMapping(/login) public ResultMapString, Object login(RequestBody LoginRequest request) { return userService.login(request.getCode()); } }Service 层核心逻辑public ResultMapString, Object login(String code) { // 1. 调用微信接口获取 openid String openid wxService.getOpenid(code); if (openid null) { return Result.error(登录失败); } // 2. 查询用户是否存在不存在则注册 User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户); user.setRole(1); userMapper.insert(user); } // 3. 生成自定义 token String token TokenUtils.generateToken(String.valueOf(user.getId())); MapString, Object data new HashMap(); data.put(token, token); data.put(userInfo, user); return Result.success(data); }这里的 Token 可以使用 JWT也可以使用随机 UUID 配合 Redis 保存。毕业设计如果不想引入 Redis可以简单生成 UUID 后存数据库或用 JWT 自包含用户信息。推荐使用 JWT因为它无状态便于横向扩展。拦截器统一校验 Tokenpublic class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } Integer userId TokenUtils.parseToken(token); if (userId null) { response.setStatus(401); return false; } request.setAttribute(userId, userId); return true; } }4.4 发布订单接口发布订单需要处理的核心业务逻辑参数校验标题、地址必填。生成唯一订单号。保存订单初始状态为“待接单”。public ResultString publish(Integer userId, Order order) { // 参数校验 if (order.getTitle() null || order.getTitle().trim().isEmpty()) { return Result.error(订单标题不能为空); } if (order.getPickupAddress() null || order.getDeliveryAddress() null) { return Result.error(取件地址和送达地址不能为空); } order.setUserId(userId); order.setOrderNo(generateOrderNo()); order.setStatus(0); order.setCreateTime(new Date()); orderMapper.insert(order); return Result.success(order.getOrderNo()); }generateOrderNo()的生成规则可以选择“日期 随机数”private String generateOrderNo() { return RT System.currentTimeMillis() RandomUtil.randomNumbers(4); }4.5 接单与完成接口接单接口是整个系统中的重点必须防止并发情况下多个人同时抢到同一订单因此需要使用乐观锁或行级锁保证并发安全。示例采用 SQL 条件更新public ResultString accept(Integer userId, Long orderId) { Order order orderMapper.selectById(orderId); if (order null || order.getStatus() ! 0) { return Result.error(订单不存在或已被接走); } if (order.getUserId().equals(userId)) { return Result.error(不能接自己发布的订单); } int rows orderMapper.updateStatusWithLock(orderId, userId, 0, 1); if (rows 0) { return Result.error(订单已被其他人接走); } return Result.success(接单成功); }对应 Mapper 中的条件更新update idupdateStatusWithLock UPDATE orders SET status #{newStatus}, accept_id #{userId}, accept_time NOW() WHERE id #{orderId} AND status #{expectedStatus} /update这里的核心思想是CASCompare And Swap更新时不仅设置新状态还带上期望状态数据库执行 UPDATE 时会自动加行锁从而避免并发覆盖。完成订单接口同理public ResultString complete(Integer userId, Long orderId) { Order order orderMapper.selectById(orderId); if (order null || order.getStatus() ! 1) { return Result.error(订单状态异常); } // 只有接单人本人可以标记完成 if (!order.getAcceptId().equals(userId)) { return Result.error(无权操作该订单); } int rows orderMapper.updateStatus(orderId, 1, 2); if (rows 0) { return Result.error(操作失败); } return Result.success(订单完成); }在实际毕设项目中还可以扩展支付模块用户发布订单时先扣减余额或虚拟币接单人完成后结算跑腿费。由于涉及资金不建议在课设阶段直接接入真实微信支付可以用“模拟金额”或“积分”方案替代。5. 运行与验证5.1 环境准备开发前需要准备微信开发者工具稳定版即可。微信小程序 AppID测试账号可用测试号。JDK 1.8。Maven 3.6。MySQL 5.7。后端 IDEIDEA 或 Eclipse。版本说明JDK、Spring Boot、MySQL 版本需要根据你本机环境调整本文示例以 Spring Boot 2.x JDK 1.8 为常见环境如果你使用 JDK 17 或 Spring Boot 3.x部分依赖包名与配置会有差异需要按实际情况修改。5.2 后端配置在application.yml中配置数据库与微信参数server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/campus_run?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver wx: appid: 你的小程序appid secret: 你的小程序secret其中wx.appid和wx.secret在微信公众平台的小程序后台获取。注意不要提交到公开仓库生产环境建议放到配置中心或环境变量中。5.3 启动与联调后端启动后在微信开发者工具中导入小程序项目将utils/request.js中的接口地址改成http://localhost:8080。这里要注意开发工具中需要关闭“合法域名校验”真机预览时则必须使用https域名。// 文件路径utils/request.js const BASE_URL http://localhost:8080; function request(url, method GET, data {}) { const token wx.getStorageSync(token); return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, Authorization: Bearer token }, success: resolve, fail: reject }); }); } module.exports { request };推荐先跑通“登录 - 发布订单 - 列表展示 - 接单 - 完成”这条主链路再逐步完善个人中心和管理员功能。6. 常见问题与排查思路问题现象常见原因解决思路登录失败报错getUserProfile:fail can only be invoked by user TAP gesture登录按钮在onLoad或onShow中直接调用必须由用户点击事件触发不能在小程序启动时自动调用真机测试报错failed net::ERR_CONNECTION_RESET请求地址不是合法域名 / 未配置 request 合法域名在公众平台配置 HTTPS 合法域名或开发阶段勾选“不校验合法域名”小程序获取登录后的微信用户失败code需要在有效期内使用通常 5 分钟且只能使用一次检查后端 jscode2session 调用是否正确code 是否被重复使用在 HBuilderX 中修改小程序 id 无效项目配置与 manifest 配置不同步或打开的是旧项目缓存在manifest.json的微信小程序配置项中重新填写 appid并重新编译请求返回 401Token 缺失或过期检查请求头 Authorization 是否携带后端拦截器是否放行登录接口订单发布成功但列表不展示状态筛选条件错误或订单列表只查询了 status0检查列表接口的状态参数以及发布后是否跳转到订单大厅刷新多人同时接单订单被重复接走缺少状态更新时的并发控制使用 UPDATE ... WHERE status0 条件更新避免先查询再更新6.1 开发工具调试注意事项开发者工具中可以选择“不使用合法域名”这样本地开发可以直接请求http://localhost:8080。但真机预览时微信要求所有网络请求必须为 HTTPS 且配置到小程序后台的“request 合法域名”中。如果只是在校园局域网内测试可以暂时开启“不校验合法域名”开关但不能作为正式上线方案。上线前需要将后端部署到带 HTTPS 证书的服务器。6.2 登录态失效Token 过期、用户删掉小程序重新打开、微信 session_key 过期都可能导致登录态失效。小程序端应在全局封装“登录后重试”逻辑// 简化思路请求返回 401 时自动重新登录并重发原请求 function requestWithAuth() { // 先检查本地 token // 不存在则先 login()再执行 request // 返回 401 则清除 token重新 login 后再次请求 }这个逻辑建议在utils/request.js中统一封装避免每个页面重复写。7. 最佳实践与工程建议7.1 小程序端建议请求统一封装不要在页面中散落大量wx.request统一走request.js方便做 Token 注入、错误处理和加载状态管理。页面传参谨慎订单列表到详情页传orderId不要让前端直接传递整个订单对象避免数据不一致或占用缓存。分包加载如果页面数量增多可以启用微信小程序分包降低首屏加载耗时。列表使用虚拟滚动或分页不要一次渲染全部订单分页是底线。7.2 后端建议接口鉴权不能只在业务方法里手动判断统一使用拦截器或 Spring Security对/api/**下的接口做 Token 校验排除登录接口。数据库字段使用逻辑删除订单删除时建议采用deleted字段标记避免硬删除导致统计失真。订单状态流转放在同一个事务里涉及“接单”“完成”这种多表更新时保证事务一致性。金额计算避免浮点误差跑腿费使用Decimal类型不要用float/double存储金额。敏感配置不要写死在代码里微信 secret、数据库密码通过环境变量或配置中心管理。日志规范订单操作必须保留日志比如谁在什么时间修改了订单状态方便纠纷回溯。7.3 并发安全抢单场景是跑腿系统的核心并发问题。多次点击“接单”按钮时后端接口可能被同时调用因此必须用条件 UPDATE 防止重复接单。不要使用“先 select 再 update”的方式因为并发下可能两个请求都查到 status0然后都更新成功造成一单多接。7.4 安全边界用户只能查看和操作自己的订单。用户不能接自己发布的订单。订单状态变更必须校验当前状态。后端接口必须做参数校验避免 SQL 注入和越权。8. 后续扩展方向毕业设计如果只做到“发单/接单/完成”已经满足基本功能要求但为了答辩展示和功能完整度可以继续扩展消息通知订单被接单后通过订阅消息通知发布人。信用评价订单完成后双方互评记录用户信用分。余额充值模拟充值跑腿费发布订单时先冻结该笔费用完成后结算给接单者。管理员后台用 Vue Element UI 或小程序管理端实现订单统计、用户管理。LBS 定位基于微信小程序wx.getLocation获取当前位置按距离排序订单。地图选点发布订单时使用腾讯地图插件选择取件地址和送达地址前端展示配送路径。这些扩展点都可以作为论文中的“系统创新点”或“后续展望”同时也能让系统从“演示 Demo”走向“可体验原型”。核心开发思路其实不复杂先跑通登录链路再实现订单主流程最后补充管理功能和细节优化。希望这篇教程能帮你快速搭起一个能运行、能讲解、能答辩的完整项目。如果后续遇到具体报错也可以按照“错误现象 - 接口层日志 - 数据层日志 - 微信开发者工具 Network 面板”的顺序逐步定位多数问题都能在半小时内排查清楚。