feat(qrLogin): 支持公众号扫码登录绑定手机号功能
- 新增扫码登录绑定手机号请求参数类 QrLoginBindPhoneRequest - 在 QrLoginController 添加绑定手机号并完成扫码登录的接口 - QrLoginData 增加 needBindPhone 和 message 字段,支持绑定手机号状态描述 - QrLoginGenerateResponse 添加公众号二维码图片URL字段 wechatQrCodeUrl - QrLoginService 新增 bindPhone 方法以支持手机号绑定流程 - QrLoginServiceImpl 实现手机号绑定逻辑,包含验证码校验及用户信息更新 - 优化扫码登录状态查询和确认逻辑,支持待绑定手机号状态及提示信息 - 生成公众号带参数二维码方法,实现公众号扫码登录二维码的生成 - 扫码状态新增 bind_phone 状态和对应常量,区分待绑定手机号阶段 - 改进扫码登录token过期判断与缓存处理,完善异常处理和日志记录 - 统一构建扫码登录状态响应,返回包含手机号绑定需求及状态信息
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.gxwebsoft.auto.controller;
|
package com.gxwebsoft.auto.controller;
|
||||||
|
|
||||||
|
import com.gxwebsoft.auto.dto.QrLoginBindPhoneRequest;
|
||||||
import com.gxwebsoft.auto.dto.QrLoginConfirmRequest;
|
import com.gxwebsoft.auto.dto.QrLoginConfirmRequest;
|
||||||
import com.gxwebsoft.auto.dto.QrLoginGenerateResponse;
|
import com.gxwebsoft.auto.dto.QrLoginGenerateResponse;
|
||||||
import com.gxwebsoft.auto.dto.QrLoginStatusResponse;
|
import com.gxwebsoft.auto.dto.QrLoginStatusResponse;
|
||||||
@@ -93,6 +94,20 @@ public class QrLoginController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公众号关注注册后绑定手机号
|
||||||
|
*/
|
||||||
|
@Operation(summary = "绑定手机号并完成扫码登录")
|
||||||
|
@PostMapping("/bind-phone")
|
||||||
|
public ApiResult<?> bindPhone(@Valid @RequestBody QrLoginBindPhoneRequest request) {
|
||||||
|
try {
|
||||||
|
QrLoginStatusResponse response = qrLoginService.bindPhone(request);
|
||||||
|
return success("绑定成功", response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信扫码登录确认(H5页面调用)
|
* 微信扫码登录确认(H5页面调用)
|
||||||
*/
|
*/
|
||||||
@@ -117,7 +132,7 @@ public class QrLoginController extends BaseController {
|
|||||||
String appId = wxService.getOfficialAppId(getTenantId());
|
String appId = wxService.getOfficialAppId(getTenantId());
|
||||||
// 回调地址,指向 H5 扫码确认页面
|
// 回调地址,指向 H5 扫码确认页面
|
||||||
String redirectUri = java.net.URLEncoder.encode(
|
String redirectUri = java.net.URLEncoder.encode(
|
||||||
"https://" + request.getHeader("Host") + "/wx-scan?token=" + token,
|
"https://" + request.getHeader("Host") + "/wx-scan?token=" + token,
|
||||||
java.nio.charset.StandardCharsets.UTF_8);
|
java.nio.charset.StandardCharsets.UTF_8);
|
||||||
// 构造微信 OAuth 授权 URL
|
// 构造微信 OAuth 授权 URL
|
||||||
String oauthUrl = String.format(
|
String oauthUrl = String.format(
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.gxwebsoft.auto.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫码登录绑定手机号请求
|
||||||
|
*
|
||||||
|
* @author 科技小王子
|
||||||
|
* @since 2026-04-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "扫码登录绑定手机号请求")
|
||||||
|
public class QrLoginBindPhoneRequest {
|
||||||
|
|
||||||
|
@Schema(description = "扫码登录token")
|
||||||
|
@NotBlank(message = "token不能为空")
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Schema(description = "手机号")
|
||||||
|
@NotBlank(message = "手机号不能为空")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Schema(description = "短信验证码")
|
||||||
|
@NotBlank(message = "验证码不能为空")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,8 +4,6 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫码登录数据模型
|
* 扫码登录数据模型
|
||||||
*
|
*
|
||||||
@@ -23,7 +21,7 @@ public class QrLoginData {
|
|||||||
private String token;
|
private String token;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期
|
* 状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, bind_phone-待绑定手机号, expired-已过期
|
||||||
*/
|
*/
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@@ -57,4 +55,14 @@ public class QrLoginData {
|
|||||||
*/
|
*/
|
||||||
private Integer tenantId;
|
private Integer tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要绑定手机号
|
||||||
|
*/
|
||||||
|
private Boolean needBindPhone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态提示信息
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ public class QrLoginGenerateResponse {
|
|||||||
@Schema(description = "微信公众号AppID")
|
@Schema(description = "微信公众号AppID")
|
||||||
private String wechatAppId;
|
private String wechatAppId;
|
||||||
|
|
||||||
|
@Schema(description = "微信公众号带参数二维码图片URL")
|
||||||
|
private String wechatQrCodeUrl;
|
||||||
|
|
||||||
// 保持向后兼容的构造函数
|
// 保持向后兼容的构造函数
|
||||||
public QrLoginGenerateResponse(String token, String qrCodeContent, Long expiresIn) {
|
public QrLoginGenerateResponse(String token, String qrCodeContent, Long expiresIn) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.gxwebsoft.auto.dto;
|
|||||||
|
|
||||||
import com.gxwebsoft.common.system.entity.User;
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
@@ -14,17 +13,16 @@ import lombok.NoArgsConstructor;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
|
||||||
@Schema(description = "扫码登录状态响应")
|
@Schema(description = "扫码登录状态响应")
|
||||||
public class QrLoginStatusResponse {
|
public class QrLoginStatusResponse {
|
||||||
|
|
||||||
@Schema(description = "状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期")
|
@Schema(description = "状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, bind_phone-待绑定手机号, expired-已过期")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@Schema(description = "JWT访问令牌(仅在confirmed状态时返回)")
|
@Schema(description = "JWT访问令牌(仅在confirmed状态时返回)")
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
|
|
||||||
@Schema(description = "用户信息(仅在confirmed状态时返回)")
|
@Schema(description = "用户信息")
|
||||||
private User userInfo;
|
private User userInfo;
|
||||||
|
|
||||||
@Schema(description = "剩余过期时间(秒)")
|
@Schema(description = "剩余过期时间(秒)")
|
||||||
@@ -33,4 +31,18 @@ public class QrLoginStatusResponse {
|
|||||||
@Schema(description = "租户ID")
|
@Schema(description = "租户ID")
|
||||||
private Integer tenantId;
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "是否需要绑定手机号")
|
||||||
|
private Boolean needBindPhone;
|
||||||
|
|
||||||
|
@Schema(description = "状态提示信息")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public QrLoginStatusResponse(String status, String accessToken, User userInfo, Long expiresIn, Integer tenantId) {
|
||||||
|
this.status = status;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.userInfo = userInfo;
|
||||||
|
this.expiresIn = expiresIn;
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.gxwebsoft.auto.service;
|
package com.gxwebsoft.auto.service;
|
||||||
|
|
||||||
|
import com.gxwebsoft.auto.dto.QrLoginBindPhoneRequest;
|
||||||
import com.gxwebsoft.auto.dto.QrLoginConfirmRequest;
|
import com.gxwebsoft.auto.dto.QrLoginConfirmRequest;
|
||||||
import com.gxwebsoft.auto.dto.QrLoginGenerateResponse;
|
import com.gxwebsoft.auto.dto.QrLoginGenerateResponse;
|
||||||
import com.gxwebsoft.auto.dto.QrLoginStatusResponse;
|
import com.gxwebsoft.auto.dto.QrLoginStatusResponse;
|
||||||
@@ -45,6 +46,14 @@ public interface QrLoginService {
|
|||||||
*/
|
*/
|
||||||
boolean scanQrCode(String token);
|
boolean scanQrCode(String token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注后绑定手机号并完成登录
|
||||||
|
*
|
||||||
|
* @param request 绑定手机号请求
|
||||||
|
* @return QrLoginStatusResponse
|
||||||
|
*/
|
||||||
|
QrLoginStatusResponse bindPhone(QrLoginBindPhoneRequest request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信扫码登录确认(H5页面调用)
|
* 微信扫码登录确认(H5页面调用)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.gxwebsoft.auto.service.impl;
|
|||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.core.lang.UUID;
|
import cn.hutool.core.lang.UUID;
|
||||||
|
import cn.hutool.core.util.DesensitizedUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.HttpRequest;
|
import cn.hutool.http.HttpRequest;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
@@ -13,6 +14,7 @@ import com.gxwebsoft.auto.service.QrLoginService;
|
|||||||
import com.gxwebsoft.common.core.config.ConfigProperties;
|
import com.gxwebsoft.common.core.config.ConfigProperties;
|
||||||
import com.gxwebsoft.common.core.security.JwtSubject;
|
import com.gxwebsoft.common.core.security.JwtSubject;
|
||||||
import com.gxwebsoft.common.core.security.JwtUtil;
|
import com.gxwebsoft.common.core.security.JwtUtil;
|
||||||
|
import com.gxwebsoft.common.core.utils.CommonUtil;
|
||||||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||||
import com.gxwebsoft.common.system.entity.User;
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
import com.gxwebsoft.common.system.entity.UserOauth;
|
import com.gxwebsoft.common.system.entity.UserOauth;
|
||||||
@@ -27,8 +29,9 @@ import java.io.File;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static com.gxwebsoft.common.core.constants.RedisConstants.*;
|
|
||||||
import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_OFFICIAL;
|
import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_OFFICIAL;
|
||||||
|
import static com.gxwebsoft.common.core.constants.RedisConstants.*;
|
||||||
|
import static com.gxwebsoft.common.core.constants.WebsiteConstants.CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫码登录服务实现
|
* 扫码登录服务实现
|
||||||
@@ -55,66 +58,50 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private UserOauthService userOauthService;
|
private UserOauthService userOauthService;
|
||||||
|
|
||||||
private static final String QR_LOGIN_TOKEN = "QR_LOGIN_TOKEN";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QrLoginGenerateResponse generateQrLoginToken(Integer tenantId) {
|
public QrLoginGenerateResponse generateQrLoginToken(Integer tenantId) {
|
||||||
// 生成唯一的扫码登录token
|
|
||||||
String token = UUID.randomUUID().toString(true);
|
String token = UUID.randomUUID().toString(true);
|
||||||
|
|
||||||
// 创建扫码登录数据
|
|
||||||
QrLoginData qrLoginData = new QrLoginData();
|
QrLoginData qrLoginData = new QrLoginData();
|
||||||
qrLoginData.setToken(token);
|
qrLoginData.setToken(token);
|
||||||
qrLoginData.setStatus(QR_LOGIN_STATUS_PENDING);
|
qrLoginData.setStatus(QR_LOGIN_STATUS_PENDING);
|
||||||
qrLoginData.setTenantId(tenantId);
|
qrLoginData.setTenantId(tenantId);
|
||||||
|
qrLoginData.setNeedBindPhone(false);
|
||||||
|
qrLoginData.setMessage("等待微信扫码");
|
||||||
qrLoginData.setCreateTime(DateUtil.formatDateTime(DateUtil.date()));
|
qrLoginData.setCreateTime(DateUtil.formatDateTime(DateUtil.date()));
|
||||||
qrLoginData.setExpireTime(DateUtil.formatDateTime(DateUtil.offsetSecond(DateUtil.date(), QR_LOGIN_TOKEN_TTL.intValue())));
|
qrLoginData.setExpireTime(DateUtil.formatDateTime(DateUtil.offsetSecond(DateUtil.date(), QR_LOGIN_TOKEN_TTL.intValue())));
|
||||||
|
|
||||||
// 存储到Redis,设置过期时间
|
|
||||||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||||||
redisUtil.set(redisKey, qrLoginData, QR_LOGIN_TOKEN_TTL, TimeUnit.SECONDS);
|
redisUtil.set(redisKey, qrLoginData, QR_LOGIN_TOKEN_TTL, TimeUnit.SECONDS);
|
||||||
|
|
||||||
log.info("生成扫码登录token: {}", token);
|
log.info("生成扫码登录token: {}", token);
|
||||||
|
|
||||||
// 构造响应对象
|
|
||||||
QrLoginGenerateResponse response = new QrLoginGenerateResponse();
|
QrLoginGenerateResponse response = new QrLoginGenerateResponse();
|
||||||
response.setToken(token);
|
response.setToken(token);
|
||||||
response.setExpiresIn(QR_LOGIN_TOKEN_TTL);
|
response.setExpiresIn(QR_LOGIN_TOKEN_TTL);
|
||||||
|
|
||||||
// APP扫码内容
|
|
||||||
response.setQrCodeContent("qr-login:" + token);
|
response.setQrCodeContent("qr-login:" + token);
|
||||||
|
|
||||||
// 微信小程序路径
|
|
||||||
response.setMiniprogramPath("/pages/qr-login?token=" + token);
|
response.setMiniprogramPath("/pages/qr-login?token=" + token);
|
||||||
|
|
||||||
// 生成微信小程序码
|
|
||||||
try {
|
try {
|
||||||
String miniprogramQrCodeUrl = generateMiniprogramQrCode(token,tenantId);
|
String miniprogramQrCodeUrl = generateMiniprogramQrCode(token, tenantId);
|
||||||
response.setMiniprogramQrCodeUrl(miniprogramQrCodeUrl);
|
response.setMiniprogramQrCodeUrl(miniprogramQrCodeUrl);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("生成微信小程序码失败: {}", e.getMessage());
|
log.warn("生成微信小程序码失败: {}", e.getMessage());
|
||||||
// 小程序码生成失败不影响整体功能,继续返回其他信息
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成微信扫码登录 H5 页面 URL
|
|
||||||
try {
|
try {
|
||||||
String appId = wxService.getOfficialAppId(tenantId);
|
String appId = wxService.getOfficialAppId(tenantId);
|
||||||
// 优先使用专门的微信扫码配置,否则使用文件服务器地址
|
|
||||||
String baseUrl = configProperties.getWechatScanUrl();
|
String baseUrl = configProperties.getWechatScanUrl();
|
||||||
if (StrUtil.isBlank(baseUrl)) {
|
if (StrUtil.isBlank(baseUrl)) {
|
||||||
baseUrl = configProperties.getFileServer();
|
baseUrl = "https://websopy.websoft.top";
|
||||||
}
|
}
|
||||||
if (StrUtil.isBlank(baseUrl)) {
|
|
||||||
baseUrl = "https://server.websoft.top";
|
|
||||||
}
|
|
||||||
// 微信扫码后跳转的 H5 确认页面
|
|
||||||
String wechatScanUrl = baseUrl + "/wx-scan?token=" + token;
|
String wechatScanUrl = baseUrl + "/wx-scan?token=" + token;
|
||||||
response.setWechatScanUrl(wechatScanUrl);
|
response.setWechatScanUrl(wechatScanUrl);
|
||||||
response.setWechatAppId(appId);
|
response.setWechatAppId(appId);
|
||||||
log.info("生成微信扫码登录URL: {}", wechatScanUrl);
|
response.setWechatQrCodeUrl(generateOfficialQrCodeUrl(token, tenantId));
|
||||||
|
log.info("生成公众号扫码登录URL: {}", wechatScanUrl);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("生成微信扫码URL失败: {}", e.getMessage());
|
log.warn("生成公众号扫码URL失败: {}", e.getMessage());
|
||||||
// 不影响整体功能
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -123,49 +110,34 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
@Override
|
@Override
|
||||||
public QrLoginStatusResponse checkQrLoginStatus(String token) {
|
public QrLoginStatusResponse checkQrLoginStatus(String token) {
|
||||||
if (StrUtil.isBlank(token)) {
|
if (StrUtil.isBlank(token)) {
|
||||||
return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L, null);
|
return buildExpiredResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||||||
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||||||
|
|
||||||
if (qrLoginData == null) {
|
if (qrLoginData == null) {
|
||||||
return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L, null);
|
return buildExpiredResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否过期
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
||||||
if (DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
||||||
// 删除过期的token
|
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L, null);
|
return buildExpiredResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算剩余过期时间
|
long expiresIn = Math.max(0L,
|
||||||
long expiresIn = (DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000;
|
(DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000);
|
||||||
|
|
||||||
QrLoginStatusResponse response = new QrLoginStatusResponse();
|
if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())
|
||||||
response.setStatus(qrLoginData.getStatus());
|
&& StrUtil.isBlank(qrLoginData.getAccessToken())
|
||||||
response.setExpiresIn(expiresIn);
|
&& qrLoginData.getUserId() != null) {
|
||||||
response.setTenantId(qrLoginData.getTenantId());
|
User user = userService.getAllByUserId(String.valueOf(qrLoginData.getUserId()));
|
||||||
|
if (user != null) {
|
||||||
// 如果已确认,返回token和用户信息
|
qrLoginData.setAccessToken(buildAccessToken(user));
|
||||||
if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())) {
|
redisUtil.set(redisKey, qrLoginData, Math.max(expiresIn, 120L), TimeUnit.SECONDS);
|
||||||
response.setAccessToken(qrLoginData.getAccessToken());
|
|
||||||
// 获取用户信息
|
|
||||||
if (qrLoginData.getUserId() != null) {
|
|
||||||
User user = userService.getAllByUserId("" + qrLoginData.getUserId());
|
|
||||||
if (user != null) {
|
|
||||||
// 清除敏感信息
|
|
||||||
user.setPassword(null);
|
|
||||||
response.setUserInfo(user);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认后删除token,防止重复使用
|
|
||||||
// redisUtil.delete(redisKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return buildStatusResponse(qrLoginData, expiresIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -179,47 +151,35 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
|
|
||||||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||||||
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||||||
|
|
||||||
if (qrLoginData == null) {
|
if (qrLoginData == null) {
|
||||||
throw new RuntimeException("扫码登录token不存在或已过期");
|
throw new RuntimeException("扫码登录token不存在或已过期");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否过期
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
||||||
if (DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
throw new RuntimeException("扫码登录token已过期");
|
throw new RuntimeException("扫码登录token已过期");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
|
||||||
User user = userService.getAllByUserId(String.valueOf(userId));
|
User user = userService.getAllByUserId(String.valueOf(userId));
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new RuntimeException("用户不存在");
|
throw new RuntimeException("用户不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查用户状态
|
|
||||||
if (user.getStatus() != null && user.getStatus() != 0) {
|
if (user.getStatus() != null && user.getStatus() != 0) {
|
||||||
throw new RuntimeException("用户已被冻结");
|
throw new RuntimeException("用户已被冻结");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成JWT token
|
String accessToken = buildAccessToken(user);
|
||||||
JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId());
|
|
||||||
String accessToken = JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
|
||||||
|
|
||||||
// 更新扫码登录数据
|
|
||||||
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
||||||
qrLoginData.setUserId(userId);
|
qrLoginData.setUserId(userId);
|
||||||
qrLoginData.setUsername(user.getUsername());
|
qrLoginData.setUsername(user.getUsername());
|
||||||
qrLoginData.setAccessToken(accessToken);
|
qrLoginData.setAccessToken(accessToken);
|
||||||
qrLoginData.setTenantId(user.getTenantId());
|
qrLoginData.setTenantId(user.getTenantId());
|
||||||
// 更新Redis中的数据
|
qrLoginData.setNeedBindPhone(false);
|
||||||
redisUtil.set(redisKey, qrLoginData, 60L, TimeUnit.SECONDS); // 给前端60秒时间获取token
|
qrLoginData.setMessage("登录成功");
|
||||||
|
redisUtil.set(redisKey, qrLoginData, 120L, TimeUnit.SECONDS);
|
||||||
|
|
||||||
log.info("用户 {} 确认扫码登录,token: {}", user.getUsername(), token);
|
log.info("用户 {} 确认扫码登录,token: {}", user.getUsername(), token);
|
||||||
|
return buildStatusResponse(qrLoginData, 120L);
|
||||||
// 清除敏感信息
|
|
||||||
user.setPassword(null);
|
|
||||||
|
|
||||||
return new QrLoginStatusResponse(QR_LOGIN_STATUS_CONFIRMED, accessToken, user, 60L, user.getTenantId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -230,25 +190,21 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
|
|
||||||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||||||
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||||||
|
|
||||||
if (qrLoginData == null) {
|
if (qrLoginData == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否过期
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
||||||
if (DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只有pending状态才能更新为scanned
|
|
||||||
if (QR_LOGIN_STATUS_PENDING.equals(qrLoginData.getStatus())) {
|
if (QR_LOGIN_STATUS_PENDING.equals(qrLoginData.getStatus())) {
|
||||||
qrLoginData.setStatus(QR_LOGIN_STATUS_SCANNED);
|
qrLoginData.setStatus(QR_LOGIN_STATUS_SCANNED);
|
||||||
|
qrLoginData.setMessage("已识别扫码,等待公众号回调");
|
||||||
// 计算剩余过期时间
|
long remainingSeconds = Math.max(1L,
|
||||||
long remainingSeconds = (DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000;
|
(DateUtil.parseDateTime(qrLoginData.getExpireTime()).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;
|
||||||
}
|
}
|
||||||
@@ -256,12 +212,82 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QrLoginStatusResponse bindPhone(QrLoginBindPhoneRequest request) {
|
||||||
|
if (request == null || StrUtil.isBlank(request.getToken()) || StrUtil.isBlank(request.getPhone()) || StrUtil.isBlank(request.getCode())) {
|
||||||
|
throw new RuntimeException("参数不能为空");
|
||||||
|
}
|
||||||
|
if (!CommonUtil.isValidPhoneNumber(request.getPhone())) {
|
||||||
|
throw new RuntimeException("请输入有效的手机号码");
|
||||||
|
}
|
||||||
|
|
||||||
|
String redisKey = QR_LOGIN_TOKEN_KEY + request.getToken();
|
||||||
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||||||
|
if (qrLoginData == null) {
|
||||||
|
throw new RuntimeException("二维码已过期,请刷新后重试");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
||||||
|
redisUtil.delete(redisKey);
|
||||||
|
throw new RuntimeException("二维码已过期,请刷新后重试");
|
||||||
|
}
|
||||||
|
if (!QR_LOGIN_STATUS_BIND_PHONE.equals(qrLoginData.getStatus()) && !Boolean.TRUE.equals(qrLoginData.getNeedBindPhone())) {
|
||||||
|
throw new RuntimeException("当前二维码无需绑定手机号");
|
||||||
|
}
|
||||||
|
if (qrLoginData.getUserId() == null) {
|
||||||
|
throw new RuntimeException("绑定账号不存在,请重新扫码");
|
||||||
|
}
|
||||||
|
|
||||||
|
String codeKey = "code:" + request.getPhone();
|
||||||
|
String smsCode = redisUtil.get(codeKey);
|
||||||
|
String devCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
|
||||||
|
if (StrUtil.isBlank(smsCode) && StrUtil.isBlank(devCode)) {
|
||||||
|
throw new RuntimeException("验证码已过期,请重新获取");
|
||||||
|
}
|
||||||
|
if (!StrUtil.equals(request.getCode(), smsCode) && !StrUtil.equals(request.getCode(), devCode)) {
|
||||||
|
throw new RuntimeException("验证码不正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userService.getAllByUserId(String.valueOf(qrLoginData.getUserId()));
|
||||||
|
if (user == null) {
|
||||||
|
throw new RuntimeException("用户不存在");
|
||||||
|
}
|
||||||
|
if (user.getStatus() != null && user.getStatus() != 0) {
|
||||||
|
throw new RuntimeException("账号已被冻结");
|
||||||
|
}
|
||||||
|
|
||||||
|
User existed = userService.getByPhone(request.getPhone());
|
||||||
|
if (existed != null && !existed.getUserId().equals(user.getUserId())) {
|
||||||
|
throw new RuntimeException("该手机号已绑定其他账号");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setPhone(request.getPhone());
|
||||||
|
if (StrUtil.isBlank(user.getNickname()) || "微信公众号用户".equals(user.getNickname())) {
|
||||||
|
user.setNickname(DesensitizedUtil.mobilePhone(request.getPhone()));
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(user.getUsername()) || user.getUsername().startsWith("wxoff_")) {
|
||||||
|
user.setUsername(request.getPhone());
|
||||||
|
}
|
||||||
|
userService.updateUser(user);
|
||||||
|
redisUtil.delete(codeKey);
|
||||||
|
|
||||||
|
String accessToken = buildAccessToken(user);
|
||||||
|
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
||||||
|
qrLoginData.setUserId(user.getUserId());
|
||||||
|
qrLoginData.setUsername(user.getUsername());
|
||||||
|
qrLoginData.setTenantId(user.getTenantId());
|
||||||
|
qrLoginData.setAccessToken(accessToken);
|
||||||
|
qrLoginData.setNeedBindPhone(false);
|
||||||
|
qrLoginData.setMessage("手机号绑定成功,正在登录");
|
||||||
|
redisUtil.set(redisKey, qrLoginData, 120L, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
return buildStatusResponse(qrLoginData, 120L);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成微信小程序码
|
* 生成微信小程序码
|
||||||
*/
|
*/
|
||||||
private String generateMiniprogramQrCode(String token, Integer tenantId) {
|
private String generateMiniprogramQrCode(String token, Integer tenantId) {
|
||||||
try {
|
try {
|
||||||
// 使用公共的 WxService 获取 AccessToken
|
|
||||||
String accessToken = wxService.getAccessToken(tenantId);
|
String accessToken = wxService.getAccessToken(tenantId);
|
||||||
if (StrUtil.isBlank(accessToken)) {
|
if (StrUtil.isBlank(accessToken)) {
|
||||||
throw new RuntimeException("获取微信AccessToken失败");
|
throw new RuntimeException("获取微信AccessToken失败");
|
||||||
@@ -270,19 +296,16 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + accessToken;
|
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + accessToken;
|
||||||
HashMap<String, Object> params = new HashMap<>();
|
HashMap<String, Object> params = new HashMap<>();
|
||||||
params.put("path", "/pages/qr-login?token=" + token);
|
params.put("path", "/pages/qr-login?token=" + token);
|
||||||
params.put("width", 430); // 二维码宽度,默认430px
|
params.put("width", 430);
|
||||||
|
|
||||||
// 调用微信API生成小程序码
|
|
||||||
byte[] qrCodeBytes = HttpRequest.post(apiUrl)
|
byte[] qrCodeBytes = HttpRequest.post(apiUrl)
|
||||||
.body(JSON.toJSONString(params))
|
.body(JSON.toJSONString(params))
|
||||||
.execute().bodyBytes();
|
.execute().bodyBytes();
|
||||||
|
|
||||||
// 保存文件
|
|
||||||
String fileName = "qr-login-" + token + ".png";
|
String fileName = "qr-login-" + token + ".png";
|
||||||
String uploadPath = getUploadPath();
|
String uploadPath = getUploadPath();
|
||||||
String filePath = uploadPath + "qrcode/" + fileName;
|
String filePath = uploadPath + "qrcode/" + fileName;
|
||||||
|
|
||||||
// 确保目录存在
|
|
||||||
File dir = new File(uploadPath + "qrcode/");
|
File dir = new File(uploadPath + "qrcode/");
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
@@ -290,18 +313,48 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
|
|
||||||
File file = FileUtil.writeBytes(qrCodeBytes, filePath);
|
File file = FileUtil.writeBytes(qrCodeBytes, filePath);
|
||||||
if (file != null && file.exists()) {
|
if (file != null && file.exists()) {
|
||||||
// 返回可访问的URL
|
|
||||||
return configProperties.getFileServer() + "/qrcode/" + fileName;
|
return configProperties.getFileServer() + "/qrcode/" + fileName;
|
||||||
} else {
|
|
||||||
throw new RuntimeException("保存小程序码文件失败");
|
|
||||||
}
|
}
|
||||||
|
throw new RuntimeException("保存小程序码文件失败");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("生成微信小程序码失败: {}", e.getMessage(), e);
|
log.error("生成微信小程序码失败: {}", e.getMessage(), e);
|
||||||
throw new RuntimeException("生成微信小程序码失败: " + e.getMessage());
|
throw new RuntimeException("生成微信小程序码失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成公众号带参数二维码
|
||||||
|
*/
|
||||||
|
private String generateOfficialQrCodeUrl(String token, Integer tenantId) {
|
||||||
|
try {
|
||||||
|
String accessToken = wxService.getOfficialAccessToken(tenantId);
|
||||||
|
JSONObject scene = new JSONObject();
|
||||||
|
scene.put("scene_str", token);
|
||||||
|
JSONObject actionInfo = new JSONObject();
|
||||||
|
actionInfo.put("scene", scene);
|
||||||
|
JSONObject params = new JSONObject();
|
||||||
|
params.put("action_name", "QR_STR_SCENE");
|
||||||
|
params.put("expire_seconds", QR_LOGIN_TOKEN_TTL.intValue());
|
||||||
|
params.put("action_info", actionInfo);
|
||||||
|
|
||||||
|
String response = HttpRequest.post("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + accessToken)
|
||||||
|
.body(params.toJSONString())
|
||||||
|
.timeout(10000)
|
||||||
|
.execute()
|
||||||
|
.body();
|
||||||
|
|
||||||
|
JSONObject result = JSON.parseObject(response);
|
||||||
|
String ticket = result.getString("ticket");
|
||||||
|
if (StrUtil.isBlank(ticket)) {
|
||||||
|
throw new RuntimeException("生成公众号二维码失败: " + response);
|
||||||
|
}
|
||||||
|
return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="
|
||||||
|
+ java.net.URLEncoder.encode(ticket, java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("生成公众号二维码失败: {}", e.getMessage(), e);
|
||||||
|
throw new RuntimeException("生成公众号二维码失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文件上传路径
|
* 获取文件上传路径
|
||||||
@@ -320,6 +373,38 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
return uploadPath;
|
return uploadPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildAccessToken(User user) {
|
||||||
|
JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId());
|
||||||
|
return JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
private QrLoginStatusResponse buildExpiredResponse() {
|
||||||
|
QrLoginStatusResponse response = new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L, null);
|
||||||
|
response.setNeedBindPhone(false);
|
||||||
|
response.setMessage("二维码已过期,请刷新后重试");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private QrLoginStatusResponse buildStatusResponse(QrLoginData qrLoginData, Long expiresIn) {
|
||||||
|
QrLoginStatusResponse response = new QrLoginStatusResponse();
|
||||||
|
response.setStatus(qrLoginData.getStatus());
|
||||||
|
response.setAccessToken(qrLoginData.getAccessToken());
|
||||||
|
response.setExpiresIn(expiresIn);
|
||||||
|
response.setTenantId(qrLoginData.getTenantId());
|
||||||
|
response.setNeedBindPhone(Boolean.TRUE.equals(qrLoginData.getNeedBindPhone())
|
||||||
|
|| QR_LOGIN_STATUS_BIND_PHONE.equals(qrLoginData.getStatus()));
|
||||||
|
response.setMessage(qrLoginData.getMessage());
|
||||||
|
|
||||||
|
if (qrLoginData.getUserId() != null) {
|
||||||
|
User user = userService.getAllByUserId(String.valueOf(qrLoginData.getUserId()));
|
||||||
|
if (user != null) {
|
||||||
|
user.setPassword(null);
|
||||||
|
response.setUserInfo(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WechatScanResponse wechatScanConfirm(WechatScanRequest request) {
|
public WechatScanResponse wechatScanConfirm(WechatScanRequest request) {
|
||||||
String token = request.getToken();
|
String token = request.getToken();
|
||||||
@@ -329,13 +414,10 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
|
|
||||||
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
||||||
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
||||||
|
|
||||||
if (qrLoginData == null) {
|
if (qrLoginData == null) {
|
||||||
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
||||||
}
|
}
|
||||||
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
||||||
// 检查是否过期
|
|
||||||
if (DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
||||||
redisUtil.delete(redisKey);
|
redisUtil.delete(redisKey);
|
||||||
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
||||||
}
|
}
|
||||||
@@ -344,7 +426,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
String openId = request.getOpenId();
|
String openId = request.getOpenId();
|
||||||
Integer tenantId = qrLoginData.getTenantId();
|
Integer tenantId = qrLoginData.getTenantId();
|
||||||
|
|
||||||
// 如果没有直接传 unionId,但有 code,需要通过 code 获取
|
|
||||||
if (StrUtil.isBlank(unionId) && StrUtil.isNotBlank(request.getCode())) {
|
if (StrUtil.isBlank(unionId) && StrUtil.isNotBlank(request.getCode())) {
|
||||||
try {
|
try {
|
||||||
JSONObject userAccessToken = wxService.getOfficialUserAccessToken(request.getCode(), tenantId);
|
JSONObject userAccessToken = wxService.getOfficialUserAccessToken(request.getCode(), tenantId);
|
||||||
@@ -362,8 +443,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
User user = null;
|
User user = null;
|
||||||
|
|
||||||
// 优先通过 unionId 查找用户
|
|
||||||
if (StrUtil.isNotBlank(unionId)) {
|
if (StrUtil.isNotBlank(unionId)) {
|
||||||
user = userService.getOne(new LambdaQueryWrapper<User>()
|
user = userService.getOne(new LambdaQueryWrapper<User>()
|
||||||
.eq(User::getUnionid, unionId)
|
.eq(User::getUnionid, unionId)
|
||||||
@@ -372,9 +451,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
log.info("通过 unionId {} 查找用户: {}", unionId, user != null ? user.getUsername() : "未找到");
|
log.info("通过 unionId {} 查找用户: {}", unionId, user != null ? user.getUsername() : "未找到");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果通过 unionId 没找到,尝试通过 openId 查找
|
|
||||||
if (user == null && StrUtil.isNotBlank(openId)) {
|
if (user == null && StrUtil.isNotBlank(openId)) {
|
||||||
// 尝试从 sys_user 表的 openid 字段查找
|
|
||||||
user = userService.getOne(new LambdaQueryWrapper<User>()
|
user = userService.getOne(new LambdaQueryWrapper<User>()
|
||||||
.eq(User::getOpenid, openId)
|
.eq(User::getOpenid, openId)
|
||||||
.eq(User::getDeleted, 0)
|
.eq(User::getDeleted, 0)
|
||||||
@@ -382,7 +459,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
log.info("通过 openId {} 查找用户: {}", openId, user != null ? user.getUsername() : "未找到");
|
log.info("通过 openId {} 查找用户: {}", openId, user != null ? user.getUsername() : "未找到");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果还没找到,尝试从 sys_user_oauth 表查找
|
|
||||||
if (user == null && (StrUtil.isNotBlank(unionId) || StrUtil.isNotBlank(openId))) {
|
if (user == null && (StrUtil.isNotBlank(unionId) || StrUtil.isNotBlank(openId))) {
|
||||||
try {
|
try {
|
||||||
LambdaQueryWrapper<UserOauth> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<UserOauth> wrapper = new LambdaQueryWrapper<>();
|
||||||
@@ -410,30 +486,21 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
if (user == null) {
|
if (user == null) {
|
||||||
return WechatScanResponse.notBound("该微信未绑定平台账号,请先在平台注册并绑定微信");
|
return WechatScanResponse.notBound("该微信未绑定平台账号,请先在平台注册并绑定微信");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查用户状态
|
|
||||||
if (user.getStatus() != null && user.getStatus() != 0) {
|
if (user.getStatus() != null && user.getStatus() != 0) {
|
||||||
return WechatScanResponse.notBound("账号已被冻结");
|
return WechatScanResponse.notBound("账号已被冻结");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 JWT token
|
String accessToken = buildAccessToken(user);
|
||||||
JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId());
|
|
||||||
String accessToken = JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
|
||||||
|
|
||||||
// 更新扫码登录数据
|
|
||||||
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
||||||
qrLoginData.setUserId(user.getUserId());
|
qrLoginData.setUserId(user.getUserId());
|
||||||
qrLoginData.setUsername(user.getUsername());
|
qrLoginData.setUsername(user.getUsername());
|
||||||
qrLoginData.setAccessToken(accessToken);
|
qrLoginData.setAccessToken(accessToken);
|
||||||
qrLoginData.setTenantId(user.getTenantId());
|
qrLoginData.setTenantId(user.getTenantId());
|
||||||
// 更新Redis中的数据
|
qrLoginData.setNeedBindPhone(false);
|
||||||
redisUtil.set(redisKey, qrLoginData, 60L, TimeUnit.SECONDS);
|
qrLoginData.setMessage("登录成功");
|
||||||
|
redisUtil.set(redisKey, qrLoginData, 120L, TimeUnit.SECONDS);
|
||||||
|
|
||||||
log.info("微信扫码登录成功,用户 {} 确认扫码登录,token: {}", user.getUsername(), token);
|
|
||||||
|
|
||||||
// 清除敏感信息
|
|
||||||
user.setPassword(null);
|
user.setPassword(null);
|
||||||
|
|
||||||
return WechatScanResponse.success(accessToken, user, user.getTenantId());
|
return WechatScanResponse.success(accessToken, user, user.getTenantId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public class RedisConstants {
|
|||||||
public static final String QR_LOGIN_STATUS_PENDING = "pending"; // 等待扫码
|
public static final String QR_LOGIN_STATUS_PENDING = "pending"; // 等待扫码
|
||||||
public static final String QR_LOGIN_STATUS_SCANNED = "scanned"; // 已扫码
|
public static final String QR_LOGIN_STATUS_SCANNED = "scanned"; // 已扫码
|
||||||
public static final String QR_LOGIN_STATUS_CONFIRMED = "confirmed"; // 已确认
|
public static final String QR_LOGIN_STATUS_CONFIRMED = "confirmed"; // 已确认
|
||||||
|
public static final String QR_LOGIN_STATUS_BIND_PHONE = "bind_phone"; // 待绑定手机号
|
||||||
public static final String QR_LOGIN_STATUS_EXPIRED = "expired"; // 已过期
|
public static final String QR_LOGIN_STATUS_EXPIRED = "expired"; // 已过期
|
||||||
|
|
||||||
// 哗啦啦key
|
// 哗啦啦key
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import com.alibaba.fastjson.JSONObject;
|
|||||||
import com.alipay.api.internal.util.file.IOUtils;
|
import com.alipay.api.internal.util.file.IOUtils;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.qq.weixin.mp.aes.WXBizJsonMsgCrypt;
|
import com.qq.weixin.mp.aes.WXBizJsonMsgCrypt;
|
||||||
|
import com.gxwebsoft.auto.dto.QrLoginData;
|
||||||
|
import com.gxwebsoft.common.core.config.ConfigProperties;
|
||||||
|
import com.gxwebsoft.common.core.security.JwtSubject;
|
||||||
|
import com.gxwebsoft.common.core.security.JwtUtil;
|
||||||
import com.gxwebsoft.common.core.utils.CommonUtil;
|
import com.gxwebsoft.common.core.utils.CommonUtil;
|
||||||
import com.gxwebsoft.common.core.utils.JSONUtil;
|
import com.gxwebsoft.common.core.utils.JSONUtil;
|
||||||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||||
|
|||||||
Reference in New Issue
Block a user