Java和微信小程序结合的地方美食打卡系统毕设开发复盘
2026/9/8 15:29:30 网站建设 项目流程

每年到了大四下学期,后台就会有一批人问:毕设主题可以用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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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:cityCode,value 是 feedId,score 就是热度值。

前端请求首页时,后端先查 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] Description=FoodCheckinServer After=network.target [Service] User=root WorkingDirectory=/home/app ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar food-checkin.jar --spring.profiles.active=prod Restart=always RestartSec=5 Environment=MYSQL_HOST=127.0.0.1 Environment=REDIS_HOST=127.0.0.1 [Install] WantedBy=multi-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 当前系统到底能扛多大并发

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询