1.添加 web 端扫码登录功能
This commit is contained in:
BIN
.idea/.cache/.Apifox_Helper/.toolWindow.db
generated
BIN
.idea/.cache/.Apifox_Helper/.toolWindow.db
generated
Binary file not shown.
12
pom.xml
12
pom.xml
@@ -140,12 +140,22 @@
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.38</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>javax.mail</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
<!-- 二维码生成-->
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>3.4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>javase</artifactId>
|
||||
<version>3.4.1</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
31
src/main/java/vvpkassistant/Tools/QRCodeUtil.java
Normal file
31
src/main/java/vvpkassistant/Tools/QRCodeUtil.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package vvpkassistant.Tools;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Hashtable;
|
||||
|
||||
public class QRCodeUtil {
|
||||
|
||||
public static String generateQRCode(String content, int width, int height)
|
||||
throws WriterException, IOException {
|
||||
|
||||
Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||
|
||||
BitMatrix matrix = new MultiFormatWriter().encode(
|
||||
content, BarcodeFormat.QR_CODE, width, height, hints);
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
MatrixToImageWriter.writeToStream(matrix, "PNG", outputStream);
|
||||
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,12 @@ import vvpkassistant.Data.ResponseData;
|
||||
import vvpkassistant.Data.ResponseInfo;
|
||||
import vvpkassistant.Data.WxChatParam;
|
||||
import vvpkassistant.User.mapper.UserDao;
|
||||
import vvpkassistant.User.model.DTO.LoginInfoDTO;
|
||||
import vvpkassistant.User.model.DTO.ScanInfoDTO;
|
||||
import vvpkassistant.User.model.DTO.UserModelDTO;
|
||||
import vvpkassistant.User.model.UserModel;
|
||||
import vvpkassistant.User.model.UserModelVO;
|
||||
import vvpkassistant.User.model.enumeration.LoginStatusEnum;
|
||||
import vvpkassistant.User.service.UserService;
|
||||
import vvpkassistant.common.ErrorCode;
|
||||
import vvpkassistant.config.FunctionConfigHolder;
|
||||
@@ -123,7 +126,10 @@ public class UserController {
|
||||
// return ResponseData.error(ResponseInfo.ERROR,"创建聊天账号失败,请稍后再试");
|
||||
// }
|
||||
Map<String,Object> result = new HashMap<>();
|
||||
result.put("info", tempModel);
|
||||
StpUtil.login(tempModel.getId());
|
||||
UserModelVO userModelVO = BeanUtil.copyProperties(tempModel, UserModelVO.class);
|
||||
userModelVO.setToken(StpUtil.getTokenValue());
|
||||
result.put("info", userModelVO);
|
||||
result.put("newAccount",false);
|
||||
//否则直接返回用户
|
||||
return ResponseData.success(result);
|
||||
@@ -149,7 +155,10 @@ public class UserController {
|
||||
UserModel model = userDao.queryWithPhoneNumber(phoneNumber);
|
||||
Map<String,Object> result = new HashMap<>();
|
||||
if (model != null) { // 老用户
|
||||
result.put("info",model);
|
||||
UserModelVO userModelVO = BeanUtil.copyProperties(model, UserModelVO.class);
|
||||
StpUtil.login(userModelVO.getId());
|
||||
userModelVO.setToken(StpUtil.getTokenValue());
|
||||
result.put("info",userModelVO);
|
||||
result.put("newAccount", false);
|
||||
result.put("chatInfo",wxChatParam);
|
||||
return ResponseData.success(result);
|
||||
@@ -197,7 +206,9 @@ public class UserController {
|
||||
@PostMapping("getUserInfo")
|
||||
public ResponseData<Object> getUserInfo(@RequestBody Map<String,Integer> map) {
|
||||
UserModel userModel = userDao.selectById(map.get("id"));
|
||||
return ResponseData.success(userModel);
|
||||
UserModelVO userModelVO = BeanUtil.copyProperties(userModel, UserModelVO.class);
|
||||
userModelVO.setHavaPassword(userModel.getPassword() != null);
|
||||
return ResponseData.success(userModelVO);
|
||||
}
|
||||
|
||||
// 查询用户所有pk数据
|
||||
@@ -394,4 +405,27 @@ public class UserController {
|
||||
public ResponseData<Object> verificationMail(@RequestParam("token") String token){
|
||||
return ResponseData.success(userService.verificationMail(token));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/qrcode")
|
||||
public ResponseData<Object> generatedQrcode(){
|
||||
return ResponseData.success(userService.generatedQrcode());
|
||||
}
|
||||
|
||||
@GetMapping("/check/{uuid}")
|
||||
public ResponseData<Object> checkQrcode(@PathVariable String uuid){
|
||||
return ResponseData.success(userService.checkQrcode(uuid));
|
||||
}
|
||||
|
||||
@PostMapping("/scan")
|
||||
public ResponseData<?> scanQrCode(@RequestBody ScanInfoDTO scanInfoDTO) {
|
||||
userService.scanQrcode(scanInfoDTO);
|
||||
return ResponseData.success("");
|
||||
}
|
||||
|
||||
@PostMapping("/confirm")
|
||||
public ResponseData<?> confirm(@RequestBody ScanInfoDTO scanInfoDTO) {
|
||||
userService.confirm(scanInfoDTO);
|
||||
return ResponseData.success("");
|
||||
}
|
||||
}
|
||||
|
||||
29
src/main/java/vvpkassistant/User/model/DTO/LoginInfoDTO.java
Normal file
29
src/main/java/vvpkassistant/User/model/DTO/LoginInfoDTO.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package vvpkassistant.User.model.DTO;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoginInfoDTO {
|
||||
|
||||
/**
|
||||
* 唯一标识
|
||||
*/
|
||||
private String uuid;
|
||||
/**
|
||||
* 设备号
|
||||
*/
|
||||
private String device;
|
||||
|
||||
/**
|
||||
* 扫码状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
private Integer userId;
|
||||
}
|
||||
13
src/main/java/vvpkassistant/User/model/DTO/ScanInfoDTO.java
Normal file
13
src/main/java/vvpkassistant/User/model/DTO/ScanInfoDTO.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package vvpkassistant.User.model.DTO;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/8/5 16:43
|
||||
*/
|
||||
@Data
|
||||
public class ScanInfoDTO {
|
||||
private String uuid;
|
||||
private Integer userId;
|
||||
}
|
||||
13
src/main/java/vvpkassistant/User/model/QrcodeEntity.java
Normal file
13
src/main/java/vvpkassistant/User/model/QrcodeEntity.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package vvpkassistant.User.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/8/5 20:06
|
||||
*/
|
||||
@Data
|
||||
public class QrcodeEntity {
|
||||
private String uuid;
|
||||
private String type;
|
||||
}
|
||||
15
src/main/java/vvpkassistant/User/model/QrcodeVO.java
Normal file
15
src/main/java/vvpkassistant/User/model/QrcodeVO.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package vvpkassistant.User.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/8/5 18:14
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class QrcodeVO {
|
||||
private String uuid;
|
||||
private String qrcode;
|
||||
}
|
||||
32
src/main/java/vvpkassistant/User/model/ResponseVO.java
Normal file
32
src/main/java/vvpkassistant/User/model/ResponseVO.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package vvpkassistant.User.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ResponseVO {
|
||||
|
||||
/**
|
||||
* 唯一标识
|
||||
*/
|
||||
private String uuid;
|
||||
/**
|
||||
* 登录二维码
|
||||
*/
|
||||
private String qrcode;
|
||||
|
||||
/**
|
||||
* jwt令牌
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 扫码状态
|
||||
*/
|
||||
private String status;
|
||||
}
|
||||
@@ -23,5 +23,6 @@ public class UserModelVO {
|
||||
private String token;
|
||||
private Boolean newAccount;
|
||||
private WxChatParam chatInfo;
|
||||
private Boolean havaPassword;
|
||||
private Integer mailVerification;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package vvpkassistant.User.model.enumeration;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum LoginStatusEnum {
|
||||
UNSCANNED("未扫描"),
|
||||
SCANNED("已扫描"),
|
||||
CONFIRMED("已确认");
|
||||
|
||||
private String desc;
|
||||
|
||||
|
||||
LoginStatusEnum(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package vvpkassistant.User.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import vvpkassistant.User.model.DTO.ScanInfoDTO;
|
||||
import vvpkassistant.User.model.DTO.UserModelDTO;
|
||||
import vvpkassistant.User.model.UserModel;
|
||||
import vvpkassistant.User.model.UserModelVO;
|
||||
@@ -21,4 +22,12 @@ public interface UserService extends IService<UserModel> {
|
||||
Boolean activateAccount(String token);
|
||||
|
||||
Boolean verificationMail(String token);
|
||||
|
||||
Object generatedQrcode();
|
||||
|
||||
Object checkQrcode(String uuid);
|
||||
|
||||
void scanQrcode(ScanInfoDTO scanInfoDTO);
|
||||
|
||||
void confirm(ScanInfoDTO scanInfoDTO);
|
||||
}
|
||||
|
||||
@@ -3,28 +3,41 @@ package vvpkassistant.User.service;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.temp.SaTempUtil;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.google.zxing.WriterException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import vvpkassistant.Data.WxChatParam;
|
||||
import vvpkassistant.Tools.BcryptUtils;
|
||||
import vvpkassistant.Tools.QRCodeUtil;
|
||||
import vvpkassistant.Tools.VVTools;
|
||||
import vvpkassistant.User.mapper.UserDao;
|
||||
import vvpkassistant.User.model.*;
|
||||
import vvpkassistant.User.model.DTO.LoginInfoDTO;
|
||||
import vvpkassistant.User.model.DTO.ScanInfoDTO;
|
||||
import vvpkassistant.User.model.DTO.UserModelDTO;
|
||||
import vvpkassistant.User.model.UserModel;
|
||||
import vvpkassistant.User.model.UserModelVO;
|
||||
import vvpkassistant.User.model.enumeration.LoginStatusEnum;
|
||||
import vvpkassistant.common.ErrorCode;
|
||||
import vvpkassistant.exception.BusinessException;
|
||||
import vvpkassistant.mail.service.MailService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/8/4 16:19
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class UserServiceImpl extends ServiceImpl<UserDao, UserModel> implements UserService {
|
||||
|
||||
@Resource
|
||||
@@ -35,6 +48,11 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserModel> implements
|
||||
@Autowired
|
||||
private MailService mailService;
|
||||
|
||||
|
||||
private final Cache<String, LoginInfoDTO> qrcodeCache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public UserModelVO loginWithMail(UserModelDTO model) {
|
||||
|
||||
@@ -164,4 +182,74 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserModel> implements
|
||||
}
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"邮箱验证失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public QrcodeVO generatedQrcode() {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
QrcodeEntity qrcodeEntity = new QrcodeEntity();
|
||||
qrcodeEntity.setUuid(uuid);
|
||||
qrcodeEntity.setType("qrcdoe");
|
||||
String base64QR = null;
|
||||
try {
|
||||
base64QR = QRCodeUtil.generateQRCode(JSONUtil.toJsonStr(qrcodeEntity), 200, 200);
|
||||
} catch (WriterException | IOException e) {
|
||||
log.error(e.getMessage());
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"二维码生成失败");
|
||||
}
|
||||
|
||||
LoginInfoDTO loginInfoDTO = new LoginInfoDTO();
|
||||
loginInfoDTO.setStatus(LoginStatusEnum.UNSCANNED.name());
|
||||
loginInfoDTO.setUuid(uuid);
|
||||
|
||||
// 二维码uuid绑定,存入缓存
|
||||
qrcodeCache.put(uuid,loginInfoDTO);
|
||||
// 返回生成的二维码信息
|
||||
QrcodeVO vo = QrcodeVO.builder().uuid(uuid).qrcode("data:image/png;base64," + base64QR).build();
|
||||
log.info("-------生成二维码成功:{}-------", uuid);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object checkQrcode(String uuid) {
|
||||
LoginInfoDTO loginInfoDTO = qrcodeCache.getIfPresent(uuid);
|
||||
if (loginInfoDTO == null) {
|
||||
throw new BusinessException(ErrorCode.QRCODE_EXPIRED);
|
||||
}
|
||||
if (Objects.equals(loginInfoDTO.getStatus(), LoginStatusEnum.SCANNED.name())) {
|
||||
return loginInfoDTO;
|
||||
}
|
||||
if (LoginStatusEnum.CONFIRMED.name().equals(loginInfoDTO.getStatus())) {
|
||||
UserModel userModel = userDao.selectById(loginInfoDTO.getUserId());
|
||||
StpUtil.login(userModel.getId());
|
||||
UserModelVO userModelVO = BeanUtil.copyProperties(userModel, UserModelVO.class);
|
||||
userModelVO.setToken(StpUtil.getTokenValue());
|
||||
userModelVO.setChatInfo(wxChatParam);
|
||||
return userModelVO;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanQrcode(ScanInfoDTO scanInfoDTO) {
|
||||
LoginInfoDTO loginInfoDTO = qrcodeCache.getIfPresent(scanInfoDTO.getUuid());
|
||||
if (loginInfoDTO != null) {
|
||||
loginInfoDTO.setStatus(LoginStatusEnum.SCANNED.name());
|
||||
}
|
||||
if (loginInfoDTO != null) {
|
||||
qrcodeCache.put(scanInfoDTO.getUuid(),loginInfoDTO);
|
||||
}
|
||||
|
||||
log.info("-------扫码成功uuid:{}-------", scanInfoDTO.getUuid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirm(ScanInfoDTO scanInfoDTO) {
|
||||
LoginInfoDTO loginInfoDTO = qrcodeCache.getIfPresent(scanInfoDTO.getUuid());
|
||||
if (loginInfoDTO != null) {
|
||||
loginInfoDTO.setStatus(LoginStatusEnum.CONFIRMED.name());
|
||||
loginInfoDTO.setUserId(scanInfoDTO.getUserId());
|
||||
qrcodeCache.put(scanInfoDTO.getUuid(),loginInfoDTO);
|
||||
}
|
||||
log.info("-------确认登录成功uuid:{}-------", scanInfoDTO.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ public enum ErrorCode {
|
||||
CONFIG_NAME_DUPLICATE(1004, "配置名称重复"),
|
||||
SIGN_IN_FAIL(1004, "当天已签到"),
|
||||
EMAIL_SEND_FREQUENT(1005,"邮件发送太频繁,请 1 分钟后再试"),
|
||||
QRCODE_EXPIRED(1006,"二维码已过期"),
|
||||
|
||||
/* =============== 主播相关 =============== */
|
||||
ANCHOR_ALREADY_EXISTS(2001, "主播已存在"),
|
||||
|
||||
@@ -45,7 +45,14 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
"/user/loginWithPhoneNumber",
|
||||
"/user/registerWithMail",
|
||||
"/user/loginWithMail",
|
||||
"/user/activate"
|
||||
"/user/inputUserInfo",
|
||||
"/user/verificationMail",
|
||||
"/user/activate",
|
||||
"/user/qrcode",
|
||||
"/user/check/*",
|
||||
"/user/scan",
|
||||
"/user/confirm"
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ sa-token:
|
||||
# token 名称(同时也是 cookie 名称)
|
||||
token-name: token
|
||||
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
|
||||
timeout: 2592000
|
||||
timeout: -1
|
||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||
active-timeout: -1
|
||||
active-timeout: 648000
|
||||
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||
@@ -42,4 +42,5 @@ sa-token:
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
activateUrl: http://192.168.1.174:8086/user/activate?token=
|
||||
activateUrl: http://192.168.1.174:8086/user/activate?token=
|
||||
verificationMailUrl: http://192.168.1.174:8086/user/verification?token=
|
||||
Reference in New Issue
Block a user