diff --git a/.idea/.cache/.Apifox_Helper/.toolWindow.db b/.idea/.cache/.Apifox_Helper/.toolWindow.db index 2d8ba4b..b7b7bfd 100644 Binary files a/.idea/.cache/.Apifox_Helper/.toolWindow.db and b/.idea/.cache/.Apifox_Helper/.toolWindow.db differ diff --git a/pom.xml b/pom.xml index ac84f77..9d3e30a 100644 --- a/pom.xml +++ b/pom.xml @@ -140,12 +140,22 @@ hutool-all 5.8.38 - com.sun.mail javax.mail 1.6.2 + + + com.google.zxing + core + 3.4.1 + + + com.google.zxing + javase + 3.4.1 + diff --git a/src/main/java/vvpkassistant/Tools/QRCodeUtil.java b/src/main/java/vvpkassistant/Tools/QRCodeUtil.java new file mode 100644 index 0000000..ff0bef8 --- /dev/null +++ b/src/main/java/vvpkassistant/Tools/QRCodeUtil.java @@ -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 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()); + } +} \ No newline at end of file diff --git a/src/main/java/vvpkassistant/User/controller/UserController.java b/src/main/java/vvpkassistant/User/controller/UserController.java index a8e8992..20cdeb3 100644 --- a/src/main/java/vvpkassistant/User/controller/UserController.java +++ b/src/main/java/vvpkassistant/User/controller/UserController.java @@ -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 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 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 getUserInfo(@RequestBody Map 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 verificationMail(@RequestParam("token") String token){ return ResponseData.success(userService.verificationMail(token)); } + + + @GetMapping("/qrcode") + public ResponseData generatedQrcode(){ + return ResponseData.success(userService.generatedQrcode()); + } + + @GetMapping("/check/{uuid}") + public ResponseData 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(""); + } } diff --git a/src/main/java/vvpkassistant/User/model/DTO/LoginInfoDTO.java b/src/main/java/vvpkassistant/User/model/DTO/LoginInfoDTO.java new file mode 100644 index 0000000..4f70a20 --- /dev/null +++ b/src/main/java/vvpkassistant/User/model/DTO/LoginInfoDTO.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/vvpkassistant/User/model/DTO/ScanInfoDTO.java b/src/main/java/vvpkassistant/User/model/DTO/ScanInfoDTO.java new file mode 100644 index 0000000..59a17c4 --- /dev/null +++ b/src/main/java/vvpkassistant/User/model/DTO/ScanInfoDTO.java @@ -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; +} diff --git a/src/main/java/vvpkassistant/User/model/QrcodeEntity.java b/src/main/java/vvpkassistant/User/model/QrcodeEntity.java new file mode 100644 index 0000000..dfd2215 --- /dev/null +++ b/src/main/java/vvpkassistant/User/model/QrcodeEntity.java @@ -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; +} diff --git a/src/main/java/vvpkassistant/User/model/QrcodeVO.java b/src/main/java/vvpkassistant/User/model/QrcodeVO.java new file mode 100644 index 0000000..c17290e --- /dev/null +++ b/src/main/java/vvpkassistant/User/model/QrcodeVO.java @@ -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; +} diff --git a/src/main/java/vvpkassistant/User/model/ResponseVO.java b/src/main/java/vvpkassistant/User/model/ResponseVO.java new file mode 100644 index 0000000..a3cf16b --- /dev/null +++ b/src/main/java/vvpkassistant/User/model/ResponseVO.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/vvpkassistant/User/model/UserModelVO.java b/src/main/java/vvpkassistant/User/model/UserModelVO.java index cbda940..27bc72d 100644 --- a/src/main/java/vvpkassistant/User/model/UserModelVO.java +++ b/src/main/java/vvpkassistant/User/model/UserModelVO.java @@ -23,5 +23,6 @@ public class UserModelVO { private String token; private Boolean newAccount; private WxChatParam chatInfo; + private Boolean havaPassword; private Integer mailVerification; } diff --git a/src/main/java/vvpkassistant/User/model/enumeration/LoginStatusEnum.java b/src/main/java/vvpkassistant/User/model/enumeration/LoginStatusEnum.java new file mode 100644 index 0000000..476add8 --- /dev/null +++ b/src/main/java/vvpkassistant/User/model/enumeration/LoginStatusEnum.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/vvpkassistant/User/service/UserService.java b/src/main/java/vvpkassistant/User/service/UserService.java index 044e53a..fecd2ea 100644 --- a/src/main/java/vvpkassistant/User/service/UserService.java +++ b/src/main/java/vvpkassistant/User/service/UserService.java @@ -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 { Boolean activateAccount(String token); Boolean verificationMail(String token); + + Object generatedQrcode(); + + Object checkQrcode(String uuid); + + void scanQrcode(ScanInfoDTO scanInfoDTO); + + void confirm(ScanInfoDTO scanInfoDTO); } diff --git a/src/main/java/vvpkassistant/User/service/UserServiceImpl.java b/src/main/java/vvpkassistant/User/service/UserServiceImpl.java index 228aaf7..c19481c 100644 --- a/src/main/java/vvpkassistant/User/service/UserServiceImpl.java +++ b/src/main/java/vvpkassistant/User/service/UserServiceImpl.java @@ -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 implements UserService { @Resource @@ -35,6 +48,11 @@ public class UserServiceImpl extends ServiceImpl implements @Autowired private MailService mailService; + + private final Cache qrcodeCache = Caffeine.newBuilder() + .expireAfterWrite(2, TimeUnit.MINUTES) + .build(); + @Override public UserModelVO loginWithMail(UserModelDTO model) { @@ -164,4 +182,74 @@ public class UserServiceImpl extends ServiceImpl 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()); + } } diff --git a/src/main/java/vvpkassistant/common/ErrorCode.java b/src/main/java/vvpkassistant/common/ErrorCode.java index d6f23aa..65c033e 100644 --- a/src/main/java/vvpkassistant/common/ErrorCode.java +++ b/src/main/java/vvpkassistant/common/ErrorCode.java @@ -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, "主播已存在"), diff --git a/src/main/java/vvpkassistant/config/SaTokenConfigure.java b/src/main/java/vvpkassistant/config/SaTokenConfigure.java index f984cff..e269c02 100644 --- a/src/main/java/vvpkassistant/config/SaTokenConfigure.java +++ b/src/main/java/vvpkassistant/config/SaTokenConfigure.java @@ -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" + }; } diff --git a/target/classes/application.yml b/target/classes/application.yml index f69cce0..054a339 100644 --- a/target/classes/application.yml +++ b/target/classes/application.yml @@ -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= \ No newline at end of file +activateUrl: http://192.168.1.174:8086/user/activate?token= +verificationMailUrl: http://192.168.1.174:8086/user/verification?token= \ No newline at end of file