公司动态

基于Spring Boot、微信小程序与AI的智能快递代收系统设计

📅 2026/9/2 1:18:35
基于Spring Boot、微信小程序与AI的智能快递代收系统设计
智能快递代收系统是近两年计算机毕业设计里出现频率很高的选题也是把 Spring Boot、微信小程序和 AI 大模型三者串起来的一类典型项目。校园和社区里常见的痛点很明确快递员投递时收件人不在家包裹放在代收点后如果没有统一登记用户取件时很难快速找到对应包裹时间久了还容易出现滞留和异常。这个系统的核心价值是把“快递员入库、自动生成取件码、用户查件扫码取件、异常包裹提醒”这条链路用代码完整实现。下面以一套最小可运行的智能快递代收系统为例讲清楚需求建模、表结构设计、后端接口、小程序端、AI 大模型接入和后续排查方法。如果你正在做毕业设计或者想找一个前后端加 AI 的综合项目练手可以按这条主线走一遍。1. 先拆业务快递代收系统要管理的是包裹状态很多人拿到这类题目后第一反应是先做页面。其实这类管理系统的核心问题是数据模型尤其是一张包裹表的状态变化。页面只是状态的展示入口接口只是状态的流转动作。1.1 一条包裹从入库到取件经历了哪些状态快递代收系统的主线不是用户也不是订单而是包裹。包裹从快递员送到代收点到用户取走中间会经历多个状态。在常见实现里至少需要设计下面几个状态。状态业务含义触发动作责任人待入库快递员已经登记运单号但还未放到货架上创建包裹记录快递员/代收员已代收包裹已经放到货架生成取件码用户可查询确认入库代收点已取件用户输入取件码或扫码完成签收用户取件用户逾期未取到达预设时间后仍未取件定时任务扫描系统异常包裹破损、错放、滞留、用户投诉待处理人工或 AI 判断管理员设计状态时要注意不要把业务动作直接写成字符串到处比较。比如在 Java 中使用枚举在数据库中使用TINYINT或VARCHAR编码并统一维护状态码含义。这样可以避免页面显示、接口判定和数据库索引三者各写一套规则。1.2 用户、快递员和管理员分别需要哪些功能一个最小可用的智能快递代收系统角色至少要拆成三类用户、快递员/代收员、系统管理员。很多毕设项目只做用户端和管理后台其实还要考虑快递员入库这个关键入口。用户端微信授权登录、查看我的包裹、查看取件码、扫码取件、收藏异常问题、使用 AI 助手咨询。快递员/代收员登记包裹、分配货架号、生成取件码、修改异常状态。系统管理员用户管理、包裹状态管理、通知日志查看、AI 对话日志查看、基础数据统计。这里有一个容易被忽略的点快递员入库操作往往不是高频操作但它是整个系统的数据源头。如果入库环节设计得繁琐快递员不愿用后续用户端做得再好也没有数据。所以入库接口要尽量简单只提交运单号、收件人手机号、货架号即可其余信息由后端自动补充。1.3 数据表按状态和时间轴设计而不是按页面设计在设计 MySQL 表时不要围着页面转而要考虑一条包裹记录从创建到归档的完整生命周期。下面这张parcel表是核心表可以先用最小字段验证流程。CREATE TABLE parcel ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 收件用户ID, courier_id BIGINT DEFAULT NULL COMMENT 快递员/代收员ID, tracking_no VARCHAR(64) NOT NULL COMMENT 运单号, pickup_code VARCHAR(10) NOT NULL COMMENT 取件码, shelf_no VARCHAR(32) DEFAULT NULL COMMENT 货架号, recipient_name VARCHAR(64) DEFAULT NULL COMMENT 收件人姓名, recipient_phone VARCHAR(20) DEFAULT NULL COMMENT 收件人手机号, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待入库 1已代收 2已取件 3逾期 4异常, in_time DATETIME DEFAULT NULL COMMENT 入库时间, out_time DATETIME DEFAULT NULL COMMENT 取件时间, expired_at DATETIME DEFAULT NULL COMMENT 逾期时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_tracking_no (tracking_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT快递包裹表;取件码建议使用 6 位数字入库时随机生成。tracking_no要加唯一索引否则同一个运单号可能会被重复入库两次导致用户看到两条相同包裹记录。除了包裹表还需要user用户表、chat_ai_logAI 对话日志表、notify_log通知日志表。通知日志建议单独建表因为微信订阅消息是否发送成功、失败原因是什么都需要留痕否则答辩演示时很难解释“这个通知到底发出去没有”。2. 技术选型和项目初始化版本一致才能避免后面返工这类系统通常会选择 Spring Boot 做后端、微信小程序做用户端、MySQL 做数据库AI 部分通过大模型 API 接入。选型本身不难真正容易出问题的是版本不一致。2.1 这套系统的技术栈怎么搭模块推荐方案说明后端框架Spring Boot 2.7.x 或 3.x二选一不能混用ORMMyBatis-Plus 或 Spring Data JPA毕业设计用 MyBatis-Plus 更直观数据库MySQL 5.7 或 8.0注意 utf8mb4 字符集缓存Redis 可选用于取件码短时校验或登录态不加也不影响最小闭环小程序端原生微信小程序或 uni-app原生最简单uni-app 适合跨端AI 接入大模型 HTTP API服务端转发不要把密钥放小程序端如果是第一次做完整项目建议后端使用 Spring Boot 2.7.x。原因不是 3.x 不好而是网上的教程、毕设代码、MyBatis-Plus 插件兼容性大多围绕 2.x 讲解。Spring Boot 3.x 已经进入 Jakarta EE 命名空间很多从旧项目复制过来的代码javax.servlet这类包会直接报编译错误。2.2 Spring Boot 版本太高是最常见的返工原因经常能看到有人遇到问题后搜索“springboot版本太高”。这通常不是单一报错而是一连串版本连锁反应。比如新建项目时默认选了 Spring Boot 3.2.x但参考代码里写的是javax.annotation.Resource或者是为 JDK 8 配置的依赖启动时就会报ClassNotFoundException或NoClassDefFoundError。处理这类问题有两个方向。第一统一降低版本。如果项目以毕业设计为目标建议使用 Spring Boot 2.7.18搭配 JDK 8 或 11依赖兼容性最稳妥。第二坚持使用 Spring Boot 3.x但要把包名从javax改成jakarta并确保 JDK 是 17 以上MyBatis-Plus 使用适配 3.x 的版本连接池、Redis、JWT 工具类也要全部检查一遍。实际项目里最怕的不是版本高而是代码在“2.x 思维”和“3.x 环境”之间混着写。建议一开始就确定版本然后统一在pom.xml里固定。2.3 后端项目目录结构后端项目建议按功能分包而不是按技术类型分包。下面是一个适合毕设的目录结构。express-center/ ├── src/main/java/com/example/express │ ├── config # 跨域、拦截器、MyBatis-Plus 配置 │ ├── controller # 接口层 │ ├── service # 业务层 │ ├── mapper # 数据访问层 │ ├── entity # 实体类 │ ├── dto # 入参出参对象 │ ├── common # 统一返回结果、异常处理、JWT 工具 │ └── ExpressCenterApplication.java ├── src/main/resources │ ├── application.yml │ ├── application-dev.yml │ └── mapper ├── sql │ └── init.sql └── pom.xml这样的分层可以让答辩时讲得更清楚Controller 负责参数接收Service 负责业务流转Mapper 负责数据库操作Config 负责跨域和拦截器。2.4 pom.xml 关键依赖如果使用 Spring Boot 2.7.x依赖可以这样写重点是版本保持一致。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.12.0/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesokhttp用于后端调用微信接口和大模型接口。如果使用 JDK 8 配合 MyBatis-Plus尽量选择对应版本不要盲目升级到最新版。3. 后端实现把登录、入库、取件和 AI 助手串起来后端接口不用做太多先把最小闭环跑通。最小闭环可以分成四条链路登录链路、入库链路、取件链路、AI 咨询链路。3.1 小程序登录用 code 换 openid再签发 JWT微信小程序登录流程中前端调用wx.login()拿到临时code后端再用这个code调用微信服务端接口换取openid。openid是识别用户的唯一标识不能依赖前端传入。RestController RequestMapping(/api/auth) public class AuthController { private final WxService wxService; private final UserService userService; public AuthController(WxService wxService, UserService userService) { this.wxService wxService; this.userService userService; } PostMapping(/login) public ResultLoginVO login(RequestBody LoginDTO dto) { // 1. 用 wx.login 的 code 换取 openid String openid wxService.code2Session(dto.getCode()); // 2. 第一次登录则注册否则直接登录 User user userService.findOrCreate(openid, dto.getNickname()); // 3. 签发 JWT后续接口通过 token 识别用户 String token JwtUtil.createToken(user.getId(), user.getRole()); return Result.ok(new LoginVO(token, user)); } }在WxService中核心逻辑是调用下面的微信接口。GET https://api.weixin.qq.com/sns/jscode2session ?appidAPPID secretSECRET js_codeCODE grant_typeauthorization_code一个重要原则secret只能保存在后端不能出现在小程序代码里。如果哪天发现源码提交到 GitHub 后登录接口大量报错大概率是secret泄露后被人恶意调用。正式项目建议把secret放到配置中心或环境变量中而不是直接写在源码里。3.2 快递入库生成取件码、保存货架号、触发通知入库时后端应该完成三件事保存包裹信息、生成唯一取件码、计算逾期时间。取件码的随机性很重要不能用固定规则或者连续数字。public String generatePickupCode() { SecureRandom random new SecureRandom(); int code 100000 random.nextInt(900000); return String.valueOf(code); }生成后要检查是否和已有取件码冲突。最稳妥的做法是在数据库加唯一索引插入失败后重新生成。如果同一时刻入库量不大只做应用层查重也可以接受。入库的核心逻辑可以写在ParcelService中。Transactional public Parcel createParcel(CreateParcelDTO dto) { Parcel parcel new Parcel(); parcel.setTrackingNo(dto.getTrackingNo()); parcel.setShelfNo(dto.getShelfNo()); parcel.setRecipientName(dto.getRecipientName()); parcel.setRecipientPhone(dto.getRecipientPhone()); parcel.setStatus(ParcelStatus.STORED.getCode()); parcel.setPickupCode(pickupCodeService.generateUnique()); parcel.setInTime(LocalDateTime.now()); parcel.setExpiredAt(LocalDateTime.now().plusDays(3)); parcelMapper.insert(parcel); // 发送微信订阅消息 notifyService.sendPickupMessage(parcel); return parcel; }注意Transactional的使用。如果发送订阅消息失败不能让包裹数据被回滚否则快递员入库会一直失败。所以在生产实现里更合理的做法是发送通知放到事务提交后执行或者记录通知日志后异步处理。毕业设计里可以先同步发送但答辩时如果能说出“通知失败不影响包裹保存”这个点会显得更有工程经验。3.3 取件接口校验取件码或扫码结果取件接口是安全要求最高的接口。用户输入取件码、或者扫描包裹码后后端必须校验取件码是否匹配并且当前状态必须是已代收。如果状态是已取件要提示“该包裹已取走”。PostMapping(/api/parcel/pickup) public ResultVoid pickup(RequestBody PickupDTO dto) { if (!StringUtils.hasText(dto.getPickupCode())) { return Result.fail(取件码不能为空); } Parcel parcel parcelMapper.selectByPickupCode(dto.getPickupCode()); if (parcel null) { return Result.fail(取件码不存在); } if (parcel.getStatus() ParcelStatus.PICKED_UP.getCode()) { return Result.fail(该包裹已取件请勿重复操作); } if (parcel.getStatus() ! ParcelStatus.STORED.getCode()) { return Result.fail(当前包裹状态不可取件); } parcel.setStatus(ParcelStatus.PICKED_UP.getCode()); parcel.setOutTime(LocalDateTime.now()); parcelMapper.updateById(parcel); return Result.ok(); }这里可以考虑加一层用户校验取件接口必须要求登录并且取件码对应的包裹属于当前用户。如果只凭取件码就能取件那别人拿到取件码也能把包裹取走系统就没有归属校验了。3.4 AI 大模型接入提示词要输出结构化 JSONAI 大模型在这个系统里可以承担几个角色智能客服、包裹异常判断、用户问题分类。不要一上来就让它自由发挥应该通过提示词约束输出格式这样才能在后端可靠解析。举个智能客服的例子。private String buildPrompt(String question, Parcel parcel) { return 你是一个快递代收点的智能客服。请根据用户问题和包裹信息输出 JSON 对象\n {\category\:\查件|投诉|修改取件时间|其他\,\need_manual\:true,\reply\:\回复用户的话\}\n 用户问题 question \n 包裹状态 (parcel null ? 未知 : parcel.getStatus()) \n 注意reply 要简短友好不要暴露内部字段。; }后端调用大模型时使用 OkHttp 发送 HTTP POST 请求请求体结构可以按 OpenAI 兼容接口来写。{ model: gpt-3.5-turbo, messages: [ {role: system, content: 你是快递代收点智能客服}, {role: user, content: 我的包裹显示已代收但我去取的时候没找到怎么办} ], temperature: 0.3 }解析返回结果时不要直接把文本返回给用户要提取JSON中的reply字段同时把need_manual为true的会话标记为人工处理。这样可以给管理员一个待处理列表也让 AI 助手不只是一个聊天玩具。3.5 微信订阅消息一次授权只能推一次取件通知这个场景很多同学会写成“用户入库后直接收到通知”。但在微信小程序里并没有这么自由。现在主流的方案是订阅消息而且用户授权一次只能推送一次。小程序端在用户确认取件通知时调用wx.requestSubscribeMessage后端在包裹入库后调用subscribeMessage.send发送模板消息。你需要在小程序管理后台申请快递服务通知的模板拿到模板 ID 后配置到后端。如果用户没有授权后端推送时微信会返回43101等错误码。演示时不要把全部希望放在订阅消息上建议前端同时维护一个“站内通知列表”这样即使订阅消息授权失败用户也能在小程序里看到通知记录。4. 微信小程序端从 wx.login 到扫码取件和 AI 对话小程序端是用户直接操作的入口。这一层不需要写太多复杂逻辑重点是处理好登录态和请求封装。4.1 小程序页面结构最小页面可以分为四个首页、包裹列表、扫码取件、AI 咨询。miniprogram/ ├── app.js ├── app.json ├── app.wxss └── pages ├── index # 首页展示用户信息和待取包裹数量 ├── parcel # 包裹列表与取件码 ├── scan # 扫码取件页 └── chat # AI 智能咨询页如果项目有管理端也可以把管理员页面放到单独的小程序或后台管理网页里。但最小闭环先不做管理端用数据库和接口文档代替即可。4.2 baseUrl 与请求封装小程序开发环境中后端接口可能是http://localhost:8080但真机调试时localhost指向手机本身不是电脑。所以建议把baseUrl单独抽出来放在一个配置文件中。const BASE_URL http://192.168.1.100:8080; function request(path, method GET, data {}) { const token wx.getStorageSync(token); return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${path}, method, data, header: { Content-Type: application/json, Authorization: Bearer ${token} }, success: (res) resolve(res.data), fail: reject }); }); } module.exports { request, BASE_URL };这里要注意开发工具中可以勾选“不校验合法域名”但真机预览和上线都必须配置合法域名而且必须是 HTTPS。演示前如果时间紧张