一、引子:我们为何要“自找麻烦”?
在最近的几个项目中,我们反复遭遇这样一个场景:活动报名环节需要收集的用户信息,因活动而异,变化莫测。
活动A要求收集:姓名、性别、年龄、学校,以及单位信息和家长信息(这两者本身又是独立的复杂模型)。
活动B则要求收集:站点、规划、进度,以及同伴信息等。
...以此类推,每次都有新花样。
最初的应对策略:专题定制模式
起初,我们的做法简单“粗暴”:每来一个新活动,就为其量身定制一个专题项目。从数据库表结构,到后端接口,再到前端页面,全套流程都得修改一遍。虽然主体流程大同小异,但绝大部分修改都耗费在适配这些千变万化的信息结构上。
这种模式的真实写照:
走得通,但很累: 如果活动不多、参与人少,尚可勉强支撑。但从开发到运营,整个团队都在“遭罪”。
定制与妥协并存: 好处是能快速响应大部分“临时需求”,但大方向不能动,某些需求仍需妥协。
边际成本过高: 说起来都是“报名”,但每次都要“全套服务”。尽管复制粘贴不会消耗太多脑力劳动,依然需要投入宝贵的时间和精力,因为量太大,牵一发而动全身,投入产出比极低。这终究不是长久之计。
二、破局思路:能否“配置”出一个表单?
从实际业务出发,我们开始思考:能否在每次活动开始前,通过一个后台系统,像搭积木一样配置好本次活动需要采集的信息?然后让客户端系统自动识别这些配置,并动态渲染出对应的表单。
核心目标:
- 大流程不变: 主体业务流程保持稳定,这也是动态表单落地的大前提。
- 降低边际成本: 践行编程界的DRY原则,让类似的活动不再产生重复的开发工作。
- 提升效率: 将开发人员从繁琐的重复劳动中解放出来。
理想很丰满,但挑战接踵而至:
- 数据存储: 动态表单的数据怎么存?存到哪里?
- 数据检索: 信息收集上来后,如何高效检索?
- 数据验证: 提交的数据如何保证规范性?
- 数据导出: 之前固定表单下,导出PDF/Excel相对简单,动态化之后如何实现?
- 业务关联: 如何与后续复杂的业务流程无缝衔接?
这些问题在定制模式下都不是问题,但在新路线下,却成了我们必须攻克的道道难关。
三、解决方案:“元数据”驱动的动态表单引擎
我的解决思路是构建一个 “元数据”驱动的表单引擎。其核心生命周期如下图所示:
- 表单定义
- 动态获取
- 表单渲染
- 数据存储
- 业务关联
整个流程可以概括为以下几个核心阶段:
阶段一:表单定义 - 系统的基石
这相当于我们定义“积木”和“图纸”的过程。
定义表单元素
我们预先定义了一系列基础组件的元数据模板,例如:
- 独立输入框
- 级联输入框
- 独立下拉框
- 级联下拉框
- 独立单选框
- 上传组件(用于照片等场景)
每个元素都通过一个结构化的JSON Schema来描述。例如,一个“独立输入框”的元数据如下:
{
"key": "name",
"label": "姓名",
"type": "input",
"props": {
"placeholder": "请输入您的姓名",
"maxlength": 10,
"dataType": "string"
},
"validation": {
"required": true,
"message": "姓名不能为空",
"pattern": "^[\u4e00-\u9fa5]{2,10}$"
}
}
关键点: 在实际配置时,运营人员无需接触复杂的JSON,只需在友好的表单页面中填写关键信息,系统会自动生成最终的Schema。
组合表单角色
将定义好的表单元素,像拼图一样组合到不同的“角色”中。
- 例如,将“姓名”、“联系方式”、“家庭住址”组合成 “个人信息” 角色。
- 将“职称”、“职务”、“技能”组合成 “个人能力” 角色。
最终,系统会产出一个完整的、可供前端直接使用的表单Schema。
{
"DecMainId": xxx,
"AssociatedName": "DecProjectDetail",
"DynamicRoleName": "\u8EAB\u4EFD\u4FE1\u606F",
"DynamicRoleId": xxx,
"Fields": [
{
"Key": "IdCardType",
"Label": "\u8BC1\u4EF6\u7C7B\u578B",
"Type": "select",
"Props": {
"Placeholder": "\u8BF7\u8F93\u5165\u8BC1\u4EF6\u7C7B\u578B",
"Maxlength": null,
"Format": null,
"Default": null,
"DataType": "string",
"Rows": null,
"Multiple": false,
"Options": [
{
"Value": "0",
"Label": "\u8BF7\u9009\u62E9\u9002\u7528\u7684\u8BC1\u4EF6\u7C7B\u578B",
"Meta": null
},
//...其他
],
"FieldMapping": {
"Value": "",
"Label": ""
},
//...其他属性
},
"Validation": {
"Required": true,
//...其他属性
}
},
{
"Key": "IdCard",
"Label": "\u8BC1\u4EF6\u53F7\u7801",
//...其他属性
},
{
"Key": "Name",
"Label": "\u59D3\u540D",
//...其他属性
}
]
}
阶段二:动态获取与渲染
获取: 前端通过固定的API接口,传入活动ID和角色ID,即可获取到对应的表单Schema。
渲染: 前端根据Schema中的type、label、props等信息,动态渲染出真实的表单控件。这部分涉及具体的前端技术选型,本篇不做深入探讨。
当用户填写后,提交的数据结构非常简单:
{
"decProjectId": xx,
"dynamicRoleId": xx,
"decProjectDetailId": xxx,
"formData": {
"UnitName": "落云宗",
"UnitAddress": "天南大陆",
"Principal": "韩立",
"Duty": "太上长老",
"ConnectPhone": "xxx",
"MobilePhone": "+xxx",
"Email": "abc@xxt.com",
"Fax": "",
"PostCode": "xxx"
}
}
阶段三:数据存储与验证 - 守护数据安全的防线
数据到达服务端后,将经历严格的“安检”流程。
非业务验证 - 数据规范性检查
这层验证完全依据我们之前定义的Schema规则来执行,是数据安全的第一道防线。我的验证器会按顺序执行以下检查:
-
必填校验: 字段是否必须填写。
-
格式校验: 日期、自定义格式等是否正确。
-
数值范围校验: 数字是否在最小/最大值范围内。
-
正则校验: 是否符合预设的正则表达式。
-
级联动态验证: 例如,当“证件类型”选为“身份证”时,才按身份证规则验证“证件号码”。
业务验证 - 逻辑正确性检查
通过规范性验证后,数据进入业务层校验。例如:
-
活动是否在报名期内?
-
报名名额是否已满?
-
信息是否重复?
关于“判重”的性能优化:
在动态表单场景下,精准判重非常复杂。为了提升性能,我们采用了“两级判重”策略:
粗略判重: 在精准判重前,先使用高性能的xxHash算法计算整个表单数据的哈希值,与历史记录对比。这能快速过滤掉完全相同的重复提交。
精准判重: 再根据具体业务规则进行更细致的检查。
public static ulong ComputeFormHash64(object formData, JsonSerializerOptions? options = null)
{
if (formData == null)
throw new ArgumentNullException(nameof(formData));
var jsonBytes = SerializeToJsonBytes(formData, options);
return XxHash64.HashToUInt64(jsonBytes);
}
常用Git的小伙伴应该对这个比较熟悉,我们用“git log --oneline”查看提交记录的时候,前面那个标记,就是用这个算法生成的,既能保证足够的运算空间避免产生碰撞,性能又快!目前大部分开发语言都原生支持xxHash系列算法了,在安全不敏感的场景,完全可以替代早先的md5,SH1等哈希算法。
数据脱敏 - 合规性保障
根据《网络安全法》等法规要求,敏感信息必须脱敏存储。
当前策略: 通过预定义的敏感字段关键词(如IdCard, Phone, Mobile)进行匹配和脱敏。
未来展望: 结合AI,对字段值内容进行智能识别,实现更精准的脱敏。
关于脱敏这一块,单拎出来也能讲好多,道友们需要注意的是,2026年起新的国家网络完全法就要实施了(www.ghxrd.gov.cn/zlk_0/zcfg/…),对数据安全的要求更为严格,建议早做打算。笔者之前有过一篇相关的博客,各位道友也可以自行找一下相关测材料。
保存
以上所有步骤后,数据最终被安全地存入数据库。
四、业务关联与AI增强*
动态表单的落地远不止于数据的收集。真正的挑战在于如何让这些动态数据重新赋能业务。
业务关联: 我们正在设计动态模板,使得导出PDF报名表、生成Excel统计报表等功能,也能像表单一样通过配置实现,确保管理效率不因动态化而降低。
AI增强: 我们计划引入AI能力,用于更智能的敏感信息识别、表单填写辅助,甚至自动生成表单模板,让系统变得更加智能。
这一趴,也是我们正在努力攻克的地方,所以目前还没什么能拿出来分享。
五、结语
构建动态表单引擎,是一个将不确定性封装为确定性的过程。它初期投入更大,挑战更多,但一旦建成,将从根本上扭转我们被动应对需求的局面。
这条路还很长,本文仅抛砖引玉,分享了我们在“表单定义”“数据存储与验证”方面的初步实践以及解决问题的方向,代码没有贴很多,实际上上面聊掉的那几块每一块单拎出来都能聊好久,后面有机会在接着聊,届时相关的代码也会贴出来请各位道友交流指正。
好了,这次就到这,算是及时记录一下开发进度,换换脑子,接着赶项目去啦😭~