简介:本资源是一套完整的‘家庭大厨’微信小程序毕业设计案例,面向计算机专业本科生及Java全栈初学者,聚焦前后端分离开发实践,解决课程设计、期末大作业与求职项目储备等实际需求。压缩包共856个文件,含120个Java后端核心类(SSM三层架构)、104个JS/WXML/WXSS小程序页面逻辑与视图文件、96个Vue组件(用于管理后台)、175个PNG/SVG图标资源及22个MyBatis映射XML,辅以SQL建表脚本、BAT一键部署脚本(install/run/build)和完整配置文件,整体36.84MB。已有108人学习下载,资源结构清晰:前端按pages与components分层,后端严格遵循Controller-Service-Mapper包结构,配套README与目录说明便于快速理解模块职责。读者可直接运行调试,掌握微信登录态对接、食谱CRUD、用户评论交互、RESTful API设计及SSM整合细节,是少有的兼顾教学性、工程规范与微信生态集成的全流程源码范例。
1. 项目概述:一个家庭大厨的数字化厨房构想
最近在整理过往项目时,翻到了一个挺有意思的“家庭大厨微信小程序+SSM后端源码案例设计.zip”。这名字听起来有点学院派,像是一个课程设计或者毕业项目,但仔细琢磨一下,它其实触及了一个非常生活化且实用的场景:如何用技术来管理和优化我们日常的家庭烹饪。这个项目本质上是一个集成了菜谱管理、食材清单、烹饪计时等功能的微信小程序,后端则采用了经典的Java SSM框架。它不像那些动辄上亿流量的商业应用,却精准地瞄准了每个家庭厨房里的真实痛点——菜谱散落在各个App、纸质笔记里,想做菜时找不到;购物时忘记要买什么;烹饪过程中手忙脚乱,不是糊锅就是错过最佳火候。
这个案例的价值,不在于它用了多么前沿的技术栈,而在于它提供了一个非常完整、可落地的“样板间”。对于刚学完Java Web和微信小程序开发,想找个综合项目练手的新手来说,它是一个绝佳的起点。你能看到从前端页面布局、微信API调用,到后端Controller、Service、Dao层的完整数据流转,再到数据库表的设计。对于有一定经验的开发者,它则展示了如何将一个生活场景抽象成具体的功能模块,并用相对成熟的技术栈稳健地实现出来。接下来,我就结合这个源码包,为你深度拆解一下,一个这样的“家庭大厨”系统是如何从想法变成代码的,其中有哪些设计巧思,又有哪些是新手容易踩的“坑”。
2. 项目整体架构与设计思路拆解
拿到一个项目源码,第一步不是急着看代码,而是先理解它的整体蓝图。这个“家庭大厨”项目采用了典型的前后端分离架构,这是目前移动应用开发的主流模式。
2.1 技术栈选型背后的逻辑
前端:微信小程序。选择微信小程序而非原生App或H5,是经过深思熟虑的。对于“家庭大厨”这类工具型、使用场景碎片化(可能在厨房、超市随时打开)的应用,小程序有着无可比拟的优势:无需安装,即用即走;依托微信生态,分享菜谱给家人朋友极其方便;开发成本和学习门槛相对较低。小程序提供的丰富组件(如swiper轮播图展示菜品、video播放烹饪教程)和API(如本地存储保存用户偏好、云开发能力备用)足以支撑核心功能。在厨房环境中,用户可能手上沾有水或面粉,小程序的触控交互也比网页更友好。
后端:SSM框架。即Spring + Spring MVC + MyBatis。这是一个在Java企业级开发中经久不衰的“老兵”组合。为什么不用更时髦的Spring Boot?对于教学案例或中小型项目,SSM框架结构清晰,分层明确(Controller、Service、Mapper),非常有利于学习者理解MVC模式和ORM思想。每一层做什么,数据怎么流动,一目了然。Spring负责Bean的管理和事务控制,Spring MVC处理Web请求和路由,MyBatis则作为数据持久层,通过灵活的SQL映射文件与数据库交互。这个组合稳定、资料丰富,社区遇到的所有问题几乎都能找到解决方案,对于项目后续的维护和功能扩展,提供了可靠的基础。
数据库:MySQL。关系型数据库是存储菜谱、用户、食材这类结构化数据的自然选择。菜谱与食材的多对多关系、用户与菜谱的收藏关系,都能通过外键清晰地表达。MySQL的轻量、开源和强大的社区支持,使其成为此类项目的标配。
这个技术选型体现了一种务实的态度:不盲目追求新技术,而是在满足需求、保证稳定性和可学习性之间找到最佳平衡点。
2.2 核心功能模块设计
打开项目,你会发现它的功能模块围绕“家庭大厨”的核心场景展开,主要分为以下几块:
- 用户中心:包括微信一键登录、个人资料管理(如厨艺等级、擅长菜系)、我的收藏、我的发布等。这里的设计关键在于利用微信的
wx.login和getUserProfile接口安全地获取用户身份,并在后端生成自定义的登录态令牌(如JWT或Session)来维持会话。 - 菜谱广场:这是小程序的门面。通常以信息流或分类(如川菜、烘焙、快手菜)的形式展示菜谱列表。每个菜谱卡片包含封面图、标题、难度、耗时、收藏数等。设计上需要考虑图片的懒加载、下拉刷新和上拉加载更多,以保障流畅的浏览体验。
- 菜谱详情:这是功能最集中的页面。不仅展示详细的步骤图文、所需食材及用量,还应集成烹饪计时器、食材清单勾选功能。用户可以在浏览时,一键将所需食材加入购物清单,并在实际烹饪时,根据步骤启动相应的计时器(例如,“小火焖煮20分钟”)。
- 发布菜谱:允许用户上传自己的拿手菜。这涉及到富文本编辑(或分步骤的图文上传)、多张图片上传、食材和用量的动态表单添加。后端需要处理文件上传(图片存储到云存储或本地服务器)和复杂表单数据的接收。
- 智能清单:由“购物清单”和“厨房存货”两部分演化而来。系统可以根据用户收藏或计划的菜谱,自动合并生成一份去重后的总购物清单。用户也可以在“厨房存货”中手动录入现有食材,系统或许能反向推荐可制作的菜谱(这是一个可扩展的亮点功能)。
- 搜索与分类:除了按菜系、难度、时间分类,强大的搜索功能必不可少。应支持按菜名、食材、甚至模糊描述(如“下饭菜”、“夏天汤”)进行搜索。后端这里可能会用到MySQL的全文索引,或引入更简单的
LIKE模糊查询。
这个模块划分清晰地勾勒出了一个最小可行产品。它没有一开始就追求大而全,而是抓住了“找菜谱-看菜谱-用菜谱”这个核心闭环。
3. 数据库设计与核心表结构解析
后端开发中,数据库设计是地基,地基打得好,后续的代码写起来才顺畅。我们来看看“家庭大厨”这个项目里,几张核心表是如何设计的。
3.1 核心实体关系分析
主要的实体有:用户、菜谱、食材、菜谱步骤。它们之间的关系是:
- 一个用户可以发布多个菜谱,也可以收藏多个菜谱(用户-菜谱:一对多,以及多对多的收藏关系)。
- 一个菜谱包含多个步骤(菜谱-步骤:一对多)。
- 一个菜谱需要多种食材,一种食材也可以出现在多个菜谱中(菜谱-食材:多对多)。这里还需要一个关联表来记录每个菜谱中每种食材的具体用量。
3.2 关键表结构设计示例
基于以上分析,我们可以设计出类似以下的表结构(以下为示例,非源码直接拷贝):
用户表 (user)
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `openid` varchar(100) NOT NULL UNIQUE COMMENT '微信开放ID,唯一标识', `nickname` varchar(100) DEFAULT NULL COMMENT '微信昵称', `avatar_url` varchar(500) DEFAULT NULL COMMENT '微信头像URL', `cooking_level` varchar(20) DEFAULT '新手' COMMENT '厨艺等级', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';注意:
openid是微信用户的唯一标识,必须建立唯一索引。切勿存储用户的微信敏感信息。
菜谱表 (recipe)
CREATE TABLE `recipe` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜谱ID', `user_id` int(11) NOT NULL COMMENT '发布者ID', `title` varchar(200) NOT NULL COMMENT '菜谱标题', `cover_image` varchar(500) DEFAULT NULL COMMENT '封面图URL', `description` text COMMENT '菜谱描述', `cuisine_type` varchar(50) DEFAULT NULL COMMENT '菜系', `difficulty` varchar(20) DEFAULT NULL COMMENT '难度', `total_time` int(11) DEFAULT NULL COMMENT '总耗时(分钟)', `view_count` int(11) DEFAULT '0' COMMENT '浏览数', `collect_count` int(11) DEFAULT '0' COMMENT '收藏数', `status` tinyint(4) DEFAULT '1' COMMENT '状态(1正常,0下架)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_cuisine` (`cuisine_type`), KEY `idx_title` (`title`(20)) -- 为标题前缀创建索引以优化模糊查询 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜谱主表';菜谱步骤表 (recipe_step)
CREATE TABLE `recipe_step` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '步骤ID', `recipe_id` int(11) NOT NULL COMMENT '所属菜谱ID', `step_number` int(11) NOT NULL COMMENT '步骤序号', `content` text NOT NULL COMMENT '步骤说明', `image_url` varchar(500) DEFAULT NULL COMMENT '步骤图URL', `timer_duration` int(11) DEFAULT NULL COMMENT '计时时长(秒),可为空', PRIMARY KEY (`id`), KEY `idx_recipe_id` (`recipe_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜谱步骤表';实操心得:
timer_duration字段的设计是个亮点。它允许发布者在编辑步骤时直接设定该步骤需要的计时时间(如“腌制15分钟”对应900秒)。前端在渲染步骤时,如果检测到这个字段有值,就可以在旁边渲染一个“启动计时器”的按钮,极大地提升了用户体验的连贯性。
食材表 (ingredient) 和 菜谱-食材关联表 (recipe_ingredient)
CREATE TABLE `ingredient` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '食材ID', `name` varchar(100) NOT NULL UNIQUE COMMENT '食材名称', `category` varchar(50) DEFAULT NULL COMMENT '食材类别(如蔬菜、肉类)', PRIMARY KEY (`id`), KEY `idx_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材字典表'; CREATE TABLE `recipe_ingredient` ( `id` int(11) NOT NULL AUTO_INCREMENT, `recipe_id` int(11) NOT NULL COMMENT '菜谱ID', `ingredient_id` int(11) NOT NULL COMMENT '食材ID', `quantity` varchar(50) DEFAULT NULL COMMENT '用量(如200g、适量)', PRIMARY KEY (`id`), UNIQUE KEY `uk_recipe_ingredient` (`recipe_id`,`ingredient_id`), -- 防止重复添加 KEY `idx_recipe_id` (`recipe_id`), KEY `idx_ingredient_id` (`ingredient_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜谱食材关联表';这种设计(字典表+关联表)保证了数据的一致性。例如,“西红柿”和“番茄”在系统中会被视为同一种食材,避免了数据冗余和查询混乱。当用户搜索“番茄炒蛋”时,即使菜谱里写的是“西红柿”,也能被正确检索到(这需要在搜索逻辑或食材名称标准化上做处理)。
收藏表 (favorite)和可能的购物清单表 (shopping_list)设计思路类似,都是记录用户与菜谱或食材的关联关系,这里不再赘述。这样的数据库设计,支撑起了整个应用的数据骨架,是SSM后端业务逻辑实现的坚实基础。
4. SSM后端核心代码实现详解
理解了数据库,我们进入后端的核心。SSM框架的分层结构在这里体现得淋漓尽致。我们以“获取菜谱详情”这个典型业务场景为例,走一遍代码流程。
4.1 Controller层:接收请求与返回响应
Controller是前后端的桥梁,它接收小程序端的HTTP请求,解析参数,调用服务,并返回JSON格式的数据。
@RestController // 表明这是一个RESTful风格的控制器,返回JSON数据 @RequestMapping("/api/recipe") public class RecipeController { @Autowired private RecipeService recipeService; /** * 根据ID获取菜谱详情 * @param recipeId 菜谱ID * @return 包含菜谱、步骤、食材的完整对象 */ @GetMapping("/detail/{recipeId}") public ApiResponse<RecipeDetailVO> getRecipeDetail(@PathVariable Integer recipeId) { // 1. 参数校验 (简单示例) if (recipeId == null || recipeId <= 0) { return ApiResponse.error("参数错误"); } try { // 2. 调用Service层获取业务数据 RecipeDetailVO detail = recipeService.getRecipeDetailById(recipeId); // 3. 增加浏览量 (可以异步处理,避免阻塞主流程) recipeService.incrementViewCount(recipeId); return ApiResponse.success(detail); } catch (BusinessException e) { // 4. 捕获已知业务异常,如菜谱不存在 return ApiResponse.error(e.getMessage()); } catch (Exception e) { // 5. 捕获未知异常,记录日志,返回友好提示 log.error("获取菜谱详情异常, recipeId: {}", recipeId, e); return ApiResponse.error("系统繁忙,请稍后再试"); } } }注意事项:Controller层应保持“瘦”,它只负责流程协调、参数校验、权限控制和结果封装。复杂的业务逻辑一定要下沉到Service层。统一的
ApiResponse封装和全局异常处理是提升代码可维护性和前端对接体验的关键。
4.2 Service层:业务逻辑的核心
Service层承载了具体的业务规则。getRecipeDetailById这个方法需要聚合来自多个Mapper的数据。
@Service public class RecipeServiceImpl implements RecipeService { @Autowired private RecipeMapper recipeMapper; @Autowired private RecipeStepMapper stepMapper; @Autowired private RecipeIngredientMapper recipeIngredientMapper; @Override public RecipeDetailVO getRecipeDetailById(Integer recipeId) { // 1. 获取菜谱基本信息 Recipe recipe = recipeMapper.selectById(recipeId); if (recipe == null || recipe.getStatus() == 0) { throw new BusinessException("菜谱不存在或已下架"); } // 2. 获取菜谱步骤列表 List<RecipeStep> steps = stepMapper.selectByRecipeId(recipeId); // 按step_number排序 steps.sort(Comparator.comparingInt(RecipeStep::getStepNumber)); // 3. 获取菜谱所需食材列表(联表查询,包含食材名称和用量) List<RecipeIngredientVO> ingredients = recipeIngredientMapper.selectDetailByRecipeId(recipeId); // 4. 组装成前端需要的视图对象(View Object) RecipeDetailVO detailVO = new RecipeDetailVO(); BeanUtils.copyProperties(recipe, detailVO); // 使用工具类拷贝属性 detailVO.setSteps(steps); detailVO.setIngredients(ingredients); // 5. 可以在此处补充其他业务逻辑,如判断当前用户是否已收藏该菜谱 // detailVO.setHasCollected(checkCollected(currentUserId, recipeId)); return detailVO; } @Async // 使用Spring的@Async实现异步方法,提升接口响应速度 @Override public void incrementViewCount(Integer recipeId) { recipeMapper.incrementViewCount(recipeId); } }实操心得:
RecipeDetailVO是一个专门为前端详情页设计的视图对象,它聚合了多个实体类的数据。这种VO(View Object)模式非常有用,可以避免将数据库实体直接暴露给前端,也能灵活组装数据,是前后端解耦的常见做法。另外,像incrementViewCount这种非核心的、可延迟的操作,使用@Async进行异步化,是一个提升性能的好习惯。
4.3 Mapper层与MyBatis:与数据库对话
Mapper层(或称Dao层)定义了数据操作的接口,具体的SQL写在XML映射文件或通过注解实现。这里展示XML配置的方式,更清晰。
RecipeMapper.xml 片段:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.familychef.mapper.RecipeMapper"> <!-- 根据ID查询菜谱 --> <select id="selectById" parameterType="int" resultType="com.familychef.entity.Recipe"> SELECT * FROM recipe WHERE id = #{id} AND status = 1 </select> <!-- 增加浏览量:使用原子操作避免并发问题 --> <update id="incrementViewCount"> UPDATE recipe SET view_count = view_count + 1 WHERE id = #{recipeId} </update> <!-- 复杂查询示例:分页查询菜谱列表,可按菜系、难度筛选,按热度或时间排序 --> <select id="selectRecipeList" resultType="com.familychef.vo.RecipeListVO"> SELECT r.id, r.title, r.cover_image, r.description, r.difficulty, r.total_time, r.view_count, r.collect_count, u.nickname, u.avatar_url FROM recipe r LEFT JOIN user u ON r.user_id = u.id WHERE r.status = 1 <if test="cuisineType != null and cuisineType != ''"> AND r.cuisine_type = #{cuisineType} </if> <if test="difficulty != null and difficulty != ''"> AND r.difficulty = #{difficulty} </if> <if test="keyword != null and keyword != ''"> AND (r.title LIKE CONCAT('%', #{keyword}, '%') OR r.description LIKE CONCAT('%', #{keyword}, '%')) </if> ORDER BY <choose> <when test="sortType == 'hot'">r.view_count DESC, r.collect_count DESC</when> <when test="sortType == 'time'">r.create_time DESC</when> <otherwise>r.create_time DESC</otherwise> </choose> LIMIT #{offset}, #{pageSize} </select> </mapper>注意事项:MyBatis的动态SQL(
<if>,<choose>)极大地提高了SQL的灵活性。但要注意,LIKE模糊查询在数据量大时性能很差,如果搜索是核心功能,应考虑引入Elasticsearch等搜索引擎。incrementViewCount这样的更新操作,一定要用原子操作(view_count + 1),而不是先查询再更新,这在并发场景下会导致数据错误。
5. 微信小程序前端关键功能实现
后端提供了坚实的API,前端小程序的职责就是创造出流畅的用户体验。我们聚焦几个与“家庭大厨”场景强相关的关键功能点。
5.1 用户登录与状态管理
小程序启动时,首先要处理登录。
// app.js 或独立的auth.js中 App({ onLaunch: function() { this.checkLoginStatus(); }, checkLoginStatus: function() { const token = wx.getStorageSync('access_token'); if (token) { // 验证token是否过期(可发起一个轻量级API请求) this.verifyToken(token); } else { this.wxLogin(); } }, wxLogin: function() { wx.login({ success: (res) => { if (res.code) { // 将code发送到自己的后端服务器 wx.request({ url: 'https://your-domain.com/api/auth/login', method: 'POST', data: { code: res.code }, success: (resp) => { if (resp.data.success) { const { token, userInfo } = resp.data.data; // 存储token和用户信息 wx.setStorageSync('access_token', token); wx.setStorageSync('userInfo', userInfo); // 触发全局登录成功事件 this.globalData.isLoggedIn = true; this.globalData.userInfo = userInfo; } } }); } } }); } })核心逻辑:小程序端调用
wx.login()获取临时code,将这个code发送到自己的后端服务器。后端服务器用appid、secret和这个code,去微信接口服务端换取用户的唯一标识openid和session_key。后端据此创建或查找对应用户,并生成自定义的登录态令牌(如JWT)返回给小程序。绝对不要在前端直接使用code去换openid,secret必须保密在服务器端。
5.2 菜谱详情页与烹饪计时器联动
详情页是核心交互页面,需要将步骤、食材和计时功能有机结合起来。
<!-- recipe-detail.wxml 片段 --> <view class="steps-container"> <block wx:for="{{steps}}" wx:key="stepNumber"> <view class="step-item"> <text class="step-num">步骤{{item.stepNumber}}</text> <text class="step-content">{{item.content}}</text> <!-- 如果有步骤图 --> <image wx:if="{{item.imageUrl}}" src="{{item.imageUrl}}" mode="widthFix"></image> <!-- 如果该步骤需要计时,显示计时器按钮 --> <view wx:if="{{item.timerDuration > 0}}" class="timer-section"> <text>建议时长:{{item.timerDuration}}秒</text> <button size="mini" bindtap="startTimer">// recipe-detail.js Page({ data: { steps: [], ingredients: [], activeTimerIndex: -1, // 当前正在计时的步骤索引 countdown: 0, timer: null // 定时器ID }, onLoad: function(options) { const recipeId = options.id; this.loadRecipeDetail(recipeId); }, // 启动计时器 startTimer: function(e) { const index = e.currentTarget.dataset.index; const duration = this.data.steps[index].timerDuration; if (this.data.timer) { clearInterval(this.data.timer); // 清除上一个计时器 } this.setData({ activeTimerIndex: index, countdown: duration }); // 开始倒计时 const timerId = setInterval(() => { let count = this.data.countdown - 1; if (count <= 0) { clearInterval(timerId); wx.showToast({ title: '时间到!', icon: 'success' }); this.setData({ activeTimerIndex: -1, countdown: 0, timer: null }); } else { this.setData({ countdown: count }); } }, 1000); this.setData({ timer: timerId }); }, // 食材勾选事件 onIngredientCheck: function(e) { const id = e.detail.value; const checked = e.detail.checked; // 更新本地数据中对应食材的checked状态 const ingredients = this.data.ingredients.map(item => { if (item.id == id) { item.checked = checked; } return item; }); this.setData({ ingredients }); // 可以同步到本地缓存或后端,实现清单持久化 wx.setStorageSync(`recipe_${this.data.recipeId}_ingredients`, ingredients); } })实操心得:计时器功能的关键在于管理好定时器的生命周期。在页面
onUnload或切换步骤时,务必用clearInterval清除之前的定时器,防止内存泄漏和状态错乱。食材勾选状态存储在本地Storage中是个不错的方案,即使用户关闭小程序再打开,清单状态也能保留。对于更复杂的清单同步(如多设备),则需要将状态保存到后端。
5.3 图片上传与富文本编辑
发布菜谱功能涉及多图上传和步骤编辑,这是前端的一个难点。
// publish.js - 图片上传示例 uploadStepImage: function(stepIndex) { const that = this; wx.chooseImage({ count: 1, // 一次选一张 success(res) { const tempFilePath = res.tempFilePaths[0]; wx.showLoading({ title: '上传中...' }); wx.uploadFile({ url: 'https://your-domain.com/api/upload/image', filePath: tempFilePath, name: 'file', formData: { 'type': 'step' }, success(resp) { const data = JSON.parse(resp.data); if (data.success) { // 更新对应步骤的图片URL const steps = that.data.steps; steps[stepIndex].imageUrl = data.data.url; that.setData({ steps }); wx.showToast({ title: '上传成功', icon: 'success' }); } }, complete() { wx.hideLoading(); } }); } }) }注意事项:微信小程序上传文件有大小限制(通常单个文件不超过10MB)。对于菜谱步骤图,在上传前最好用
wx.compressImageAPI进行压缩。后端接口需要做好文件类型校验、重命名(防止文件名冲突)、以及存储到可靠的位置(如云存储或CDN)。对于富文本,小程序原生不支持,通常采用分段编辑的模式(每个步骤一个文本域+图片),或者集成第三方富文本编辑器组件(如wxParser用于渲染HTML),但后者交互可能较重,需权衡。
6. 项目部署与运维注意事项
一个完整的项目,开发完成只是第一步,让它能稳定地跑起来才是关键。
6.1 后端部署(以Linux服务器+Tomcat为例)
- 环境准备:确保服务器已安装JDK(1.8或以上)、MySQL、Tomcat或Jetty等Servlet容器。
- 数据库初始化:将项目SQL脚本(通常在
/sql目录下)在MySQL中执行,创建数据库和表结构。 - 项目打包:在项目根目录下,使用Maven命令
mvn clean package -DskipTests进行打包。这会在target目录下生成一个war包(如family-chef.war)。 - 配置修改:将打包好的
war包上传到服务器的Tomcatwebapps目录下。至关重要的一步是修改项目配置文件(如application.properties或jdbc.properties),将数据库连接地址、用户名、密码从本地的localhost改为服务器的实际地址。同时,检查文件上传路径、微信小程序appid和secret等配置是否正确。 - 启动服务:启动Tomcat,它会自动解压
war包并部署应用。访问http://服务器IP:端口/项目名(例如http://123.45.67.89:8080/family-chef)查看是否启动成功。可以使用tail -f logs/catalina.out命令实时查看日志,排查启动错误。
6.2 微信小程序配置与上线
- 服务器域名配置:在小程序管理后台的“开发”->“开发设置”中,将你的后端API域名(如
https://your-domain.com)添加到“服务器域名”的request合法域名列表中。如果使用了文件上传,还需配置uploadFile和downloadFile域名。务必注意:域名必须备案,且支持HTTPS。 - AppID与密钥:确保后端代码中配置的微信小程序
AppID和AppSecret与管理后台的一致。AppSecret是最高机密,绝不能泄露在前端代码中。 - 版本提交与审核:在微信开发者工具中上传代码,提交审核。审核通过后,即可发布线上版本。注意,小程序代码包有大小限制(目前主包2M,总包20M),对于图片等资源,强烈建议使用CDN。
6.3 常见运维问题与排查技巧
即使部署成功,在运行过程中也可能遇到各种问题。这里记录几个典型的排查场景:
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 小程序请求后端API报错“不在以下 request 合法域名列表中” | 1. 域名未配置或配置错误。 2. 开发者工具未勾选“不校验合法域名”。 | 1. 检查小程序后台域名配置。 2. 线上环境必须配置,开发阶段可在工具中临时勾选不校验。 |
| 图片上传失败或无法显示 | 1. 服务器上传目录权限不足。 2. 返回的图片URL路径错误。 3. CDN未刷新缓存。 | 1. 检查服务器上存储目录的读写权限(chmod)。 2. 查看后端上传接口返回的URL,是否可直接在浏览器访问。 3. 清理CDN缓存或检查防盗链设置。 |
| 数据库连接超时或缓慢 | 1. 数据库服务器性能瓶颈。 2. 连接池配置不当。 3. SQL查询未走索引。 | 1. 监控服务器CPU、内存、数据库连接数。 2. 优化MyBatis连接池(如Druid)参数。 3. 对慢查询日志进行分析,为常用查询字段添加索引。 |
| 微信登录失败,无法获取用户信息 | 1.code过期(5分钟)。2. 后端向微信服务器换 openid的请求失败。3. AppSecret错误或重置。 | 1. 确保wx.login成功后立即将code发送到后端。2. 查看后端日志,确认调用微信接口的网络和响应状态。 3. 核对小程序后台的 AppSecret。 |
| 定时器(如烹饪计时)在后台运行不准 | 小程序切到后台后,定时器可能被减速或暂停。 | 对于需要精确长时间计时的场景,考虑使用wx.setInterval并结合后台提醒,或提示用户保持前台运行。 |
我个人在实际部署这个案例时的体会是,配置文件的管理是个大学问。建议将开发、测试、生产环境的配置完全分离,可以使用Spring的Profile机制,通过启动参数指定spring.profiles.active=prod来加载不同的配置文件。这样能极大避免因配置错误导致的线上事故。另外,日志一定要打好,尤其是异常捕获的地方,要打印出足够的上下文信息(如用户ID、请求参数),这样当用户反馈问题时,你才能快速定位。对于这种个人或小团队项目,初期可以不用复杂的微服务和容器化,但基本的备份(代码、数据库)和监控(服务器基础资源、应用健康检查接口)一定要做,这是保证项目能长期稳定运行的底线。
本文还有配套的精品资源,点击获取