diff --git a/src/main/java/com/yunhai/userbase/model/application/ApplicationInfoRequest.java b/src/main/java/com/yunhai/userbase/model/application/ApplicationInfoRequest.java
index db3c4c0..3e0167b 100644
--- a/src/main/java/com/yunhai/userbase/model/application/ApplicationInfoRequest.java
+++ b/src/main/java/com/yunhai/userbase/model/application/ApplicationInfoRequest.java
@@ -36,18 +36,18 @@ public class ApplicationInfoRequest {
private Integer categoryId;
/**
- * 技术标识(= projman app.name,如 notice),全库唯一,新增必填。
- * 编辑不强制(部分提交场景不回传则保持原值);值域为引用键,改名须盘点引用。
+ * 技术标识(= projman app.name,如 notice),全库唯一,新增与编辑均必填。
+ * 编辑必填(2026-09-28 拍板):app_code 是应用身份锚点(消息 jump_app/服务目录键),不许置空;
+ * 存量 null 历史行不追溯,但编辑提交时必须补填。值域为引用键,改名须盘点引用。
*/
- @NotBlank(groups = ValidationGroups.Create.class, message = "技术标识不能为空")
+ @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "技术标识不能为空")
@Size(max = 32, message = "技术标识长度不能超过32")
private String appCode;
/**
- * 应用性质:BASE=基石 / BUSINESS=业务,新增必填。
- * 编辑不强制(不回传则保持原值);学校模式覆盖线不涉及本字段。
+ * 应用性质:BASE=基石 / BUSINESS=业务,新增与编辑均必填(2026-09-28 拍板,存量 null 行不追溯)。
*/
- @NotBlank(groups = ValidationGroups.Create.class, message = "应用性质不能为空")
+ @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "应用性质不能为空")
@Size(max = 16, message = "应用性质长度不能超过16")
private String appNature;
diff --git a/src/test/java/com/yunhai/userbase/model/application/ApplicationInfoRequestValidationTest.java b/src/test/java/com/yunhai/userbase/model/application/ApplicationInfoRequestValidationTest.java
new file mode 100644
index 0000000..98c8410
--- /dev/null
+++ b/src/test/java/com/yunhai/userbase/model/application/ApplicationInfoRequestValidationTest.java
@@ -0,0 +1,83 @@
+package com.yunhai.userbase.model.application;
+
+import com.yunhai.userbase.common.validation.ValidationGroups;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * app_code / app_nature 编辑必填校验组单测(消息方案 C5,2026-09-28 拍板:新增/编辑均必填,
+ * 存量 null 历史行不追溯)。Create 组此前已必填;本测试冻结 Update 组同口径——
+ * 编辑提交置空 → 校验失败;带值 → 通过。学校模式覆盖线由前端分支原样回传原值满足校验
+ * (后端 editAppForSchool 只读 name/desc/pcIcon,其余字段忽略)。
+ */
+@DisplayName("ApplicationInfoRequest - app_code/app_nature 新增与编辑均必填")
+class ApplicationInfoRequestValidationTest {
+
+ private static Validator validator;
+
+ @BeforeAll
+ static void setUp() {
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ validator = factory.getValidator();
+ }
+
+ private ApplicationInfoRequest validRequest() {
+ ApplicationInfoRequest request = new ApplicationInfoRequest();
+ request.setId(100011);
+ request.setName("流程中心");
+ request.setCategoryId(2);
+ request.setAppCode("flow-center");
+ request.setAppNature("BUSINESS");
+ return request;
+ }
+
+ private static boolean hasViolation(Set> violations, String field) {
+ return violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals(field));
+ }
+
+ @Test
+ void edit_空appCode_校验失败() {
+ ApplicationInfoRequest request = validRequest();
+ request.setAppCode(" ");
+ Set> violations =
+ validator.validate(request, ValidationGroups.Update.class);
+ assertTrue(hasViolation(violations, "appCode"), "编辑时技术标识置空必须被 Update 组拦截");
+ }
+
+ @Test
+ void edit_空appNature_校验失败() {
+ ApplicationInfoRequest request = validRequest();
+ request.setAppNature(null);
+ Set> violations =
+ validator.validate(request, ValidationGroups.Update.class);
+ assertTrue(hasViolation(violations, "appNature"), "编辑时应用性质置空必须被 Update 组拦截");
+ }
+
+ @Test
+ void edit_带appCodeAppNature_通过() {
+ Set> violations =
+ validator.validate(validRequest(), ValidationGroups.Update.class);
+ assertFalse(hasViolation(violations, "appCode"));
+ assertFalse(hasViolation(violations, "appNature"));
+ }
+
+ @Test
+ void create_空appCode_校验失败_口径不变() {
+ ApplicationInfoRequest request = validRequest();
+ request.setId(null);
+ request.setAppCode(null);
+ Set> violations =
+ validator.validate(request, ValidationGroups.Create.class);
+ assertTrue(hasViolation(violations, "appCode"), "新增必填为既有口径,防回归");
+ }
+}