

接手万村乐数字乡村系统的二次开发时,我拿到的需求清单是这样的:A 村要一张"人居环境整治打分表",五个评分项加一张现场照片;B 村要"农机具借用登记",带归还日期和押金;C 村的驻村干部希望"低保申请"走三级审批,村里初审、镇里复核、县里备案。三个村,三张表,三条流程。如果按传统写法,就是三套实体类、三套 Mapper、三套增删改查接口,外加三套前端页面。
系统本身已经内置了大量固定功能,村庄切换、村民管理、论坛管理、事件反馈、办事大厅、土地流转、种养管理、家庭积分这些模块都是编译进代码的。问题在于基层业务的长尾需求根本枚举不完。我们统计了半年内的定制工单,超过六成属于"换几个字段的表单 + 换几个节点的审批",真正需要写业务逻辑的不到两成。这种结构,硬编码就是自杀。
后端底座决定了元数据方案能走多远
万村乐 Java 版的后端技术栈是 JDK 1.8、MySQL 8.0.28、SpringBoot 2.5.4、MybatisPlus 3.5.1、Sa-Token 1.30.0、Hutool 5.7.22、Druid 1.2.8、Knife4j 2.0.9、EasyTrans 2.0.3。前端是 Vue 3.2.31 配 Ant-Design-Vue 3.2.10、Vite 2.8.6、Vuex 4.0.2、Tailwindcss 3.0.23,小程序侧走 UNI-APP 打包。这套组合对元数据驱动很友好,MybatisPlus 的QueryWrapper可以直接拼动态条件,EasyTrans 天然适合把字典码翻译成中文,Sa-Token 的注解鉴权可以挂在动态接口上。
我们没有引入 Activiti 或者 Flowable。原因很实在:村级审批的节点数量普遍在两到四个,分支条件基本是"通过/驳回/转办"这三种,引入完整 BPMN 引擎会带来二十多张表和一堆运行时依赖,对只有 2 核 4G 的私有化部署机器不友好。系统本身支持私有云独立部署,客户机器配置参差不齐,能省的中间件就省。
表单元数据我们拆成三层。第一层是表单定义,记录表单编码、所属村庄区划、版本号、状态。第二层是字段定义,记录字段编码、控件类型、校验规则、字典来源、是否参与列表展示。第三层是数据实例,用一张宽表加 JSON 列存放村民实际填写的内容。
form_ver这个字段是踩坑之后加的。早期版本没有它,某个村在三月份改了打分表的评分项,把"垃圾清运"从五分制改成十分制,结果二月份提交的历史数据在列表页全部显示错乱。加上版本号之后,渲染详情时按提交时的版本取字段定义,历史数据就稳住了。MySQL 8.0.28 原生支持 JSON 列和函数索引,对payload里的高频查询字段可以单独建虚拟列索引,避免全表扫描。
流程用状态机描述,比 BPMN 轻得多
我们把流程定义成一张有向图,节点是状态,边是动作。整条流程用一段 JSON 描述,存在biz_flow_def表里,村管理员在后台配置界面上拖节点生成这段 JSON。
flowCache用 Hutool 的TimedCache做本地缓存,过期时间设成 300 秒。村级流程定义的变更频率很低,一天改不了几次,本地缓存比每次查库划算,也避免了给缓存中间件增加依赖。变更之后主动remove对应 key,保证配置改完立刻生效。
审批日志表biz_flow_log是必须落的。系统原本就有访问日志和操作日志两条记录线,责任可以追究到人。低保申请、宅基地审批这类业务一旦产生纠纷,需要拿出完整的操作轨迹,谁在什么时间点了什么按钮、填了什么意见,都得有据可查。这张表我们按月分表,单村单月的数据量在几百条量级,压力不大,但全国范围铺开之后总量可观。

前端渲染器的三个坑
前端用 Vue 3.2.31 的component :is做动态渲染,字段定义里的widget字段映射到具体的 Ant-Design-Vue 组件。看起来简单,实际踩了三个坑。
第一个坑是校验时机。Ant-Design-Vue 3.2.10 的a-form依赖rules在初始化时确定,动态增删字段之后校验规则不刷新。解决办法是给整个表单加一个由字段定义计算出来的key,字段变了就强制重建组件树。
第二个坑是移动端适配。村民侧走的是 UNI-APP 打包的小程序和 App,PC 端后台用的是 Ant-Design-Vue,两套 UI 库的组件名对不上。我们的做法是字段定义里只存抽象控件类型,比如TEXT、NUMBER、DATE、IMAGE、SELECT、REGION,两端各自维护一份映射表。这样一份元数据能同时驱动两个端。
第三个坑是图片上传。系统的文件存储支持本地、阿里云、腾讯云三种方式,具体用哪种由部署时的配置决定。前端不能写死上传地址,得从后端配置接口拿。我们把上传凭证的获取封装进渲染器,字段类型是IMAGE时自动走这条链路。
数据出口接到可视化大屏
表单收上来的数据要能看。万村乐的可视化大数据平台本身就是低代码的,技术栈是 Vue 3.0+、Vite 3.0+、Pinia 2.0+、TypeScript 4.0+、ECharts 5.0+、Mars3D 3.6+、Three.js 0.159.0,数据源管理支持 MySQL、达梦、MongoDB、PostgreSQL、Oracle、SQLite、MsSQL,也支持 API 数据源,通过 SQL 语句直接拉取。
我们给每张业务表单自动生成一个视图,把 JSON 里的字段展开成列,大屏那边配数据源时直接选视图,不用写复杂的 JSON 提取函数。这一步是用 MybatisPlus 的自定义 SQL 在表单发布时执行的。
大屏支持省、市、县、镇、村多级区域权限划分,也支持创建一个主屏加多个子屏并实现跳转。镇干部登录看到的是全镇汇总,村干部登录只能看本村明细,权限过滤是在数据源的 SQL 里拼region_code前缀匹配实现的,跟表单侧共用同一套区划编码。

上线之后的实际效果
改造完成之后,新增一张表单从提测到上线的周期从三天压到二十分钟,村文书自己就能在后台配好。定制工单里"改几个字段"这类需求彻底不占用开发排期了。真正需要写代码的场景剩下两类:一类是要对接外部接口的,比如短信验证走阿里云、支付走微信、物流查询走快递 100;另一类是有复杂计算的,比如家庭积分的加减分规则,那部分我们仍然留在 Java 里用策略模式实现。
有几个经验值得说。JSON 列虽然灵活,但不要指望它替代关系模型,凡是需要 JOIN 或者聚合的字段,该抽成实体列还是要抽。流程状态机的动作名要设计成幂等的,村里网络不稳定,村民点两次提交是常态,我们在transit方法外面套了一层基于dataId + action的分布式去重,窗口期 3 秒。表单版本号一定要在设计阶段就加上,等到数据积累了几万条再补,迁移成本会翻好几倍。
低代码不是银弹,它解决的是"结构相同、内容不同"的重复劳动。基层治理场景里这类需求占比高,收益就明显。如果业务逻辑本身很复杂,硬套低代码反而会把简单问题搞复杂。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。