From 6f60d651951cd42c3525e69846a19d95ee83ae35 Mon Sep 17 00:00:00 2001 From: chenxing Date: Wed, 28 Oct 2026 11:45:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(app):=20=E5=BA=94=E7=94=A8=E7=A8=B3?= =?UTF-8?q?=E5=AE=9A=E6=9D=A5=E6=BA=90=E4=B8=BB=E4=BD=93=20UUID=20+=20/oap?= =?UTF-8?q?i/app/activated=20=E5=BC=80=E9=80=9A=E5=88=A4=E5=AE=9A=EF=BC=88?= =?UTF-8?q?FL=20=E6=8E=88=E6=9D=83=E9=97=A8=E7=A6=81=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/oapi/AppController.java | 30 +++++++++++++++++++ .../userbase/entity/ApplicationInfo.java | 10 +++++++ .../model/application/AppActivatedVo.java | 25 ++++++++++++++++ .../model/application/ApplicationInfoVo.java | 3 ++ 4 files changed, 68 insertions(+) create mode 100644 src/main/java/com/yunhai/userbase/model/application/AppActivatedVo.java diff --git a/src/main/java/com/yunhai/userbase/controller/oapi/AppController.java b/src/main/java/com/yunhai/userbase/controller/oapi/AppController.java index bdb497a..0bab68e 100644 --- a/src/main/java/com/yunhai/userbase/controller/oapi/AppController.java +++ b/src/main/java/com/yunhai/userbase/controller/oapi/AppController.java @@ -5,11 +5,13 @@ import com.yunhai.platform.utils.enums.IEnum; import com.yunhai.platform.utils.model.BoaAppType; import com.yunhai.userbase.config.Config; import com.yunhai.userbase.entity.ApplicationInfo; +import com.yunhai.userbase.model.application.AppActivatedVo; import com.yunhai.userbase.model.application.AppIconMapVo; import com.yunhai.userbase.model.application.ApplicationInfoVo; import com.yunhai.userbase.service.IAppCategoryService; import com.yunhai.userbase.service.IApplicationInfoService; import com.yunhai.userbase.service.IGroupAuthzService; +import com.yunhai.platform.utils.exception.DException; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -38,6 +40,9 @@ public class AppController { @Autowired private IGroupAuthzService groupAuthzService; + @Autowired + private com.yunhai.userbase.service.ISysGroupService sysGroupService; + /** * 按主键 ID 查询应用信息(带缓存)。
* 与客户端契约 {@code GET /oapi/app/{appId}} 对应;查不到返回 null(HTTP 200 + data:null 信封)。
@@ -158,4 +163,29 @@ public class AppController { public List iconMap() { return applicationInfoService.listIconMap(); } + + /** + * 应用×学校单点开通判定(2026-10-25 T01 最小只读口)。
+ * 权威事实源 = {@code sys_group_app_rel}(组应用开通表,学校组单点、不沿祖先、不并角色模块), + * 与 {@code GET /oapi/app/list-by-school} 同一口径,只是收敛成 boolean 判定—— + * form-center/flow-center 等下游把本端点作为「该应用是否在该校开通」的平台资格第二重证明 + * (fail-closed:本端点不可达时下游必须拒绝,不得降级为"仅验签通过")。
+ * 应用不存在(含已注销)返回 activated=false,不区分"没开通"与"没这应用", + * 避免把应用存在性做成探测面;schoolId 非法(非学校组锚点)按既有 {@code assertSchoolId} + * 口径抛业务错误(code=5)。只读、无敏感字段。 + * + * @param appId 应用主键 ID(必填;稳定主体,非 appKey 凭据索引) + * @param schoolId 学校组 ID(必填) + */ + @GetMapping("activated") + public AppActivatedVo activated(@RequestParam("appId") Integer appId, + @RequestParam("schoolId") Integer schoolId) { + if (appId == null || schoolId == null) { + throw new DException("appId 与 schoolId 必填"); + } + sysGroupService.assertSchoolId(schoolId); + boolean appExists = applicationInfoService.getById(appId) != null; + boolean activated = appExists && groupAuthzService.findAuthorizedAppIds(schoolId).contains(appId); + return new AppActivatedVo(appId, schoolId, activated); + } } diff --git a/src/main/java/com/yunhai/userbase/entity/ApplicationInfo.java b/src/main/java/com/yunhai/userbase/entity/ApplicationInfo.java index dbad556..420e7a5 100644 --- a/src/main/java/com/yunhai/userbase/entity/ApplicationInfo.java +++ b/src/main/java/com/yunhai/userbase/entity/ApplicationInfo.java @@ -26,6 +26,16 @@ import java.util.List; @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) public class ApplicationInfo extends BaseEntity { + /** Immutable application incarnation; never derive it from reusable numeric IDs or credentials. */ + @Column(name = "source_principal_id", unique = true, updatable = false, length = 36) + private String sourcePrincipalId; + + @javax.persistence.PrePersist + void assignSourcePrincipal() { + // Always generate server-side, including when an entity supplied a value. + sourcePrincipalId = java.util.UUID.randomUUID().toString(); + } + /** * 应用名称(页面*必填) */ diff --git a/src/main/java/com/yunhai/userbase/model/application/AppActivatedVo.java b/src/main/java/com/yunhai/userbase/model/application/AppActivatedVo.java new file mode 100644 index 0000000..0ccb066 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/model/application/AppActivatedVo.java @@ -0,0 +1,25 @@ +package com.yunhai.userbase.model.application; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 应用×学校单点开通判定结果(2026-10-25 T01 最小只读口)。 + * 只回判定所需三件,不透出 secret/别名等管理信息;消费方(form-center/flow-center) + * 把它作为「该应用是否在该校开通」的平台事实第二重证明,fail-closed 消费。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppActivatedVo { + + /** 应用主键 ID(稳定主体,与入参一致) */ + private Integer appId; + + /** 学校组 ID(与入参一致) */ + private Integer schoolId; + + /** true=该校单点开通集合包含该应用(sys_group_app_rel 口径,不沿祖先) */ + private boolean activated; +} diff --git a/src/main/java/com/yunhai/userbase/model/application/ApplicationInfoVo.java b/src/main/java/com/yunhai/userbase/model/application/ApplicationInfoVo.java index 23492e6..52c3d53 100644 --- a/src/main/java/com/yunhai/userbase/model/application/ApplicationInfoVo.java +++ b/src/main/java/com/yunhai/userbase/model/application/ApplicationInfoVo.java @@ -12,6 +12,9 @@ import java.util.List; */ @Data public class ApplicationInfoVo { + /** Stable, non-secret application incarnation used for source isolation. */ + private String sourcePrincipalId; + /** * 主键ID。
*/