
每年到了大四下学期后台就会有一批人问毕设主题可以用Java吗能结合微信小程序吗确实在相关搜索词里持续排前几位的组合就是“Java”和“微信小程序”。第一次看到“计算机毕设Java地方特色美食打卡系统”这种长标题时我第一反应是它把三个高度相关的需求压缩在了一句话里Java、微信小程序、地方美食打卡。如果核心目标只是完成毕业设计这个题目其实相当友好。它既不是网上泛滥的图书管理系统也不是只有前端切图的静态展厅而是真实存在用户需求、天然需要联网服务、又有足够数据模型可以展开的项目。这个系统的本质不是做一个“美食点评App”而是围绕“打卡”这个核心动作做一套从用户发现美食、到店签到、发布分享、互动评论的完整闭环。前期需要一个稳定的Java后端来支撑数据需要一个微信小程序来满足用户扫码式、轻量化的使用习惯。换句话说它有两个鲜明特点一是业务链路长可以从简单的CRUD演变成有位置服务、有防作弊校验、有内容推荐的中型系统二是每一层都踩在基本功上Spring Boot、MySQL、Redis、小程序API全是能直接写进简历的技术点。如果你正准备拿这个方向做毕设又不想做成那种“答辩三分钟就被问住”的项目这篇复盘应该有帮助。我会从需求边界、数据建模、定位打卡、社区互动、管理后台、真实部署到答辩追问把我实际做完这个项目的全过程和经验重新梳理一遍尽量把“为什么这么设计”也讲清楚。1. 为什么这个毕设题目值得做热度、复杂度与答辩价值1.1 从热搜词看选题的“技术红利”Java和小程序生态如果你去翻近两年的技术社区热搜会发现“Java”“Java面试”“微信小程序”几乎是长期霸榜的词条。Java不是没有争议但它依然是大量后端系统的真实选择学习资料、社区问答、组件文档都极其成熟微信小程序则是目前入门移动端开发和验证商业想法成本最低的渠道之一。两者组合起来意味着你在查找资料时几乎不会遇到“无人区”。但成熟生态也带来另一个问题题目太容易撞车。纯学生管理系统、纯班级通讯录已经很难做出区分度了。美食打卡系统的价值在于它把技术从“悬浮的某管理系统”拉回到真实的场景里用户会去一个真实位置吃什么、拍什么、在哪里打卡、和谁互动。这天然要求系统具备业务建模能力而不是只写一堆增删改查页面。更关键的是这类系统的功能清单可以做得很有层次。基础层是用户注册登录和分类浏览提高一点是地理位置校验和打卡行为限制再提高一点是动态热度和推荐排序。每一个层次都能在答辩时展开讲五分钟不至于被问两句就冷场。1.2 功能边界一个“打卡平台”不等于“点餐外卖平台”我刚拿到这个选题时最纠结的一点是到底要做到多“完整”是不是要把菜单、排号、外卖、支付全部做进去后来我意识到标题里反复出现的词是“打卡”不是“点餐”。一个良性的地方美食打卡系统核心应该聚焦在三个动作发现美食、到店打卡、分享互动。用户在首页浏览某个城市的地方特色分类点进商家或美食详情页看到介绍真正到达门店附近时可以签到签到后顺带写一句推荐语、上传几张实拍图。这就是完整的产品闭环。不需要做外卖因为外卖是另一个交易体系涉及商户实时的库存、配送范围、支付结算复杂度远超毕设的容量。不需要做商家入驻后的点单管理因为那会把后台从“内容审核平台”引入“门店运营系统”。把边界划清楚反而是一个合格产品需求分析能力的表现。1.3 从评分视角看项目可演示、可扩展、可追问毕设评分通常不只看功能数量更看重“完整性”和“技术理解”。一个美食打卡平台在答辩时可以按一条主线演示用户进入小程序在“发现”页看到本地特色美食榜点击详情了解门店位置和推荐菜到达门店附近后点击打卡小程序获取定位后端校验距离并记录打卡内容同步出现在首页动态流里其他人可以点赞评论管理员在后台审核新增门店、删除违规内容查看打卡统计。这条主线走完答辩老师能看到前端界面、后端逻辑、数据库设计和接口安全。同时系统保留了很好的扩展点累计打卡可以发勋章收藏门店可以做个性化推荐如果将来有真实商户入驻可以再扩展商家端。这些问题即使在答辩中被追问也不会把项目问死因为底层数据结构已经允许向上生长。2. 技术选型不是越新越好我为什么选这套组合2.1 后端Spring Boot 2.7 MyBatis-Plus Redis MySQL后端我选了 Spring Boot 2.7而不是直接上 3.x。原因很实际稳定、生态成熟、网上遇到坑能搜到正确答案。Spring Boot 3 最低要求 JDK 17很多毕设环境还不一定升级到那边评审老师也未必熟悉新特性。2.7 配合 JDK 8 或 JDK 11兼容性最保险。持久层用 MyBatis-Plus因为它把单表 CRUD 省到了极致。我只需要写实体类简单的查询连 SQL 都不建能省下大量时间去处理真正的业务逻辑。不过这不意味着完全不写 SQL。城市维度的聚合统计、多表联查打卡记录、后台数据看板这类场景我依然在 XML 里手写了 SQL既能看到执行计划也方便调优。MySQL 选 8.x因为字符集默认更好地支持 utf8mb4处理 emoji 评论不会出现乱码问题。Redis 在这个系统里不是摆设首页动态流的热度排行、短时间内的重复打卡拦截、动态点赞数的热点缓存都用得上。技术组件承担职责选型理由Spring Boot 2.7HTTP接口、业务编排、定时任务生态成熟资料多开发效率高MyBatis-Plus单表落地与复杂SQL结合省掉基础CRUD代码XML保留复杂查询空间MySQL 8业务数据持久化utf8mb4、窗口函数、稳定可靠Redis热榜、防重复、热点数据缓存支持ZSET、SETNX等结构天然适合业务腾讯位置服务逆地址解析、JS-SDK坐标转换与微信生态兼容个人使用免费配额足够2.2 前端用原生微信小程序还是 uni-app这是另一个高频纠结。我也在 HBuilderX 里用 uni-app 跑过一个版本但最后重构时还是换回了原生微信小程序。原因很简单项目中最重的前端逻辑就是地图定位、上传图片、列表分页和分享朋友圈原生框架完全能搞定反而少一层编译转换。微信开发者工具对原生代码的报错更直接打断点也方便。如果你预判自己以后要同时发布抖音小程序或支付宝小程序再选择 uni-app 也来得及如果只是为了毕设原生小程序的学习曲线更短而且当你展示代码结构时评审老师能清楚看到你在写 WXML、WXSS 和 JS。当然如果你已经用 uni-app 跑了一阵子也不必强行重构。只要保持后端接口不变前端框架替换成本不高。需要留意的是用 HBuilderX 运行到微信开发者工具时经常出现“当前不是开发者”或“模拟器里小程序ID还是原来那个”的提示。这不是代码问题而是 AppID 没有同步到 manifest.json或者你的微信号没有被加到小程序后台的开发者列表里。去“成员管理”里添加一下再把微信开发者工具里的“项目的AppID”重新选择一次就能解决。2.3 文件存储、地图服务与后台模板用户打卡时会传图片我不建议把图片直接存在后端服务器。虽然本地磁盘也能用但部署后会有两个麻烦一是需要单独配置静态资源映射二是服务器带宽有限图片一多页面打开就很慢。我选择用腾讯云对象存储 COS 做图片存储后端通过 SDK 生成临时上传凭证小程序端直传图片到 COS然后只把返回的 URL 提交给后端。这样服务器的压力和带宽压力瞬间降下来。地图服务我用了腾讯位置服务。微信小程序里 wx.getLocation 返回的是 GCJ-02 坐标腾讯位置服务天然支持这个坐标系逆地址解析和行政区划查询也方便。后台界面没有选择前后端分离而是用 Spring Boot 集成 Thymeleaf 和 AdminLTE 这套轻量后台模板。理由很直接毕设管理后台只有管理员自己用不需要复杂权限不需要独立部署前端项目模板引擎渲染最省事。3. 先把“打卡”这件事想透核心数据模型设计3.1 不急着写代码先画出用户故事我见过很多同学一上来就建表结果做到最后发现字段不够、关系不对。美食打卡系统的表结构其实没那么复杂但必须先理解角色关系。游客打开小程序看看有哪些推荐城市、哪些地方特色分类。普通用户授权登录后可打卡可发布分享可点赞、评论。系统管理员审核用户上报的门店管理分类删除违规动态查看统计。把这三个角色列出来后会出现两个核心问题。第一美食地点必须是一个独立实体不能把它当作用户发布动态时的附属字段否则打卡的“地点”无法统一维护也无法形成榜单。第二打卡行为本身和内容分享要有所区分。用户在门店点“打卡”并写下推荐语会同时生成一条打卡记录和一条动态但如果他之后纯粹想发一条“感觉今天吃了家隐藏小店”的图文就没有打卡属性。这两类内容分开建模后续扩展“连续打卡勋章”时就很从容。3.2 五张核心表的设计与关键字段根据上面的思路我把系统拆成了五个最重要的表用户表、美食分类表、美食地点表、打卡记录表、动态内容表。别的点赞、评论、收藏表都可以在这五张表的基础上做关联子表。用户的 openid 要唯一同时保存昵称、头像、手机号、状态。美食分类表用 parent_id 支持层级比如“江苏菜”下面可以挂“南京小吃”“苏州糕点”。美食地点表是整个系统的核心资产包含名称、分类、城市编码、详细地址、经度、纬度、封面图、介绍、热度、状态。之所以预留 status 字段是因为用户上报的门店需要经过管理员审核才能展示否则小程序里会混入很多错误或重复的地点。打卡记录表记录的是某用户在某时间、某地点附近提交的一次签到除了 user_id、poi_id、checkin_time还要保存提交签到时的经纬度这样后台可以回溯距离计算过程。动态表则保存内容本身正文、多图 URL、点赞数、评论数、归属的 poi_id。点赞和评论单独拆表避免把一个字段存成 JSON 而导致后续查询困难。3.3 经纬度存储与索引别把地理想得太复杂新人最常见的写法是把经纬度存成 varchar(20)后面做距离筛选时再用字符串转换这不科学。推荐用两个 decimal(10,6) 字段来存 longitude 和 latitude。decimal(10,6) 能精确到 0.1 米级别存储真正的数字方便参与距离计算和范围过滤。查询附近热门打卡点时我会先按经纬度做一个粗略的矩形过滤比如 WHERE longitude BETWEEN ? AND ? AND latitude BETWEEN ? AND ?先把数据量缩小再用 Haversine 公式精确计算。这样既不用给 MySQL 引入复杂空间索引也能保证小规模数据下的性能。如果你的桌子只有几千到几万个地点这套方案非常够用。如果未来要支撑几十万 POI再考虑 MySQL 的 ST_Distance_Sphere 或引入 ElasticSearch 都不迟。毕设阶段把计算逻辑讲透比盲目拉一个空间索引更有实际价值。3.4 建表 SQL 片段参考下面只截取最重要的三张表作为参考完整表结构完全可以在此基础上扩展。CREATE TABLE food_poi ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 美食地点名称, category_id bigint NOT NULL COMMENT 美食分类ID, city_code varchar(20) NOT NULL COMMENT 城市编码, address varchar(255) DEFAULT NULL COMMENT 详细地址, longitude decimal(10,6) NOT NULL COMMENT 经度, latitude decimal(10,6) NOT NULL COMMENT 纬度, cover_url varchar(500) DEFAULT NULL COMMENT 封面图, intro varchar(1000) DEFAULT NULL COMMENT 推荐介绍, heat int DEFAULT 0 COMMENT 热度值, status tinyint DEFAULT 1 COMMENT 0下架 1正常 2待审核, create_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_city_category (city_code, category_id, status), KEY idx_lng_lat (longitude, latitude) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT美食地点表; CREATE TABLE checkin_record ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, poi_id bigint NOT NULL, checkin_time datetime NOT NULL, longitude decimal(10,6) DEFAULT NULL COMMENT 签到上报经度, latitude decimal(10,6) DEFAULT NULL COMMENT 签到上报纬度, distance_meter int DEFAULT NULL COMMENT 与门店目标距离米, content varchar(1000) DEFAULT NULL COMMENT 打卡短评, images varchar(2000) DEFAULT NULL COMMENT 图片URL逗号分隔, is_valid tinyint DEFAULT 1 COMMENT 是否有效, create_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_user_time (user_id, checkin_time), KEY idx_poi_time (poi_id, checkin_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT打卡记录表;注意经纬度字段不要用 float 或 double。float 在范围跨界时容易产生误差double 虽然精度高但占字节多decimal(10,6) 已经覆盖了全球经纬度的全部范围和 0.1 米的精度需求是最务实的方案。4. 打卡功能最核心的“地理围栏校验”定位误差与体验的平衡4.1 小程序端定位授权配置和真实场景差异如果要在真实位置打卡第一步是小程序端拿到用户当前的经纬度。原生小程序里核心接口是 wx.getLocation但这里有两个非常容易踩的坑。第一个坑是后台配置和 app.json 必须同步。你需要在app.json里声明 permission 字段同时在小程序管理后台的“设置-服务内容声明-用户隐私保护指引”里说明会使用位置信息否则某些基础库版本下调用直接失败报“getLocation:fail the api need to be declared in the requiredPrivateInfos field”。具体配置如下{ permission: { scope.userLocation: { desc: 你的位置信息将用于打卡美食地点 } }, requiredPrivateInfos: [getLocation] }第二个坑是模拟器定位和真机定位偏差很大。微信开发者工具里默认定位通常是城市中心或你手工指定的模拟位置不能用来验证“到了门店附近”的逻辑。要测试签到成功率必须用手机真机预览走到门店 500 米范围以内。这个细节如果没注意会出现开发者工具里打卡永远成功、真机上却怎么都失败的诡异现象。4.2 Haversine 距离计算为什么会出现在我的 Service 里打卡校验不能只在小程序端判断距离因为前端完全可能被绕过去。后端必须根据库里的门店坐标和前端上报的坐标做一次真实距离计算。我使用的算法是 Haversine。它计算的是球面上两点间的大圆距离比粗暴的欧几里得距离更贴近真实地面场景。地球半径我用 6371 公里最后转成米。public class DistanceUtils { private static final double EARTH_RADIUS_M 6371000; public static double distance(double lat1, double lng1, double lat2, double lng2) { double radLat1 Math.toRadians(lat1); double radLat2 Math.toRadians(lat2); double deltaLat Math.toRadians(lat2 - lat1); double deltaLng Math.toRadians(lng2 - lng1); double a Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) Math.cos(radLat1) * Math.cos(radLat2) * Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2); double c 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS_M * c; } }这个代码看起来不短但每个变量都能在球面三角公式里找到对应含义答辩时完全能说清楚。不要直接拷贝一段自己都不理解的“魔法数字”否则老师一旦展开问现场很容易露馅。4.3 允许误差半径怎么定300 米背后的取舍地理围栏的半径设置有讲究。如果半径设成 10 米用户明明站在店门口但 GPS 波动一下就可能打卡失败如果半径设成 2 公里用户住在隔壁县城也能打卡成功数据就会失真。我最终给普通门店设置了 300 米的允许范围。理由是微信定位在室外空旷场景通常能到 20-50 米精度室内或商圈里可能出现 50-100 米偏差300 米能覆盖大多数“顾客在门店周边”的真实场景又能排除同城异地打卡。对于商场内位置我把后台可配置的 range_meter 设置为 500 米补偿楼层定位偏移。打卡提交接口的完整校验序列大致如下用户请求头携带登录态后端解析用户ID根据 poiId 查询门店与门店坐标计算上报坐标与目标门店坐标距离如果距离大于可配置的 rangeMeter直接拒绝并提示“距门店太远”检查 Redis 里是否存在该用户当天已成功打卡同一门店的标志检查该用户在 30 秒内是否有打卡成功记录防脚本快速打最后再写入打卡记录表异步更新门店 heat 值。4.4 防刷与体验平衡的额外手段纯技术校验挡不住所有恶意用户所以系统还需要一个可解释的业务规则。我给打卡加了一条同一用户对同一家门店 24 小时内只能打卡一次。这个规则用 Redis 的 SETNX 加过期时间实现天然支持并发场景下的幂等即使两个请求同时进来只有一个能拿到锁。代码如下long poiId poi.getId(); long userId currentUser.getId(); String lockKey checkin:poi: poiId :user: userId; Boolean first stringRedisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofHours(24)); if (Boolean.FALSE.equals(first)) { throw new BizException(今天已经打卡过这家店了换个地方再逛逛吧); }这样操作让数据库层不需要额外增加唯一索引也能挡住大部分重复请求同时给用户在页面上明确的反馈。管理员在后台还能查看 distance_meter 字段和 checkin_time一旦发现同一用户高频出现在多家非邻近门店可以直接把打卡记录设为无效并相应扣减用户积分。5. 从“打卡”到“互动”美食分享与内容社区的落地5.1 美食地点库的来源预制种子数据与用户共建一个只有打卡功能、没有内容的平台是空的。第一版上线时我整理了 10 个地方美食分类和 200 多条代表性地点数据覆盖南京、成都、广州、长沙等热门城市。这些种子数据不需要刻意收集整理几篇省级非遗美食名录、各地大众点评高口碑老字号再补上基础经纬度就可以。但一个真实的分享平台不可能只靠官方维护。因此我在小程序里设计了“上报新地点”的能力用户发现没有被收录的特色门店时可以提交店名、位置和推荐理由。上报的数据不直接公开而是插入 food_poi 表status 置为 2由管理员在后台审核审核通过后状态改为 1。这个小功能让系统变成了可生长的数据模型也顺带撑起了后台审核模块的存在价值。5.2 动态图片上传小程序直传 COS 的流程发布动态时用户能选择九张以内的图片。图片上传如果走后端中转每次都得占一次后端带宽而且遇到大图片会很吃力。我改用小程序端直传 COS用户拍照后小程序先请求后端一个签名接口后端利用 SecretId 和 SecretKey 生成一个短时效的临时上传凭证前端直接用 wx.cloud 或 COS SDK 上传到指定 bucket上传成功后拿到文件 URL再连同动态内容一起提交给后端。要注意在上传前对图片做压缩。小程序端的 wx.compressImage 或者 chooseMedia 自带的 sizeType 都行。很多新手忽略了这一步一张原图 5MB 直接传到 COS不仅消耗流量还会让首页动态流加载变慢。体验上我在上传时为每个用户限制了单张图片大小不超过 2MB格式仅支持 jpg、png、webp。5.3 首页推荐里的“热度值”是怎么算的打卡只有一条时间线还不够。为了体现“地方特色”的差异性我首页设计了三个板块今日热门打卡、本地精选、最新分享。今日热门打卡来自一个热度机制。系统不会直接拿原始点赞数展示而是按公式计算动态热度heat_score 点赞数 x 5 评论数 x 10 打卡产生权重 x 3 发布时间衰减发布时间衰减用了一个简单思路越新的动态获得一个基础加分随着时间推移递减。因为动态的展示周期短我不能让一条三天前的动态凭旧点赞量永远霸榜。定时任务每 5 分钟对所有有效动态重新算一次热度然后把结果刷到 Redis 的 ZSET 里。成员按城市分组key 形如feed:hot:cityCodevalue 是 feedIdscore 就是热度值。前端请求首页时后端先查 Redis ZREVRANGE 拿排名靠前的 feedId再批量回查 MySQL 补齐发布者昵称、头像、图片、POI 名称等信息。这样做防止了每次都全表排序也让 Redis 的价值体现得很自然。如果你未来想往深扩展还可以引入更复杂的推荐算法但毕设阶段能把这个热度淘汰机制跑通已经很有说服力。5.4 评论、点赞操作的缓存策略点赞和评论是高频操作。点赞数如果每次都更新 MySQL数据库压力不小而且容易发生并发覆盖。我的做法是点赞操作先直接写 MySQL 点赞表同时用 Redis INCR 更新缓存中的点赞数动态详情接口优先展示缓存数字后台每 10 分钟把缓存值同步回动态表。虽然缓存和数据库之间有一定时间差但这种程度的不一致在讨论区业务里完全可以接受。评论则比较简单新评论写入后对动态的 comment_count 做一次 INCR再通过 WebSocket 推送在线用户没有引入额外消息队列避免把一个毕设项目撑得太重。6. 管理后台与数据看板完整交付物的最后一块拼图6.1 后台为什么不用前后端分离很多毕设技术报告里都喜欢写“Vue Spring Boot 前后端分离”但落到实际管理后台时我会提醒自己做这个后台是为了给管理员审核数据和查看统计不是为了考察自己写了多少 Vue 组件。如果功能简单且使用者极少服务端渲染反而是更高效率的选择。我在 Spring Boot 里集成了 Thymeleaf 和 AdminLTE页面直接由 Controller 返回。“美食地点管理”页面加载时就渲染表格审核动作通过 jQuery 提交 AJAX 请求JSON 返回结果。整体代码量大概是在 Vue 端再开一个工程的三分之一。而且因为前后端在同一个进程里演示时只需要启动一个 Spring Boot 应用再打开特定端口页面就行不用同时维护两个服务。6.2 后台的具体功能模块功能模块说明美食分类管理添加、隐藏、排序分类用于小程序端分类筛选美食地点审核审核用户上报的POI支持通过/拒绝通过后展示在小程序用户管理查询用户状态禁用异常账号打卡记录审核查看打卡详情与距离对可疑记录标记无效动态管理浏览最新动态单个或批量下架违规内容数据看板统计近7日打卡趋势、分类热度占比、城市美食排行打卡记录审核里我特意保留了“距离”字段。管理员能直接看到这条提交与门店之间的实际距离。如果距离小于 10 米、又在凌晨高频出现基本就是模拟定位或脚本行为直接标记无效即可。这个字段让审核不再是凭感觉删帖而是有依据、可回溯的。6.3 数据看板背后的 SQL 和图表后台首页的数据看板是答辩展示时最容易出效果的地方。近 7 日打卡趋势图的数据来自一条简单的聚合查询SELECT DATE(checkin_time) AS d, COUNT(*) AS cnt FROM checkin_record WHERE checkin_time DATE_SUB(CURDATE(), INTERVAL 6 DAY) GROUP BY DATE(checkin_time) ORDER BY d;分类热度占比则通过关联 food_poi 表计算。比如统计“过去30天打卡次数最多的地方美食分类”就是先按打卡记录关联到 POI再关联分类表最后 group by category_name取前 8 条。前端用 ECharts 画柱状图和饼图不需要额外数据库压力数据量也不大这个组合足够支撑演示。后台完全不需要做复杂登录权限但管理员账号的加密不能省。我不会用明文密码放在数据库里至少用 BCrypt 做哈希。进入后台页面时必须校验登录态否则任何普通用户只要知道后台地址就能操作数据这会在答辩时成为非常致命的安全漏洞。7. 部署上线从本地能跑到微信公众平台能访问7.1 小程序后台的注册与成员配置部署的第一步不是写配置而是去微信公众平台注册一个小程序账号。这里要注意 AppID 和普通测试号的区别。用测试号可以完成一部分开发但 wx.getLocation、组件分享等能力在某些基础库下会受到限制体验版和预览版也不稳定。建议毕设一开始就注册自己的小程序账号哪怕主体类型是个人也没关系后台功能完全能运行起来只是个别类目选择上要规规矩矩。注册完成后进入“成员管理”把指导老师和自己的微信号都加为项目成员或体验成员。否则用微信开发者工具预览时会提示“当前微信号不是开发者”。如果你日常使用 HBuilderX还要去 manifest.json 的小程序配置里把 AppID 填正确避免反复出现“运行到模拟器时小程序 ID 还是原来的”这种很耗耐心的问题。7.2 HTTPS 域名与 Nginx 反向代理小程序正式环境有一个硬性要求请求的接口地址必须是 HTTPS且域名已经备案。如果你只是给老师演示体验版可以在微信开发者工具右上角“详情-本地设置”里勾选“不校验合法域名”但这样做有一个问题真机预览也可能需要打开调试模式才能请求体验很别扭。因此还是要把后端部署到带公网 IP 的云服务器上并完成域名解析。我在云服务器上安装了 Nginx把 443 端口的 HTTPS 流量反向代理到 Spring Boot 的 8080 端口。Nginx 关键配置如下server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /etc/nginx/cert/yourdomain.pem; ssl_certificate_key /etc/nginx/cert/yourdomain.key; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }云服务商一般可以申请免费证书下载 Nginx 版本证书并上传到服务器。域名备案不能临时抱佛脚周期可能十几天所以建议项目刚开始搭建时就把域名和服务器买好别等最后要交材料时才想起来。7.3 Spring Boot 如何长期稳定运行使用 java -jar 直接跑后端有一个问题关闭终端控制台后进程就断了。正确做法是编写一个 systemd 服务让 Linux 系统自动守护进程。下图是一个可用的 service 文件示例[Unit] DescriptionFoodCheckinServer Afternetwork.target [Service] Userroot WorkingDirectory/home/app ExecStart/usr/bin/java -Xms256m -Xmx512m -jar food-checkin.jar --spring.profiles.activeprod Restartalways RestartSec5 EnvironmentMYSQL_HOST127.0.0.1 EnvironmentREDIS_HOST127.0.0.1 [Install] WantedBymulti-user.target把文件放到/etc/systemd/system/food-checkin.service后执行 systemctl daemon-reload再执行 systemctl start food-checkin进程就会驻留在后台。以后每次更新代码只需要重新构建 jar 包并 systemctl restart food-checkin。线上 MySQL 和 Redis 不要在配置里写明文密码可以采用 Spring Boot 的配置外部化把生产环境变量放在 service 文件或单独的配置目录里避免把密钥提交到代码仓库。7.4 真机验证与提审前需要检查的点真机验证时我踩过一次比较隐蔽的坑开发者工具里打卡一切正常但 iPhone 真机微信里点打卡总提示定位失败。排查后发现小程序基础库升级后需要在管理后台的“用户隐私保护指引”中补充位置信息收集说明同时 requiredPrivateInfos 里要包含 getLocation。返回小程序后台补充隐私声明并提交后问题就消失了。提审方面小程序内容不能包含测试数据或不存在的商家信息。地方美食类的小程序如果涉及“餐饮服务”类目可能要提供相应资质但如果你的项目定位是“美食分享与打卡记录工具”类目选择工具-信息查询或生活服务笔记类通常更容易被接受。最终我验证时用的是“开发版 体验版”给小范围成员使用并不强制走正式发布流程也足以完成毕设答辩的全部演示环节。8. 答辩之前性能、安全与老师最爱追问的几个问题8.1 接口安全从 Token 校验到越权防护很多毕设作品最大的问题不是功能少而是接口裸奔。任何人都能直接调用后端接口随意修改数据。我在项目里加了一层简单的拦截器对所有/api/**请求统一校验 JWT。登录流程是小程序调用 wx.login 拿到 code后端把 code 传给微信接口服务换 openid再根据 openid 查用户表生成一个包含 userId 的 JWT 返回给小程序。后续请求在 Header 中携带Authorization: Bearer token拦截器解析并确定当前用户身份。再强调一个细节写更新操作时绝对不要从前端请求参数里取 userId。例如“删除自己发布的动态”接口应该从 JWT 中解析当前 userId再和动态表的 user_id 做匹配。如果直接让前端传 userId那么用户改一下请求参数就能删除别人动态属于严重的越权漏洞。这一点是答辩老师最常抽查的也是最容易暴露代码习惯的地方。8.2 当前系统到底能扛多大并发