1. 项目概述:本庄村果园预售系统管理平台
这个基于SpringBoot+Vue的果园预售系统,是我去年帮老家本庄村开发的农产品线上销售平台。当时村里果农面临的最大痛点,就是每年水果成熟季节的集中销售压力——要么被收购商压价,要么因滞销导致损耗。系统上线后,实现了预售订单提前锁定、分级定价和采摘计划优化,第一季水蜜桃的损耗率直接降了40%。
系统采用前后端分离架构,前端用Vue 2.x + Element UI实现响应式管理界面,后端基于SpringBoot 2.7提供RESTful API,数据库选用MySQL 8.0。特别适合作为计算机专业学生的毕业设计或课程设计案例,因为:
- 业务场景真实且完整(用户端+管理端+果农端)
- 技术栈覆盖企业主流开发框架
- 包含支付对接、微信通知等实用功能模块
- 源码结构清晰且有完整开发文档
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构详解
2.1 后端SpringBoot设计
后端采用经典的三层架构,但针对农产品业务做了特殊优化:
java复制// 典型控制器示例 - 水果分类管理
@RestController
@RequestMapping("/api/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
// 支持树形分类结构查询
@GetMapping("/tree")
public Result getCategoryTree() {
List<CategoryVO> tree = categoryService.getCategoryTree();
return Result.success(tree);
}
// 带分页的条件查询
@GetMapping("/list")
public Result list(@RequestParam Map<String,Object> params) {
PageUtils page = categoryService.queryPage(params);
return Result.success(page);
}
}
关键技术点:
- 使用MyBatis-Plus 3.5实现快速CRUD开发
- 自定义统一返回结果封装(Result)
- 采用JWT+Redis实现分布式认证
- 集成Swagger 3.0自动生成API文档
- 使用Hutool工具类处理常见业务逻辑
2.2 前端Vue实现方案
前端采用Vue CLI 4.x脚手架搭建,核心模块包括:
code复制src/
├── api/ # 接口请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── UploadImg.vue # 图片上传组件
│ └── TablePage.vue # 分页表格组件
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具类
└── views/ # 页面组件
├── order/ # 订单管理
├── product/ # 商品管理
└── user/ # 用户中心
性能优化技巧:
- 使用路由懒加载减少首屏体积
- 采用keep-alive缓存高频访问页面
- 对大数据表格进行虚拟滚动优化
- 封装axios拦截器统一处理错误和loading状态
3. 数据库设计与业务实现
3.1 MySQL表结构设计
核心表关系如下图所示(主要表结构):
sql复制-- 水果商品表
CREATE TABLE `product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '水果名称',
`category_id` bigint NOT NULL COMMENT '分类ID',
`price` decimal(10,2) NOT NULL COMMENT '零售价',
`pre_price` decimal(10,2) DEFAULT NULL COMMENT '预售优惠价',
`stock` int DEFAULT '0' COMMENT '预估产量(kg)',
`pre_stock` int DEFAULT '0' COMMENT '已预售量(kg)',
`status` tinyint DEFAULT '0' COMMENT '状态:0-待上架 1-预售中 2-已售罄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 预售订单表(关键业务表)
CREATE TABLE `pre_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`user_id` bigint NOT NULL,
`product_id` bigint NOT NULL,
`quantity` int NOT NULL COMMENT '购买数量(kg)',
`actual_price` decimal(10,2) NOT NULL COMMENT '成交单价',
`total_amount` decimal(10,2) NOT NULL COMMENT '总金额',
`pickup_date` date DEFAULT NULL COMMENT '预计采摘日期',
`pickup_code` varchar(10) DEFAULT NULL COMMENT '提货码',
`status` tinyint DEFAULT '0' COMMENT '0-待支付 1-已支付 2-已取消',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 核心业务逻辑实现
预售库存控制方案:
采用乐观锁解决超卖问题,关键代码如下:
java复制@Transactional
public Result createPreOrder(OrderDTO dto) {
// 1. 校验库存
Product product = productMapper.selectById(dto.getProductId());
if (product.getStock() - product.getPreStock() < dto.getQuantity()) {
return Result.error("库存不足");
}
// 2. 使用CAS更新预售量
int update = productMapper.updatePreStock(
dto.getProductId(),
dto.getQuantity(),
product.getVersion()
);
if (update == 0) {
throw new RuntimeException("并发修改失败,请重试");
}
// 3. 创建订单逻辑...
}
特色功能实现:
- 动态采摘计划:根据预售订单的地理分布和成熟度,自动生成最优采摘路线
- 价格浮动机制:预售量达到阈值后自动调整价格阶梯
- 微信模板消息通知:关键节点(支付成功、可提货等)实时通知
4. 项目部署与扩展建议
4.1 开发环境搭建
后端环境:
- JDK 1.8+
- Maven 3.6+
- MySQL 8.0(需要开启大小写敏感配置)
- Redis 5.0+
前端环境:
- Node.js 14.x
- npm 6.x 或 yarn 1.22+
快速启动命令:
bash复制# 后端
mvn clean install
java -jar target/orchard-system-1.0.0.jar
# 前端
npm install
npm run serve
4.2 生产环境部署方案
推荐使用Docker Compose部署:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: orchard123
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
redis:
image: redis:5.0
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
4.3 毕业设计扩展方向
- 数据分析模块:增加销售数据可视化(ECharts)
- 溯源系统:集成区块链实现水果溯源
- 小程序端:开发微信小程序版本
- 智能推荐:基于用户历史购买做推荐
- 物流跟踪:对接快递鸟API实现物流查询
我在实际开发中遇到的一个典型坑是:Vue的axios拦截器中对401错误的处理没有考虑刷新token的情况,导致连续请求时会多次触发登出。解决方案是增加请求队列和token刷新锁:
javascript复制let isRefreshing = false
let requests = []
axios.interceptors.response.use(response => {
return response
}, error => {
if (error.response.status === 401) {
if (!isRefreshing) {
isRefreshing = true
return refreshToken().then(res => {
const newToken = res.data.token
setToken(newToken)
requests.forEach(cb => cb(newToken))
requests = []
return axios(error.config)
}).finally(() => {
isRefreshing = false
})
} else {
return new Promise(resolve => {
requests.push(token => {
error.config.headers['Authorization'] = 'Bearer ' + token
resolve(axios(error.config))
})
})
}
}
return Promise.reject(error)
})
