From cc604203c57d40b85eae6f3c01de1763b9eee65d Mon Sep 17 00:00:00 2001 From: huwei <3313749341@qq.com> Date: Mon, 3 Aug 2026 15:48:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=9B=91=E7=AE=A1=E7=AB=AF?= =?UTF-8?q?=E5=92=8C=E6=9C=BA=E6=9E=84=E7=AB=AF=E9=A6=96=E9=A1=B5=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/executor/OrgPersonnelExecutor.java | 17 ++++ .../InstitutionDashboardExecutor.java | 21 +++-- .../support/InstitutionOrgSupport.java | 35 ++++++++ .../support/UserOrgIdResolver.java | 87 +++++++++++++++++++ 4 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/UserOrgIdResolver.java diff --git a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java index 29c707d..22a1977 100644 --- a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java +++ b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/OrgPersonnelExecutor.java @@ -26,6 +26,7 @@ import org.qinan.safetyeval.domain.service.OrgPositionDomainService; import org.qinan.safetyeval.infrastructure.adapter.ThreadLocalUserInfoAdapter; import org.qinan.safetyeval.infrastructure.adapter.auth.AuthUserContextAdapter; import org.qinan.safetyeval.infrastructure.adapter.gbs.GbsUserFacadeClient; +import org.qinan.safetyeval.infrastructure.support.UserOrgIdResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -77,6 +78,10 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi { @Autowired private OrgPersonnelExcelImporter orgPersonnelExcelImporter; + @Lazy + @Autowired(required = false) + private UserOrgIdResolver userOrgIdResolver; + @Value("${def.password:a123456}") private String defPassword; @Value("${def.publicKey:0402df2195296d4062ac85ad766994d73e871b887e18efb9a9a06b4cebc72372869b7da6c347c129dee2b46a0f279ff066b01c76208c2a052af75977c722a2ccee}") @@ -94,6 +99,10 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi { public SingleResponse add(OrgPersonnelAddCmd cmd) { OrgPersonnelEntity entity = orgPersonnelConvertor.toEntity(cmd); OrgPersonnelEntity result = orgPersonnelDomainService.add(entity); + // 组织信息变更,清理该用户机构缓存 + if (userOrgIdResolver != null && result != null) { + userOrgIdResolver.evict(result.getId()); + } OrgPersonnelCO co = orgPersonnelConvertor.toCO(result); enrich(co); UserAddCmd userAddCmd = new UserAddCmd(); @@ -134,6 +143,10 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi { OrgPersonnelEntity entity = orgPersonnelConvertor.toEntity(cmd); entity.setId(cmd.getId()); OrgPersonnelEntity result = orgPersonnelDomainService.modify(entity); + // 组织信息变更,清理该用户机构缓存 + if (userOrgIdResolver != null) { + userOrgIdResolver.evict(cmd.getId()); + } orgPersonnelChangeRecorder.recordChanges(existing, result); // 岗位变更且新岗位已关联 GBS 角色时,同步该人员 GBS 角色 if (result != null && !Objects.equals( @@ -148,6 +161,10 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi { @Override public SingleResponse delete(Long id) { orgPersonnelDomainService.delete(id); + // 组织信息变更,清理该用户机构缓存 + if (userOrgIdResolver != null) { + userOrgIdResolver.evict(id); + } return SingleResponse.success(); } diff --git a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/institution/InstitutionDashboardExecutor.java b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/institution/InstitutionDashboardExecutor.java index 6236967..aa71675 100644 --- a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/institution/InstitutionDashboardExecutor.java +++ b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/executor/institution/InstitutionDashboardExecutor.java @@ -20,7 +20,9 @@ import org.qinan.safetyeval.domain.gateway.SafetyMessageGateway; import org.qinan.safetyeval.domain.query.EvalProjectQuery; import org.qinan.safetyeval.domain.query.PageResult; import org.qinan.safetyeval.domain.query.SafetyMessageQuery; -import org.qinan.safetyeval.infrastructure.adapter.ThreadLocalUserInfoAdapter; +import org.qinan.safetyeval.infrastructure.support.InstitutionOrgSupport; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -35,7 +37,8 @@ import java.util.stream.Collectors; * 机构端首页(驾驶舱)聚合执行器 * *

全部为新增接口实现,复用既有 Gateway / 节点聚合装配器,不做任何写操作、不改动既有功能。

- *

orgId 取自 {@link ThreadLocalUserInfoAdapter}(机构端请求上下文)。

+ *

orgId 取自 {@link InstitutionOrgSupport#resolveCurrentOrgIdByUserId()}:通过当前登录 userId 反查 + * org_personnel 得到用户真实归属机构,避免使用 token 中租户级 orgId 导致数据查询为空。

*/ @Component public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @@ -52,6 +55,10 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @Resource private EvalCustomerGateway evalCustomerGateway; + @Lazy + @Autowired(required = false) + private InstitutionOrgSupport institutionOrgSupport; + /** 节点编码 → 大屏展示名称(待X) */ private static final LinkedHashMap NODE_DISPLAY = new LinkedHashMap<>(); static { @@ -73,7 +80,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @Override public SingleResponse projectNodeStats() { - Long orgId = ThreadLocalUserInfoAdapter.get(); + Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId(); if (orgId == null) { return SingleResponse.success(new InstitutionDashboardProjectNodeStatsCO()); } @@ -129,7 +136,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @Override public SingleResponse notices() { - Long orgId = ThreadLocalUserInfoAdapter.get(); + Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId(); if (orgId == null) { return SingleResponse.success(new InstitutionDashboardNoticeCO()); } @@ -144,7 +151,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @Override public SingleResponse industryStat() { - Long orgId = ThreadLocalUserInfoAdapter.get(); + Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId(); if (orgId == null) { return SingleResponse.success(new InstitutionDashboardIndustryStatCO()); } @@ -177,7 +184,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @Override public SingleResponse evalTypeRatio() { - Long orgId = ThreadLocalUserInfoAdapter.get(); + Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId(); if (orgId == null) { return SingleResponse.success(new InstitutionDashboardEvalTypeRatioCO()); } @@ -209,7 +216,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi { @Override public SingleResponse projectExecution(Integer limit) { - Long orgId = ThreadLocalUserInfoAdapter.get(); + Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId(); if (orgId == null) { return SingleResponse.success(new InstitutionDashboardProjectExecutionCO()); } diff --git a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/InstitutionOrgSupport.java b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/InstitutionOrgSupport.java index 2a99bef..85c164f 100644 --- a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/InstitutionOrgSupport.java +++ b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/InstitutionOrgSupport.java @@ -4,7 +4,9 @@ import org.qinan.safetyeval.domain.exception.BizException; import org.qinan.safetyeval.domain.exception.ErrorCode; import org.qinan.safetyeval.domain.gateway.OrgInfoGateway; import org.qinan.safetyeval.infrastructure.adapter.ThreadLocalUserInfoAdapter; +import org.qinan.safetyeval.infrastructure.adapter.auth.AuthUserContextAdapter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -16,6 +18,15 @@ public class InstitutionOrgSupport { @Autowired(required = false) private OrgInfoGateway orgInfoGateway; + @Lazy + @Autowired(required = false) + private UserOrgIdResolver userOrgIdResolver; + + /** + * 解析当前机构 orgId(沿用既有逻辑:优先请求头 orgInfoId,兜底 ThreadLocal)。 + *

注意:该值可能是租户级 ID,未必等于机构数据归属的 org_id,机构端数据统计类接口请改用 + * {@link #resolveCurrentOrgIdByUserId()}。

+ */ public Long resolveCurrentOrgId() { Long orgId = ThreadLocalUserInfoAdapter.getOrgId(); if (orgId == null) { @@ -24,6 +35,30 @@ public class InstitutionOrgSupport { return orgId; } + /** + * 通过当前登录 userId 反查用户所属机构 orgId(不使用 token 的 orgId)。 + *

机构端首页等数据统计接口统一使用该方法,保证按用户真实归属机构过滤数据。 + * 结果带本地缓存,组织信息变更后可调用 {@link #evictCurrentUserOrgId()} 清理。

+ * + * @return 当前用户所属机构 orgId;未登录或非机构人员返回 null + */ + public Long resolveCurrentOrgIdByUserId() { + if (userOrgIdResolver == null) { + return null; + } + Long userId = AuthUserContextAdapter.getCurrentUserId(); + return userOrgIdResolver.resolveOrgIdByUserId(userId); + } + + /** + * 清理当前登录用户的机构缓存(组织信息变更后调用)。 + */ + public void evictCurrentUserOrgId() { + if (userOrgIdResolver != null) { + userOrgIdResolver.evict(AuthUserContextAdapter.getCurrentUserId()); + } + } + public void assertOrgAccess(Long resourceOrgId, ErrorCode notFoundCode) { if (resourceOrgId == null || !resolveCurrentOrgId().equals(resourceOrgId)) { throw new BizException(notFoundCode); diff --git a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/UserOrgIdResolver.java b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/UserOrgIdResolver.java new file mode 100644 index 0000000..97e49aa --- /dev/null +++ b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/support/UserOrgIdResolver.java @@ -0,0 +1,87 @@ +package org.qinan.safetyeval.infrastructure.support; + +import org.qinan.safetyeval.infrastructure.dataobject.OrgPersonnelDO; +import org.qinan.safetyeval.infrastructure.mapper.OrgPersonnelMapper; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 通用组件:根据 userId 反查用户在项目中的组织(机构)信息,并提供本地缓存与清理能力。 + * + *

背景:机构端数据归属的 {@code org_id} 是机构信息表主键({@code org_info.id}), + * 与 SSO token 中的 {@code orgId}(租户级 ID)不一致。若直接使用 token 的 orgId 过滤 + * {@code eval_project.org_id} 永远匹配不上,导致机构端首页查不到数据。 + * 因此改为:通过当前登录 {@code userId} 反查 {@code org_personnel.org_id} 得到用户所属真实机构。 + *

+ * + *

缓存键为 {@code userId},默认 5 分钟过期;未命中机构(非机构人员)短暂缓存 30 秒, + * 避免对不存在的用户反复查库。组织信息发生新增/修改/删除时,应调用 {@link #evict(Long)} 或 + * {@link #evictAll()} 清理,保证即时生效。

+ */ +@Component +public class UserOrgIdResolver { + + /** 组织信息有效缓存时长(毫秒) */ + private static final long ORG_TTL_MILLIS = 5 * 60 * 1000L; + /** 未命中机构(非机构人员)的缓存时长(毫秒) */ + private static final long MISS_TTL_MILLIS = 30 * 1000L; + + @Resource + private OrgPersonnelMapper orgPersonnelMapper; + + private final Map cache = new ConcurrentHashMap<>(); + + /** + * 根据 userId 反查用户所属机构 orgId。 + * + * @param userId 当前登录用户 id(org_personnel.id) + * @return 用户所属机构 orgId;用户不存在或非机构人员返回 null + */ + public Long resolveOrgIdByUserId(Long userId) { + if (userId == null) { + return null; + } + long now = System.currentTimeMillis(); + CacheEntry entry = cache.get(userId); + if (entry != null && entry.expireAt > now) { + return entry.orgId; + } + Long orgId = queryOrgIdByUserId(userId); + cache.put(userId, new CacheEntry(orgId, now + (orgId == null ? MISS_TTL_MILLIS : ORG_TTL_MILLIS))); + return orgId; + } + + /** + * 清理单个用户的机构缓存(组织信息发生增删改后调用)。 + */ + public void evict(Long userId) { + if (userId != null) { + cache.remove(userId); + } + } + + /** + * 清理全部机构缓存。 + */ + public void evictAll() { + cache.clear(); + } + + private Long queryOrgIdByUserId(Long userId) { + OrgPersonnelDO personnel = orgPersonnelMapper.selectById(userId); + return personnel == null ? null : personnel.getOrgId(); + } + + private static class CacheEntry { + private final Long orgId; + private final long expireAt; + + CacheEntry(Long orgId, long expireAt) { + this.orgId = orgId; + this.expireAt = expireAt; + } + } +}