feat(app): 应用稳定来源主体 UUID + /oapi/app/activated 开通判定(FL 授权门禁依赖)
build / build-push (push) Successful in 2m10s
build / build-push (push) Successful in 2m10s
This commit is contained in:
@@ -5,11 +5,13 @@ import com.yunhai.platform.utils.enums.IEnum;
|
|||||||
import com.yunhai.platform.utils.model.BoaAppType;
|
import com.yunhai.platform.utils.model.BoaAppType;
|
||||||
import com.yunhai.userbase.config.Config;
|
import com.yunhai.userbase.config.Config;
|
||||||
import com.yunhai.userbase.entity.ApplicationInfo;
|
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.AppIconMapVo;
|
||||||
import com.yunhai.userbase.model.application.ApplicationInfoVo;
|
import com.yunhai.userbase.model.application.ApplicationInfoVo;
|
||||||
import com.yunhai.userbase.service.IAppCategoryService;
|
import com.yunhai.userbase.service.IAppCategoryService;
|
||||||
import com.yunhai.userbase.service.IApplicationInfoService;
|
import com.yunhai.userbase.service.IApplicationInfoService;
|
||||||
import com.yunhai.userbase.service.IGroupAuthzService;
|
import com.yunhai.userbase.service.IGroupAuthzService;
|
||||||
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -38,6 +40,9 @@ public class AppController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IGroupAuthzService groupAuthzService;
|
private IGroupAuthzService groupAuthzService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.yunhai.userbase.service.ISysGroupService sysGroupService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按主键 ID 查询应用信息(带缓存)。<br>
|
* 按主键 ID 查询应用信息(带缓存)。<br>
|
||||||
* 与客户端契约 {@code GET /oapi/app/{appId}} 对应;查不到返回 null(HTTP 200 + data:null 信封)。<br>
|
* 与客户端契约 {@code GET /oapi/app/{appId}} 对应;查不到返回 null(HTTP 200 + data:null 信封)。<br>
|
||||||
@@ -158,4 +163,29 @@ public class AppController {
|
|||||||
public List<AppIconMapVo> iconMap() {
|
public List<AppIconMapVo> iconMap() {
|
||||||
return applicationInfoService.listIconMap();
|
return applicationInfoService.listIconMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用×学校单点开通判定(2026-10-25 T01 最小只读口)。<br>
|
||||||
|
* 权威事实源 = {@code sys_group_app_rel}(组应用开通表,学校组单点、不沿祖先、不并角色模块),
|
||||||
|
* 与 {@code GET /oapi/app/list-by-school} 同一口径,只是收敛成 boolean 判定——
|
||||||
|
* form-center/flow-center 等下游把本端点作为「该应用是否在该校开通」的平台资格第二重证明
|
||||||
|
* (fail-closed:本端点不可达时下游必须拒绝,不得降级为"仅验签通过")。<br>
|
||||||
|
* 应用不存在(含已注销)返回 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,16 @@ import java.util.List;
|
|||||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||||
public class ApplicationInfo extends BaseEntity {
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用名称(页面*必填)
|
* 应用名称(页面*必填)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -12,6 +12,9 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class ApplicationInfoVo {
|
public class ApplicationInfoVo {
|
||||||
|
/** Stable, non-secret application incarnation used for source isolation. */
|
||||||
|
private String sourcePrincipalId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主键ID。<br>
|
* 主键ID。<br>
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user