ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot村务管理系统开发实践:从需求分析到部署避坑

Spring Boot村务管理系统开发实践:从需求分析到部署避坑 二月底接了个活儿老同学在乡镇工作说申家沟村准备上村务管理系统问我能不能用Spring Boot做一版。说实话我一开始以为就是个常规的增删改查项目等把村委会真实工作流程摸了一遍才发现这个系统的难点不在技术框架而是在于怎么把村干部手里那堆纸质台账、Excel表、微信群通知整理成一套真正有人愿意用的数据系统。这篇文章就把申家沟村务管理系统的设计实现过程完整拆一遍从哪里入手做需求、为什么选Spring Boot这套技术栈、数据库怎么设计、核心模块怎么写、部署到村里之后踩过哪些坑。给准备做村务、政务、基层管理类Spring Boot项目的朋友当个参考模板。1. 申家沟村的管理困境先从四件头疼事说起1.1 纸质台账和微信群撑不起的日常申家沟村户籍人口接近两千分了六个村民小组。在系统上线之前村里的人口底数靠什么管几本纸质登记表加上村会计电脑里一个用了七八年的Excel。每年镇里要常住人口、流出人口数据村干部就翻箱倒柜找台账再挨个打电话核实。嫁入的媳妇户口没迁、外出打工的人联系不上、老人去世了信息没更新这类问题每年都要折腾一遍一个人口统计少说耗上半个月。村务公开的情况也差不多。村里财务收支、惠农补贴名单、项目建设进度制度要求定期公示实际操作就是在村务公开栏贴一张A4纸风吹日晒一个月就烂了。后来大家用微信群拍照发公告照片在聊天记录里一刷就找不到了真到村民追问某笔钱去向的时候村干部得翻半天聊天记录。最费劲的是事项审批。村民要开个证明、办个宅基地审批、申请临时救助流程全靠人跑腿。先找村民小组长签字再找村主任有时候还要等村干部开会。审批走到哪一步村民完全不知道只能一趟一趟跑村委会问。还有一个容易被忽略的问题——通知触达。防汛提醒、医保缴费、疫苗通知这类消息群里接龙一片收到实际上很多老人根本没看到。1.2 系统的边界我先决定不做什么搞清楚痛点之后我做的第一件事不是画界面而是定边界。村里有人建议我做个大而全的数字乡村平台把财务做账、农业生产、摄像头监控全包进去。我没接这个话定下的原则很简单这个系统只做四件事——管好人口底数、做好公开公示、打通申请审批、保证通知触达。财务做账交给现有的专业财务软件农业生产和物联网设备跟这个系统没关系跟上级部门重复的功能一律不做数据能导出 Excel 交给镇里就行。这个边界非常重要。基层系统的失败一半不是功能太少而是功能太多、流程太重。申家沟村的情况是电脑老旧、网络一般、村干部平均年龄偏大一个太复杂的系统根本没人用。先把最高频的四个痛点解决掉系统才有机会活下去。后面的事实也证明这个克制救了项目。2. 为什么选Spring Boot一套能维护五年以上的技术栈2.1 Spring Boot版本的取舍2.7.18 JDK8技术选型这块我几乎没有纠结。团队就两个人我负责后端一个朋友负责前端。Spring Boot在这个场景下就是最优解——生态成熟、社区资料多、遇到问题一搜就有答案而且整个Spring生态里的Spring Security、Spring Validation、Spring Data都现成不用自己造轮子。版本上我选的是Spring Boot 2.7.18 JDK8没用Spring Boot 3.x。原因有三个第一3.x 强制要求 JDK17对2核4G的轻量服务器来说JDK17的内存占用和GC行为不如JDK8省心第二3.x发布后一些基础组件比如某些国产数据库驱动、老版本的中间件客户端适配有坑这个项目没有必须要用3.x新特性的场景第三是求稳基层政务类项目长期维护稳定比追新重要。你可能会问JDK8是不是太老了我的判断是对一个要跑五年以上的村务系统2.7.18加JDK8是成本最低、最不容易出幺蛾子的组合。2.2 周边组件的搭配与理由整体技术栈是这样的组件选型为什么这么选后端框架Spring Boot 2.7.18生态成熟稳定优先ORMMyBatis-Plus 3.5.xSQL可控、LambdaQueryWrapper写起来快、分页插件现成数据库MySQL 8.0免费、生态好、村级数据量毫无压力缓存Redis验证码、登录Token缓存、热点数据认证授权Spring Security JWT前后端分离的标配接口以后还能给小程序用前端Vue 3 Element Plus Vite组件完善、后台管理界面开发效率高部署Docker docker-compose Nginx环境隔离、迁移方便、前端静态文件由Nginx托管这里有一个取舍可以说透。我见过很多同类系统用Thymeleaf做全栈不分离部署就一个jar包省事。但我选了前后端分离原因是申家沟村后续大概率要扩展微信小程序或者App接口直接复用后端就行。代价是前端多了一套Node构建环境对村里的技术员来说学习成本高一点所以我写了一份很详细的前端部署文档把npm构建步骤固定好。详细原因在第六章再说。3. 需求分析村干部的表格堆里藏着真正的功能清单3.1 四类角色权限完全不一样需求分析做了两周走访了村委会和几个村民小组。第一件事是把角色理清楚。系统里一共有四类角色权限边界完全不同系统管理员乡镇或村里的信息化负责人负责用户管理、系统配置、数据备份。村委会干部日常使用主群体。其中会计侧重财务公开模块村主任能看全村所有数据普通村干部只能操作自己分管的那部分。村民小组长相当于网格员负责本组村民信息维护、代办村民申请事项、完成村里指派的核查任务。普通村民账号由村干部批量开通默认只能看公开信息和与自己相关的补贴、审批进度。普通村民的账号是个敏感点。一开始有人建议不给村民开账号说他们不会用但我坚持要开因为村务公开的公开必须有对象。村民端做得很克制首页就三件事看公开、查申请进度、留言反馈没有多余功能。3.2 把痛点翻译成功能清单需求调研结束后功能清单基本就浮出水面了。这里直接给当时整理的表格功能模块还原哪个痛点优先级村民信息台账人口底数不清、人口统计耗时高户信息管理以家庭为单位管理匹配宅基地和补贴场景高通知公告微信群触达不精准、重要通知无法确认高村务公开公开栏纸质公示不可查、无留痕高事项审批村民跑腿多、审批进度不透明中资产管理集体资产底数不清中意见反馈村民意见收集渠道单一低统计报表给镇里报表重复填写高注意我把统计报表列为高优先级。事实证明这个判断非常正确——村干部对一个系统最直观的体感不是界面多漂亮而是月底填报表能不能省半天时间。后面我会详细说这个模块的实现因为它是系统上线后被使用最多的功能之一。3.3 数据权限看到什么比能不能登录更重要权限设计上做了两个维度。第一个维度是菜单权限控制能不能看到这个功能入口第二个维度是数据权限控制能看谁的数据。比如村民小组长登录后只能看到本组村民的信息会计账号可以看到财务公开管理菜单但不能看全量村民台账普通村民登录后连管理后台的入口都不出现直接进入村民端页面。数据权限这块用MyBatis-Plus的数据权限插件实现。做法是在核心查询上通过自定义注解注入数据范围条件比如household.group_id 当前用户所属组避免每个Mapper手动拼SQL。这个设计后来被证明很值系统上线一个月后镇上要求各村数据互相隔离我只需要改一个注解参数不用动业务代码。4. 数据库设计17张表与四个不能省的设计细节4.1 核心业务表的划分整个系统一共17张表可以分成几类权限类5张用户、角色、菜单、用户角色关联、角色菜单关联、人口台账类3张户表、村民表、家庭成员关联、公开公示类2张通知公告表、村务公开信息表、审批类2张审批申请表、审批流转记录表、其他4张资产、反馈、操作日志、数据字典。另外还有一张用户扩展表存村民的额外信息标识。人口台账是核心中的核心设计时采用了户人两层结构。户表存户主、户籍地址、现居地址、联系电话、家庭类别一般农户、低保户、独居老人户等村民表存个人信息和与户的关系比如户主、配偶、子女。为什么要拆两层因为宅基地审批、低保申请、人口统计全都是以户为单位发起或核算的而具体到补贴发放和疫苗接种记录又精确到个人。拆开之后两种维度的查询都不别扭。4.2 几个容易忽视的字段设计细节第一主键没有用数据库自增用的MyBatis-Plus的ASSIGN_ID雪花算法。原因是乡镇以后很可能做数据交换或者多村合并自增ID一旦撞上就是大麻烦。雪花ID还能从ID里读出大概的生成时间排查数据问题时多一个维度。第二所有表都加了deleted字段做逻辑删除。村务数据按制度要求要留痕误删了也要能找回来所以物理DELETE一律禁止。第三没有用timestamp统一用datetime存储时间。这里有个容易踩的坑timestamp会受数据库时区影响服务器时区设置错了会导致时间偏移8小时而村务公开涉及公示时间差8小时就可能出合规问题。用datetime存字面时间插入时由Java统一用系统当前时间写入。第四也是最容易忽略的一点身份证号和手机号不是明文存储而是用AES加密后存进varchar(64)字段。村民身份证属于敏感个人信息明文存库一旦泄露就是安全事故。查询展示时统一脱敏身份证显示前6位和后4位手机号显示前3位和后4位。这么做会带来一点麻烦——没法直接用身份证号模糊查询只能通过脱敏逻辑做精确匹配或加解密工具类。我为此写了一个统一的CryptoUtil限制所有入口都必须走这个工具。安全这块没有商量余地。再补一个字段村民表里预留了region_code区域编码字段。这个字段当时没用上但等镇上要对接上级平台做数据同步时你就知道它有多省事了。基层系统一定要有这种前瞻字段不然以后对接就是噩梦。5. 三个核心模块的实现登录认证、事项审批、村务公开5.1 登录认证Spring Security与JWT的正确打开方式登录这块用的是Spring Security JWT无状态会话前端每次请求在Authorization头里带Token。密码加密用BCrypt登录成功后签发JWT然后通过一个自定义过滤器解析Token、把用户信息放进SecurityContext。核心配置如下Configuration EnableWebSecurity public class SecurityConfig { Resource private JwtAuthenticationFilter jwtFilter; Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests() .requestMatchers(/api/auth/login, /api/public/**).permitAll() .requestMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated(); http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }JWT的过期时间设的是8小时村民端和村干部端都一样。8小时这个值不是随手定的太短村干部一上午要重新登录好几次烦太长存在Token泄露的风险窗口。还有一个细节是登录接口做了验证码用的Redis存储验证码5分钟有效防止暴力破解。系统上线后我加了一个小功能连续输错5次密码账号锁定15分钟。这个功能在基层很管用因为大家习惯用123456这类弱密码没这个限制心里不踏实。5.2 事项审批一个小型状态机胜过重量级工作流审批模块一开始有人提议接个工作流引擎我直接否了。村委会的审批流程是固定的村民提交申请 - 小组长初审 - 村委复审 - 办结归档会签、分支、多级流转这些复杂流程根本不存在。用Activiti、Flowable纯属杀鸡用牛刀维护还麻烦。最后设计了一个简单的状态机加一张流转记录表状态流转是0草稿 - 1待初审 - 2待复审 - 3已通过 - 4已驳回另外支持5已撤回。村民提交后可以撤回小组长初审驳回的直接退回到草稿村委复审驳回的就结束流程。核心表结构分两张审批申请表存业务主数据审批流转记录表存每一步的操作痕迹。这个设计对基层特别重要——审计的时候每一步谁操作、什么时候操作的、填了什么意见全都能查。审批状态流转的服务层代码大概是这样的Transactional public void approve(Long approvalId, String operatorId, String action, String comment) { Approval approval approvalMapper.selectById(approvalId); if (!checkTransition(approval.getStatus(), action)) { throw new BizException(当前状态不允许执行该操作); } // 更新审批主表状态 approval.setStatus(nextStatus(approval.getStatus(), action)); approval.setCurrentNode(nextNode(approval.getCurrentNode(), action)); approvalMapper.updateById(approval); // 写入流转记录留痕 ApprovalRecord record new ApprovalRecord(); record.setApprovalId(approvalId); record.setOperatorId(operatorId); record.setAction(action); record.setComment(comment); approvalRecordMapper.insert(record); }状态机的关键就是一个checkTransition方法它维护了一张当前状态动作 - 下一步状态的映射表。为什么不用多个if else因为状态一旦多起来if else会把自己绕晕映射表一看就清楚。业务流程上村民提交申请后系统会推送通知给所属小组长小组长手机上就能处理不用专门跑村委会。审批过程中村民在村民端可以实时看到进度到哪一步了、卡在谁手里。就这一个简单的进度透明化功能上线后村委会跑腿询问量直接少了一大半。5.3 村务公开内容留痕与隐私脱敏村务公开模块看起来就是个富文本附件发布但有两个设计点值得展开。第一是公开内容一旦发布就不允许修改。为什么财务收支和补贴名单这类信息如果发布之后还能静默修改一旦有村民质疑前后不一致村委会说不清楚。所以我的实现是发布之后在库里只允许下架修改状态不允许修改正文真要更正只能重新发布一条并在标题里注明更正。这是基层审查最容易查出的问题一开始就堵上这个口子。第二是隐私脱敏在展示层做。村民端列表页和详情页展示补贴名单时身份证和手机号必须打码只有具备管理权限的账号在后台才能看到完整数据。另外村务公开内容会设置一个expireTime比如一则公示默认挂30天到期后定时任务自动下架避免过期信息长期挂在首页造成误解。附件的处理上PDF和图片上传到服务器上的一个upload目录Nginx直接托管数据库里只存相对路径。数据量不用担心村一级的高频公示一个月也就十几条几张表够用很多年。顺便提一句村民端的搜索框一定要做没有搜索的公开栏就是摆设。按类别、按时间筛选加上评论留言的跳转村民才会真的用起来。5.4 统计报表村干部最离不开的模块统计报表这个模块是上线后被使用频率最高的没有之一。村里每个月要向镇上报表口径包括户籍人口、常住人口、流出人口、低保户数、独居老人数、宅基地审批数等等。以前村干部靠Excel人工筛选数据对不上就一个一个核对。现在系统里做了几个固定报表页面数据全部实时聚合。实现上复杂统计没有用MyBatis-Plus的QueryWrapper硬拼而是直接在Mapper XML里写聚合SQL。比如计算年龄结构SELECT CASE WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) 18 THEN 0-17 WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) BETWEEN 18 AND 60 THEN 18-60 ELSE 60 END AS age_group, COUNT(*) AS cnt FROM villager WHERE deleted 0 GROUP BY age_group报表导出用Apache POI一键导出Excel格式和镇里发的模板保持一致。你要知道在基层系统里一键生成报表比任何大屏展示都更能打动使用者。村主任最常干的事情就是打开系统点两下导出Excel发给镇里。以前这要花半天现在五分钟搞定。6. 部署和上线在村里真实跑起来才知道的坑6.1 部署环境与资源控制服务器用的2核4G轻量云服务器数据库、缓存、后端、前端全在这台机器上。部署用Docker编排一套docker-compose文件全部搞定services: mysql: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD} TZ: Asia/Shanghai volumes: - ./mysql-data:/var/lib/mysql command: --innodb_buffer_pool_size512M --max_connections200 redis: image: redis:6 restart: always command: redis-server --requirepass ${REDIS_PASSWORD} backend: build: ./backend restart: always depends_on: - mysql - redis environment: SPRING_PROFILES_ACTIVE: prod TZ: Asia/Shanghai nginx: image: nginx:1.24 restart: always ports: - 80:80 - 443:443 volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./dist:/usr/share/nginx/html - ./upload:/usr/share/nginx/upload depends_on: - backend4G内存其实有点紧张。MySQL、Redis、后端jar包都挤在这一台机器上不做限制很容易OOM。我的做法是后端JVM参数强制设-Xmx512m、-Xms256mMySQL的innodb_buffer_pool_size限制在512M再加一个定时任务定时清理日志。数据库和上传文件目录一定要挂载到宿主机项目里我吃过大亏——Docker重建容器的时候没挂载数据卷数据差点全丢从那次之后凡是有状态的数据一律挂载。6.2 老旧电脑、弱网络和手滑重复提交村里真实环境给开发上带来的教训比任何教科书都多。第一是浏览器兼容。村委会还有几台老电脑系统装的是老版本ChromeElement Plus的部分组件在老浏览器上样式会错乱。排查了半天最后方案是在村委会的几台电脑上统一装了Chromium内核的浏览器锁定不升级前端尽量不用太新的CSS特性。这事情看着小不处理的话村干部第一印象就是这系统有问题。第二是网络不稳定。村民提交申请的时候网络卡顿手一抖点了两次提交按钮结果生成了两条重复申请。前端按钮要做loading防抖后端service层还要有幂等校验同一个村民、同一类申请、同一天只能有一条待处理记录。一句话的校验就能避免数据垃圾。第三是打印适配。村委会开证明、盖章的时候要打印这是刚需。一开始前端没做打印样式window.print打出来页面错乱。后来专门做了一个打印模板用media print隐藏掉无关元素纸型设成A4村干部打印一键搞定。这个功能别看小村干部对系统的认可度就是靠这些细节攒出来的。第四是Excel数据迁移。老台账Excel脏数据多得一塌糊涂出生日期格式有的是1990.1.1、有的是1990年1月、还有的是文本格式身份证15位和18位混用。我写了一个Python清洗脚本先处理一遍清洗结果逐条展示给村会计确认确认后再导入。数据迁移这个阶段急不得台账是村里的家底导入错了后面全乱套。6.3 数据迁移与推广期的过渡策略推广上最重要的一个决策是并行期制度系统上线后跟纸质台账并行运行三个月每个月底核对一次数据确认系统数据准确无误后纸质流程才正式退出。这在基层数字化项目里是最稳妥的做法不要指望一步切换人都是有习惯的要给适应期。账号安全这块也吃过一个教训。系统上线初期几个村干部嫌麻烦共用一个账号登录结果后来村委会内部要查某项操作是谁做的根本查不出来。我在系统里加了一条规则同一账号不允许同时多地登录操作日志强制记录精确到秒。另外村干部有离职或调整的时候账号要及时停用这事情我写成了一条运维约定。基层的数据安全靠技术只是一半另一半靠规则。给村里培训的时候我还搞了个代办员制度村里选了两个年纪轻、会用手机的年轻人当代办员年纪大的村民要办业务不用自己操作手机找代办员就行。系统上线前两周代办员几乎是手把手教每个村干部怎么录数据、怎么批申请。我写的操作手册只有一页A4纸截图加箭头不整那些花里胡哨的大部头。最后分享一点我个人的体会。做申家沟村务管理系统这个过程让我重新理解了技术落地这件事。Spring Boot、MyBatis-Plus这些都是成熟得不能再成熟的技术真正的难点全在业务理解和使用习惯上村干部要的是什么是少填一次表村民要的是什么是办事少跑一趟。技术方案再漂亮使用者不买账就是零。另外还有个小建议做这类基层系统一定要把操作留痕和日志审计放在跟业务功能同等重要的位置。越是基层系统越要经得起检查。申家沟这个项目上线三个月被调用最多的接口点开一看不是登录也不是村民查询而是报表导出。这让我特别感慨——基层数字化真正打动人心的从来不是炫酷的技术而是让人省力的细节。项目做完之后这套系统的数据库设计和审批状态机还被隔壁两个村借去参考了这大概就是它最大的价值了。
返回列表