先说个结论:计算机毕业设计里,“基于Spring Boot的扶贫助农系统 + 助农农产品销售小程序”这个组合,属于典型的两头吃香选题。后端用Java生态里最主流的Spring Boot,前端落在微信小程序这种轻量级载体上,业务主题又做了助农方向的包装,从开题、中期检查到最终答辩,每个环节都有东西可讲,代码量可控,技术栈覆盖完整,是Java方向毕设里性价比很高的选择。
我自己带过几届毕业生,也帮人改过不少类似项目,今天就把这类项目从技术选型、数据库设计、前后端联调到部署上线的完整套路拆开聊一遍。你不需要完全照抄我的方案,但理解了这套思路,不管你的题目是助农、商城、二手交易还是校园服务,都能很快套出自己的版本。
1. 项目背景与核心价值拆解
1.1 为什么扶贫助农类选题这么受欢迎
先说现实原因:毕设选题要过开题答辩,老师最看重的三件事是“有实际意义、有技术含量、工作量够”。扶贫助农这个主题自带政策和社会价值,天然就过了“意义关”。农产品销售又是很标准的电商业务模型,用户在微信里浏览农产品、下单、支付、查看订单,管理员在后台管理商品、订单、用户、数据统计,整个业务流程非常完整,工作量和技术点都摆在那。
再说技术原因:电商类业务是Java后端最成熟的场景之一。Spring Boot + MyBatis-Plus + MySQL这套组合,网上资料多、踩坑记录多、模板也多,遇到问题基本都能搜到答案。从小程序端看,微信小程序开发门槛低,原生框架就能搞定,不需要额外搭前端工程。前后端分离的交互模式也是现在企业开发的主流,答辩时能讲清楚这一点,很加分。
最后一个隐蔽优势是“演示效果好”。小程序端在手机和微信开发者工具里都能跑,演示时评委直接在微信里打开小程序就能看你做的系统,比纯网页项目更具视觉冲击力。助农主题的农产品图片、乡村振兴元素也让整个项目看起来完整度高。
1.2 一套系统拆成两条产品线
这类项目的完整形态通常包含三个端:
第一是用户端小程序,也就是消费者在微信里看到的部分。核心页面包括首页(轮播图、助农专区入口、热销农产品推荐)、商品分类页、商品详情页、购物车、下单确认页、订单列表、个人中心、收货地址管理等。
第二是管理后台,一般是Web端,给平台运营人员用。主要功能包括农产品商品管理(上下架、库存修改)、订单管理(发货、退款处理)、用户管理(买家/农户账号管理)、农户入驻审核、助农活动配置、数据统计图表等。
第三是服务端接口,也就是Spring Boot后端项目。它统一处理小程序的业务请求,管理数据库读写,对接微信登录接口,处理文件上传,并给管理后台提供RESTful API。小程序和后台共用这一套后端服务。
很多第一次做毕设的同学容易犯一个错:把精力全花在小程序和商品展示上,后台只做两三个页面应付了事。这在中期检查时很危险,老师一眼就能看出工作量不足。我建议后台至少把“商品管理、订单管理、用户管理、数据统计”四个模块做完整,这四块是电商平台的底线配置。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构设计
2.1 Spring Boot版本与配套组件怎么定
技术选型的核心原则是“用最主流的版本,不要追求最新”。我看到太多人一上来就装Spring Boot 3.x + JDK 17,然后碰到各种兼容性问题,白白消耗大量时间。
我的建议组合是:
- JDK 1.8:稳定、兼容性好、教程最多
- Spring Boot 2.7.x:这是2.x系列的收官版本,功能完整且稳定
- MySQL 5.7或8.0:两者都可以,8.0性能更好但要注意驱动配置
- MyBatis-Plus 3.5.x:比纯MyBatis多了很多便捷功能,代码生成器能直接生成实体类、Mapper、Service
- Hutool工具包:处理日期、加密、随机数等常用逻辑
- JWT做登录令牌
这个组合的兼容性经过大量项目验证,几乎不会因为版本问题卡壳。举个例子,Spring Boot 2.7.x对JDK 1.8是完美支持的,但Spring Boot 3.x强制要求JDK 17,如果你电脑上装的是JDK 8,直接启动报错。很多人不知道这一点,项目启动时报个红色错误就慌了,其实查一下版本对应关系就能解决。
2.2 小程序端用什么技术实现
小程序端我推荐直接用微信原生框架,也就是WXML + WXSS + JavaScript。不要额外引入uniapp、Taro这类跨端框架。原因很简单:这是毕设,不是商业项目,重点是完成功能并让评委看懂。原生框架没有额外编译层,出问题容易定位,查找资料方便,微信开发者工具里调试也直观。
如果你对Vue很熟,也可以用uniapp,写起来确实舒服很多,而且以后还能顺手编译成H5版。但注意uniapp打包到微信小程序后,有些原生API调用会有差异,踩坑成本不低。我见过的毕设项目中,用原生框架完成率明显高于跨端框架。
小程序端关键依赖就是微信官方提供的API,没有第三方依赖。需要注意的就是request请求的封装、登录状态的存储、页面跳转时参数的传递。这些我在后面第5部分详细展开。
2.3 整体架构与数据流设计
整个系统采用前后端分离架构。数据流向是这样:小程序用户操作页面 -> 调用wx.request发起HTTP请求 -> Spring Boot的Controller接收请求 -> Service层处理业务逻辑 -> Mapper层操作MySQL数据库 -> 数据以JSON格式返回 -> 小程序端渲染页面。
管理后台采用类似模式,不过为了方便实现,后台常用thymeleaf模板引擎做服务端渲染,或者也用前后端分离的Vue页面。我偏向于后台用Vue + Element UI做一套简单的管理界面,因为同一套后端API既能服务小程序又能服务后台,演示时也更灵活。
身份认证这块,小程序端使用JWT令牌。流程是:小程序端wx.login拿到code -> 传给后端 -> 后端调用微信接口换取openid -> 查询数据库找到对应用户则直接生成JWT返回,没找到则自动注册再返回。小程序端拿到token后存储在本地storage,后续每个请求都在header里带上token,后端通过拦截器统一解析。
2.4 为什么要用MyBatis-Plus而不是JPA或纯MyBatis
这个选择背后有实操考量。JPA虽然在关系映射上更自动,但对复杂查询的支持需要写JPQL,网上教程中电商项目的示例少,遇到问题不好排查。纯MyBatis灵活但样板代码多,每张表都要手写增删改查,工作量很大。
MyBatis-Plus处于两者之间:单表操作直接调用内置方法,连SQL都不用写,比如selectById、insert、updateById这些;多表关联和复杂统计则在XML文件里写SQL,灵活度保留得很好。它还带分页插件、代码生成器、条件构造器,这些对毕设推进的帮助非常明显。
我举例说明一下分页。商品列表分页如果自己写,要先写一条count查询再写一条limit查询,还要处理总页数计算。MyBatis-Plus只需要配置一个分页插件,然后:
java复制Page<Product> page = new Page<>(current, size);
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Product::getStatus, 1)
.and(w -> w.like(StringUtils.isNotBlank(keyword), Product::getName, keyword))
.orderByDesc(Product::getCreateTime);
productMapper.selectPage(page, wrapper);
一行selectPage就完成了分页查询,wrapper里的条件筛选也清晰易懂。这对节省写码时间是实打实的帮助。
3. 核心功能模块与数据库设计
3.1 用户与角色体系怎么设计
电商平台的用户体系至少包含三种角色:普通消费者(买家)、农户或商家(卖家)、平台管理员。设计上的常见做法是用户表里存一个role字段做区分。这里有个容易纠结的点:到底用一张用户表加角色字段,还是拆成用户表和角色表?
毕设项目建议一张表搞定。角色就管理员、农户、买家三种,用role字段区分就够了。如果你要用Spring Security做细粒度的权限控制,可以设计角色表 + 权限表,但对毕设来说这是过度设计,还会引入大量配置成本。我用一张user表,通过role值控制前端能访问的接口和页面,简单直接。
用户表的核心字段:id、openid(微信唯一标识)、username、password(后台管理员登录用)、nickname、avatar、phone、role(0管理员、1买家、2农户)、status(是否禁用)、create_time。
这里要特别说明openid的设计:普通用户通过微信小程序登录时,我们其实拿不到用户的真实手机号或密码(除非用户主动授权手机号),靠的就是openid识别用户身份。每个微信用户在每个小程序下有唯一的openid,这是用户登录的核心凭证。
另外农户入驻功能,建议单独建一张farmer_apply表,存农户的申请信息(姓名、电话、身份证号、农产品介绍、申请状态)。管理员审核通过后,把用户表的role更新为2,同时给农户生成一个属于自己的店铺维度。这个设计在答辩时很有亮点,体现你考虑了“审核”这个业务场景。
3.2 商品、购物车、订单模块的表结构
商品表product是另一个核心表。字段包括:id、category_id(分类)、farmer_id(所属农户)、name、sub_title(副标题)、cover_image、detail_images(详情图片)、price(单价)、stock(库存)、sales(销量)、status(0下架、1上架)、is_recommend(是否推荐)、create_time、update_time。
这里有两个容易被忽略的点。一是库存和销量字段,库存扣减是电商里最容易出并发问题的环节,后文我会单独讲。二是状态字段和管理操作绑定,商品审核通过后才能上架,管理后台下架商品后小程序端立即不可见。
购物车表cart相对简单:id、user_id、product_id、quantity、checked(是否选中)、create_time。商品加入购物车时先查是否已存在同款,存在则数量加一,不存在则插入新记录。
订单模块是整张数据库设计中最复杂的,分散在两个表里。订单主表orders(order是MySQL的关键字,千万别用它做表名)存放订单整体信息:id、order_no(订单编号)、user_id、total_price、status、receiver_name、receiver_phone、receiver_address、pay_time、delivery_time、finish_time、create_time。订单明细表order_item存放每个商品项:id、order_id、product_id、product_name、product_image、price、quantity、subtotal。
为什么要拆成主子表?因为一个订单里可能包含多个商品,每个商品的名称、价格、数量都可能和下单时的商品表快照不同。如果不做快照,后续商品改价或删除,订单数据就乱了。这个设计在答辩时回答“为什么拆订单表”时直接能说清楚。
订单状态建议用数字或字符串表示,我常用:0待付款、1待发货(已付款)、2待收货(已发货)、3已完成、4已取消。状态流转在Service层统一处理,避免小程序端任意改状态。
3.3 扶贫助农特色功能怎么做出差异化
这部分是让项目摆脱“普通商城换皮”感觉的关键,也是答辩时能聊起来的亮点模块。
第一个可以做的是助农专区配置。可以在商品上加一个is_help字段,助农商品在首页单独一个入口展示,页面里用横幅标语配合农产品图片展示,营造主题氛围。后台管理员可以配置哪些商品进入助农专区。
第二个常见的是农产品溯源信息展示。商品表加一个source_place(产地)、grow_process(种植过程简介)、farmer_story(农户故事)字段。商品详情页展示这些内容,配上农户头像和故事,让用户感受到“我买的不是普通商品,是助农帮扶”,这个设计很戳评委的点。
第三个是数据统计模块。管理后台首页展示平台累计销量、累计销售额、订单总数、农户入驻数量,用一个简单的柱状图或折线图展示近7天/30天销售额趋势。这个用ECharts自带的Demo改一改就行,但呈现效果非常加分。
这三个模块都不需要额外引入复杂技术,只是利用了已有用户、商品、订单数据做增补展示,但让整个系统的业务完整性上了一个台阶。
3.4 数据库表结构设计关键点总结
我直接把核心表列出来,方便你对照建表:
| 表名 | 核心字段 | 说明 |
|---|---|---|
| user | openid, username, password, role, nickname, phone, status | 用户主表,三种角色共用 |
| farmer_apply | user_id, name, phone, id_card, intro, status | 农户入驻审核表 |
| category | name, sort, icon | 农产品分类 |
| product | category_id, farmer_id, name, price, stock, cover_image, detail_images, status, is_recommend, is_help | 农产品商品表 |
| cart | user_id, product_id, quantity, checked | 购物车表 |
| orders | order_no, user_id, total_price, status, receiver_info, pay_time | 订单主表 |
| order_item | order_id, product_id, product_name, price, quantity, subtotal | 订单明细表 |
| address | user_id, receiver_name, receiver_phone, province, city, detail | 收货地址表 |
| banner | image, link_type, link_id, sort | 首页轮播图表 |
每张表都要加create_time、update_time,这是约定俗成。另外注意几点:商品价格用decimal(10,2)避免浮点精度问题;detail_images可以用JSON字符串存储多个图片地址,查询时解析成数组;order_no要保证唯一,可以用时间戳加用户ID和随机数拼接生成。
4. 服务端核心实现细节
4.1 微信登录与JWT鉴权完整流程
这是整个服务端最关键的接口,也是很多项目第一次联调时卡住的地方。我先说完整流程,再贴核心代码。
小程序端点击登录按钮时,调用微信的wx.login接口获取一个临时code,这个code有效期只有5分钟,而且只能使用一次。小程序端把这个code传给后端自己的登录接口,后端拿着code去请求微信的接口https://api.weixin.qq.com/sns/jscode2session,微信会返回该用户的openid和session_key。openid就是用户的唯一身份标识。
拿到openid后,后端查user表:
- 如果用户存在,直接生成JWT返回
- 如果用户不存在,自动注册一个新账号,再生成JWT返回
核心代码如下:
java复制@PostMapping("/wx/login")
public Result wxLogin(@RequestBody WxLoginDTO dto) {
// 1. 调用微信接口换取openid
String url = "https://api.weixin.qq.com/sns/jscode2session?appid="
+ appId + "&secret=" + appSecret + "&js_code=" + dto.getCode()
+ "&grant_type=authorization_code";
String result = HttpClientUtil.doGet(url);
JSONObject json = JSONObject.parseObject(result);
String openid = json.getString("openid");
// 2. 根据openid查用户,不存在则自动注册
User user = userService.getByOpenid(openid);
if (user == null) {
user = new User();
user.setOpenid(openid);
user.setNickname("微信用户" + RandomUtil.randomNumbers(6));
user.setRole(1);
user.setStatus(1);
userService.save(user);
}
// 3. 生成JWT返回给前端
String token = JwtUtil.createToken(user.getId(), user.getRole());
return Result.success(token);
}
这里有几个关键点你必须注意:
appid和appsecret要配置在后端的application.yml里,不要写死在代码里。小程序端调用后端登录接口时,不需要自己拿appid和secret,这些是后端和微信服务器之间的凭证,如果泄露到前端,别人可以冒充你的小程序调用微信接口。
JWT的密钥要设一个足够复杂的字符串,解析时用同一密钥验证。我给一个简单的JwtUtil封装,包含创建和解析两个方法,注意设置过期时间,一般7天或30天,过期后需要重新登录。
登录之后的鉴权用一个拦截器实现。拦截器里读取请求头中的token,解析出userId和role,存入ThreadLocal,后续代码从ThreadLocal获取当前用户。如果token缺失或过期,统一返回401错误码,小程序端收到401后跳转到登录页。
这类项目里很多同学忽略了拦截器配置,导致接口裸奔,这是答辩时被问到“接口安全怎么保证”就很尴尬的事。
4.2 商品和订单接口设计要点
商品接口相对简单,就是列表查询和详情查询。列表接口支持分类筛选、关键词搜索、按销量或价格排序。详情接口返回商品基本信息、农户信息、溯源信息、评价列表。
我要重点讲订单创建这个接口,因为它涉及一个多表事务操作,代码如下:
java复制@Transactional
@Override
public OrderVO createOrder(OrderCreateDTO dto, Long userId) {
// 1. 校验购物车选中项或直接提交的商品列表
List<OrderItemDTO> items = dto.getItems();
// 2. 计算总价并从数据库查最新价格和库存
BigDecimal totalPrice = new BigDecimal("0");
List<OrderItem> orderItems = new ArrayList<>();
for (OrderItemDTO item : items) {
Product product = productService.getById(item.getProductId());
if (product == null || product.getStatus() != 1) {
throw new BusinessException("商品不存在或已下架");
}
if (product.getStock() < item.getQuantity()) {
throw new BusinessException("商品库存不足");
}
totalPrice = totalPrice.add(product.getPrice().multiply(new BigDecimal(item.getQuantity())));
// 构造订单明细快照
OrderItem orderItem = buildOrderItem(product, item.getQuantity());
orderItems.add(orderItem);
}
// 3. 扣减库存(注意并发问题)
boolean success = productService.deductStock(item.getProductId(), item.getQuantity());
if (!success) {
throw new BusinessException("库存扣减失败");
}
// 4. 生成订单主表 + 明细表
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(userId);
order.setTotalPrice(totalPrice);
order.setStatus(0);
order.setReceiverName(dto.getReceiverName());
order.setReceiverPhone(dto.getReceiverPhone());
order.setReceiverAddress(dto.getReceiverAddress());
orderService.save(order);
// 5. 保存订单明细
for (OrderItem orderItem : orderItems) {
orderItem.setOrderId(order.getId());
orderItemService.save(orderItem);
}
return buildOrderVO(order, orderItems);
}
这个方法上加了@Transactional注解,保证所有数据库操作要么全部成功,要么全部回滚。经常有同学忘了添加注解,然后出现“订单创建了但库存没减”这种数据不一致情况,排查起来很费劲。
扣减库存这里有个并发隐患。如果两个用户同时下单购买同一商品,两个请求同时读到库存为1,同时执行扣减,最后库存会变成-1。比较简单的做法是用数据库的乐观锁或原子更新:
java复制// 使用条件更新,库存足够才更新成功
int rows = productMapper.deductStock(productId, quantity);
// SQL: UPDATE product SET stock = stock - #{quantity},
// sales = sales + #{quantity} WHERE id = #{productId} AND stock >= #{quantity}
if (rows == 0) {
throw new BusinessException("手慢了,库存不足");
}
rows == 0表示更新影响行数为0,说明库存不足或商品被下架。这个写法比读出来再减更安全,也更容易在答辩时说明你的并发考虑。
4.3 文件上传与图片处理方案
农产品展示非常依赖图片效果,所以上传功能必须做好。在Spring Boot里,文件上传通常有本地存储和OSS对象存储两种方案。毕设项目推荐本地存储,把图片存到服务器的某个目录,然后通过一个映射路径访问。
配置如下:
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
file:
upload-path: /data/upload/
access-path: /images/**
然后写一个配置类,把本地磁盘路径映射到URL路径:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${file.upload-path}")
private String uploadPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**")
.addResourceHandler("file:" + uploadPath);
}
}
上传接口用MultipartFile接收文件,生成一个UUID或者时间戳文件名,避免文件名冲突和中文乱码:
java复制@PostMapping("/upload")
public Result upload(MultipartFile file) {
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = System.currentTimeMillis() + RandomUtil.randomString(4) + suffix;
File dest = new File(uploadPath + newFileName);
file.transferTo(dest);
return Result.success("/images/" + newFileName);
}
我遇到很多人在这一步卡住,原因基本是磁盘路径不存在导致transferTo报错。解决方法是启动时自动创建目录,或者在upload方法里先FileUtil.mkdir(uploadPath)。
还有两个细节容易踩坑:一是小程序wx.uploadFile上传时,name参数要跟后端MultipartFile参数名一致;二是管理后台如果用<img>标签回显图片,开发环境地址形如http://localhost:8080/images/xxx.jpg,需要保证后端启动端口和图片映射路径配置正确。
4.4 接口安全与参数校验
除了登录鉴权,还有几道安全防线要加。最基础的是参数校验,用Spring自带的@Validated注解加在Controller方法的DTO参数上,配合字段上的校验注解:
java复制public class OrderCreateDTO {
@NotBlank(message = "收货人不能为空")
private String receiverName;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String receiverPhone;
@NotBlank(message = "收货地址不能为空")
private String receiverAddress;
@NotEmpty(message = "商品列表不能为空")
private List<OrderItemDTO> items;
}
这样前端传过来的数据在进入业务逻辑前就被拦截了,避免脏数据直接进入数据库。不过要记得在全局异常处理器里统一捕获校验异常,返回带字段错误信息的JSON,否则前端拿到的是默认错误格式,体验很差。
还有一个很多人忽略的点:商品列表接口要区分买家端和管理端。买家端只能看到status=1(上架)的商品,管理端可以看到全部商品。我经常见到项目里一个查询接口把所有商品都返回了,前端再通过状态过滤,这在数据量小的时候没问题,但答辩时容易被追问“数据权限怎么做”,所以最好在后端接口上就区分开。
5. 小程序端核心实现与联调
5.1 小程序项目结构搭建
小程序端的目录结构需要提前规划好,不要把所有页面都堆在pages根目录。页面多的时候管理起来很痛苦,推荐这样组织:
code复制miniprogram/
├── pages/
│ ├── index/ // 首页
│ ├── category/ // 分类页
│ ├── product/ // 商品详情页
│ ├── cart/ // 购物车
│ ├── order/
│ │ ├── confirm/ // 确认订单
│ │ └── list/ // 订单列表
│ ├── user/ // 个人中心
│ ├── login/ // 登录页
│ └── address/ // 地址管理
├── utils/
│ ├── request.js // 请求封装
│ ├── auth.js // 登录状态管理
│ └── util.js // 通用工具函数
├── components/
│ ├── product-card/ // 商品卡片组件
│ └── empty-state/ // 空状态组件
├── app.js
├── app.json
└── app.wxss
一个小建议是用上Components自定义组件。比如商品卡片在首页、分类页、搜索结果页都要用,抽成一个组件后,改样式只需改一处,减少大量重复代码。这个设计在代码评审时也会是加分项。
app.json里要配置好页面路由、底部tabBar(通常有首页、分类、购物车、我的四个tab)、全局窗口样式。tabBar图标需要准备本地png图片,在iconfont或阿里巴巴矢量图标库都能找到。
5.2 请求封装和登录态管理
小程序端的核心基础代码是request.js,因为所有业务都要调用。如果每个页面都写一遍wx.request,代码会非常冗余且难以维护。我建议封装成一个Promise风格的请求函数:
javascript复制const BASE_URL = 'http://localhost:8080/api';
function request(url, method = 'GET', data = {}) {
return new Promise((resolve, reject) => {
const token = wx.getStorageSync('token');
wx.request({
url: BASE_URL + url,
method: method,
data: data,
header: {
'Content-Type': 'application/json',
'token': token || ''
},
success(res) {
if (res.statusCode === 401) {
// token过期或未登录,跳转登录页
wx.removeStorageSync('token');
wx.navigateTo({ url: '/pages/login/login' });
reject(res.data);
return;
}
if (res.data.code !== 200) {
wx.showToast({ title: res.data.msg, icon: 'none' });
reject(res.data);
return;
}
resolve(res.data.data);
},
fail(err) {
wx.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
reject(err);
}
});
});
}
module.exports = { request, BASE_URL };
登录页的逻辑:页面加载时先检查本地有没有token,有就直接返回上一页或跳转首页;没有则调用wx.login获取code,再调用后端登录接口:
javascript复制wx.login({
success(res) {
const code = res.code;
request('/wx/login', 'POST', { code: code }).then(token => {
wx.setStorageSync('token', token);
// 跳转首页或返回上一页
});
}
});
这里要注意,开发调试时后端接口地址是http://localhost:8080,微信开发者工具里需要在详情-本地设置中勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”,否则request会被拦截报域名不合法的错误。真机预览时,localhost就访问不了了,需要后端的局域网IP,比如http://192.168.1.100:8080,手机和电脑要在同一WiFi下。
5.3 首页、商品列表和详情页实现
首页通常由轮播图、助农专区入口、热门农产品列表组成。轮播图接口从banner表读取,助农专区入口是一个固定路由跳转到商品列表页并带上isHelp=1参数。
商品列表页要支持分类筛选、分页加载。分页用的是触底加载模式,onReachBottom时请求下一页,注意加一个loading状态防止重复请求。首页推荐商品和服务端联调时,我先确认接口返回的数据结构,写成:
javascript复制{
code: 200,
msg: "success",
data: {
records: [...], // 当前页数据
total: 100, // 总记录数
current: 1, // 当前页码
size: 10 // 每页大小
}
}
商品详情页是展示信息最全的页面,包括图片轮播、价格、库存、销量、农户信息、溯源信息、加入购物车/立即购买按钮。这里的sku(规格)一般不用做,毕设商品大多没有规格属性,价格直接是一个值,这大大降低了页面复杂度。
加入购物车逻辑很直接,检查用户登录态,组装参数调用后端接口。立即购买则是跳过购物车直接跳到确认订单页,参数是商品ID和数量。
5.4 用户中心和订单流程
用户中心页面展示用户头像、昵称、订单入口(待付款、待发货、待收货、已完成)、收货地址管理、农户入驻入口(普通用户)、退出登录。
这里有个要注意的逻辑:农户入驻入口只对普通用户展示,如果用户已经是农户角色,则显示“我的店铺”入口。我的店铺页面可以做简单的商品发布和订单查看功能。这个功能如果全部实现,代码量会增加不少。如果时间紧,建议做一个简化版:农户可以发布商品,查看自己商品的订单列表,管理发货状态。这已经能覆盖大部分演示需求了。
订单流程在小程序端的核心是状态展示和操作按钮。待付款订单显示“去支付”按钮(毕设通常用模拟支付,点击后直接调用后端支付接口把状态改成待发货);待发货订单(已付款)显示“申请退款”按钮(可选实现);待收货订单显示“确认收货”按钮;已完成订单没有操作按钮只展示评价入口(评价功能可以和商品详情页的评论列表配合实现)。
模拟支付这个点,答辩时一定要主动说明:真实微信支付需要企业主体的小程序账号和微信支付商户号,个人小程序无法开通。我的做法是在下单确认页显示应付金额,点击“提交订单”后生成订单(状态0待支付),再点击“支付”时调一个mockPay接口,后端直接把订单状态更新为1。你可以在接口里加一行注释说明这里预留了微信支付配置,如果接入真实微信支付只需替换这段逻辑。
6. 环境部署与运行配置
6.1 本地开发环境准备清单
开发前需要准备好的工具和环境,我列一个清单:
| 工具 | 版本建议 | 用途 |
|---|---|---|
| JDK | 1.8 | 后端运行环境 |
| Maven | 3.6+ | 后端依赖管理 |
| MySQL | 5.7+ | 主数据库 |
| IDEA | 2021+ | 后端开发IDE |
| 微信开发者工具 | 最新稳定版 | 小程序开发调试 |
| Navicat | 任意版本 | 数据库可视化管理 |
这里要注意的是MySQL安装完成后,需要在application.yml里配置账号密码、数据库名。同时注意数据库时区配置,连接串里建议加serverTimezone=Asia/Shanghai,否则控制台会报时区错误,或者数据时间差8小时。
SQL脚本文件建议在项目根目录放一份init.sql,包含建库、建表、基础数据插入。这既是项目文档的一部分,也是拷给别人演示时必用的东西。我就吃过亏,项目写完了没整理SQL脚本,换电脑时数据库里没数据,演示效果大打折扣。
6.2 后端打包部署步骤
项目开发完成后,本地运行直接用IDEA启动SpringBootApplication即可。但如果你想打jar包部署到服务器,或者演示前需要在别的电脑上跑,建议用Maven打包:
bash复制mvn clean package -DskipTests
打包成功后,在target目录下会生成一个xxx.jar文件。服务器上执行:
bash复制java -jar xxx.jar --spring.profiles.active=prod
如果你有application-prod.yml配置文件,可以用这种方式指定生产环境配置。没有的话就去掉这参数直接java -jar启动。
还有一个在服务器上保持程序不中断的启动方式,用nohup:
bash复制nohup java -jar xxx.jar > app.log 2>&1 &
启动后通过tail -f app.log查看日志。看到“Started Application in x.xxx seconds”就说明启动成功了。Linux服务器上部署MySQL后,注意设置远程访问权限和开放3306端口,否则后端服务连不上数据库。
6.3 小程序上线前的基本配置
如果只是毕设演示,小程序走“开发版”就够了,微信开发者工具里直接预览或真机调试就能演示。但如果要上线发布,需要在微信公众平台注册小程序账号,完成认证(个人主体可以认证部分类目),然后在“开发管理-开发设置”里获取正式的AppID和AppSecret。
上线前还有几个必做步骤:
第一,在小程序后台配置request合法域名。小程序正式版里请求的域名必须是HTTPS,不能用IP或HTTP。如果你没有云服务器和备案域名,演示阶段就不考虑上线,用开发版即可。
第二,发布前要做版本审核。在微信开发者工具中点击“上传”按钮,把代码上传到微信后台,然后在小程序管理后台“版本管理”中提交审核。审核周期一般1-7天,个人开发者需要提前准备好测试账号说明。
第三,后端接口需要部署到公网服务器,并配置好SSL证书。这里分享一个常见坑:上线后小程序请求不到接口,通常不是代码问题,而是HTTPS证书没配置好或域名没有备案。你可以在浏览器直接访问接口地址看是否正常返回JSON,以此判断是网络问题还是证书问题。
7. 常见问题排查与避坑指南
7.1 联调期间的高频问题速查表
我把这类项目实战中频繁出现的问题整理成一个速查表,每一项都是真实踩过的:
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 小程序请求接口报“域名不合法” | 开发工具未勾选跳过域名校验 | 详情-本地设置勾选“不校验合法域名” |
| 登录接口报code无效 | code一次性且5分钟过期 | 重新调用wx.login获取新code |
| 后端启动报Consider defining a bean | Service接口未加@Service或扫描包错误 | 检查启动类@ComponentScan路径 |
| MySQL连接报Public Key Retrieval异常 | MySQL 8.0驱动默认要求获取公钥 | 连接串加allowPublicKeyRetrieval=true |
| 上传图片后访问403/404 | 本地磁盘路径映射不对 | 检查WebConfig映射路径和URL前缀是否一致 |
| 订单时间差8小时 | 数据库时区未配置 | 连接串加serverTimezone=Asia/Shanghai |
| 前端传中文乱码 | 字符编码不一致 | 确认后端application.yml配置characterEncoding=utf8 |
| 商品列表接口慢 | 缺少索引 | 给product表category_id、status字段加索引 |
| 接口报401但后端没有日志 | 拦截器拦截了OPTIONS预检请求 | 拦截器放行OPTIONS请求 |
| IDEA导入项目后找不到依赖 | Maven未重新加载 | 右键项目Maven-Reload Project |
7.2 跳过域名校验的坑与测试技巧
微信开发者工具中跳过域名校验是为了开发方便,但有个副作用:模拟器里头像上传、图片加载等涉及外域资源的操作非常慢或失败。比如商品详情页里图片URL是http://localhost:8080/images/xxx.jpg,在开发工具里能加载,但在真机预览里就无法加载,因为真机要求HTTPS。
我的经验是:准备两张图片,一张产品图放本地,另一张从线上图床获取,用来验证远程图片场景。如果只想快速在真机演示,调试后端时可以用内网穿透工具或直接把后端接口地址改为电脑局域网IP。
另外,前端报“network error”时,先别急着怀疑代码,先用Postman或浏览器测试后端接口是否正常,能排除大部分环境问题。
7.3 后端接口联调时最容易犯的错
小程序和Spring Boot联调阶段,最多的坑是参数格式不匹配。小程序端wx.request的POST请求,Content-Type默认是application/json,后端Controller接收时要用@RequestBody注解。我看到很多同学后端写的是@RequestParam,前端传的是JSON,结果参数就是接收不到。
调试方法很简单:在后端Controller入参处加一行日志打印,或者用debug模式看清楚收到的数据。更快的是用Postman先测后端接口,正常后再去调小程序端,这样能判断问题出在前端还是后端。
另一个常见错误是后端返回的数据结构和前端预期不一致。比如后端返回的是{code:200, data:{list:[...]}},前端代码却取的是res.data.list,而数据实际在res.data.data.list。建议项目前期就统一返回类Result的格式,前端封装一个统一的解包方法,省得每个页面写一遍重复逻辑。
7.4 我的独家避坑经验
做这类项目最有成就感的是第一版跑通,最耗神的往往是跑通之后的细节打磨。分享几个我摸索出来的经验。
第一个是关于数据库初始化数据。开发过程中需要一些商品图片、农户信息、轮播图数据才能看出效果。建议准备一组固定的测试图片地址和文案,比如10个商品分4个分类,每个商品配上稍显真实的价格和名称,做演示时直接展示这些数据,效果比临时建一堆随意数据好得多。
第二个是关于前后端接口的命名规范。接口统一以/api开头,业务模块名在第二层,比如/api/product/list、/api/order/create。这样每个接口的职责一目了然,答辩介绍项目时口述也清晰。同时接口名尽量用名词加动词的组合,不要用含糊的/getData。
第三个是一些小细节:用户下单成功后有短提示“下单成功,请尽快支付”;商品库存不足时点击购买按钮置灰并显示“已售罄”;订单列表空数据显示“暂无订单,去逛逛”。这些细节看着不费劲,但演示时会给评委留下“这同学像一个真正做过产品的人”的印象。
8. 项目扩展与答辩准备建议
8.1 三个能在答辩环节加分的扩展方向
如果你的时间充足,或者想给项目增加一些亮点,我建议从下面三个方向里选一个深入:
第一个方向是数据统计可视化。在管理后台增加一个“销售分析”页面,用ECharts展示近30天的销售额折线图、商品分类销售占比饼图、农户销量排行榜。这些数据都可以从订单表和商品表聚合统计出来。做之前要稍微规划一下SQL,比如按天统计销售额:
sql复制SELECT DATE_FORMAT(create_time, '%Y-%m-%d') as day, SUM(total_price) as amount
FROM orders
WHERE status IN (1, 2, 3) AND create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
ORDER BY day
第二个方向是引入缓存层。把商品列表、轮播图这类热点数据放到Redis里,设置过期时间比如10分钟,接口请求时先查缓存,缓存没有再去查数据库并回填。在答辩时说明“引入Redis是为了缓解数据库压力”比单纯写上“用了Redis”更有说服力。
第三个方向是做农产品溯源信息。可以给每个商品绑定一个溯源编号,商品详情页展示从种植、施肥、采摘到上架的图文记录。数据表只需要一张trace表存环节记录,开发量不大,但功能亮点很突出,和助农主题契合度极高。
8.2 答辩时会被追问的经典问题
这个项目做完后,评委大概率会围绕以下几个问题追问,提前准备好答案很重要。
“为什么用Spring Boot而不用SSH或SSM?”回答要点:Spring Boot简化了配置,内嵌Tomcat容器,适合快速构建微服务,是当前Java后端开发的主流框架。
“怎么保证接口的安全性?”回答要点:JWT鉴权 + 拦截器 + 参数校验 + 登录令牌过期处理,可以再补充说明为防止SQL注入,MyBatis-Plus默认使用预编译语句,SQL也全部用预编译参数。
“订单并发你怎么处理?”回答要点:扣减库存时使用数据库条件更新,UPDATE product SET stock=stock-#{quantity} WHERE id=#{productId} AND stock>=#{quantity},通过影响行数判断是否扣减成功。这就把并发问题转移到了数据库的行锁层面。
“如果用户支付了但订单状态没更新怎么办?”回答要点:项目中预留了支付回调的接口位,真实微信支付会通过回调通知后端,后端在回调中更新订单状态。如果是模拟支付,直接提交成功即更新状态。
“为什么不直接用现成商城源码?”回答要点:虽然现有商城系统很多,但自己实现可以从需求分析、表结构设计、接口设计到前后端联调完整走一遍,对全栈开发流程有深入理解,同时助农业务也有自己的特色逻辑。
8.3 文档撰写和演示视频录制建议
毕设文档(毕业论文)和演示视频是交付物里不可忽视的部分。文档建议按“需求分析 - 系统设计 - 数据库设计 - 功能实现 - 测试分析”的结构组织,每个模块配上界面截图和核心代码片段,图表比文字重要。
关于系统测试章节,不用做得很复杂,但要真实记录测验用例。比如“测试用户添加商品到购物车成功”“测试库存不足提示”这类基本用例,附上测试结果截图。这一章在答辩时直接回答“系统如何保证质量”的问题。
演示视频录制有个小技巧:先录管理员后台上架商品、审核农户的操作,再切到小程序端展示用户购物下单的完整流程,最后录一段用户中心订单状态变化的画面。录的时候注意先整理桌面,浏览器窗口调整到合适的分辨率,打开开发者控制台显示接口请求日志,这样评委能直观看到数据交互过程。
许多同学在录制视频时容易忘记中途查看接口返回,导致“点一个按钮画面不动”或“看不出请求成功”的尴尬。加上接口请求日志(比如Chrome开发者工具Network面板)会让演示更有说服力,评审老师能看到你确实调到了后端并拿到了数据。
8.4 我个人操作中的一点体会
做这类全栈项目最核心的体验是“不要讨厌重复,但要减少重复”。比如商品列表展示在小程序多个页面都会用到,首次实现时花点时间抽成组件;订单状态枚举值在后端多个判断里反复出现,在一开始就定义一个常量类,后续改动会轻松许多。很多项目前期图快省了这一步,到了后面牵一发动全身,真的很痛苦。
另外,答辩时千万不要只讲“我做了一个系统”就完了,要用“业务场景 + 核心模块 + 技术亮点”的方式来讲。我当时带的学生里,有个最普通的助农商城项目,但他把农户入驻审核流程、订单状态机流转、数据统计报表都讲清楚了,评委明显表现出兴趣,追问的问题也都答得上来,最终成绩比一些功能更花哨但表达不清的项目还好。这说明技术完成度是一方面,呈现方式同样决定最终评价。
