<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>FormDSL - tag - 沐木</title><link>https://oldletter.cn/tags/formdsl/</link><description>FormDSL - tag - 沐木</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 11 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://oldletter.cn/tags/formdsl/" rel="self" type="application/rss+xml"/><item><title>测评平台（二）：多资源快照、作答、评分与报告</title><link>https://oldletter.cn/posts/assessment-platform-02-multi-resource-runtime/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/assessment-platform-02-multi-resource-runtime/</guid><description><![CDATA[<blockquote>
<p>系列导航：<a href="/posts/assessment-platform-01-resource-release-task/" rel="">5</a> | <a href="/posts/assessment-platform-02-multi-resource-runtime/" rel="">6</a> | <a href="/posts/assessment-platform-03-draft-preview-publish/" rel="">7</a></p></blockquote>
<p>一次测评可能包含问卷、考试、阅读材料、多个量表和报告模板。运行期间如果每个页面都去读取资源当前版本，后台编辑会让同一参与者前后拿到不同题目。运行单位应当是一个绑定发布包的实例，所有子资源从同一份快照派生。</p>
<h2 id="建立运行实例">建立运行实例</h2>
<p>任务启动时校验任务状态、访问模式、时间窗和提交令牌，再创建 <code>assessment_run</code>。实例写入任务 ID、<code>release_id</code>、参与者 ID、开始时间和状态。后续查询不再接受客户端选择的资源版本，服务端通过 <code>AssessmentRelease</code> 找到冻结的 FormDSL、题目快照和报告配置。</p>
<div class="mermaid" id="id-2" data-mermaid-definition="c2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBQIGFzIOWPguS4juiAhQogICAgcGFydGljaXBhbnQgQVBJIGFzIOi/kOihjCBBUEkKICAgIHBhcnRpY2lwYW50IFQgYXMg5Lu75Yqh5LiO5Y&#43;R5biD5YyFCiAgICBwYXJ0aWNpcGFudCBSIGFzIOi/kOihjOWunuS&#43;iwogICAgcGFydGljaXBhbnQgUyBhcyDor4TliIbkuI7miqXlkYoKICAgIFAtPj5BUEk6IHN0YXJ0KHRhc2tJZCwgdG9rZW4pCiAgICBBUEktPj5UOiDmoKHpqowgYWNjZXNzX21vZGUg5LiOIHJlbGVhc2VfaWQKICAgIFQtLT4&#43;QVBJOiDlhrvnu5Plv6vnhacKICAgIEFQSS0&#43;PlI6IOWIm&#43;W7uui/kOihjOWunuS&#43;iwogICAgUC0&#43;PkFQSTogYXV0b3NhdmUoYW5zd2VyIHBhdGNoKQogICAgQVBJLT4&#43;Ujog5qCh6aqM5bm25L&#43;d5a2Y6KeE6IyD5YyW562U5qGICiAgICBQLT4&#43;QVBJOiBzdWJtaXQoKQogICAgQVBJLT4&#43;Uzog6K&#43;75Y&#43;W5ZCM5LiAIHJlbGVhc2VfaWQKICAgIFMtLT4&#43;UDog57uT5p6c5LiO5oql5ZGK">sequenceDiagram
    participant P as 参与者
    participant API as 运行 API
    participant T as 任务与发布包
    participant R as 运行实例
    participant S as 评分与报告
    P-&gt;&gt;API: start(taskId, token)
    API-&gt;&gt;T: 校验 access_mode 与 release_id
    T--&gt;&gt;API: 冻结快照
    API-&gt;&gt;R: 创建运行实例
    P-&gt;&gt;API: autosave(answer patch)
    API-&gt;&gt;R: 校验并保存规范化答案
    P-&gt;&gt;API: submit()
    API-&gt;&gt;S: 读取同一 release_id
    S--&gt;&gt;P: 结果与报告</div><h2 id="formdsl-是输入不是信任边界">FormDSL 是输入，不是信任边界</h2>
<p>运行服务从冻结快照解析题目类型、选项、必填规则和可见性条件。客户端提交的字段只作为候选值，服务端重新检查题目标识、数据类型、选项范围、矩阵结构和条件规则。题目被删除或版本不匹配时，提交失败并记录具体项，不把未知字段写入答案表。</p>
<p>答案保存分为原始接收和规范化结果。原始请求用于审计时应做长度和字段限制，评分只读取服务端生成的规范化对象。NUMBER 类型的答案使用 <code>numberValue</code>，不能依赖前端展示用的 <code>rawValue</code>。这个区别曾造成草稿试答提交失败，修复点在 <code>FrozenFormAnswerResolver</code>：它从冻结 FormDSL 解析标准值，再交给评分器。</p>
<h2 id="自动保存要可重放">自动保存要可重放</h2>
<p>自动保存请求携带 <code>runId</code>、客户端序号和答案补丁。服务端按题目标识合并补丁，并以运行实例版本或乐观锁防止旧请求覆盖新答案。重复请求返回同一版本，网络重试不会产生重复答案记录。保存成功只表示答案落盘，不表示题目已完成或评分已通过。</p>]]></description></item><item><title>测评平台（一）：资源、发布包与测评任务分层</title><link>https://oldletter.cn/posts/assessment-platform-01-resource-release-task/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/assessment-platform-01-resource-release-task/</guid><description><![CDATA[<blockquote>
<p>系列导航：<a href="/posts/assessment-platform-01-resource-release-task/" rel="">5</a> | <a href="/posts/assessment-platform-02-multi-resource-runtime/" rel="">6</a> | <a href="/posts/assessment-platform-03-draft-preview-publish/" rel="">7</a></p></blockquote>
<p>测评后台同时承载问卷、考试、材料和报告配置。编辑页面需要频繁保存，参与者却必须在整个作答周期内读取同一份题目和评分规则。把资源、发布版本和任务放在一张表里，任何一次后台修改都可能改变进行中的结果，问题发生后也很难回答“这份报告依据了哪个版本”。</p>
<h2 id="三个对象对应三种生命周期">三个对象对应三种生命周期</h2>
<p>资源是可编辑的设计对象，保存标题、FormDSL、评分配置和报告模板。资源可以有多个草稿版本，但草稿不参与正式作答。发布包是审核通过后的不可变快照，绑定一个资源版本、题目快照和报告规则。测评任务引用发布包，运行阶段再附加参与者范围与执行策略。</p>
<div class="mermaid" id="id-2" data-mermaid-definition="Zmxvd2NoYXJ0IFRECiAgICBEcmFmdFvotYTmupDojYnnqL9dIC0tPiBSZXZpZXdb5qCh6aqM5LiO5a6h5qC4XQogICAgUmV2aWV3IC0tPiBSZWxlYXNlW&#43;S4jeWPr&#43;WPmOWPkeW4g&#43;WMhV0KICAgIFJlbGVhc2UgLS0&#43;IFRhc2tb5rWL6K&#43;E5Lu75YqhXQogICAgVGFzayAtLT4gUnVuW&#43;WPguS4juiAhei/kOihjOWunuS&#43;i10KICAgIERyYWZ0IC0tPnzlpI3liLZ8IE5ld0RyYWZ0W&#43;aWsOiNieeov10KICAgIFJlbGVhc2UgLS7npoHmraLkv67mlLkuLT4gUmVsZWFzZQ==">flowchart TD
    Draft[资源草稿] --&gt; Review[校验与审核]
    Review --&gt; Release[不可变发布包]
    Release --&gt; Task[测评任务]
    Task --&gt; Run[参与者运行实例]
    Draft --&gt;|复制| NewDraft[新草稿]
    Release -.禁止修改.-&gt; Release</div><p>核心关系可以简化为：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-text">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">assessment_resource(id)
</span></span><span class="line"><span class="cl">assessment_form_version(resource_id, version_no, form_dsl)
</span></span><span class="line"><span class="cl">assessment_release(id, resource_id, form_version_id, status, checksum)
</span></span><span class="line"><span class="cl">assessment_task(id, release_id, access_mode, starts_at, ends_at)
</span></span><span class="line"><span class="cl">assessment_run(id, task_id, release_id, participant_id, status)</span></span></code></pre></div></div>
<p>运行实例保存 <code>release_id</code>，不能只保存资源 ID。资源后来创建了新版本，旧运行实例仍然通过发布包得到原题目。发布包的 checksum 用来检查快照是否被意外修改，历史版本不在原记录上覆盖。</p>
<h2 id="发布前校验要靠服务端">发布前校验要靠服务端</h2>
<p>保存草稿时可以允许未完成字段，进入发布流程后执行完整 FormDSL 校验：题目标识唯一、选项引用存在、必填规则可解析、跳题条件不形成非法引用、评分维度和报告模板能够对应。前端校验提供反馈，服务端校验决定是否生成发布包。</p>
<p>发布动作在事务中完成：锁定目标资源版本，写入题目快照和配置快照，计算摘要，创建 <code>AssessmentRelease</code>，再把发布状态改为可用。任何一步失败都不应留下“已发布但快照为空”的记录。重复发布请求使用幂等键返回已有发布包。</p>]]></description></item></channel></rss>