SpringBoot茶饮门店点餐系统设计:从数据库到订单状态机的完整实践
发布时间:2026/10/2 15:23:27来源:尧图网络
其实一开始我的毕业设计题目并不叫这个名字。老师给的方向是“基于Java的点餐系统”太宽泛了。我翻了十几篇相关论文最后把范围锁定在“茶饮门店数字化运营平台”上原因是实际需求复杂度正好纯奶茶店的点餐链路太扁平三张表就能写完答辩时没有内容可讲而新中式茶馆的场景要复杂得多茶底、温度、糖度、加料是多维度选项订单状态变化更碎既有堂食又有外带和零售茶包。这种复杂度对开发来说是个麻烦但对毕业设计来说恰恰是素材——系统有得写、有得讲也能把Java和SpringBoot的各种技术点落到可以演示的功能上。系统最终做完答辩顺利通过题目定稿为“基于Java的茶饮门店数字化运营平台——SpringBoot框架下的新中式茶馆智能服务系统”。这篇文章我会按需求分析、技术选型、数据库设计、核心接口实现、故障排查、答辩准备这几个维度展开把整个项目最值得写的东西——踩过的坑、验证过的方案、能直接用的代码思路——都整理出来给正在做同类点餐系统选题的同学一份真实可复现的参考。1. 先搞清楚它解决什么问题角色、流程与场景拆解做毕设最容易犯的错就是一上来写代码。我见过太多同学把“点餐系统”做成一堆增删改查页面答辩老师问一句“你这个系统解决了门店什么实际问题”当场答不上来。所以在动手之前我花了一星期做需求拆解这个步骤在后来的答辩中发挥了巨大作用。1.1 四类使用者决定了两条完全不同的业务链路该系统在设计时明确划分出四个角色顾客、门店服务员、后厨、系统管理员。前三个角色都围绕“点餐”这条主链路运转只有管理员面对的是“运营”这条数据链路。这两条链路的产品逻辑完全不同。顾客侧的堂食点餐流程是扫码进入点餐页 → 选择茶饮并配置规格茶底、温度、糖度、加料 → 加购 → 提交订单 → 模拟支付 → 等待制作 → 订单状态实时更新 → 取餐/评价。这里最关键的体验是“订单状态可见”顾客需要能实时看到自己那杯茶是被接受、制作中还是已完成。服务员侧的流程是桌台管理开台、换台、并台、协助顾客下单、处理异常订单退款、修改、查看当日营收。其中桌台状态的管理必须和订单状态联动比如桌台被占用了就不能再重复开台。后厨侧的流程则纯粹得多只看“待制作订单队列”按时间顺序处理每完成一单就更新状态。这里不需要顾客信息只需要订单号、商品名、规格选项和制作要求。管理员侧的数字化运营功能包括菜品上下架、分类管理、库存预警、会员储值、营业报表日营收、月营收、畅销饮品排行、时段客流分析。这部分的报表能力才是“数字化运营平台”这个题目的灵魂如果只做点餐而不做数据统计这个标题的后半段就落空了。1.2 新中式茶馆不是奶茶店场景差异决定了功能边界我见过不少资料把茶饮系统直接做成“奶茶店模板”看起来差不多但实际上新中式茶馆有三个场景差异必须体现第一是商品规格维度多。一杯茶饮可能有茶底选择、冷热选择、糖度选择、加料选择甚至还能选杯型。这四个维度如果全部硬编码成SKU商品表会爆炸。比较合理的做法是把规格做成JSON配置项每种商品关联一份规格模板下单时让顾客勾选后端校验合法性。第二是出餐节奏差异大。手冲纯茶和奶盖茶的制作时间能差出十分钟后厨队列不能简单按下单时间排需要在订单上记录预计制作时长后厨大屏按“预计时长下单时间”综合排序。第三是茶点零售与饮品并存。新中式茶馆的营收结构里茶点、茶叶、周边产品往往占了不小比例这类商品不需要规格配置点单逻辑更接近“购物车直购”。所以系统里要把“规格商品”和“普通商品”两种下单模式兼容在同一个购物车里。1.3 我最后确定的功能清单经过上述分析我把功能范围收敛成下表。这个表直接影响后续数据库设计和接口设计模块核心功能角色说明用户认证登录、注册、角色鉴权全部JWT token实现游客扫码免登录下单菜单管理分类、商品、规格模板、上下架管理员图片上传至本地目录支持批量上下架桌台管理扫码绑定、开台、换台、状态显示顾客/服务员每个桌台对应唯一二维码点餐购物车加购、改规格、购物车持久化顾客游客购物车存Redis登录用户存数据库订单管理下单、支付、状态流转、退款顾客/服务员状态机驱动全程记录操作日志后厨工作台待制作队列、状态更新、排序后厨WebSocket实时推送新订单库存管理商品库存扣减、预警、补货记录管理员扣库存时机放在“下单成功”不放在“加购”会员系统储值、余额支付、消费记录顾客/管理员简化版只做余额和消费明细数据报表营收统计、畅销排行、时段分析管理员基于订单表聚合查询ECharts展示这个清单看起来功能很多但执行下来发现大部分是标准CRUD真正花时间的是订单状态流转和实时推送后面会重点讲。2. 技术选型不纠结为什么SpringBoot是毕业设计的最优解我很早就锁定了Java语言原因很直接一是Java生态的参考资料最多遇到报错一搜就能找到解决方案对毕设周期来说试错成本最低二是Java本身就是就业市场的头号需求语言做完这套毕设JVM、集合、并发、Spring这些知识点能串成一条线面试时直接拿项目说话。2.1 SpringBoot框架解决了毕设开发的哪些痛点如果是十年前做毕设你可能还在用SSHSpring Struts Hibernate那一套光配置XML就能写几百行。SpringBoot把“配置地狱”彻底终结了它基于“约定大于配置”的理念通过自动装配机制把绝大多数默认配置内置在Starter里。以本项目为例我用到的核心依赖只有这么几个spring-boot-starter-web内嵌Tomcat不需要手动部署war包spring-boot-starter-data-redis用于购物车缓存、桌台令牌、WebSocket状态下发mybatis-plus-boot-starter大幅简化单表CRUD分页查出一行代码搞定spring-boot-starter-websocket后厨大屏实时推送jjwt生成和解析JWT令牌swaggerspringfox或knife4j生成接口文档答辩时直接展示API结构关键要理解SpringBoot的自动装配原理面试和答辩都会被问到。简单说SpringBootApplication注解由三个注解组合而成其中EnableAutoConfiguration会通过spring.factories或AutoConfiguration.imports文件加载所有自动配置类再配合ConditionalOnClass、ConditionalOnMissingBean这类条件注解按需装配Bean。比如spring-boot-starter-web引入了Servlet相关类自动配置类就开始装配DispatcherServlet和Tomcat。我当时为了让答辩更稳还专门看了源码画了一张自动装配的调用链图。这个知识点在“为什么选择SpringBoot”这种必答题里几乎是送分题。2.2 配套选型前端与数据存储的实战选择后端确定SpringBoot之后前端没有跟风上前后分离的复杂架构选的是Vue Element-UI管理后台 uni-app小程序端可打包H5。学员管理后台用Vue写顾客端用H5页面即可。这是因为当时在毕设答辩场地你没法保证有Wi-Fi让评委扫小程序H5页面直接用浏览器随时可演示更稳。数据库选MySQL 8.0原因是性能足够且语法兼容。ORM层用MyBatis-Plus而非原生MyBatis省掉了大量XML mapper缓存Redis用来存游客购物车和桌台令牌后面并发扣库存的兜底也用到Redis实现简单锁。2.3 版本组合建议一个经过验证的稳定搭配版本问题是我踩过的第一个大坑后面第5章会详细说。这里先给出一套我已经跑通的组合组件版本说明JDK1.8毕业设计最稳不要追求JDK 17Spring Boot2.7.18目前2.x分支维护的最终版兼容性最好MyBatis-Plus3.5.3和Spring Boot 2.x匹配MySQL8.0驱动用mysql-connector-jRedis6.x/7.x本地安装或Docker启动Maven3.8项目构建工具IDEA自带即可Vue3.2.x配合Element-Plus为什么不用Spring Boot 3.x因为3.x强制要求JDK 17很多同学本机还是JDK 8配起来容易出问题而且早期3.x版本对部分starter兼容性不稳找资料时你会发现大量教程还是2.x的复制粘贴就能跑。毕业设计求稳是第一位。3. 数据库设计从菜单表到订单状态机数据库设计是整套系统的地基。点餐系统的表结构在网上随手能搜到很多但很多版本存在严重缺陷比如金额用浮点类型存放、订单没有状态字段、商品库存用事务里先查后改导致超卖。这章我把自己的表结构设计讲清楚重点说订单状态机的设计思路。3.1 核心表结构一览我最终落了11张表比最初规划的多了几张主要是在开发过程中发现需要补充。核心表如下表名关键字段说明sys_userid, username, password, real_name, role, phone统一用户表用role区分顾客/服务员/后厨/管理员menu_categoryid, name, sort, status商品分类menu_itemid, category_id, name, image, price, stock, spec_template, statusspec_template存JSON规格模板普通商品为空menu_specid, item_id, spec_name, spec_values规格名和可选值如“糖度不另外加糖/三分甜/五分甜”dining_tableid, table_no, capacity, status, qr_content桌台状态空闲/已占用/已清洁shopping_cartid, user_token, item_id, spec_value, quantity游客购物车可选存Redist_orderid, order_no, user_id, table_id, order_type, total_amount, discount_amount, pay_amount, order_status, pay_status, remark, create_time, finish_time订单主表t_order_itemid, order_id, item_id, item_name, spec_value, unit_price, quantity, subtotal订单明细冗余商品快照信息inventory_recordid, item_id, change_type, change_num, after_stock, create_time库存变动流水member_cardid, user_id, balance, total_recharge, created_time会员储值账户payment_recordid, order_id, pay_type, pay_amount, pay_time支付流水关表设计的三个原则值得展开说说。第一订单明细必须冗余商品快照。商品名称、价格、规格文案在生成订单明细时锁定不能以后实时关联商品表。否则商品改价或删除后历史订单的金额和名称就乱了。这个经验是我在测试退款功能时发现的——把一件商品改价后历史订单的明细也跟着变了这绝对不行。第二金额统一以分为单位存储。数据库字段用bigint存分而不是用decimal(10,2)存元。原因是浮点运算会有精度问题而且前端JS和Java做金额换算时按“元转分”的整型运算最安全。起初我用decimal测试时发现0.10.2这种经典问题会出现在折扣计算里于是全面改成int存储分。前端展示时再除以100。第三全表使用逻辑删除。用户删单、下架商品都设置deleted字段配合MyBatis-Plus的TableLogic注解自动拦截查询。这个设计的好处是数据可以追溯报表统计时不担心数据被物理删除影响。3.2 订单状态机设计从待支付到已完成订单状态是整个系统的核心命脉。我设计了一个双向状态机字段拆分为order_status和pay_status两个字段避免混在一起导致状态判断混乱。支付状态pay_status0-未支付、1-已支付、2-已退款订单状态order_status0-待支付、1-待制作、2-制作中、3-待取餐、4-已完成、5-已取消、6-退款中两者的组合是这样的未支付且订单状态为待支付顾客可以取消已支付且订单状态为待制作等待后厨接单已支付且订单状态为制作中后厨已开始制作已支付且订单状态为待取餐制作完成等待取餐已支付且订单状态为已完成顾客确认取走或超时自动完成未支付且订单状态为已取消顾客主动取消或超时系统取消我用枚举类来处理状态合法性而不是在代码里到处写魔法数字public enum OrderStatus { PENDING_PAYMENT(0, 待支付), PENDING_PRODUCTION(1, 待制作), PRODUCING(2, 制作中), WAITING_PICKUP(3, 待取餐), COMPLETED(4, 已完成), CANCELLED(5, 已取消); private final Integer code; private final String desc; // 构造函数和getter省略 }状态流转在Service层封装不允许直接修改状态字段所有的状态变更走统一的状态迁移方法。例如“取消订单”方法里做了严格的校验只有PENDING_PAYMENT状态且未支付时才能取消已支付的订单要走到退款流程先退款再取消。这个设计让答辩老师觉得逻辑严谨实际开发中也避免了很多状态错乱。3.3 表结构设计里我踩过的三个坑第一个坑是订单表和明细表都加了create_time自动填充但更新操作却忘了更新finish_time。起初用TIMESTAMP DEFAULT CURRENT_TIMESTAMP只负责插入后来发现所有订单的完成时间都是空导致报表统计“平均制作时长”功能直接崩了。后来在订单状态迁移方法里显式set完成时间才解决了问题。第二个坑是商品规格模板的JSON格式没有做版本管理。我改了一次规格字段的名字结果线上已经存在的购物车数据全部解析失败。后来给规格模板加上version字段解析时先校验版本再处理兼容旧数据。第三个坑是库存字段直接放在商品表里每次扣库存都更新商品表导致商品的主查询频繁锁行。后来把库存拆到独立的事务里用专门的库存表记录变动流水商品表只剩基础信息。这样即便大批量抢购也只是库存记录表的行级竞争不影响商品列表查询性能。4. 核心接口实现一次扫码点餐的完整链路数据库结构稳定后我开始写核心接口。这个系统里最复杂、最值得讲的不是CRUD而是“一次扫码点餐到底经历了什么”。完整链路是顾客扫码 → 系统绑定桌台和会话 → 浏览加购 → 提交订单 → 扣库存 → 支付 → 后厨大屏实时收到新订单 → 后厨更新制作状态 → 顾客端状态同步。每一步都有技术细节。4.1 扫码令牌与桌台绑定逻辑每个桌台在初始化时生成一个二维码内容是一个带有tableId的参数URL。顾客扫到之后前端解析出tableId请求后端接口绑定会话。这里关键点是“桌台令牌防重放”。如果别人把链接转发到别处就可能出现人在店外却占着桌台的情况。我的方案是桌台二维码内容不直接用明文tableId而是用tableId 随机盐 时间戳生成签名令牌一天内有效。后端验证签名合法性再检查桌台状态是“空闲”才允许绑定。PostMapping(/bindTable) public Result bindTable(RequestBody BindTableRequest req) { // 1. 校验签名 String sign genSign(req.getTableId(), req.getTimestamp(), SECRET_KEY); if (!sign.equals(req.getSign())) { return Result.fail(二维码无效); } // 2. 校验桌台状态 DiningTable table tableService.getById(req.getTableId()); if (table null || table.getStatus() ! TableStatus.FREE) { return Result.fail(桌台不可用); } // 3. 生成游客会话token无需登录也能下单 String token JwtUtil.generateGuestToken(req.getTableId()); return Result.ok(token); }游客会话token的过期时间设置为2小时覆盖一餐的时间晚餐高峰期后自动失效重扫。这套设计在答辩时是一个加分点因为很多人的扫码系统就是明文传tableId没有任何防护。4.2 提交订单幂等设计与事务边界提交订单接口是整个系统最容易出并发问题的接口。顾客手滑多点几次提交后端就可能创建多个订单。我在设计时用了“幂等键”机制前端在初始化购物车时生成一个唯一的clientRequestId提交订单时带上这个ID后端用Redis做幂等校验相同的clientRequestId在3分钟内只会处理一次。public Result createOrder(OrderCreateReq req) { String clientReqId req.getClientRequestId(); // 幂等校验同一个请求ID不重复处理 Boolean first redisTemplate.opsForValue().setIfAbsent( order:req: clientReqId, 1, Duration.ofMinutes(3)); if (first null || !first) { return Result.ok(请勿重复提交); } // 后续事务逻辑校验商品库存、计算金额、扣库存、生成订单 }事务边界放在service方法上用Transactional(rollbackFor Exception.class)。事务里做的事情依次是校验商品是否上架 → 校验库存充足 → 扣减库存并写流水 → 计算订单总金额后端计算不信任前端传参 → 生成订单主表和明细 → 返回订单号。为什么金额必须后端重算因为前端传total_amount可能被篡改。我在写接口时把前端传来的金额只当作参考真实金额由商品单价乘数量加总得到折扣在服务端基于会员等级计算。这样后端重算后即使被前端注入也无法影响金额。4.3 库存扣减与并发超卖库存扣减是并发场景里最容易翻车的地方。常规的错误写法是“先select库存判断大于0再update扣减”这在单线程下没问题但并发请求两个事务同时读到库存1就都会执行update导致库存变成负数。我用的是条件更新法一条SQL解决UPDATE menu_item SET stock stock - #{quantity} WHERE id #{itemId} AND stock #{quantity}执行后如果影响行数为0说明库存不足直接抛业务异常回滚整个订单事务。这个方案不依赖锁性能足够逻辑也简单清晰答辩时我直接拿出这条SQL来解释“如何保证数据一致性”老师马上理解。4.4 支付与后厨实时推送支付环节没有接真实支付网关做了模拟支付一个“确认支付”接口前端调用后后端把pay_status改为已支付同时把order_status改为待制作接着触发后厨推送。后厨推送用的是Spring Boot整合WebSocket。配置一个WebSocketConfigurer注册/ws/kitchen端点后厨前端连接之后监听订单消息。服务端用SimpMessagingTemplate推送Autowired private SimpMessagingTemplate messagingTemplate; // 支付成功后调用 public void pushNewOrderToKitchen(OrderVO orderVO) { // 面向所有后厨大屏广播新订单消息 messagingTemplate.convertAndSend(/topic/kitchen/orders/new, orderVO); // 同时写入Redis队列防止后端大屏离线期间丢单 redisTemplate.opsForList().leftPush(kitchen:pendingOrders, JSON.toJSONString(orderVO)); }为什么要额外写Redis队列因为WebSocket是长连接如果后厨大屏在支付成功的瞬间断网或者刷新页面广播消息就丢了。我用Redis队列做补偿新订单先推给在线连接同时push进队列后厨大屏启动时先从Redis队列拉取未读订单再建立WebSocket连接。这样双保险基本不会丢单。后厨更新制作状态后同样通过WebSocket推送消息到/topic/orders/{orderId}顾客端页面实时刷新状态。整个实时链路完成。5. 开发中我真正遇到的坑排查过程记录这部分是我觉得对后来者最有价值的。开发过程中崩溃的瞬间往往不是因为功能复杂而是环境、版本、框架机制的不熟悉。我翻了聊天记录把三个印象最深的坑完整还原出来。5.1 SpringBoot版本太高的连锁反应项目刚开始时我觉得2025年了肯定用最新版直接创建Spring Boot 3.2的项目JDK选了17。结果进了第一个坑mybatis-plus-boot-starter和Swagger的兼容性问题。Spring Boot 3.x把javax命名空间改成了jakarta很多starter还停留在2.x的javax包上一启动就报NoClassDefFoundError: javax/servlet/...。刚开始我硬着头皮升级starter版本MyBatis-Plus升级到3.5.5之后勉强跑起来了但knife4j的兼容性又出问题接口文档一直无法访问。排查了两天最后参考网上主流项目的稳定配置把Spring Boot降到2.7.18JDK退回8所有组件瞬间跑通。我的建议是毕业设计不要追新。Spring Boot 3.x再好2.7.x也足够完成所有需求找教程、找解决方案时2.x的资料是全网最丰富的遇到问题几秒钟就能搜到答案。版本带来的时间成本是毕设最不值得耗的地方。5.2 事务回滚失效的三个典型场景在开发退款功能时我发现一个严重bug支付成功后如果后面的“推送后厨”操作抛异常订单的支付状态已经改了但事务回滚没有生效导致订单卡在已支付但后厨不知道的中间状态。排查定位到三个原因任何一个都会导致Transactional失效场景一同类调用导致自代理失效。我在同一个类里写了一个createOrder方法直接调用本类的payOrderpayOrder上的Transactional注解被忽略了。因为Spring的事务代理基于AOP只有当方法通过代理对象调用时事务才生效同类内部调用直接走this对象绕过代理。解决方案是拆分成不同的Service让两个方法分别经过Spring代理。场景二捕获异常吞掉。我在trade逻辑里把异常catch起来打日志没有向上抛事务管理器看到“一切正常”自然不回滚。正确做法是判断为业务失败时必须throw new RuntimeException事务才能感知。场景三默认只回滚运行时异常。Transactional默认只对RuntimeException和Error回滚对受检异常如Exception的子类不回滚。如果抛的是自定义的受检异常必须指定rollbackFor Exception.class。我所有的service方法统一写成Transactional(rollbackFor Exception.class)命名规范上再约定“业务异常统一继承RuntimeException”从根上消灭这个问题。5.3 并发下单测试超卖问题复现与解决写完后厨推送功能后我用JMeter模拟20个并发请求同时下单发现库存出现了负数超卖问题实锤。先把问题复现出来再定位到扣库存那步的“先查后改”写法然后改成前面4.3节的UPDATE ... WHERE stock quantity模式。改完再次压测同时验证两件事库存不会为负成功下单的订单数等于初始库存数。结果20并发下单10个库存商品时只有10个请求返回成功其余10个返回“库存不足”订单表和库存流水一致。压测至少要覆盖四种场景正常并发、库存临界点并发、重复提交、支付超时。每个场景数据都要核对订单表、库存流水表和支付记录表的三方一致性这也是答辩时“数据一致性如何保证”的答案基础。6. 部署交付和答辩准备毕设拿得出手的最后一公里功能做完只是第一步能不能在答辩现场顺利演示、让老师看到系统的亮点是另一门学问。这一节我把部署和答辩准备的实操经验整理清楚。6.1 部署方案选择本地演示远比服务器部署稳妥很多同学喜欢把项目部署到云服务器展示“上线成果”。但对我这个毕设来说本地演示反而更稳原因很现实答辩现场的网络环境不可控服务器如果不在同一内网跨域、端口开放、数据库连接都是隐患而且演示时如果卡顿评委印象分会大打折扣。我最终选择本地演示环境全部装在笔记本上。Maven打包后直接用java -jar运行不需要可视化界面mvn clean package -DskipTests java -jar target/teahouse-system.jar --spring.profiles.activeprod为了让演示环境快速恢复我把Redis和MySQL都改成了本机安装服务并写了两个脚本一个启动脚本负责拉起MySQL、Redis、后端Jar一个重置脚本负责恢复初始数据。答辩现场如果出问题一键重置比手忙脚乱改数据库强得多。需要提醒的是application.yml里配置数据源时spring.datasource.url的时区参数要写对我用的是serverTimezoneAsia/Shanghai否则测试环境时间会偏差8小时报表统计全错。6.2 演示数据与演示脚本的设计演示数据一直是毕设容易忽略的部分。有的同学数据库空空如也现场清汤寡水点不了几个按钮。我的做法是准备一套带业务感的数据4个分类20个商品覆盖“规格商品”和“普通商品”两类6张桌台其中2张设置了占用状态方便展示换台逻辑3个测试账号管理员/admin123、服务员/waiter123、后厨/kitchen123预置近30天的订单数据用于报表模块展示日营收曲线和畅销排行演示的时候按照“顾客扫码点餐 → 支付 → 后厨大屏接单 → 制作完成 → 服务员核销 → 管理员查报表”这条剧本走流程控制在3分钟以内保证不拖沓每个环节都能让评委看到。6.3 答辩高频问题与应对思路结合我自己的答辩经历老师最常问的问题集中在三块技术选型理由、框架原理、数据一致性。每个问题的回答思路如下“为什么选择SpringBoot”可以从自动装配原理切入SpringBoot通过EnableAutoConfiguration加载自动配置类按需装配Bean省去大量XML配置让项目能轻量启动同时生态成熟Starter体系完善适合快速构建Web服务。“如何保证数据一致性”讲三层事务边界用Transactional(rollbackFor Exception.class)保证订单与库存操作要么全成功要么全回滚并发扣库存用SQL条件更新UPDATE ... SET stockstock-#{num} WHERE stock#{num}防止超卖Redis队列做WebSocket推送到后厨的补偿防止订单消息丢失。“订单状态如何管理”讲状态机设计订单状态和支付状态分开存储状态迁移统一走Service层方法枚举类做状态合法校验避免任意改动状态导致脏数据。“游客不登录怎么点餐”讲JWT Guest Token扫码绑定桌台后生成游客token携带桌台信息有效期2小时登录时可绑定用户和会员卡余额支付时校验会员身份。把上面四个问题答顺了答辩基本就稳了。临场如果被问到没准备的问题我的策略是诚实回答“这块我没有深入实现”然后主动转到自己熟的方向比如“但我在XX模块中遇到过类似问题我的处理方式是……”比硬编一个答案靠谱得多。复盘整个项目最深的体会是毕业设计的价值不在于代码量多少而在于是否有一条清晰完整的主链路。我这套系统从扫码桌台绑定开始到后厨大屏实时推送结束整条链路是通的每一步都有真实业务场景支撑所以在答辩时讲起来特别顺。另外给所有准备做类似选题的同学一个建议不要为了展示“技术多全面”而堆砌功能把点餐、支付、状态流转、库存一致性、实时推送这五件事做扎实比做二十个半成品功能管用得多。项目完成后把开发过程中解决的问题整理成笔记这既是答辩素材也是将来面试时讲项目的底气。
网站建设高端定制企业官网