diff --git a/docs/WECHAT_MINIPROGRAM_QR_LOGIN_GUIDE.md b/docs/WECHAT_MINIPROGRAM_QR_LOGIN_GUIDE.md new file mode 100644 index 0000000..d1e3a64 --- /dev/null +++ b/docs/WECHAT_MINIPROGRAM_QR_LOGIN_GUIDE.md @@ -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无效 +- `用户已被冻结` - 用户状态异常 + +建议在小程序端添加适当的错误提示和重试机制。 diff --git a/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java b/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java new file mode 100644 index 0000000..d296d82 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java @@ -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()); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java new file mode 100644 index 0000000..f3b423e --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java @@ -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; + } + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java new file mode 100644 index 0000000..563bf1d --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java @@ -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; + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java new file mode 100644 index 0000000..f0b69e5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java @@ -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; + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java new file mode 100644 index 0000000..1eb0d4a --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java @@ -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; + +} diff --git a/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java b/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java new file mode 100644 index 0000000..85ed28f --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java @@ -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); + +} diff --git a/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java new file mode 100644 index 0000000..34658e8 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java @@ -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; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java index 2765754..68d8fef 100644 --- a/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java +++ b/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java @@ -27,6 +27,14 @@ public class RedisConstants { + // 扫码登录相关key + public static final String QR_LOGIN_TOKEN_KEY = "qr-login:token:"; // 扫码登录token前缀 + public static final Long QR_LOGIN_TOKEN_TTL = 300L; // 扫码登录token过期时间(5分钟) + 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_CONFIRMED = "confirmed"; // 已确认 + public static final String QR_LOGIN_STATUS_EXPIRED = "expired"; // 已过期 + // 哗啦啦key public static final String getAllShop = "allShop"; public static final String getBaseInfo = "baseInfo"; diff --git a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java index 28dcabc..7972875 100644 --- a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java +++ b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java @@ -39,6 +39,7 @@ public class SecurityConfig { .permitAll() .antMatchers( "/api/login", + "/api/qr-login/**", "/api/register", "/api/cms/website/createWebsite", "/druid/**", diff --git a/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java b/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java index 5297884..9a31563 100644 --- a/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java +++ b/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java @@ -402,7 +402,7 @@ public class WxLoginController extends BaseController { String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken(); final HashMap map = new HashMap<>(); map.put("path", "/package/admin/order-scan?orderNo=".concat(orderNo)); - map.put("env_version", "trial"); + map.put("env_version", "release"); // 获取图片 Buffer byte[] qrCode = HttpRequest.post(apiUrl) .body(JSON.toJSONString(map)) @@ -439,7 +439,7 @@ public class WxLoginController extends BaseController { final HashMap map = new HashMap<>(); map.put("scene", scene); map.put("page", "pages/index/index"); - map.put("env_version", "trial"); + map.put("env_version", "release"); String jsonBody = JSON.toJSONString(map); System.out.println("请求的 JSON body = " + jsonBody); diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 0615070..63de5e2 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -48,6 +48,11 @@ config: server-url: https://server.websoft.top/api upload-path: /Users/gxwebsoft/JAVA/mp-java/src/main/resources/ # window(D:\Temp) + # JWT配置 + jwt: + secret: websoft-jwt-secret-key-2025-dev-environment + expire: 86400 # token过期时间(秒) 24小时 + # 开发环境证书配置 certificate: load-mode: CLASSPATH # 开发环境从classpath加载