refactor(auto): 优化扫码登录逻辑与状态管理
- 引入统一的过期时间解析和剩余秒数计算方法,提升代码复用性 - 增加扫码登录状态刷新时对用户手机号绑定状态的处理逻辑 - 补充扫码登录状态存储流程,新增持久化方法支持过期时间自动更新 - 完善扫码登录完成流程,支持手机号未绑定时提示绑定操作 - 调整扫码登录相关日志输出,增强异常捕获与日志记录 - 移除冗余的字符串时间解析,改用统一的Date对象处理 - WxOfficialController 中新增构建 JWT 访问令牌的私有方法,简化代码结构
This commit is contained in:
0
.workbuddy/memory/MEMORY.md
Normal file
0
.workbuddy/memory/MEMORY.md
Normal file
6
javac.20260406_223620.args
Normal file
6
javac.20260406_223620.args
Normal file
File diff suppressed because one or more lines are too long
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -119,21 +120,38 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
return buildExpiredResponse();
|
return buildExpiredResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
Date expireAt = parseExpireTime(qrLoginData.getExpireTime());
|
||||||
|
if (expireAt == null || DateUtil.date().after(expireAt)) {
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
return buildExpiredResponse();
|
return buildExpiredResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
long expiresIn = Math.max(0L,
|
long expiresIn = calculateExpiresIn(expireAt);
|
||||||
(DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000);
|
|
||||||
|
|
||||||
if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())
|
if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())
|
||||||
&& StrUtil.isBlank(qrLoginData.getAccessToken())
|
&& StrUtil.isBlank(qrLoginData.getAccessToken())
|
||||||
&& qrLoginData.getUserId() != null) {
|
&& qrLoginData.getUserId() != null) {
|
||||||
|
try {
|
||||||
User user = userService.getAllByUserId(String.valueOf(qrLoginData.getUserId()));
|
User user = userService.getAllByUserId(String.valueOf(qrLoginData.getUserId()));
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
qrLoginData.setUsername(user.getUsername());
|
||||||
|
if (StrUtil.isBlank(user.getPhone())) {
|
||||||
|
qrLoginData.setStatus(QR_LOGIN_STATUS_BIND_PHONE);
|
||||||
|
qrLoginData.setNeedBindPhone(true);
|
||||||
|
qrLoginData.setAccessToken(null);
|
||||||
|
qrLoginData.setMessage("请先绑定手机号完成登录");
|
||||||
|
} else {
|
||||||
|
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
||||||
|
qrLoginData.setNeedBindPhone(false);
|
||||||
qrLoginData.setAccessToken(buildAccessToken(user));
|
qrLoginData.setAccessToken(buildAccessToken(user));
|
||||||
redisUtil.set(redisKey, qrLoginData, Math.max(expiresIn, 120L), TimeUnit.SECONDS);
|
qrLoginData.setMessage(StrUtil.blankToDefault(qrLoginData.getMessage(), "登录成功"));
|
||||||
|
}
|
||||||
|
long refreshedTtl = Math.max(expiresIn, 120L);
|
||||||
|
persistQrLoginData(redisKey, qrLoginData, refreshedTtl, true);
|
||||||
|
expiresIn = refreshedTtl;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("补全扫码登录状态失败,token={}", token, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +173,8 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
throw new RuntimeException("扫码登录token不存在或已过期");
|
throw new RuntimeException("扫码登录token不存在或已过期");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
Date expireAt = parseExpireTime(qrLoginData.getExpireTime());
|
||||||
|
if (expireAt == null || DateUtil.date().after(expireAt)) {
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
throw new RuntimeException("扫码登录token已过期");
|
throw new RuntimeException("扫码登录token已过期");
|
||||||
}
|
}
|
||||||
@@ -176,7 +195,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
qrLoginData.setTenantId(user.getTenantId());
|
qrLoginData.setTenantId(user.getTenantId());
|
||||||
qrLoginData.setNeedBindPhone(false);
|
qrLoginData.setNeedBindPhone(false);
|
||||||
qrLoginData.setMessage("登录成功");
|
qrLoginData.setMessage("登录成功");
|
||||||
redisUtil.set(redisKey, qrLoginData, 120L, TimeUnit.SECONDS);
|
persistQrLoginData(redisKey, qrLoginData, 120L, true);
|
||||||
|
|
||||||
log.info("用户 {} 确认扫码登录,token: {}", user.getUsername(), token);
|
log.info("用户 {} 确认扫码登录,token: {}", user.getUsername(), token);
|
||||||
return buildStatusResponse(qrLoginData, 120L);
|
return buildStatusResponse(qrLoginData, 120L);
|
||||||
@@ -194,7 +213,8 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
Date expireAt = parseExpireTime(qrLoginData.getExpireTime());
|
||||||
|
if (expireAt == null || DateUtil.date().after(expireAt)) {
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -203,7 +223,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
qrLoginData.setStatus(QR_LOGIN_STATUS_SCANNED);
|
qrLoginData.setStatus(QR_LOGIN_STATUS_SCANNED);
|
||||||
qrLoginData.setMessage("已识别扫码,等待公众号回调");
|
qrLoginData.setMessage("已识别扫码,等待公众号回调");
|
||||||
long remainingSeconds = Math.max(1L,
|
long remainingSeconds = Math.max(1L,
|
||||||
(DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000);
|
(expireAt.getTime() - DateUtil.date().getTime()) / 1000);
|
||||||
redisUtil.set(redisKey, qrLoginData, remainingSeconds, TimeUnit.SECONDS);
|
redisUtil.set(redisKey, qrLoginData, remainingSeconds, TimeUnit.SECONDS);
|
||||||
log.info("扫码登录token {} 状态更新为已扫码", token);
|
log.info("扫码登录token {} 状态更新为已扫码", token);
|
||||||
return true;
|
return true;
|
||||||
@@ -226,7 +246,8 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
if (qrLoginData == null) {
|
if (qrLoginData == null) {
|
||||||
throw new RuntimeException("二维码已过期,请刷新后重试");
|
throw new RuntimeException("二维码已过期,请刷新后重试");
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
Date expireAt = parseExpireTime(qrLoginData.getExpireTime());
|
||||||
|
if (expireAt == null || DateUtil.date().after(expireAt)) {
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
throw new RuntimeException("二维码已过期,请刷新后重试");
|
throw new RuntimeException("二维码已过期,请刷新后重试");
|
||||||
}
|
}
|
||||||
@@ -278,7 +299,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
qrLoginData.setAccessToken(accessToken);
|
qrLoginData.setAccessToken(accessToken);
|
||||||
qrLoginData.setNeedBindPhone(false);
|
qrLoginData.setNeedBindPhone(false);
|
||||||
qrLoginData.setMessage("手机号绑定成功,正在登录");
|
qrLoginData.setMessage("手机号绑定成功,正在登录");
|
||||||
redisUtil.set(redisKey, qrLoginData, 120L, TimeUnit.SECONDS);
|
persistQrLoginData(redisKey, qrLoginData, 120L, true);
|
||||||
|
|
||||||
return buildStatusResponse(qrLoginData, 120L);
|
return buildStatusResponse(qrLoginData, 120L);
|
||||||
}
|
}
|
||||||
@@ -378,6 +399,32 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
return JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
return JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Date parseExpireTime(String expireTime) {
|
||||||
|
if (StrUtil.isBlank(expireTime)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return DateUtil.parseDateTime(expireTime);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("扫码登录 expireTime 解析失败: {}", expireTime, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long calculateExpiresIn(Date expireAt) {
|
||||||
|
if (expireAt == null) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
return Math.max(0L, (expireAt.getTime() - DateUtil.date().getTime()) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistQrLoginData(String redisKey, QrLoginData qrLoginData, long ttlSeconds, boolean refreshExpireTime) {
|
||||||
|
if (refreshExpireTime) {
|
||||||
|
qrLoginData.setExpireTime(DateUtil.formatDateTime(DateUtil.offsetSecond(DateUtil.date(), (int) ttlSeconds)));
|
||||||
|
}
|
||||||
|
redisUtil.set(redisKey, qrLoginData, ttlSeconds, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
private QrLoginStatusResponse buildExpiredResponse() {
|
private QrLoginStatusResponse buildExpiredResponse() {
|
||||||
QrLoginStatusResponse response = new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L, null);
|
QrLoginStatusResponse response = new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L, null);
|
||||||
response.setNeedBindPhone(false);
|
response.setNeedBindPhone(false);
|
||||||
@@ -396,11 +443,15 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
response.setMessage(qrLoginData.getMessage());
|
response.setMessage(qrLoginData.getMessage());
|
||||||
|
|
||||||
if (qrLoginData.getUserId() != null) {
|
if (qrLoginData.getUserId() != null) {
|
||||||
|
try {
|
||||||
User user = userService.getAllByUserId(String.valueOf(qrLoginData.getUserId()));
|
User user = userService.getAllByUserId(String.valueOf(qrLoginData.getUserId()));
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
user.setPassword(null);
|
user.setPassword(null);
|
||||||
response.setUserInfo(user);
|
response.setUserInfo(user);
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("构建扫码登录状态响应时查询用户失败,userId={}", qrLoginData.getUserId(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -417,7 +468,8 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
if (qrLoginData == null) {
|
if (qrLoginData == null) {
|
||||||
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
Date expireAt = parseExpireTime(qrLoginData.getExpireTime());
|
||||||
|
if (expireAt == null || DateUtil.date().after(expireAt)) {
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
||||||
}
|
}
|
||||||
@@ -490,6 +542,18 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
return WechatScanResponse.notBound("账号已被冻结");
|
return WechatScanResponse.notBound("账号已被冻结");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(user.getPhone())) {
|
||||||
|
qrLoginData.setStatus(QR_LOGIN_STATUS_BIND_PHONE);
|
||||||
|
qrLoginData.setUserId(user.getUserId());
|
||||||
|
qrLoginData.setUsername(user.getUsername());
|
||||||
|
qrLoginData.setTenantId(user.getTenantId());
|
||||||
|
qrLoginData.setAccessToken(null);
|
||||||
|
qrLoginData.setNeedBindPhone(true);
|
||||||
|
qrLoginData.setMessage("请先绑定手机号完成登录");
|
||||||
|
persistQrLoginData(redisKey, qrLoginData, 120L, true);
|
||||||
|
return WechatScanResponse.needBind("请先绑定手机号完成登录");
|
||||||
|
}
|
||||||
|
|
||||||
String accessToken = buildAccessToken(user);
|
String accessToken = buildAccessToken(user);
|
||||||
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
||||||
qrLoginData.setUserId(user.getUserId());
|
qrLoginData.setUserId(user.getUserId());
|
||||||
@@ -498,7 +562,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
qrLoginData.setTenantId(user.getTenantId());
|
qrLoginData.setTenantId(user.getTenantId());
|
||||||
qrLoginData.setNeedBindPhone(false);
|
qrLoginData.setNeedBindPhone(false);
|
||||||
qrLoginData.setMessage("登录成功");
|
qrLoginData.setMessage("登录成功");
|
||||||
redisUtil.set(redisKey, qrLoginData, 120L, TimeUnit.SECONDS);
|
persistQrLoginData(redisKey, qrLoginData, 120L, true);
|
||||||
|
|
||||||
user.setPassword(null);
|
user.setPassword(null);
|
||||||
return WechatScanResponse.success(accessToken, user, user.getTenantId());
|
return WechatScanResponse.success(accessToken, user, user.getTenantId());
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
|
|||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import com.gxwebsoft.common.core.Constants;
|
import com.gxwebsoft.common.core.Constants;
|
||||||
import com.gxwebsoft.common.core.web.ApiResult;
|
import com.gxwebsoft.common.core.web.ApiResult;
|
||||||
|
import com.gxwebsoft.common.core.utils.JSONUtil;
|
||||||
import com.gxwebsoft.common.system.entity.Role;
|
import com.gxwebsoft.common.system.entity.Role;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.gxwebsoft.common.system.controller;
|
package com.gxwebsoft.common.system.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.core.util.XmlUtil;
|
import cn.hutool.core.util.XmlUtil;
|
||||||
@@ -95,6 +96,8 @@ public class WxOfficialController extends BaseController {
|
|||||||
private WxService wxService;
|
private WxService wxService;
|
||||||
@Resource
|
@Resource
|
||||||
private RedisUtil redisUtil;
|
private RedisUtil redisUtil;
|
||||||
|
@Resource
|
||||||
|
private ConfigProperties configProperties;
|
||||||
|
|
||||||
@Operation(summary = "验证微信服务器")
|
@Operation(summary = "验证微信服务器")
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@@ -315,37 +318,51 @@ public class WxOfficialController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
private void completeQrLogin(String token, Integer userId, Integer tenantId) {
|
private void completeQrLogin(String token, Integer userId, Integer tenantId) {
|
||||||
try {
|
try {
|
||||||
// 获取已有的扫码登录数据
|
|
||||||
String redisKey = "qr-login:token:" + token;
|
String redisKey = "qr-login:token:" + token;
|
||||||
String existingData = redisUtil.get(redisKey);
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||||||
|
if (qrLoginData == null) {
|
||||||
|
qrLoginData = new QrLoginData();
|
||||||
|
qrLoginData.setToken(token);
|
||||||
|
qrLoginData.setCreateTime(DateUtil.formatDateTime(DateUtil.date()));
|
||||||
|
}
|
||||||
|
|
||||||
// 如果有现有数据,解析后更新
|
User user = userService.getAllByUserId(String.valueOf(userId));
|
||||||
if (StrUtil.isNotBlank(existingData)) {
|
if (user == null) {
|
||||||
JSONObject jsonData = JSONObject.parseObject(existingData);
|
log.warn("扫码登录完成时未找到用户,token={}, userId={}", token, userId);
|
||||||
jsonData.put("status", "confirmed");
|
return;
|
||||||
jsonData.put("userId", userId);
|
}
|
||||||
jsonData.put("tenantId", tenantId);
|
|
||||||
jsonData.put("confirmTime", System.currentTimeMillis());
|
long ttlSeconds = 120L;
|
||||||
// 保存60秒,给前端足够时间获取
|
qrLoginData.setToken(token);
|
||||||
redisUtil.set(redisKey, jsonData.toJSONString(), 60L, TimeUnit.SECONDS);
|
qrLoginData.setUserId(userId);
|
||||||
System.out.println("扫码登录完成,token=" + token + ", userId=" + userId);
|
qrLoginData.setUsername(user.getUsername());
|
||||||
|
qrLoginData.setTenantId(user.getTenantId() != null ? user.getTenantId() : tenantId);
|
||||||
|
qrLoginData.setExpireTime(DateUtil.formatDateTime(DateUtil.offsetSecond(DateUtil.date(), (int) ttlSeconds)));
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(user.getPhone())) {
|
||||||
|
qrLoginData.setStatus("bind_phone");
|
||||||
|
qrLoginData.setNeedBindPhone(true);
|
||||||
|
qrLoginData.setAccessToken(null);
|
||||||
|
qrLoginData.setMessage("请先绑定手机号完成登录");
|
||||||
} else {
|
} else {
|
||||||
// 没有现有数据,创建一个新的
|
qrLoginData.setStatus("confirmed");
|
||||||
JSONObject qrLoginData = new JSONObject();
|
qrLoginData.setNeedBindPhone(false);
|
||||||
qrLoginData.put("token", token);
|
qrLoginData.setAccessToken(buildAccessToken(user));
|
||||||
qrLoginData.put("status", "confirmed");
|
qrLoginData.setMessage("登录成功");
|
||||||
qrLoginData.put("userId", userId);
|
|
||||||
qrLoginData.put("tenantId", tenantId);
|
|
||||||
qrLoginData.put("confirmTime", System.currentTimeMillis());
|
|
||||||
// 保存60秒
|
|
||||||
redisUtil.set(redisKey, qrLoginData.toJSONString(), 60L, TimeUnit.SECONDS);
|
|
||||||
System.out.println("扫码登录完成(新建),token=" + token + ", userId=" + userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redisUtil.set(redisKey, qrLoginData, ttlSeconds, TimeUnit.SECONDS);
|
||||||
|
log.info("扫码登录状态已更新,token={}, userId={}, status={}", token, userId, qrLoginData.getStatus());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("完成扫码登录失败: {}", e.getMessage());
|
log.error("完成扫码登录失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildAccessToken(User user) {
|
||||||
|
JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId());
|
||||||
|
return JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "生成微信扫码登录二维码")
|
@Operation(summary = "生成微信扫码登录二维码")
|
||||||
@GetMapping("/qrcode/{token}")
|
@GetMapping("/qrcode/{token}")
|
||||||
public ApiResult<?> generateQrCode(@PathVariable("token") String token) {
|
public ApiResult<?> generateQrCode(@PathVariable("token") String token) {
|
||||||
|
|||||||
Reference in New Issue
Block a user