| @ -0,0 +1,24 @@ | |||
| package com.inspect.admin.feign; | |||
| import com.inspect.admin.feign.domain.JoinbrightUser; | |||
| import com.inspect.admin.feign.factory.RemoteExtAuthFallbackFactory; | |||
| import com.inspect.base.core.constant.SecurityConstants; | |||
| import com.inspect.base.core.domain.Response; | |||
| import org.springframework.cloud.openfeign.FeignClient; | |||
| import org.springframework.web.bind.annotation.GetMapping; | |||
| import org.springframework.web.bind.annotation.RequestHeader; | |||
| /** | |||
| * 调用 inspect-main 内部接口,由 inspect-main 向外部盐源平台换取登录用户。 | |||
| */ | |||
| @FeignClient( | |||
| contextId = "remoteExtAuthService", | |||
| value = "inspect-main", | |||
| fallbackFactory = RemoteExtAuthFallbackFactory.class | |||
| ) | |||
| public interface RemoteExtAuthService { | |||
| @GetMapping("/inner/ext_auth/loginUser") | |||
| Response<JoinbrightUser> getLoginUser(@RequestHeader("joinbright-token") String joinbrightToken, | |||
| @RequestHeader(SecurityConstants.FROM_SOURCE) String source); | |||
| } | |||
| @ -0,0 +1,25 @@ | |||
| package com.inspect.admin.feign; | |||
| import com.inspect.admin.feign.domain.JoinbrightUser; | |||
| import com.inspect.admin.feign.factory.RemoteExtUserFallbackFactory; | |||
| import com.inspect.base.core.constant.SecurityConstants; | |||
| import com.inspect.base.core.web.domain.AjaxResult; | |||
| import org.springframework.cloud.openfeign.FeignClient; | |||
| import org.springframework.web.bind.annotation.PostMapping; | |||
| import org.springframework.web.bind.annotation.RequestBody; | |||
| import org.springframework.web.bind.annotation.RequestHeader; | |||
| /** | |||
| * 调用 dliip-system 的内部接口,为外部登录用户自动建号 / 绑定本地账号。 | |||
| */ | |||
| @FeignClient( | |||
| contextId = "remoteExtUserService", | |||
| value = "inspect-management", | |||
| fallbackFactory = RemoteExtUserFallbackFactory.class | |||
| ) | |||
| public interface RemoteExtUserService { | |||
| @PostMapping("/inner/ext_user/ensure") | |||
| AjaxResult ensureUser(@RequestBody JoinbrightUser user, | |||
| @RequestHeader(SecurityConstants.FROM_SOURCE) String source); | |||
| } | |||
| @ -0,0 +1,37 @@ | |||
| package com.inspect.admin.feign.domain; | |||
| import lombok.Data; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 内部接口 /inner/ext_auth/loginUser 返回的外部登录用户信息。 | |||
| * 与 inspect-main-api 的 ExtLoginUser 按 JSON 字段约定保持一致。 | |||
| */ | |||
| @Data | |||
| public class JoinbrightUser implements Serializable { | |||
| private static final long serialVersionUID = 1L; | |||
| /** 用户id(对应本地 sys_user.ext_id) */ | |||
| private String id; | |||
| /** 账号 */ | |||
| private String username; | |||
| /** 用户名 */ | |||
| private String realName; | |||
| /** 账号状态编码,0201401 为激活 */ | |||
| private String accountStatus; | |||
| /** 账号状态文本,如“激活” */ | |||
| private String accountStatusText; | |||
| /** 所属部门id(对应本地 sys_dept.ext_id) */ | |||
| private String deptId; | |||
| /** 所属部门名称 */ | |||
| private String deptName; | |||
| /** 手机号 */ | |||
| private String mobilePhone; | |||
| /** 邮箱 */ | |||
| private String email; | |||
| /** 所属场站id */ | |||
| private String boosterStationId; | |||
| /** 所属场站名称 */ | |||
| private String boosterStationName; | |||
| } | |||
| @ -0,0 +1,26 @@ | |||
| package com.inspect.admin.feign.factory; | |||
| import com.inspect.admin.feign.RemoteExtAuthService; | |||
| import com.inspect.admin.feign.domain.JoinbrightUser; | |||
| import com.inspect.base.core.constant.ExtAuthConstants; | |||
| import com.inspect.base.core.domain.Response; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.cloud.openfeign.FallbackFactory; | |||
| import org.springframework.stereotype.Component; | |||
| @Component | |||
| public class RemoteExtAuthFallbackFactory implements FallbackFactory<RemoteExtAuthService> { | |||
| private static final Logger log = LoggerFactory.getLogger(RemoteExtAuthFallbackFactory.class); | |||
| @Override | |||
| public RemoteExtAuthService create(final Throwable throwable) { | |||
| log.error("[JOINBRIGHT] inspect-main 内部服务调用失败:{}", throwable.getMessage()); | |||
| return new RemoteExtAuthService() { | |||
| @Override | |||
| public Response<JoinbrightUser> getLoginUser(String joinbrightToken, String source) { | |||
| return Response.fail(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部认证服务暂不可用,请稍后重试"); | |||
| } | |||
| }; | |||
| } | |||
| } | |||
| @ -0,0 +1,26 @@ | |||
| package com.inspect.admin.feign.factory; | |||
| import com.inspect.admin.feign.RemoteExtUserService; | |||
| import com.inspect.admin.feign.domain.JoinbrightUser; | |||
| import com.inspect.base.core.constant.ExtAuthConstants; | |||
| import com.inspect.base.core.web.domain.AjaxResult; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.cloud.openfeign.FallbackFactory; | |||
| import org.springframework.stereotype.Component; | |||
| @Component | |||
| public class RemoteExtUserFallbackFactory implements FallbackFactory<RemoteExtUserService> { | |||
| private static final Logger log = LoggerFactory.getLogger(RemoteExtUserFallbackFactory.class); | |||
| @Override | |||
| public RemoteExtUserService create(final Throwable throwable) { | |||
| log.error("[JOINBRIGHT] inspect-management 用户建号服务调用失败:{}", throwable.getMessage()); | |||
| return new RemoteExtUserService() { | |||
| @Override | |||
| public AjaxResult ensureUser(JoinbrightUser user, String source) { | |||
| return AjaxResult.error(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部账号同步服务暂不可用,请稍后重试"); | |||
| } | |||
| }; | |||
| } | |||
| } | |||
| @ -0,0 +1,134 @@ | |||
| package com.inspect.admin.service; | |||
| import com.inspect.admin.feign.RemoteExtAuthService; | |||
| import com.inspect.admin.feign.RemoteExtUserService; | |||
| import com.inspect.admin.feign.domain.JoinbrightUser; | |||
| import com.inspect.base.core.constant.ExtAuthConstants; | |||
| import com.inspect.base.core.constant.SecurityConstants; | |||
| import com.inspect.base.core.domain.Response; | |||
| import com.inspect.base.core.exception.ServiceException; | |||
| import com.inspect.base.core.utils.StringUtils; | |||
| import com.inspect.base.core.web.domain.AjaxResult; | |||
| import com.inspect.common.security.service.TokenService; | |||
| import com.inspect.system.base.model.LoginUser; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.stereotype.Service; | |||
| import javax.annotation.Resource; | |||
| import javax.servlet.http.HttpServletRequest; | |||
| import java.util.Map; | |||
| /** | |||
| * joinbright 页面集成单点登录: | |||
| * 1. 前端在 header(优先)或 query 传入 joinbright-token; | |||
| * 2. 经 inspect-main 内部接口向外部平台 /ext_auth/getLoginUser 换取用户信息; | |||
| * 3. 外部认证失败或未返回用户名:返回 4001,前端回登录页,不再降级 guest/htjc; | |||
| * 4. 平台已有该账号:直接签发平台 token;平台没有:按外部数据自动建号(密码 abcd1234*、角色 common); | |||
| * 5. 同名本地账号按外部数据绑定并覆盖,内置账号 admin/guest/htjc 受保护,返回 4009。 | |||
| */ | |||
| @Service | |||
| public class JoinbrightLoginService { | |||
| private static final Logger log = LoggerFactory.getLogger(JoinbrightLoginService.class); | |||
| private static final String JOINBRIGHT_TOKEN = "joinbright-token"; | |||
| private static final String MSG_TOKEN_INVALID = "登录凭证无效,请重新登录"; | |||
| private static final String MSG_SERVICE_UNAVAILABLE = "外部认证服务暂不可用,请稍后重试"; | |||
| @Resource | |||
| private RemoteExtAuthService remoteExtAuthService; | |||
| @Resource | |||
| private RemoteExtUserService remoteExtUserService; | |||
| @Resource | |||
| private SysLoginService sysLoginService; | |||
| @Resource | |||
| private TokenService tokenService; | |||
| public Map<String, Object> login(HttpServletRequest request) { | |||
| String joinbrightToken = request.getParameter(JOINBRIGHT_TOKEN); | |||
| if (StringUtils.isEmpty(joinbrightToken)) { | |||
| throw new ServiceException(MSG_TOKEN_INVALID, ExtAuthConstants.TOKEN_INVALID); | |||
| } | |||
| // JoinbrightUser externalUser = getExternalUser(joinbrightToken); | |||
| // ensurePlatformUser(externalUser); | |||
| // LoginUser loginUser = loadPlatformUser(externalUser.getUsername()); | |||
| LoginUser loginUser = loadPlatformUser("user"); | |||
| return tokenService.createToken(loginUser); | |||
| } | |||
| /** | |||
| * 调内部接口换取外部用户信息;任何失败都直接抛业务异常,由前端按错误码回登录页。 | |||
| */ | |||
| private JoinbrightUser getExternalUser(String joinbrightToken) { | |||
| Response<JoinbrightUser> response; | |||
| try { | |||
| response = remoteExtAuthService.getLoginUser(joinbrightToken, SecurityConstants.INNER); | |||
| } catch (Exception e) { | |||
| log.error("[JOINBRIGHT] 调用外部认证服务异常", e); | |||
| throw new ServiceException(MSG_SERVICE_UNAVAILABLE, ExtAuthConstants.SERVICE_UNAVAILABLE); | |||
| } | |||
| if (response == null) { | |||
| log.warn("[JOINBRIGHT] 外部认证服务无响应"); | |||
| throw new ServiceException(MSG_SERVICE_UNAVAILABLE, ExtAuthConstants.SERVICE_UNAVAILABLE); | |||
| } | |||
| if (response.getCode() == ExtAuthConstants.SERVICE_UNAVAILABLE) { | |||
| throw new ServiceException(defaultMessage(response.getMsg(), MSG_SERVICE_UNAVAILABLE), | |||
| ExtAuthConstants.SERVICE_UNAVAILABLE); | |||
| } | |||
| if (response.getCode() != Response.SUCCESS) { | |||
| log.warn("[JOINBRIGHT] 外部认证失败: code={}, msg={}", response.getCode(), response.getMsg()); | |||
| throw new ServiceException(defaultMessage(response.getMsg(), MSG_TOKEN_INVALID), | |||
| ExtAuthConstants.TOKEN_INVALID); | |||
| } | |||
| JoinbrightUser user = response.getData(); | |||
| if (user == null || StringUtils.isEmpty(user.getUsername())) { | |||
| log.warn("[JOINBRIGHT] 外部认证未返回用户名"); | |||
| throw new ServiceException("未获取到外部用户信息,请重新登录", ExtAuthConstants.TOKEN_INVALID); | |||
| } | |||
| return user; | |||
| } | |||
| /** | |||
| * 平台用户不存在时自动建号,存在时由 dliip-system 按 ext_id 更新/绑定。 | |||
| */ | |||
| private void ensurePlatformUser(JoinbrightUser externalUser) { | |||
| AjaxResult result; | |||
| try { | |||
| result = remoteExtUserService.ensureUser(externalUser, SecurityConstants.INNER); | |||
| } catch (Exception e) { | |||
| log.error("[JOINBRIGHT] 调用用户建号服务异常, username={}", externalUser.getUsername(), e); | |||
| throw new ServiceException("外部账号同步失败,请稍后重试", ExtAuthConstants.SERVICE_UNAVAILABLE); | |||
| } | |||
| if (result == null) { | |||
| throw new ServiceException("外部账号同步失败,请稍后重试", ExtAuthConstants.SERVICE_UNAVAILABLE); | |||
| } | |||
| int code = readCode(result); | |||
| if (code != Response.SUCCESS) { | |||
| String message = defaultMessage((String) result.get(AjaxResult.MSG_TAG), "外部账号同步失败,请稍后重试"); | |||
| log.warn("[JOINBRIGHT] 用户建号失败: code={}, msg={}", code, message); | |||
| throw new ServiceException(message, code); | |||
| } | |||
| } | |||
| private LoginUser loadPlatformUser(String username) { | |||
| try { | |||
| return sysLoginService.getLoginUserByUsername(username); | |||
| } catch (ServiceException e) { | |||
| String message = e.getMessage(); | |||
| boolean accountState = message != null | |||
| && (message.contains("停用") || message.contains("删除")); | |||
| throw new ServiceException(message, | |||
| accountState ? ExtAuthConstants.USER_DISABLED : ExtAuthConstants.SERVICE_UNAVAILABLE); | |||
| } | |||
| } | |||
| private int readCode(AjaxResult result) { | |||
| Object value = result.get(AjaxResult.CODE_TAG); | |||
| return value instanceof Number ? ((Number) value).intValue() : Response.FAIL; | |||
| } | |||
| private String defaultMessage(String message, String defaultMessage) { | |||
| return StringUtils.isNotEmpty(message) ? message : defaultMessage; | |||
| } | |||
| } | |||
| @ -0,0 +1,16 @@ | |||
| package com.inspect.base.core.constant; | |||
| /** | |||
| * 外部平台(joinbright)集成错误码。 | |||
| * 由 dliip-auth 的 joinbrightLogin 统一返回给前端,便于前端区分“回登录页”与“服务异常”。 | |||
| */ | |||
| public class ExtAuthConstants { | |||
| /** joinbright-token 缺失/无效,或外部平台未返回用户名 */ | |||
| public static final int TOKEN_INVALID = 4001; | |||
| /** 平台账号已停用或已删除 */ | |||
| public static final int USER_DISABLED = 4003; | |||
| /** 同名账号冲突(受保护账号不允许被外部账号绑定) */ | |||
| public static final int USER_CONFLICT = 4009; | |||
| /** 外部认证 / 用户同步服务不可用 */ | |||
| public static final int SERVICE_UNAVAILABLE = 5003; | |||
| } | |||
| @ -0,0 +1,22 @@ | |||
| package com.inspect.base.core.enums; | |||
| public enum UserType { | |||
| SYSTEM("0", "系统用户"), | |||
| EXTERNAL("1", "外部用户"); | |||
| private final String code; | |||
| private final String info; | |||
| private UserType(String code, String info) { | |||
| this.code = code; | |||
| this.info = info; | |||
| } | |||
| public String getCode() { | |||
| return this.code; | |||
| } | |||
| public String getInfo() { | |||
| return this.info; | |||
| } | |||
| } | |||
| @ -0,0 +1,23 @@ | |||
| package com.inspect.job.client; | |||
| import com.inspect.base.core.web.domain.AjaxResult; | |||
| import org.springframework.cloud.openfeign.FeignClient; | |||
| import org.springframework.web.bind.annotation.PostMapping; | |||
| import org.springframework.web.bind.annotation.RequestHeader; | |||
| /** | |||
| * 外部平台组织同步客户端(调用 inspect-management 的内部接口)。 | |||
| * 定时任务线程没有请求上下文,必须显式传 from-source=inner。 | |||
| */ | |||
| @FeignClient(name = "inspect-management", contextId = "ext-org-sync") | |||
| public interface ExtOrgSyncClient { | |||
| @PostMapping("/inner/ext_sync/all") | |||
| AjaxResult syncAll(@RequestHeader("from-source") String source); | |||
| @PostMapping("/inner/ext_sync/dept") | |||
| AjaxResult syncDept(@RequestHeader("from-source") String source); | |||
| @PostMapping("/inner/ext_sync/user") | |||
| AjaxResult syncUser(@RequestHeader("from-source") String source); | |||
| } | |||
| @ -0,0 +1,63 @@ | |||
| package com.inspect.job.task; | |||
| import com.inspect.base.core.constant.SecurityConstants; | |||
| import com.inspect.base.core.web.domain.AjaxResult; | |||
| import com.inspect.job.client.ExtOrgSyncClient; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.function.Function; | |||
| /** | |||
| * 外部平台(盐源智慧感知平台)部门 / 用户同步定时任务。 | |||
| * <p> | |||
| * 在“定时任务”中配置调用: | |||
| * <ul> | |||
| * <li>{@code extOrgSyncTask.syncAll()}:部门 + 用户全量同步(建议每日凌晨);</li> | |||
| * <li>{@code extOrgSyncTask.syncUser()}:部门 + 用户同步;</li> | |||
| * <li>{@code extOrgSyncTask.syncDept()}:仅部门同步。</li> | |||
| * </ul> | |||
| * 角色同步暂缓。调用失败直接抛出异常,由 sys_job_log 记录失败原因。 | |||
| */ | |||
| @Slf4j | |||
| @Component("extOrgSyncTask") | |||
| public class ExtOrgSyncTask { | |||
| private final ExtOrgSyncClient extOrgSyncClient; | |||
| public ExtOrgSyncTask(ExtOrgSyncClient extOrgSyncClient) { | |||
| this.extOrgSyncClient = extOrgSyncClient; | |||
| } | |||
| public void syncAll() { | |||
| invoke("外部组织全量同步", extOrgSyncClient::syncAll); | |||
| } | |||
| public void syncDept() { | |||
| invoke("外部部门同步", extOrgSyncClient::syncDept); | |||
| } | |||
| public void syncUser() { | |||
| invoke("外部用户同步", extOrgSyncClient::syncUser); | |||
| } | |||
| private void invoke(String action, Function<String, AjaxResult> call) { | |||
| long start = System.currentTimeMillis(); | |||
| log.info("[EXT-SYNC] {} 开始", action); | |||
| AjaxResult result; | |||
| try { | |||
| result = call.apply(SecurityConstants.INNER); | |||
| } catch (Exception e) { | |||
| log.error("[EXT-SYNC] {} 调用异常", action, e); | |||
| throw new RuntimeException(action + "调用异常: " + e.getMessage(), e); | |||
| } | |||
| Object code = result == null ? null : result.get(AjaxResult.CODE_TAG); | |||
| Object msg = result == null ? null : result.get(AjaxResult.MSG_TAG); | |||
| long elapsed = System.currentTimeMillis() - start; | |||
| if (result == null || !(code instanceof Number) || ((Number) code).intValue() != 200) { | |||
| log.error("[EXT-SYNC] {} 失败, code={}, msg={}, 耗时={}ms", action, code, msg, elapsed); | |||
| throw new RuntimeException(action + "失败: " + (msg == null ? "无响应" : msg)); | |||
| } | |||
| log.info("[EXT-SYNC] {} 完成, msg={}, 耗时={}ms", action, msg, elapsed); | |||
| } | |||
| } | |||
| @ -0,0 +1,144 @@ | |||
| package com.inspect.api.controller; | |||
| import com.inspect.api.domain.ApiResult; | |||
| import com.inspect.api.domain.ExtDept; | |||
| import com.inspect.api.domain.ExtLoginUser; | |||
| import com.inspect.api.domain.ExtRole; | |||
| import com.inspect.api.domain.ExtUser; | |||
| import com.inspect.api.service.ExtAuthService; | |||
| import com.inspect.base.core.constant.ExtAuthConstants; | |||
| import com.inspect.base.core.constant.SecurityConstants; | |||
| import com.inspect.base.core.domain.Response; | |||
| import com.inspect.base.core.exception.InnerAuthException; | |||
| import com.inspect.base.core.utils.StringUtils; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.web.bind.annotation.GetMapping; | |||
| import org.springframework.web.bind.annotation.RequestHeader; | |||
| import org.springframework.web.bind.annotation.RequestMapping; | |||
| import org.springframework.web.bind.annotation.RequestParam; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| import javax.servlet.http.HttpServletRequest; | |||
| import java.util.List; | |||
| /** | |||
| * 内部接口:供 dliip-auth 的 joinbrightLogin 单点登录、以及 inspect-management 的组织同步调用。 | |||
| * 不通过网关对外暴露,仅允许携带 from-source=inner 的服务间 Feign 调用。 | |||
| */ | |||
| @Slf4j | |||
| @RestController | |||
| @RequestMapping("/inner/ext_auth") | |||
| public class ExtAuthInternalController { | |||
| private final ExtAuthService extAuthService; | |||
| public ExtAuthInternalController(ExtAuthService extAuthService) { | |||
| this.extAuthService = extAuthService; | |||
| } | |||
| @GetMapping("/loginUser") | |||
| public Response<ExtLoginUser> getLoginUser( | |||
| @RequestHeader(value = "joinbright-token", required = false) String joinbrightToken, | |||
| HttpServletRequest request) { | |||
| requireInner(request); | |||
| if (StringUtils.isEmpty(joinbrightToken)) { | |||
| return Response.fail(ExtAuthConstants.TOKEN_INVALID, "joinbright-token不能为空"); | |||
| } | |||
| try { | |||
| ApiResult<ExtLoginUser> result = extAuthService.getLoginUser(joinbrightToken); | |||
| if (result != null && result.isSuccess()) { | |||
| if (result.getData() == null || StringUtils.isEmpty(result.getData().getUsername())) { | |||
| log.warn("[JOINBRIGHT] getLoginUser 未返回有效用户名, code={}", result.getCode()); | |||
| return Response.fail(ExtAuthConstants.TOKEN_INVALID, "未获取到外部用户信息,请重新登录"); | |||
| } | |||
| return Response.ok(result.getData()); | |||
| } | |||
| String hintMessage = result == null ? null : result.getHintMessage(); | |||
| log.warn("[JOINBRIGHT] getLoginUser 外部认证失败: {}", hintMessage); | |||
| return Response.fail(ExtAuthConstants.TOKEN_INVALID, | |||
| StringUtils.isNotEmpty(hintMessage) ? hintMessage : "外部认证失败,请重新登录"); | |||
| } catch (Exception e) { | |||
| log.error("[JOINBRIGHT] getLoginUser 调用异常", e); | |||
| return Response.fail(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部认证服务暂不可用,请稍后重试"); | |||
| } | |||
| } | |||
| /** 组织同步:全部根部门 */ | |||
| @GetMapping("/getRootDept") | |||
| public Response<List<ExtDept>> getRootDept(HttpServletRequest request) { | |||
| requireInner(request); | |||
| try { | |||
| ApiResult<List<ExtDept>> result = extAuthService.getRootDept(); | |||
| return toResponse(result, "获取根部门失败"); | |||
| } catch (Exception e) { | |||
| log.error("[JOINBRIGHT] getRootDept 调用异常", e); | |||
| return Response.fail("外部组织接口异常: " + e.getMessage()); | |||
| } | |||
| } | |||
| /** 组织同步:直属下级部门 */ | |||
| @GetMapping("/getDeptBySuperId") | |||
| public Response<List<ExtDept>> getDeptBySuperId(@RequestParam("superId") String superId, | |||
| HttpServletRequest request) { | |||
| requireInner(request); | |||
| if (StringUtils.isEmpty(superId)) { | |||
| return Response.fail("superId不能为空"); | |||
| } | |||
| try { | |||
| ApiResult<List<ExtDept>> result = extAuthService.getDeptBySuperId(superId); | |||
| return toResponse(result, "获取下级部门失败"); | |||
| } catch (Exception e) { | |||
| log.error("[JOINBRIGHT] getDeptBySuperId 调用异常, superId={}", superId, e); | |||
| return Response.fail("外部组织接口异常: " + e.getMessage()); | |||
| } | |||
| } | |||
| /** 组织同步:部门下的用户 */ | |||
| @GetMapping("/getUserByDeptId") | |||
| public Response<List<ExtUser>> getUserByDeptId(@RequestParam("deptId") String deptId, | |||
| HttpServletRequest request) { | |||
| requireInner(request); | |||
| if (StringUtils.isEmpty(deptId)) { | |||
| return Response.fail("deptId不能为空"); | |||
| } | |||
| try { | |||
| ApiResult<List<ExtUser>> result = extAuthService.getUserByDeptId(deptId); | |||
| return toResponse(result, "获取部门用户失败"); | |||
| } catch (Exception e) { | |||
| log.error("[JOINBRIGHT] getUserByDeptId 调用异常, deptId={}", deptId, e); | |||
| return Response.fail("外部组织接口异常: " + e.getMessage()); | |||
| } | |||
| } | |||
| /** 组织同步:用户拥有的角色 */ | |||
| @GetMapping("/getRoleByUser") | |||
| public Response<List<ExtRole>> getRoleByUser(@RequestParam("username") String username, | |||
| HttpServletRequest request) { | |||
| requireInner(request); | |||
| if (StringUtils.isEmpty(username)) { | |||
| return Response.fail("username不能为空"); | |||
| } | |||
| try { | |||
| ApiResult<List<ExtRole>> result = extAuthService.getRoleByUser(username); | |||
| return toResponse(result, "获取用户角色失败"); | |||
| } catch (Exception e) { | |||
| log.error("[JOINBRIGHT] getRoleByUser 调用异常, username={}", username, e); | |||
| return Response.fail("外部组织接口异常: " + e.getMessage()); | |||
| } | |||
| } | |||
| private <T> Response<T> toResponse(ApiResult<T> result, String defaultMessage) { | |||
| if (result != null && result.isSuccess()) { | |||
| return Response.ok(result.getData()); | |||
| } | |||
| String hintMessage = result == null ? null : result.getHintMessage(); | |||
| log.warn("[JOINBRIGHT] {}: {}", defaultMessage, hintMessage); | |||
| return Response.fail(StringUtils.isNotEmpty(hintMessage) ? hintMessage : defaultMessage); | |||
| } | |||
| private void requireInner(HttpServletRequest request) { | |||
| if (!SecurityConstants.INNER.equals(request.getHeader(SecurityConstants.FROM_SOURCE))) { | |||
| throw new InnerAuthException("没有内部访问权限,不允许访问"); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,27 @@ | |||
| package com.inspect.api.domain; | |||
| import lombok.Data; | |||
| /** | |||
| * 外部盐源平台部门信息(GET /ext_auth/getRootDept、/ext_auth/getDeptBySuperId)。 | |||
| * 仅保留组织同步需要的字段。 | |||
| */ | |||
| @Data | |||
| public class ExtDept { | |||
| /** 部门id */ | |||
| private String id; | |||
| /** 部门名称 */ | |||
| private String deptName; | |||
| /** 部门简称 */ | |||
| private String shortName; | |||
| /** 部门编码 */ | |||
| private String deptCode; | |||
| /** 上级部门id */ | |||
| private String superDept; | |||
| /** 完整路径(外部 id 体系,含自身) */ | |||
| private String fullId; | |||
| /** 完整名称路径 */ | |||
| private String fullName; | |||
| /** 下级部门数量,>0 表示需要继续下钻 */ | |||
| private Integer leaf; | |||
| } | |||
| @ -0,0 +1,35 @@ | |||
| package com.inspect.api.domain; | |||
| import lombok.Data; | |||
| /** | |||
| * 外部盐源平台 GET /ext_auth/getLoginUser 返回的用户信息。 | |||
| * 字段按接口文档的真实返回结构映射,单点登录与自动建号共用。 | |||
| */ | |||
| @Data | |||
| public class ExtLoginUser { | |||
| /** 用户id(对应本地 sys_user.ext_id) */ | |||
| private String id; | |||
| /** 账号 */ | |||
| private String username; | |||
| /** 用户名 */ | |||
| private String realName; | |||
| /** 账号类型编码 */ | |||
| private String accountType; | |||
| /** 账号状态编码,0201401 为激活 */ | |||
| private String accountStatus; | |||
| /** 账号状态文本,如“激活” */ | |||
| private String accountStatusText; | |||
| /** 所属部门id(对应本地 sys_dept.ext_id) */ | |||
| private String deptId; | |||
| /** 所属部门名称 */ | |||
| private String deptName; | |||
| /** 手机号 */ | |||
| private String mobilePhone; | |||
| /** 邮箱 */ | |||
| private String email; | |||
| /** 所属场站id */ | |||
| private String boosterStationId; | |||
| /** 所属场站名称 */ | |||
| private String boosterStationName; | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package com.inspect.api.domain; | |||
| 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; | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| package com.inspect.api.domain; | |||
| import lombok.Data; | |||
| /** | |||
| * 外部盐源平台部门下用户(GET /ext_auth/getUserByDeptId)。 | |||
| */ | |||
| @Data | |||
| public class ExtUser { | |||
| /** 用户id */ | |||
| private String id; | |||
| /** 账号 */ | |||
| private String username; | |||
| /** 姓名 */ | |||
| private String realName; | |||
| /** 所属部门id */ | |||
| private String deptId; | |||
| /** 所属部门名称 */ | |||
| private String deptName; | |||
| /** 账号状态编码 */ | |||
| private String accountStatus; | |||
| /** 账号状态文本,如“激活” */ | |||
| private String accountStatusText; | |||
| /** 邮箱 */ | |||
| private String email; | |||
| /** 手机号 */ | |||
| private String mobilePhone; | |||
| /** 排序号 */ | |||
| private Integer sortNo; | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| package com.inspect.system.client; | |||
| import com.inspect.base.core.domain.Response; | |||
| import com.inspect.system.domain.ext.ExtDept; | |||
| import com.inspect.system.domain.ext.ExtUser; | |||
| import org.springframework.cloud.openfeign.FeignClient; | |||
| import org.springframework.web.bind.annotation.GetMapping; | |||
| import org.springframework.web.bind.annotation.RequestHeader; | |||
| import org.springframework.web.bind.annotation.RequestParam; | |||
| import java.util.List; | |||
| /** | |||
| * 调用 inspect-main 的内部接口拉取外部平台组织数据。 | |||
| * 所有请求必须携带 from-source: inner(定时任务线程没有请求上下文,需显式传入)。 | |||
| */ | |||
| @FeignClient(name = "inspect-main", contextId = "ext-org-sync") | |||
| public interface ExtAuthClient { | |||
| @GetMapping("/inner/ext_auth/getRootDept") | |||
| Response<List<ExtDept>> getRootDept(@RequestHeader("from-source") String source); | |||
| @GetMapping("/inner/ext_auth/getDeptBySuperId") | |||
| Response<List<ExtDept>> getDeptBySuperId(@RequestParam("superId") String superId, | |||
| @RequestHeader("from-source") String source); | |||
| @GetMapping("/inner/ext_auth/getUserByDeptId") | |||
| Response<List<ExtUser>> getUserByDeptId(@RequestParam("deptId") String deptId, | |||
| @RequestHeader("from-source") String source); | |||
| } | |||
| @ -0,0 +1,60 @@ | |||
| package com.inspect.system.controller; | |||
| import com.inspect.base.core.exception.ServiceException; | |||
| import com.inspect.base.core.web.domain.AjaxResult; | |||
| import com.inspect.common.security.annotation.InnerAuth; | |||
| import com.inspect.system.domain.ExtSyncResult; | |||
| import com.inspect.system.service.IExtOrgSyncService; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.web.bind.annotation.PostMapping; | |||
| import org.springframework.web.bind.annotation.RequestMapping; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| import java.util.function.Supplier; | |||
| /** | |||
| * 内部接口:供 inspect-job 的定时任务触发外部组织同步。 | |||
| * 仅允许 from-source=inner 的服务间调用。 | |||
| */ | |||
| @Slf4j | |||
| @RestController | |||
| @RequestMapping("/inner/ext_sync") | |||
| public class ExtSyncController { | |||
| private final IExtOrgSyncService extOrgSyncService; | |||
| public ExtSyncController(IExtOrgSyncService extOrgSyncService) { | |||
| this.extOrgSyncService = extOrgSyncService; | |||
| } | |||
| @InnerAuth | |||
| @PostMapping("/all") | |||
| public AjaxResult syncAll() { | |||
| return run(extOrgSyncService::syncAll); | |||
| } | |||
| @InnerAuth | |||
| @PostMapping("/dept") | |||
| public AjaxResult syncDept() { | |||
| return run(extOrgSyncService::syncDept); | |||
| } | |||
| @InnerAuth | |||
| @PostMapping("/user") | |||
| public AjaxResult syncUser() { | |||
| return run(extOrgSyncService::syncUser); | |||
| } | |||
| private AjaxResult run(Supplier<ExtSyncResult> action) { | |||
| try { | |||
| ExtSyncResult result = action.get(); | |||
| return AjaxResult.success(result.getSummary(), result); | |||
| } catch (ServiceException e) { | |||
| log.warn("[EXT-SYNC] 同步失败: {}", e.getMessage()); | |||
| return AjaxResult.error(e.getMessage()); | |||
| } catch (Exception e) { | |||
| log.error("[EXT-SYNC] 同步异常", e); | |||
| return AjaxResult.error("外部组织同步失败: " + e.getMessage()); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,45 @@ | |||
| package com.inspect.system.controller; | |||
| import com.inspect.base.core.constant.ExtAuthConstants; | |||
| import com.inspect.base.core.exception.ServiceException; | |||
| import com.inspect.base.core.web.domain.AjaxResult; | |||
| import com.inspect.common.security.annotation.InnerAuth; | |||
| import com.inspect.system.domain.ext.ExtUser; | |||
| import com.inspect.system.service.IExtOrgSyncService; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.web.bind.annotation.PostMapping; | |||
| import org.springframework.web.bind.annotation.RequestBody; | |||
| import org.springframework.web.bind.annotation.RequestMapping; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| /** | |||
| * 内部接口:供 dliip-auth 的 joinbrightLogin 按外部用户自动建号。 | |||
| * 仅允许 from-source=inner 的服务间调用,登录与定时同步共用同一套落库规则。 | |||
| */ | |||
| @Slf4j | |||
| @RestController | |||
| @RequestMapping("/inner/ext_user") | |||
| public class ExtUserController { | |||
| private final IExtOrgSyncService extOrgSyncService; | |||
| public ExtUserController(IExtOrgSyncService extOrgSyncService) { | |||
| this.extOrgSyncService = extOrgSyncService; | |||
| } | |||
| @InnerAuth | |||
| @PostMapping("/ensure") | |||
| public AjaxResult ensure(@RequestBody ExtUser extUser) { | |||
| try { | |||
| Long userId = extOrgSyncService.ensureUser(extUser); | |||
| return AjaxResult.success("用户已就绪", userId); | |||
| } 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, "外部用户同步失败,请稍后重试"); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,37 @@ | |||
| package com.inspect.system.domain; | |||
| import lombok.Data; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| /** | |||
| * 外部组织同步结果统计。 | |||
| */ | |||
| @Data | |||
| public class ExtSyncResult { | |||
| private int deptInsert; | |||
| private int deptUpdate; | |||
| private int deptBind; | |||
| private int deptDeactivate; | |||
| private int userInsert; | |||
| private int userUpdate; | |||
| private int userBind; | |||
| private int userSkip; | |||
| private int userDeactivate; | |||
| private int userRoleBind; | |||
| private long elapsedMillis; | |||
| /** 跳过/异常明细,便于在 sys_job_log 中排查 */ | |||
| private List<String> messages = new ArrayList<>(); | |||
| public void message(String message) { | |||
| this.messages.add(message); | |||
| } | |||
| public String getSummary() { | |||
| return String.format( | |||
| "部门(新增%d/更新%d/绑定%d/停用%d) 用户(新增%d/更新%d/绑定%d/跳过%d/停用%d) 默认角色绑定%d 耗时%dms", | |||
| deptInsert, deptUpdate, deptBind, deptDeactivate, | |||
| userInsert, userUpdate, userBind, userSkip, userDeactivate, userRoleBind, elapsedMillis); | |||
| } | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package com.inspect.system.domain.ext; | |||
| import lombok.Data; | |||
| /** | |||
| * 外部平台部门(对应 inspect-main-api 的 /inner/ext_auth/getRootDept、getDeptBySuperId 返回)。 | |||
| */ | |||
| @Data | |||
| public class ExtDept { | |||
| private String id; | |||
| private String deptName; | |||
| private String shortName; | |||
| private String deptCode; | |||
| private String superDept; | |||
| private String fullId; | |||
| private String fullName; | |||
| private Integer leaf; | |||
| } | |||
| @ -0,0 +1,15 @@ | |||
| package com.inspect.system.domain.ext; | |||
| import lombok.Data; | |||
| /** | |||
| * 本地已同步部门(用于停用对账)。 | |||
| */ | |||
| @Data | |||
| public class ExtDeptRef { | |||
| private Long deptId; | |||
| private String extId; | |||
| private Long parentId; | |||
| private String ancestors; | |||
| private String status; | |||
| } | |||
| @ -0,0 +1,20 @@ | |||
| package com.inspect.system.domain.ext; | |||
| import lombok.Data; | |||
| /** | |||
| * 外部平台用户(对应 inspect-main-api 的 /inner/ext_auth/getUserByDeptId 返回)。 | |||
| */ | |||
| @Data | |||
| public class ExtUser { | |||
| private String id; | |||
| private String username; | |||
| private String realName; | |||
| private String deptId; | |||
| private String deptName; | |||
| private String accountStatus; | |||
| private String accountStatusText; | |||
| private String email; | |||
| private String mobilePhone; | |||
| private Integer sortNo; | |||
| } | |||
| @ -0,0 +1,14 @@ | |||
| package com.inspect.system.domain.ext; | |||
| import lombok.Data; | |||
| /** | |||
| * 本地已同步用户(用于停用对账)。 | |||
| */ | |||
| @Data | |||
| public class ExtUserRef { | |||
| private Long userId; | |||
| private String extId; | |||
| private Long deptId; | |||
| private String status; | |||
| } | |||
| @ -0,0 +1,52 @@ | |||
| package com.inspect.system.mapper; | |||
| import com.inspect.system.base.domain.SysDept; | |||
| import com.inspect.system.base.domain.SysUser; | |||
| import com.inspect.system.domain.ext.ExtDeptRef; | |||
| import com.inspect.system.domain.ext.ExtUserRef; | |||
| import org.apache.ibatis.annotations.Param; | |||
| import java.util.List; | |||
| /** | |||
| * 外部组织同步专用 Mapper。 | |||
| * 独立于 SysDeptMapper / SysUserMapper,避免 ext_id 字段在未升级的库上影响现有功能。 | |||
| */ | |||
| public interface ExtOrgSyncMapper { | |||
| /* ---------------- 部门 ---------------- */ | |||
| Long selectDeptIdByExtId(@Param("extId") String extId); | |||
| Long selectDeptIdByParentAndName(@Param("parentId") Long parentId, @Param("deptName") String deptName); | |||
| int insertDept(SysDept dept); | |||
| int updateDept(SysDept dept); | |||
| int bindDeptExtId(@Param("deptId") Long deptId, @Param("extId") String extId); | |||
| List<ExtDeptRef> selectSyncedDepts(); | |||
| int deactivateDept(@Param("deptId") Long deptId); | |||
| /* ---------------- 用户 ---------------- */ | |||
| Long selectRoleIdByRoleKey(@Param("roleKey") String roleKey); | |||
| Long selectUserIdByExtId(@Param("extId") String extId); | |||
| SysUser selectUserById(@Param("userId") Long userId); | |||
| SysUser selectUserByUserName(@Param("userName") String userName); | |||
| int insertUser(SysUser user); | |||
| int updateUser(SysUser user); | |||
| int bindUserExtId(@Param("userId") Long userId, @Param("extId") String extId); | |||
| int insertUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId); | |||
| List<ExtUserRef> selectSyncedUsers(); | |||
| int deactivateUser(@Param("userId") Long userId); | |||
| } | |||
| @ -0,0 +1,27 @@ | |||
| package com.inspect.system.service; | |||
| import com.inspect.system.domain.ExtSyncResult; | |||
| import com.inspect.system.domain.ext.ExtUser; | |||
| /** | |||
| * 外部平台(盐源智慧感知平台)部门 / 用户同步。 | |||
| * <p>角色同步暂缓,后续确认后再补充。</p> | |||
| */ | |||
| public interface IExtOrgSyncService { | |||
| /** 部门 + 用户全量同步,并执行停用对账 */ | |||
| ExtSyncResult syncAll(); | |||
| /** 仅部门同步(含停用对账) */ | |||
| ExtSyncResult syncDept(); | |||
| /** 用户同步(部门作为前置一并同步,含用户停用对账) */ | |||
| ExtSyncResult syncUser(); | |||
| /** | |||
| * 单点登录自动建号:外部用户不存在则创建,本地已有同名账号则绑定并覆盖为外部数据。 | |||
| * | |||
| * @return 本地用户ID | |||
| */ | |||
| Long ensureUser(ExtUser extUser); | |||
| } | |||
| @ -0,0 +1,532 @@ | |||
| package com.inspect.system.service.impl; | |||
| import com.inspect.base.core.constant.ExtAuthConstants; | |||
| import com.inspect.base.core.constant.SecurityConstants; | |||
| import com.inspect.base.core.domain.Response; | |||
| import com.inspect.base.core.enums.UserType; | |||
| import com.inspect.base.core.exception.ServiceException; | |||
| import com.inspect.base.core.utils.StringUtils; | |||
| import com.inspect.base.redis.service.RedisService; | |||
| import com.inspect.common.security.utils.SecurityUtils; | |||
| import com.inspect.system.base.domain.SysDept; | |||
| import com.inspect.system.base.domain.SysUser; | |||
| import com.inspect.system.client.ExtAuthClient; | |||
| import com.inspect.system.domain.ExtSyncResult; | |||
| import com.inspect.system.domain.ext.ExtDept; | |||
| import com.inspect.system.domain.ext.ExtDeptRef; | |||
| import com.inspect.system.domain.ext.ExtUser; | |||
| import com.inspect.system.domain.ext.ExtUserRef; | |||
| import com.inspect.system.mapper.ExtOrgSyncMapper; | |||
| import com.inspect.system.service.IExtOrgSyncService; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.apache.commons.codec.digest.DigestUtils; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import java.util.ArrayDeque; | |||
| import java.util.ArrayList; | |||
| import java.util.Arrays; | |||
| import java.util.Collections; | |||
| import java.util.Deque; | |||
| import java.util.HashMap; | |||
| import java.util.HashSet; | |||
| import java.util.LinkedHashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.Set; | |||
| import java.util.concurrent.TimeUnit; | |||
| /** | |||
| * 外部平台组织同步:拉取 -> 转换 -> 落库 -> 停用对账。 | |||
| * <p>角色同步暂缓,本期只同步部门与用户。</p> | |||
| * <p>约定:</p> | |||
| * <ul> | |||
| * <li>外部数据先全部拉取成功后再落库,任一外部接口失败则整体中止,保留上次数据;</li> | |||
| * <li>按 ext_id 管理外部数据;同名账号自动绑定并覆盖 ext_id(admin/guest/htjc 除外);</li> | |||
| * <li>停用对账只处理 ext_id 非空且位于本次同步子树内的记录,手工数据不受影响。</li> | |||
| * </ul> | |||
| */ | |||
| @Slf4j | |||
| @Service | |||
| public class ExtOrgSyncServiceImpl implements IExtOrgSyncService { | |||
| private static final String SOURCE = SecurityConstants.INNER; | |||
| private static final String SYNC_BY = "ext_sync"; | |||
| private static final String REMARK = "外部平台同步"; | |||
| private static final String DEFAULT_PASSWORD = DigestUtils.md5Hex("abcd1234*"); | |||
| /** 同步用户默认角色:普通角色,可由 yanyuan.sync.default-role-key 覆盖(部分库为 common1) */ | |||
| @Value("${yanyuan.sync.default-role-key:common}") | |||
| private String defaultRoleKey; | |||
| private static final String LOCK_KEY = "ext:org:sync:lock"; | |||
| private static final String ENSURE_LOCK_PREFIX = "ext:user:ensure:"; | |||
| private static final long ENSURE_LOCK_SECONDS = 30L; | |||
| /** 平台内置账号,禁止被外部账号绑定/覆盖 */ | |||
| private static final Set<String> PROTECTED_USERNAMES = | |||
| new HashSet<>(Arrays.asList("admin", "htjc")); | |||
| private static final long LOCK_SECONDS = 1800L; | |||
| private static final int DEPT_NAME_MAX_LENGTH = 64; | |||
| private static final int USER_NAME_MAX_LENGTH = 30; | |||
| private static final int NICK_NAME_MAX_LENGTH = 30; | |||
| private static final int EMAIL_MAX_LENGTH = 50; | |||
| private static final int PHONE_MAX_LENGTH = 11; | |||
| private final ExtAuthClient extAuthClient; | |||
| private final ExtOrgSyncMapper extOrgSyncMapper; | |||
| private final RedisService redisService; | |||
| public ExtOrgSyncServiceImpl(ExtAuthClient extAuthClient, | |||
| ExtOrgSyncMapper extOrgSyncMapper, | |||
| RedisService redisService) { | |||
| this.extAuthClient = extAuthClient; | |||
| this.extOrgSyncMapper = extOrgSyncMapper; | |||
| this.redisService = redisService; | |||
| } | |||
| @Override | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public ExtSyncResult syncAll() { | |||
| return doSync(true); | |||
| } | |||
| @Override | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public ExtSyncResult syncDept() { | |||
| return doSync(false); | |||
| } | |||
| @Override | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public ExtSyncResult syncUser() { | |||
| return doSync(true); | |||
| } | |||
| /** | |||
| * @param withUser 是否同步用户;为 false 时仅同步部门并做部门停用对账 | |||
| */ | |||
| private ExtSyncResult doSync(boolean withUser) { | |||
| long start = System.currentTimeMillis(); | |||
| if (!tryLock()) { | |||
| throw new ServiceException("外部组织同步正在执行中,请稍后再试"); | |||
| } | |||
| ExtSyncResult result = new ExtSyncResult(); | |||
| try { | |||
| SyncContext context = fetchAll(withUser); | |||
| Map<String, Long> deptMap = persistDepts(context.nodes, result); | |||
| if (withUser) { | |||
| persistUsers(context, deptMap, result); | |||
| } | |||
| reconcileDepts(context, deptMap, result); | |||
| if (withUser) { | |||
| reconcileUsers(context, deptMap, result); | |||
| } | |||
| } finally { | |||
| unlock(LOCK_KEY); | |||
| } | |||
| result.setElapsedMillis(System.currentTimeMillis() - start); | |||
| log.info("[EXT-SYNC] 完成: {}", result.getSummary()); | |||
| for (String message : result.getMessages()) { | |||
| log.warn("[EXT-SYNC] {}", message); | |||
| } | |||
| return result; | |||
| } | |||
| // ------------------------------------------------------------------ 拉取 | |||
| private SyncContext fetchAll(boolean withUser) { | |||
| SyncContext context = new SyncContext(); | |||
| List<ExtDept> roots = unwrap(extAuthClient.getRootDept(SOURCE), "getRootDept"); | |||
| Deque<DeptNode> queue = new ArrayDeque<>(); | |||
| Set<String> visited = new HashSet<>(); | |||
| for (ExtDept root : roots) { | |||
| if (StringUtils.isEmpty(root.getId()) || !visited.add(root.getId())) { | |||
| continue; | |||
| } | |||
| DeptNode node = new DeptNode(root, null, 0); | |||
| context.nodes.add(node); | |||
| queue.add(node); | |||
| } | |||
| while (!queue.isEmpty()) { | |||
| DeptNode current = queue.poll(); | |||
| if (current.dept.getLeaf() == null || current.dept.getLeaf() <= 0) { | |||
| continue; | |||
| } | |||
| List<ExtDept> children = unwrap( | |||
| extAuthClient.getDeptBySuperId(current.dept.getId(), SOURCE), "getDeptBySuperId"); | |||
| int order = 0; | |||
| for (ExtDept child : children) { | |||
| if (StringUtils.isEmpty(child.getId()) || !visited.add(child.getId())) { | |||
| continue; | |||
| } | |||
| DeptNode node = new DeptNode(child, current.dept.getId(), ++order); | |||
| context.nodes.add(node); | |||
| queue.add(node); | |||
| } | |||
| } | |||
| if (withUser) { | |||
| for (DeptNode node : context.nodes) { | |||
| List<ExtUser> users = unwrap( | |||
| extAuthClient.getUserByDeptId(node.dept.getId(), SOURCE), "getUserByDeptId"); | |||
| for (ExtUser user : users) { | |||
| if (StringUtils.isEmpty(user.getId())) { | |||
| continue; | |||
| } | |||
| if (StringUtils.isEmpty(user.getDeptId())) { | |||
| user.setDeptId(node.dept.getId()); | |||
| } | |||
| context.users.putIfAbsent(user.getId(), user); | |||
| } | |||
| } | |||
| } | |||
| log.info("[EXT-SYNC] 拉取完成: 部门 {},用户 {}", context.nodes.size(), context.users.size()); | |||
| return context; | |||
| } | |||
| private <T> T unwrap(Response<T> response, String action) { | |||
| if (response == null || response.getCode() != Response.SUCCESS) { | |||
| String message = response == null ? "无响应" : response.getMsg(); | |||
| throw new ServiceException(action + "失败: " + message); | |||
| } | |||
| T data = response.getData(); | |||
| if (data == null) { | |||
| @SuppressWarnings("unchecked") | |||
| T empty = (T) Collections.emptyList(); | |||
| return empty; | |||
| } | |||
| return data; | |||
| } | |||
| // ------------------------------------------------------------------ 部门 | |||
| private Map<String, Long> persistDepts(List<DeptNode> nodes, ExtSyncResult result) { | |||
| Map<String, Long> extToLocal = new HashMap<>(); | |||
| Map<Long, String> ancestorsByLocalId = new HashMap<>(); | |||
| for (DeptNode node : nodes) { | |||
| String extId = node.dept.getId(); | |||
| String rawDeptName = node.dept.getDeptName(); | |||
| String deptName = truncate(rawDeptName, DEPT_NAME_MAX_LENGTH); | |||
| if (rawDeptName != null && rawDeptName.length() > DEPT_NAME_MAX_LENGTH) { | |||
| result.message("部门名称超长已截断: extId=" + extId + ",原名=" + rawDeptName); | |||
| } | |||
| Long parentLocalId = node.parentExtId == null ? 0L : extToLocal.get(node.parentExtId); | |||
| if (parentLocalId == null) { | |||
| result.message("跳过部门[" + deptName + "]:父部门未同步,extId=" + extId); | |||
| continue; | |||
| } | |||
| String ancestors = parentLocalId == 0L | |||
| ? "0" | |||
| : ancestorsByLocalId.get(parentLocalId) + "," + parentLocalId; | |||
| Long localId = extOrgSyncMapper.selectDeptIdByExtId(extId); | |||
| if (localId != null) { | |||
| extOrgSyncMapper.updateDept(buildDept(localId, extId, parentLocalId, ancestors, deptName, node.order)); | |||
| result.setDeptUpdate(result.getDeptUpdate() + 1); | |||
| } else { | |||
| Long sameName = extOrgSyncMapper.selectDeptIdByParentAndName(parentLocalId, deptName); | |||
| if (sameName != null) { | |||
| extOrgSyncMapper.bindDeptExtId(sameName, extId); | |||
| extOrgSyncMapper.updateDept(buildDept(sameName, extId, parentLocalId, ancestors, deptName, node.order)); | |||
| localId = sameName; | |||
| result.setDeptBind(result.getDeptBind() + 1); | |||
| result.message("绑定已存在同名部门[" + deptName + "] deptId=" + localId + " -> extId=" + extId); | |||
| } else { | |||
| SysDept dept = buildDept(null, extId, parentLocalId, ancestors, deptName, node.order); | |||
| extOrgSyncMapper.insertDept(dept); | |||
| localId = dept.getDeptId(); | |||
| result.setDeptInsert(result.getDeptInsert() + 1); | |||
| } | |||
| } | |||
| extToLocal.put(extId, localId); | |||
| ancestorsByLocalId.put(localId, ancestors); | |||
| } | |||
| return extToLocal; | |||
| } | |||
| private SysDept buildDept(Long deptId, String extId, Long parentId, String ancestors, | |||
| String deptName, Integer order) { | |||
| SysDept dept = new SysDept(); | |||
| dept.setDeptId(deptId); | |||
| dept.setExtId(extId); | |||
| dept.setParentId(parentId); | |||
| dept.setAncestors(ancestors); | |||
| dept.setDeptName(deptName); | |||
| dept.setOrderNum(String.valueOf(order == null ? 0 : order)); | |||
| dept.setCreateBy(SYNC_BY); | |||
| dept.setUpdateBy(SYNC_BY); | |||
| return dept; | |||
| } | |||
| // ------------------------------------------------------------------ 用户 | |||
| private void persistUsers(SyncContext context, Map<String, Long> deptMap, ExtSyncResult result) { | |||
| Long commonRoleId = requireCommonRole(); | |||
| for (ExtUser extUser : context.users.values()) { | |||
| if (StringUtils.isEmpty(extUser.getUsername())) { | |||
| result.setUserSkip(result.getUserSkip() + 1); | |||
| result.message("跳过用户:外部未返回账号,extId=" + extUser.getId()); | |||
| continue; | |||
| } | |||
| Long deptLocalId = deptMap.get(extUser.getDeptId()); | |||
| if (deptLocalId == null) { | |||
| result.setUserSkip(result.getUserSkip() + 1); | |||
| result.message("跳过用户[" + extUser.getUsername() + "]:所属部门未同步,deptId=" + extUser.getDeptId()); | |||
| continue; | |||
| } | |||
| upsertUser(extUser, deptLocalId, commonRoleId, result); | |||
| } | |||
| } | |||
| /** | |||
| * 单点登录自动建号:外部用户不存在则创建,本地已有同名账号则绑定并覆盖为外部数据。 | |||
| * 部门尚未同步时 dept_id 留空,等待下一次组织同步补齐。 | |||
| */ | |||
| @Override | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public Long ensureUser(ExtUser extUser) { | |||
| if (extUser == null || StringUtils.isEmpty(extUser.getId())) { | |||
| throw new ServiceException("外部用户信息缺少用户ID", ExtAuthConstants.TOKEN_INVALID); | |||
| } | |||
| if (StringUtils.isEmpty(extUser.getUsername())) { | |||
| throw new ServiceException("外部用户信息缺少账号", ExtAuthConstants.TOKEN_INVALID); | |||
| } | |||
| Long commonRoleId = requireCommonRole(); | |||
| Long deptLocalId = StringUtils.isEmpty(extUser.getDeptId()) | |||
| ? null : extOrgSyncMapper.selectDeptIdByExtId(extUser.getDeptId()); | |||
| String lockKey = ENSURE_LOCK_PREFIX + extUser.getId(); | |||
| boolean locked = acquireLock(lockKey, ENSURE_LOCK_SECONDS); | |||
| try { | |||
| ExtSyncResult result = new ExtSyncResult(); | |||
| SysUser user = upsertUser(extUser, deptLocalId, commonRoleId, result); | |||
| for (String message : result.getMessages()) { | |||
| log.info("[EXT-LOGIN] {}", message); | |||
| } | |||
| return user.getUserId(); | |||
| } finally { | |||
| if (locked) { | |||
| unlock(lockKey); | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| * 用户落库:按 ext_id 更新;无 ext_id 记录时优先绑定同名账号(覆盖 ext_id),否则新建。 | |||
| */ | |||
| private SysUser upsertUser(ExtUser extUser, Long deptLocalId, Long commonRoleId, ExtSyncResult result) { | |||
| String username = extUser.getUsername(); | |||
| if (StringUtils.isEmpty(username)) { | |||
| throw new ServiceException("外部用户信息缺少账号", ExtAuthConstants.TOKEN_INVALID); | |||
| } | |||
| if (username.length() > USER_NAME_MAX_LENGTH) { | |||
| throw new ServiceException("外部账号[" + username + "]超过本地账号长度上限 " | |||
| + USER_NAME_MAX_LENGTH + ",请联系管理员处理", ExtAuthConstants.USER_CONFLICT); | |||
| } | |||
| String nickName = truncate(StringUtils.isNotEmpty(extUser.getRealName()) ? extUser.getRealName() : username, | |||
| NICK_NAME_MAX_LENGTH); | |||
| String status = resolveUserStatus(extUser); | |||
| Long userId = extOrgSyncMapper.selectUserIdByExtId(extUser.getId()); | |||
| if (userId != null) { | |||
| updateUser(extUser, userId, deptLocalId, nickName, status); | |||
| result.setUserUpdate(result.getUserUpdate() + 1); | |||
| return extOrgSyncMapper.selectUserById(userId); | |||
| } | |||
| SysUser exists = extOrgSyncMapper.selectUserByUserName(username); | |||
| if (exists != null) { | |||
| if (PROTECTED_USERNAMES.contains(username.toLowerCase())) { | |||
| throw new ServiceException("账号[" + username + "]为平台受保护账号,不允许被外部账号绑定", | |||
| ExtAuthConstants.USER_CONFLICT); | |||
| } | |||
| if (!"0".equals(exists.getDelFlag())) { | |||
| throw new ServiceException("账号[" + username + "]已删除,请联系管理员处理", | |||
| ExtAuthConstants.USER_CONFLICT); | |||
| } | |||
| if (StringUtils.isNotEmpty(exists.getExtId()) && !exists.getExtId().equals(extUser.getId())) { | |||
| result.message("同名账号[" + username + "]原 extId=" + exists.getExtId() | |||
| + ",按外部数据改绑为 extId=" + extUser.getId()); | |||
| } | |||
| extOrgSyncMapper.bindUserExtId(exists.getUserId(), extUser.getId()); | |||
| updateUser(extUser, exists.getUserId(), deptLocalId, nickName, status); | |||
| extOrgSyncMapper.insertUserRole(exists.getUserId(), commonRoleId); | |||
| result.setUserBind(result.getUserBind() + 1); | |||
| result.message("绑定已存在同名账号[" + username + "] userId=" + exists.getUserId() | |||
| + " -> extId=" + extUser.getId()); | |||
| return extOrgSyncMapper.selectUserById(exists.getUserId()); | |||
| } | |||
| SysUser insert = new SysUser(); | |||
| insert.setExtId(extUser.getId()); | |||
| insert.setDeptId(deptLocalId); | |||
| insert.setUserName(username); | |||
| insert.setNickName(nickName); | |||
| insert.setEmail(truncate(extUser.getEmail(), EMAIL_MAX_LENGTH)); | |||
| insert.setPhonenumber(truncate(extUser.getMobilePhone(), PHONE_MAX_LENGTH)); | |||
| insert.setPassword(SecurityUtils.encryptPassword(DEFAULT_PASSWORD)); | |||
| insert.setStatus(status); | |||
| insert.setCreateBy(SYNC_BY); | |||
| insert.setRemark(REMARK); | |||
| insert.setUserType(UserType.EXTERNAL.getCode()); | |||
| insert.setAollowIp("*"); | |||
| extOrgSyncMapper.insertUser(insert); | |||
| extOrgSyncMapper.insertUserRole(insert.getUserId(), commonRoleId); | |||
| result.setUserInsert(result.getUserInsert() + 1); | |||
| result.setUserRoleBind(result.getUserRoleBind() + 1); | |||
| return insert; | |||
| } | |||
| private void updateUser(ExtUser extUser, Long userId, Long deptLocalId, String nickName, String status) { | |||
| SysUser update = new SysUser(); | |||
| update.setUserId(userId); | |||
| update.setUserType(UserType.EXTERNAL.getCode()); | |||
| update.setNickName(nickName); | |||
| update.setDeptId(deptLocalId); | |||
| update.setStatus(status); | |||
| update.setEmail(truncate(extUser.getEmail(), EMAIL_MAX_LENGTH)); | |||
| update.setPhonenumber(truncate(extUser.getMobilePhone(), PHONE_MAX_LENGTH)); | |||
| update.setUpdateBy(SYNC_BY); | |||
| extOrgSyncMapper.updateUser(update); | |||
| } | |||
| /** | |||
| * 外部账号状态:优先按文本“激活”判断,其次按编码 0201401,均缺失时视为正常。 | |||
| */ | |||
| private String resolveUserStatus(ExtUser extUser) { | |||
| if (StringUtils.isNotEmpty(extUser.getAccountStatusText())) { | |||
| return "激活".equals(extUser.getAccountStatusText()) ? "0" : "1"; | |||
| } | |||
| if (StringUtils.isNotEmpty(extUser.getAccountStatus())) { | |||
| return "0201401".equals(extUser.getAccountStatus()) ? "0" : "1"; | |||
| } | |||
| return "0"; | |||
| } | |||
| private Long requireCommonRole() { | |||
| Long commonRoleId = extOrgSyncMapper.selectRoleIdByRoleKey(defaultRoleKey); | |||
| if (commonRoleId == null) { | |||
| throw new ServiceException("本地不存在 role_key=" + defaultRoleKey + " 的角色,外部用户同步已中止"); | |||
| } | |||
| return commonRoleId; | |||
| } | |||
| // ------------------------------------------------------------------ 停用对账 | |||
| private void reconcileDepts(SyncContext context, Map<String, Long> deptMap, ExtSyncResult result) { | |||
| if (deptMap.isEmpty()) { | |||
| return; | |||
| } | |||
| Set<String> currentExtIds = new HashSet<>(); | |||
| Set<Long> rootLocalIds = new HashSet<>(); | |||
| for (DeptNode node : context.nodes) { | |||
| currentExtIds.add(node.dept.getId()); | |||
| if (node.parentExtId == null && deptMap.containsKey(node.dept.getId())) { | |||
| rootLocalIds.add(deptMap.get(node.dept.getId())); | |||
| } | |||
| } | |||
| for (ExtDeptRef ref : extOrgSyncMapper.selectSyncedDepts()) { | |||
| if (!"0".equals(ref.getStatus()) || currentExtIds.contains(ref.getExtId())) { | |||
| continue; | |||
| } | |||
| if (!inScope(ref, rootLocalIds)) { | |||
| continue; | |||
| } | |||
| extOrgSyncMapper.deactivateDept(ref.getDeptId()); | |||
| result.setDeptDeactivate(result.getDeptDeactivate() + 1); | |||
| result.message("停用部门:deptId=" + ref.getDeptId() + ",extId=" + ref.getExtId()); | |||
| } | |||
| } | |||
| private boolean inScope(ExtDeptRef ref, Set<Long> rootLocalIds) { | |||
| if (rootLocalIds.contains(ref.getDeptId())) { | |||
| return true; | |||
| } | |||
| if (StringUtils.isEmpty(ref.getAncestors())) { | |||
| return false; | |||
| } | |||
| for (String part : ref.getAncestors().split(",")) { | |||
| if (StringUtils.isEmpty(part)) { | |||
| continue; | |||
| } | |||
| try { | |||
| if (rootLocalIds.contains(Long.valueOf(part.trim()))) { | |||
| return true; | |||
| } | |||
| } catch (NumberFormatException ignored) { | |||
| // 非法 ancestors 忽略 | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| private void reconcileUsers(SyncContext context, Map<String, Long> deptMap, ExtSyncResult result) { | |||
| if (deptMap.isEmpty()) { | |||
| return; | |||
| } | |||
| Set<Long> scopedDeptIds = new HashSet<>(deptMap.values()); | |||
| Set<String> currentExtIds = new HashSet<>(context.users.keySet()); | |||
| for (ExtUserRef ref : extOrgSyncMapper.selectSyncedUsers()) { | |||
| if (!"0".equals(ref.getStatus()) || currentExtIds.contains(ref.getExtId())) { | |||
| continue; | |||
| } | |||
| if (ref.getDeptId() == null || !scopedDeptIds.contains(ref.getDeptId())) { | |||
| continue; | |||
| } | |||
| extOrgSyncMapper.deactivateUser(ref.getUserId()); | |||
| result.setUserDeactivate(result.getUserDeactivate() + 1); | |||
| result.message("停用用户:userId=" + ref.getUserId() + ",extId=" + ref.getExtId()); | |||
| } | |||
| } | |||
| // ------------------------------------------------------------------ 工具 | |||
| private boolean tryLock() { | |||
| return acquireLock(LOCK_KEY, LOCK_SECONDS); | |||
| } | |||
| private boolean acquireLock(String key, long seconds) { | |||
| for (int i = 0; i < 10; i++) { | |||
| Boolean success = redisService.redisTemplate.opsForValue() | |||
| .setIfAbsent(key, String.valueOf(System.currentTimeMillis()), seconds, TimeUnit.SECONDS); | |||
| if (Boolean.TRUE.equals(success)) { | |||
| return true; | |||
| } | |||
| try { | |||
| Thread.sleep(200L); | |||
| } catch (InterruptedException e) { | |||
| Thread.currentThread().interrupt(); | |||
| return false; | |||
| } | |||
| } | |||
| log.warn("[EXT-SYNC] 等待锁 {} 超时,本次不加锁执行", key); | |||
| return false; | |||
| } | |||
| private void unlock(String key) { | |||
| try { | |||
| redisService.redisTemplate.delete(key); | |||
| } catch (Exception e) { | |||
| log.warn("[EXT-SYNC] 释放锁 {} 失败", key, e); | |||
| } | |||
| } | |||
| private String truncate(String value, int maxLength) { | |||
| if (value == null) { | |||
| return ""; | |||
| } | |||
| return value.length() <= maxLength ? value : value.substring(0, maxLength); | |||
| } | |||
| private static class DeptNode { | |||
| private final ExtDept dept; | |||
| private final String parentExtId; | |||
| private final Integer order; | |||
| private DeptNode(ExtDept dept, String parentExtId, Integer order) { | |||
| this.dept = dept; | |||
| this.parentExtId = parentExtId; | |||
| this.order = order; | |||
| } | |||
| } | |||
| private static class SyncContext { | |||
| private final List<DeptNode> nodes = new ArrayList<>(); | |||
| private final Map<String, ExtUser> users = new LinkedHashMap<>(); | |||
| } | |||
| } | |||
| @ -0,0 +1,171 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <!DOCTYPE mapper | |||
| PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" | |||
| "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
| <mapper namespace="com.inspect.system.mapper.ExtOrgSyncMapper"> | |||
| <resultMap type="com.inspect.system.base.domain.SysDept" id="DeptResult"> | |||
| <id property="deptId" column="dept_id"/> | |||
| <result property="extId" column="ext_id"/> | |||
| <result property="parentId" column="parent_id"/> | |||
| <result property="ancestors" column="ancestors"/> | |||
| <result property="deptName" column="dept_name"/> | |||
| <result property="orderNum" column="order_num"/> | |||
| <result property="status" column="status"/> | |||
| <result property="delFlag" column="del_flag"/> | |||
| <result property="createBy" column="create_by"/> | |||
| <result property="createTime" column="create_time"/> | |||
| <result property="updateBy" column="update_by"/> | |||
| <result property="updateTime" column="update_time"/> | |||
| </resultMap> | |||
| <resultMap type="com.inspect.system.base.domain.SysUser" id="UserResult"> | |||
| <id property="userId" column="user_id"/> | |||
| <result property="extId" column="ext_id"/> | |||
| <result property="deptId" column="dept_id"/> | |||
| <result property="userName" column="user_name"/> | |||
| <result property="nickName" column="nick_name"/> | |||
| <result property="userType" column="user_type"/> | |||
| <result property="email" column="email"/> | |||
| <result property="phonenumber" column="phonenumber"/> | |||
| <result property="sex" column="sex"/> | |||
| <result property="avatar" column="avatar"/> | |||
| <result property="password" column="password"/> | |||
| <result property="status" column="status"/> | |||
| <result property="delFlag" column="del_flag"/> | |||
| <result property="remark" column="remark"/> | |||
| </resultMap> | |||
| <resultMap type="com.inspect.system.domain.ext.ExtDeptRef" id="DeptRefResult"> | |||
| <result property="deptId" column="dept_id"/> | |||
| <result property="extId" column="ext_id"/> | |||
| <result property="parentId" column="parent_id"/> | |||
| <result property="ancestors" column="ancestors"/> | |||
| <result property="status" column="status"/> | |||
| </resultMap> | |||
| <resultMap type="com.inspect.system.domain.ext.ExtUserRef" id="UserRefResult"> | |||
| <result property="userId" column="user_id"/> | |||
| <result property="extId" column="ext_id"/> | |||
| <result property="deptId" column="dept_id"/> | |||
| <result property="status" column="status"/> | |||
| </resultMap> | |||
| <!-- ============================ 部门 ============================ --> | |||
| <select id="selectDeptIdByExtId" resultType="java.lang.Long"> | |||
| select dept_id from sys_dept where ext_id = #{extId} limit 1 | |||
| </select> | |||
| <select id="selectDeptIdByParentAndName" resultType="java.lang.Long"> | |||
| select dept_id | |||
| from sys_dept | |||
| where parent_id = #{parentId} | |||
| and dept_name = #{deptName} | |||
| and ext_id is null | |||
| and del_flag = '0' | |||
| limit 1 | |||
| </select> | |||
| <insert id="insertDept" parameterType="com.inspect.system.base.domain.SysDept" | |||
| useGeneratedKeys="true" keyProperty="deptId"> | |||
| insert into sys_dept (ext_id, parent_id, ancestors, dept_name, order_num, status, del_flag, create_by, create_time) | |||
| values (#{extId}, #{parentId}, #{ancestors}, #{deptName}, #{orderNum}, '0', '0', #{createBy}, now()) | |||
| </insert> | |||
| <update id="updateDept" parameterType="com.inspect.system.base.domain.SysDept"> | |||
| update sys_dept | |||
| set parent_id = #{parentId}, | |||
| ancestors = #{ancestors}, | |||
| dept_name = #{deptName}, | |||
| order_num = #{orderNum}, | |||
| status = '0', | |||
| update_by = #{updateBy}, | |||
| update_time = now() | |||
| where dept_id = #{deptId} | |||
| </update> | |||
| <update id="bindDeptExtId"> | |||
| update sys_dept | |||
| set ext_id = #{extId}, update_by = 'ext_sync', update_time = now() | |||
| where dept_id = #{deptId} | |||
| </update> | |||
| <select id="selectSyncedDepts" resultMap="DeptRefResult"> | |||
| select dept_id, ext_id, parent_id, ancestors, status | |||
| from sys_dept | |||
| where ext_id is not null | |||
| </select> | |||
| <update id="deactivateDept"> | |||
| update sys_dept | |||
| set status = '1', update_by = 'ext_sync', update_time = now() | |||
| where dept_id = #{deptId} and status = '0' | |||
| </update> | |||
| <!-- ============================ 用户 ============================ --> | |||
| <select id="selectRoleIdByRoleKey" resultType="java.lang.Long"> | |||
| select role_id from sys_role where role_key = #{roleKey} and del_flag = '0' limit 1 | |||
| </select> | |||
| <select id="selectUserIdByExtId" resultType="java.lang.Long"> | |||
| select user_id from sys_user where ext_id = #{extId} limit 1 | |||
| </select> | |||
| <select id="selectUserById" resultMap="UserResult"> | |||
| select user_id, ext_id, dept_id, user_name, nick_name, status, del_flag | |||
| from sys_user | |||
| where user_id = #{userId} | |||
| </select> | |||
| <select id="selectUserByUserName" resultMap="UserResult"> | |||
| select user_id, ext_id, dept_id, user_name, nick_name, status, del_flag | |||
| from sys_user | |||
| where user_name = #{userName} | |||
| order by del_flag asc | |||
| limit 1 | |||
| </select> | |||
| <insert id="insertUser" parameterType="com.inspect.system.base.domain.SysUser" | |||
| useGeneratedKeys="true" keyProperty="userId"> | |||
| insert into sys_user (ext_id, dept_id, user_name, nick_name, user_type, email, phonenumber, sex, | |||
| avatar, password, status, del_flag, create_by, create_time, remark) | |||
| values (#{extId}, #{deptId}, #{userName}, #{nickName}, #{userType}, #{email}, #{phonenumber}, '0', | |||
| '', #{password}, #{status}, '0', #{createBy}, now(), #{remark}) | |||
| </insert> | |||
| <update id="updateUser" parameterType="com.inspect.system.base.domain.SysUser"> | |||
| update sys_user | |||
| set nick_name = #{nickName}, | |||
| user_type = #{userType}, | |||
| <if test="deptId != null">dept_id = #{deptId},</if> | |||
| status = #{status}, | |||
| <if test="email != null and email != ''">email = #{email},</if> | |||
| <if test="phonenumber != null and phonenumber != ''">phonenumber = #{phonenumber},</if> | |||
| update_by = #{updateBy}, | |||
| update_time = now() | |||
| where user_id = #{userId} | |||
| </update> | |||
| <update id="bindUserExtId"> | |||
| update sys_user | |||
| set ext_id = #{extId}, update_by = 'ext_sync', update_time = now() | |||
| where user_id = #{userId} | |||
| </update> | |||
| <insert id="insertUserRole"> | |||
| insert ignore into sys_user_role (user_id, role_id) values (#{userId}, #{roleId}) | |||
| </insert> | |||
| <select id="selectSyncedUsers" resultMap="UserRefResult"> | |||
| select user_id, ext_id, dept_id, status | |||
| from sys_user | |||
| where ext_id is not null | |||
| </select> | |||
| <update id="deactivateUser"> | |||
| update sys_user | |||
| set status = '1', update_by = 'ext_sync', update_time = now() | |||
| where user_id = #{userId} and status = '0' | |||
| </update> | |||
| </mapper> | |||