表单平台
Form 模块提供问卷、调研、报名和反馈类业务的表单平台底座。
当前已完成模块骨架、应用装配、后台表单定义 / 版本 / 字段契约、发布渠道、公开预览、公开提交、答案快照、 文件题型、文本内容安全、条件显示、分步骤填写、统计报表、表单模板、复制创建、协作者权限快照、通知、审批、Webhook 端点和 Admin 管理入口。模块 Artifact 为 spring-open-module-form,包名为 com.springopen.module.form。
API 前缀
- 后台管理:
/api/v1/admin/forms - 公开入口:
/api/v1/forms/public - 开放 API:
/api/v1/open/forms
能力发现
后台可通过 GET /api/v1/admin/forms/capabilities 查询当前装配的基础能力:
- 模块启用状态。
- 后台与公开 API 前缀。
- 单表单最大字段数。
- 单次提交 payload 默认最大字节数。
- 公开入口开关。
- 支持题型与生命周期状态。
- 后台管理权限码清单,便于 Admin 侧按元数据启停操作入口。
表单定义管理
后台表单定义 API 已接入真实数据库契约,路径前缀为 /api/v1/admin/forms/definitions。
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /api/v1/admin/forms/definitions | 分页查询表单定义、当前版本和字段摘要 |
GET | /api/v1/admin/forms/definitions/{id} | 查询表单定义、版本列表和当前版本字段 |
GET | /api/v1/admin/forms/definitions/{id}/versions/{versionId} | 查询指定版本字段 |
POST | /api/v1/admin/forms/definitions | 创建表单定义和初始草稿版本 |
PUT | /api/v1/admin/forms/definitions/{id} | 修改表单定义;字段变更只写入草稿版本 |
POST | /api/v1/admin/forms/definitions/{id}/versions/draft | 基于当前版本创建新草稿 |
POST | /api/v1/admin/forms/definitions/{id}/versions/{versionId}/publish | 发布草稿版本,旧发布版本转为历史 |
DELETE | /api/v1/admin/forms/definitions/{id} | 逻辑删除定义、版本和字段 |
权限码:
form:viewform:createform:updateform:publishform:delete
后台菜单由迁移文件预置为顶级表单菜单,菜单 code 为 form,组件为 forms/index。
字段契约:
- 字段 Key 在同一版本内必须唯一。
- 支持题型与
GET /capabilities返回的supportedQuestionTypes保持一致。 - 字段定义保存前会转换为 Schema starter 的
SchemaDefinition并执行统一定义校验;提交值会转换为SchemaValidationRequest并执行统一值校验。Form 仍负责表单可见性、内容安全、文件引用快照、审批、Webhook 和付费提交等业务流程。 single_choice/multiple_choice必须提供optionsJson。file题型可在validationJson中配置maxFiles,用于限制单字段最多上传 / 提交的文件引用数量。text/textarea可在validationJson中配置contentSafety=false关闭提交期内容安全检测;默认会在装配 Content Safety Provider 时检测。- 所有题型都可在
validationJson中配置visibleWhen、step和stepTitle,用于条件显示、逻辑跳转和分步骤填写。 - 单表单字段数受
spring.open.form.max-fields-per-form限制。
除旧题型外,Form 可复用 Schema starter 已定义的 email、url、phone、integer、decimal、boolean、time、date_time、image、json 等字段类型。需要 Schema 校验的新字段类型依赖 spring-open-starter-schema 装配;若显式禁用 Schema starter,Form 只接受旧题型,避免出现定义可保存但提交不可校验的状态。
模板、复制创建与协作
后台表单详情页可将当前表单版本保存为模板,也可从模板复制创建新的私有草稿表单。模板保存字段结构、表单设置、来源表单 / 版本、 分类、官方标记和使用次数;复制时会按模板字段快照重建字段,不依赖来源表单后续变更。
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /api/v1/admin/forms/templates | 分页查询表单模板,可按名称 / 编码、场景、分类、官方标记和状态筛选 |
GET | /api/v1/admin/forms/templates/{id} | 查询模板字段和设置快照 |
POST | /api/v1/admin/forms/templates | 将指定表单版本保存为模板 |
PUT | /api/v1/admin/forms/templates/{id} | 修改模板信息;传入来源表单时刷新字段快照 |
POST | /api/v1/admin/forms/templates/{id}/copy | 基于模板复制创建新的表单草稿 |
DELETE | /api/v1/admin/forms/templates/{id} | 删除模板 |
GET | /api/v1/admin/forms/definitions/{formId}/collaborators | 查询表单协作者权限快照 |
POST | /api/v1/admin/forms/definitions/{formId}/collaborators | 新增或更新表单协作者 |
DELETE | /api/v1/admin/forms/definitions/{formId}/collaborators/{collaboratorId} | 移除表单协作者 |
模板查询复用 form:view,模板创建 / 复制复用 form:create,模板修改和协作者维护复用 form:update, 模板删除复用 form:delete。协作者角色当前支持 admin、viewer 和 exporter,服务端会写入对应权限 JSON 快照。
发布渠道、公开预览与提交
后台可在表单详情中为已发布版本创建发布渠道。发布渠道只绑定已发布版本, 后续编辑草稿不会影响已生成渠道的预览快照。
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /api/v1/admin/forms/definitions/{formId}/publish-channels | 查询表单发布渠道 |
POST | /api/v1/admin/forms/definitions/{formId}/publish-channels | 基于已发布版本创建发布渠道 |
PUT | /api/v1/admin/forms/definitions/{formId}/publish-channels/{channelId}/status | 启用或暂停发布渠道 |
DELETE | /api/v1/admin/forms/definitions/{formId}/publish-channels/{channelId} | 删除发布渠道 |
GET | /api/v1/forms/public/{channelCode} | 公开预览发布渠道绑定的表单版本 |
POST | /api/v1/forms/public/{channelCode}/submissions | 提交公开表单答案 |
渠道类型:
public_link:公开链接。short_link:短链渠道快照,当前不直接依赖 Short Link 业务模块。live_code:活码渠道快照,当前不直接依赖 Live Code 业务模块。
公开预览默认受 spring.open.form.public-access-enabled=false 保护。开启后, 只有 active 状态且处于有效时间窗口内的渠道才会返回表单结构。
公开提交要求请求体提供 answersJson,该值必须是 JSON 对象字符串。服务端会按发布版本字段重新校验:
- 登录填写渠道必须存在当前登录用户。
- 渠道
submissionLimit已达到上限时拒绝提交。 answersJson不能超过spring.open.form.max-submission-payload-bytes。- 必填字段必须存在且非空;数组和对象答案不能是空集合。
- 未在发布版本中的字段会被拒绝;选择题、数字、日期、评分和文件题型会按字段类型重新校验。
- 服务端会按发布版本字段的
visibleWhen重新计算当前可见字段;隐藏字段不会参与必填、类型和内容安全校验,也不会写入form_submission.answers_json或答案快照。
提交成功后会写入 form_submission 和 form_submission_answer,返回提交编号、提交状态、字段数量和提交时间。后台可查询提交列表、 详情和字段答案快照,并可按筛选条件创建 CSV 异步导出任务。导出产物只包含提交元数据,不包含答案 JSON。
付费提交可在发布渠道上开启:paidEnabled=1 且 paymentAmount>0 时,公开提交会先写入 pending_payment 状态并返回 billingRequired=true、paymentOrderNo、paymentStatus=pending、 paymentAmount 和 paymentCurrency。支付订单由 Cashier scene=form-submission 承接,Cashier 支付成功回调后 Form 会幂等标记提交支付成功,并按原本内容安全结果进入 submitted 或 under_review,此时才增加提交计数、触发通知 / 审批 / Webhook 等后续副作用。未开启付费的渠道仍按原公开提交流程直接完成。
发布渠道付费字段:
paidEnabled:是否开启付费提交。paymentAmount:单次提交金额,开启付费时必须大于 0。paymentCurrency:币种,未填写时默认CNY。cashierProvider:可选 Cashier 支付 Provider。paymentSubject:可选支付标题,未填写时使用渠道名或表单名。
开放表单 API 与商业化配额
开放表单 API 复用 Open Platform 的 AK/SK、签名、防重放、scope、限流、调用日志和计量链路。Form 模块不直接依赖 Open Platform 业务实体,只消费 Runtime 提供的中立 OpenApiAccessGrant 和路由 scope 注册 contract。
| 方法 | 路径 | Scope | Entitlement item |
|---|---|---|---|
GET | /api/v1/open/forms/{channelCode} | open:form:read | open-api-read |
POST | /api/v1/open/forms/{channelCode}/submissions | open:form:submission:create | open-api-submit |
GET | /api/v1/open/forms/{channelCode}/submissions/{submissionNo} | open:form:submission:read | open-api-submission-read |
GET | /api/v1/open/forms/{channelCode}/report | open:form:report:read | open-api-report |
开放 API 配额由 Entitlement 中性底座承接,Form 使用:
consumerCode=formownerType=open-platform-appownerId=<Open Platform App ID>quotaScope=open-apiquotaPeriod=daily/monthly
当目标 Open Platform App 没有配置 Form 权益或对应 item / 配额策略时,Form 不做商业化拦截;一旦配置了启用中的日 / 月配额, 读、提交、提交详情和报表都会按对应 item 检查并消费配额,余额不足返回表单模块业务错误。
条件显示、逻辑跳转与分步骤
字段的 validationJson.visibleWhen 支持对象或数组。数组按 AND 语义计算,全部满足时字段可见。单个条件支持:
| 字段 | 说明 |
|---|---|
fieldKey / field / key | 依赖的字段 Key |
operator / op | eq、ne、in、not_in、contains、not_contains、empty、not_empty |
value / values / equals | 期望值,支持标量或数组 |
示例:
{
"visibleWhen": {
"fieldKey": "customerType",
"operator": "eq",
"value": "company"
},
"step": 2,
"stepTitle": "企业资料"
}App /form/public 和 PC /forms/<channelCode> 会按同一规则实时显示字段,并把可见字段按 step 分组显示上一步 / 下一步。 当某一步的字段因条件隐藏后,公开填写页会自动跳过该步骤,形成逻辑跳转体验。服务端仍以发布版本规则为准,前端隐藏或本地篡改不能绕过校验。
文件题型与内容安全
file 题型答案保存为资源引用快照,不在 Form 模块复制二进制文件或 Core File 文件状态。公开填写页上传文件时复用当前用户文件上传接口, 上传成功后提交形态如下:
{
"resume": {
"resourceId": 1001,
"fileId": "FILE-1001",
"name": "resume.pdf",
"size": 128000,
"contentType": "application/pdf",
"extension": "pdf"
}
}当 validationJson.maxFiles 大于 1 时,同一字段提交上述对象数组;服务端只保留 resourceId、fileId、resourceCode、 url、name、originalFilename、size、contentType、extension、path、disk、bucket、visibility 和 hash 等可审计快照字段,其他客户端附加字段会被丢弃。没有登录态的公开渠道仍可由业务自有入口提交已存在的 resourceCode 或 url 引用;三端内置上传入口面向有当前用户文件权限的场景。
text / textarea 题型默认接入 Content Safety Provider。检测结果为 reject 时拒绝提交,检测结果为 review 时提交保存为 under_review,普通安全结果保存为 submitted。若当前应用未装配 Content Safety Manager,则检测步骤会跳过,不影响表单提交。
通知、审批与 Webhook
当公开提交进入 under_review 时,Form 会注册并使用审批流定义 form.submission.review;后台可对提交执行通过或拒绝,提交会记录 reviewedBy、reviewedAt、reviewAction 和 reviewRemark,状态转为 approved 或 rejected。若提交者是登录用户,审核结果会尽力发送站内通知;通知发送异常不会回滚审核结果。
Webhook 端点保存在 form_webhook_endpoint,投递快照保存在 form_webhook_delivery。每次提交创建、进入待审核、审核通过或审核拒绝时,Form 会为匹配事件的启用端点写入本地投递快照,并把同一 payload 入 Runtime Outbox 的 consumer=webhook,由 Runtime Webhook 投递器负责 HTTP 发送和系统级重试。后台“重试”会把本地投递重新排队并再次入 Runtime Outbox;若 Outbox 不可用或入队失败,本地投递会保留失败状态和错误摘要,便于运营排障。 端点可启用 HMAC-SHA256 签名;签名密钥只在创建 / 更新时写入,不会在接口响应中回显,后台只展示脱敏值。启用签名时服务端会要求端点存在密钥, 并把 webhook.signing-enabled、webhook.secret 和端点标识写入 Runtime Outbox headers,由 Runtime Webhook 投递器生成标准签名头。
当前支持事件:
form.submission.createdform.submission.review-requiredform.submission.approvedform.submission.rejectedform.webhook.test(测试投递)
后台提交查询
后台提交查询 API 路径前缀为 /api/v1/admin/forms/submissions。
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /api/v1/admin/forms/submissions | 分页查询提交记录,可按表单、渠道、提交编号、状态、提交者、追踪标识和提交时间筛选 |
GET | /api/v1/admin/forms/submissions/report | 按提交筛选条件生成统计报表快照,返回状态、渠道、每日趋势和字段答案分布 |
GET | /api/v1/admin/forms/submissions/{id} | 查询提交主记录、答案 JSON 和字段答案快照 |
POST | /api/v1/admin/forms/submissions/{id}/approve | 审核通过提交,写入审核动作、备注和审核人 |
POST | /api/v1/admin/forms/submissions/{id}/reject | 审核拒绝提交,写入审核动作、备注和审核人 |
POST | /api/v1/admin/forms/submissions/export | 按提交列表筛选条件创建 CSV 异步导出任务,产物只包含提交元数据,不导出答案 JSON |
提交查询、报表和导出复用 form:view 权限,审核操作复用 form:update 权限。分页 / 详情接口返回提交编号、表单 / 版本 / 渠道、提交者、状态、 答案 JSON、字段数量、字段答案快照、客户端追踪标识和提交时间;导出接口返回 Core Async Task 异步任务,任务完成后可在后台异步任务中心下载私有 CSV 文件。 CSV 不包含答案 JSON;公开提交响应仍不会暴露答案 JSON。
报表接口即时生成 FR- 前缀的快照编号和生成时间,不额外持久化报表产物。后台页面复用提交列表筛选条件展示提交总数、审核中数量、 登录用户数、渠道数量、字段数量、状态占比、渠道占比、每日趋势、字段已答 / 空值、选项分布和数字 / 评分均值。
公开填写页:
- App:
/form/public?channelCode=<channelCode>。 - PC:
/forms/<channelCode>。
两端都会调用真实公开预览和提交 API,不使用本地伪造数据。
Webhook 管理
Webhook 管理 API 路径挂在表单定义下:
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /api/v1/admin/forms/definitions/{formId}/webhook-endpoints | 查询表单 Webhook 端点 |
POST | /api/v1/admin/forms/definitions/{formId}/webhook-endpoints | 创建 Webhook 端点 |
PUT | /api/v1/admin/forms/definitions/{formId}/webhook-endpoints/{endpointId} | 修改端点名称、目标地址、订阅事件、状态、最大尝试次数和签名配置 |
DELETE | /api/v1/admin/forms/definitions/{formId}/webhook-endpoints/{endpointId} | 删除端点 |
POST | /api/v1/admin/forms/definitions/{formId}/webhook-endpoints/{endpointId}/test | 创建测试投递并入 Runtime Outbox |
GET | /api/v1/admin/forms/webhook-deliveries | 分页查询投递快照,可按表单、端点、提交、事件、状态和 traceId 筛选 |
POST | /api/v1/admin/forms/webhook-deliveries/{deliveryId}/retry | 将投递重新排队并再次入 Runtime Outbox |
端点请求支持 signingEnabled、signingSecret 和 clearSigningSecret。signingSecret 是 write-only 字段,响应只返回 signingSecretMasked;启用签名但没有新旧密钥时会拒绝保存,避免生成无效签名配置。端点状态支持 active 和 disabled;投递状态支持 pending、succeeded 和 failed。Admin 表单详情页已提供端点维护、签名密钥轮换 / 清空、测试投递、投递查询和人工重试入口。
配置
配置前缀:spring.open.form。
| 配置项 | 默认值 | 说明 |
|---|---|---|
enabled | true | 是否启用表单平台 API |
max-fields-per-form | 200 | 单个表单默认允许配置的最大字段数 |
max-submission-payload-bytes | 262144 | 单次提交 payload 默认最大字节数 |
public-access-enabled | false | 是否允许公开表单入口被直接访问 |
这些配置已由 FormServiceProvider 声明到 lifecycle 配置 schema,后台配置中心可识别表单启停、字段数、提交 payload 和公开入口开关;未接入配置中心的部署仍可通过 application.yml / 环境变量配置。
后续
当前运营视图已覆盖提交列表、报表快照、字段答案分布、异步导出、审核、模板、协作者和 Webhook 投递。后续只有在出现新的明确运营视角时,再按多表单看板、漏斗分析或跨渠道转化等独立功能簇扩展。
