一、背景——如何重新构建一个政务APP开放平台
近年来,各个省市都在尝试进行政务服务数字化,技术比较发达的城市如广东、深圳、杭州这些城市已经完成了政务服务的集中化,通过一个APP就能访问全部的政务服务,无需跳转。
但一些IT建设预算不足的城市,很多业务办理还是只能通过微信或者支付宝的小程序进行,体验非常的割裂。例如,市民要在线上办成一件事,经常需要在多个小程序之间跳转。社保查询在社保局的小程序里,公积金在公积金中心的小程序里,预约挂号又是另一个小程序。每个小程序都是不同供应商开发的,交互体验不统一,账号体系不互通,每次都要重新登录。
当然,这也是历史遗留问题,每个部门各自招标、各自开发、各自运营,数据和用户都分散在不同系统里。市民办事找不到入口,或者找到了也不知道用哪个。各部门之间信息不同步,存在重复建设的情况。
我们的想法是,既然各个部门的小程序生态已经建设好,那有没有可能基于已有的小程序资源,在不进行大规模该走的情况下,把各个部门已经开发好的微信/支付宝小程序统一到一个APP里,市民一个入口就能访问到功能,部门各自维护自己的服务,平台负责统一管理和分发。
然后在开发端就是:让政务APP成为一个宿主APP,各个部门把自己开发的小程序接入进来,市民在同一个APP里找到所有服务,部门在自己租户里管理自己的小程序上下架和数据,平台统一管控安全合规。
二、技术架构——APP侧需要做什么
2.1 整体架构
接入小程序容器 SDK之后,政务APP的技术架构变为:
政务APP
├── 原生模块(登录认证、实名核验、消息通知等基础能力)
├── 小程序运行时(FinClip SDK)
│ ├── 社保小程序(社保局开发和维护)
│ ├── 公积金小程序(公积金中心开发和维护)
│ ├── 预约挂号小程序(卫健委开发和维护)
│ └── ……
└── 政务小程序管理后台(统一管理所有入驻小程序、权限、数据)
这套架构和传统的"一个APP集成所有功能"完全不同:每个部门的小程序是独立开发和迭代的,APP本身只是一个承载容器,不需要为每个部门的业务需求做定制开发。
2.2 SDK初始化——双端代码
APP接入小程序容器 SDK是整个项目最基础的环节。iOS和Android两端同时接入,接口协议一致,初始化代码量都在40行以内。
iOS侧在AppDelegate中初始化:
// iOS SDK 初始化
NSMutableArray *storeArrayM = [NSMutableArray array];
FATStoreConfig *storeConfig = [[FATStoreConfig alloc] init];
storeConfig.sdkKey = @"您的sdkKey信息";
storeConfig.sdkSecret = @"您的sdkSecret信息";
storeConfig.apiServer = @"服务端地址";
[storeArrayM addObject:storeConfig];
[[FATClient sharedClient] initWithConfig:storeArrayM];
Android侧在Application中初始化,需要注意多进程处理:小程序运行在独立进程中,初始化时需要判断当前进程,只在宿主进程执行第三方库初始化:
// Android SDK 初始化——多进程处理
if (FinAppClient.INSTANCE.isFinAppProcess(this)) {
// 小程序进程不执行任何初始化操作
return;
}
List<FinStoreConfig> storeConfigs = new ArrayList<>();
FinStoreConfig config = new FinStoreConfig();
config.setSdkKey("您的sdkKey信息");
config.setSdkSecret("您的sdkSecret信息");
config.setApiServer("服务端地址");
storeConfigs.add(config);
FinAppClient.init(this, storeConfigs);
FinAppClient.start();
2.3 小程序启动与参数传递
政务APP中的小程序启动,通常需要携带用户身份信息(市民登录态)和业务参数(要查询的事项ID、预约时间等)。SDK支持在启动时传入自定义参数,小程序内部可以读取这些参数完成业务初始化:
// Flutter 启动小程序——携带用户身份和业务参数
Future<Map> startApplet(RemoteAppletRequest request)
RemoteAppletRequest request = new RemoteAppletRequest(
apiServer: 'https://api.finclip.com',
appletId: appId
);
request.startParams = {
'userId': '市民userId',
'query':'事项ID=xxx&日期=2024-12-01'
};
Mop.instance.startApplet(request);
如果政务APP预置了某个小程序的离线包,首次打开时无需等待下载,可以直接加载本地包体:
// Android 离线包启动——首次打开无等待
FinAppClient.appletApiManager.startApplet(
this,
IFinAppletRequest.Companion.fromAppId("https://api.finclip.com","小程序appId")
.setOfflineParams("$filesDir/framework.zip", "$filesDir/小程序包.zip")
)
2.4 原生能力透传——实名认证桥接
政务服务对实名认证有强需求,市民打开不同部门的小程序时,不需要每个小程序都走一遍认证流程。SDK支持在原生侧注册扩展API,小程序通过桥接文件调用宿主APP的实名认证能力:
// Flutter 注册实名认证扩展 API
void addWebExtentionApi(String name, ExtensionApiHandler handler)
Mop.instance.addWebExtentionApi('getUserIdentity', (params) async {
// 调用宿主APP的实名认证模块,返回市民身份信息
Map identity = await IdentityModule.getCurrentUser();
return identity;
});
小程序内的H5页面引用桥接文件后即可调用:
// H5 调用原生实名认证能力
window.ft.miniProgram.callNativeAPI('getUserIdentity', {}, (result) => {
console.log('市民实名信息:', result)
});
2.5 多租户隔离与权限管控
相较于常规APP,政务APP一定要有多租户管理的架构:因为每个部门在后台有独立的租户空间,部门只能看到和管理自己的小程序,无法访问其他部门的数据。平台方(APP运营方)拥有最高权限,可以统一配置每个小程序的可见范围、访问时段和功能权限。
在实名认证基础上,平台还可以为不同部门的小程序配置差异化的数据权限:比如公积金查询需要人脸识别级别的高实名人像,社保查询只需要手机号实名,信息敏感度不同,管控策略也不同。
三、热更新——部门服务免发版迭代
传统模式下,部门开发一个小程序如果需要更新功能,要走完政务APP的发版流程:从开发、测试、政务APP打包、提交应用市场审核、用户更新,往往需要数周时间。
小程序容器模式可以实现解耦优化:各部门在自己租户内提交小程序更新,平台审核通过后发布,用户下次打开时SDK自动拉取最新包。整个过程不需要政务APP重新发版,也不需要用户手动更新。
以医保信息查询小程序为例:医保政策经常调整,查询规则和展示内容可能每隔几周就要更新一次。通过热更新机制,医保中心可以在后台自行完成更新发布,用户打开时已是最新版本,不需要等APP发版。
Android SDK从2.35.1版本开始支持配置离线包路径,政务APP预置常用小程序(如社保、公积金等高频服务)的离线包,首次打开无等待,后续迭代走热更新通道。
四、灰度发布——按区域和人群精准下发
政务服务涉及民生,稳定性要求高。新版本上线前必须经过充分验证。小程序管理后台支持按维度配置灰度规则:
| 灰度维度 | 适用场景 |
|---|---|
| 百分比灰度 | 新功能上线,先让5%用户试用,观察无报错再扩量 |
| 地区灰度 | 先行试点街道/区县,确认稳定后扩展到全市 |
| 用户群灰度 | 精准匹配退休人员、在职职工等群体,验证不同群体的使用体验 |
灰度策略在后台配置,无需APP发版,发现异常可以一键回滚到上一稳定版本。
APP版本热更新——老人家不用更新APP也能用新服务
政务APP还有一个实际问题:很多老人家不会主动更新APP,应用市场上架的版本可能已经是半年前甚至更早的版本。如果每次服务升级都依赖APP发版,这些用户永远用不到新功能。
小程序容器的热更新机制解决了这个问题:政务APP本身的版本可以保持不变,但各个部门的小程序是在APP运行时动态加载的。只要小程序有新版本发布,SDK会自动拉取,用户打开时已经是最新版。
这个机制对中老年用户特别有价值。他们的APP可能一年都不更新一次,但每次打开社保查询小程序,用到的都是最新版的界面和最新鲜的数据。APP版本的旧和新,在小程序这个层面变得没那么重要了。
五、市民体验——一个APP解决所有事
政务APP接入小程序容器之后,市民感受到的变化是具体的。
以前市民办事要记很多个小程序的名字、在微信里翻来翻去。现在打开政务APP,首页直接展示各部门的服务入口,不用记不用找。
部门之间数据不打通的问题,也在一定程度上得到缓解:小程序通过原生桥接API读取市民实名信息,不需要每个部门都单独做认证。市民在第一个小程序完成实名,后续小程序直接复用。
从上线的数据看,社保查询、公积金查询、预约挂号是使用频率最高的三个服务。这三个服务的日均使用次数占平台总使用量的70%以上。
六、业务层面挑战
各部门开发能力参差不齐,是主要的管理挑战。 有的部门有自己的技术团队,能独立开发和迭代小程序;有的部门完全依赖外包供应商,开发质量和进度难以把控。我们的解决办法是:平台方提供小程序开发规范文档和模板,各部门或供应商按规范开发,上线前必须通过平台方的安全审查和质量检测。
数据安全责任边界需要提前明确。 市民数据由各部门的小程序处理,但数据存储在政务APP的容器环境里,平台方和部门方在数据安全上的责任边界需要在上线前通过协议明确下来。安全沙箱隔离了小程序的数据访问,但制度层面的责任划分不能靠技术自动解决。
离线使用场景需要提前规划。 政务服务有相当一部分用户是中老年人,使用的是老旧手机,网络条件也不好。如果某个小程序完全依赖网络加载,网络差的时候完全不可用。我们要求所有高频服务小程序预置离线包,保证基础功能在弱网环境下仍可正常使用。
七、常见问题总结
7.1 市民打开小程序白屏,加载失败
现象:市民点击某个部门的小程序后,页面显示白屏,无法正常加载。
原因:小程序的资源包下载过程中网络中断,或者小程序包损坏导致签名校验失败。
解决:检查该小程序的离线包是否正确预置。在管理后台查看该小程序的异常日志,确认是下载失败还是校验失败。如果是个别用户的偶发问题,可以尝试清除该小程序本地缓存后重新打开。
7.2 部门更新小程序后,部分用户看不到新版
现象:某部门在后台发布了新版小程序,但部分用户打开后看到的还是旧版。
原因:SDK默认采用"热启动更新"策略——用户在后台静默下载新版,用户退出小程序后重新进入时切换。部分用户一直在使用中,从未退出,新版没有触发加载。
解决:在管理后台将该小程序的更新策略调整为"强制更新",用户下次打开时SDK会强制加载最新包。对于政务服务类小程序,建议使用强制更新策略,避免版本不一致导致业务逻辑差异。
7.3 各部门数据格式不统一,市民信息无法互通
现象:市民在社保小程序里填写了个人信息,进入公积金小程序后还需要重新填写同样的信息。
原因:各部门小程序独立开发,数据格式和字段定义没有统一标准,也没有接入统一身份认证体系。
解决:平台方在接入规范中强制要求各小程序接入统一身份认证桥接API,市民登录后各小程序共享基础身份信息。同时建立基础数据字典规范,各部门在开发新小程序时必须遵循统一的数据格式。
需要的话可以在Gitee中了解一下:Gitee Finclip