|
|
|
@@ -3,6 +3,7 @@ package com.gxwebsoft.auto.service.impl;
|
|
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
|
|
import cn.hutool.core.io.FileUtil;
|
|
|
|
|
import cn.hutool.core.lang.UUID;
|
|
|
|
|
import cn.hutool.core.util.DesensitizedUtil;
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
|
import cn.hutool.http.HttpRequest;
|
|
|
|
|
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.security.JwtSubject;
|
|
|
|
|
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.system.entity.User;
|
|
|
|
|
import com.gxwebsoft.common.system.entity.UserOauth;
|
|
|
|
@@ -27,8 +29,9 @@ import java.io.File;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
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.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)
|
|
|
|
|
private UserOauthService userOauthService;
|
|
|
|
|
|
|
|
|
|
private static final String QR_LOGIN_TOKEN = "QR_LOGIN_TOKEN";
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public QrLoginGenerateResponse generateQrLoginToken(Integer tenantId) {
|
|
|
|
|
// 生成唯一的扫码登录token
|
|
|
|
|
String token = UUID.randomUUID().toString(true);
|
|
|
|
|
|
|
|
|
|
// 创建扫码登录数据
|
|
|
|
|
QrLoginData qrLoginData = new QrLoginData();
|
|
|
|
|
qrLoginData.setToken(token);
|
|
|
|
|
qrLoginData.setStatus(QR_LOGIN_STATUS_PENDING);
|
|
|
|
|
qrLoginData.setTenantId(tenantId);
|
|
|
|
|
qrLoginData.setNeedBindPhone(false);
|
|
|
|
|
qrLoginData.setMessage("等待微信扫码");
|
|
|
|
|
qrLoginData.setCreateTime(DateUtil.formatDateTime(DateUtil.date()));
|
|
|
|
|
qrLoginData.setExpireTime(DateUtil.formatDateTime(DateUtil.offsetSecond(DateUtil.date(), QR_LOGIN_TOKEN_TTL.intValue())));
|
|
|
|
|
|
|
|
|
|
// 存储到Redis,设置过期时间
|
|
|
|
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
|
|
|
|
redisUtil.set(redisKey, qrLoginData, QR_LOGIN_TOKEN_TTL, TimeUnit.SECONDS);
|
|
|
|
|
|
|
|
|
|
log.info("生成扫码登录token: {}", token);
|
|
|
|
|
|
|
|
|
|
// 构造响应对象
|
|
|
|
|
QrLoginGenerateResponse response = new QrLoginGenerateResponse();
|
|
|
|
|
response.setToken(token);
|
|
|
|
|
response.setExpiresIn(QR_LOGIN_TOKEN_TTL);
|
|
|
|
|
|
|
|
|
|
// APP扫码内容
|
|
|
|
|
response.setQrCodeContent("qr-login:" + token);
|
|
|
|
|
|
|
|
|
|
// 微信小程序路径
|
|
|
|
|
response.setMiniprogramPath("/pages/qr-login?token=" + token);
|
|
|
|
|
|
|
|
|
|
// 生成微信小程序码
|
|
|
|
|
try {
|
|
|
|
|
String miniprogramQrCodeUrl = generateMiniprogramQrCode(token,tenantId);
|
|
|
|
|
String miniprogramQrCodeUrl = generateMiniprogramQrCode(token, tenantId);
|
|
|
|
|
response.setMiniprogramQrCodeUrl(miniprogramQrCodeUrl);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.warn("生成微信小程序码失败: {}", e.getMessage());
|
|
|
|
|
// 小程序码生成失败不影响整体功能,继续返回其他信息
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成微信扫码登录 H5 页面 URL
|
|
|
|
|
try {
|
|
|
|
|
String appId = wxService.getOfficialAppId(tenantId);
|
|
|
|
|
// 优先使用专门的微信扫码配置,否则使用文件服务器地址
|
|
|
|
|
String baseUrl = configProperties.getWechatScanUrl();
|
|
|
|
|
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;
|
|
|
|
|
response.setWechatScanUrl(wechatScanUrl);
|
|
|
|
|
response.setWechatAppId(appId);
|
|
|
|
|
log.info("生成微信扫码登录URL: {}", wechatScanUrl);
|
|
|
|
|
response.setWechatQrCodeUrl(generateOfficialQrCodeUrl(token, tenantId));
|
|
|
|
|
log.info("生成公众号扫码登录URL: {}", wechatScanUrl);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.warn("生成微信扫码URL失败: {}", e.getMessage());
|
|
|
|
|
// 不影响整体功能
|
|
|
|
|
log.warn("生成公众号扫码URL失败: {}", e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
@@ -123,49 +110,34 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
@Override
|
|
|
|
|
public QrLoginStatusResponse checkQrLoginStatus(String 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;
|
|
|
|
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
|
|
|
|
|
|
|
|
|
if (qrLoginData == null) {
|
|
|
|
|
return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L, null);
|
|
|
|
|
return buildExpiredResponse();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否过期
|
|
|
|
|
if (DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
|
|
|
// 删除过期的token
|
|
|
|
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
|
|
|
redisUtil.delete(redisKey);
|
|
|
|
|
return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L, null);
|
|
|
|
|
return buildExpiredResponse();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算剩余过期时间
|
|
|
|
|
long expiresIn = (DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000;
|
|
|
|
|
long expiresIn = Math.max(0L,
|
|
|
|
|
(DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000);
|
|
|
|
|
|
|
|
|
|
QrLoginStatusResponse response = new QrLoginStatusResponse();
|
|
|
|
|
response.setStatus(qrLoginData.getStatus());
|
|
|
|
|
response.setExpiresIn(expiresIn);
|
|
|
|
|
response.setTenantId(qrLoginData.getTenantId());
|
|
|
|
|
|
|
|
|
|
// 如果已确认,返回token和用户信息
|
|
|
|
|
if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())) {
|
|
|
|
|
response.setAccessToken(qrLoginData.getAccessToken());
|
|
|
|
|
// 获取用户信息
|
|
|
|
|
if (qrLoginData.getUserId() != null) {
|
|
|
|
|
User user = userService.getAllByUserId("" + qrLoginData.getUserId());
|
|
|
|
|
if (user != null) {
|
|
|
|
|
// 清除敏感信息
|
|
|
|
|
user.setPassword(null);
|
|
|
|
|
response.setUserInfo(user);
|
|
|
|
|
}
|
|
|
|
|
if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())
|
|
|
|
|
&& StrUtil.isBlank(qrLoginData.getAccessToken())
|
|
|
|
|
&& qrLoginData.getUserId() != null) {
|
|
|
|
|
User user = userService.getAllByUserId(String.valueOf(qrLoginData.getUserId()));
|
|
|
|
|
if (user != null) {
|
|
|
|
|
qrLoginData.setAccessToken(buildAccessToken(user));
|
|
|
|
|
redisUtil.set(redisKey, qrLoginData, Math.max(expiresIn, 120L), TimeUnit.SECONDS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确认后删除token,防止重复使用
|
|
|
|
|
// redisUtil.delete(redisKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
return buildStatusResponse(qrLoginData, expiresIn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@@ -179,47 +151,35 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
|
|
|
|
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
|
|
|
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
|
|
|
|
|
|
|
|
|
if (qrLoginData == null) {
|
|
|
|
|
throw new RuntimeException("扫码登录token不存在或已过期");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否过期
|
|
|
|
|
if (DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
|
|
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
|
|
|
redisUtil.delete(redisKey);
|
|
|
|
|
throw new RuntimeException("扫码登录token已过期");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取用户信息
|
|
|
|
|
User user = userService.getAllByUserId(String.valueOf(userId));
|
|
|
|
|
if (user == null) {
|
|
|
|
|
throw new RuntimeException("用户不存在");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查用户状态
|
|
|
|
|
if (user.getStatus() != null && user.getStatus() != 0) {
|
|
|
|
|
throw new RuntimeException("用户已被冻结");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成JWT token
|
|
|
|
|
JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId());
|
|
|
|
|
String accessToken = JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
|
|
|
|
|
|
|
|
|
// 更新扫码登录数据
|
|
|
|
|
String accessToken = buildAccessToken(user);
|
|
|
|
|
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
|
|
|
|
qrLoginData.setUserId(userId);
|
|
|
|
|
qrLoginData.setUsername(user.getUsername());
|
|
|
|
|
qrLoginData.setAccessToken(accessToken);
|
|
|
|
|
qrLoginData.setTenantId(user.getTenantId());
|
|
|
|
|
// 更新Redis中的数据
|
|
|
|
|
redisUtil.set(redisKey, qrLoginData, 60L, TimeUnit.SECONDS); // 给前端60秒时间获取token
|
|
|
|
|
qrLoginData.setNeedBindPhone(false);
|
|
|
|
|
qrLoginData.setMessage("登录成功");
|
|
|
|
|
redisUtil.set(redisKey, qrLoginData, 120L, TimeUnit.SECONDS);
|
|
|
|
|
|
|
|
|
|
log.info("用户 {} 确认扫码登录,token: {}", user.getUsername(), token);
|
|
|
|
|
|
|
|
|
|
// 清除敏感信息
|
|
|
|
|
user.setPassword(null);
|
|
|
|
|
|
|
|
|
|
return new QrLoginStatusResponse(QR_LOGIN_STATUS_CONFIRMED, accessToken, user, 60L, user.getTenantId());
|
|
|
|
|
return buildStatusResponse(qrLoginData, 120L);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@@ -230,25 +190,21 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
|
|
|
|
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
|
|
|
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
|
|
|
|
|
|
|
|
|
if (qrLoginData == null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否过期
|
|
|
|
|
if (DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
|
|
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(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 = (DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000;
|
|
|
|
|
qrLoginData.setMessage("已识别扫码,等待公众号回调");
|
|
|
|
|
long remainingSeconds = Math.max(1L,
|
|
|
|
|
(DateUtil.parseDateTime(qrLoginData.getExpireTime()).getTime() - DateUtil.date().getTime()) / 1000);
|
|
|
|
|
redisUtil.set(redisKey, qrLoginData, remainingSeconds, TimeUnit.SECONDS);
|
|
|
|
|
|
|
|
|
|
log.info("扫码登录token {} 状态更新为已扫码", token);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
@@ -256,12 +212,82 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
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) {
|
|
|
|
|
try {
|
|
|
|
|
// 使用公共的 WxService 获取 AccessToken
|
|
|
|
|
String accessToken = wxService.getAccessToken(tenantId);
|
|
|
|
|
if (StrUtil.isBlank(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;
|
|
|
|
|
HashMap<String, Object> params = new HashMap<>();
|
|
|
|
|
params.put("path", "/pages/qr-login?token=" + token);
|
|
|
|
|
params.put("width", 430); // 二维码宽度,默认430px
|
|
|
|
|
params.put("width", 430);
|
|
|
|
|
|
|
|
|
|
// 调用微信API生成小程序码
|
|
|
|
|
byte[] qrCodeBytes = HttpRequest.post(apiUrl)
|
|
|
|
|
.body(JSON.toJSONString(params))
|
|
|
|
|
.execute().bodyBytes();
|
|
|
|
|
.body(JSON.toJSONString(params))
|
|
|
|
|
.execute().bodyBytes();
|
|
|
|
|
|
|
|
|
|
// 保存文件
|
|
|
|
|
String fileName = "qr-login-" + token + ".png";
|
|
|
|
|
String uploadPath = getUploadPath();
|
|
|
|
|
String filePath = uploadPath + "qrcode/" + fileName;
|
|
|
|
|
|
|
|
|
|
// 确保目录存在
|
|
|
|
|
File dir = new File(uploadPath + "qrcode/");
|
|
|
|
|
if (!dir.exists()) {
|
|
|
|
|
dir.mkdirs();
|
|
|
|
@@ -290,18 +313,48 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
|
|
|
|
|
File file = FileUtil.writeBytes(qrCodeBytes, filePath);
|
|
|
|
|
if (file != null && file.exists()) {
|
|
|
|
|
// 返回可访问的URL
|
|
|
|
|
return configProperties.getFileServer() + "/qrcode/" + fileName;
|
|
|
|
|
} else {
|
|
|
|
|
throw new RuntimeException("保存小程序码文件失败");
|
|
|
|
|
}
|
|
|
|
|
throw new RuntimeException("保存小程序码文件失败");
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("生成微信小程序码失败: {}", e.getMessage(), e);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
public WechatScanResponse wechatScanConfirm(WechatScanRequest request) {
|
|
|
|
|
String token = request.getToken();
|
|
|
|
@@ -329,13 +414,10 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
|
|
|
|
|
String redisKey = QR_LOGIN_TOKEN_KEY + token;
|
|
|
|
|
QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
|
|
|
|
|
|
|
|
|
|
if (qrLoginData == null) {
|
|
|
|
|
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否过期
|
|
|
|
|
if (DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
|
|
|
if (StrUtil.isBlank(qrLoginData.getExpireTime()) || DateUtil.date().after(DateUtil.parseDateTime(qrLoginData.getExpireTime()))) {
|
|
|
|
|
redisUtil.delete(redisKey);
|
|
|
|
|
return WechatScanResponse.notBound("二维码已过期,请刷新重试");
|
|
|
|
|
}
|
|
|
|
@@ -344,7 +426,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
String openId = request.getOpenId();
|
|
|
|
|
Integer tenantId = qrLoginData.getTenantId();
|
|
|
|
|
|
|
|
|
|
// 如果没有直接传 unionId,但有 code,需要通过 code 获取
|
|
|
|
|
if (StrUtil.isBlank(unionId) && StrUtil.isNotBlank(request.getCode())) {
|
|
|
|
|
try {
|
|
|
|
|
JSONObject userAccessToken = wxService.getOfficialUserAccessToken(request.getCode(), tenantId);
|
|
|
|
@@ -362,8 +443,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
User user = null;
|
|
|
|
|
|
|
|
|
|
// 优先通过 unionId 查找用户
|
|
|
|
|
if (StrUtil.isNotBlank(unionId)) {
|
|
|
|
|
user = userService.getOne(new LambdaQueryWrapper<User>()
|
|
|
|
|
.eq(User::getUnionid, unionId)
|
|
|
|
@@ -372,9 +451,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
log.info("通过 unionId {} 查找用户: {}", unionId, user != null ? user.getUsername() : "未找到");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果通过 unionId 没找到,尝试通过 openId 查找
|
|
|
|
|
if (user == null && StrUtil.isNotBlank(openId)) {
|
|
|
|
|
// 尝试从 sys_user 表的 openid 字段查找
|
|
|
|
|
user = userService.getOne(new LambdaQueryWrapper<User>()
|
|
|
|
|
.eq(User::getOpenid, openId)
|
|
|
|
|
.eq(User::getDeleted, 0)
|
|
|
|
@@ -382,7 +459,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
log.info("通过 openId {} 查找用户: {}", openId, user != null ? user.getUsername() : "未找到");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果还没找到,尝试从 sys_user_oauth 表查找
|
|
|
|
|
if (user == null && (StrUtil.isNotBlank(unionId) || StrUtil.isNotBlank(openId))) {
|
|
|
|
|
try {
|
|
|
|
|
LambdaQueryWrapper<UserOauth> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
@@ -410,30 +486,21 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|
|
|
|
if (user == null) {
|
|
|
|
|
return WechatScanResponse.notBound("该微信未绑定平台账号,请先在平台注册并绑定微信");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查用户状态
|
|
|
|
|
if (user.getStatus() != null && user.getStatus() != 0) {
|
|
|
|
|
return WechatScanResponse.notBound("账号已被冻结");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成 JWT token
|
|
|
|
|
JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId());
|
|
|
|
|
String accessToken = JwtUtil.buildToken(jwtSubject, configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
|
|
|
|
|
|
|
|
|
// 更新扫码登录数据
|
|
|
|
|
String accessToken = buildAccessToken(user);
|
|
|
|
|
qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
|
|
|
|
|
qrLoginData.setUserId(user.getUserId());
|
|
|
|
|
qrLoginData.setUsername(user.getUsername());
|
|
|
|
|
qrLoginData.setAccessToken(accessToken);
|
|
|
|
|
qrLoginData.setTenantId(user.getTenantId());
|
|
|
|
|
// 更新Redis中的数据
|
|
|
|
|
redisUtil.set(redisKey, qrLoginData, 60L, TimeUnit.SECONDS);
|
|
|
|
|
qrLoginData.setNeedBindPhone(false);
|
|
|
|
|
qrLoginData.setMessage("登录成功");
|
|
|
|
|
redisUtil.set(redisKey, qrLoginData, 120L, TimeUnit.SECONDS);
|
|
|
|
|
|
|
|
|
|
log.info("微信扫码登录成功,用户 {} 确认扫码登录,token: {}", user.getUsername(), token);
|
|
|
|
|
|
|
|
|
|
// 清除敏感信息
|
|
|
|
|
user.setPassword(null);
|
|
|
|
|
|
|
|
|
|
return WechatScanResponse.success(accessToken, user, user.getTenantId());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|