SSM+微信小程序实战:家庭菜谱管理系统的设计与开发全解析
2026/9/5 14:17:39 网站建设 项目流程

简介:本资源是一套完整的‘家庭大厨’微信小程序毕业设计案例,面向计算机专业本科生及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 核心功能模块设计

打开项目,你会发现它的功能模块围绕“家庭大厨”的核心场景展开,主要分为以下几块:

  1. 用户中心:包括微信一键登录、个人资料管理(如厨艺等级、擅长菜系)、我的收藏、我的发布等。这里的设计关键在于利用微信的wx.logingetUserProfile接口安全地获取用户身份,并在后端生成自定义的登录态令牌(如JWT或Session)来维持会话。
  2. 菜谱广场:这是小程序的门面。通常以信息流或分类(如川菜、烘焙、快手菜)的形式展示菜谱列表。每个菜谱卡片包含封面图、标题、难度、耗时、收藏数等。设计上需要考虑图片的懒加载、下拉刷新和上拉加载更多,以保障流畅的浏览体验。
  3. 菜谱详情:这是功能最集中的页面。不仅展示详细的步骤图文、所需食材及用量,还应集成烹饪计时器食材清单勾选功能。用户可以在浏览时,一键将所需食材加入购物清单,并在实际烹饪时,根据步骤启动相应的计时器(例如,“小火焖煮20分钟”)。
  4. 发布菜谱:允许用户上传自己的拿手菜。这涉及到富文本编辑(或分步骤的图文上传)、多张图片上传、食材和用量的动态表单添加。后端需要处理文件上传(图片存储到云存储或本地服务器)和复杂表单数据的接收。
  5. 智能清单:由“购物清单”和“厨房存货”两部分演化而来。系统可以根据用户收藏或计划的菜谱,自动合并生成一份去重后的总购物清单。用户也可以在“厨房存货”中手动录入现有食材,系统或许能反向推荐可制作的菜谱(这是一个可扩展的亮点功能)。
  6. 搜索与分类:除了按菜系、难度、时间分类,强大的搜索功能必不可少。应支持按菜名、食材、甚至模糊描述(如“下饭菜”、“夏天汤”)进行搜索。后端这里可能会用到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发送到自己的后端服务器。后端服务器用appidsecret和这个code,去微信接口服务端换取用户的唯一标识openidsession_key。后端据此创建或查找对应用户,并生成自定义的登录态令牌(如JWT)返回给小程序。绝对不要在前端直接使用code去换openidsecret必须保密在服务器端。

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为例)

  1. 环境准备:确保服务器已安装JDK(1.8或以上)、MySQL、Tomcat或Jetty等Servlet容器。
  2. 数据库初始化:将项目SQL脚本(通常在/sql目录下)在MySQL中执行,创建数据库和表结构。
  3. 项目打包:在项目根目录下,使用Maven命令mvn clean package -DskipTests进行打包。这会在target目录下生成一个war包(如family-chef.war)。
  4. 配置修改:将打包好的war包上传到服务器的Tomcatwebapps目录下。至关重要的一步是修改项目配置文件(如application.propertiesjdbc.properties),将数据库连接地址、用户名、密码从本地的localhost改为服务器的实际地址。同时,检查文件上传路径、微信小程序appidsecret等配置是否正确。
  5. 启动服务:启动Tomcat,它会自动解压war包并部署应用。访问http://服务器IP:端口/项目名(例如http://123.45.67.89:8080/family-chef)查看是否启动成功。可以使用tail -f logs/catalina.out命令实时查看日志,排查启动错误。

6.2 微信小程序配置与上线

  1. 服务器域名配置:在小程序管理后台的“开发”->“开发设置”中,将你的后端API域名(如https://your-domain.com)添加到“服务器域名”的request合法域名列表中。如果使用了文件上传,还需配置uploadFiledownloadFile域名。务必注意:域名必须备案,且支持HTTPS。
  2. AppID与密钥:确保后端代码中配置的微信小程序AppIDAppSecret与管理后台的一致。AppSecret是最高机密,绝不能泄露在前端代码中。
  3. 版本提交与审核:在微信开发者工具中上传代码,提交审核。审核通过后,即可发布线上版本。注意,小程序代码包有大小限制(目前主包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、请求参数),这样当用户反馈问题时,你才能快速定位。对于这种个人或小团队项目,初期可以不用复杂的微服务和容器化,但基本的备份(代码、数据库)和监控(服务器基础资源、应用健康检查接口)一定要做,这是保证项目能长期稳定运行的底线。

本文还有配套的精品资源,点击获取

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

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

立即咨询