feat: 表单一期放行扩到 11 类(+checkbox/multiselect/phone/image)+ 契约审计修复
build / build-push (push) Successful in 2m9s
build / build-push (push) Successful in 2m9s
- FormValidator:PHASE1_TYPES 加 checkbox/multiselect/phone/image;options 必填口径扩到 select/radio/checkbox/multiselect(4 类);validateValue 补多选(值须在 options 内)分支, image 并入 upload 的附件数组通道 - 审计修复:maxFiles 上限判定改为 maxFiles>0(缺省或 0=不限),与前端设计器「留空不限」 及渲染器口径对齐——原实现把 0 当上限,会让 maxFiles:0 的表单任何附件都被拒 - ServiceForm 注释纠偏:契约类名 FormValidator(原误写 FormSchemaValidator)、一期 11 类 - 测试:FormValidatorTest +6(新 4 类 schema、多分组计数、options 必填、多选越界、phone pattern/maxLength、image maxFiles/非数组、precision 非白名单)、ServiceFormServiceTest +4 (冻结行不可变契约:带已发布版本 id 被拒 / 不传 id 按 code 升版出草稿 / 草稿就地更新) 验证:mvn test 102/102 绿(基线 92)。
This commit is contained in:
@@ -14,7 +14,7 @@ import java.time.LocalDateTime;
|
||||
* 自定义表单定义(V2.0 Part B.2.3,自流程中心 flw_form 平移——表单能力归办事大厅)。
|
||||
* 行不可变原则:已发布版本不可改,编辑已发布表单 = 克隆出新草稿版本(version+1)。
|
||||
* 事项经 form_code 绑定,发起时取**最新已发布版本**并快照 form_version。
|
||||
* schema_json 契约见 FormSchemaValidator(14 类命名冻结、声明式校验白名单、groups 分组)。
|
||||
* schema_json 契约见 FormValidator(14 类命名冻结、一期放行 11 类、声明式校验白名单、groups 分组)。
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -46,7 +46,7 @@ public class ServiceForm extends BaseEntity {
|
||||
@Column(name = "status", nullable = false)
|
||||
private Integer status = 0;
|
||||
|
||||
/** 表单结构 JSON({"groups":[{title,fields:[…]}]};M1 仅放行 7 类控件) */
|
||||
/** 表单结构 JSON({"groups":[{title,fields:[…]}]};一期放行 11 类控件) */
|
||||
@Column(name = "schema_json", nullable = false, columnDefinition = "LONGTEXT")
|
||||
private String schemaJson;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 表单 schema 契约校验 + 提交数据强校验(V2.0 Part B.3;契约自流程中心概览 v1.2 §4.2 平移)。
|
||||
* 纪律:类型用字符串 key(一期 7 类 / 14 类命名冻结);校验只用声明式白名单
|
||||
* 纪律:类型用字符串 key(14 类命名一次冻结,一期放行 11 类);校验只用声明式白名单
|
||||
* required/pattern/min/max/maxLength;严禁存库 JS + eval(旧 CustomForm 反面先例)。
|
||||
*/
|
||||
public final class FormValidator {
|
||||
@@ -27,9 +27,10 @@ public final class FormValidator {
|
||||
"text", "textarea", "number", "date", "select", "radio", "upload",
|
||||
"checkbox", "multiselect", "phone", "address", "image", "user", "users"));
|
||||
|
||||
/** 一期启用 7 类 */
|
||||
/** 一期启用 11 类(设计器迁移后放行 checkbox/multiselect/phone/image;address/user/users 二期) */
|
||||
public static final Set<String> PHASE1_TYPES = new HashSet<>(Arrays.asList(
|
||||
"text", "textarea", "number", "date", "select", "radio", "upload"));
|
||||
"text", "textarea", "number", "date", "select", "radio", "upload",
|
||||
"checkbox", "multiselect", "phone", "image"));
|
||||
|
||||
/** 声明式校验白名单属性(其余属性一律拒绝,防契约外语义渗入) */
|
||||
private static final Set<String> ALLOWED_PROPS = new HashSet<>(Arrays.asList(
|
||||
@@ -113,10 +114,12 @@ public final class FormValidator {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "字段 pattern 不可编译(字段 " + key + ")");
|
||||
}
|
||||
}
|
||||
if ("select".equals(type) || "radio".equals(type)) {
|
||||
if ("select".equals(type) || "radio".equals(type)
|
||||
|| "checkbox".equals(type) || "multiselect".equals(type)) {
|
||||
JsonNode options = field.get("options");
|
||||
if (options == null || !options.isArray() || options.size() == 0) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "select/radio 必须提供 options(字段 " + key + ")");
|
||||
throw new BizException(ErrorCode.PARAM_ERROR,
|
||||
"select/radio/checkbox/multiselect 必须提供 options(字段 " + key + ")");
|
||||
}
|
||||
}
|
||||
Integer min = intVal(field, "min");
|
||||
@@ -210,16 +213,39 @@ public final class FormValidator {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "upload": {
|
||||
case "upload":
|
||||
case "image": {
|
||||
if (!value.isArray()) {
|
||||
throw fail(field, key, "必须是附件数组");
|
||||
throw fail(field, key, "必须是附件/图片数组");
|
||||
}
|
||||
// maxFiles 缺省或 ≤0 = 不限(与设计器属性面板、渲染器的「留空/0 不限」同口径)
|
||||
Integer maxFiles = intVal(field, "maxFiles");
|
||||
if (maxFiles != null && value.size() > maxFiles) {
|
||||
if (maxFiles != null && maxFiles > 0 && value.size() > maxFiles) {
|
||||
throw fail(field, key, "附件数超过上限 " + maxFiles);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "checkbox":
|
||||
case "multiselect": {
|
||||
if (!value.isArray()) {
|
||||
throw fail(field, key, "必须是多选数组");
|
||||
}
|
||||
JsonNode options = field.get("options");
|
||||
boolean hit;
|
||||
for (JsonNode v : value) {
|
||||
hit = false;
|
||||
for (JsonNode option : options) {
|
||||
if (option.asText().equals(v.asText())) {
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hit) {
|
||||
throw fail(field, key, "选项值不在候选列表内");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
String v = value.asText();
|
||||
Integer maxLength = intVal(field, "maxLength");
|
||||
|
||||
Reference in New Issue
Block a user