Browse Source

feat: 新增中恒token登录后的“动环”相关角色同步

feature/agg
yinhuaiwei 2 weeks ago
parent
commit
932d5b0824
11 changed files with 228 additions and 0 deletions
  1. +5
    -0
      inspect-admin/src/main/java/com/inspect/admin/feign/RemoteExtUserService.java
  2. +5
    -0
      inspect-admin/src/main/java/com/inspect/admin/feign/factory/RemoteExtUserFallbackFactory.java
  3. +19
    -0
      inspect-admin/src/main/java/com/inspect/admin/service/JoinbrightLoginService.java
  4. +5
    -0
      inspect-management/src/main/java/com/inspect/system/client/ExtAuthClient.java
  5. +18
    -0
      inspect-management/src/main/java/com/inspect/system/controller/ExtUserController.java
  6. +18
    -0
      inspect-management/src/main/java/com/inspect/system/domain/ext/ExtRole.java
  7. +24
    -0
      inspect-management/src/main/java/com/inspect/system/domain/ext/RoleSyncResult.java
  8. +8
    -0
      inspect-management/src/main/java/com/inspect/system/mapper/ExtOrgSyncMapper.java
  9. +6
    -0
      inspect-management/src/main/java/com/inspect/system/service/IExtOrgSyncService.java
  10. +92
    -0
      inspect-management/src/main/java/com/inspect/system/service/impl/ExtOrgSyncServiceImpl.java
  11. +28
    -0
      inspect-management/src/main/resources/mapper/ext_sync/ExtOrgSyncMapper.xml

+ 5
- 0
inspect-admin/src/main/java/com/inspect/admin/feign/RemoteExtUserService.java View File

@ -8,6 +8,7 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
/** /**
* 调用 dliip-system 的内部接口,为外部登录用户自动建号 / 绑定本地账号。 * 调用 dliip-system 的内部接口,为外部登录用户自动建号 / 绑定本地账号。
@ -22,4 +23,8 @@ public interface RemoteExtUserService {
@PostMapping("/inner/ext_user/ensure") @PostMapping("/inner/ext_user/ensure")
AjaxResult ensureUser(@RequestBody JoinbrightUser user, AjaxResult ensureUser(@RequestBody JoinbrightUser user,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source); @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
@PostMapping("/inner/ext_user/syncRoles")
AjaxResult syncRoles(@RequestParam("username") String username,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
} }

+ 5
- 0
inspect-admin/src/main/java/com/inspect/admin/feign/factory/RemoteExtUserFallbackFactory.java View File

@ -21,6 +21,11 @@ public class RemoteExtUserFallbackFactory implements FallbackFactory<RemoteExtUs
public AjaxResult ensureUser(JoinbrightUser user, String source) { public AjaxResult ensureUser(JoinbrightUser user, String source) {
return AjaxResult.error(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部账号同步服务暂不可用,请稍后重试"); return AjaxResult.error(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部账号同步服务暂不可用,请稍后重试");
} }
@Override
public AjaxResult syncRoles(String username, String source) {
return AjaxResult.error(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部角色同步服务暂不可用");
}
}; };
} }
} }

+ 19
- 0
inspect-admin/src/main/java/com/inspect/admin/service/JoinbrightLoginService.java View File

@ -52,6 +52,7 @@ public class JoinbrightLoginService {
JoinbrightUser externalUser = getExternalUser(joinbrightToken); JoinbrightUser externalUser = getExternalUser(joinbrightToken);
ensurePlatformUser(externalUser); ensurePlatformUser(externalUser);
syncExternalRoles(externalUser.getUsername());
LoginUser loginUser = loadPlatformUser(externalUser.getUsername()); LoginUser loginUser = loadPlatformUser(externalUser.getUsername());
return tokenService.createToken(loginUser); return tokenService.createToken(loginUser);
} }
@ -110,6 +111,24 @@ public class JoinbrightLoginService {
} }
} }
/**
* 登录时同步外部角色:失败只记录日志,不阻断登录(fail-open)。
*/
private void syncExternalRoles(String username) {
try {
AjaxResult result = remoteExtUserService.syncRoles(username, SecurityConstants.INNER);
if (result == null || readCode(result) != Response.SUCCESS) {
log.warn("[JOINBRIGHT] 外部角色同步未成功,按现有角色放行, username={}, result={}",
username, result);
return;
}
log.info("[JOINBRIGHT] 外部角色同步完成, username={}, msg={}",
username, result.get(AjaxResult.MSG_TAG));
} catch (Exception e) {
log.warn("[JOINBRIGHT] 外部角色同步异常,按现有角色放行, username={}", username, e);
}
}
private LoginUser loadPlatformUser(String username) { private LoginUser loadPlatformUser(String username) {
try { try {
return sysLoginService.getLoginUserByUsername(username); return sysLoginService.getLoginUserByUsername(username);


+ 5
- 0
inspect-management/src/main/java/com/inspect/system/client/ExtAuthClient.java View File

@ -2,6 +2,7 @@ package com.inspect.system.client;
import com.inspect.base.core.domain.Response; import com.inspect.base.core.domain.Response;
import com.inspect.system.domain.ext.ExtDept; import com.inspect.system.domain.ext.ExtDept;
import com.inspect.system.domain.ext.ExtRole;
import com.inspect.system.domain.ext.ExtUser; import com.inspect.system.domain.ext.ExtUser;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -27,4 +28,8 @@ public interface ExtAuthClient {
@GetMapping("/inner/ext_auth/getUserByDeptId") @GetMapping("/inner/ext_auth/getUserByDeptId")
Response<List<ExtUser>> getUserByDeptId(@RequestParam("deptId") String deptId, Response<List<ExtUser>> getUserByDeptId(@RequestParam("deptId") String deptId,
@RequestHeader("from-source") String source); @RequestHeader("from-source") String source);
@GetMapping("/inner/ext_auth/getRoleByUser")
Response<List<ExtRole>> getRoleByUser(@RequestParam("username") String username,
@RequestHeader("from-source") String source);
} }

+ 18
- 0
inspect-management/src/main/java/com/inspect/system/controller/ExtUserController.java View File

@ -5,11 +5,13 @@ import com.inspect.base.core.exception.ServiceException;
import com.inspect.base.core.web.domain.AjaxResult; import com.inspect.base.core.web.domain.AjaxResult;
import com.inspect.common.security.annotation.InnerAuth; import com.inspect.common.security.annotation.InnerAuth;
import com.inspect.system.domain.ext.ExtUser; import com.inspect.system.domain.ext.ExtUser;
import com.inspect.system.domain.ext.RoleSyncResult;
import com.inspect.system.service.IExtOrgSyncService; import com.inspect.system.service.IExtOrgSyncService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
@ -27,6 +29,22 @@ public class ExtUserController {
this.extOrgSyncService = extOrgSyncService; this.extOrgSyncService = extOrgSyncService;
} }
@InnerAuth
@PostMapping("/syncRoles")
public AjaxResult syncRoles(@RequestParam("username") String username) {
try {
RoleSyncResult result = extOrgSyncService.syncUserRoles(username);
return AjaxResult.success("角色同步完成", result);
} catch (ServiceException e) {
log.warn("[EXT-LOGIN] 外部角色同步失败: {}", e.getMessage());
return AjaxResult.error(e.getCode() == null ? ExtAuthConstants.SERVICE_UNAVAILABLE : e.getCode(),
e.getMessage());
} catch (Exception e) {
log.error("[EXT-LOGIN] 外部角色同步异常", e);
return AjaxResult.error(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部角色同步失败,请稍后重试");
}
}
@InnerAuth @InnerAuth
@PostMapping("/ensure") @PostMapping("/ensure")
public AjaxResult ensure(@RequestBody ExtUser extUser) { public AjaxResult ensure(@RequestBody ExtUser extUser) {


+ 18
- 0
inspect-management/src/main/java/com/inspect/system/domain/ext/ExtRole.java View File

@ -0,0 +1,18 @@
package com.inspect.system.domain.ext;
import lombok.Data;
/**
* 外部盐源平台用户角色(GET /ext_auth/getRoleByUser)。
*/
@Data
public class ExtRole {
/** 用户角色关联 id */
private String id;
/** 角色 id */
private String roleId;
/** 角色名称,外部可能返回 null */
private String roleName;
/** 用户 id */
private String userId;
}

+ 24
- 0
inspect-management/src/main/java/com/inspect/system/domain/ext/RoleSyncResult.java View File

@ -0,0 +1,24 @@
package com.inspect.system.domain.ext;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 登录时外部角色同步结果。
*/
@Data
public class RoleSyncResult {
private String username;
/** 本次新增的本地角色名 */
private List<String> added = new ArrayList<>();
/** 本次回收的本地角色名 */
private List<String> removed = new ArrayList<>();
/** 跳过明细(未映射、本地不存在、已停用等) */
private List<String> skipped = new ArrayList<>();
public String getSummary() {
return String.format("角色(新增%d/回收%d/跳过%d)", added.size(), removed.size(), skipped.size());
}
}

+ 8
- 0
inspect-management/src/main/java/com/inspect/system/mapper/ExtOrgSyncMapper.java View File

@ -1,6 +1,7 @@
package com.inspect.system.mapper; package com.inspect.system.mapper;
import com.inspect.system.base.domain.SysDept; import com.inspect.system.base.domain.SysDept;
import com.inspect.system.base.domain.SysRole;
import com.inspect.system.base.domain.SysUser; import com.inspect.system.base.domain.SysUser;
import com.inspect.system.domain.ext.ExtDeptRef; import com.inspect.system.domain.ext.ExtDeptRef;
import com.inspect.system.domain.ext.ExtUserRef; import com.inspect.system.domain.ext.ExtUserRef;
@ -46,6 +47,13 @@ public interface ExtOrgSyncMapper {
int insertUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId); int insertUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId);
/* ---------------- 角色(登录时同步) ---------------- */
SysRole selectRoleByName(@Param("roleName") String roleName);
List<SysRole> selectRolesByUserId(@Param("userId") Long userId);
int deleteUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId);
List<ExtUserRef> selectSyncedUsers(); List<ExtUserRef> selectSyncedUsers();
int deactivateUser(@Param("userId") Long userId); int deactivateUser(@Param("userId") Long userId);

+ 6
- 0
inspect-management/src/main/java/com/inspect/system/service/IExtOrgSyncService.java View File

@ -2,6 +2,7 @@ package com.inspect.system.service;
import com.inspect.system.domain.ExtSyncResult; import com.inspect.system.domain.ExtSyncResult;
import com.inspect.system.domain.ext.ExtUser; import com.inspect.system.domain.ext.ExtUser;
import com.inspect.system.domain.ext.RoleSyncResult;
/** /**
* 外部平台(盐源智慧感知平台)部门 / 用户同步。 * 外部平台(盐源智慧感知平台)部门 / 用户同步。
@ -24,4 +25,9 @@ public interface IExtOrgSyncService {
* @return 本地用户ID * @return 本地用户ID
*/ */
Long ensureUser(ExtUser extUser); Long ensureUser(ExtUser extUser);
/**
* 登录时同步外部角色:白名单映射 + 映射范围内对账回收,common 角色不参与回收。
*/
RoleSyncResult syncUserRoles(String username);
} }

+ 92
- 0
inspect-management/src/main/java/com/inspect/system/service/impl/ExtOrgSyncServiceImpl.java View File

@ -9,13 +9,16 @@ import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.redis.service.RedisService; import com.inspect.base.redis.service.RedisService;
import com.inspect.common.security.utils.SecurityUtils; import com.inspect.common.security.utils.SecurityUtils;
import com.inspect.system.base.domain.SysDept; import com.inspect.system.base.domain.SysDept;
import com.inspect.system.base.domain.SysRole;
import com.inspect.system.base.domain.SysUser; import com.inspect.system.base.domain.SysUser;
import com.inspect.system.client.ExtAuthClient; import com.inspect.system.client.ExtAuthClient;
import com.inspect.system.domain.ExtSyncResult; import com.inspect.system.domain.ExtSyncResult;
import com.inspect.system.domain.ext.ExtDept; import com.inspect.system.domain.ext.ExtDept;
import com.inspect.system.domain.ext.ExtDeptRef; import com.inspect.system.domain.ext.ExtDeptRef;
import com.inspect.system.domain.ext.ExtRole;
import com.inspect.system.domain.ext.ExtUser; import com.inspect.system.domain.ext.ExtUser;
import com.inspect.system.domain.ext.ExtUserRef; import com.inspect.system.domain.ext.ExtUserRef;
import com.inspect.system.domain.ext.RoleSyncResult;
import com.inspect.system.mapper.ExtOrgSyncMapper; import com.inspect.system.mapper.ExtOrgSyncMapper;
import com.inspect.system.service.IExtOrgSyncService; import com.inspect.system.service.IExtOrgSyncService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -58,6 +61,11 @@ public class ExtOrgSyncServiceImpl implements IExtOrgSyncService {
/** 同步用户默认角色:普通角色,可由 yanyuan.sync.default-role-key 覆盖(部分库为 common1) */ /** 同步用户默认角色:普通角色,可由 yanyuan.sync.default-role-key 覆盖(部分库为 common1) */
@Value("${yanyuan.sync.default-role-key:common}") @Value("${yanyuan.sync.default-role-key:common}")
private String defaultRoleKey; private String defaultRoleKey;
/** 仅同步以该前缀开头的外部角色名 */
@Value("${yanyuan.sync.role-name-prefix:动环}")
private String roleNamePrefix;
private static final String LOCK_KEY = "ext:org:sync:lock"; private static final String LOCK_KEY = "ext:org:sync:lock";
private static final String ENSURE_LOCK_PREFIX = "ext:user:ensure:"; private static final String ENSURE_LOCK_PREFIX = "ext:user:ensure:";
private static final long ENSURE_LOCK_SECONDS = 30L; private static final long ENSURE_LOCK_SECONDS = 30L;
@ -309,6 +317,90 @@ public class ExtOrgSyncServiceImpl implements IExtOrgSyncService {
} }
} }
/**
* 登录时同步外部角色:只处理以配置前缀(默认动环)开头的外部角色名,
* 与本地 role_name 精确匹配后授权;回收范围同样限定为动环前缀角色。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public RoleSyncResult syncUserRoles(String username) {
if (StringUtils.isEmpty(username)) {
throw new ServiceException("用户名不能为空", ExtAuthConstants.TOKEN_INVALID);
}
RoleSyncResult result = new RoleSyncResult();
result.setUsername(username);
SysUser user = extOrgSyncMapper.selectUserByUserName(username);
if (user == null || !"0".equals(user.getDelFlag())) {
throw new ServiceException("本地用户[" + username + "]不存在或已删除",
ExtAuthConstants.TOKEN_INVALID);
}
if (StringUtils.isEmpty(roleNamePrefix)) {
log.info("[EXT-ROLE] 未配置角色名前缀,跳过角色同步, username={}", username);
return result;
}
Response<List<ExtRole>> response = extAuthClient.getRoleByUser(username, SOURCE);
if (response == null || response.getCode() != Response.SUCCESS || response.getData() == null) {
throw new ServiceException("获取外部角色失败", ExtAuthConstants.SERVICE_UNAVAILABLE);
}
Map<Long, String> desired = new LinkedHashMap<>();
for (ExtRole extRole : response.getData()) {
String roleName = StringUtils.trim(extRole.getRoleName());
if (StringUtils.isEmpty(roleName)) {
result.getSkipped().add("外部角色名为空");
continue;
}
if (!roleName.startsWith(roleNamePrefix)) {
continue;
}
SysRole localRole = extOrgSyncMapper.selectRoleByName(roleName);
if (localRole == null) {
result.getSkipped().add(roleName + "(本地角色不存在)");
continue;
}
if (!"0".equals(localRole.getStatus())) {
result.getSkipped().add(roleName + "(本地角色已停用)");
continue;
}
desired.put(localRole.getRoleId(), roleName);
}
Long commonRoleId = extOrgSyncMapper.selectRoleIdByRoleKey(defaultRoleKey);
Set<Long> currentIds = new HashSet<>();
for (SysRole current : extOrgSyncMapper.selectRolesByUserId(user.getUserId())) {
currentIds.add(current.getRoleId());
if (commonRoleId != null && commonRoleId.equals(current.getRoleId())) {
continue;
}
String currentName = current.getRoleName();
// 只回收动环前缀角色;非动环角色视为管理员手工授权,不参与对账
if (StringUtils.isEmpty(currentName) || !currentName.startsWith(roleNamePrefix)) {
continue;
}
if (!desired.containsKey(current.getRoleId())) {
extOrgSyncMapper.deleteUserRole(user.getUserId(), current.getRoleId());
result.getRemoved().add(currentName);
}
}
for (Map.Entry<Long, String> entry : desired.entrySet()) {
if (currentIds.contains(entry.getKey())) {
continue;
}
extOrgSyncMapper.insertUserRole(user.getUserId(), entry.getKey());
result.getAdded().add(entry.getValue());
}
log.info("[EXT-ROLE] username={}, {}", username, result.getSummary());
for (String skipped : result.getSkipped()) {
log.warn("[EXT-ROLE] username={}, 跳过: {}", username, skipped);
}
return result;
}
/** /**
* 用户落库:按 ext_id 更新;无 ext_id 记录时优先绑定同名账号(覆盖 ext_id),否则新建。 * 用户落库:按 ext_id 更新;无 ext_id 记录时优先绑定同名账号(覆盖 ext_id),否则新建。
*/ */


+ 28
- 0
inspect-management/src/main/resources/mapper/ext_sync/ExtOrgSyncMapper.xml View File

@ -44,6 +44,14 @@
<result property="status" column="status"/> <result property="status" column="status"/>
</resultMap> </resultMap>
<resultMap type="com.inspect.system.base.domain.SysRole" id="RoleRefResult">
<id property="roleId" column="role_id"/>
<result property="roleName" column="role_name"/>
<result property="roleKey" column="role_key"/>
<result property="status" column="status"/>
<result property="delFlag" column="del_flag"/>
</resultMap>
<resultMap type="com.inspect.system.domain.ext.ExtUserRef" id="UserRefResult"> <resultMap type="com.inspect.system.domain.ext.ExtUserRef" id="UserRefResult">
<result property="userId" column="user_id"/> <result property="userId" column="user_id"/>
<result property="extId" column="ext_id"/> <result property="extId" column="ext_id"/>
@ -168,4 +176,24 @@
where user_id = #{userId} and status = '0' where user_id = #{userId} and status = '0'
</update> </update>
<!-- ============================ 角色(登录时同步) ============================ -->
<select id="selectRoleByName" resultMap="RoleRefResult">
select role_id, role_name, role_key, status, del_flag
from sys_role
where role_name = #{roleName} and del_flag = '0'
order by role_id
limit 1
</select>
<select id="selectRolesByUserId" resultMap="RoleRefResult">
select r.role_id, r.role_name, r.role_key, r.status, r.del_flag
from sys_role r
inner join sys_user_role ur on ur.role_id = r.role_id
where ur.user_id = #{userId} and r.del_flag = '0'
</select>
<delete id="deleteUserRole">
delete from sys_user_role where user_id = #{userId} and role_id = #{roleId}
</delete>
</mapper> </mapper>

Loading…
Cancel
Save