更新一键登录
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package com.vetti.web.controller.hotake;
|
||||
|
||||
import com.vetti.common.annotation.Anonymous;
|
||||
import com.vetti.common.core.domain.AjaxResult;
|
||||
import com.vetti.common.core.domain.R;
|
||||
import com.vetti.common.utils.SecurityUtils;
|
||||
import com.vetti.hotake.domain.HotakeSocialUser;
|
||||
import com.vetti.hotake.domain.dto.HotakeSocialLoginRequestDto;
|
||||
import com.vetti.hotake.domain.dto.HotakeSocialLoginResultDto;
|
||||
import com.vetti.hotake.service.IHotakeSocialUserService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社交登录控制器
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
@Api(tags = "社交登录模块")
|
||||
@RestController
|
||||
@RequestMapping("/oauth2")
|
||||
public class HotakeSocialLoginController {
|
||||
|
||||
@Autowired
|
||||
private IHotakeSocialUserService socialUserService;
|
||||
|
||||
/**
|
||||
* 获取OAuth授权URL
|
||||
*/
|
||||
@Anonymous
|
||||
@ApiOperation("获取OAuth授权URL")
|
||||
@GetMapping("/authorize/{provider}")
|
||||
public R<String> getAuthorizationUrl(
|
||||
@ApiParam(value = "平台类型:google/microsoft/linkedin", required = true)
|
||||
@PathVariable String provider,
|
||||
@ApiParam(value = "state参数,用于防止CSRF攻击")
|
||||
@RequestParam(required = false) String state) {
|
||||
String authUrl = socialUserService.getAuthorizationUrl(provider, state);
|
||||
return R.ok(authUrl, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 社交登录回调(用code换取token并登录)
|
||||
*/
|
||||
@Anonymous
|
||||
@ApiOperation("社交登录")
|
||||
@PostMapping("/login")
|
||||
public R<HotakeSocialLoginResultDto> socialLogin(@Validated @RequestBody HotakeSocialLoginRequestDto requestDto) {
|
||||
HotakeSocialLoginResultDto resultDto = socialUserService.socialLogin(requestDto);
|
||||
return R.ok(resultDto, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户绑定的社交账号列表
|
||||
*/
|
||||
@ApiOperation("获取当前用户绑定的社交账号列表")
|
||||
@GetMapping("/bindList")
|
||||
public R<List<HotakeSocialUser>> getBindList() {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
List<HotakeSocialUser> list = socialUserService.listByUserId(userId);
|
||||
// 脱敏处理,不返回token等敏感信息
|
||||
list.forEach(item -> {
|
||||
item.setAccessToken(null);
|
||||
item.setRefreshToken(null);
|
||||
item.setRawUserInfo(null);
|
||||
});
|
||||
return R.ok(list, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑社交账号
|
||||
*/
|
||||
@ApiOperation("解绑社交账号")
|
||||
@DeleteMapping("/unbind/{provider}")
|
||||
public AjaxResult unbind(
|
||||
@ApiParam(value = "平台类型:google/microsoft/linkedin", required = true)
|
||||
@PathVariable String provider) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
socialUserService.unbind(userId, provider);
|
||||
return AjaxResult.success("解绑成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
package com.vetti.web.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.vetti.common.config.HotakeOAuth2Properties;
|
||||
import com.vetti.common.core.domain.entity.SysUser;
|
||||
import com.vetti.common.core.domain.model.LoginUser;
|
||||
import com.vetti.common.enums.UserFlagEnum;
|
||||
import com.vetti.common.exception.ServiceException;
|
||||
import com.vetti.common.utils.DateUtils;
|
||||
import com.vetti.common.utils.ip.AddressUtils;
|
||||
import com.vetti.common.utils.ip.IpUtils;
|
||||
import com.vetti.framework.web.service.SysPermissionService;
|
||||
import com.vetti.framework.web.service.TokenService;
|
||||
import com.vetti.hotake.domain.HotakeSocialLoginLog;
|
||||
import com.vetti.hotake.domain.HotakeSocialUser;
|
||||
import com.vetti.hotake.domain.dto.HotakeSocialLoginRequestDto;
|
||||
import com.vetti.hotake.domain.dto.HotakeSocialLoginResultDto;
|
||||
import com.vetti.hotake.domain.dto.HotakeSocialUserInfoDto;
|
||||
import com.vetti.hotake.mapper.HotakeSocialLoginLogMapper;
|
||||
import com.vetti.hotake.mapper.HotakeSocialUserMapper;
|
||||
import com.vetti.hotake.service.IHotakeSocialUserService;
|
||||
import com.vetti.system.service.ISysUserService;
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
import okhttp3.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 社交用户服务实现
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
@Service
|
||||
public class HotakeSocialUserServiceImpl implements IHotakeSocialUserService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HotakeSocialUserServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private HotakeOAuth2Properties oAuth2Properties;
|
||||
|
||||
@Autowired
|
||||
private HotakeSocialUserMapper socialUserMapper;
|
||||
|
||||
@Autowired
|
||||
private HotakeSocialLoginLogMapper socialLoginLogMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Autowired
|
||||
private SysPermissionService permissionService;
|
||||
|
||||
private final OkHttpClient httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public HotakeSocialLoginResultDto socialLogin(HotakeSocialLoginRequestDto requestDto) {
|
||||
String provider = requestDto.getProvider().toLowerCase();
|
||||
String code = requestDto.getCode();
|
||||
|
||||
// 1. 获取第三方用户信息
|
||||
HotakeSocialUserInfoDto socialUserInfo = getSocialUserInfo(provider, code);
|
||||
if (socialUserInfo == null || StrUtil.isBlank(socialUserInfo.getProviderUserId())) {
|
||||
recordLoginLog(null, provider, null, "login", "1", "获取第三方用户信息失败");
|
||||
throw new ServiceException("获取第三方用户信息失败");
|
||||
}
|
||||
|
||||
// 2. 查询是否已绑定
|
||||
HotakeSocialUser existSocialUser = socialUserMapper.selectByProviderAndProviderUserId(
|
||||
provider, socialUserInfo.getProviderUserId());
|
||||
|
||||
SysUser sysUser;
|
||||
String loginType;
|
||||
boolean isNewUser = false;
|
||||
|
||||
if (existSocialUser != null && existSocialUser.getUserId() != null) {
|
||||
// 已绑定,直接登录
|
||||
loginType = "login";
|
||||
sysUser = sysUserService.selectUserById(existSocialUser.getUserId());
|
||||
if (sysUser == null) {
|
||||
recordLoginLog(null, provider, socialUserInfo.getProviderUserId(), loginType, "1", "绑定的用户不存在");
|
||||
throw new ServiceException("绑定的用户不存在");
|
||||
}
|
||||
// 更新token信息
|
||||
updateSocialUserToken(existSocialUser, socialUserInfo);
|
||||
} else {
|
||||
// 未绑定,自动注册
|
||||
loginType = "register";
|
||||
isNewUser = true;
|
||||
sysUser = autoRegister(socialUserInfo, requestDto.getSysUserType());
|
||||
// 如果是已存在用户绑定,则不是新用户
|
||||
if (sysUser.getCreateTime() != null &&
|
||||
System.currentTimeMillis() - sysUser.getCreateTime().getTime() > 5000) {
|
||||
isNewUser = false;
|
||||
}
|
||||
// 创建社交绑定
|
||||
createSocialUserBinding(sysUser.getUserId(), socialUserInfo);
|
||||
}
|
||||
|
||||
// 3. 生成登录Token
|
||||
HotakeSocialLoginResultDto resultDto = createLoginToken(sysUser, provider, isNewUser);
|
||||
|
||||
// 4. 记录登录日志
|
||||
recordLoginLog(sysUser.getUserId(), provider, socialUserInfo.getProviderUserId(), loginType, "0", "登录成功");
|
||||
|
||||
// 5. 更新登录信息
|
||||
recordLoginInfo(sysUser.getUserId());
|
||||
|
||||
return resultDto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthorizationUrl(String provider, String state) {
|
||||
HotakeOAuth2Properties.OAuthClientConfig config = oAuth2Properties.getByProvider(provider);
|
||||
if (config == null) {
|
||||
throw new ServiceException("不支持的登录平台: " + provider);
|
||||
}
|
||||
|
||||
try {
|
||||
StringBuilder url = new StringBuilder(config.getAuthUri());
|
||||
url.append("?client_id=").append(URLEncoder.encode(config.getClientId(), StandardCharsets.UTF_8.name()));
|
||||
url.append("&redirect_uri=").append(URLEncoder.encode(config.getRedirectUri(), StandardCharsets.UTF_8.name()));
|
||||
url.append("&response_type=code");
|
||||
url.append("&scope=").append(URLEncoder.encode(config.getScope(), StandardCharsets.UTF_8.name()));
|
||||
if (StrUtil.isNotBlank(state)) {
|
||||
url.append("&state=").append(URLEncoder.encode(state, StandardCharsets.UTF_8.name()));
|
||||
}
|
||||
return url.toString();
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("生成授权URL失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HotakeSocialUser> listByUserId(Long userId) {
|
||||
return socialUserMapper.selectByUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void unbind(Long userId, String provider) {
|
||||
HotakeSocialUser socialUser = socialUserMapper.selectByUserIdAndProvider(userId, provider);
|
||||
if (socialUser == null) {
|
||||
throw new ServiceException("未绑定该平台账号");
|
||||
}
|
||||
socialUserMapper.deleteById(socialUser.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第三方用户信息
|
||||
*/
|
||||
private HotakeSocialUserInfoDto getSocialUserInfo(String provider, String code) {
|
||||
HotakeOAuth2Properties.OAuthClientConfig config = oAuth2Properties.getByProvider(provider);
|
||||
if (config == null) {
|
||||
throw new ServiceException("不支持的登录平台: " + provider);
|
||||
}
|
||||
|
||||
// 1. 用code换取access_token
|
||||
JSONObject tokenResponse = getAccessToken(config, code);
|
||||
if (tokenResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String accessToken = tokenResponse.getString("access_token");
|
||||
String refreshToken = tokenResponse.getString("refresh_token");
|
||||
Long expiresIn = tokenResponse.getLong("expires_in");
|
||||
|
||||
// 2. 用access_token获取用户信息
|
||||
JSONObject userInfo = getUserInfo(config, accessToken);
|
||||
if (userInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 解析用户信息(不同平台字段不同)
|
||||
return parseSocialUserInfo(provider, userInfo, accessToken, refreshToken, expiresIn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取access_token
|
||||
*/
|
||||
private JSONObject getAccessToken(HotakeOAuth2Properties.OAuthClientConfig config, String code) {
|
||||
try {
|
||||
FormBody.Builder formBuilder = new FormBody.Builder()
|
||||
.add("client_id", config.getClientId())
|
||||
.add("client_secret", config.getClientSecret())
|
||||
.add("code", code)
|
||||
.add("redirect_uri", config.getRedirectUri())
|
||||
.add("grant_type", "authorization_code");
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(config.getTokenUri())
|
||||
.post(formBuilder.build())
|
||||
.addHeader("Accept", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
String body = response.body().string();
|
||||
log.debug("Token response: {}", body);
|
||||
return JSON.parseObject(body);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("获取access_token失败", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
private JSONObject getUserInfo(HotakeOAuth2Properties.OAuthClientConfig config, String accessToken) {
|
||||
try {
|
||||
Request request = new Request.Builder()
|
||||
.url(config.getUserInfoUri())
|
||||
.get()
|
||||
.addHeader("Authorization", "Bearer " + accessToken)
|
||||
.addHeader("Accept", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
String body = response.body().string();
|
||||
log.debug("UserInfo response: {}", body);
|
||||
return JSON.parseObject(body);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("获取用户信息失败", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析不同平台的用户信息为统一格式
|
||||
*/
|
||||
private HotakeSocialUserInfoDto parseSocialUserInfo(String provider, JSONObject userInfo,
|
||||
String accessToken, String refreshToken, Long expiresIn) {
|
||||
HotakeSocialUserInfoDto dto = new HotakeSocialUserInfoDto();
|
||||
dto.setProvider(provider);
|
||||
dto.setAccessToken(accessToken);
|
||||
dto.setRefreshToken(refreshToken);
|
||||
dto.setExpiresIn(expiresIn);
|
||||
dto.setRawUserInfo(userInfo.toJSONString());
|
||||
|
||||
switch (provider.toLowerCase()) {
|
||||
case "google":
|
||||
dto.setProviderUserId(userInfo.getString("sub"));
|
||||
dto.setEmail(userInfo.getString("email"));
|
||||
dto.setName(userInfo.getString("name"));
|
||||
dto.setAvatar(userInfo.getString("picture"));
|
||||
break;
|
||||
case "microsoft":
|
||||
dto.setProviderUserId(userInfo.getString("id"));
|
||||
// Microsoft返回的邮箱字段是mail或userPrincipalName
|
||||
String email = userInfo.getString("mail");
|
||||
if (StrUtil.isBlank(email)) {
|
||||
email = userInfo.getString("userPrincipalName");
|
||||
}
|
||||
dto.setEmail(email);
|
||||
dto.setName(userInfo.getString("displayName"));
|
||||
// Microsoft头像需要单独请求,这里暂不处理
|
||||
break;
|
||||
case "linkedin":
|
||||
dto.setProviderUserId(userInfo.getString("sub"));
|
||||
dto.setEmail(userInfo.getString("email"));
|
||||
dto.setName(userInfo.getString("name"));
|
||||
dto.setAvatar(userInfo.getString("picture"));
|
||||
break;
|
||||
default:
|
||||
throw new ServiceException("不支持的登录平台: " + provider);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动注册用户
|
||||
*/
|
||||
private SysUser autoRegister(HotakeSocialUserInfoDto socialUserInfo, String sysUserType) {
|
||||
// 检查邮箱是否已存在
|
||||
if (StrUtil.isNotBlank(socialUserInfo.getEmail())) {
|
||||
SysUser existUser = sysUserService.selectUserByUserName(socialUserInfo.getEmail());
|
||||
if (existUser != null) {
|
||||
// 邮箱已存在,直接绑定
|
||||
return existUser;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setUserName(socialUserInfo.getEmail());
|
||||
sysUser.setEmail(socialUserInfo.getEmail());
|
||||
sysUser.setNickName(socialUserInfo.getName());
|
||||
sysUser.setAvatar(socialUserInfo.getAvatar());
|
||||
sysUser.setSysUserType(StrUtil.isNotBlank(sysUserType) ? sysUserType : "candidate");
|
||||
sysUser.setUserFlag(UserFlagEnum.FLAG_1.getCode());
|
||||
sysUser.setUserOperStatus("1");
|
||||
sysUser.setPwdUpdateDate(DateUtils.getNowDate());
|
||||
// 社交登录用户不设置密码,或设置随机密码
|
||||
sysUser.setPassword("");
|
||||
|
||||
boolean success = sysUserService.registerUser(sysUser);
|
||||
if (!success) {
|
||||
throw new ServiceException("自动注册用户失败");
|
||||
}
|
||||
|
||||
return sysUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建社交账号绑定
|
||||
*/
|
||||
private void createSocialUserBinding(Long userId, HotakeSocialUserInfoDto socialUserInfo) {
|
||||
HotakeSocialUser socialUser = new HotakeSocialUser();
|
||||
socialUser.setUserId(userId);
|
||||
socialUser.setProvider(socialUserInfo.getProvider());
|
||||
socialUser.setProviderUserId(socialUserInfo.getProviderUserId());
|
||||
socialUser.setEmail(socialUserInfo.getEmail());
|
||||
socialUser.setName(socialUserInfo.getName());
|
||||
socialUser.setAvatar(socialUserInfo.getAvatar());
|
||||
socialUser.setAccessToken(socialUserInfo.getAccessToken());
|
||||
socialUser.setRefreshToken(socialUserInfo.getRefreshToken());
|
||||
if (socialUserInfo.getExpiresIn() != null) {
|
||||
socialUser.setTokenExpireTime(new Date(System.currentTimeMillis() + socialUserInfo.getExpiresIn() * 1000));
|
||||
}
|
||||
socialUser.setRawUserInfo(socialUserInfo.getRawUserInfo());
|
||||
|
||||
socialUserMapper.insert(socialUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新社交账号token信息
|
||||
*/
|
||||
private void updateSocialUserToken(HotakeSocialUser socialUser, HotakeSocialUserInfoDto socialUserInfo) {
|
||||
socialUser.setAccessToken(socialUserInfo.getAccessToken());
|
||||
socialUser.setRefreshToken(socialUserInfo.getRefreshToken());
|
||||
if (socialUserInfo.getExpiresIn() != null) {
|
||||
socialUser.setTokenExpireTime(new Date(System.currentTimeMillis() + socialUserInfo.getExpiresIn() * 1000));
|
||||
}
|
||||
socialUser.setRawUserInfo(socialUserInfo.getRawUserInfo());
|
||||
socialUserMapper.update(socialUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建登录Token
|
||||
*/
|
||||
private HotakeSocialLoginResultDto createLoginToken(SysUser sysUser, String provider, boolean isNewUser) {
|
||||
// 获取权限
|
||||
Set<String> permissions = permissionService.getMenuPermission(sysUser);
|
||||
|
||||
// 创建LoginUser
|
||||
LoginUser loginUser = new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissions);
|
||||
|
||||
// 生成token
|
||||
String token = tokenService.createToken(loginUser);
|
||||
|
||||
// 构建返回对象
|
||||
HotakeSocialLoginResultDto resultDto = new HotakeSocialLoginResultDto();
|
||||
resultDto.setToken(token);
|
||||
resultDto.setUserId(sysUser.getUserId());
|
||||
resultDto.setSysUserType(sysUser.getSysUserType());
|
||||
resultDto.setIsNewUser(isNewUser);
|
||||
resultDto.setProvider(provider);
|
||||
resultDto.setUser(sysUser);
|
||||
|
||||
return resultDto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*/
|
||||
private void recordLoginInfo(Long userId) {
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setUserId(userId);
|
||||
sysUser.setLoginIp(IpUtils.getIpAddr());
|
||||
sysUser.setLoginDate(DateUtils.getNowDate());
|
||||
sysUserService.updateUserProfile(sysUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录社交登录日志
|
||||
*/
|
||||
private void recordLoginLog(Long userId, String provider, String providerUserId,
|
||||
String loginType, String status, String msg) {
|
||||
try {
|
||||
HotakeSocialLoginLog loginLog = new HotakeSocialLoginLog();
|
||||
loginLog.setUserId(userId);
|
||||
loginLog.setProvider(provider);
|
||||
loginLog.setProviderUserId(providerUserId);
|
||||
loginLog.setLoginType(loginType);
|
||||
loginLog.setLoginIp(IpUtils.getIpAddr());
|
||||
loginLog.setLoginLocation(AddressUtils.getRealAddressByIP(IpUtils.getIpAddr()));
|
||||
|
||||
try {
|
||||
UserAgent userAgent = UserAgent.parseUserAgentString(
|
||||
com.vetti.common.utils.ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
loginLog.setBrowser(userAgent.getBrowser().getName());
|
||||
loginLog.setOs(userAgent.getOperatingSystem().getName());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
loginLog.setStatus(status);
|
||||
loginLog.setMsg(msg);
|
||||
loginLog.setLoginTime(new Date());
|
||||
|
||||
socialLoginLogMapper.insert(loginLog);
|
||||
} catch (Exception e) {
|
||||
log.error("记录社交登录日志失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,3 +171,44 @@ chatGpt:
|
||||
http:
|
||||
client:
|
||||
connect-timeout-seconds: 10
|
||||
|
||||
# OAuth2.0 社交登录配置
|
||||
oauth2:
|
||||
# ================================
|
||||
# Google (Gmail) 登录配置
|
||||
# 申请地址: https://console.cloud.google.com/apis/credentials
|
||||
# ================================
|
||||
google:
|
||||
client-id: your-google-client-id # Google Cloud Console获取
|
||||
client-secret: your-google-client-secret # Google Cloud Console获取
|
||||
redirect-uri: https://your-domain.com/api/oauth2/callback/google
|
||||
scope: openid email profile # Google标准scope(空格分隔)
|
||||
auth-uri: https://accounts.google.com/o/oauth2/v2/auth # Google授权地址
|
||||
token-uri: https://oauth2.googleapis.com/token # Google令牌地址
|
||||
user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo # Google用户信息接口
|
||||
|
||||
# ================================
|
||||
# Microsoft (Outlook) 登录配置
|
||||
# 申请地址: https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps
|
||||
# ================================
|
||||
microsoft:
|
||||
client-id: your-microsoft-client-id # Azure Portal获取
|
||||
client-secret: your-microsoft-client-secret # Azure Portal获取
|
||||
redirect-uri: https://your-domain.com/api/oauth2/callback/microsoft
|
||||
scope: openid,email,profile,User.Read # 需要额外的User.Read权限才能读取用户信息
|
||||
auth-uri: https://login.microsoftonline.com/common/oauth2/v2.0/authorize # 微软授权地址
|
||||
token-uri: https://login.microsoftonline.com/common/oauth2/v2.0/token # 微软令牌地址
|
||||
user-info-uri: https://graph.microsoft.com/v1.0/me # 使用Microsoft Graph API
|
||||
|
||||
# ================================
|
||||
# LinkedIn 登录配置
|
||||
# 申请地址: https://www.linkedin.com/developers/apps
|
||||
# ================================
|
||||
linkedin:
|
||||
client-id: 86uq3opzshd3bq # LinkedIn Developer Portal获取
|
||||
client-secret: 86uq3opzshd3bq # LinkedIn Developer Portal获取
|
||||
redirect-uri: http://localhost:8080/oauth2/callback/linkedin
|
||||
scope: openid profile email # LinkedIn使用OpenID Connect,scope顺序和命名略有不同
|
||||
auth-uri: https://www.linkedin.com/oauth/v2/authorization # LinkedIn授权地址
|
||||
token-uri: https://www.linkedin.com/oauth/v2/accessToken # LinkedIn令牌地址
|
||||
user-info-uri: https://api.linkedin.com/v2/userinfo # LinkedIn用户信息接口
|
||||
@@ -197,3 +197,44 @@ chatGpt:
|
||||
http:
|
||||
client:
|
||||
connect-timeout-seconds: 600
|
||||
|
||||
# OAuth2.0 社交登录配置
|
||||
oauth2:
|
||||
# ================================
|
||||
# Google (Gmail) 登录配置
|
||||
# 申请地址: https://console.cloud.google.com/apis/credentials
|
||||
# ================================
|
||||
google:
|
||||
client-id: 398978985110-ve0usu381mmdio12ff01iqvv1g087qvi.apps.googleusercontent.com # Google Cloud Console获取
|
||||
client-secret: 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 # Google Cloud Console获取
|
||||
redirect-uri: https://vetti.hotake.cn/oauth2/callback/google
|
||||
scope: openid email profile # Google标准scope(空格分隔)
|
||||
auth-uri: https://accounts.google.com/o/oauth2/v2/auth # Google授权地址
|
||||
token-uri: https://oauth2.googleapis.com/token # Google令牌地址
|
||||
user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo # Google用户信息接口
|
||||
|
||||
# ================================
|
||||
# Microsoft (Outlook) 登录配置
|
||||
# 申请地址: https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps
|
||||
# ================================
|
||||
microsoft:
|
||||
client-id: 608cbc4f-3ee2-4f51-a72c-b2e3133fc5b2 # Azure Portal获取
|
||||
client-secret: gvw8Q~dwG8Sv7HN3R3W3R7TQtcZyvrh88ZiJubPa # Azure Portal获取
|
||||
redirect-uri: https://vetti.hotake.cn/api/oauth2/callback/microsoft
|
||||
scope: openid,email,profile,User.Read # 需要额外的User.Read权限才能读取用户信息
|
||||
auth-uri: https://login.microsoftonline.com/common/oauth2/v2.0/authorize # 微软授权地址
|
||||
token-uri: https://login.microsoftonline.com/common/oauth2/v2.0/token # 微软令牌地址
|
||||
user-info-uri: https://graph.microsoft.com/v1.0/me # 使用Microsoft Graph API
|
||||
|
||||
# ================================
|
||||
# LinkedIn 登录配置
|
||||
# 申请地址: https://www.linkedin.com/developers/apps
|
||||
# ================================
|
||||
linkedin:
|
||||
client-id: 86uq3opzshd3bq # LinkedIn Developer Portal获取
|
||||
client-secret: WPL_AP1.mipgyxfgfBoN12Th.1TXeFg== # LinkedIn Developer Portal获取
|
||||
redirect-uri: https://vetti.hotake.cn/api/oauth2/callback/linkedin
|
||||
scope: openid profile email # LinkedIn使用OpenID Connect,scope顺序和命名略有不同
|
||||
auth-uri: https://www.linkedin.com/oauth/v2/authorization # LinkedIn授权地址
|
||||
token-uri: https://www.linkedin.com/oauth/v2/accessToken # LinkedIn令牌地址
|
||||
user-info-uri: https://api.linkedin.com/v2/userinfo # LinkedIn用户信息接口
|
||||
|
||||
@@ -191,8 +191,50 @@ chatGpt:
|
||||
modelAiIntPf: gpt-4o-mini
|
||||
modelAiCvSr: gpt-4o-mini
|
||||
modelAiCac: gpt-4o-mini
|
||||
modelAiCiv: gpt-4o-mini
|
||||
role: system
|
||||
|
||||
http:
|
||||
client:
|
||||
connect-timeout-seconds: 600
|
||||
|
||||
# OAuth2.0 社交登录配置
|
||||
oauth2:
|
||||
# ================================
|
||||
# Google (Gmail) 登录配置
|
||||
# 申请地址: https://console.cloud.google.com/apis/credentials
|
||||
# ================================
|
||||
google:
|
||||
client-id: 398978985110-ve0usu381mmdio12ff01iqvv1g087qvi.apps.googleusercontent.com # Google Cloud Console获取
|
||||
client-secret: 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 # Google Cloud Console获取
|
||||
redirect-uri: https://vetti.hotake.cn/oauth2/callback/google
|
||||
scope: openid email profile # Google标准scope(空格分隔)
|
||||
auth-uri: https://accounts.google.com/o/oauth2/v2/auth # Google授权地址
|
||||
token-uri: https://oauth2.googleapis.com/token # Google令牌地址
|
||||
user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo # Google用户信息接口
|
||||
|
||||
# ================================
|
||||
# Microsoft (Outlook) 登录配置
|
||||
# 申请地址: https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps
|
||||
# ================================
|
||||
microsoft:
|
||||
client-id: 608cbc4f-3ee2-4f51-a72c-b2e3133fc5b2 # Azure Portal获取
|
||||
client-secret: gvw8Q~dwG8Sv7HN3R3W3R7TQtcZyvrh88ZiJubPa # Azure Portal获取
|
||||
redirect-uri: https://vetti.hotake.cn/api/oauth2/callback/microsoft
|
||||
scope: openid,email,profile,User.Read # 需要额外的User.Read权限才能读取用户信息
|
||||
auth-uri: https://login.microsoftonline.com/common/oauth2/v2.0/authorize # 微软授权地址
|
||||
token-uri: https://login.microsoftonline.com/common/oauth2/v2.0/token # 微软令牌地址
|
||||
user-info-uri: https://graph.microsoft.com/v1.0/me # 使用Microsoft Graph API
|
||||
|
||||
# ================================
|
||||
# LinkedIn 登录配置
|
||||
# 申请地址: https://www.linkedin.com/developers/apps
|
||||
# ================================
|
||||
linkedin:
|
||||
client-id: 86uq3opzshd3bq # LinkedIn Developer Portal获取
|
||||
client-secret: WPL_AP1.mipgyxfgfBoN12Th.1TXeFg== # LinkedIn Developer Portal获取
|
||||
redirect-uri: https://vetti.hotake.cn/api/oauth2/callback/linkedin
|
||||
scope: openid profile email # LinkedIn使用OpenID Connect,scope顺序和命名略有不同
|
||||
auth-uri: https://www.linkedin.com/oauth/v2/authorization # LinkedIn授权地址
|
||||
token-uri: https://www.linkedin.com/oauth/v2/accessToken # LinkedIn令牌地址
|
||||
user-info-uri: https://api.linkedin.com/v2/userinfo # LinkedIn用户信息接口
|
||||
|
||||
@@ -63,3 +63,6 @@ HotakeRolesInfoServiceImpl10001 = The job information is abnormal. Please try ag
|
||||
# manager.页面,字段 = User Manager
|
||||
VerificationEmailTiTle = Your verification code
|
||||
VerificationEmailContent = Your verification code is: {0}, valid for {1} minutes.
|
||||
|
||||
|
||||
HotakeRolesApplyInfoServiceImpl10001 = You have already applied for this position
|
||||
@@ -62,3 +62,4 @@ HotakeRolesInfoServiceImpl10001 = 岗位信息异常,请稍后再试
|
||||
VerificationEmailTiTle = 你的验证码
|
||||
VerificationEmailContent = 你的验证码是: {0},有效期为 {1} 分钟。
|
||||
|
||||
HotakeRolesApplyInfoServiceImpl10001 = 您已申请该职位
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.vetti.common.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* OAuth2.0 社交登录配置属性类
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "oauth2")
|
||||
public class HotakeOAuth2Properties {
|
||||
|
||||
/** Google配置 */
|
||||
private OAuthClientConfig google;
|
||||
|
||||
/** Microsoft配置 */
|
||||
private OAuthClientConfig microsoft;
|
||||
|
||||
/** LinkedIn配置 */
|
||||
private OAuthClientConfig linkedin;
|
||||
|
||||
public OAuthClientConfig getGoogle() {
|
||||
return google;
|
||||
}
|
||||
|
||||
public void setGoogle(OAuthClientConfig google) {
|
||||
this.google = google;
|
||||
}
|
||||
|
||||
public OAuthClientConfig getMicrosoft() {
|
||||
return microsoft;
|
||||
}
|
||||
|
||||
public void setMicrosoft(OAuthClientConfig microsoft) {
|
||||
this.microsoft = microsoft;
|
||||
}
|
||||
|
||||
public OAuthClientConfig getLinkedin() {
|
||||
return linkedin;
|
||||
}
|
||||
|
||||
public void setLinkedin(OAuthClientConfig linkedin) {
|
||||
this.linkedin = linkedin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据provider获取对应配置
|
||||
*/
|
||||
public OAuthClientConfig getByProvider(String provider) {
|
||||
switch (provider.toLowerCase()) {
|
||||
case "google":
|
||||
return google;
|
||||
case "microsoft":
|
||||
return microsoft;
|
||||
case "linkedin":
|
||||
return linkedin;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth客户端配置
|
||||
*/
|
||||
public static class OAuthClientConfig {
|
||||
/** 客户端ID */
|
||||
private String clientId;
|
||||
/** 客户端密钥 */
|
||||
private String clientSecret;
|
||||
/** 回调地址 */
|
||||
private String redirectUri;
|
||||
/** 授权范围 */
|
||||
private String scope;
|
||||
/** 授权端点 */
|
||||
private String authUri;
|
||||
/** 令牌端点 */
|
||||
private String tokenUri;
|
||||
/** 用户信息端点 */
|
||||
private String userInfoUri;
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getAuthUri() {
|
||||
return authUri;
|
||||
}
|
||||
|
||||
public void setAuthUri(String authUri) {
|
||||
this.authUri = authUri;
|
||||
}
|
||||
|
||||
public String getTokenUri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public void setTokenUri(String tokenUri) {
|
||||
this.tokenUri = tokenUri;
|
||||
}
|
||||
|
||||
public String getUserInfoUri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
public void setUserInfoUri(String userInfoUri) {
|
||||
this.userInfoUri = userInfoUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.vetti.hotake.domain;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 社交登录日志对象 hotake_social_login_log
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
@Data
|
||||
public class HotakeSocialLoginLog implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("主键ID")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("系统用户ID")
|
||||
private Long userId;
|
||||
|
||||
@ApiModelProperty("第三方平台类型")
|
||||
private String provider;
|
||||
|
||||
@ApiModelProperty("第三方平台用户ID")
|
||||
private String providerUserId;
|
||||
|
||||
@ApiModelProperty("登录类型:login/register/bind")
|
||||
private String loginType;
|
||||
|
||||
@ApiModelProperty("登录IP")
|
||||
private String loginIp;
|
||||
|
||||
@ApiModelProperty("登录地点")
|
||||
private String loginLocation;
|
||||
|
||||
@ApiModelProperty("浏览器类型")
|
||||
private String browser;
|
||||
|
||||
@ApiModelProperty("操作系统")
|
||||
private String os;
|
||||
|
||||
@ApiModelProperty("登录状态(0成功 1失败)")
|
||||
private String status;
|
||||
|
||||
@ApiModelProperty("提示消息")
|
||||
private String msg;
|
||||
|
||||
@ApiModelProperty("登录时间")
|
||||
private Date loginTime;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.vetti.hotake.domain;
|
||||
|
||||
import com.vetti.common.core.domain.BaseEntity;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户社交账号绑定对象 hotake_social_user
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class HotakeSocialUser extends BaseEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("主键ID")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("系统用户ID")
|
||||
private Long userId;
|
||||
|
||||
@ApiModelProperty("第三方平台类型:google/microsoft/linkedin")
|
||||
private String provider;
|
||||
|
||||
@ApiModelProperty("第三方平台用户唯一标识")
|
||||
private String providerUserId;
|
||||
|
||||
@ApiModelProperty("第三方平台邮箱")
|
||||
private String email;
|
||||
|
||||
@ApiModelProperty("第三方平台用户名")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("第三方平台头像URL")
|
||||
private String avatar;
|
||||
|
||||
@ApiModelProperty("访问令牌")
|
||||
private String accessToken;
|
||||
|
||||
@ApiModelProperty("刷新令牌")
|
||||
private String refreshToken;
|
||||
|
||||
@ApiModelProperty("令牌过期时间")
|
||||
private Date tokenExpireTime;
|
||||
|
||||
@ApiModelProperty("第三方平台原始用户信息JSON")
|
||||
private String rawUserInfo;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.vetti.hotake.domain.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 社交登录请求DTO
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
@Data
|
||||
public class HotakeSocialLoginRequestDto {
|
||||
|
||||
@NotBlank(message = "平台类型不能为空")
|
||||
@ApiModelProperty(value = "第三方平台类型:google/microsoft/linkedin", required = true)
|
||||
private String provider;
|
||||
|
||||
@NotBlank(message = "授权码不能为空")
|
||||
@ApiModelProperty(value = "OAuth授权码", required = true)
|
||||
private String code;
|
||||
|
||||
@NotBlank(message = "用户类型不能为空")
|
||||
@ApiModelProperty(value = "用户类型(interviewer:面试官,candidate:候选者)", required = true)
|
||||
private String sysUserType;
|
||||
|
||||
@ApiModelProperty("state参数(用于防止CSRF攻击)")
|
||||
private String state;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.vetti.hotake.domain.dto;
|
||||
|
||||
import com.vetti.common.core.domain.entity.SysUser;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 社交登录返回结果DTO
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
@Data
|
||||
public class HotakeSocialLoginResultDto {
|
||||
|
||||
@ApiModelProperty("令牌")
|
||||
private String token;
|
||||
|
||||
@ApiModelProperty("用户ID")
|
||||
private Long userId;
|
||||
|
||||
@ApiModelProperty("用户类型(interviewer:面试官,candidate:候选者)")
|
||||
private String sysUserType;
|
||||
|
||||
@ApiModelProperty("是否新注册用户(true:新注册,false:已存在用户登录)")
|
||||
private Boolean isNewUser;
|
||||
|
||||
@ApiModelProperty("第三方平台类型")
|
||||
private String provider;
|
||||
|
||||
@ApiModelProperty("用户信息对象")
|
||||
private SysUser user;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.vetti.hotake.domain.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 第三方平台用户信息DTO(统一格式)
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
@Data
|
||||
public class HotakeSocialUserInfoDto {
|
||||
|
||||
@ApiModelProperty("第三方平台类型")
|
||||
private String provider;
|
||||
|
||||
@ApiModelProperty("第三方平台用户唯一标识")
|
||||
private String providerUserId;
|
||||
|
||||
@ApiModelProperty("邮箱")
|
||||
private String email;
|
||||
|
||||
@ApiModelProperty("用户名/昵称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("头像URL")
|
||||
private String avatar;
|
||||
|
||||
@ApiModelProperty("访问令牌")
|
||||
private String accessToken;
|
||||
|
||||
@ApiModelProperty("刷新令牌")
|
||||
private String refreshToken;
|
||||
|
||||
@ApiModelProperty("令牌有效期(秒)")
|
||||
private Long expiresIn;
|
||||
|
||||
@ApiModelProperty("原始用户信息JSON")
|
||||
private String rawUserInfo;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.vetti.hotake.mapper;
|
||||
|
||||
import com.vetti.hotake.domain.HotakeSocialLoginLog;
|
||||
|
||||
/**
|
||||
* 社交登录日志Mapper接口
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
public interface HotakeSocialLoginLogMapper {
|
||||
|
||||
/**
|
||||
* 新增社交登录日志
|
||||
*/
|
||||
int insert(HotakeSocialLoginLog log);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.vetti.hotake.mapper;
|
||||
|
||||
import com.vetti.hotake.domain.HotakeSocialUser;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户社交账号绑定Mapper接口
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
public interface HotakeSocialUserMapper {
|
||||
|
||||
/**
|
||||
* 根据provider和providerUserId查询社交用户
|
||||
*/
|
||||
HotakeSocialUser selectByProviderAndProviderUserId(@Param("provider") String provider,
|
||||
@Param("providerUserId") String providerUserId);
|
||||
|
||||
/**
|
||||
* 根据用户ID和provider查询社交绑定
|
||||
*/
|
||||
HotakeSocialUser selectByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询所有社交绑定
|
||||
*/
|
||||
List<HotakeSocialUser> selectByUserId(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 新增社交用户绑定
|
||||
*/
|
||||
int insert(HotakeSocialUser socialUser);
|
||||
|
||||
/**
|
||||
* 更新社交用户绑定
|
||||
*/
|
||||
int update(HotakeSocialUser socialUser);
|
||||
|
||||
/**
|
||||
* 删除社交用户绑定(逻辑删除)
|
||||
*/
|
||||
int deleteById(@Param("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.vetti.hotake.service;
|
||||
|
||||
import com.vetti.common.core.domain.entity.SysUser;
|
||||
import com.vetti.hotake.domain.HotakeSocialUser;
|
||||
import com.vetti.hotake.domain.dto.HotakeSocialLoginRequestDto;
|
||||
import com.vetti.hotake.domain.dto.HotakeSocialLoginResultDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社交用户服务接口
|
||||
*
|
||||
* @author vetti
|
||||
*/
|
||||
public interface IHotakeSocialUserService {
|
||||
|
||||
/**
|
||||
* 社交登录(登录或自动注册)
|
||||
*
|
||||
* @param requestDto 社交登录请求
|
||||
* @return 登录结果
|
||||
*/
|
||||
HotakeSocialLoginResultDto socialLogin(HotakeSocialLoginRequestDto requestDto);
|
||||
|
||||
/**
|
||||
* 获取OAuth授权URL
|
||||
*
|
||||
* @param provider 平台类型
|
||||
* @param state state参数
|
||||
* @return 授权URL
|
||||
*/
|
||||
String getAuthorizationUrl(String provider, String state);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询绑定的社交账号
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 社交账号列表
|
||||
*/
|
||||
List<HotakeSocialUser> listByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 解绑社交账号
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param provider 平台类型
|
||||
*/
|
||||
void unbind(Long userId, String provider);
|
||||
}
|
||||
@@ -129,8 +129,8 @@ public class HotakeReferenceCheckServiceImpl extends BaseServiceImpl implements
|
||||
|
||||
if (CollectionUtil.isNotEmpty(cvInfoList)) {
|
||||
HotakeCvInfo cvInfo = cvInfoList.get(0);
|
||||
if (StrUtil.isNotEmpty(cvInfo.getAnalyzedCvJson())) {
|
||||
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(cvInfo.getAnalyzedCvJson(), HotakeCvInfoDto.class);
|
||||
if (StrUtil.isNotEmpty(cvInfo.getCvTemplateJson())) {
|
||||
HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(cvInfo.getCvTemplateJson(), HotakeCvInfoDto.class);
|
||||
if (cvInfoDto != null && CollectionUtil.isNotEmpty(cvInfoDto.getExperience())) {
|
||||
experienceList = cvInfoDto.getExperience();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?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.vetti.hotake.mapper.HotakeSocialLoginLogMapper">
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into hotake_social_login_log (
|
||||
user_id, provider, provider_user_id, login_type, login_ip, login_location,
|
||||
browser, os, status, msg, login_time
|
||||
) values (
|
||||
#{userId}, #{provider}, #{providerUserId}, #{loginType}, #{loginIp}, #{loginLocation},
|
||||
#{browser}, #{os}, #{status}, #{msg}, #{loginTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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.vetti.hotake.mapper.HotakeSocialUserMapper">
|
||||
|
||||
<resultMap type="com.vetti.hotake.domain.HotakeSocialUser" id="HotakeSocialUserResult">
|
||||
<id property="id" column="id"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="provider" column="provider"/>
|
||||
<result property="providerUserId" column="provider_user_id"/>
|
||||
<result property="email" column="email"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="avatar" column="avatar"/>
|
||||
<result property="accessToken" column="access_token"/>
|
||||
<result property="refreshToken" column="refresh_token"/>
|
||||
<result property="tokenExpireTime" column="token_expire_time"/>
|
||||
<result property="rawUserInfo" column="raw_user_info"/>
|
||||
<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"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectColumns">
|
||||
id, user_id, provider, provider_user_id, email, name, avatar, access_token, refresh_token,
|
||||
token_expire_time, raw_user_info, del_flag, create_by, create_time, update_by, update_time, remark
|
||||
</sql>
|
||||
|
||||
<select id="selectByProviderAndProviderUserId" resultMap="HotakeSocialUserResult">
|
||||
select <include refid="selectColumns"/>
|
||||
from hotake_social_user
|
||||
where provider = #{provider} and provider_user_id = #{providerUserId} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectByUserIdAndProvider" resultMap="HotakeSocialUserResult">
|
||||
select <include refid="selectColumns"/>
|
||||
from hotake_social_user
|
||||
where user_id = #{userId} and provider = #{provider} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectByUserId" resultMap="HotakeSocialUserResult">
|
||||
select <include refid="selectColumns"/>
|
||||
from hotake_social_user
|
||||
where user_id = #{userId} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into hotake_social_user (
|
||||
user_id, provider, provider_user_id, email, name, avatar, access_token, refresh_token,
|
||||
token_expire_time, raw_user_info, del_flag, create_by, create_time, remark
|
||||
) values (
|
||||
#{userId}, #{provider}, #{providerUserId}, #{email}, #{name}, #{avatar}, #{accessToken}, #{refreshToken},
|
||||
#{tokenExpireTime}, #{rawUserInfo}, '0', #{createBy}, now(), #{remark}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
update hotake_social_user
|
||||
<set>
|
||||
<if test="userId != null">user_id = #{userId},</if>
|
||||
<if test="email != null">email = #{email},</if>
|
||||
<if test="name != null">name = #{name},</if>
|
||||
<if test="avatar != null">avatar = #{avatar},</if>
|
||||
<if test="accessToken != null">access_token = #{accessToken},</if>
|
||||
<if test="refreshToken != null">refresh_token = #{refreshToken},</if>
|
||||
<if test="tokenExpireTime != null">token_expire_time = #{tokenExpireTime},</if>
|
||||
<if test="rawUserInfo != null">raw_user_info = #{rawUserInfo},</if>
|
||||
update_by = #{updateBy},
|
||||
update_time = now()
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="deleteById">
|
||||
update hotake_social_user set del_flag = '2', update_time = now() where id = #{id}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user