☰
基于SpringBoot2+Vue3的乡村政务办公系统开发实践
2026/10/14 10:44:24 网站建设 项目流程

乡村政务办公系统这类项目,近几年在基层数字化里需求量一直不小。标题给的信息很直白:SpringBoot2 做后端,Vue3 做前端,MyBatis-Plus 操作数据库,MySQL8.0 存数据,还带完整源码和开发文档。这意味着不用从零搭环境,拿到手就能跑起来,后续做毕业设计、课程实训或者乡镇单位内部系统改造,都能直接作为底座。

这篇博客我按实际开发流程来拆:先理需求,再选技术,然后数据库设计、前后端实现,最后部署和排错。所有内容都基于这套技术栈的真实开发经验,我会把关键步骤、参数选择、容易翻车的地方都展开说,确保你看完能照着落地。

1. 项目概述与目标

1.1 乡村政务办公的核心需求解析

乡村政务办公系统和普通的企业管理系统有区别,它的用户场景更贴近基层实际。一个典型的村镇级政务办公系统,要覆盖的不只是简单的增删改查,还要考虑办事流程、数据汇总、信息公开、文件流转这些环节。

从需求角度拆解,大概有这么几类:

  • 人员与组织管理:乡镇干部、村委会成员、网格员、村民代表等不同角色,需要统一管理和权限分配。
  • 日常办公流程:公文收发、会议通知、请假审批、用章申请、物资领用等,这些都需要审批流转。
  • 村民事务管理:村民基础档案、低保申请、宅基地审批、补贴发放记录等,数据敏感度较高。
  • 信息公开与公告:政策宣传、村务公开、通知公告,既要发布又要留痕。
  • 报表统计:需要按村、按时间统计办事数量、审批时效、人口数据等,给领导决策提供依据。

这些都是标题背后隐含的业务需求。很多刚开始做这类项目的人容易犯一个错误,就是一上来直接写代码,结果做到一半发现权限模型没设计好、审批流程写死了、统计数据查不出来。所以第一步一定要把业务场景理清楚。

1.2 功能清单与用户角色

基于上面的需求,我把这个系统拆成六大功能模块:

模块功能点说明
系统管理用户管理、角色管理、菜单管理、字典管理基础权限和数据字典,所有系统共用的部分
组织管理乡镇信息、村委会信息、部门管理树形结构,支撑数据按层级隔离
公文管理发文登记、收文登记、公文审批、公文归档核心办公场景,状态流转要清晰
村民管理村民档案、补贴记录、低保申请、宅基地申请业务数据量最大,需要支持导入导出
信息公开公告发布、村务公开、政策宣传分村展示,设置公开范围
统计报表办事统计、审批时效、人口信息统计用图表展示,前端需要可视化组件

用户角色一般分为超级管理员、乡镇管理员、村委干部、普通工作人员四级。权限设计上要做到“数据隔离+功能隔离”,不同角色看到的菜单不同,处理的数据范围也不同。比如某个村的村委干部,只能查看和办理自己村的事务,不能越权访问其他村的数据。

1.3 文档交付物与源码结构

标题里特别提到了【含文档】,这一点对这类项目很重要。单纯的源码如果没有文档,接手的人光是配置环境都要浪费大量时间。一套完整的配套文档应该至少包含三类内容:

第一是需求文档,说清楚系统要解决什么问题,模块怎么划分,用户角色有哪些。第二是数据库设计文档,每张表的作用、字段含义、表间关系都要写明白。第三是部署文档,从JDK安装到MySQL初始化再到前后端打包,一步步操作说明。

源码结构上,后端建议按标准的Maven多模块或单模块分层来组织,controller、service、mapper、entity、config各司其职。前端用Vue3的标准目录,views、components、router、store、utils都分开。这样的结构不仅后期好维护,面试或者答辩的时候讲起来也清晰。

2. 技术选型与架构设计

2.1 为什么选 SpringBoot2 + Vue3 + MyBatis-Plus

这套组合在Java Web开发领域属于典型的主流方案,但每个技术选型背后都有足够的理由,不是随手凑的。

SpringBoot2 是目前中小型项目最稳妥的选择。SpringBoot3 虽然已经发布,但考虑到大量第三方依赖的兼容性、以及JDK版本要求(SpringBoot3需要JDK17),很多生产环境和教学环境还在用JDK8。SpringBoot2 对JDK8的支持非常完善,生态资料也是最丰富的。做政务系统这种要求稳定压倒一切的项目,选成熟版本比追新版本靠谱得多。

Vue3 相比Vue2,组合式API(Composition API)让逻辑复用变得容易很多,配合Vite构建工具,开发体验和构建速度都有明显提升。配合Element Plus组件库,后台管理界面的开发效率非常高。Vue3 Composition API 的setup语法写起来简洁,但要注意响应式数据的绑定方式变了,习惯Vue2选项式写法的人需要一点适应时间。

MyBatis-Plus 是MyBatis的增强插件,它最大的价值是内置了通用的CRUD方法。传统的MyBatis每张表都要写大量XML和mapper接口,而MyBatis-Plus直接用BaseMapper就能完成大部分单表操作。配合LambdaQueryWrapper,写条件查询的代码比手拼SQL舒服太多,并且减少了SQL注入的风险。

2.2 前后端分离架构与目录设计

整套系统采用前后端分离架构,后端只提供JSON接口,前端通过HTTP调用。这种架构的好处是前后端可以并行开发,部署时可以单独扩展,也方便后续对接移动端。

后端目录设计参考如下:

village-office ├── src/main/java/com/village/office │ ├── common // 通用结果封装、异常处理、常量 │ ├── config // 配置类,如安全配置、跨域配置 │ ├── controller // 接口层 │ ├── service // 业务层 │ ├── mapper // 数据访问层 │ ├── entity // 实体类 │ ├── dto // 接收参数的对象 │ ├── vo // 返回前端的数据对象 │ └── utils // 工具类 └── src/main/resources ├── mapper // MyBatis XML文件 ├── application.yml └── sql // 初始化脚本

前端目录参考设计如下:

village-web ├── src │ ├── api // 接口请求封装 │ ├── assets // 静态资源 │ ├── components // 通用组件 │ ├── layout // 布局组件 │ ├── router // 路由配置 │ ├── store // Pinia状态管理 │ ├── utils // 工具函数 │ └── views // 页面文件 ├── vite.config.js └── package.json

前后端分离之后,跨域问题需要特别注意。开发环境下用Vite的proxy代理解决,生产环境则通过Nginx反向代理统一转发,这样可以避免在前端代码里写死后端地址,也方便切换环境。

2.3 MySQL8.0 带来的变化

MySQL8.0 在很多细节上和5.7不一样。第一是默认字符集变成了utf8mb4,对中文、emoji、生僻字的支持更完整,建库的时候不用刻意去指定utf8mb4了。第二是增加了窗口函数、公用表表达式(CTE)等功能,写统计报表SQL方便不少。第三是驱动程序的变化,JDBC连接串里driver-class-name需要写成com.mysql.cj.jdbc.Driver,而不是5.7时代的com.mysql.jdbc.Driver。

还有一个在实际开发中容易被忽略的点:MySQL8.0 对时区参数更敏感,连接字符串里最好显式指定serverTimezone=Asia/Shanghai,否则会有8小时时差的问题。这些问题在部署文档里都要提前写清楚,避免接手的人白折腾。

3. 数据库设计

3.1 核心表结构与关系设计

数据库设计是整个系统最基础也最关键的部分,表结构设计好了,后面开发会很顺畅。我按模块来分析核心表的设计思路。

第一类是用户权限相关表:sys_user(用户表)、sys_role(角色表)、sys_menu(菜单表)、sys_user_role(用户角色关联表)、sys_role_menu(角色菜单关联表)。这类表的设计比较成熟,基本沿用经典的RBAC权限模型。sys_user表里需要包含用户名、密码、手机号、状态、所属部门或所属村ID这些字段。密码不能明文存储,要用BCrypt加密。

第二类是组织架构表:sys_dept(部门表)。这张表采用父子关系实现树形结构,字段包括parent_id、ancestors、sort_order等。注意ancestors字段很有用,它存了从根节点到当前节点的路径,比如1,2,5,这样查询某个节点下所有子节点时,直接like匹配ancestors就行,不用递归。

第三类是业务数据表,比如village_info(村信息表)、villager_info(村民档案表)、document_receipt(收文表)、document_issue(发文表)、approval_record(审批记录表)等。业务表之间通过村ID、用户ID等字段关联。审批记录表设计成通用表,用business_type和business_id来关联不同业务,这样新加一个审批流程不需要改表结构。

3.2 典型建表SQL示例

下面给出一张典型业务表的建表SQL,以村民档案表为例:

CREATE TABLE `villager_info` ( `id` bigint NOT NULL COMMENT '主键ID', `village_id` bigint NOT NULL COMMENT '所属村ID', `name` varchar(50) NOT NULL COMMENT '姓名', `gender` tinyint NOT NULL DEFAULT 0 COMMENT '性别 0未知 1男 2女', `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号', `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `householder` varchar(50) DEFAULT NULL COMMENT '户主姓名', `relation_to_householder` varchar(20) DEFAULT NULL COMMENT '与户主关系', `address` varchar(255) DEFAULT NULL COMMENT '家庭住址', `household_type` tinyint DEFAULT 0 COMMENT '户籍类型 0农业 1非农', `live_status` tinyint DEFAULT 1 COMMENT '居住状态 0迁出 1常住 2暂住', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `create_by` bigint DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` bigint DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` tinyint DEFAULT 0 COMMENT '逻辑删除 0正常 1删除', PRIMARY KEY (`id`), KEY `idx_village_id` (`village_id`), KEY `idx_id_card` (`id_card`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='村民档案表';

注意几点:主键我建议用雪花ID或者MyBatis-Plus的ASSIGN_ID,而不是数据库自增。这样在分布式场景下不会有冲突,而且数据合并迁移的时候方便。字段加了deleted逻辑删除标记,这样可以避免误删数据,查询的时候记得加deleted=0的条件。身份证号、手机号等高频查询字段要建索引,但考虑隐私安全,考虑到列表页的查询特点,联合索引经常比单字段索引更有效。

3.3 数据初始化与基础字典

系统平台创建好之后,要先初始化一批基础数据。这批数据包括:内置账号(超级管理员、演示账号)、菜单权限数据、数据字典、示例村和乡镇数据等。

数据字典是很多新手容易忽略的设计。比如性别、状态、审批类型这些字段,如果直接在代码里写死数字,后期维护会非常痛苦。正确做法是建一张sys_dict_type和sys_dict_data表,把数据类型和数据项都维护在字典表里。前端通过接口读取字典数据生成下拉选项,后端在业务逻辑里也能直接引用字典值。

初始化数据要写在SQL脚本里,放在src/main/resources/sql目录下。脚本分成两类:schema.sql放建表语句,data.sql放初始化数据。执行顺序不能乱,先建库建表,再执行初始化脚本。部署文档里要写清楚,防止有人只执行了建表没执行数据初始化,导致系统启动后没有菜单可用。

4. 后端实现与实操

4.1 项目初始化与分层开发

后端项目通过Spring Initializr创建,选择SpringBoot2.x版本、Java8或Java11,依赖选择Spring Web、MyBatis、MySQL Driver、Lombok,安全方面可以后面手动添加。

启动类加上@MapperScan注解扫描Mapper接口,这是MyBatis-Plus能正常工作的前提之一。

@SpringBootApplication @MapperScan("com.village.office.mapper") public class VillageOfficeApplication { public static void main(String[] args) { SpringApplication.run(VillageOfficeApplication.class, args); } }

分层开发的核心思路是:Controller不写业务逻辑,只做参数接收和结果返回;Service层处理业务规则;Mapper层负责数据访问。Controller返回统一的Result对象,格式为code、message、data三段式。这样前端可以统一拦截处理异常,不用每个接口都单独判断成功失败。

我在写这个项目的时候,Service层习惯先定义接口再写实现类。虽然代码量会多一些,但这样模块间的解耦更彻底,后续做单元测试mock也更方便。MyBatis-Plus提供的ServiceImpl和IService基类,可以直接继承,通用方法都帮你实现了,省不少事。

4.2 基于Spring Security的登录鉴权

登录鉴权是政务系统绕不开的模块。该项目可以用Spring Security框架来实现,整体流程是:用户提交用户名密码,后端校验通过后生成Token,前端保存Token并在每次请求时放进请求头,后端通过过滤器校验Token是否有效。

推荐使用JWT作为Token格式,优点是无状态、不需要在服务端存储会话。实现思路如下:

自定义一个UserDetailsServiceImpl实现UserDetailsService接口,通过用户名查库,把用户信息和权限列表组装成UserDetails。

自定义一个JwtAuthenticationFilter,继承OncePerRequestFilter,在请求进来时读取Header里的Authorization字段,解析Token,如果有效就把用户信息放进SecurityContext。

public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); // 解析token,拿到用户信息后设置SecurityContext } filterChain.doFilter(request, response); } }

密码加密使用BCryptPasswordEncoder,BCrypt加密即使数据库泄露,攻击者也无法直接还原明文密码。这里特别提醒一句:不要在数据库里存明文密码,这属于底线问题。

还要注意登录接口本身要放行,不需要鉴权,否则会陷入无法登录的死循环。Spring Security的配置里通过authorizeRequests配置哪些路径放行、哪些路径需要认证,静态资源、登录接口、验证码接口一般都要放行。

4.3 MyBatis-Plus 使用技巧

MyBatis-Plus在项目里的使用有几个口诀:实体类继承Model不做;Mapper继承BaseMapper;Service继承IService;ServiceImpl继承ServiceImpl。

实体类字段的映射要注意驼峰转下划线,MyBatis-Plus默认开启了map-underscore-to-camel-case,数据库里create_time能自动映射到实体类的createTime。如果字段名对不上,用@TableField注解指定。

写条件查询的时候,LambdaQueryWrapper是最常用的方式。它的好处是直接用Java方法引用指定字段,如果字段名拼写错了,编译期就会报错,不会拖到运行时才炸。

LambdaQueryWrapper<VillagerInfo> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(StringUtils.hasText(name), VillagerInfo::getName, name) .eq(villageId != null, VillagerInfo::getVillageId, villageId) .orderByDesc(VillagerInfo::getCreateTime); Page<VillagerInfo> page = villagerInfoMapper.selectPage(new Page<>(current, size), wrapper);

分页查询是后台管理系统最常用的功能。MyBatis-Plus的分页插件需要单独配置一个分页拦截器,否则selectPage不会自动拼接limit。这是一个经典的配置坑,漏了就发现分页无效。

逻辑删除功能,MyBatis-Plus默认支持。只需要在配置里指定逻辑删除字段名和值,然后实体类字段加@TableLogic注解。这样调用deleteById方法时,实际执行的是update语句把deleted置为1,而不是物理删除。查询时MP会自动追加deleted=0条件,特别方便。要注意的是,如果自己写XML手写SQL,这个逻辑删除的条件需要自己手动加上,MP不会帮你自动拼接。

4.4 文件上传与附件管理

政务系统里文件上传是高频需求,比如公文附件、村民证明材料、会议记录扫描件等。SpringBoot里处理文件上传相对简单,通过MultipartFile接口接收文件,配置好上传路径和大小限制就行。

在application.yml里配置:

spring: servlet: multipart: max-file-size: 20MB max-request-size: 40MB

上传后的文件建议统一存放在一个独立目录,不做散落存储。文件名重命名是一个关键点,不能使用原始文件名直接存,否则中文名、重名、违规字符都会出问题。我用UUID重新生成文件名,并把原始文件名保存在数据库的file_name字段,这样下载时可以把原始名字还回去,用户下载体验更好。

上传接口成功后,返回一个文件ID或者文件路径。业务表里只需要存文件ID,然后通过关联查询拿到下载地址。这样业务数据和文件存储解耦,后续如果迁移文件服务器,只需要改存储路径就行,不用改业务代码。

4.5 定时任务与统计报表

统计报表在政务系统里常用来做“办事数量”“审批时效”“人口信息”的统计。数据量大时,每次统计都实时查库会很慢,尤其是涉及跨表聚合的场景。更合理的做法是建立一张报表统计表,用定时任务在每天凌晨把前一天的统计数据提前算好,存储起来。前端查询的时候直接查报表表,速度快很多。

SpringBoot自带@Scheduled注解,适合做这类轻量级定时任务。比如每天凌晨1点执行一次统计,可以用cron表达式配置:

@Component public class ReportScheduledTask { @Scheduled(cron = "0 0 1 * * ?") public void generateDailyReport() { // 统计昨天的办事数量、新增村民数量等 } }

需要注意,定时任务如果执行时间较长,要考虑分布式环境下的重复执行问题。单机部署可以用@Scheduled加个开关;多机部署就需要引入分布式锁了。对于乡村办公系统这种体量,单机定时任务通常就够用了。

5. 前端实现

5.1 Vue3 项目搭建与初始化

前端使用Vite构建工具进行初始化,命令行执行创建项目之后,安装核心依赖:vue-router、pinia、axios、element-plus。Element Plus的样式按需引入,通过unplugin-auto-import和unplugin-vue-components插件自动导入组件和样式,这样不会打包出大量无用样式,构建体积能小很多。

vite.config.js里需要配置别名和代理。别名让import路径不用写一长串相对路径,代理解决开发环境跨域问题。

import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ], resolve: { alias: { '@': '/src' } }, server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

5.2 登录状态管理与请求拦截

登录成功后,后端返回的Token要存到本地,一般放在localStorage里。Pinia里的user仓库负责存储用户信息、Token和登录状态。页面刷新时,如果Token还在,就调接口获取用户信息,恢复登录状态。

axios请求拦截器是所有请求都要经过的地方,统一添加Token请求头。响应拦截器只需要统一处理code不为200的情况,比如Token过期时跳转登录页。

import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 15000 }) // 请求拦截器 service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = 'Bearer ' + token } return config }, error => Promise.reject(error)) // 响应拦截器 service.interceptors.response.use(response => { const res = response.data if (res.code !== 200) { // 业务错误统一提示 } return res }, error => { if (error.response && error.response.status === 401) { // Token过期,跳转登录页 } return Promise.reject(error) })

前端一次登录失效的处理逻辑处理好,整个系统的体验就有了一半保障。

5.3 动态路由与菜单权限

菜单权限的处理逻辑是:后端根据用户角色返回可访问的菜单列表,前端根据菜单生成路由。这是因为不同角色的菜单不同,直接在前端写死路由会导致权限不生效,用户通过手动输入URL就能访问未授权页面。

具体做法是:登录成功后调接口查菜单树,拿到菜单数据后用Router.addRoute动态添加路由。路由的meta字段里存菜单的标题、图标、组件路径等信息,组件可以用defineAsyncComponent方式动态加载。这样权限控制在前端路由层面就生效了,加上后端接口权限双重验证,安全性才有保障。

5.4 页面设计与数据展示

Vue3的后台页面常用Element Plus搭建,布局采用经典的侧边栏+顶栏结构。侧边栏渲染菜单树,顶栏放用户信息和退出按钮,主体区域用router-view承载页面内容。

表单页面要配合el-form的校验规则,必填字段、长度限制都在前端做一层校验,减少无效请求。列表页面一般包含查询条件区域和表格区域,表格用el-table,分页用el-pagination。我习惯把分页组件封装成自定义组件Pagination,统一处理页码和每页数量的变化逻辑,整个系统中所有列表页复用,代码会干净很多。

统计页面需要数据可视化展示,可以用echarts。Vue3中用echarts要注意在组件卸载时销毁实例,避免内存泄漏。图表数据和接口返回的数据结构不一致时,在前端做一层数据转换,别指望后端直接返回图表专用结构,这样前后端耦合太深。

6. 部署与运维

6.1 本地开发环境搭建

本地开发环境是入门这套项目的第一关。需要准备JDK8或JDK11、Maven3.6+、MySQL8.0、Node.js14+。顺序特别重要,先装MySQL并建好数据库,再启动后端,最后启动前端。

MySQL8.0安装完,创建数据库的时候字符集选择utf8mb4,执行初始化脚本。后端通过application.yml里的数据源配置连库,确认用户名密码正确。如果连接报错,优先检查MySQL服务有没有启动、密码对不对、连接串的时区参数有没有写。

前端启动方式是npm install安装依赖,然后npm run dev。开发模式下Vite默认跑在5173端口,通过代理转发到8080后端。如果接口跨域报错,多半是代理没生效,检查vite.config.js里的proxy配置和后端接口路径是否一致。

6.2 打包部署到服务器

项目要上线,后端需要打包成可执行的jar包。执行mvn clean package命令后,在target目录下生成jar文件,服务器上只需安装JDK并执行java -jar命令即可。

服务器部署建议用systemd配置成系统服务,这样能实现开机自启、异常自动重启,不用手工维护进程。

生产环境MySQL数据库密码一定不要用默认密码,至少要16位以上混合密码。后端配置文件的密码通过环境变量方式注入,不要把真实密码写进代码仓库。

前端打包执行npm run build,生成dist目录。部署方式推荐用Nginx做静态服务,同时配置反向代理把/api请求转发到后端8080端口,并做gzip压缩和静态页面缓存。Nginx的核心配置示例:

server { listen 80; server_name your-domain.com; location / { root /opt/village-web/dist; index index.html; 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; } }

try_files配置一定要写,否则vue-router的history模式刷新页面时会404。

6.3 常见问题与排查技巧

这套系统最常见的坑,我列个排查表:

现象可能原因排查方法
登录报用户名密码错误密码未加密或BCrypt版本不匹配检查注册时是否用了BCryptPasswordEncoder
接口调用报401Token过期或请求头未带Authorization用浏览器DevTools看Network请求头
中文显示乱码MySQL连接串没有指定utf8mb4检查url参数characterEncoding=utf8
上传报文件大小超限multipart配置或Nginx配置限制调整后端max-file-size和Nginx的client_max_body_size
分页数据异常分页插件未配置或Page对象未初始化确认MybatisPlusInterceptor已注册
前端配好后接口404代理路径和后端路径不一致核对axios baseURL与后端controller的RequestMapping

有一个很隐蔽的坑是后端返回时间字段差8小时。排查下来基本是serverTimezone设置问题,加上Asia/Shanghai就能解决。还有Elastic beanstalk那种容器化部署,要特别注意环境变量传递。

7. 经验与扩展建议

写了这么多,我把这类项目最容易忽视的几个点收个尾。

第一,接口返回参数要规范。后端返回的Result对象,code统一用200代表成功,401代表未登录,403代表无权限,500代表服务器异常。前端只需要根据code做全局处理,这样代码写起来能少一大半判断逻辑。

第二,日志要打全。重要接口的入参、出参都打印一下,用@Slf4j注解加log.info即可。接口报错时没有日志,排查问题全靠猜,效率极低。

第三,数据库设计要预留扩展字段。业务表多设计几个expand字段,比如用JSON字符串存储自定义属性。乡村政务业务变化快,今天加个字段明天加个字段,每次改表结构都要发版,有扩展字段能顶一阵。

第四,权限不只是前端隐藏,后端接口必须做同样的校验。用Spring Security的@PreAuthorize注解,在Controller方法上标注权限标识。前端隐藏菜单只是用户体验优化,真正的安全边界在后端。

这套技术栈做完之后,尝试加一个简单的消息推送模块。用Vue3监听后端WebSocket,把新的审批待办实时推送到用户界面。这一步做完,系统的完整度会明显提升,属于性价比很高的扩展方向。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询