8 小时以前 9bad721754fe8bbe2e5f459d0706e0fefac569f3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package cn.iocoder.yudao.module.qcreport.dal.dataobject.version;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
 
import java.util.List;
import java.util.Map;
 
/**
 * 报告模板 Schema —— 设计器产物,也是报告出件的唯一输入。
 * <p>
 * 模板不保存裸 HTML。保存的是「画布数据 + 业务语义」,由渲染引擎据此生成 HTML,
 * 这样同一份模板在不同渲染环境下产物一致,也才有可能做服务端 Sanitization。
 *
 * <h3>字段分工</h3>
 * <table>
 *   <tr><td>{@link #grapes}</td><td>GrapesJS 原始项目数据。设计器靠它无损还原画布,<b>渲染引擎也读它</b>(正文结构在这里)</td></tr>
 *   <tr><td>{@link #components}</td><td>质量组件业务语义清单。渲染不读,供设计器定位组件、供后续 AI 生成模板时理解结构</td></tr>
 *   <tr><td>{@link #rules}</td><td>判定规则。渲染前由判定器求值,决定每项 PASS/FAIL 与报告结论</td></tr>
 *   <tr><td>{@link #page}</td><td>纸张与页边距,渲染时换算成 CSS {@code @page}</td></tr>
 * </table>
 *
 * <h3>语义层是<strong>有意的子集</strong>(重要)</h3>
 * {@link #components} 只表达「用已注册的质量组件搭出来的扁平结构」,
 * <b>不表达任意 HTML</b>——用户手工拖进画布的裸表格、自定义 div 不在其内。
 * 这是有意收窄的边界,不是实现疏漏:有这条边界,AI 生成模板时才能被约束成
 * 「只能用已注册组件当积木」,而不是吐一段谁也不敢渲染的 HTML。
 * 任何「给语义层加个万能 customHtml 字段」的改动都在拆这条边界,需要先讨论。
 *
 * <h3>兼容性</h3>
 * <p>
 * {@code schema_json} 列里已经存着含 1.0 字段(dataSources / bindings / styles)的历史 JSON,
 * 所以这里显式标注 {@link JsonIgnoreProperties} 容错。
 * <p>
 * 说明一句,免得后人以为它必不可少:<b>去掉这个注解,兼容性测试也是绿的</b>——
 * 读库那条链路用的 {@code Jackson3TypeHandler} 自己 new 的 ObjectMapper,
 * 而 Jackson 3 的 {@code FAIL_ON_UNKNOWN_PROPERTIES} 默认就是关的。
 * 真正需要它防的是另外两条:Spring 反序列化请求体时用的 ObjectMapper(配置不受本模块控制),
 * 以及将来有人给类型处理器换一个更严格的 Mapper。留着是把意图写明,不是摆设。
 *
 * <h3>版本历史</h3>
 * <ul>
 *   <li><b>1.0</b>:初版。含 dataSources / bindings / styles 三个字段,但它们始终是空数组,
 *       既无人写入也无人读取,属死数据。</li>
 *   <li><b>1.1</b>:删除上述三个死字段,{@code components} 由 {@code Map} 改为强类型
 *       {@link QualityComponentNode}。因死字段原本就恒为空数组,1.0 → 1.1 无需数据迁移。</li>
 * </ul>
 */
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ReportTemplateSchema {
 
    /**
     * 当前 Schema 版本号。
     * <p>
     * 与前端 {@code designer/constants.ts} 的 {@code SCHEMA_VERSION} 必须一致:
     * 两端各写一份是历史原因(后端不参与设计器构建),改这里就要同步改那边。
     */
    public static final String SCHEMA_VERSION = "1.1";
 
    /**
     * Schema 版本,用于后续兼容升级
     */
    private String schemaVersion;
    /**
     * 纸张与页边距配置
     */
    private Page page;
    /**
     * GrapesJS 原始画布数据(保留以便设计器无损还原)
     */
    private Map<String, Object> grapes;
    /**
     * 质量组件业务语义清单,按文档顺序排列
     */
    private List<QualityComponentNode> components;
    /**
     * 判定规则定义。
     * <p>
     * 元素形状为 {@code {id, name, scope, expression, enabled}},由
     * {@code QualityRuleDefinition.from(Map)} 宽松读取——历史数据里可能有缺字段的规则,
     * 读不动的跳过而不是让整份模板打不开。
     */
    private List<Map<String, Object>> rules;
 
    /**
     * 纸张与页边距配置
     */
    @Data
    public static class Page {
 
        /**
         * 纸张尺寸:A3 / A4 / A5 / Letter
         */
        private String size;
        /**
         * 纸张方向:portrait / landscape
         */
        private String orientation;
        /**
         * 页边距,单位 mm
         */
        private Margin margin;
 
    }
 
    /**
     * 页边距,单位 mm
     */
    @Data
    public static class Margin {
 
        private Double top;
        private Double right;
        private Double bottom;
        private Double left;
 
    }
 
    /**
     * 画布上的一个质量组件节点。
     * <p>
     * 只记「是谁、在哪、什么属性」,不记子节点——画布的嵌套结构以 {@link #grapes} 为准,
     * 这里再存一份树就出现两个真相来源,迟早对不上。
     */
    @Data
    public static class QualityComponentNode {
 
        /** 画布内组件 id */
        private String id;
        /** 组件在文档中的顺序,供绑定与定位使用 */
        private Integer index;
        /** 组件类型,即画布节点上的 data-quality-type */
        private String qualityType;
        /**
         * 组件的业务属性。
         * <p>
         * 取值统一是 {@code data-qc-*} / {@code data-quality-type} 这类字符串属性,
         * 用 Object 是因为 GrapesJS 的 getAttributes() 原样带出,可能有非字符串值。
         */
        private Map<String, Object> attributes;
 
    }
 
}