新增监管端和机构端首页数据
parent
311fbf6946
commit
cc604203c5
|
|
@ -26,6 +26,7 @@ import org.qinan.safetyeval.domain.service.OrgPositionDomainService;
|
||||||
import org.qinan.safetyeval.infrastructure.adapter.ThreadLocalUserInfoAdapter;
|
import org.qinan.safetyeval.infrastructure.adapter.ThreadLocalUserInfoAdapter;
|
||||||
import org.qinan.safetyeval.infrastructure.adapter.auth.AuthUserContextAdapter;
|
import org.qinan.safetyeval.infrastructure.adapter.auth.AuthUserContextAdapter;
|
||||||
import org.qinan.safetyeval.infrastructure.adapter.gbs.GbsUserFacadeClient;
|
import org.qinan.safetyeval.infrastructure.adapter.gbs.GbsUserFacadeClient;
|
||||||
|
import org.qinan.safetyeval.infrastructure.support.UserOrgIdResolver;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
@ -77,6 +78,10 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi {
|
||||||
@Autowired
|
@Autowired
|
||||||
private OrgPersonnelExcelImporter orgPersonnelExcelImporter;
|
private OrgPersonnelExcelImporter orgPersonnelExcelImporter;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Autowired(required = false)
|
||||||
|
private UserOrgIdResolver userOrgIdResolver;
|
||||||
|
|
||||||
@Value("${def.password:a123456}")
|
@Value("${def.password:a123456}")
|
||||||
private String defPassword;
|
private String defPassword;
|
||||||
@Value("${def.publicKey:0402df2195296d4062ac85ad766994d73e871b887e18efb9a9a06b4cebc72372869b7da6c347c129dee2b46a0f279ff066b01c76208c2a052af75977c722a2ccee}")
|
@Value("${def.publicKey:0402df2195296d4062ac85ad766994d73e871b887e18efb9a9a06b4cebc72372869b7da6c347c129dee2b46a0f279ff066b01c76208c2a052af75977c722a2ccee}")
|
||||||
|
|
@ -94,6 +99,10 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi {
|
||||||
public SingleResponse<OrgPersonnelCO> add(OrgPersonnelAddCmd cmd) {
|
public SingleResponse<OrgPersonnelCO> add(OrgPersonnelAddCmd cmd) {
|
||||||
OrgPersonnelEntity entity = orgPersonnelConvertor.toEntity(cmd);
|
OrgPersonnelEntity entity = orgPersonnelConvertor.toEntity(cmd);
|
||||||
OrgPersonnelEntity result = orgPersonnelDomainService.add(entity);
|
OrgPersonnelEntity result = orgPersonnelDomainService.add(entity);
|
||||||
|
// 组织信息变更,清理该用户机构缓存
|
||||||
|
if (userOrgIdResolver != null && result != null) {
|
||||||
|
userOrgIdResolver.evict(result.getId());
|
||||||
|
}
|
||||||
OrgPersonnelCO co = orgPersonnelConvertor.toCO(result);
|
OrgPersonnelCO co = orgPersonnelConvertor.toCO(result);
|
||||||
enrich(co);
|
enrich(co);
|
||||||
UserAddCmd userAddCmd = new UserAddCmd();
|
UserAddCmd userAddCmd = new UserAddCmd();
|
||||||
|
|
@ -134,6 +143,10 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi {
|
||||||
OrgPersonnelEntity entity = orgPersonnelConvertor.toEntity(cmd);
|
OrgPersonnelEntity entity = orgPersonnelConvertor.toEntity(cmd);
|
||||||
entity.setId(cmd.getId());
|
entity.setId(cmd.getId());
|
||||||
OrgPersonnelEntity result = orgPersonnelDomainService.modify(entity);
|
OrgPersonnelEntity result = orgPersonnelDomainService.modify(entity);
|
||||||
|
// 组织信息变更,清理该用户机构缓存
|
||||||
|
if (userOrgIdResolver != null) {
|
||||||
|
userOrgIdResolver.evict(cmd.getId());
|
||||||
|
}
|
||||||
orgPersonnelChangeRecorder.recordChanges(existing, result);
|
orgPersonnelChangeRecorder.recordChanges(existing, result);
|
||||||
// 岗位变更且新岗位已关联 GBS 角色时,同步该人员 GBS 角色
|
// 岗位变更且新岗位已关联 GBS 角色时,同步该人员 GBS 角色
|
||||||
if (result != null && !Objects.equals(
|
if (result != null && !Objects.equals(
|
||||||
|
|
@ -148,6 +161,10 @@ public class OrgPersonnelExecutor implements OrgPersonnelApi {
|
||||||
@Override
|
@Override
|
||||||
public SingleResponse<Void> delete(Long id) {
|
public SingleResponse<Void> delete(Long id) {
|
||||||
orgPersonnelDomainService.delete(id);
|
orgPersonnelDomainService.delete(id);
|
||||||
|
// 组织信息变更,清理该用户机构缓存
|
||||||
|
if (userOrgIdResolver != null) {
|
||||||
|
userOrgIdResolver.evict(id);
|
||||||
|
}
|
||||||
return SingleResponse.success();
|
return SingleResponse.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ import org.qinan.safetyeval.domain.gateway.SafetyMessageGateway;
|
||||||
import org.qinan.safetyeval.domain.query.EvalProjectQuery;
|
import org.qinan.safetyeval.domain.query.EvalProjectQuery;
|
||||||
import org.qinan.safetyeval.domain.query.PageResult;
|
import org.qinan.safetyeval.domain.query.PageResult;
|
||||||
import org.qinan.safetyeval.domain.query.SafetyMessageQuery;
|
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 org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
@ -35,7 +37,8 @@ import java.util.stream.Collectors;
|
||||||
* 机构端首页(驾驶舱)聚合执行器
|
* 机构端首页(驾驶舱)聚合执行器
|
||||||
*
|
*
|
||||||
* <p>全部为<b>新增</b>接口实现,复用既有 Gateway / 节点聚合装配器,不做任何写操作、不改动既有功能。</p>
|
* <p>全部为<b>新增</b>接口实现,复用既有 Gateway / 节点聚合装配器,不做任何写操作、不改动既有功能。</p>
|
||||||
* <p>orgId 取自 {@link ThreadLocalUserInfoAdapter}(机构端请求上下文)。</p>
|
* <p>orgId 取自 {@link InstitutionOrgSupport#resolveCurrentOrgIdByUserId()}:通过当前登录 userId 反查
|
||||||
|
* org_personnel 得到用户真实归属机构,避免使用 token 中租户级 orgId 导致数据查询为空。</p>
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class InstitutionDashboardExecutor implements InstitutionDashboardApi {
|
public class InstitutionDashboardExecutor implements InstitutionDashboardApi {
|
||||||
|
|
@ -52,6 +55,10 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi {
|
||||||
@Resource
|
@Resource
|
||||||
private EvalCustomerGateway evalCustomerGateway;
|
private EvalCustomerGateway evalCustomerGateway;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Autowired(required = false)
|
||||||
|
private InstitutionOrgSupport institutionOrgSupport;
|
||||||
|
|
||||||
/** 节点编码 → 大屏展示名称(待X) */
|
/** 节点编码 → 大屏展示名称(待X) */
|
||||||
private static final LinkedHashMap<String, String> NODE_DISPLAY = new LinkedHashMap<>();
|
private static final LinkedHashMap<String, String> NODE_DISPLAY = new LinkedHashMap<>();
|
||||||
static {
|
static {
|
||||||
|
|
@ -73,7 +80,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SingleResponse<InstitutionDashboardProjectNodeStatsCO> projectNodeStats() {
|
public SingleResponse<InstitutionDashboardProjectNodeStatsCO> projectNodeStats() {
|
||||||
Long orgId = ThreadLocalUserInfoAdapter.get();
|
Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId();
|
||||||
if (orgId == null) {
|
if (orgId == null) {
|
||||||
return SingleResponse.success(new InstitutionDashboardProjectNodeStatsCO());
|
return SingleResponse.success(new InstitutionDashboardProjectNodeStatsCO());
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +136,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SingleResponse<InstitutionDashboardNoticeCO> notices() {
|
public SingleResponse<InstitutionDashboardNoticeCO> notices() {
|
||||||
Long orgId = ThreadLocalUserInfoAdapter.get();
|
Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId();
|
||||||
if (orgId == null) {
|
if (orgId == null) {
|
||||||
return SingleResponse.success(new InstitutionDashboardNoticeCO());
|
return SingleResponse.success(new InstitutionDashboardNoticeCO());
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +151,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SingleResponse<InstitutionDashboardIndustryStatCO> industryStat() {
|
public SingleResponse<InstitutionDashboardIndustryStatCO> industryStat() {
|
||||||
Long orgId = ThreadLocalUserInfoAdapter.get();
|
Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId();
|
||||||
if (orgId == null) {
|
if (orgId == null) {
|
||||||
return SingleResponse.success(new InstitutionDashboardIndustryStatCO());
|
return SingleResponse.success(new InstitutionDashboardIndustryStatCO());
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +184,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SingleResponse<InstitutionDashboardEvalTypeRatioCO> evalTypeRatio() {
|
public SingleResponse<InstitutionDashboardEvalTypeRatioCO> evalTypeRatio() {
|
||||||
Long orgId = ThreadLocalUserInfoAdapter.get();
|
Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId();
|
||||||
if (orgId == null) {
|
if (orgId == null) {
|
||||||
return SingleResponse.success(new InstitutionDashboardEvalTypeRatioCO());
|
return SingleResponse.success(new InstitutionDashboardEvalTypeRatioCO());
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +216,7 @@ public class InstitutionDashboardExecutor implements InstitutionDashboardApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SingleResponse<InstitutionDashboardProjectExecutionCO> projectExecution(Integer limit) {
|
public SingleResponse<InstitutionDashboardProjectExecutionCO> projectExecution(Integer limit) {
|
||||||
Long orgId = ThreadLocalUserInfoAdapter.get();
|
Long orgId = institutionOrgSupport.resolveCurrentOrgIdByUserId();
|
||||||
if (orgId == null) {
|
if (orgId == null) {
|
||||||
return SingleResponse.success(new InstitutionDashboardProjectExecutionCO());
|
return SingleResponse.success(new InstitutionDashboardProjectExecutionCO());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import org.qinan.safetyeval.domain.exception.BizException;
|
||||||
import org.qinan.safetyeval.domain.exception.ErrorCode;
|
import org.qinan.safetyeval.domain.exception.ErrorCode;
|
||||||
import org.qinan.safetyeval.domain.gateway.OrgInfoGateway;
|
import org.qinan.safetyeval.domain.gateway.OrgInfoGateway;
|
||||||
import org.qinan.safetyeval.infrastructure.adapter.ThreadLocalUserInfoAdapter;
|
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.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -16,6 +18,15 @@ public class InstitutionOrgSupport {
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private OrgInfoGateway orgInfoGateway;
|
private OrgInfoGateway orgInfoGateway;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Autowired(required = false)
|
||||||
|
private UserOrgIdResolver userOrgIdResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析当前机构 orgId(沿用既有逻辑:优先请求头 orgInfoId,兜底 ThreadLocal)。
|
||||||
|
* <p>注意:该值可能是租户级 ID,未必等于机构数据归属的 org_id,机构端数据统计类接口请改用
|
||||||
|
* {@link #resolveCurrentOrgIdByUserId()}。</p>
|
||||||
|
*/
|
||||||
public Long resolveCurrentOrgId() {
|
public Long resolveCurrentOrgId() {
|
||||||
Long orgId = ThreadLocalUserInfoAdapter.getOrgId();
|
Long orgId = ThreadLocalUserInfoAdapter.getOrgId();
|
||||||
if (orgId == null) {
|
if (orgId == null) {
|
||||||
|
|
@ -24,6 +35,30 @@ public class InstitutionOrgSupport {
|
||||||
return orgId;
|
return orgId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过当前登录 userId 反查用户所属机构 orgId(不使用 token 的 orgId)。
|
||||||
|
* <p>机构端首页等数据统计接口统一使用该方法,保证按用户真实归属机构过滤数据。
|
||||||
|
* 结果带本地缓存,组织信息变更后可调用 {@link #evictCurrentUserOrgId()} 清理。</p>
|
||||||
|
*
|
||||||
|
* @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) {
|
public void assertOrgAccess(Long resourceOrgId, ErrorCode notFoundCode) {
|
||||||
if (resourceOrgId == null || !resolveCurrentOrgId().equals(resourceOrgId)) {
|
if (resourceOrgId == null || !resolveCurrentOrgId().equals(resourceOrgId)) {
|
||||||
throw new BizException(notFoundCode);
|
throw new BizException(notFoundCode);
|
||||||
|
|
|
||||||
|
|
@ -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 反查用户在项目中的组织(机构)信息,并提供本地缓存与清理能力。
|
||||||
|
*
|
||||||
|
* <p><b>背景</b>:机构端数据归属的 {@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} 得到用户所属真实机构。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>缓存键为 {@code userId},默认 5 分钟过期;未命中机构(非机构人员)短暂缓存 30 秒,
|
||||||
|
* 避免对不存在的用户反复查库。组织信息发生新增/修改/删除时,应调用 {@link #evict(Long)} 或
|
||||||
|
* {@link #evictAll()} 清理,保证即时生效。</p>
|
||||||
|
*/
|
||||||
|
@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<Long, CacheEntry> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue