1.修改1101(求职者)、1102(招聘者)、1103(网格员)、1104(内部工作者)
2.添加门户认证的类
This commit is contained in:
@@ -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", "登录成功");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
// 移动端公用查询,可匿名访问
|
// 移动端公用查询,可匿名访问
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user