Browse Source
- 新增扫码登录接口和相关服务 - 实现微信小程序端扫码登录逻辑 - 更新文档,添加微信小程序扫码登录指南 - 调整微信登录相关接口,使用 release 版本 - 新增 JWT 配置项pan
12 changed files with 784 additions and 2 deletions
@ -0,0 +1,213 @@ |
|||
# 微信小程序扫码登录使用指南 |
|||
|
|||
## 概述 |
|||
|
|||
扫码登录接口现已全面支持微信小程序端,用户可以通过微信小程序扫码快速登录网页端或其他平台。 |
|||
|
|||
## 支持的平台 |
|||
|
|||
- ✅ **网页端** - 传统的网页扫码登录 |
|||
- ✅ **移动APP** - 原生移动应用扫码登录 |
|||
- ✅ **微信小程序** - 微信小程序扫码登录(新增) |
|||
|
|||
## 接口说明 |
|||
|
|||
### 1. 生成扫码登录token |
|||
``` |
|||
POST /api/qr-login/generate |
|||
``` |
|||
|
|||
**响应示例:** |
|||
```json |
|||
{ |
|||
"code": 0, |
|||
"message": "生成成功", |
|||
"data": { |
|||
"token": "abc123def456", |
|||
"qrCode": "qr-login:abc123def456", |
|||
"expiresIn": 300 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 2. 检查扫码登录状态 |
|||
``` |
|||
GET /api/qr-login/status/{token} |
|||
``` |
|||
|
|||
**响应示例:** |
|||
```json |
|||
{ |
|||
"code": 0, |
|||
"message": "查询成功", |
|||
"data": { |
|||
"status": "confirmed", |
|||
"accessToken": "eyJhbGciOiJIUzI1NiJ9...", |
|||
"userInfo": { |
|||
"userId": 123, |
|||
"username": "user123", |
|||
"nickname": "张三" |
|||
}, |
|||
"expiresIn": 60 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 3. 微信小程序确认登录(专用接口) |
|||
``` |
|||
POST /api/qr-login/wechat-confirm |
|||
``` |
|||
|
|||
**请求示例:** |
|||
```json |
|||
{ |
|||
"token": "abc123def456", |
|||
"userId": 123, |
|||
"platform": "miniprogram", |
|||
"wechatInfo": { |
|||
"openid": "oABC123DEF456", |
|||
"unionid": "uXYZ789ABC123", |
|||
"nickname": "张三", |
|||
"avatar": "https://wx.qlogo.cn/..." |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 微信小程序端实现示例 |
|||
|
|||
### 1. 扫码功能 |
|||
```javascript |
|||
// 小程序扫码 |
|||
wx.scanCode({ |
|||
success: (res) => { |
|||
const qrContent = res.result; // 例如: "qr-login:abc123def456" |
|||
if (qrContent.startsWith('qr-login:')) { |
|||
const token = qrContent.replace('qr-login:', ''); |
|||
this.confirmLogin(token); |
|||
} |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
### 2. 确认登录 |
|||
```javascript |
|||
confirmLogin(token) { |
|||
// 获取用户信息 |
|||
wx.getUserProfile({ |
|||
desc: '用于扫码登录', |
|||
success: (userRes) => { |
|||
// 调用确认登录接口 |
|||
wx.request({ |
|||
url: 'https://your-api.com/api/qr-login/wechat-confirm', |
|||
method: 'POST', |
|||
data: { |
|||
token: token, |
|||
userId: this.data.currentUserId, // 当前登录用户ID |
|||
platform: 'miniprogram', |
|||
wechatInfo: { |
|||
openid: this.data.openid, |
|||
unionid: this.data.unionid, |
|||
nickname: userRes.userInfo.nickName, |
|||
avatar: userRes.userInfo.avatarUrl |
|||
} |
|||
}, |
|||
success: (res) => { |
|||
if (res.data.code === 0) { |
|||
wx.showToast({ |
|||
title: '登录确认成功', |
|||
icon: 'success' |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
## 网页端轮询状态示例 |
|||
|
|||
```javascript |
|||
// 网页端轮询检查登录状态 |
|||
function checkLoginStatus(token) { |
|||
const interval = setInterval(() => { |
|||
fetch(`/api/qr-login/status/${token}`) |
|||
.then(res => res.json()) |
|||
.then(data => { |
|||
if (data.code === 0) { |
|||
const status = data.data.status; |
|||
|
|||
switch(status) { |
|||
case 'pending': |
|||
console.log('等待扫码...'); |
|||
break; |
|||
case 'scanned': |
|||
console.log('已扫码,等待确认...'); |
|||
break; |
|||
case 'confirmed': |
|||
console.log('登录成功!'); |
|||
localStorage.setItem('token', data.data.accessToken); |
|||
clearInterval(interval); |
|||
// 跳转到主页 |
|||
window.location.href = '/dashboard'; |
|||
break; |
|||
case 'expired': |
|||
console.log('二维码已过期'); |
|||
clearInterval(interval); |
|||
// 重新生成二维码 |
|||
generateNewQrCode(); |
|||
break; |
|||
} |
|||
} |
|||
}); |
|||
}, 2000); // 每2秒检查一次 |
|||
} |
|||
``` |
|||
|
|||
## 状态流转 |
|||
|
|||
``` |
|||
pending (等待扫码) |
|||
↓ |
|||
scanned (已扫码) |
|||
↓ |
|||
confirmed (已确认) → 返回JWT token |
|||
↓ |
|||
expired (已过期) |
|||
``` |
|||
|
|||
## 特殊功能 |
|||
|
|||
### 1. 微信信息自动更新 |
|||
当微信小程序用户确认登录时,系统会自动更新用户的微信相关信息: |
|||
- openid |
|||
- unionid |
|||
- 昵称(如果用户昵称为空) |
|||
- 头像(如果用户头像为空) |
|||
|
|||
### 2. 平台识别 |
|||
系统会记录用户通过哪个平台进行的扫码登录,便于后续分析和统计。 |
|||
|
|||
### 3. 安全特性 |
|||
- Token有效期5分钟 |
|||
- 确认后Token立即失效,防止重复使用 |
|||
- 支持过期自动清理 |
|||
- JWT token有效期24小时 |
|||
|
|||
## 注意事项 |
|||
|
|||
1. **微信小程序需要配置扫码权限** |
|||
2. **确保用户已在小程序中登录** |
|||
3. **处理用户拒绝授权的情况** |
|||
4. **网页端需要定期轮询状态** |
|||
5. **处理网络异常和超时情况** |
|||
|
|||
## 错误处理 |
|||
|
|||
常见错误码: |
|||
- `token不能为空` - 请求参数缺失 |
|||
- `扫码登录token不存在或已过期` - Token无效 |
|||
- `用户不存在` - 用户ID无效 |
|||
- `用户已被冻结` - 用户状态异常 |
|||
|
|||
建议在小程序端添加适当的错误提示和重试机制。 |
@ -0,0 +1,104 @@ |
|||
package com.gxwebsoft.auto.controller; |
|||
|
|||
import com.gxwebsoft.auto.dto.QrLoginConfirmRequest; |
|||
import com.gxwebsoft.auto.dto.QrLoginGenerateResponse; |
|||
import com.gxwebsoft.auto.dto.QrLoginStatusResponse; |
|||
import com.gxwebsoft.auto.service.QrLoginService; |
|||
import com.gxwebsoft.common.core.web.BaseController; |
|||
import com.gxwebsoft.common.core.web.ApiResult; |
|||
import io.swagger.v3.oas.annotations.Operation; |
|||
import io.swagger.v3.oas.annotations.Parameter; |
|||
import io.swagger.v3.oas.annotations.tags.Tag; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
import javax.validation.Valid; |
|||
|
|||
/** |
|||
* 认证模块 |
|||
* |
|||
* @author 科技小王子 |
|||
* @since 2025-03-06 22:50:25 |
|||
*/ |
|||
@Tag(name = "认证模块") |
|||
@RestController |
|||
@RequestMapping("/api/qr-login") |
|||
public class QrLoginController extends BaseController { |
|||
|
|||
@Autowired |
|||
private QrLoginService qrLoginService; |
|||
|
|||
/** |
|||
* 生成扫码登录token |
|||
*/ |
|||
@Operation(summary = "生成扫码登录token") |
|||
@PostMapping("/generate") |
|||
public ApiResult<?> generateQrLoginToken() { |
|||
try { |
|||
QrLoginGenerateResponse response = qrLoginService.generateQrLoginToken(); |
|||
return success("生成成功", response); |
|||
} catch (Exception e) { |
|||
return fail(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查扫码登录状态 |
|||
*/ |
|||
@Operation(summary = "检查扫码登录状态") |
|||
@GetMapping("/status/{token}") |
|||
public ApiResult<?> checkQrLoginStatus( |
|||
@Parameter(description = "扫码登录token") @PathVariable String token) { |
|||
try { |
|||
QrLoginStatusResponse response = qrLoginService.checkQrLoginStatus(token); |
|||
return success("查询成功", response); |
|||
} catch (Exception e) { |
|||
return fail(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 确认扫码登录 |
|||
*/ |
|||
@Operation(summary = "确认扫码登录") |
|||
@PostMapping("/confirm") |
|||
public ApiResult<?> confirmQrLogin(@Valid @RequestBody QrLoginConfirmRequest request) { |
|||
try { |
|||
QrLoginStatusResponse response = qrLoginService.confirmQrLogin(request); |
|||
return success("确认成功", response); |
|||
} catch (Exception e) { |
|||
return fail(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 扫码操作(可选接口,用于移动端扫码后更新状态) |
|||
*/ |
|||
@Operation(summary = "扫码操作") |
|||
@PostMapping("/scan/{token}") |
|||
public ApiResult<?> scanQrCode(@Parameter(description = "扫码登录token") @PathVariable String token) { |
|||
try { |
|||
boolean result = qrLoginService.scanQrCode(token); |
|||
return success("操作成功", result); |
|||
} catch (Exception e) { |
|||
return fail(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 微信小程序扫码登录确认(便捷接口) |
|||
*/ |
|||
@Operation(summary = "微信小程序扫码登录确认") |
|||
@PostMapping("/wechat-confirm") |
|||
public ApiResult<?> wechatMiniProgramConfirm(@Valid @RequestBody QrLoginConfirmRequest request) { |
|||
try { |
|||
// 设置平台为微信小程序
|
|||
request.setPlatform("miniprogram"); |
|||
QrLoginStatusResponse response = qrLoginService.confirmQrLogin(request); |
|||
return success("微信小程序登录确认成功", response); |
|||
} catch (Exception e) { |
|||
return fail(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,50 @@ |
|||
package com.gxwebsoft.auto.dto; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Data; |
|||
|
|||
import javax.validation.constraints.NotBlank; |
|||
|
|||
/** |
|||
* 扫码登录确认请求 |
|||
* |
|||
* @author 科技小王子 |
|||
* @since 2025-08-31 |
|||
*/ |
|||
@Data |
|||
@Schema(description = "扫码登录确认请求") |
|||
public class QrLoginConfirmRequest { |
|||
|
|||
@Schema(description = "扫码登录token") |
|||
@NotBlank(message = "token不能为空") |
|||
private String token; |
|||
|
|||
@Schema(description = "用户ID") |
|||
private Integer userId; |
|||
|
|||
@Schema(description = "登录平台: web-网页端, app-移动应用, miniprogram-微信小程序") |
|||
private String platform; |
|||
|
|||
@Schema(description = "微信小程序相关信息") |
|||
private WechatMiniProgramInfo wechatInfo; |
|||
|
|||
/** |
|||
* 微信小程序信息 |
|||
*/ |
|||
@Data |
|||
@Schema(description = "微信小程序信息") |
|||
public static class WechatMiniProgramInfo { |
|||
@Schema(description = "微信openid") |
|||
private String openid; |
|||
|
|||
@Schema(description = "微信unionid") |
|||
private String unionid; |
|||
|
|||
@Schema(description = "微信昵称") |
|||
private String nickname; |
|||
|
|||
@Schema(description = "微信头像") |
|||
private String avatar; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,55 @@ |
|||
package com.gxwebsoft.auto.dto; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
import java.time.LocalDateTime; |
|||
|
|||
/** |
|||
* 扫码登录数据模型 |
|||
* |
|||
* @author 科技小王子 |
|||
* @since 2025-08-31 |
|||
*/ |
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class QrLoginData { |
|||
|
|||
/** |
|||
* 扫码登录token |
|||
*/ |
|||
private String token; |
|||
|
|||
/** |
|||
* 状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期 |
|||
*/ |
|||
private String status; |
|||
|
|||
/** |
|||
* 用户ID(扫码确认后设置) |
|||
*/ |
|||
private Integer userId; |
|||
|
|||
/** |
|||
* 用户名(扫码确认后设置) |
|||
*/ |
|||
private String username; |
|||
|
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
private LocalDateTime createTime; |
|||
|
|||
/** |
|||
* 过期时间 |
|||
*/ |
|||
private LocalDateTime expireTime; |
|||
|
|||
/** |
|||
* JWT访问令牌(确认后生成) |
|||
*/ |
|||
private String accessToken; |
|||
|
|||
} |
@ -0,0 +1,29 @@ |
|||
package com.gxwebsoft.auto.dto; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
/** |
|||
* 扫码登录生成响应 |
|||
* |
|||
* @author 科技小王子 |
|||
* @since 2025-08-31 |
|||
*/ |
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@Schema(description = "扫码登录生成响应") |
|||
public class QrLoginGenerateResponse { |
|||
|
|||
@Schema(description = "扫码登录token") |
|||
private String token; |
|||
|
|||
@Schema(description = "二维码内容") |
|||
private String qrCode; |
|||
|
|||
@Schema(description = "过期时间(秒)") |
|||
private Long expiresIn; |
|||
|
|||
} |
@ -0,0 +1,32 @@ |
|||
package com.gxwebsoft.auto.dto; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
/** |
|||
* 扫码登录状态响应 |
|||
* |
|||
* @author 科技小王子 |
|||
* @since 2025-08-31 |
|||
*/ |
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@Schema(description = "扫码登录状态响应") |
|||
public class QrLoginStatusResponse { |
|||
|
|||
@Schema(description = "状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期") |
|||
private String status; |
|||
|
|||
@Schema(description = "JWT访问令牌(仅在confirmed状态时返回)") |
|||
private String accessToken; |
|||
|
|||
@Schema(description = "用户信息(仅在confirmed状态时返回)") |
|||
private Object userInfo; |
|||
|
|||
@Schema(description = "剩余过期时间(秒)") |
|||
private Long expiresIn; |
|||
|
|||
} |
@ -0,0 +1,46 @@ |
|||
package com.gxwebsoft.auto.service; |
|||
|
|||
import com.gxwebsoft.auto.dto.QrLoginConfirmRequest; |
|||
import com.gxwebsoft.auto.dto.QrLoginGenerateResponse; |
|||
import com.gxwebsoft.auto.dto.QrLoginStatusResponse; |
|||
|
|||
/** |
|||
* 扫码登录服务接口 |
|||
* |
|||
* @author 科技小王子 |
|||
* @since 2025-08-31 |
|||
*/ |
|||
public interface QrLoginService { |
|||
|
|||
/** |
|||
* 生成扫码登录token |
|||
* |
|||
* @return QrLoginGenerateResponse |
|||
*/ |
|||
QrLoginGenerateResponse generateQrLoginToken(); |
|||
|
|||
/** |
|||
* 检查扫码登录状态 |
|||
* |
|||
* @param token 扫码登录token |
|||
* @return QrLoginStatusResponse |
|||
*/ |
|||
QrLoginStatusResponse checkQrLoginStatus(String token); |
|||
|
|||
/** |
|||
* 确认扫码登录 |
|||
* |
|||
* @param request 确认请求 |
|||
* @return QrLoginStatusResponse |
|||
*/ |
|||
QrLoginStatusResponse confirmQrLogin(QrLoginConfirmRequest request); |
|||
|
|||
/** |
|||
* 扫码操作(更新状态为已扫码) |
|||
* |
|||
* @param token 扫码登录token |
|||
* @return boolean |
|||
*/ |
|||
boolean scanQrCode(String token); |
|||
|
|||
} |
@ -0,0 +1,239 @@ |
|||
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; |
|||
} |
|||
} |
Loading…
Reference in new issue