ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

膳食营养健康网站:SpringBoot+Vue前后端分离设计与实现

膳食营养健康网站:SpringBoot+Vue前后端分离设计与实现 我做毕业设计的时候选了膳食营养健康网站这个方向最初的想法很简单现在大家越来越关注吃得好不好、吃得健不健康做一个能查食物营养、能记录饮食、能给出合理建议的网站既有实际意义也适合用来练习完整的全栈开发。整个系统用SpringBoot Vue MySQL MyBatis这套Java技术栈来实现后端负责业务逻辑和数据存储前端负责页面展示和交互。这篇文章我就把整个系统的设计与实现过程拆开来讲从需求分析、数据库设计、后端分层、前端页面到最后的打包部署手把手还原我是怎么一步步把这个项目做出来的希望能给正在做类似课题或者想入门前后端分离开发的朋友一个可参考的完整路径。1. 膳食营养网站的模块划分与需求拆解很多同学拿到这种题目第一反应是“赶紧写代码”但真正做过项目的人都清楚前期把需求拆清楚后面能少走一半弯路。这个系统本质上是一个信息管理类平台但它和普通的商品管理系统不一样的地方在于它需要处理“营养数据”这种专业性较强的信息而且用户的饮食记录会持续积累涉及大量查询和统计分析的需求所以我在最开始就先明确了系统的角色和功能边界。1.1 用户角色的划分与权限设计系统面向的使用者主要分成两类普通用户和管理员。普通用户做的事情很直观——注册登录、浏览食物营养信息、查看推荐食谱、记录每天的饮食、查看自己的营养摄入分析。管理员则负责后台维护管理食物数据、管理食谱分类、审核用户发布的评价内容、发布健康资讯文章。这里有一点需要特别注意用户和管理员的权限控制必须从后端做而不是只在前端隐藏按钮否则别人直接构造URL就能访问管理接口这个系统的安全性就形同虚设了。我在SpringBoot里用拦截器HandlerInterceptor做了一层登录校验再配合一个简单的角色判断。用户在登录成功之后后端会返回一个包含用户ID和角色信息的Token前端把这个Token存在本地后续每一次请求都在请求头里带上。拦截器统一判断白名单里的路径比如登录接口、注册接口直接放行其余接口必须先过登录校验管理相关的接口再加一层角色校验。这样一个拦截面下来权限控制的逻辑集中在了一起不用在每个Controller里重复写判断代码。1.2 核心功能模块清单与业务流程整个网站的功能模块我划分成了八个核心部分模块名称核心功能面向角色用户认证注册、登录、退出登录普通用户食物数据管理食物营养信息增删改查管理员营养信息展示按分类浏览食物、搜索食物普通用户饮食记录记录每餐摄入的食物与份量普通用户营养分析统计当日/历史营养摄入情况普通用户食谱推荐根据营养目标推荐食谱普通用户健康资讯管理发布、维护健康知识文章管理员评论互动对食物和食谱发表评论普通用户业务流程里最核心的一条线是用户登录 → 在前端选择食物 → 记录为一条饮食记录 → 后端根据食物表中每100克所含的热量、蛋白质、脂肪、碳水化合物乘以实际摄入的份量算出这一餐的营养数据 → 当天的多条记录汇总后与推荐摄入量做对比生成反馈图表。这条链路是网站的“灵魂”数据库表设计和后端接口设计都要为它服务。2. 数据库表结构设计与营养数据建模数据库设计做得好不好直接决定后面写代码的时候是舒舒服服还是被各种LEFT JOIN折磨。我花了挺长时间梳理表之间的关系画了好几版ER图才最终定下来。整个系统一共设计了六张核心表围绕两个核心对象展开食物和饮食记录。2.1 核心表结构说明第一张是用户表user除了常规的ID、用户名、密码、昵称、邮箱之外我还加了身高、体重、年龄、性别、活动强度这几个字段。为什么加这些因为后面做“每日推荐摄入量”计算需要用到哈里斯-本尼迪克特公式通过性别、年龄、身高、体重算出基础代谢率再乘以活动系数得到每日消耗热量这组字段是营养推荐功能的数据基础。第二张是食物表food这是整个系统最基础的数据表。我设计的字段包括食物名称、分类主食、蔬菜、水果、肉蛋奶、豆制品等、热量、蛋白质、脂肪、碳水化合物、膳食纤维单位统一按“每100克可食部分”记录。CREATE TABLE food ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 食物名称, category varchar(50) DEFAULT NULL COMMENT 食物分类, calorie decimal(10,2) DEFAULT 0 COMMENT 热量千卡/100g, protein decimal(10,2) DEFAULT 0 COMMENT 蛋白质克/100g, fat decimal(10,2) DEFAULT 0 COMMENT 脂肪克/100g, carbohydrate decimal(10,2) DEFAULT 0 COMMENT 碳水化合物克/100g, fiber decimal(10,2) DEFAULT 0 COMMENT 膳食纤维克/100g, image varchar(255) DEFAULT NULL COMMENT 食物图片, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;第三张是饮食记录表meal_record它本质上是一个关联表记录“哪个用户在哪一天哪个餐次吃了什么食物、吃了多少克”。字段包括记录ID、用户ID、食物ID、餐次类型早餐/午餐/晚餐/加餐、摄入量克、记录日期。摄入量这个字段非常关键后端在做营养分析时就是拿它乘以食物表中的每100克营养值再除以100。第四张是食谱表recipe存的是菜谱信息包括菜名、主料配料、制作步骤、适合的饮食目标减脂、增肌、均衡等、图片路径。第五张是评论表comment关联用户、食物或食谱、评论内容和时间。第六张是资讯表article管理员发布健康科普文章用。2.2 表关系与数据一致性设计考量表关系上用户和饮食记录是一对多食物和饮食记录也是一对多评论表则设计成了多态关联——用target_type字段区分这条评论是给食物还是给食谱再用target_id指向对应的ID。这样设计能少建一张表代价是查询时需要在代码里判断目标类型逻辑上稍微复杂一点但整体利大于弊。营养数据的一致性问题我在做的时候也踩过坑。比如食物表里热量单位是“千卡/100克”用户在记录饮食时输入的单位是“克”前端如果没做说明用户可能直接把“1份”当成1克来填那营养分析结果就会完全失真。我的处理方式是前端表单里明确标注“请输入食用量克”并在后端的VO视图对象里把营养数值的计算结果直接算好返回给前端前端只管展示不在浏览器里做乘除法避免因前端计算精度问题导致数据偏差。3. SpringBoot后端分层设计与MyBatis实战后端我严格按照三层架构来做Controller层负责接收参数和返回响应、Service层负责业务逻辑、Mapper层负责数据库操作。实体类用通用泛型封装返回结果——ResultT统一包含状态码、消息和数据三个字段前端拿到之后统一处理对错分明。3.1 项目初始化与依赖配置我用的是SpringBoot 2.7.x版本。这里要提醒一点SpringBoot版本不要盲目求新有些较新的版本和MyBatis官方启动器存在兼容性问题如果不匹配启动时会出现各种奇怪的报错。我之前就遇到过版本冲突后来固定用2.7.x加MyBatis 2.3.x的组合稳定性就好多了。核心依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency配置文件里除了常规的数据源配置还有两个容易被忽略的点一个是mybatis.mapper-locations必须指向XML文件的位置比如classpath:mapper/*.xml另一个是mybatis.configuration.map-underscore-to-camel-casetrue。这个配置项特别实用开启之后数据库的user_name字段能自动映射到Java实体类的userName属性省掉一大波手动映射代码。3.2 从Controller到Mapper的完整请求链路以“获取食物列表分页按名称模糊搜索”这个接口为例完整的链路是这样的Controller层最薄只做参数接收和结果封装RestController RequestMapping(/api/food) public class FoodController { Resource private FoodService foodService; GetMapping(/list) public ResultPageResultFoodVO list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String keyword) { return Result.success(foodService.pageQuery(pageNum, pageSize, keyword)); } }Service层承载业务逻辑分页查询要先用PageHelper插件做物理分页再手动把PO持久化对象转成VO返回给前端。这里我解释一下为什么要转VO实体类里的创建时间、更新时间这些字段用户根本不需要看到而且食物表中每条数据对应的“营养标签”在前端展示时需要格式化直接用实体类返回会暴露不需要的字段用VO隔离一层更安全也更好维护。Mapper层是MyBatis的核心玩法我在XML文件里写了动态SQL来处理可选的关键词搜索条件select idpageQuery resultTypecom.example.entity.Food SELECT * FROM food where if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if /where ORDER BY id DESC /selectwhere标签的妙处在于它会自动处理第一个条件前面的AND——如果没有关键词它生成的SQL就是干净的SELECT * FROM food不会出现多余的关键字。这一点对于新手来说非常容易写错手动拼接SQL多了个AND就会直接报语法错误我在初期就犯过好几次。分页这一块的实现我用的是PageHelper在Service里写完查询逻辑后调用PageHelper.startPage(pageNum, pageSize)再执行查询返回的结果就是一个已经分页好的列表。很多人不知道PageHelper的原理——它是用了MyBatis的拦截器机制在执行查询前自动改写SQL在原始的SELECT语句外面包了一层带LIMIT的查询。不过用PageHelper有个必须注意的坑startPage方法后面必须紧跟着第一条查询语句中间如果插了其他数据库操作分页就失效了而且很多人在service里写循环调用查询时还会遇到分页参数被复用的问题所以我的习惯是分页查询单独写一个方法尽量保持它纯粹。3.3 登录鉴权与敏感操作的保护用户的密码我做了加密存储用的是BCrypt算法。很多人喜欢自己在代码里写MD5加密这其实是很不安全的做法——MD5是摘要算法存在彩虹表暴力破解的威胁。BCrypt的优势在于同一段明文每次加密生成的密文都不一样因为里面混入了随机盐值而且它的计算过程相对耗时这反而成了优点——暴力破解的成本大大增加。SpringBoot里集成BCrypt非常方便用spring-security-crypto这个独立模块就可以不需要引入完整的Spring Security不会给项目增加额外的拦截逻辑。4. Vue前端页面设计与核心交互实现前端我用的Vue 2 Element UI这套经典组合。选Vue 2不是说Vue 3不好而是因为这个项目的生态兼容性、Element UI的成熟度、以及网上能查到的资料都是Vue 2的居多遇到问题更容易解决对于毕业设计或者中小型管理系统来说完全够用。4.1 页面路由与整体结构规划前端页面分成两大部分用户端的网站页面和管理端的后台页面我用Vue Router设置了不同的布局组件来区分。用户端包括以下页面首页推荐位最新资讯、食物库分类搜索列表、食物详情营养数据评论、食谱页面、个人中心我的资料、饮食记录页面记录三餐、营养分析报告页。管理端包括登录页、食物管理、食谱管理、资讯管理、评论审核。路由配置里我会做全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path.startsWith(/admin) !token) { next(/login); } else { next(); } });这里有一点要说明白前端的路由守卫只能改善用户体验它真正的安全边界必须由后端接口的权限拦截来兜底。前端没有Token就跳转登录页只是让用户“看不到”后台界面但后端接口没有鉴权的话别人抓包分析出接口地址照样可以绕过前端直接调用所以前后端两层防护一个都不能少。4.2 axios封装与接口调用模式整个项目前端的异步请求全部通过axios发出。我在utils/request.js里做了统一封装核心逻辑是四件事设置基础URL、请求头带Token、统一处理业务状态码200代表成功其他代表失败、后端Session过期时自动跳转登录页。import axios from axios; const request axios.create({ baseURL: /api, timeout: 10000 }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] token; } return config; }); request.interceptors.response.use( response { const res response.data; if (res.code ! 200) { if (res.code 401) { localStorage.removeItem(token); window.location.href /login; } return Promise.reject(new Error(res.message)); } return res; }, error { return Promise.reject(error); } );特别提一下baseURL: /api这个配置。我开发的时候前端是localhost:8081后端是localhost:8080跨域是绕不开的问题。我的做法不是在前端配代理而是在后端加CORS配置。部署上线的时候前后端同域这样一个配置在开发和生产环境下都是通的。不过在本地开发时如果你直接把前端页面打开file://协议调试还是会遇到跨域问题最省事的方式是用Vue CLI的devServer代理转发但在做前后端分离演示时我个人更喜欢后端CORS全局放开这种方式代码改动少演示也方便。4.3 营养分析页面的数据可视化营养分析页面是整个网站最能体现“健康网站”调性的功能。用户选择某一天默认今天后端会返回这一天摄入的总热量、蛋白质、脂肪、碳水化合物的数值同时返回根据个人资料算出的推荐值。前端拿到两组数据之后用ECharts画了一张对比图表。我用的是雷达图来展示蛋白质、脂肪、碳水化合物这三项与推荐值的对比关系蓝色多边形代表推荐值绿色多边形代表实际摄入值两者重叠度越高说明当天的三大营养素配比越均衡。为了让这个图有意义我专门设计了后端的计算逻辑推荐值不能写死而是通过用户的身高体重年龄计算基础代谢率再乘以活动系数得到每日消耗热量然后按供能比分别计算出三大营养素的推荐克数。// 营养分析图表渲染核心逻辑 drawRadarChart(actualData, recommendedData) { const chart echarts.init(this.$refs.radarChart); chart.setOption({ radar: { indicator: [ { name: 蛋白质, max: 200 }, { name: 脂肪, max: 100 }, { name: 碳水化合物, max: 400 } ] }, series: [{ type: radar, data: [ { value: [recommendedData.protein, recommendedData.fat, recommendedData.carbohydrate], name: 推荐摄入 }, { value: [actualData.protein, actualData.fat, actualData.carbohydrate], name: 实际摄入 } ] }] }); }这里图表放在一个区域里显示为了能看到及时更新的数据我在用户新增一条饮食记录后主动调用一次获取分析数据的方法替换掉旧的图表数据。有一个细节值得注意ECharts实例在组件销毁时需要调用dispose()释放否则页面频繁切换路由会导致内存泄漏页面也会越用越卡。5. 饮食记录与营养分析的算法细节这个部分是整个项目的技术核心也是答辩的时候最容易被老师追问“你这系统到底有什么技术亮点”的地方。我一直认为前面那些增删改查功能说白了都是常规操作真正的价值体现在把用户的饮食行为转化成有意义的营养学结论。5.1 单餐营养计算的实现方式先看最基础的单条饮食记录。用户在前端选择了一个食物比如“米饭”填了摄入量200克前端提交的数据是{ userId, foodId, mealType, amount, date }。后端拿到请求后先从食物表查出米饭的营养数据——每100克含热量116千卡、蛋白质2.6克、脂肪0.3克、碳水化合物25.9克——然后做一次简单的比例换算存入当下的营养快照BigDecimal calorie food.getCalorie() .multiply(new BigDecimal(amount)) .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);这里我故意在写入记录的那一瞬间就把营养数值算好并冗余存储而不是在查询分析的时候再去关联食物表计算。为什么用冗余存储因为用户隔了几个月回来看自己的饮食记录如果这时候管理员把食物数据修正了比如把某食物的热量值更新了那用户的历史记录也应该保持原来的样子不能跟着变。这种“写时计算”的快照设计在金融账本、订单系统里很常见放在饮食记录里同样成立。5.2 每日摄入汇总与推荐值计算一天的营养汇总逻辑就是把这个用户当天所有记录的蛋白质、脂肪、碳水化合物、热量分别求和。SQL用一个GROUP BY可以轻松做到SELECT SUM(calorie) AS totalCalorie, SUM(protein) AS totalProtein, SUM(fat) AS totalFat, SUM(carbohydrate) AS totalCarbohydrate FROM meal_record WHERE user_id #{userId} AND record_date #{date}推荐值计算这块稍微复杂一点。我先用哈里斯-本尼迪克特公式计算基础代谢率BMR这个公式区分男女男性BMR 88.362 (13.397 × 体重kg) (4.799 × 身高cm) - (5.677 × 年龄)女性BMR 447.593 (9.247 × 体重kg) (3.098 × 身高cm) - (4.330 × 年龄)算完BMR之后乘上活动系数久坐族1.2轻度活动1.375中度活动1.55高强度1.725。得出每日消耗总热量后按中国居民膳食指南的供能比——碳水化合物占50%-65%蛋白质占10%-20%脂肪占20%-30%——取中值碳水55%、蛋白质15%、脂肪25%再分别除以各自的产能系数碳水4千卡/克、蛋白质4千卡/克、脂肪9千卡/克就得到推荐克数。这些计算全部封装在NutritionAnalyzer组件里和数据库操作完全解耦单测非常好写。5.3 食谱推荐功能的规则食谱推荐我用的不是复杂的协同过滤算法而是基于营养标签的规则匹配。每份食谱预先标注了适合的饮食目标减脂、增肌、均衡同时在后端根据食材汇总计算出这份食谱的预估营养构成。推荐逻辑是这样的用户进入推荐页面时选择目标比如“减脂”系统优先筛选目标匹配的食谱再按总热量从低到高排序同时保证单份食谱的蛋白质比例不低于15%。这套规则简单稳定解释起来也很直白对于这个体量的项目来说可解释性比算法复杂度重要得多。6. 部署联调与开发中遇到的几个大坑项目做完之后我把前后端分别打包部署到服务器上测试整体运行效果。部署方式我用的是最经典也最容易排查问题的方式后端打包成Jar包用java -jar直接跑前端构建后生成的dist目录里的静态文件交给Nginx托管。整体的访问路径很清晰这也是大多数Java单体项目的标准落地方案。6.1 本地联调环境的CORS配置开发时的跨域问题我在前面提过后端用CORS配置解决。SpirngBoot里只需要一个配置类就可以全局开启Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true); } }这个配置遇到过一个问题allowedOrigins(*)和allowCredentials(true)在较新的SpringBoot版本里不能同时使用否则启动会报When allowCredentials is true, allowedOrigins cannot contain the special value *。解决方式就是像我上面这样用allowedOriginPatterns(*)来替代既能允许所有来源又能携带凭证信息。6.2 Vue打包之后放进SpringBoot的单体部署还有一种常见的演示方式是直接把Vue打包后的静态资源放进SpringBoot的src/main/resources/static目录这样前后端合并成一个Jar包双击就能跑起来。这种方式对答辩演示特别友好不用装Nginx拿到哪个电脑都能跑。操作上要注意两步第一步是Vue的构建配置在vue.config.js里把publicPath设为./否则静态资源路径是绝对路径放到Jar包里会找不到JS和CSS文件第二步是后端放行静态资源SpringBoot默认会拦截所有请求需要在配置里把页面路由的交还给前端处理同时确保后端API的/api/**路径仍然走Controller。Override public void addViewControllers(ViewControllerRegistry registry) { registry.addRedirectViewController(/, /index.html); }其实前端用的history路由模式在打包放进SpringBoot之后刷新页面会出现404因为请求路径被当作后端接口处理了最省事的做法是前端用hash模式路由路径里自动带#绕开这个坑。一个小项目为了稳定优先我直接用hash模式不折腾history模式的回退配置。另外动态端口配置这些也可以灵活处理我习惯把后端的端口设置为server.port8081避免和本地其他服务的开发端口冲突。6.3 MyBatis最常见的两个坑第一个是SQL语句里用了或符号。MyBatis的XML在解析时会把当作标签起始符如果你在SQL里写age 18就会直接报XML解析错误。解决方式是用转义字符lt;代表小于、gt;代表大于或者更优雅的做法是把这类判断都写在![CDATA[ ... ]]里CDATA段内不需要任何转义。第二个坑和缓存有关。MyBatis一级缓存在同一个SqlSession里有效默认是开启的很多人写循环里多次点查的时候能感受到性能优化但Spring集成MyBatis之后每次数据库操作默认都开启一个新的SqlSession一级缓存基本等于失效。如果项目内有频繁读取且不常变动的数据比如食物分类列表要手动开启二级缓存才有效果。我在这类数据对应的Mapper上加了cache/配置实测下来分类接口的响应时间明显下降。不过二级缓存有个副作用在多表联合查询时容易出现脏数据所以我的建议是只对基本不会变的字典类数据开启业务数据查询慎用。6.4 数据库初始化与部署环境准备数据库脚本我用的是自动执行策略在SpringBoot的配置文件里设置spring.sql.init.schema-locationsclasspath:sql/schema.sql应用启动时自动建表和插入初始食物数据。这样整个项目无论拿到哪里只要本机装了MySQL改了账号密码就能直接跑起来不需要手动去执行数据库脚本导入。我不建议他们把数据初始化这份工作省掉因为系统里的食物营养库如果是一片空白那你演示的时候连截图都不好看。我在初始SQL里预置了大约一百条常见食物的营养数据覆盖米饭、面条、鸡蛋、牛奶、鸡胸肉、各类蔬菜水果等保证用户一进去就有东西可看可查。7. 前端体验细节与移动端适配的思路现在大家用手机浏览器访问网站的比例越来越高虽然这种管理系统大多数场景用电脑但我觉得前端适配做得稍微好一点整套系统的完成度就会高一个档次。我用Element UI的栅格布局重写了几个列表页让它在手机宽度下自动从多列变成单列。7.1 组件化抽取与复用在写前端的过程中我强烈体会到一个老生常谈的道理页面多了以后组件化是唯一的解药。食物列表页和食谱列表页长得非常像——都是卡片列表、都有搜索框、都有分页——所以我抽了一个ListCard组件出来把数据加载、分页逻辑、空状态展示全部封装好页面之间传不同的API路径和字段配置就行。这也导致后期我加新页面的时候核心代码只写了几十行大部分功能都是组装出来的。7.2 图片加载的轻量化处理系统里涉及食物图片、食谱图片和资讯封面图我统一存放在本地静态目录下数据库只存相对路径。图片体积控制是个容易被忽视的问题某次我在测试环境放了几张手机拍的高清照片页面加载速度肉眼可见地变慢。后来我把所有上传图片做了压缩处理统一转成WebP格式并且在前端用懒加载指令v-lazy处理图片列表滚动到视口附近才加载整个页面的首屏加载速度提高了一倍多。这个体验问题在答辩演示时特别加分因为老师的电脑通常不会太好网络也不一定快图片全部瞬间加载完成和一张张慢慢转圈是完全不同的观感。8. 写在最后的几点实操心得项目从零到一做完回头来看有几个心得体会值得在这里分享。首先选题决定了项目走向膳食营养管理这种和生活场景强相关的系统天然适合用技术去呈现“数据价值”在答辩讲解时比单纯的CRUD系统好讲故事因为你可以现场给老师演示输入身高体重年龄记录一顿饭系统自动告诉你摄入超标了没有这种交互反馈是看得见摸得着的。其次SpringBoot Vue这种前后端分离架构在开题时容易被老师问“分离和传统JSP有什么区别”。你要准备一套清晰的话述前后端职责分离可以并行开发、接口复用、部署弹性更强但同时也引入了跨域、鉴权、部署复杂度上升等代价。你能主动说出这套架构带来的成本和权衡比单纯说“分离好”要有说服力得多。再有一个经验是文档和代码注释一定要同步写。我前后写了三百多行核心的SQL和一百多个接口如果不在写代码的时候顺手把注释补上过一周自己都记不清某个字段的含义。答辩前老师一般会翻你的源码注释清晰的项目印象分很高。最后给同样在做这个方向的同学一个明确的开工顺序建议先搭数据库表结构再写后端的接口和单元测试接口全部跑通之后再写前端页面。不要一头扎进前端写UI因为前端的每个列表和表单都对应后端的接口后端接口不稳定前端代码写了大概率也要推翻重来。我是按照“食物库管理 → 用户认证 → 饮食记录 → 营养分析 → 食谱推荐 → 资讯评论”的顺序推进的每完成一个闭环就自测一遍整个开发过程没有出现大规模返工整体节奏稳扎稳打。希望这篇拆解能帮到正在做类似课题的你如果你在数据库设计或者营养计算这部分有更好的思路也欢迎一起交流。
返回列表