240 lines
8.4 KiB
Java
240 lines
8.4 KiB
Java
package com.gxwebsoft.auto.service.impl;
|
||
|
||
import cn.hutool.core.lang.UUID;
|
||
import cn.hutool.core.util.StrUtil;
|
||
import com.gxwebsoft.auto.dto.*;
|
||
import com.gxwebsoft.auto.service.QrLoginService;
|
||
import com.gxwebsoft.common.core.security.JwtSubject;
|
||
import com.gxwebsoft.common.core.security.JwtUtil;
|
||
import com.gxwebsoft.common.core.utils.JSONUtil;
|
||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||
import com.gxwebsoft.common.system.entity.User;
|
||
import com.gxwebsoft.common.system.service.UserService;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.beans.factory.annotation.Value;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
import java.time.LocalDateTime;
|
||
import java.time.temporal.ChronoUnit;
|
||
import java.util.concurrent.TimeUnit;
|
||
|
||
import static com.gxwebsoft.common.core.constants.RedisConstants.*;
|
||
|
||
/**
|
||
* 扫码登录服务实现
|
||
*
|
||
* @author 科技小王子
|
||
* @since 2025-08-31
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
public class QrLoginServiceImpl implements QrLoginService {
|
||
|
||
@Autowired
|
||
private RedisUtil redisUtil;
|
||
|
||
@Autowired
|
||
private UserService userService;
|
||
|
||
@Value("${config.jwt.secret:websoft-jwt-secret-key-2025}")
|
||
private String jwtSecret;
|
||
|
||
@Value("${config.jwt.expire:86400}")
|
||
private Long jwtExpire;
|
||
|
||
@Override
|
||
public QrLoginGenerateResponse generateQrLoginToken() {
|
||
// 生成唯一的扫码登录token
|
||
String token = UUID.randomUUID().toString(true);
|
||
|
||
// 创建扫码登录数据
|
||
QrLoginData qrLoginData = new QrLoginData();
|
||
qrLoginData.setToken(token);
|
||
qrLoginData.setStatus(QR_LOGIN_STATUS_PENDING);
|
||
qrLoginData.setCreateTime(LocalDateTime.now());
|
||
qrLoginData.setExpireTime(LocalDateTime.now().plusSeconds(QR_LOGIN_TOKEN_TTL));
|
||
|
||
// 存储到Redis,设置过期时间
|
||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||
redisUtil.set(redisKey, qrLoginData, QR_LOGIN_TOKEN_TTL, TimeUnit.SECONDS);
|
||
|
||
log.info("生成扫码登录token: {}", token);
|
||
|
||
// 构造二维码内容(这里可以是前端登录页面的URL + token参数)
|
||
String qrCodeContent = "qr-login:" + token;
|
||
|
||
return new QrLoginGenerateResponse(token, qrCodeContent, QR_LOGIN_TOKEN_TTL);
|
||
}
|
||
|
||
@Override
|
||
public QrLoginStatusResponse checkQrLoginStatus(String token) {
|
||
if (StrUtil.isBlank(token)) {
|
||
return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L);
|
||
}
|
||
|
||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||
|
||
if (qrLoginData == null) {
|
||
return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L);
|
||
}
|
||
|
||
// 检查是否过期
|
||
if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) {
|
||
// 删除过期的token
|
||
redisUtil.delete(redisKey);
|
||
return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L);
|
||
}
|
||
|
||
// 计算剩余过期时间
|
||
long expiresIn = ChronoUnit.SECONDS.between(LocalDateTime.now(), qrLoginData.getExpireTime());
|
||
|
||
QrLoginStatusResponse response = new QrLoginStatusResponse();
|
||
response.setStatus(qrLoginData.getStatus());
|
||
response.setExpiresIn(expiresIn);
|
||
|
||
// 如果已确认,返回token和用户信息
|
||
if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())) {
|
||
response.setAccessToken(qrLoginData.getAccessToken());
|
||
|
||
// 获取用户信息
|
||
if (qrLoginData.getUserId() != null) {
|
||
User user = userService.getByIdRel(qrLoginData.getUserId());
|
||
if (user != null) {
|
||
// 清除敏感信息
|
||
user.setPassword(null);
|
||
response.setUserInfo(user);
|
||
}
|
||
}
|
||
|
||
// 确认后删除token,防止重复使用
|
||
redisUtil.delete(redisKey);
|
||
}
|
||
|
||
return response;
|
||
}
|
||
|
||
@Override
|
||
public QrLoginStatusResponse confirmQrLogin(QrLoginConfirmRequest request) {
|
||
String token = request.getToken();
|
||
Integer userId = request.getUserId();
|
||
String platform = request.getPlatform();
|
||
|
||
if (StrUtil.isBlank(token) || userId == null) {
|
||
throw new RuntimeException("参数不能为空");
|
||
}
|
||
|
||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||
|
||
if (qrLoginData == null) {
|
||
throw new RuntimeException("扫码登录token不存在或已过期");
|
||
}
|
||
|
||
// 检查是否过期
|
||
if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) {
|
||
redisUtil.delete(redisKey);
|
||
throw new RuntimeException("扫码登录token已过期");
|
||
}
|
||
|
||
// 获取用户信息
|
||
User user = userService.getByIdRel(userId);
|
||
if (user == null) {
|
||
throw new RuntimeException("用户不存在");
|
||
}
|
||
|
||
// 检查用户状态
|
||
if (user.getStatus() != null && user.getStatus() != 0) {
|
||
throw new RuntimeException("用户已被冻结");
|
||
}
|
||
|
||
// 如果是微信小程序登录,处理微信相关信息
|
||
if ("miniprogram".equals(platform) && request.getWechatInfo() != null) {
|
||
handleWechatMiniProgramLogin(user, request.getWechatInfo());
|
||
}
|
||
|
||
// 生成JWT token
|
||
JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId());
|
||
String accessToken = JwtUtil.buildToken(jwtSubject, jwtExpire, jwtSecret);
|
||
|
||
// 更新扫码登录数据
|
||
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
||
qrLoginData.setUserId(userId);
|
||
qrLoginData.setUsername(user.getUsername());
|
||
qrLoginData.setAccessToken(accessToken);
|
||
|
||
// 更新Redis中的数据
|
||
redisUtil.set(redisKey, qrLoginData, 60L, TimeUnit.SECONDS); // 给前端60秒时间获取token
|
||
|
||
log.info("用户 {} 通过 {} 平台确认扫码登录,token: {}", user.getUsername(),
|
||
platform != null ? platform : "unknown", token);
|
||
|
||
// 清除敏感信息
|
||
user.setPassword(null);
|
||
|
||
return new QrLoginStatusResponse(QR_LOGIN_STATUS_CONFIRMED, accessToken, user, 60L);
|
||
}
|
||
|
||
/**
|
||
* 处理微信小程序登录相关逻辑
|
||
*/
|
||
private void handleWechatMiniProgramLogin(User user, QrLoginConfirmRequest.WechatMiniProgramInfo wechatInfo) {
|
||
// 更新用户的微信信息
|
||
if (StrUtil.isNotBlank(wechatInfo.getOpenid())) {
|
||
user.setOpenid(wechatInfo.getOpenid());
|
||
}
|
||
if (StrUtil.isNotBlank(wechatInfo.getUnionid())) {
|
||
user.setUnionid(wechatInfo.getUnionid());
|
||
}
|
||
if (StrUtil.isNotBlank(wechatInfo.getNickname()) && StrUtil.isBlank(user.getNickname())) {
|
||
user.setNickname(wechatInfo.getNickname());
|
||
}
|
||
if (StrUtil.isNotBlank(wechatInfo.getAvatar()) && StrUtil.isBlank(user.getAvatar())) {
|
||
user.setAvatar(wechatInfo.getAvatar());
|
||
}
|
||
|
||
// 更新用户信息到数据库
|
||
try {
|
||
userService.updateById(user);
|
||
log.info("更新用户 {} 的微信小程序信息成功", user.getUsername());
|
||
} catch (Exception e) {
|
||
log.warn("更新用户 {} 的微信小程序信息失败: {}", user.getUsername(), e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public boolean scanQrCode(String token) {
|
||
if (StrUtil.isBlank(token)) {
|
||
return false;
|
||
}
|
||
|
||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||
|
||
if (qrLoginData == null) {
|
||
return false;
|
||
}
|
||
|
||
// 检查是否过期
|
||
if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) {
|
||
redisUtil.delete(redisKey);
|
||
return false;
|
||
}
|
||
|
||
// 只有pending状态才能更新为scanned
|
||
if (QR_LOGIN_STATUS_PENDING.equals(qrLoginData.getStatus())) {
|
||
qrLoginData.setStatus(QR_LOGIN_STATUS_SCANNED);
|
||
|
||
// 计算剩余过期时间
|
||
long remainingSeconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), qrLoginData.getExpireTime());
|
||
redisUtil.set(redisKey, qrLoginData, remainingSeconds, TimeUnit.SECONDS);
|
||
|
||
log.info("扫码登录token {} 状态更新为已扫码", token);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|