

接手一个县域数字乡村项目的时候,我拿到的需求文档里有一句话让我盯了很久:全县 200 多个行政村,每个村要有自己的首页、自己的管理员、自己的村民库,但数据要能汇总到县里看。这句话决定了后面所有的技术选型。
这篇把万村乐数字乡村 Java 版的架构思路拆开讲,重点在三块:服务边界怎么切、租户模型怎么设计、数据中台怎么把散落的数据收拢。
一、技术栈清单先摆出来
架构讨论容易变成玄学,所以我习惯先把版本号列清楚。万村乐 Java 独立版的技术栈是这样的:
后端部分:Jdk 1.8、MySQL 8.0.28、SpringBoot 2.5.4、MybatisPlus 3.5.1、SaToken 1.30.0、Hutool 5.7.22、Druid 1.2.8、Knife4j 2.0.9、EasyTrans 2.0.3、Smcrypto 0.3.2。
前端部分:vue 3.2.31、ant-design-vue 3.2.10、vuex 4.0.2、vite 2.8.6、less 4.1.2、axios 0.24.0、vue-router 4.0.12、tailwindcss 3.0.23、sm-crypto 0.3.11。
小程序端用 UNI-APP 开发后打包,APP 端是安卓与 iOS 的混编模式而非原生。PHP 版走的是另一条路线,ThinkPHP 加 Bootstrap 做后台,前端同样是 UNI-APP,数据库仍然是 MySQL。
这套组合里有两个选择值得说一下。SaToken 而不是 Spring Security,原因是县域项目的权限模型是区划树形结构,SaToken 的会话与权限接口更薄,改造成本低。sm-crypto 与 Smcrypto 成对出现,前后端都做国密 SM2 与 SM4,这是政务类项目验收环节绕不开的一项。
二、服务边界按业务域切,不按技术层切
前后端分离之后,后端还要不要继续拆,拆到什么粒度,这是第一个要拍板的事。
万村乐把功能划成了几个相对独立的域:基础运营(村庄切换、天气、地图标记、短信、支付、物流、文件存储、日志)、村民互动(论坛、发帖、审核、事件反馈、选举投票、村内活动)、政务服务(三务公开、通知公告、民主议事、智慧党建、网格信息、办事大厅)、民生服务(农技课堂、土地流转、乡村旅游、通讯录)、电商产业(一村一品商城、积分商城)、可视化大数据。
按域切的好处在实际迭代中体现得很明显。商城模块要接微信支付、支付宝提现、快递 100 物流查询,这些外部依赖的故障率明显高于其他模块;党建考试模块要跑题库和排行榜,属于计算密集。把这两块隔开之后,商城调外部接口超时不会把党建考试的线程池拖垮。
后台管理侧分为总后台与村后台两级,每个级别独立账号管理。这个设计直接对应了服务里的权限上下文,总后台的请求携带的是区划节点,村后台携带的是具体村庄 ID。

三、多租户:村庄就是租户
县域平台的租户模型有个特点,租户不是独立注册的商家,而是行政区划里的一个节点。后台可以按照区域自由添加全国所有地区的村庄,并直接给村庄分配账号密码。这就意味着租户的创建是自上而下的,租户之间还存在父子关系。
我们没有走一村一库的物理隔离,原因很直接:200 多个村建 200 多个库,MySQL 的连接数和运维成本都受不了。走的是共享库加行级隔离,每张业务表带一个 village_id 字段,配合区划编码做树形查询。
用 MybatisPlus 3.5.1 的租户插件可以把这件事做成无感知的。核心配置大致是这个样子:
需要提醒的是忽略表清单一定要维护好。我们踩过一次坑,字典表忘记加进忽略清单,结果村后台读不到全局字典,前端下拉框全空,排查了小半天才定位到是租户插件在 SQL 后面拼了 village_id 条件。
上级查下级的场景要单独处理。县级管理员看全县数据,不能只按单个 village_id 过滤,得按区划前缀展开。做法是在会话里额外存一个可见区划集合,租户处理器判断当前身份是村级还是区划级,村级走等值过滤,区划级走 IN 集合过滤。
四、权限模型:四级区划加角色
通知公告模块支持四级权限,各级维护本级的公告信息。发布公告时可以选择本级以下的发布范围,并详细选择范围内的标签人员,让推送更精准。这套逻辑落到代码里就是区划路径加角色的双重判定。
村管理员可以设置多个权限角色来控制不同账号查看不同内容,这是租户内部的二级授权。可视化大数据平台的权限划分更细,省、市、县、镇、村每个区域都可以自由创建。
一个容易被忽略的点是阅读回执。通知公告要记录推送人数、推送户数、已读人数、已读户数、已读人数占比和已读户数占比。户是比人更粗的粒度,需要通过家庭管理模块的户主关系聚合。这张统计表的写入量在通知高峰期不小,我们把它从主库拆成了异步写入,用消息队列削峰。
五、数据中台:先解决数据源接进来的问题
可视化大数据平台 V1.0 是这套架构里的数据出口,技术栈是 Vue 3.0+、Vite 3.0+、ECharts 5.0+、Mars3D 3.6+、Three.js 0.159.0。
它的数据源管理支持 MySQL、达梦、MongoDB、PostgreSQL、Oracle 等主流及国产数据库,也支持 API 数据源,可以通过 SQL 语句直接拉取数据。大屏内所有数据支持动态、静态、SQL 语句、API 接口四种拉取方式,需要自定义数据时开启静态数据手动输入即可。
这里的工程价值在于解耦。乡村治理的数据来源天然分散,村民信息在业务库,监控视频在物联网平台,农产品行情在第三方接口。中台不强求把所有数据物理搬迁到一处,而是统一数据源注册与查询编排。一个大屏组件的数据配置大致是这样:
刷新间隔按 300 秒配置是我们试出来的折中值。乡村治理类数据变动频率低,村民标签统计一天变几次,5 分钟一刷已经足够,再密就是白白给数据库加压。真正需要秒级的只有监控告警,那条链路走的是推送而不是轮询。
多屏创建能力支持一个主屏加多个子屏并实现跳转,配合区域权限,县里看汇总屏,镇里点进去看本镇子屏,数据口径一致但可见范围不同。

六、多端接入与几条实践结论
前端有微信小程序、公众号 H5、PC 端、安卓 APP、iOS APP 五个入口。这些端共用同一套 REST 接口,用 Knife4j 2.0.9 生成文档给多端联调。文件存储对接本地、阿里云、腾讯云三种方式,切换靠配置而不是改代码,这在私有化部署时省了很多事,有的县要求文件必须落在本地机房,有的县已经买了云存储。
系统采用在线升级,用户点击更新即可自动升级。这个能力对分散部署的县域项目意义很大,200 多个村的实例如果靠人工逐个升级,运维会崩溃。
几条落地后的经验:
租户字段要在建表阶段就规划好,后补字段意味着历史数据回填加索引重建,风险远高于一开始就加上。
日志统计模块记录访问日志和操作日志,责任追究到人明确操作记录,这在政务验收时是硬要求,不要等到验收前才加。
村庄切换功能会记录已访问村庄,这个看似产品需求的点,实际上帮我们解决了缓存预热问题,用户高频访问的村庄数据可以提前加载。
数据源接入达梦这类国产数据库时,SQL 方言差异要提前测,分页语法和函数名跟 MySQL 8.0.28 并不完全一致。
架构没有标准答案,县域平台的约束条件跟互联网产品差别很大:并发不高但租户极多,数据量不大但口径要求严,迭代不快但合规检查密集。按这些约束去调整,比照搬大厂方案更实在。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。