feat: 修复登录页面验证码功能
后端修复: - 启用验证码功能(captchaEnabled: true) - 实现验证码图片生成(Java Graphics2D绘制) - 实现验证码存储和验证逻辑(内存ConcurrentHashMap) - 验证码包含干扰线、噪点、随机颜色和角度 前端修复: - 正确获取登录响应中的token(res.data.token) - 登录失败时显示错误消息并刷新验证码
This commit is contained in:
parent
20a7ebfd0b
commit
b67187f5ab
|
|
@ -3,8 +3,12 @@ package com.pangu.web.controller.system;
|
||||||
import com.pangu.common.core.domain.AjaxResult;
|
import com.pangu.common.core.domain.AjaxResult;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import javax.imageio.ImageIO;
|
||||||
import java.util.Map;
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录控制器
|
* 登录控制器
|
||||||
|
|
@ -14,6 +18,11 @@ import java.util.Map;
|
||||||
@RestController
|
@RestController
|
||||||
public class SysLoginController {
|
public class SysLoginController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码存储(开发阶段使用内存,生产环境应使用Redis)
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> CAPTCHA_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户信息
|
* 获取用户信息
|
||||||
*/
|
*/
|
||||||
|
|
@ -39,6 +48,27 @@ public class SysLoginController {
|
||||||
*/
|
*/
|
||||||
@PostMapping("/api/login")
|
@PostMapping("/api/login")
|
||||||
public AjaxResult login(@RequestBody Map<String, String> loginBody) {
|
public AjaxResult login(@RequestBody Map<String, String> loginBody) {
|
||||||
|
String username = loginBody.get("username");
|
||||||
|
String password = loginBody.get("password");
|
||||||
|
String code = loginBody.get("code");
|
||||||
|
String uuid = loginBody.get("uuid");
|
||||||
|
|
||||||
|
// 验证码校验
|
||||||
|
if (uuid != null && !uuid.isEmpty()) {
|
||||||
|
String cachedCode = CAPTCHA_CACHE.remove(uuid);
|
||||||
|
if (cachedCode == null) {
|
||||||
|
return AjaxResult.error("验证码已过期");
|
||||||
|
}
|
||||||
|
if (code == null || !cachedCode.equalsIgnoreCase(code)) {
|
||||||
|
return AjaxResult.error("验证码错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户名密码校验(开发阶段简单校验)
|
||||||
|
if (!"admin".equals(username) || !"admin123".equals(password)) {
|
||||||
|
return AjaxResult.error("用户名或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("token", "mock-token-" + System.currentTimeMillis());
|
data.put("token", "mock-token-" + System.currentTimeMillis());
|
||||||
return AjaxResult.success(data);
|
return AjaxResult.success(data);
|
||||||
|
|
@ -57,10 +87,98 @@ public class SysLoginController {
|
||||||
*/
|
*/
|
||||||
@GetMapping("/api/captchaImage")
|
@GetMapping("/api/captchaImage")
|
||||||
public AjaxResult getCaptchaImage() {
|
public AjaxResult getCaptchaImage() {
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
String code = generateCaptchaCode(4);
|
||||||
|
|
||||||
|
// 存储验证码
|
||||||
|
CAPTCHA_CACHE.put(uuid, code);
|
||||||
|
|
||||||
|
// 生成验证码图片
|
||||||
|
String imgBase64 = generateCaptchaImage(code);
|
||||||
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("uuid", "mock-uuid-" + System.currentTimeMillis());
|
data.put("uuid", uuid);
|
||||||
data.put("img", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==");
|
data.put("img", "data:image/png;base64," + imgBase64);
|
||||||
data.put("captchaEnabled", false);
|
data.put("captchaEnabled", true);
|
||||||
return AjaxResult.success(data);
|
return AjaxResult.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机验证码字符
|
||||||
|
*/
|
||||||
|
private String generateCaptchaCode(int length) {
|
||||||
|
String chars = "ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
|
||||||
|
Random random = new Random();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
sb.append(chars.charAt(random.nextInt(chars.length())));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成验证码图片(Base64编码)
|
||||||
|
*/
|
||||||
|
private String generateCaptchaImage(String code) {
|
||||||
|
int width = 120;
|
||||||
|
int height = 40;
|
||||||
|
|
||||||
|
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics2D g = image.createGraphics();
|
||||||
|
|
||||||
|
// 设置抗锯齿
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
// 背景色
|
||||||
|
g.setColor(new Color(240, 240, 240));
|
||||||
|
g.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// 绘制干扰线
|
||||||
|
Random random = new Random();
|
||||||
|
g.setColor(new Color(200, 200, 200));
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
int x1 = random.nextInt(width);
|
||||||
|
int y1 = random.nextInt(height);
|
||||||
|
int x2 = random.nextInt(width);
|
||||||
|
int y2 = random.nextInt(height);
|
||||||
|
g.drawLine(x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制验证码文字
|
||||||
|
g.setFont(new Font("Arial", Font.BOLD, 28));
|
||||||
|
Color[] colors = {
|
||||||
|
new Color(65, 105, 225),
|
||||||
|
new Color(220, 20, 60),
|
||||||
|
new Color(34, 139, 34),
|
||||||
|
new Color(255, 140, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < code.length(); i++) {
|
||||||
|
g.setColor(colors[i % colors.length]);
|
||||||
|
// 随机旋转角度
|
||||||
|
double angle = (random.nextDouble() - 0.5) * 0.3;
|
||||||
|
g.rotate(angle, 25 + i * 25, 28);
|
||||||
|
g.drawString(String.valueOf(code.charAt(i)), 15 + i * 25, 30);
|
||||||
|
g.rotate(-angle, 25 + i * 25, 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制噪点
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
int x = random.nextInt(width);
|
||||||
|
int y = random.nextInt(height);
|
||||||
|
g.setColor(new Color(random.nextInt(200), random.nextInt(200), random.nextInt(200)));
|
||||||
|
g.fillRect(x, y, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.dispose();
|
||||||
|
|
||||||
|
// 转换为Base64
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(image, "png", baos);
|
||||||
|
return Base64.getEncoder().encodeToString(baos.toByteArray());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,9 +143,14 @@ const handleLogin = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
userStore.setToken(res.token)
|
// token在res.data.token或res.token中
|
||||||
|
const token = res.data?.token || res.token
|
||||||
|
userStore.setToken(token)
|
||||||
ElMessage.success('登录成功')
|
ElMessage.success('登录成功')
|
||||||
router.push('/')
|
router.push('/')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '登录失败')
|
||||||
|
getCaptcha()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 登录失败刷新验证码
|
// 登录失败刷新验证码
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue