1. 项目定位与设计思路拆解
1.1 “农场驿站”到底解决什么问题
第一次看到“基于微信小程序的农场驿站”这个题目时,很多人的第一反应是“这不就是一个商城小程序吗”。如果只按商城来做,那最后交付的东西大概率会被导师或者评审老师一句话问住:“你的项目和其他电商小程序有什么区别?”
我对这类项目的理解是这样的:农场驿站本质上是一条连接“农场”和“城市消费者”的数字化链路。消费者在城市里想买到产地直供的农产品,农场想把优质产品卖出去,但双方之间存在信息不透明、信任成本高、中间环节多的问题。小程序的作用就是把这个链路搬到线上:用户看到的是农场实景、当季作物、认养计划和直采价格,农场看到的是订单、库存和用户反馈。
所以这个项目相比普通商城小程序,核心差异点在于“认养”和“溯源”两个业务场景。认养指的是用户提前支付一笔费用,认养某棵果树或某块菜地,农场负责日常打理,成熟后将果实按周期寄送给用户;溯源指的是用户能看到农场上传的种植记录、施肥记录、采摘照片,增强信任感。把这两个场景做进小程序里,项目的完成度立刻就不一样了。
1.2 功能模块拆解与技术选型
在做技术选型之前,先把功能模块梳理清楚。完整的农场驿站小程序,我拆成了下面几个主要模块:
- 用户端:微信授权登录、首页轮播与推荐、农产品分类、认养专区、商品详情、购物车、订单确认、订单列表、售后入口、个人中心、收货地址管理
- 农场端(管理后台):商品上下架、库存管理、认养计划配置、订单发货、种植记录上传、数据统计
- 公共模块:微信登录态维护、图片上传、支付与退款回调、消息通知
后台管理我建议直接做成Web管理页面,不要塞进小程序里。曾见过有人把管理功能做成小程序内的隐藏页面,用权限控制入口,实际操作起来非常难受,农场工作人员在小程序里上架商品、处理订单,光是小屏输入和图片上传就能折磨死人。管理端独立成Web项目,小程序专注C端体验,这个边界一定要划清楚。
技术栈方面,给一个比较稳妥的组合:
- 小程序端:原生微信小程序框架(WXML + WXSS + JS),不引第三方UI库,或者只引入一个轻量组件库如Vant Weapp
- 后端:Java Spring Boot 2.x + MyBatis-Plus + MySQL 5.7/8.0,这是高校项目中最主流、也最容易找到参考资料的一套组合
- 服务端渲染管理端:Thymeleaf模板或者Vue + Element UI独立部署,看个人熟悉程度
- 文件存储:本地存储或者阿里云OSS,我在实际调试中推荐OSS,但考虑到多数人没有云资源,下面会给出本地存储的替代方案
为什么不选Node.js或者PHP?不是说不行,而是你要考虑“后续维护”的问题。Spring Boot这套组合在毕业设计场景里几乎是“标准答案”,导师熟悉、网上资料多、出了问题容易排查,加上MyBatis-Plus能极大减少SQL编写量,开发速度并不慢。如果你的机器配置有限,那Spring Boot的启动内存占用确实比Node高,但仍在可接受范围内,2G内存的云服务器也能跑起来。
1.3 为什么采用前后端分离、小程序单独一套
有一个很容易踩的坑:想用小程序直接请求后端接口,然后把管理后台也塞进同一个后端项目里返回JSON,前端再写一套HTML页面。这样做的结果是,两套前端共用一套接口,接口设计会很别扭,管理端需要的列表查询和小程序需要的商品信息结构完全不一样。
正确的做法是把接口分为用户端接口和管理端接口两个命名空间。比如用户端接口使用/api/user/**前缀,管理端接口使用/api/admin/**前缀,通过拦截器分别校验不同的登录态。这样前端小程序只调用用户端接口,Web管理后台只调用管理端接口,两边互不干扰,后续加权限控制也方便。
前端分离之后还有一个好处:调试效率高。小程序开发者工具 + 后端本机接口直接联调,不需要每次改动前端代码都去重启后端服务。我习惯在app.js里维护一个全局配置对象,区分开发环境、测试环境、正式环境的接口地址,切环境只改一个配置项。
2. 数据库设计与核心业务逻辑落地
2.1 核心数据表结构与设计要点
数据库设计是这类项目最先动手、也最容易被忽视的部分。很多人上来就建一张商品表、一张订单表,做到后面发现字段不够用,只能反复加列,最终表结构一团乱。
我把农场景观类项目常用的表结构整理成了一份可直接参考的清单:
| 表名 | 核心字段 | 说明 |
|---|---|---|
| user | openid、nickname、avatar、phone、address | 用户表,openid为唯一索引 |
| product_category | name、parent_id、sort | 商品分类,支持两级分类 |
| product | category_id、name、price、stock、main_image、images、detail | 普通农产品商品表 |
| adoption_plan | product_id、plan_name、cycle_months、price、installments | 认养计划表 |
| adoption_order | user_id、plan_id、status、start_date、end_date | 认养订单表 |
| order | order_no、user_id、total_price、status、pay_time、delivery_info | 普通订单表 |
| order_item | order_id、product_id、quantity、price | 订单明细表 |
| address | user_id、consignee、phone、province、city、detail | 收货地址表 |
| grow_log | plan_id、content、images、create_time | 种植记录(溯源)表 |
| cart | user_id、product_id、quantity | 购物车表 |
| banner | image、link_type、link_param | 首页轮播图配置 |
建表时有两个细节值得强调。第一,金额字段不要用float,用decimal(10,2)。浮点数在Java和MySQL之间的精度问题非常经典,0.1 + 0.2 不等于 0.3 的闹剧在订单金额上绝对不允许出现。第二,所有表都加上create_time和update_time字段,MyBatis-Plus 可以自动填充,省去业务代码里手动赋值。
还有一个项目特有的字段设计:认养订单需要记录start_date和end_date,因为用户认养的是一个周期,比如认养一棵樱桃树一年,这个时间范围决定了后续寄送果实的次数。普通订单表不涉及这些字段,所以把认养拆成单独的表,而不是复用普通订单表,这样简化了订单状态管理。
2.2 订单状态机与认养流程的实现思路
订单状态是这类业务里最容易写乱的地方。我见过有人用字符串直接存储状态,“待支付”“已支付”“已发货”“已完成”“已取消”,前端switch对应,后端也是字符串判断。前期看着省事,后期一旦要加状态或者做统计,代码里全是散落的魔法字符串。
我的建议是定义一个枚举类,把订单状态和对应的描述集中管理:
public enum OrderStatus { UNPAID(0, "待支付"), PAID(1, "已支付"), DELIVERED(2, "已发货"), COMPLETED(3, "已完成"), CANCELLED(4, "已取消"), REFUNDING(5, "退款中"), REFUNDED(6, "已退款"); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code = code; this.desc = desc; } }然后定义状态流转规则:UNPAID 可以流转到 PAID 或 CANCELLED,PAID 可以流转到 DELIVERED 或 REFUNDING,DELIVERED 可以流转到 COMPLETED 或 REFUNDING。这个规则在 Service 层写一个方法统一处理,任何接口改订单状态都必须走这个方法,避免出现“从待支付直接跳到已完成”这种脏数据。
认养流程稍微特殊一点。用户下单认养计划后,支付成功即生成认养订单,同时需要初始化一条grow_log记录,内容可以是“欢迎认养,农场已接收订单”。农场管理员在后台每隔一段时间上传照片和文字,用户在小程序的“我的认养”页面能看到认养进度。这个业务其实有点像“给时间加进度条”,做好这一块体验会特别好。
2.3 微信登录态与权限控制
小程序端的登录不是传统意义上的账号密码登录,而是通过微信授权获取code,后端拿code向微信接口换取openid和session_key。首次登录时用openid查用户表,不存在则自动创建用户,存在则直接生成自定义登录态返回给小程序。
这个自定义登录态我习惯用 JWT 实现,token 中只放userId和过期时间,不存敏感信息。小程序发起请求时在 header 中带上Authorization: Bearer token,后端通过拦截器统一校验。拦截器要放行登录接口、微信支付回调接口、首页商品列表接口,其余接口全部要求登录。
这里有一个很经典的坑:小程序不允许在<image>组件中直接加载本地相对路径图片,也不允许在正式版中请求http://localhost接口。本地调试时要在开发者工具中勾选“不校验合法域名”,真机预览时则必须把后端接口配置成 HTTPS 域名。如果只是交作业或者本地演示,这个问题不大,但如果你想部署到云服务器,就需要准备备案域名和 SSL 证书。
3. 微信小程序前端实现关键点
3.1 用户端核心页面拆解
小程序端是整个项目面对用户的“脸面”,页面还原度直接决定评审老师的第一印象。农场驿站这类项目至少需要完成下面几个页面:
首页:顶部搜索栏,下面依次是轮播图、分类快捷入口、认养活动专区、热销推荐列表。首页数据通过一个聚合接口返回,减少前端多次请求。如果每个模块单独请求一次,首页要发五六个网络请求,加载体验非常差。
商品列表页:支持按分类过滤、按销量/价格排序、上拉加载更多。这里用onReachBottom触发分页加载,页面数据用数组拼接。记住每次加载后要判断hasMore,否则会陷入无限请求。
商品详情页:展示多图轮播、价格、库存、商品参数、详情富文本、底部“加入购物车”和“立即购买”。如果是认养商品,详情页要展示认养周期、包含的权益说明,并单独放一个“开始认养”按钮。
订单确认页:选择地址、展示商品清单、计算总价、提交订单。支付环节如果没接入真实微信支付,可以直接模拟一个支付弹窗,前端点击“模拟支付”后调用后端接口把订单状态改为已支付。为了项目演示更完整,推荐把模拟支付的逻辑做成一个单独的接口,方便后续替换成真实支付。
个人中心:展示用户头像昵称、订单列表入口(待支付、待发货、待收货、已完成)、我的认养、收货地址管理、售后入口。
我的认养页:这是农场驿站的亮点页面。卡片式展示认养的果树/菜地项目,每张卡片显示认养周期进度条(当前时间在开始结束日期中的位置)、最近一条种植记录的照片和文字,点击卡片进入认养详情,查看全部生长记录。
3.2 接口封装与数据交互规范
小程序端不要在每个页面里直接写wx.request。虽然代码少的时候感觉无所谓,但页面多了以后,改一个接口地址要全局搜索替换,项目能把你逼疯。我习惯封装一个公共请求模块,放在/utils/request.js里:
const BASE_URL = 'https://your-domain.com/api/user'; function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success: (res) => { if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data); } else if (res.statusCode === 401) { wx.navigateTo({ url: '/pages/login/login' }); reject(res); } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(res); } }, fail: (err) => { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); }后端返回结构统一为{ code: 0, msg: "success", data: {} },code 为 0 表示成功。这样前端处理逻辑非常统一,不用每个接口单独判断业务状态码。
登录流程也要提前想清楚。小程序启动后先wx.login获取 code,然后调用后端接口换 token,将 token 和用户信息存入wx.setStorageSync。这个流程建议封装在app.js的onLaunch里,并且处理一个并发问题:多个页面同时发起请求时,token 可能还没获取到。简单做法是加一个全局的loginPromise,所有需要登录的接口都先等待loginPromise完成再发请求。
3.3 农场景观类页面交互细节
这类项目在页面交互上有一个共性问题:图片太多。农场场景需要展示大量实拍图,本地图片资源有限,线上图片加载又慢,直接影响体验。我的做法是,上传图片时后端统一做压缩处理,限制图片大小在200KB以内,详情页的商品图片采用懒加载方式,轮播图区域使用lazy-load属性。
另外,商品库存的实时性要注意。用户加入购物车时不锁定库存,提交订单时后端校验并扣减库存,如果库存不足则提示“商品库存不足”。扣减库存的 SQL 建议用乐观锁实现:
UPDATE product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity}执行后判断影响行数,为 0 说明库存不足或已被抢空。这个方法比先查库存再更新更安全,不怕高并发超卖。
4. 后端接口实现与调试全记录
4.1 统一返回结构与全局异常处理
后端接口设计里,第一个要解决的就是返回结构。我见过有人直接在 Controller 里返回 Map 或者干脆返回实体对象,结果前端拿到数据还要根据字段名猜类型,出错率极高。
Control层统一返回Result<T>对象:
@Data public class Result<T> { private int code; private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(0); result.setMsg("success"); result.setData(data); return result; } public static <T> Result<T> error(int code, String msg) { Result<T> result = new Result<>(); result.setCode(code); result.setMsg(msg); return result; } }配合@RestControllerAdvice做全局异常捕获,业务代码里只需要通过throw new BizException("库存不足")抛出异常,异常处理器会统一转换成{ code: 500, msg: "库存不足", data: null }返回给前端。这样Controller层不会出现一堆 try-catch,代码保持干净。
分页接口我统一返回一个PageResult<T>对象,包含total、list、pageNum、pageSize四个字段,前端分页组件直接对接。MyBatis-Plus 的Page对象可以直接转换成PageResult,不需要手写循环。
4.2 核心接口的实现细节与代码示例
以“提交订单”这个核心接口为例,完整实现逻辑如下:
@PostMapping("/order") public Result<Long> createOrder(@RequestBody CreateOrderRequest request, @RequestAttribute("userId") Long userId) { // 1. 校验地址是否存在且属于当前用户 Address address = addressService.getById(request.getAddressId()); if (address == null || !address.getUserId().equals(userId)) { throw new BizException("收货地址不存在"); } // 2. 生成订单号:时间戳 + 用户ID + 随机数 String orderNo = generateOrderNo(userId); // 3. 创建订单主表记录 Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setAddressSnapshot(address.getConsignee() + " " + address.getPhone() + " " + address.getFullAddress()); order.setTotalPrice(calculateTotalPrice(request.getItems())); order.setStatus(OrderStatus.UNPAID.getCode()); // 4. 保存订单明细 List<OrderItem> items = new ArrayList<>(); for (CartItem cartItem : request.getItems()) { Product product = productService.getById(cartItem.getProductId()); // 校验商品状态和库存,扣减库存 int updated = productService.deductStock(product.getId(), cartItem.getQuantity()); if (updated == 0) { throw new BizException("商品[" + product.getName() + "]库存不足"); } OrderItem item = new OrderItem(); item.setOrderId(order.getId()); item.setProductId(product.getId()); item.setProductName(product.getName()); item.setPrice(product.getPrice()); item.setQuantity(cartItem.getQuantity()); items.add(item); } orderService.save(order); orderItemService.saveBatch(items); // 5. 清空购物车中已下单的商品 cartService.removeByUserAndProductIds(userId, request.getItems()); return Result.success(order.getId()); }这个接口里有两个细节值得说明。第一,addressSnapshot字段是把收货地址的快照直接存入订单表,这样以后地址改了,历史订单仍然保留发货时的信息。第二,库存扣减放在创建订单时执行,而不是支付时执行,避免订单创建成功但支付前商品被其他用户买走。
认养订单的接口逻辑稍有不同:创建认养订单成功后,需要额外生成一条初始grow_log,并且生成认养表单条记录,设置起始日期为当前日期,截止日期根据cycle_months计算。这里用LocalDate.plusMonths()非常方便,注意不要用Date类手动加月份,跨年和跨月计算容易出错。
4.3 使用 Swagger/Postman 做接口调试的实战技巧
联调阶段最痛苦的事情是:前端说“接口返回不对”,后端说“我这边调着没问题”。为了减少这种扯皮,接口文档和调试工具必须从一开始就配套上。
如果后端用 Spring Boot,直接集成springfox-swagger-ui或者knife4j,项目启动后访问/doc.html就能看到所有接口的在线文档,并且可以直接在线调试。注意 Swagger 在生产环境要关掉,万一部署到公网,接口文档暴露出去会带来安全隐患。只需要在配置文件中按spring.profiles.active控制开关。
Postman 更适合管理完整的请求流程。我调试订单流程时会创建一个 Collection,按顺序排列:登录 -> 添加购物车 -> 创建订单 -> 模拟支付 -> 查询订单列表 -> 确认收货。每步请求使用 Postman 的环境变量传递上一个接口返回的数据,比如登录接口返回的 token 存储到{{token}}变量中,后续请求自动带上。
这里分享一个排查接口问题的通用套路:先看 Postman 中请求是否正常,再看小程序端代码中 URL 拼接是否正确,最后看请求头有没有正确携带 token。这三步排查完,80% 的联调问题都能定位。剩下的 20% 大概率是参数名对不上,前端传userId,后端接收userId_,这种低级错误用肉眼看不出来,直接把请求日志打出来对比最有效。
5. 文档整理与项目交付经验
5.1 “源码+文档+调试”三件套的完整构成
这个标题里最扎眼的三个字是“超全”,那到底什么才算超全?源码、文档、调试三个维度都有大量的门道。
源码部分不只是后端Java代码和小程序前端代码,还要包含数据库初始化脚本。我强烈建议把farm.sql脚本放到项目根目录的sql文件夹下,里面包含全部建表语句和初始数据(默认管理员账号、测试商品数据、轮播图配置数据)。否则别人拿到你的源码,第一件事就是到处问“数据库在哪”。很多人项目本身做得不错,但交付包乱成一锅粥,解压后连怎么启动都写不清楚,印象分大打折扣。
文档部分至少包含四份:需求分析文档、数据库设计文档、接口文档、部署说明文档。需求分析文档要说清楚项目背景、用户角色、功能需求和非功能需求;数据库设计文档画清楚ER图,说明每张表的用途;接口文档如果用了 Swagger 可以自动生成,没有用的话用 Markdown 表格维护一份;部署说明文档要从环境准备开始写,包括 JDK 版本、MySQL 版本、如何导入 SQL、如何修改配置文件启动后端、如何在小程序开发者工具中导入项目并修改app.js中的接口地址。
调试部分不是指你在开发过程中调通就行,而是在交付时让别人能顺利跑起来。这里有个关键操作:把本机后端接口地址改成局域网 IP。小程序开发者工具默认可以通过局域网访问同网段后端的http://192.168.x.x:8080接口,方便演示。真机预览时,这个局域网地址往往不通(需要小程序后台配置合法域名),但对课堂答辩演示来说够用了。
5.2 打包部署与答辩演示需要注意的坑
部署这一步,我建议至少提前一天做联调测试,不要在答辩前夜才动手。真实场景里常见的翻车点有:
- 后端数据库密码写的是本机的 root/123456,换一台机器或部署到服务器后根本没改配置文件,接口直接报数据库连接失败
- 小程序的
request合法域名没有配置,真机预览时所有请求都被拦截 - 图片资源使用本地路径,部署后换个环境图片就丢了,所以图片上传后要保存到 OSS 或者专门的静态资源目录
- 服务器上 MySQL 端口被防火墙拦截,本地能连远程连不上,查了半小时才发现
我给一个部署顺序的参考:先部署数据库,导入 SQL 脚本并验证表数据;再部署后端,修改application.yml中的连接信息,本地启动测试所有接口;然后将前端小程序中的接口地址改成服务器地址;最后在微信公众平台配置合法域名。每步验证通过再进行下一步,不要一次性改完所有配置再启动,那样出错后定位范围太大了。
答辩演示时有一个加分项:提前准备好测试账号的数据。比如演示认养流程时,账号里已经有一条进行到一半的认养记录,页面展示时能看到“生长进度第78天”,这个细节比现场从零下单更有说服力。同理,商城首页的轮播图、热销商品、评价数据,都要提前准备好,不要在演示时手忙脚乱地现场添加数据。
5.3 后期可以低成本扩展的方向
如果做完基础版本后还有余力,有几个扩展点性价比很高,既不会破坏现有架构,又能让项目在同类目中脱颖而出。
第一个是农场直播入口。在小程序首页放一个直播占位页面,通过 web-view 嵌入第三方直播平台地址,或者直接做直播预告列表。它不需要你真去实现推拉流技术,但页面逻辑完整,能体现你考虑到了“信任感”这个业务核心。
第二个是消息订阅推送。利用微信小程序的订阅消息功能,在认养订单物流状态变化时向用户推送通知。订阅消息需要用户在小程序内主动订阅,第一次授权后会生成一个一次性订阅 id,后端在订单状态变化时调用微信接口发送消息。这个功能代码量不大,但业务价值很明显。
第三个是管理端的数据看板。用 ECharts 给管理后台加两个图表:近30天订单量折线图、商品分类销售占比饼图。数据来源就是数据库中的订单表和订单明细表,通过 Group By 和日期函数聚合查询。图表不需要多复杂,能证明你有数据分析的思维就足够了。
6. 常见问题排查与避坑实战
6.1 问题一:小程序请求后端一直是 “request:fail”
这个现象在本地调试中非常常见,原因是开发者工具默认开启了“校验合法域名”。解决方法是点击右上角“详情 -> 本地设置”,勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。真机预览时这个选项不存在,只能使用配置过合法域名的正式环境。
还有一种情况是后端没有启动,或者启动的端口不对。我犯过一次特别低级的错误:接口写的是 8080 端口,Spring Boot 配置文件里配的是 8081,前端一直报请求失败,排查好久才发现是端口不匹配。建议启动后端时强制在控制台确认 “Tomcat started on port(s): 8080” 这段日志。如果使用 IDE 启动,还要注意是否有多个实例占用同一端口,用netstat -ano | findstr 8080可以快速检查。
6.2 问题二:接口返回数据正常但页面白屏
页面白屏通常是数据格式对不上导致的渲染异常,最常见的两个原因:后端返回了null字段,前端模板中使用了item.name这种写法,null.name直接报错;或者列表字段名不一致,后端返回productName,前端用的是name。
小程序的 WXML 模板对空值宽容度很低,建议在封装请求模块时统一对data做一层兜底处理。比如列表统一返回空数组而不是null,对象统一返回空对象。对于多层嵌套的数据,前端可以用wxs写一个安全取值的方法,或者在request.js中增加一个normalize函数,把null全部转成空串或空对象。这个处理逻辑写一次,后面所有页面都能受益。
6.3 问题三:订单支付成功后订单状态未改变
这类问题通常是微信支付回调没有成功触发造成的。如果你接的是模拟支付,那就是前端调用了模拟支付接口,但接口内部只改了订单状态,没有调起支付成功后的后续逻辑。模拟支付接口必须完整走一遍“支付成功 -> 修改订单状态 -> 扣减库存 -> 生成物流单(可选)”的流程,而不是只改一个状态字段。
如果接的是真实微信支付,回调接口必须返回 “SUCCESS” 给微信服务器,否则微信会持续回调多次。要注意回调接口不能做验签失败时抛异常的操作,应该捕获异常并返回失败信息,否则微信服务器会认为回调失败,不断重复通知。还有一个容易忽略的点:回调接口的返回必须是String类型,并且 Content-Type 是text/xml,不是 JSON。
6.4 附:高频问题速查表
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| 登录后拿不到 openid | wx.login 的 code 已过期 | 重新走一次 wx.login 获取新 code |
| 上传图片失败 | 后端临时目录不存在或权限不足 | 检查上传目录是否存在,Windows 注意反斜杠路径 |
| 订单超时未支付 | 没有定时任务处理过期订单 | 增加定时任务将超过30分钟的未支付订单置为取消并回滚库存 |
| 小程序页面跳转后数据丢失 | 数据保存到了页面栈而非全局 | 使用全局变量或 Storage 在 onLoad 中重新读取 |
| 管理后台图片加载失败 | 图片返回的是本地路径,管理端不同域名访问不了 | 图片返回完整绝对 URL 地址 |
| 数据库中文乱码 | 连接字符集和表字符集不一致 | JDBC URL 添加characterEncoding=utf8,建表指定 utf8mb4 |
写在最后的一点个人感受
这个项目从需求分析到编码完成,我前后用了大概三周时间。如果只是想凑一个能跑的 Demo,一周就够了;但要做到“源码+文档+调试”都能拿得出手,多花的时间其实都花在细节打磨上:数据库字段设计是否合理、订单状态流转是否严谨、异常情况有没有兜底、文档能不能让另一个人照着启动成功。这些细节单看都不起眼,但它们累加起来,才构成了一句话评价——“这个项目完整度很高”。
另外想提醒一句:调试过程中踩过的坑一定要随手记下来,写进项目文档里。答辩被问到“你遇到过什么难点”的时候,把这些真实踩坑经历讲出来,比背一百条理论都管用。这些真实的排查经历本身就是你项目最好的注脚,也是最难被复制的东西。