简介:这份毕业设计资源以“溢香园餐饮管理系统”为主题,完整覆盖Web管理端与Android客户端,适合计算机相关专业学生作为毕设参考、课程设计或二次开发底稿。系统围绕用户管理、菜品管理、订单流转、库存监控、报表分析与评价评分等模块展开,涉及前后端分离架构、RESTful API设计、数据库建模、Android网络通信与本地缓存等关键技术点;Web端可关注Spring Boot/Node.js等接口层实现,Android端涵盖Retrofit、OkHttp、SQLite及推送通知等常见集成方式。压缩包共960个文件、约77MB,以Java源码、JSP页面、XML配置、JS/CSS前端样式和Android界面图片为主,也包含SQL脚本、PDF与Word说明文档,目录结构清晰便于分模块查阅。目前已有125人学习下载,适合希望快速搭建餐饮管理原型、借鉴完整项目结构、深入理解全栈开发流程并用于毕业设计答辩展示的读者。
1. 溢香园餐饮管理系统:H-ui + MUI + Gradle 的餐饮管理全栈方案
拿到一份毕业设计压缩包,解压后一半是浏览器CSS框架文件,一半是Android构建脚本,这种"Web端+Android端"双端毕设项目的典型形态。它不炫技,但覆盖了餐饮门店最刚需的三件事:点单结账、菜品维护、订单状态跟踪。这个系统定位在餐馆、咖啡厅这类堂食场景,前台用Android平板模拟收银终端,后厨和店长在Web端看订单进度并维护菜品。技术选型走的是低成本快速成型路线:Web端用H-ui这个基于Bootstrap 3的前端框架把后台界面搭起来,Android端用Gradle管理依赖,两端共用一套RESTful API交换数据。
适合两类人来复现:一是计算机专业做毕设,需要一套能讲清楚"数据从前台到后厨是怎么跑"的完整案例;二是想给门店快速搭一个轻量管理后台、不打算上重型ERP系统的开发者。这个项目价值不在框架多新,而在于订单、菜品、库存、用户四个业务域串成了闭环,改一改就能落到自己的课程设计里。
2. 拆解项目骨架:从静态资源反推技术栈、数据库设计与API规范
拿到一份只有文件名列表的代码包,最忌讳的就是直接双击运行。我先按文件名把技术栈画出来,心里有谱了再动手。这一章从资源文件清单出发,告诉你怎么快速判断一个未知毕设项目的技术构成,以及系统背后的数据模型和接口约定。
2.1 资源文件清单里的前端框架线索
项目正文只给了十几个文件名,信息量已经很大。逐个拆开看:
| 文件 | 作用 | 技术线索 |
|---|---|---|
| gradlew.bat | Gradle Wrapper执行脚本 | Android端使用Gradle构建 |
| H-ui.css / H-ui.min.css | H-ui框架核心样式 | Web端采用H-ui后台管理模板 |
| bootstrap.min.css | Bootstrap 3样式库 | H-ui依赖Bootstrap 3 |
| style.css | 自定义样式覆盖层 | 项目定制化主题 |
| styletree.css | 树形菜单样式 | 后台管理左侧树结构 |
| font-awesome.css | Font Awesome图标库 | 界面图标方案 |
| swiper3.07.min.css | Swiper 3.07轮播组件 | 首页或Banner轮播 |
| mui.css / mui.min.css | MUI移动端UI框架 | Android端或H5页面组件 |
从这张表能读出的第一层信息:Web端不是从零手写的,它站在三个成熟组件上——H-ui提供后台布局和表格、按钮等基础样式,Bootstrap 3负责栅格系统,Font Awesome统一图标风格。这种组合在近年来的国内毕设后台项目里非常常见,因为H-ui的文档明确写了"基于Bootstrap 3,兼容IE8",用它搭后台几乎不用自己写CSS。
第二层信息藏在版本号里。swiper3.07.min.css暴露了Swiper的3.x时代版本,这个版本用jQuery插件方式初始化,跟5.x以后的原生ES模块写法完全不同。如果项目里出现轮播图,初始化语句大概率是$(".swiper-container").swiper({...}),而不是new Swiper(".swiper-container", {...})——你只有先对上了版本,才能判断一个报错是插件问题还是自己的写法问题。
mui.css的存在要分情况看:一种是MUI这个移动端UI框架(DCloud出品),配套的mui.js提供接近原生App的列表和卡片组件;另一种是在Web端H-ui后台里作为辅助被引入。判断标准是看Android端有没有用WebView加载HTML页面——如果用了,mui.css多半在给WebView页面提供样式,它与Android原生UI组件库(如Material Design)是两条平行线。
Gradle方面,gradlew.bat是Gradle Wrapper的Windows批处理入口,它存在的意义是锁版本。只要项目里有gradle/wrapper/gradle-wrapper.properties,构建时就会自动下载该文件里通过distributionUrl指定的Gradle版本,不会因为你本地装了更好、更新的版本就翻车。第一次执行gradlew时要保证网络可用,因为它会按distributionUrl指向的地址拉取Gradle发行版。
提示:拿到压缩包后先做一次"静态体检",把所有css、js、bat文件列成表格,比直接读代码快得多,几分钟就能判断出项目用的框架组合和大致的前端代码风格。
2.2 数据库表设计与订单状态机
餐饮管理系统绕不开四个核心业务域:用户、菜品、订单、库存。毕设级别的表设计不追求范式完美,但状态流转必须自洽。常见做法是按下面这组核心表来还原这个系统:
- user表:id、username、password_hash、role(admin/waiter/customer)、nickname、phone、created_at。role是权限判断的唯一依据,Web端和Android端登录后拿到的JWT里必须包含它。
- dishes表:id、name、category_id、price、image_url、description、is_selling、stock。is_selling和stock配合完成"售罄置灰"的前端逻辑:is_selling管是否展示,stock管是否可下单。
- orders表:id、order_no、user_id、table_no、status、total_amount、remark、created_at、updated_at。status字段驱动整条业务线。
- order_items表:id、order_id、dishes_id、count、price。一单多菜,用关联表拆开,不能把多个菜名拼进一个字段。
- category表:id、name、sort。菜品分类在Android端点单页上展示为Tab。
- stock_log表:id、dishes_id、change、operate_type、created_at。每次库存变动写一条日志,方便对账。
订单状态机是整个系统里最容易被讲清楚、也最容易被答辩老师追问的环节。常见做法是六态流转:待支付 → 已支付/待接单 → 已接单 → 制作中 → 已出餐 → 已完成。终端用户可以主动取消的只有待支付状态,一旦进入已支付,取消操作必须走管理员后台审核,否则财务对不平。
用SQL把状态机落到表里,一般给status字段加TINYINT约束,并在应用层定义常量:
CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, table_no VARCHAR(16), status TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2已接单 3制作中 4已出餐 5已完成 6已取消', total_amount DECIMAL(10,2) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );表设计里有三个易错点。第一,total_amount必须用DECIMAL而不能用FLOAT,餐饮金额涉及对账,浮点误差在累计汇总时会变成对不上的黑匣子。第二,order_no建议由应用层生成而不是依赖数据库自增,格式取yyyyMMddHHmmss加3位随机数,导出报表后直接按订单号排序就是时间顺序。第三,status字段不建索引的话,后台订单列表页一旦数据超过一万行,按状态筛选会明显变慢——答辩时这是个可以主动讲的优化点。
2.3 RESTful API接口规范与鉴权方案
Web端和Android端共享同一套后端API,接口设计决定了联调是否顺利。按RESTful风格,核心端点大致如下:
| 方法 | 路径 | 功能 | 权限 |
|---|---|---|---|
| POST | /api/auth/login | 用户登录 | 公开 |
| POST | /api/auth/register | 用户注册 | 公开 |
| GET | /api/dishes | 获取菜品列表(支持category和keyword过滤) | 登录用户 |
| POST | /api/dishes | 新增菜品 | 管理员 |
| PUT | /api/dishes/{id} | 编辑菜品 | 管理员 |
| GET | /api/orders | 按状态拉取订单列表 | 登录用户 |
| POST | /api/orders | 提交新订单 | 登录用户 |
| PUT | /api/orders/{id}/status | 推进订单状态 | 登录用户 |
| DELETE | /api/orders/{id} | 取消订单(仅待支付) | 登录用户 |
鉴权方面,毕设项目用JWT是最稳妥的选择。流程是:登录成功后后端签发一个包含userId和role的JWT,客户端把它存在本地(Web端放localStorage,Android端放SharedPreferences),每次请求在Authorization: Bearer <token>头里带上。后端用一个拦截器统一校验,不通过就返回401,业务代码里不需要每个接口重复写身份判断。
在Android端用OkHttp做这件事时,通常会加一个拦截器自动附加token,而不是在每个请求方法里手动拼头:
val client = OkHttpClient.Builder() .addInterceptor { chain -> val token = prefs.getString("jwt_token", null) val request = if (token != null) { chain.request().newBuilder() .addHeader("Authorization", "Bearer $token") .build() } else { chain.request() } chain.proceed(request) } .build()这段拦截器的逻辑是:先从SharedPreferences里取token,取到就构建带Authorization头的新请求,取不到就放行原请求。价值在于把Token注入收敛到一个地方——如果每个接口自己拼Header,后面换成OAuth或刷新Token逻辑时得改几十处,而拦截器只需要改这一处。Android端目前主流网络库是Retrofit加OkHttp组合,Retrofit负责把接口方法声明转成HTTP请求,OkHttp负责底层连接和拦截器,上面这段代码就是OkHttp层最常见的用法。
到这里,技术栈、表结构、接口风格都在脑子里成形了。接下来进入实操环节,把Web端页面和核心业务模块真正跑起来。
3. 复现核心功能模块:Web端页面搭建、菜品管理与订单流转
上一章讲的是"看文件清单判断项目",这一章进入"复现一个能用的系统"。不把每个页面都铺开讲,只挑三个最核心、也最能体现毕设含金量的功能模块:Web端页面骨架、菜品管理与库存联动、订单状态流转。
3.1 H-ui页面骨架与静态资源加载顺序
H-ui框架的页面结构非常固定:顶部导航栏、左侧菜单树、中间内容区。一个标准的后台页面骨架长这样:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>溢香园餐饮管理系统-后台</title> <link rel="stylesheet" href="lib/bootstrap.min.css"> <link rel="stylesheet" href="lib/H-ui.min.css"> <link rel="stylesheet" href="lib/font-awesome.css"> <link rel="stylesheet" href="lib/styletree.css"> <link rel="stylesheet" href="lib/style.css"> </head> <body> <nav class="navbar navbar-default H-ui-nav"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="index.html">溢香园后台管理</a> </div> <div class="navbar-right H-ui-nav-user"> <span id="currentUser"></span> <a href="javascript:logout()">退出</a> </div> </div> </nav> <div class="H-ui-layout"> <aside class="H-ui-aside"> <div class="styletree" id="menuTree"> <ul> <li><a href="dishes.html"><i class="fa fa-cutlery"></i> 菜品管理</a></li> <li><a href="orders.html"><i class="fa fa-list-alt"></i> 订单管理</a></li> <li><a href="users.html"><i class="fa fa-users"></i> 用户管理</a></li> </ul> </div> </aside> <main class="H-ui-content"> <!-- 各功能页面渲染位置 --> </main> </div> <script src="lib/jquery.min.js"></script> <script src="lib/H-ui.min.js"></script> </body> </html>这里的加载顺序有讲究:bootstrap.min.css必须在H-ui.min.css之前,因为H-ui是在Bootstrap基础上做样式覆盖的,反过来会让H-ui的栅格和按钮样式失效大半。font-awesome.css放在H-ui后面是安全的,它只定义图标字体,不会跟H-ui的类名冲突。style.css是项目的自定义覆盖层,放在最后才能覆盖框架默认值,不用去改动框架源码。
HTML骨架把后台最典型的三个区域固定下来:navbar是顶部导航,H-ui-aside是左侧菜单,H-ui-content是内容容器。styletree.css专门管左侧树形菜单的缩进与展开箭头,对应的JS初始化一般长这样:
$(function() { // 初始化左侧树形菜单,icon参数指定树节点的展开/收起图标 $("#menuTree").Huitree({ icon: ["fa fa-folder", "fa fa-folder-open"], isOpen: true }); // 从localStorage取登录用户信息渲染到右上角 var user = JSON.parse(localStorage.getItem("yxy_user") || "{}"); if (user.nickname) { $("#currentUser").text("欢迎," + user.nickname); } });Huitree方法的两个参数值得注意:icon数组里第一项是收起状态的图标,第二项是展开状态的图标,传的必须是Font Awesome类名;isOpen控制树默认是否全部展开,菜单超过三层时建议设成false,不然页面加载时左侧一长串菜单影响观感。
3.2 菜品管理:图片上传、库存联动与前端交互
菜品管理页是后台使用频率最高的页面,操作集中在四个动作:新增、编辑、上下架、库存调整。为了不让操作打断流程,用弹窗(H-ui的layer组件)承载表单,列表区保持整页刷新,交互上更接近真实后台。
新增菜品的核心是表单校验、图片上传、JSON数据回填三个环节。图片上传通常先传到服务器指定目录,拿到返回的URL再拼进JSON提交,不能把图片的本地路径当image_url传给后端——Android端访问Web端接口时拿到相对路径会直接404。
function submitDish() { var formData = { name: $("#dishName").val().trim(), categoryId: parseInt($("#dishCategory").val()), price: parseFloat($("#dishPrice").val()).toFixed(2), imageUrl: $("#dishImageUrl").val(), description: $("#dishDesc").val().trim(), stock: parseInt($("#dishStock").val()) }; if (!formData.name || isNaN(formData.price) || formData.price <= 0) { layer.msg("菜品名称和价格必须填写且价格要大于0", {icon: 2}); return; } $.ajax({ url: "/api/dishes", type: "POST", contentType: "application/json", data: JSON.stringify(formData), headers: { "Authorization": "Bearer " + localStorage.getItem("yxy_token") }, success: function(res) { if (res.code === 0) { layer.msg("保存成功", {icon: 1}); loadDishList(); } else { layer.msg(res.message, {icon: 2}); } }, error: function(xhr) { layer.msg("请求失败,请检查网络或登录状态", {icon: 2}); } }); }代码里的几个细节都对应实践中的常见坑:price用parseFloat(...).toFixed(2)转成两位小数字符串,避免浮点运算在传输层被约简;contentType: "application/json"和JSON.stringify必须成对出现,漏掉stringify的话后端接到的会是[object Object];localStorage.getItem("yxy_token")从本地取JWT拼进Authorization头,与第2章Android拦截器做的事情完全对称。
库存联动是这个模块里最容易翻车的点。菜品展示时,如果stock <= 0,前端要把加购按钮置灰,同时标注已售罄;但后厨依然要在后台看到该菜品并补货。所以"是否展示"由is_selling字段控制,"是否可下单"由stock字段控制,两个字段绝不能合并成一个。页面初始化时调一次loadDishList接口,前端按库存状态渲染:
function loadDishList() { $.get("/api/dishes", function(res) { var items = res.data || []; $("#dishTbody").empty(); items.forEach(function(dish) { var isSoldOut = dish.stock <= 0; var row = "<tr>" + "<td>" + dish.name + "</td>" + "<td><img src='" + dish.imageUrl + "' width='50' height='50'></td>" + "<td>¥" + dish.price.toFixed(2) + "</td>" + "<td>" + dish.stock + "</td>" + "<td>" + (isSoldOut ? "<span class='label label-danger'>已售罄</span>" : "<span class='label label-success'>在售</span>") + "</td>" + "<td>" + (isSoldOut ? "<button class='btn btn-danger btn-sm' onclick='restock(" + dish.id + ")'>补货</button>" : "<button class='btn btn-primary btn-sm' disabled>售罄中</button>") + "</td>" + "</tr>"; $("#dishTbody").append(row); }); }); }这段代码把库存状态直接渲染成行内标签和按钮:售罄的菜品显示红色已售罄标签和补货按钮,在售的菜品显示绿色在售标签和禁用的灰色按钮。虽然把HTML直接拼进jQuery的append不算优雅,但毕设后台页面数据量不大,引入模板渲染库的成本反而更高。答辩时如果被问为什么不分开,可以答"菜品量级在百位以内,整页重渲染的性能损耗可以忽略"。
3.3 订单流转:从用户下单到出餐确认的状态推进
订单模块的核心不是页面,而是状态推进。前端展示订单列表时用Tab按状态分组,点击接单按钮调用状态机对应的推进接口。整个流转必须依赖后端校验,不能只靠前端改一个数字就完事。
后端接收推进状态请求时,要做两件事:校验当前状态是否允许跳转到目标状态、校验操作者有没有对应权限。用一个简单的状态映射表能挡住大部分非法操作:
# 订单状态机的合法流转表,key为当前状态,value为可跳转的下一状态集合 STATE_TRANSITIONS = { 0: [1, 6], # 待支付 -> 已支付 或 已取消 1: [2, 6], # 已支付 -> 已接单 或 已取消(管理员) 2: [3], # 已接单 -> 制作中 3: [4], # 制作中 -> 已出餐 4: [5], # 已出餐 -> 已完成 5: [], # 已完成,终态 6: [] # 已取消,终态 } def advance_order(current_status: int, target_status: int) -> bool: if target_status not in STATE_TRANSITIONS.get(current_status, []): return False return True这段Python示例用面向过程写法,方便在答辩时讲清"状态机是一张表,不是一个if-else堆出来的判断树"。STATE_TRANSITIONS字典里每个value都是一个列表,代表当前状态能合法跳转到哪些状态:用户在待支付时可以取消(0→6),但支付后取消(1→6)就必须是管理员操作,所以1对应的列表里虽然有6,但接口层要额外校验操作者角色。
订单列表查询页通常是整个系统里压力最大的接口,因为Web端和Android端都会频繁轮询它。Android端点单页的轮询间隔建议放在3到5秒,太短会把后端压垮,太长用户体验卡顿。下拉刷新是更省资源的方案,只在用户主动操作时重新拉取。
轮询请求里有一个非常典型的优化点:按状态分接口。GET /api/orders?status=1只返回待接单列表,GET /api/orders?status=5只返回已完成列表,比GET /api/orders返回全量数据再在前端过滤省掉大量网络流量。后厨平板端只需要关心待接单和制作中的订单,把查询条件压到待处理范围内,接口响应时间能明显降下来。
4. 避坑指南:餐饮管理系统复现中的五个典型问题
把一份毕设项目从压缩包变成能跑的系统,踩坑是常态。下面五条是我在拆这类双端项目时反复遇见的,每条都按现象、原因、解决三个层面说透。
4.1 gradlew.bat 执行即报错:Gradle 版本不匹配
现象:在Windows上双击gradlew.bat,窗口闪一下就退,或者输出Could not determine java version from '11.0.2',构建直接中断。换一台高版本JDK的机器,报错内容可能变成Unsupported class file major version。
原因:gradlew.bat本身只是壳,真正决定构建行为的在gradle/wrapper/gradle-wrapper.properties文件里。这个文件用distributionUrl指定的Gradle版本,如果跟本地JDK大版本冲突,比如Gradle 4.x遇到JDK 11以上,wrapper会在环境检测阶段挂掉。另一种常见情况是distributionUrl指向的地址网络受限,wrapper下载不到指定的发行版。很多毕设源码的wrapper配置是当年作者本机环境生成的,换一台机器环境差异就炸。
解决:先打开gradle-wrapper.properties,确认distributionUrl里的版本号;再执行java -version看本地JDK版本。Gradle与JDK的匹配关系大体是:Gradle 6.x配JDK 8到11,Gradle 7.x配JDK 11到17,Gradle 8.x配JDK 17。本地JDK版本过高时两种方案任选:装回匹配的JDK,或者改distributionUrl指向与JDK匹配的更高Gradle版本。改完wrapper后,最好删掉项目根目录的.gradle缓存文件夹再重跑,否则旧缓存还会干扰构建。
4.2 页面样式全乱:CSS 加载顺序被引擎忽略
现象:页面能打开,但导航栏没有底色、按钮圆角消失、图标全变成小方块,左侧菜单点不开。最诡异的是HTML源码里每个文件都在,控制台也没有404。
原因:<link>标签的引入顺序错了。H-ui基于Bootstrap,顺序必须是Bootstrap在前、H-ui在后;如果先加载H-ui.css再加载Bootstrap,H-ui的样式会被Bootstrap的同类规则覆盖,框架UI特征全部丢失。图标小方块则是font-awesome.css缺失或路径写错,图标字体加载失败后文本会回退到系统占位符。
解决:把<link>统一调整成这个顺序:bootstrap.min.css → H-ui.min.css → style.css → font-awesome.css → styletree.css → swiper3.07.min.css。font-awesome还依赖fonts目录下的字体文件,检查fonts/fontawesome-webfont.woff2是否存在,路径缺失时图标照样显示为方块。记住一个原则:框架级样式在前、组件级样式次之、项目自定义样式永远放最后。
4.3 中文字段全变问号:数据库字符集没设对
现象:后台添加菜品"宫保鸡丁"后,Web端列表显示正常,但Android端拉到菜品名变成乱码或直接显示问号。有时候两端都正常,导出SQL备份再导入新库后乱码。
原因:典型是数据库连接串没指定characterEncoding=utf8,或者建表时表级字符集继承自库级默认的latin1。旧库导出的SQL文件头部如果写着SET NAMES latin1,导入新库时会把中文数据洗一遍。MUI和H-ui默认都是UTF-8编码的页面,但后端连接MySQL若没指定字符集,JDBC会按平台默认编码读数据,中文在两端表现就分裂了。
解决:确认三处字符集统一为utf8mb4。第一处,建库语句CREATE DATABASE yxy_db DEFAULT CHARACTER SET utf8mb4;。第二处,连接串显式加参数,例如jdbc:mysql://localhost:3306/yxy_db?useUnicode=true&characterEncoding=utf8。第三处,Android端网络库解析响应时指定UTF-8,Retrofit的GsonConverterFactory默认按UTF-8读,但如果手动用response.body().string()且服务端响应头没带charset,就有概率读成平台默认编码。SQL文件导入前先执行SET NAMES utf8mb4;,能规避九成备份恢复乱码问题。
4.4 Android 端和 Web 端数据对不上:连接的是两套环境
现象:Web端能登录、能下单,Android端却提示网络错误或登录接口404。同一个WiFi下,Android模拟器访问http://localhost:8080/api直接失败。
原因:这几乎是毕设联调必踩的坑。Android模拟器里的localhost指向模拟器自身,而不是宿主机;真机调试时localhost指向手机自己。Web端代码写的是localhost:8080,Android端代码照抄了一份,结果请求打到设备本机的8080端口上,当然什么都没有。Android端报的404并不是接口不存在,而是压根连错了主机。
解决:手机和电脑连同一个局域网,Android端API基地址改成电脑在局域网内的IP,比如http://192.168.1.105:8080;模拟器可以用http://10.0.2.2:8080访问宿主机。还要确认后端服务监听了0.0.0.0而不是默认的127.0.0.1,否则局域网内其他设备连不进来。把API基地址放到Android的BuildConfig或gradle.properties里,不要写死在Activity字符串里——多人拿同一份源码联调时,环境差异只会集中在配置部分。
4.5 浏览器拦截 Ajax 请求:CORS 跨域不是后端一个注解能解决的事
现象:Web端页面里用jQuery发Ajax请求到后端,控制台报Access to XMLHttpRequest at ... has been blocked by CORS policy,请求根本没进后端业务代码。
原因:浏览器同源策略拦截了跨域请求。后端如果只设置Access-Control-Allow-Origin: *响应头,遇到项目里自定义的Authorization头时,会被CORS预检请求(OPTIONS)卡住。预检要求后端明确声明Access-Control-Allow-Headers: Authorization,否则浏览器认为跨域不安全,整个请求直接拦截。
解决:开发阶段用代理转发最省心。以常见的Vite或webpack-dev-server为例,把/api路径代理到后端地址,前端代码里继续写相对路径/api/xxx,由开发服务器转发,浏览器看到的就是同源请求。如果没有代理,后端要在全局CORS配置里同时允许Authorization头、Content-Type头,并显式放行OPTIONS方法:
// 以Spring Boot全局CORS配置为例,覆盖所有API接口 @Configuration public class CorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(Registry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("Authorization", "Content-Type") .allowCredentials(true); } }; } }这段配置的关键在于allowedHeaders把Authorization显式加进去,而不是写*通配。allowCredentials(true)表示允许携带Cookie凭证,但注意这个配置对Origin有限制,不能再用简单的*,而要换成allowedOriginPatterns("*")。另外addMapping("/api/**")限定了路径范围,比直接配/**更安全,登录外的静态资源不该被跨域放开。
5. Android端联调与源码管理:用抓包验证数据流、用版本控制兜底
5.1 用 Fiddler 抓包验证 Android 端与 Web 端的数据一致性
双端系统最怕"Web端能用、Android端数据不对"这种黑盒问题。与其来回猜,不如用Fiddler抓包直接看两边发出的HTTP请求是否一致。操作流程:Fiddler开启HTTPS解密并允许远程连接,手机WiFi代理指向电脑IP和8888端口,然后在Android端依次执行登录、拉菜单、下单三个操作,抓到的请求会按域名分组列在面板里。
对比Web端和Android端的抓包结果,重点看三个位置:URL路径是否一致、Authorization头里的Token是否有效、请求体里的JSON字段名是否匹配。最常见的现象是Android端传的是{userName: tom},Web端传的是{username: tom},字段名差一个字母后端就只认一端——这种问题看抓包面板一眼就能定位,不用改代码反复试。
5.2 借此机会把源码管理捡起来
对接过几份毕设源码之后,我有一个习惯改不掉了:解压任何项目先git init提交一个初始版本,再开始改代码。这套做法的价值体现在两个场景:改坏了一处功能想回到昨天能跑的状态,git checkout -- .一条命令的事;答辩老师问代码是不是自己写的,打开git log给他看提交历史,比解释一万句都管用。
整个项目跑通之后,服务端、Web端、Android端三部分的配置要点其实都收敛到几个文件里:服务端看application.properties里的数据源和端口,Web端看config.js里的API地址,Android端看gradle.properties里的构建配置。从那以后我每次拆新项目,都强制走一遍"先体检、后跑通、再改代码"的流程——先看文件名清单判断技术栈,再按依赖顺序搭环境,最后用抓包工具确认两端数据一致。项目能跑只是及格,能讲清楚每个状态为什么这样流转、每个接口为什么这样设计,才是毕设答辩里真正拉开差距的地方。希望帮到你。
本文还有配套的精品资源,点击获取