做政务类小程序项目这几年,最常被问到的一个问题是:乡镇和村一级的需求,跟市区政务大厅能差多少?我的答案是:业务复杂度差得不多,但使用场景差得非常多。村民办事往往面临“不知道找哪个窗口、材料容易带不齐、去了一趟又一趟”的困扰,所以这套基于微信小程序的乡村政务服务系统的核心,不是简单把服务窗口搬上手机,而是把“办事前先问清楚、办事中能查到进度、材料可以提前上传”这三件事做好。这也是我为什么在众多方案里最终选择微信小程序加SpringBoot这套组合来落地。无论你是做毕业设计、课程设计,还是接了一个真实的乡镇便民服务项目,这套代码都足够典型,覆盖了小程序登录、手机号授权、用户鉴权、预约流程、文件上传、进度状态机,基本就是中小型业务系统的完整链路。这篇我把整个项目从源码工程结构拆到核心实现,再到部署上线会踩的坑,一次性讲清楚。
1. 系统定位与功能范围拆解
1.1 为什么是“微信小程序+SpringBoot”这个组合
先说说选型。这个项目一开始其实也纠结过H5、Uniapp打包App、原生小程序这三条路。
- H5虽然开发快,但入口散,村民很难记住网址,也拿不到微信的推送能力。
- Uniapp打包App,光安装包和版本更新就能让一半用户流失,更别说苹果安卓两套审核。
- 微信小程序免安装、微信里直接搜就能用、手机号授权顺手,特别适合农村用户“用完即走”的使用习惯。
后端选SpringBoot则主要看中三点:一是Java生态成熟,这类项目后续接工作流、消息队列、定时任务,都有现成组件;二是部署简单,一个jar包就能跑,不像PHP需要搭Nginx加PHP-FPM,也不像Python项目要额外管理虚拟环境;三是招人和接手成本低,随便找个会Java的都能维护。
唯一要提醒的是SpringBoot版本别一上来就选最新。SpringBoot 3.x把javax包改名成了jakarta,导致很多老教程里的代码直接编译不过,MyBatis-Plus也得换新版本。这个项目我用的2.7.18,稳定、资料多、踩坑成本低,具体原因后面第4节专门讲。
1.2 需求拆解:村民端到底需要哪些功能
做系统之前先把业务边界划清楚,不然很容易做成“什么都有、什么都不好用”。这个乡村政务服务系统最终拆成了六个核心功能模块:
| 功能模块 | 村民端做什么 | 管理员端做什么 |
|---|---|---|
| 公告通知 | 查看最新通知、政策信息 | 发布/修改/下线公告 |
| 办事指南 | 按分类查看办理流程、所需材料 | 维护事项分类与办事指南内容 |
| 在线预约 | 选择事项、日期、时间段,上传材料 | 审核预约请求 |
| 进度查询 | 查看预约状态和处理结果 | 更新办理进度、填写处理意见 |
| 意见反馈 | 提交建议、问题,查看回复 | 回复用户反馈 |
| 个人中心 | 查看/修改基本信息、我的预约 | 查看用户列表 |
这里我想特别说明一点:预约才是这个系统的核心,而不是公告。很多类似的源码工程把首页做成公告堆砌,预约却做得草率,这其实是本末倒置。村民真正高频用的是“我想办个低保申请,该带什么材料、什么时候能交材料”。所以数据模型和接口优先级都围绕预约展开,公告只是辅助信息。
1.3 源码工程的目录规划
拿到交付的压缩包后,你会看到这样一个结构:
govservice-back/ # SpringBoot后端工程 src/main/java/com/novillage/gov/ controller/ # 接口层:接收小程序请求 service/ # 业务逻辑层:预约流程、审核流程 mapper/ # 数据访问层:MyBatis-Plus接口 entity/ # 数据库实体映射 config/ # 全局配置:跨域、拦截器、静态资源映射 common/ # 统一返回体、异常处理、工具类 src/main/resources/ application.yml # 环境配置 mapper/ # 复杂SQL的XML文件 govservice-mini/ # 微信小程序前端工程 pages/ index/ # 首页:公告+快捷入口 guide/ # 办事指南列表与详情 reserve/ # 在线预约:表单+文件上传 progress/ # 进度查询 feedback/ # 意见反馈 profile/ # 个人中心 utils/ request.js # 请求封装,统一携带token app.json sql/govservice.sql # 数据库初始化脚本 README.md # 启动说明这样分层的好处是前后端可以完全分离开发。小程序端写死接口路径即可,后端单独测试用Swagger或Postman都行。更重要的是,这个目录结构是绝大多数SpringBoot实战项目的标准模板,你学会了这套拆分逻辑,以后接任何中小型业务系统都能直接复用。
2. SpringBoot后端:从建表到接口设计的核心逻辑
2.1 数据访问层选型与SpringBoot配置
后端数据访问我用的是MyBatis-Plus,理由很简单:单表CRUD几乎不用写SQL,只有预约列表和进度详情这种涉及多表查询的场景才需要XML。对于这个体量的项目,JPA虽然更快,但封装过度导致排查问题困难;MyBatis原生又太啰嗦。MyBatis-Plus刚好在两者中间。
application.yml里最关键的是数据源配置,我贴一下稳定可用的版本:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/govservice?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 你的数据库密码 servlet: multipart: max-file-size: 10MB max-request-size: 30MB jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0注意两个点:一是数据库连接串必须带serverTimezone=Asia/Shanghai,不然插入时间会差8小时;二是spring.jackson.time-zone要单独设置,否则前端拿到的时间是UTC格式,排查起来很痛苦。这两处是我接手别人项目时最常见的问题。
2.2 核心数据表设计思路
表结构决定了这个系统能走多远。我先列核心表,再讲设计逻辑。
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid', `nickname` varchar(64) DEFAULT NULL, `mobile` varchar(20) DEFAULT NULL COMMENT '预留手机号', `avatar` varchar(255) DEFAULT NULL, `village_name` varchar(64) DEFAULT NULL COMMENT '村/社区名称', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `item_category` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL, `icon` varchar(255) DEFAULT NULL, `sort` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `gov_item` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `category_id` bigint(20) NOT NULL, `item_name` varchar(128) NOT NULL COMMENT '事项名称', `procedure_desc` text COMMENT '办理流程说明', `material_desc` text COMMENT '所需材料清单', `time_limit` varchar(32) DEFAULT NULL COMMENT '办理时限', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `appointment` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `item_id` bigint(20) NOT NULL, `appointment_date` date DEFAULT NULL, `time_slot` varchar(16) DEFAULT NULL COMMENT '时间段:上午/下午', `status` int(11) DEFAULT 0 COMMENT '0待审核 1办理中 2已完成 3已驳回', `remark` varchar(255) DEFAULT NULL, `handle_reply` varchar(255) DEFAULT NULL COMMENT '处理意见', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里面有两个设计细节容易被忽略。第一,用户表只用自增主键,但必须给openid加唯一索引。因为openid本身就能唯一标识一个微信用户,自增id只是给其他表引用用的,如果拿openid当主键,字符串索引浪费空间,还会暴露用户隐私。第二,预约表的time_slot用“上午/下午”这种字符串,而不是时间戳。乡村办事并不是精确到几点几分,反而上午下午更符合实际情况,也让数据库校验约束更简单。
2.3 微信登录与手机号授权
小程序端用户首次打开,我会引导授权登录。流程是:小程序调用wx.login()拿到临时code,传到后端,后端再拿code去微信接口换openid。
后端核心代码:
@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private UserService userService; @PostMapping("/login") public Result<LoginVO> login(@RequestBody LoginDTO dto) { // 1. 用code换取openid String openid = wechatService.code2Session(dto.getCode()); // 2. 查用户,查不到则自动注册 User user = userService.findByOpenid(openid); if (user == null) { user = new User(); user.setOpenid(openid); user.setNickname("微信用户" + openid.substring(openid.length() - 6)); userService.save(user); } // 3. 生成自己的登录态token(这里用UUID+Redis,实际也可用JWT) String token = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set("token:" + token, user.getId().toString(), 7, TimeUnit.DAYS); LoginVO vo = new LoginVO(); vo.setToken(token); vo.setUserInfo(user); return Result.success(vo); } }手机号授权的实现这两年有变化。旧版是前端wx.getUserProfile拿不到手机号,新版基础库2.21.2以后,用<button open-type="getPhoneNumber">触发,返回一个code,后端拿这个code调用phonenumber.getPhoneNumber换手机号。必须注意:个人主体的微信小程序无法调用手机号快捷验证组件,只有企业、个体户、政府等非个人主体才能申请。很多第一次做的人卡在这里,以为是代码写错了,其实是资质问题。
2.4 统一返回体、全局异常与分页
所有接口我都封装了一套统一的返回结构,这样前端处理逻辑才会简单:
public class Result<T> { private int code; // 200成功,400参数错误,401未登录,500系统异常 private String msg; private T data; }配合全局异常处理器,后端在Service里就不需要每个方法都写try-catch。比如预约时参数校验失败,直接抛BizException(400, "预约日期不能为空"),前端收到code=400后统一Toast提示msg。这比每个接口返回不同数据结构要清晰得多。
分页查询使用MyBatis-Plus内置的分页插件,预约列表接口返回格式保持统一:
{ "total": 35, "records": [ { "id": 1, "itemName": "城乡居民养老保险参保登记", "appointmentDate": "2024-11-20", "status": 1 } ] }前端小程序只需要判断total是否大于当前已加载条数,据此决定是否继续加载下一页。这是分页场景里最不容易出Bug的方案。
2.5 文件上传与静态资源映射
预约材料里需要上传身份证照片、户口本照片等。后端提供一个通用上传接口,保存到本地目录并返回访问URL:
@PostMapping("/api/file/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = UUID.randomUUID().toString().replace("-", "") + ext; File dir = new File("/data/govfile/"); if (!dir.exists()) dir.mkdirs(); file.transferTo(new File(dir, filename)); return Result.success("/files/" + filename); }同时需要配置资源映射,否则访问不了:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**") .addResourceHandler("file:/data/govfile/"); }生产环境如果换成云OSS,只需要改这个接口的实现,Controller层调用保持不变。这也是把上传逻辑收敛到一个接口的好处。
3. 微信小程序端页面实现与那些容易忽略的细节
3.1 顶部导航栏高度与安全区域适配
很多初次做小程序的人直接使用默认导航栏,结果发现标题留白很难看。这个项目里我选择了自定义导航栏,页面效果更统一,但代价是要自己适配状态栏高度。
app.json里设置:
{ "window": { "navigationStyle": "custom" } }然后在公共工具里计算导航栏高度:
function getNavBarHeight() { const { statusBarHeight, windowWidth } = wx.getSystemInfoSync(); const menuButton = wx.getMenuButtonBoundingClientRect(); const navBarHeight = (menuButton.top - statusBarHeight) * 2 + menuButton.height; return { statusBarHeight, navBarHeight, totalHeight: statusBarHeight + navBarHeight }; }原理是:微信胶囊按钮垂直居中,胶囊上边到状态栏下边的距离,就是导航栏上下留白的一半,所以顶部到胶囊底部的高度等于(top - statusBarHeight) * 2 + height。拿到这个值后,页面里用padding-top撑开。iPhone底部安全区则用:
padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);这个细节在真机上影响很大,模拟器里看着正常,一上iPhone X就顶到小白条,显得很不专业。
3.2 request请求封装与登录态管理
小程序端我封装了一个request.js,每个请求自动带token,如果接口返回401则跳转登录页。核心代码:
const request = (url, method, data) => { return new Promise((resolve, reject) => { wx.request({ url: getApp().globalData.baseUrl + url, method: method || 'GET', data: data || {}, header: { 'Content-Type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success(res) { if (res.data.code === 200) { resolve(res.data.data); } else if (res.data.code === 401) { wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/login' }); reject(res.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail: reject }); }); };登录态只存token,用户信息单独存userInfo。这样下次打开小程序先检查token,没有就直接静默登录(用wx.login换code),不打断用户操作。注意这里的“静默登录”不是获取用户头像昵称,只是后台用openid识别身份,用户感知不到,体验最好。
3.3 办事指南与预约页面的表单交互
办事指南页用分类Tabs加列表实现,点击进入详情展示“办理流程”和“所需材料”。这个页面本身不难,重点在预约页的表单交互。
预约页涉及字段比较多,我列一下容易出问题的几个控件:
- 事项选择:用
picker模式选择分类和具体事项,注意数据量大了以后要用两级联动,不能一次性把所有事项放到一个picker里。 - 日期选择:
picker mode="date",设置start="今天"。 - 时间段选择:用
radio-group渲染“上午/下午”两个按钮,这是典型的“微信小程序单选框”场景。 - 材料上传:用
wx.chooseMedia一次最多选9张,上传后把返回的URL拼到list里,提交时整体附带。
表单提交前必须做校验,不能只依赖后端。我给前端的经验是:校验不能只判断非空,日期必须晚于今天、手机号必须正则匹配、至少上传一张材料图。这些规则如果只在后端做,用户填完点提交才弹错,体验会差很多;如果只在前端做,又会被人绕过。所以两层都要有。
3.4 进度查询的状态机设计
进度查询是整个系统最能体现“政务服务”价值的地方。村民提交预约后,最关心的就是“到底办到哪一步了”。后端用status字段表示状态:
| status | 含义 | 前端显示 |
|---|---|---|
| 0 | 待审核 | 已提交,等待工作人员审核 |
| 1 | 办理中 | 审核通过,正在办理 |
| 2 | 已完成 | 办理完成 |
| 3 | 已驳回 | 被驳回,附处理意见 |
小程序端我用steps组件纵向展示状态:申请提交、审核通过、办理中、办结。注意这里有个细节:驳回并不是一条横向上的状态,而是单独一个分支。如果用户被驳回,不能再走一遍步骤条,而是显示“审核未通过+原因”。很多源码在这个地方处理不当,把驳回当成另一个节点塞进步骤条,用户搞不清顺序。我的做法是:状态为3时,步骤条高亮到“审核”,下面用红色提示区展示驳回原因,同时提供“重新预约”按钮。实现起来逻辑更清晰。
后端审核接口也需要校验状态流转的合法性,不能允许“已完成 -> 待审核”这种倒流。简单做法是:
if (appointment.getStatus() == 1 && dto.getStatus() == 2) { // 合法 } else if (appointment.getStatus() == 0 && dto.getStatus() == 3) { // 合法 } else { throw new BizException(400, "非法的状态流转"); }3.5 意见反馈与个人中心
意见反馈页就是一个textarea加提交按钮,但后端需要保存user_id,并记录状态。个人中心则展示头像、昵称、所属村、手机号,还有“我的预约”入口,点击进入自己的预约列表,能直接看每条的状态。
个人中心这里有个用户信息一致性坑:很多项目把手机号直接放在user表里,用户改了一次手机号,旧手机号就被覆盖了。这个系统里我如果还没做短信验证就去填手机号,只在本人主动编辑时更新。其实这个项目里手机号最初是登录时授权获取,后续不允许用户任意改,避免审核记录里手机号前后对不上。
4. 真正容易翻车的地方:联调、真机与部署
这块内容是经验部分,也是最想跟读者强调的部分。很多项目代码本身没错,却卡在跑不起来的环节。
4.1 本地联调环境配置
后端代码写完后,第一步不是直接调接口,而是先确认小程序开发者工具能访问到后端。
开发环境下,在微信开发者工具右上角“详情 -> 本地设置”,勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”。然后小程序里baseUrl填http://127.0.0.1:8080或局域网IP。这里有个巨坑:如果小程序和SpringBoot不在同一台机器上,千万不要用localhost,必须填后端的局域网IP,例如http://192.168.1.100:8080。否则真机预览时会连接到手机自己,直接报“网络无法连接”。
同时后端要解决跨域问题。最简单的方式是加一个配置类:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }生产环境我建议用Nginx同域代理,让前端请求/api转发到后端的8080,这样就不存在跨域了。
4.2 SpringBoot版本太高引发的连锁问题
热词里有很多人在搜“springboot版本太高”,这条必须单独说。
如果你新建项目时直接选最新版SpringBoot(3.x),会面临这些连锁问题:
| 问题 | 原因 | 解决方案 |
|---|---|---|
javax.servlet不存在 | SpringBoot 3.x 改用jakarta.servlet | 把所有javax.*改成jakarta.*,或干脆用2.7.x |
| MyBatis-Plus 启动失败 | 旧版MP不兼容SpringBoot3 | 升级mybatis-plus-spring-boot3-starter,但依赖和配置差异大 |
| druid 数据源配置失效 | druid版本未适配 | 替换为HikariCP |
| 局部代码报ClassNotFound | 部分老工具类基于java8 | 用SpringBoot 2.7.18最稳妥 |
我的经验是:这个项目的目标不是研究最新框架,而是稳定跑通业务流程。所以后端直接选SpringBoot 2.7.18 + MyBatis-Plus 3.5.3.1,对应JDK1.8。这个组合我用了两年没出过兼容问题,网上案例也多,遇到问题一搜就有答案。
4.3 小程序真机调试与线上部署
真机调试和本地开发环境完全不同。本地可以忽略域名校验,真机必须满足:
- 后端部署到公网服务器,并且使用HTTPS协议;微信小程序要求所有request、uploadFile合法域名必须是HTTPS,且证书有效。
- 域名必须完成ICP备案,且在小程序管理后台的“开发设置 -> 服务器域名”里添加
request合法域名和uploadFile合法域名。 - 如果只是演示,可以用微信开发者工具的真机调试功能,它会通过本机转发,但仅限于开发调试,不能给真实用户使用。
后端部署我推荐最轻量的方式:云服务器 + JDK1.8 + MySQL + Nginx。流程:
# 1. 打包(跳过测试) mvn clean package -DskipTests # 2. 上传 jar 到服务器,例如 /opt/govservice/ scp target/govservice-back-0.0.1-SNAPSHOT.jar root@你的IP:/opt/govservice/ # 3. 启动 nohup java -jar /opt/govservice/govservice-back-0.0.1-SNAPSHOT.jar > /opt/govservice/logs/app.log 2>&1 & # 4. Nginx 反向代理,配置示例server { listen 80; server_name yourdomain.com; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }HTTPS证书可以用免费版,配置后重启Nginx。我建议把前端接口路径设计成相对路径(如/api/auth/login),这样开发和生产的baseUrl只需要从 http 改为 https 即可,代码不用动。
4.4 常见报错与解决方案对照
最后把高频报错列一个表,你遇到直接查:
| 现象 | 原因 | 解决 |
|---|---|---|
| 40029 code无效 | wx.login的code只能用一次,重复使用会过期 | 确保后端只用一次;时间相差太大也会失效 |
| getPhoneNumber:fail no permission | 个人主体小程序无法使用手机号快速验证 | 换企业主体,或改为用户手动填写手机号 |
| 上传图片失败:url not in domain list | uploadFile合法域名未配置 | 管理后台添加文件上传域名 |
| 接口返回500:Table doesn't exist | 数据库脚本没执行或表名大小写问题 | 检查数据库,注意Windows和Linux下大小写敏感 |
| 预约列表时间差8小时 | 服务端时区设置不对 | 配置serverTimezone=Asia/Shanghai,JVM加-Duser.timezone=GMT+8 |
| 前端按钮连续点击产生两条预约记录 | 没有做幂等控制 | 后端在user_id+item_id+appointment_date+time_slot唯一约束,或前端提交时加锁 |
5. 源码交付内容与二次开发扩展方向
5.1 拿到源码后按这个顺序跑通
交付的压缩包里有后端、小程序前端、SQL脚本和README。第一次运行建议严格按下面步骤,不要跳:
- 安装环境:JDK1.8、Maven 3.6+、MySQL5.7/8.0、微信开发者工具最新稳定版。
- 创建数据库:
CREATE DATABASE govservice DEFAULT CHARACTER SET utf8mb4;,然后导入sql/govservice.sql。 - 修改
application.yml里的数据库用户名密码,确保能连上。 - 启动后端:
mvn spring-boot:run,看到“Started Application”表示成功。用浏览器访问http://localhost:8080/api/notice/list,能返回JSON就说明后端正常。 - 微信开发者工具中导入
govservice-mini目录,修改app.js里的baseUrl为本机IP或服务器IP。 - 修改
appid:小程序后台的AppID,测试号也可以,但需在开发者工具中正确填。 - 编译运行,先学习和熟悉页面与接口,再考虑改造。
5.2 二次开发最容易踩的雷
跑通之后大部分人都会做定制,这里列几个我实际踩过的坑,希望你能绕开。
第一,改表结构以后必须同步修改Entity和Mapper XML。很多时候只改了数据库字段,MyBatis的结果集映射就报Unknown column,排半天发现是XML里的resultMap没更新。建议只用MyBatis-Plus的注解@TableField,少写复杂XML,减少同步成本。
第二,时间字段全链路统一成字符串。小程序端直接展示后端返回的yyyy-MM-dd HH:mm:ss字符串,不要在前端new Date。微信开发者工具的JavaScript在不同版本上解析2024-11-20 10:00:00的兼容性不一致,尤其iOS会把这种格式解析成NaN。
第三,不要把处理逻辑全放在前端。比如预约材料清单,前端展示是一回事,后端审核时必须再校验附件数量是否满足条件。政务类系统讲究留痕,操作日志和状态流转时间最好都记录一下,哪怕只存个操作时间和操作人。
5.3 可以继续扩展的功能方向
一个源码工程交付只是开始,你可以基于它继续扩展。热词里有人搜“天地图集成微信小程序”,这确实是有价值的方向。乡村政务服务需要空间展示,比如村级便民服务点分布、农田位置导航等。天地图提供微信小程序SDK,可以加载地图、标记服务点,效果很直观。集成步骤可以单独写一篇,这里先提一句:需要在小程序后台配置业务域名,并且地图容器的尺寸计算要考虑顶部导航栏高度。
另一个值得扩展的是消息主动通知。当前版本用户得自己点“进度查询”才知道办到哪一步。更合理的方式是接入微信订阅消息,审核通过或驳回时给用户推送一条通知。后端可以用SpringBoot整合ActiveMQ或RabbitMQ,在预约状态变更的地方发一条消息,消费者负责调微信订阅消息接口。热词里有人搜“springboot整合activemq”,这类异步处理模式就适用于这个场景。如果用户量不大,甚至可以直接用Spring的事件机制,不必引入消息中间件,别过度设计。
还可以增加“办事指南自动问答”或者“常见问题库”,用关键词匹配返回答案。对村民来说,比翻政策文件友好得多。
最后再分享一点个人体会。这个项目我前后重写过两个版本,第一版把预约状态设计得太简单,只有“未处理”和“已处理”,后来补丁打了一层又一层。第二版才定下“0待审核/1办理中/2已完成/3已驳回”这个四态模型,所有页面和接口都围绕它展开,代码反而清爽了很多。如果你拿这套源码做毕业设计,建议重点理清2.3的登录时序、2.4的统一返回结构、3.4的状态流转逻辑,以及4.2的版本选型原因,这些地方答辩时最容易深挖。如果后面有时间,我再单独写一篇管理端审核流程和微信订阅消息推送的实现细节。