从接到这个题目开始,很多人的第一反应就是"又是一个典型的增删改查毕业设计"——确实,基于SpringBoot和Vue做管理系统已经是Java方向最经典的组合拳了。但真正动手做"扶贫惠农推介系统"的时候你会发现,它跟普通的商品管理系统完全不是一个难度量级:你要处理多层角色权限、农产品的季节性和地域性特征、供需信息的撮合逻辑,还得让界面看起来真的像"惠农服务"而不是"后台管理面板"。这篇文章我把整个系统的落地过程拆开讲清楚,从业务建模、技术选型到数据库设计、核心接口实现,再到前端页面和部署上线,每一步用到的关键代码和设计思路都会给你交代明白。如果你正准备做一个类似的SpringBoot+Vue全栈项目,或者想在毕设里把"推介系统"做出真正可运行、有说服力的样子,这篇内容值得你从头读完。
1. 扶贫惠农推介系统到底是什么:业务角色与功能地图
先说个很多人容易踩的坑:接到这类项目,第一件事不是打开IDEA建工程,而是把业务方(或者你自己假设的业务方)到底想要什么捋清楚。一个挂着"推介"二字的系统,本质不是商品上架加购物车那种电商闭环,而是一个信息撮合平台——它要解决的核心问题是:让买得着的人看见好东西,让卖不动的人找到出路,让管理方有数据可看、有记录可查。
1.1 系统的四个核心角色与业务闭环
我把这个系统的用户拆成四类,每一类的权限和交互逻辑完全不同,这决定了后端的接口设计和前端的页面结构:
- 农户/合作社:能维护自己的基础档案,管理自家的农产品,比如上传图片、设置产量、填写产地信息,还能看到自己被访问了多少次、有没有采购意向。
- 帮扶干部/运营人员:这是系统里最关键的中间角色。他们要审核农户提交的信息,定期录入帮扶记录(比如走访情况、技术培训、销路对接结果),还要发布惠农政策、公告和采购需求。
- 采购商/普通用户:平台面向外部开放的角色,可以浏览农产品、按品类和产地筛选、收藏感兴趣的商品、发起采购意向,也可以查看资讯公告。
- 系统管理员:负责配置基础数据、管理全部账号和内容,看统计报表。
这四个角色的业务指标不太一样。农户关心"我的东西有没有被看到",采购商关心"哪里有好货、怎么联系",运营人员关心"帮扶过程有没有留痕",管理员关心"整个平台的活跃度和成交转化"。一个合格的推介系统,必须让这四类人都觉得自己在用一套"趁手"的工具,而不是在被迫填表。
1.2 功能模块按业务链拆分
基于上面的角色划分,功能模块就不是简单堆菜单了,而是按业务链组织:
- 基础信息管理:区域管理(省市区三级联动,很多农产品的推介非常依赖产地维度)、农户档案管理、农产品分类管理。
- 内容推介模块:农产品列表的图文展示、热门推荐位运营、按季节和产地筛选、关键词搜索。这是系统的门面。
- 供需撮合模块:采购商能发求购信息,运营人员能做匹配推荐,农户能看到意向订单并处理。这块是体现"推介"价值的主战场。
- 帮扶工作台:帮扶记录录入、走访计划管理、农户问题跟踪,所有记录支持状态流转(待处理、处理中、已完成)。
- 数据统计看板:农产品浏览量排行、供需匹配成功率、帮扶记录的完成率这些指标,用图表展示给管理员和运营人员。
- 系统管理:用户管理、角色权限配置、操作日志。
我建议你先把功能地图画出来再动代码。这个系统表面看只有十几个菜单,但菜单之间是有依赖关系的,比如运营人员必须先把分类配好,农户才能上传农产品,农产品通过审核之后采购商才能看到。提前把状态机理清楚,会少走很多弯路。
2. 选型破局:SpringBoot + Vue这套组合的背后逻辑
很多初学者选技术栈是"因为别人说这个好",但真正到了要交付一个可运行系统的时候,每个选择都得有说服自己的理由。
2.1 后端为什么选SpringBoot
这个系统本质上属于典型的企业级信息系统:大量表单校验、文件上传、多表关联查询、权限控制。用SpringBoot有四个实打实的优势:
- 生态成熟:Spring Security做认证授权、MyBatis-Plus操作数据库、Actuator监控应用状态,这些组件都是现成的,不需要自己造轮子。
- 自动配置省心:数据源、对象转换、参数校验这些琐事,SpringBoot的自动配置能帮你挡掉80%的重复劳动。
- 可维护性有保障:Java的强类型约束在这种多角色、多状态的系统里特别管用。状态枚举、实体类字段一改,编译器就能帮你把错误提前找出来,这在业务逻辑复杂的场景里太关键了。
- 部署简单:一个可执行的Jar包就能跑起来,配合Nginx做前端静态资源代理,整个项目的交付成本很低。
2.2 前端为什么配Vue而不是其他
前端这块,Vue + Element Plus(或者Vue 2时代的Element UI)几乎是中后台项目的标准答案。原因也很直接:推介系统有大量的表格表单、筛选条件、弹窗确认,Element组件库能把这些页面快速搭起来,而且风格统一,不会出现自己手写CSS导致的"千奇百怪"样式。
至于Vue 2还是Vue 3,我的建议很明确:新项目直接上Vue 3 + Vite + Pinia + Element Plus。Vue 3的Composition API在管理复杂表单状态的时候比Options API好用太多,比如农产品编辑页有几十个字段,用ref和reactive管理状态比一个个data属性清晰得多。而且Vite的开发服务器启动速度极快,改一行代码热更新秒级生效,开发体验完全不一样。
注意:如果你用的是旧教程,看到有人推荐Vue 2 + Vue CLI + Element UI,不是不能跑,但生态已经明显转向Vue 3了。新项目没必要逆着生态走。
2.3 前后端分离与目录结构规划
项目采用前后端完全分离的开发模式,前端负责页面渲染和交互,后端只提供JSON接口。我习惯把工程分成两个目录:
hui-nong-system/ ├── backend/ # SpringBoot 后端工程 │ ├── src/main/java/com/huinong/ │ │ ├── controller/ # 接口层 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # 数据访问层 │ │ ├── entity/ # 数据库实体 │ │ ├── dto/ # 请求/响应对象 │ │ ├── config/ # 安全、文件、跨域等配置 │ │ └── common/ # 通用返回、异常处理、工具类 │ └── src/main/resources/ │ ├── mapper/ # XML映射文件 │ └── application.yml ├── frontend/ # Vue 前端工程 │ ├── src/ │ │ ├── api/ # 接口封装 │ │ ├── views/ # 页面组件 │ │ ├── router/ # 路由配置 │ │ ├── store/ # Pinia状态管理 │ │ └── components/ # 公共组件 │ └── vite.config.js后端按controller -> service -> mapper三层划分,前端按页面维度组织代码,刚开始别搞复杂的模块架构,简单清晰最重要,等业务复杂了再拆分也不迟。
3. 数据建模是系统的地基:核心表结构与字段设计
数据库设计是整个项目成败的关键。这个系统的表结构比较典型,我建议按"人、货、单、记录"四条线来拆,下面直接讲核心表的设计思路和关键字段。
3.1 用户与权限:RBAC模型的落地姿势
用户表、角色表、用户角色关系表,这是Java系统里最经典的一套RBAC模型。我的建议是不要为每个角色单独建表,而是用角色编码区分:
CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(100) NOT NULL COMMENT 'BCrypt加密后的密码', real_name VARCHAR(50) COMMENT '真实姓名', phone VARCHAR(20) COMMENT '手机号', avatar VARCHAR(200) COMMENT '头像URL', role_code VARCHAR(30) NOT NULL COMMENT '角色编码:FARMER/GUIDER/PURCHASER/ADMIN', region_id INT COMMENT '所属区域ID', status TINYINT DEFAULT 1 COMMENT '状态:1启用,0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );角色编码直接挂在用户表上还是通过关联表,看你的设计习惯。如果系统以后角色会扩展、同一用户可能有多重身份(比如一个既是农户又是采购商),那就用标准的sys_role和sys_user_role关联表。如果只是固定四类角色,直接加role_code字段简单够用。
需要注意几个细节:
- 密码必须用BCrypt加密存储,Spring Security自带的
BCryptPasswordEncoder就能用,不要自己写MD5加盐。 region_id很重要,因为推介系统里有大量"按区域展示"的需求。区域表建议用标准的省市区码表,预置好数据。- 联系电话等信息要加校验,比如手机号唯一,避免同一个采购商注册多个账号。
3.2 两条核心业务表:农户档案与农产品
农户档案不只是一个人的资料,要把它理解为一个"店铺"概念:
CREATE TABLE farmer_profile ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '关联sys_user表的ID', farm_name VARCHAR(100) COMMENT '合作社/农场名称', intro TEXT COMMENT '农户介绍', region_id INT COMMENT '所在区域', address VARCHAR(200) COMMENT '详细地址', main_category VARCHAR(50) COMMENT '主营品类', cover_img VARCHAR(200) COMMENT '封面图URL', verify_status TINYINT DEFAULT 0 COMMENT '审核状态:0待审核,1通过,2驳回', created_by BIGINT COMMENT '登记人ID' );农产品表则是推介系统的内容核心,字段要覆盖"人、地、货、时"四个维度:
CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, farmer_id BIGINT NOT NULL COMMENT '关联农户档案ID', name VARCHAR(100) NOT NULL COMMENT '农产品名称', category_id INT NOT NULL COMMENT '分类ID(粮油/果蔬/畜牧/水产等)', price DECIMAL(10,2) COMMENT '参考价(元/斤或元/件)', stock_quantity INT COMMENT '可售数量', unit VARCHAR(20) COMMENT '单位', origin_region_id INT COMMENT '产地区域ID', season_tag VARCHAR(30) COMMENT '季节标签:春/夏/秋/冬/全年', description TEXT COMMENT '产品描述', cover_image VARCHAR(200) COMMENT '主图', images TEXT COMMENT '轮播图,JSON数组存多个URL', status TINYINT DEFAULT 0 COMMENT '状态:0草稿,1待审核,2上架,3下架', view_count INT DEFAULT 0 COMMENT '浏览次数', created_by BIGINT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );这里的设计要点:
season_tag不是简单的展示字段,它会参与推介系统的推荐算法匹配。比如夏季推荐时,标了"夏"的瓜果类产品权重更高。view_count维护一个冗余字段,避免每次都去浏览记录表做COUNT(*)。- 状态机设计要闭环:草稿 -> 待审核 -> 上架/驳回/下架。运营人员审核操作要在后台生效,农户端能实时看到状态变化。
3.3 供需撮合与浏览记录:让"推介"有数据支撑
推介系统不能只做静态展示,如果只有商品列表,那它跟普通黄页没区别。建议加两张核心表:
CREATE TABLE purchase_intention ( id BIGINT PRIMARY KEY AUTO_INCREMENT, buyer_id BIGINT NOT NULL COMMENT '采购商用户ID', product_id BIGINT COMMENT '意向产品ID', intention_type TINYINT COMMENT '意向类型:1直接求购,2询价', quantity INT COMMENT '采购数量', expected_price DECIMAL(10,2) COMMENT '期望价格', remark VARCHAR(500) COMMENT '备注', status TINYINT DEFAULT 0 COMMENT '状态:0待处理,1已联系,2已成交,3已关闭', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE product_view_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id BIGINT NOT NULL, user_id BIGINT COMMENT '浏览用户ID,游客可为NULL', view_time DATETIME DEFAULT CURRENT_TIMESTAMP );purchase_intention是体现撮合价值的关键表,农户每次登录看到的"商机列表"其实就来自这张表的待处理记录。而product_view_log用于统计分析——哪些产品受欢迎、哪些区域的农产品被关注最多,这些数据可以直接可视化到大屏或看板。
4. 后端核心模块落地:鉴权、推介逻辑与文件处理的实现细节
骨架搭好、表建完之后,就要进入核心代码阶段。这一部分我把几个最关键的模块展开讲。
4.1 登录鉴权:Spring Security + JWT的常规组合
前端是SPA应用,不是传统的Session会话,用JWT做无状态Token是主流做法。整体流程是:
- 用户提交账号密码,后端用
AuthenticationManager验证身份。 - 认证成功后生成JWT Token(包含用户ID、用户名、角色编码),返回给前端。
- 前端把Token存到
localStorage或Pinia,每次请求在Authorization: Bearer <token>头里带上。 - 后端加一个
JwtAuthenticationFilter过滤器,每次请求都验证Token的有效性,解析出用户信息放到SecurityContextHolder。
核心的Security配置类大致长这样:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/login", "/api/home/**").permitAll() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/farmer/**").hasAnyRole("FARMER", "ADMIN") .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler()); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }有几个日常开发中特别容易踩的坑:
- 跨域配置:前后端分离后,前端运行在
localhost:5173,后端在localhost:8080,必须显式配置CORS。建议用CorsFilter注册一个全局跨域配置,允许指定的前端域名,别直接用*,生产环境容易出安全问题。 - 放行接口别放太宽:我见过有人把
/api/**全部permitAll,那等于没做安全控制。图片资源、登录接口、首页浏览这些公开,其余的都要校验权限。 - 密码初始化和重置:系统里会有管理员给农户创建账号的场景,初始密码建议用手机号后六位或系统随机生成,用户首次登录后强制改密。这个流程虽然增加一点开发量,但真实项目里基本都需要。
4.2 推介逻辑:热度加权 + 季节匹配 + 地域优先
"推介"的核心不在增删改查,而在于推荐算法的思路。不搞复杂机器学习,我们用简单的加权排序就能做出效果:
public List<ProductVO> getRecommendProducts(String season, Integer regionId, int limit) { // 基础条件:已上架且通过审核 LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Product::getStatus, 2); // 季节匹配:优先展示当季产品 if (StringUtils.hasText(season)) { wrapper.eq(Product::getSeasonTag, season); } // 地域优先:采购商所在区域的产品排前面 if (regionId != null) { wrapper.orderByDesc(Product::getRegionId, regionId); } // 热度加权:按浏览量排序,浏览量近的影响更大 wrapper.orderByDesc(Product::getViewCount); wrapper.last("LIMIT " + limit); List<Product> products = productMapper.selectList(wrapper); return products.stream().map(this::toVO).collect(Collectors.toList()); }这个算法虽然简单,但已经足够支撑业务场景。想让排序更精细,可以引入时间衰减因子,比如最近7天的浏览量加权值比总浏览量更有效。实现时可以用一条子查询统计近7天product_view_log中的浏览分组数量,再和view_count做加权排序。SQL写起来略复杂,但效果提升明显。
实操建议:推荐接口一定要做Redis缓存。首页每次刷新都实时查库的话,系统压力不小。把推荐列表的缓存key设计成
recommend:product:season:region,缓存时间15分钟,既能保证新鲜度,又能大幅减少数据库访问量。
4.3 文件上传:本地存储还是对象存储
农产品图片是这个系统的门面,图片处理方案要提前定好。如果项目是毕业设计或者中小规模应用,本地文件存储就够了,实现起来也直白:
@PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { // 1. 校验文件类型和大小 if (file.isEmpty() || file.getSize() > 5 * 1024 * 1024) { return Result.error("文件不能为空且大小不能超过5MB"); } // 2. 生成唯一文件名 String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String fileName = UUID.randomUUID().toString().replace("-", "") + ext; // 3. 按日期分目录存储 String datePath = LocalDate.now().toString().replace("-", "/"); File dir = new File(uploadPath + "/" + datePath); if (!dir.exists()) { dir.mkdirs(); } // 4. 保存文件 File dest = new File(dir, fileName); file.transferTo(dest); // 5. 返回可访问的URL String urlPath = "/uploads/" + datePath + "/" + fileName; return Result.success(urlPath); }但这里有个关键配置容易被忽略:SpringBoot默认的静态资源映射只会指到classpath:/static/,你上传到磁盘的目录并不会自动变成可访问的URL。必须加一个资源配置:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/uploads/**") .addResourceLocations("file:" + uploadPath + "/"); } }如果以后部署到云服务器,更建议把图片存到OSS或MinIO之类对象存储中——好处是扛并发、不怕磁盘扩容、和前端资源完全隔离。SpringBoot整合MinIO也不复杂,几分钟能搞定,核心就几个依赖和一个配置类。
4.4 统一返回结构与全局异常处理
写后端接口时,最忌讳每个接口返回格式五花八门。统一返回结构几乎是Java项目的标配:
@Data public class Result<T> { private Integer code; // 状态码:200成功,500业务失败 private String message; // 提示信息 private T data; // 响应数据 public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }配合@RestControllerAdvice做全局异常捕获,把参数校验异常、业务异常、系统异常分别处理,前端拿到返回体后只要判断code即可,不用一遍遍写try-catch。
5. 前端Vue实现:从页面骨架到农产展示与后台管理
前端部分要注意的坑比后端更多,因为涉及页面多、状态多,而且Vue的生态版本切换比较快。这里把关键实现讲一讲。
5.1 路由与菜单:权限控制的前端配合
路由配置直接对应后端的功能菜单。要用动态路由,也就是登录之后根据用户角色动态生成菜单和路由表,而不是把所有页面全部静态注册。原因是:农户根本不应该看到运营后台的菜单,采购商也不必看到帮抚记录管理。
这里有个实用的做法:在前端的router/index.js里把需要权限的页面统一放在一个静态路由之下,用meta.roles标记哪些角色能进入:
{ path: '/farmer', component: () => import('@/views/farmer/FarmerDashboard.vue'), meta: { roles: ['FARMER', 'ADMIN'] } }然后在路由守卫里做判断:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); if (!token && to.path !== '/login') { next('/login'); } else if (token && userInfo.role) { if (to.meta.roles && !to.meta.roles.includes(userInfo.role)) { next('/403'); } else { next(); } } else { next(); } });这个方案简单可靠。真正的大型项目会用异步路由加addRoute实现更细粒度的动态注册,但对一个推介系统来说,角色级的路由守卫已经完全够用。
5.2 农产品展示页:核心页面的组件拆分
产品展示是系统门面,页面设计要往"信息流App"的方向做,而不是弄一个平板的后台表格。我把它拆成四块:
- 筛选栏:品类下拉、产地下拉(省市区三级联动)、季节标签、价格区间,筛选条件变化时立即触发列表刷新。
- 商品卡片:封面图、标题、产地、价格、季节性标签、浏览热度。卡片组件用
el-card加自定义样式就行。 - 列表状态:加载中状态、数据为空的状态、加载失败的状态都要有。很多新手只管成功路径,结果接口超时用户看到一片空白,影响非常不好。
- 分页/滚动加载:列表数据多的时候,用分页组件比无限滚动更可控,也能减少接口压力。
商品列表的接口封装就是一个标准的GET请求:
export function getProductList(params) { return request({ url: '/api/product/page', method: 'get', params }); }Axios封装的时候要注意:请求拦截器里统一加Token,响应拦截器里统一处理code !== 200的情况,比如Token过期要跳转登录页。
5.3 管理后台:表单密集场景的Element Plus用法
管理后台(运营人员和管理员使用)的页面基本离不开表格、搜索、表单弹窗这三件套。用Element Plus的时候,有几个提高效率的小经验:
- 搜索区和表格和分页器可以抽成公共组件,这样几十个列表页不用重复写几乎相同的模板代码。
- 表格操作列放"审核""编辑""删除"按钮时,权限要区分:比如审核按钮只能运营人员看到,删除按钮只能管理员看到。用
v-if="role === 'ADMIN'"控制。 - 表单校验用Element Plus的
rules,同时后端也必须做参数校验,前后端双重校验才能保证数据质量。 - 富文本编辑器(发布政策公告要写大段内容)建议用
wangeditor或quill,不要自己写textarea,不然发布出来的公告排版很灾难。
5.4 统计看板:用ECharts把数据变成一眼能看懂的东西
推介系统的数据看板不要求多复杂,但要直观体现出平台的价值。我建议至少做三个图:
- 农产品热度TOP10柱状图:直接暴露哪些产品更受欢迎,给运营做运营位调整做参考。
- 供需匹配趋势折线图:按月展示新增采购商、新增产品、成交意向数量,观察平台活跃度变化。
- 区域农产品分布饼图:按省或市聚合农产品数量,看各地资源优势。
ECharts在Vue中的用法不复杂:
import * as echarts from 'echarts'; const chartDom = ref(null); let chartInstance = null; onMounted(() => { chartInstance = echarts.init(chartDom.value); chartInstance.setOption({ xAxis: { type: 'category', data: productNames }, yAxis: { type: 'value' }, series: [{ type: 'bar', data: viewCounts }] }); }); onBeforeUnmount(() => { chartInstance?.dispose(); });要注意的坑是:图表容器在v-if控制下可能没有高度,或者Tab切换后图表不渲染。解决办法是切换Tab后用nextTick重新初始化,或者给容器设置固定的min-height。
6. 从本地到云端:打包部署、性能优化与上线排坑
项目开发完不能只在localhost跑,必须走一遍完整的部署流程。这里的经验我是真踩过不少坑才总结出来的。
6.1 前后端打包与Nginx配置
前端构建命令是npm run build,产物会在dist目录下。你有两个选择:一是把整个后端项目的src/main/resources/static目录指向dist,让SpringBoot自己托管前端静态资源。这么做最省事,一个Jar包搞定一切;缺点是前后端耦合,前端更新时还得重新打后端包。
我更推荐前后端完全分离开:前端dist目录直接扔给Nginx托管,后端以接口服务的形式运行。Nginx配置核心部分:
server { listen 80; server_name your-domain.com; # 前端页面 root /opt/huinong/frontend/dist; index index.html; # 静态资源缓存 location /assets/ { expires 7d; } # 前端路由history模式配置 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 上传文件访问 location /uploads/ { alias /opt/huinong/uploads/; } }这个配置里有几个地方需要留神:
- 前端路由必须加
try_files那行,否则Vue用history模式时,刷新页面会404。很多新手在线部署后点个刷新就白屏,原因就在这。 - 后端接口网关用
/api/前缀,前端所有AXIOS请求的baseURL都要带上这个前缀,才能在分离开的情况下正确转发。 - 上传文件的磁盘路径要和Nginx的
alias保持一致,不一致的话图片显示不出来。
6.2 数据库连接池、缓存与接口性能优化
系统上线后,如果用户量稍微上来一点,数据库往往会成为瓶颈。几个低成本优化手段:
- Druid连接池配置:在
application.yml里显式配置初始化大小、最大空闲、最大活跃连接数。别用默认值裸跑,并发一大就会出现连接等待超时。
spring: datasource: druid: initial-size: 5 max-active: 20 min-idle: 5 max-wait: 60000Redis缓存热点数据:前面提过的推荐列表是典型的读多写少场景,适合缓存。农户端产品列表也可以做分页缓存,数据变更时主动清掉对应缓存。
图片懒加载:农产品的图片列表很长,页面一次性加载几十张图非常慢。用
v-lazy指令或者IntersectionObserver做懒加载,首屏速度的提升非常明显。手动优化SQL:MyBatis-Plus虽然方便,但复杂统计查询生成的一堆嵌套子查询性能堪忧。比如统计每个区域各品类产品数的接口,我建议直接用XML手写SQL,能用
GROUP BY解决就不要在Java里循环做聚合。
6.3 上线后我遇到的三个实际问题
这里分享三个我实际部署后遇到的问题,排查过程比较典型:
问题一:图片上传成功但访问返回404
排查过程:文件确实写入了磁盘,但浏览器访问URL时404。最后定位到是Nginx的alias路径配置问题——我上传目录实际是/opt/huinong/uploads/,Nginx配置里写成了alias /opt/huinong/upload;少了个s,导致路径不匹配。修改后重启Nginx,问题解决。
问题二:前端打包后登录接口401
排查过程:本地开发的时候跨域配的是http://localhost:5173,部署上线后前端域名变成了正式域名,CORS白名单里没有这个来源,所以登录请求全部被拦截。解决办法是把跨域配置里的域改成动态从配置读取,部署时通过环境变量区分。
问题三:凌晨定时任务没执行
排查过程:我用Spring的@Scheduled做了每天凌晨统计前一天数据的任务,上线后发现一直没跑。检查日志发现服务根本没有触发。原因是我在启动类上忘了加@EnableScheduling注解,导致定时任务功能压根没激活。加上注解后一切正常。
7. 给同类系统的三条扩展思路
如果你做完基础版本还有余力,下面这几个方向可以大幅提升系统价值的含金量:
7.1 引入位置服务,做"附近好货"推荐
农产品的地域性非常强,可以在产品表已有region_id的基础上再接地图接口,把产地做成经纬度可选字段。前端用地图组件渲染农产品分布点位,用户点击地图上的点就能看到当地的产品列表。这个功能对"地域推介"的场景提升非常明显。
7.2 增加供需消息推送
采购商发求购意向后,运营人员要能第一时间收到通知。后端可以用WebSocket做一个简单的实时推送,或者更轻量地用SSE(服务端推送事件)。当农户提交新的农产品并通过审核时,也可以给订阅了对应品类的采购商推送一条消息。这个功能会让系统从"被动展示"变成"主动撮合"。
7.3 做成数据可视化大屏
如果使用方是管理部门,一个漂亮的数据大屏比一百个报表页面都有说服力。用Vue + ECharts把区域分布、供需趋势、帮扶进度这些指标整合到大屏页面上,支持全屏轮播。这个功能做出来,整个项目的展示效果会上一个档次。
根据我个人经验,这类系统最容易陷入的误区是把全部精力花在样式和页面数量上,反而忽略了数据闭环。真正有价值的推介系统,要让每一类角色每天打开它都有事可做、有数据可看——农户上线能看到自己的产品被多少人浏览、采购商上线能快速找到目标产区的当季产品、运营人员上线能处理待审核信息和撮合意向。把这条链路跑通,比多做十个页面更重要。