1.修改1101(求职者)、1102(招聘者)、1103(网格员)、1104(内部工作者)

2.添加门户认证的类
This commit is contained in:
sh
2025-11-15 14:04:05 +08:00
parent 6b376ad8e1
commit 4716127fc2
9 changed files with 680 additions and 11 deletions

View File

@@ -5,6 +5,7 @@ import java.util.Set;
import com.ruoyi.common.core.domain.model.RegisterBody; import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.OauthLoginService;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -34,6 +35,8 @@ public class SysLoginController
@Autowired @Autowired
private SysPermissionService permissionService; private SysPermissionService permissionService;
@Autowired
private OauthLoginService oauthLoginService;
/** /**
* 登录方法 * 登录方法
@@ -166,4 +169,18 @@ public class SysLoginController
String token=loginService.registerAppUser(registerBody); String token=loginService.registerAppUser(registerBody);
return AjaxResult.success().put("token",token); return AjaxResult.success().put("token",token);
} }
/**
* 获取统一门户token
*/
@GetMapping("/getTjmhToken")
public AjaxResult getTjmhToken(@RequestParam("code") String code){
if(StringUtils.isBlank(code)){
return AjaxResult.error("参数code为空请传递code参数");
}
String token = oauthLoginService.oauthLogin(code);
return AjaxResult.success()
.put("token", token)
.put("msg", "登录成功");
}
} }

View File

@@ -283,7 +283,7 @@ public class SysUserController extends BaseController
} }
sysUser.setPhonenumber(getUsername()); sysUser.setPhonenumber(getUsername());
Long[] postIds = {1L}; Long[] postIds = {1L};
Long[] roleIds = {100L}; Long[] roleIds = {1102L};
sysUser.setPostIds(postIds); sysUser.setPostIds(postIds);
sysUser.setRoleIds(roleIds); sysUser.setRoleIds(roleIds);
userService.insertUser(sysUser); userService.insertUser(sysUser);

View File

@@ -147,10 +147,38 @@ wx:
#统一门户认证 #统一门户认证
oauth: oauth:
appid: aa #客户端的ID
clientsecretkey: bb appid: 251112100000000015
getToken: http://ip:80/serviceAPI/getToken #授权码
getUserInfo: http://ip:80/serviceAPI/getUserInfo clientsecretkey: 2a44cb8d21dcf4b0777881ca11ea0d83ebea94bbe1ab1f405508db0873cdcc99
#内网
usptnw:
#获取访问令牌
nwGatewayGetTokenUrl: http://10.98.80.146/uspt/serviceAPI/getToken
#获取用户信息
nwGatewayGetUserInfoUrl: http://10.98.80.146/uspt/serviceAPI/getUserInfo
#外网
usptww:
#门户注册
wwRegisterPostUrl: http://ip:80/whiteListServiceAPI/doWebRegister
#门户登录
wwTokenPostUrl: http://ip:80/whiteListServiceAPI/doWebLogon
#查询个人信息
wwQueryWebPersonalInfoPostUrl: http://ip:80/serviceAPI/queryWebPersonalInfo
#查询单位信息
wwQueryWebEnterpriseInfoPostUrl: http://ip:80/serviceAPI/queryWebEnterpriseInfo
#用户新增接口
tyAddUserUrl: http://10.98.80.146/qxgl_backend/security/add_user
#获取当前用户有权系统列表
tyQueryUserSysListUrl: http://10.98.80.146/qxgl_backend/security/get_effective_app_list
#获取当前用户有权角色列表
tyQueryUserRoleListUrl: http://10.98.80.146/qxgl_backend/security/get_role_by_userid
#获取角色功能权限信息
tyQueryRoleInfoUrl: http://10.98.80.146/qxgl_backend/security/get_path_by_role
#获取用户详细信息
tyQueryUserInfo: http://10.98.80.146/qxgl_backend/security/get_user_by_userid
#获取机构详细信息
tyQueryUnitInfo: http://10.98.80.146/qxgl_backend/security/get_organization_by_organizationid
connect-timeout: 10 connect-timeout: 10
read-timeout: 30 read-timeout: 30
write-timeout: 30 write-timeout: 30

View File

@@ -1,7 +1,5 @@
package com.ruoyi.cms.util; package com.ruoyi.cms.util;
import com.ruoyi.cms.domain.query.ESJobSearch;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -10,7 +8,8 @@ import java.util.stream.Collectors;
public class StringUtil { public class StringUtil {
public static final Long COMPANY_ADMIN_ROLE_KEY = 100L; /*1101(求职者)、1102(招聘者)、1103(网格员)、1104(内部工作者)*/
public static final Long COMPANY_ADMIN_ROLE_KEY = 1002L;
/** /**
* 企业用户 * 企业用户
@@ -20,11 +19,11 @@ public class StringUtil {
/** /**
* pc端-求职者 * pc端-求职者
*/ */
public static final String SYS_QZZ = "2"; public static final String SYS_QZZ = "1101";
/** /**
* pc端-企业 * pc端-企业
*/ */
public static final String SYS_QY = "100"; public static final String SYS_QY = "1102";
public static Boolean isEmptyOrNull(String s){ public static Boolean isEmptyOrNull(String s){
if(Objects.isNull(s)){return true;} if(Objects.isNull(s)){return true;}

View File

@@ -0,0 +1,151 @@
package com.ruoyi.cms.util.oauth;
import com.alibaba.fastjson2.JSON;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 简化版 HTTP 客户端工具类仅保留核心POST功能保留超时配置
*/
public class HttpUtils {
private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
// 保留默认超时配置
private static final int DEFAULT_CONNECT_TIMEOUT = 10;
private static final int DEFAULT_READ_TIMEOUT = 30;
private static final int DEFAULT_WRITE_TIMEOUT = 30;
// 媒体类型常量
public static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
public static final MediaType FORM_MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded; charset=utf-8");
// OkHttp 客户端(单例)
private static final OkHttpClient OK_HTTP_CLIENT;
static {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS);
OK_HTTP_CLIENT = builder.build();
}
/**
* POST JSON请求默认超时
*/
public static String doPostJson(String url, Map<String, Object> params) throws TimeoutException, IOException {
return doPostJson(url, params, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT);
}
/**
* POST JSON请求自定义超时
*/
public static String doPostJson(String url, Map<String, Object> params, int connectTimeout, int readTimeout, int writeTimeout)
throws TimeoutException, IOException {
String jsonParams = CollectionUtils.isEmpty(params) ? "{}" : JSON.toJSONString(params);
return doPost(url, jsonParams, JSON_MEDIA_TYPE, null, connectTimeout, readTimeout, writeTimeout);
}
/**
* POST 表单请求(默认超时)
*/
public static String doPostForm(String url, Map<String, Object> params) throws TimeoutException, IOException {
return doPostForm(url, params, null, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT);
}
/**
* POST 表单请求(自定义超时)
*/
public static String doPostForm(String url, Map<String, Object> params, Map<String, String> headers,
int connectTimeout, int readTimeout, int writeTimeout)
throws TimeoutException, IOException {
FormBody.Builder formBuilder = new FormBody.Builder();
if (!CollectionUtils.isEmpty(params)) {
params.forEach((key, value) -> {
if (value != null) {
formBuilder.add(encodeParam(key), value.toString());
}
});
}
RequestBody requestBody = formBuilder.build();
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.post(requestBody)
.header("Content-Type", FORM_MEDIA_TYPE.toString());
addHeaders(requestBuilder, headers);
return executeRequest(requestBuilder.build(), connectTimeout, readTimeout, writeTimeout);
}
/**
* 通用POST方法
*/
private static String doPost(String url, String content, MediaType mediaType, Map<String, String> headers,
int connectTimeout, int readTimeout, int writeTimeout)
throws TimeoutException, IOException {
if (mediaType == null) {
mediaType = JSON_MEDIA_TYPE;
}
RequestBody requestBody = RequestBody.create(mediaType,content);
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.post(requestBody)
.header("Content-Type", mediaType.toString());
addHeaders(requestBuilder, headers);
return executeRequest(requestBuilder.build(), connectTimeout, readTimeout, writeTimeout);
}
/**
* 执行请求核心方法
*/
private static String executeRequest(Request request, int connectTimeout, int readTimeout, int writeTimeout)
throws TimeoutException, IOException {
// 保留超时配置覆盖能力
OkHttpClient tempClient = OK_HTTP_CLIENT.newBuilder()
.connectTimeout(connectTimeout, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.writeTimeout(writeTimeout, TimeUnit.SECONDS)
.build();
try (Response response = tempClient.newCall(request).execute()) {
return response.body() != null ? response.body().string() : "";
} catch (SocketTimeoutException e) {
throw new TimeoutException(String.format("HTTP 请求超时 | URL: %s | 超时配置: 连接%d秒, 读取%d秒, 写入%d秒",
request.url(), connectTimeout, readTimeout, writeTimeout));
}
}
/**
* 参数编码
*/
private static String encodeParam(String param) {
try {
return URLEncoder.encode(param, "UTF-8");
} catch (Exception e) {
log.warn("参数编码失败 | param: {}", param, e);
return param;
}
}
/**
* 添加请求头
*/
private static void addHeaders(Request.Builder requestBuilder, Map<String, String> headers) {
if (!CollectionUtils.isEmpty(headers)) {
headers.forEach(requestBuilder::header);
}
}
}

View File

@@ -0,0 +1,253 @@
package com.ruoyi.cms.util.oauth;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwTokenResult;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwUserInfoResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.crypto.CryptoUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* OAuth2.0 认证工具类(合并相似方法)
*/
@Component
public class OauthClient {
private static final Logger log = LoggerFactory.getLogger(OauthClient.class);
/*====================== 内网 ======================*/
// 网关请求-内网获取token
@Value("${oauth.usptnw.nwGatewayGetTokenUrl}")
private String nwGatewayGetTokenUrl;
// 网关请求-内网获取用户信息
@Value("${oauth.usptnw.nwGatewayGetUserInfoUrl}")
private String nwGatewayGetUserInfoUrl;
/*====================== 外网 ======================*/
//post-外网注册地址
@Value("${oauth.usptww.wwRegisterPostUrl}")
private String wwRegisterPostUrl;
// post-外网获取token
@Value("${oauth.usptww.wwTokenPostUrl}")
private String wwTokenPostUrl;
//post-外网获取用户信息
@Value("${oauth.usptww.wwQueryWebPersonalInfoPostUrl}")
private String wwQueryWebPersonalInfoPostUrl;
//post-外网获取单位信息
@Value("${oauth.usptww.wwQueryWebEnterpriseInfoPostUrl}")
private String wwQueryWebEnterpriseInfoPostUrl;
/*====================== 统一门户 ======================*/
//用户新增接口
@Value("${oauth.tyAddUserUrl}")
private String tyAddUserUrl;
//获取当前用户有权系统列表
@Value("${oauth.tyQueryUserSysListUrl}")
private String tyQueryUserSysListUrl;
//获取当前用户有权角色列表
@Value("${oauth.tyQueryUserRoleListUrl}")
private String tyQueryUserRoleListUrl;
//获取角色功能权限信息
@Value("${oauth.tyQueryRoleInfoUrl}")
private String tyQueryRoleInfoUrl;
//获取用户详细信息
@Value("${oauth.tyQueryUserInfo}")
private String tyQueryUserInfo;
//获取机构详细信息
@Value("${oauth.tyQueryUnitInfo}")
private String tyQueryUnitInfo;
//客户端id
@Value("${oauth.appid}")
private String appid;
//授权码
@Value("${oauth.clientsecretkey}")
private String clientsecretkey;
// 超时配置
@Value("${oauth.connect-timeout:10}")
private int connectTimeout;
@Value("${oauth.read-timeout:30}")
private int readTimeout;
@Value("${oauth.write-timeout:30}")
private int writeTimeout;
/**
* 获取经办段token
* @return
*/
public NwTokenResult nwGetToken(String code) throws IOException, TimeoutException {
if (StringUtils.isEmpty(code)) {
log.error("获取Token失败授权码code不能为空");
throw new IllegalArgumentException("授权码code不能为空");
}
String decryptedCode;
try {
decryptedCode = CryptoUtil.sm2Decrypt(clientsecretkey, code);
if (StringUtils.isEmpty(decryptedCode)) {
throw new RuntimeException("授权码解密后为空");
}
} catch (Exception e) {
log.error("授权码解密失败 | 原始code: {}", code, e);
throw new RuntimeException("授权码解密失败:" + e.getMessage(), e);
}
Map<String, Object> params = new HashMap<>(2);
params.put("appid", appid);
params.put("code", decryptedCode);
NwTokenResult result = executePostRequest(
nwGatewayGetTokenUrl,
params,
new TypeReference<Response<NwTokenResult>>() {},
"获取Token"
);
if (StringUtils.isEmpty(result.getAccessToken())) {
throw new RuntimeException("Token获取失败返回的accessToken为空");
}
return result;
}
/**
* 获取经办段用户id
* @return
*/
public NwUserInfoResult nwGetUserInfo(String accessToken) throws IOException, TimeoutException {
Map<String, Object> params = new HashMap<>(2);
params.put("appid", appid);
params.put("access_token", accessToken);
return executePostRequest(
nwGatewayGetUserInfoUrl,
params,
new TypeReference<Response<NwUserInfoResult>>() {},
"获取用户信息"
);
}
/**
* 公共POST请求执行方法
* @param url 请求URL
* @param params 请求参数
* @param typeReference 响应类型引用
* @param operationName 操作名称(用于日志和异常信息)
* @return 响应数据对象
*/
private <T> T executePostRequest(String url, Map<String, Object> params,
TypeReference<Response<T>> typeReference, String operationName)
throws IOException, TimeoutException {
String responseJson = null;
try {
responseJson = HttpUtils.doPostJson(
url,
params,
connectTimeout,
readTimeout,
writeTimeout
);
}catch (Exception e){
e.printStackTrace();
}
return validateResponse(responseJson, operationName, typeReference);
}
/**
* 公共响应校验工具方法(网关响应 + 业务 errflag 校验)
*/
private <T> T validateResponse(String responseJson, String operationName, TypeReference<Response<T>> typeReference) {
// 1. 校验响应是否为空
if (StringUtils.isEmpty(responseJson)) {
String errorMsg = operationName + "失败:接口返回空响应";
log.error(errorMsg);
throw new BusinessException("EMPTY_RESPONSE", errorMsg);
}
log.debug("{}接口返回原始数据: {}", operationName, responseJson);
// 2. 解析网关响应(外层 appcode 校验)
Response<T> gatewayResponse = JSON.parseObject(responseJson, typeReference);
if (gatewayResponse == null) {
String errorMsg = operationName + "失败:网关响应格式异常(无法解析)";
log.error("{} | 原始响应: {}", errorMsg, responseJson);
throw new BusinessException("GATEWAY_PARSE_ERROR", errorMsg);
}
if (!"0".equals(gatewayResponse.getAppcode())) {
String errorMsg = String.format("%s失败网关响应失败 | appcode: %s, msg: %s",
operationName, gatewayResponse.getAppcode(), gatewayResponse.getMsg());
log.error("{} | 原始响应: {}", errorMsg, responseJson);
throw new BusinessException(gatewayResponse.getAppcode(), errorMsg);
}
T data = gatewayResponse.getData();
if (data == null) {
String errorMsg = operationName + "失败响应data字段为空";
log.error("{} | 原始响应: {}", errorMsg, responseJson);
throw new BusinessException("DATA_EMPTY", errorMsg);
}
if (data instanceof ErrFlagResponse) {
ErrFlagResponse errFlagResponse = (ErrFlagResponse) data;
if (!errFlagResponse.isSuccess()) {
String errorMsg = String.format("%s失败%s错误标识%s",
operationName, errFlagResponse.getErrtext(), errFlagResponse.getErrflag());
log.error("{} | 原始响应: {}", errorMsg, responseJson);
throw new BusinessException(errFlagResponse.getErrflag(), errorMsg);
}
}
// 6. 所有校验通过,返回业务数据
return data;
}
public static class Response<T> {
private String appcode;
private String code;
private String msg;
private T data;
private Object map;
// Getter仅保留需要的字段setter可选
public String getAppcode() { return appcode; }
public String getMsg() { return msg; }
public T getData() { return data; }
}
public class BusinessException extends RuntimeException {
private String code; // 错误码(可关联门户错误码)
public BusinessException(String message) {
super(message);
this.code = "BUSINESS_ERROR";
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
// Getter
public String getCode() {
return code;
}
}
public interface ErrFlagResponse {
String getErrflag();
String getErrtext();
// 默认方法判断是否成功0=成功1=失败)
default boolean isSuccess() {
return "0".equals(getErrflag());
}
}
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.common.core.domain.entity.tymh.nwToken;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class PortalTokenCacheDTO {
@ApiModelProperty("门户 accessToken")
private String accessToken;
@ApiModelProperty("过期时间戳(毫秒)")
private Long expireTimestamp;
}

View File

@@ -111,7 +111,7 @@ public class SecurityConfig
.authorizeHttpRequests((requests) -> { .authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage","/app/login","/websocket/**","/speech-recognition","/speech-synthesis","/cms/company/listPage","/cms/appUser/noTmlist").permitAll() requests.antMatchers("/login", "/register", "/captchaImage","/app/login","/websocket/**","/speech-recognition","/speech-synthesis","/cms/company/listPage","/cms/appUser/noTmlist","/getTjmhToken").permitAll()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
// 移动端公用查询,可匿名访问 // 移动端公用查询,可匿名访问

View File

@@ -0,0 +1,209 @@
package com.ruoyi.framework.web.service;
import com.ruoyi.cms.util.oauth.OauthClient;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwTokenResult;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwUserInfoResult;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.PortalTokenCacheDTO;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Service
public class OauthLoginService {
@Autowired
private OauthClient oauthClient;
@Autowired
private TokenService tokenService;
@Autowired
private RedisCache redisCache;
@Autowired
private ISysUserService sysUserService;
@Autowired
private AuthenticationManager authenticationManager;
// Redis缓存门户UserID → 若依本地用户名(避免重复匹配数据库)
private static final String REDIS_KEY_PORTAL_USER_MAPPING = "portal:user:mapping:";
// 门户 Token 存储前缀Redis 键:门户 userId → 门户 Token 信息)
private static final String REDIS_KEY_PORTAL_TOKEN = "portal:token:";
/**
* OAuth 登录流程:通过授权码获取系统令牌
* @param code 前端传入的 OAuth 授权码
* @return 系统内部令牌(供前端后续使用)
*/
public String oauthLogin(String code) {
try {
//获取门户token
NwTokenResult nwTokenResult = oauthClient.nwGetToken(code);
if (!"0".equals(nwTokenResult.getErrflag())) {
throw new ServiceException("获取门户 Token 失败:" + nwTokenResult.getErrtext());
}
String accessToken = nwTokenResult.getAccessToken();
Long expiresIn = nwTokenResult.getExpiresIn();
if (StringUtils.isEmpty(accessToken) || expiresIn == null || expiresIn <= 0) {
throw new ServiceException("门户 Token 无效或有效期异常");
}
//获取门户userInfo
NwUserInfoResult portalUser = oauthClient.nwGetUserInfo(accessToken);
//匹配/创建本地用户
String localUsername = getOrCreateLocalUser(portalUser);
//执行原来的登录流程
Authentication authentication = authenticateLocalUser(localUsername);
AsyncManager.me().execute(AsyncFactory.recordLogininfor(localUsername, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
storePortalToken(loginUser.getUserId(), nwTokenResult);
recordLoginInfo(loginUser.getUserId());
return tokenService.createToken(loginUser);
} catch (IOException | TimeoutException e) {
throw new ServiceException("OAuth 登录失败:" + e.getMessage());
}
}
/**
* 匹配/创建本地用户,返回若依本地用户名
*/
private String getOrCreateLocalUser(NwUserInfoResult portalUser) {
Long portalUserId = parsePortalUserId(portalUser.getUserid());
// 先从Redis查询缓存的本地用户名
String cacheKey = REDIS_KEY_PORTAL_USER_MAPPING + portalUserId;
String localUsername = redisCache.getCacheObject(cacheKey);
if (StringUtils.isNotBlank(localUsername)) {
return localUsername;
}
// 缓存未命中,查询本地用户
SysUser localUser = sysUserService.selectUserById(portalUserId);
if (localUser == null) {
// 本地无用户,自动创建
localUser = createLocalUser(portalUser, portalUserId);
// 缓存门户UserID与本地用户名的映射有效期1天可调整
redisCache.setCacheObject(cacheKey, localUser.getUserName(), 1, TimeUnit.DAYS);
return localUser.getUserName();
}
// 缓存映射关系(更新有效期)
redisCache.setCacheObject(cacheKey, localUser.getUserName(), 1, TimeUnit.DAYS);
return localUser.getUserName();
}
/**
* 门户UserID字符串转Long
*/
private Long parsePortalUserId(String portalUserIdStr) {
try {
return Long.parseLong(portalUserIdStr);
} catch (NumberFormatException e) {
throw new ServiceException("门户用户ID格式错误" + portalUserIdStr);
}
}
/**
* 自动创建本地用户
*/
private SysUser createLocalUser(NwUserInfoResult portalUser, Long portalUserId) {
SysUser newUser = new SysUser();
String localUsername = "portal_" + portalUserId;
newUser.setUserName(localUsername);
newUser.setNickName(portalUser.getName());
newUser.setIdCard(portalUser.getIdcardno());
newUser.setUserId(portalUserId);
newUser.setPassword(SecurityUtils.encryptPassword("123456"));
newUser.setDelFlag("0");
// 调用若依原生方法新增用户(自动处理角色关联,需提前配置默认角色)
sysUserService.insertUser(newUser);
return newUser;
}
/**
* 复用若依认证机制,触发 UserDetailsService 加载 LoginUser
* (关键:用本地用户名构建认证令牌,无需密码,因为门户已完成身份校验)
*/
private Authentication authenticateLocalUser(String localUsername) {
Authentication authentication = null;
try {
// 构建认证令牌:用户名+空密码(因为门户已验证身份,本地仅需加载用户信息)
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(localUsername, "");
AuthenticationContextHolder.setContext(authenticationToken);
// 触发认证:会调用 UserDetailsServiceImpl.loadUserByUsername(localUsername)
// 该方法会加载用户权限、角色,返回 LoginUser
authentication = authenticationManager.authenticate(authenticationToken);
} catch (Exception e) {
// 捕获认证异常(如用户被禁用、权限加载失败等)
throw new ServiceException("本地用户认证失败:" + e.getMessage());
} finally {
AuthenticationContextHolder.clearContext();
}
return authentication;
}
/**
* 存储门户 Token 到 Redis结构化存储含过期时间
*/
private void storePortalToken(Long portalUserId, NwTokenResult tokenResult) {
// 构建 Redis 键(门户 userId 作为唯一标识)
String redisKey = REDIS_KEY_PORTAL_TOKEN + portalUserId;
// 封装 Token 信息(含 accessToken、过期时间戳
PortalTokenCacheDTO tokenCache = new PortalTokenCacheDTO();
tokenCache.setAccessToken(tokenResult.getAccessToken());
tokenCache.setExpireTimestamp(System.currentTimeMillis() + tokenResult.getExpiresIn() * 1000);
redisCache.setCacheObject(redisKey, tokenCache, safeLongToInt(tokenResult.getExpiresIn()), TimeUnit.SECONDS);
}
/**
* 记录登录信息(复用若依原生逻辑,直接复制过来)
*/
private void recordLoginInfo(Long userId) {
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
sysUser.setLoginDate(new Date());
sysUserService.updateUserProfile(sysUser);
}
/**
* Long 转 int
*/
private int safeLongToInt(Long value) {
if (value == null) {
throw new ServiceException("门户 Token 有效期不能为空");
}
// 校验是否超过 int 最大值(实际场景几乎不会触发)
if (value > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
// 校验是否为负数(无效有效期)
if (value < 0) {
throw new ServiceException("门户 Token 有效期不能为负数");
}
return value.intValue();
}
}