修改命名

This commit is contained in:
YuNan 2024-11-09 13:22:03 +08:00
parent fdb2b98a98
commit 8eb80c7b65
33 changed files with 505 additions and 48 deletions

View File

@ -7,14 +7,14 @@
<sourceOutputDir name="target/generated-sources/annotations" /> <sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" /> <outputRelativeToContentRoot value="true" />
<module name="YuNan-demo" /> <module name="YuNan-system-start" />
</profile> </profile>
</annotationProcessing> </annotationProcessing>
</component> </component>
<component name="JavacSettings"> <component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE"> <option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="YuNan-cmd" options="" /> <module name="YuNan-cmd" options="" />
<module name="YuNan-demo" options="-parameters" /> <module name="YuNan-system-start" options="-parameters" />
</option> </option>
</component> </component>
</project> </project>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8"> <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="file://$PROJECT_DIR$/YuNan-demo/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/YuNan-system-start/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/YuNan-demo/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/YuNan-system-start/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" /> <file url="PROJECT" charset="UTF-8" />

View File

@ -1,30 +0,0 @@
package com.yunan.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class GlobalCorsConfig {
/**
* 解决跨域问题
* @return {@link CorsFilter }
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 设置你要允许的网站域名
config.addAllowedOrigin("http://localhost:3100");
//允许跨域发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

View File

@ -9,7 +9,7 @@
<version>1.0.0</version> <version>1.0.0</version>
</parent> </parent>
<artifactId>YuNan-demo</artifactId> <artifactId>YuNan-system-start</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<dependencies> <dependencies>

View File

@ -1,5 +1,6 @@
package com.yunan.config; package com.yunan.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
@ -7,7 +8,9 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
/*告诉spring,运行的时候加载读取这个类*/ /*告诉spring,运行的时候加载读取这个类*/
@Slf4j
@Configuration @Configuration
public class RedisConfig { public class RedisConfig {
@ -20,6 +23,7 @@ public class RedisConfig {
redisTemplate.setValueSerializer redisTemplate.setValueSerializer
(new JdkSerializationRedisSerializer()); (new JdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory); redisTemplate.setConnectionFactory(connectionFactory);
log.info("redis配置已加载");
return redisTemplate; return redisTemplate;
} }
} }

View File

@ -0,0 +1,33 @@
package com.yunan.controller;
import com.yunan.dto.Captcha;
import com.yunan.response.R;
import com.yunan.service.CaptchaService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/captcha")
@Api(tags = "验证码拼图")
public class CaptchaController {
@Autowired
private CaptchaService captchaService;
@ApiOperation(value = "生成验证码拼图")
@PostMapping("get-captcha")
public R getCaptcha(@RequestBody Captcha captcha) {
return R.ok(captchaService.getCaptcha(captcha));
}
}

View File

@ -0,0 +1,56 @@
package com.yunan.dto;
import lombok.Data;
@Data
public class Captcha {
/**
* 随机字符串
**/
private String nonceStr;
/**
* 验证值
**/
private String value;
/**
* 生成的画布的base64
**/
private String canvasSrc;
/**
* 画布宽度
**/
private Integer canvasWidth;
/**
* 画布高度
**/
private Integer canvasHeight;
/**
* 生成的阻塞块的base64
**/
private String blockSrc;
/**
* 阻塞块宽度
**/
private Integer blockWidth;
/**
* 阻塞块高度
**/
private Integer blockHeight;
/**
* 阻塞块凸凹半径
**/
private Integer blockRadius;
/**
* 阻塞块的横轴坐标
**/
private Integer blockX;
/**
* 阻塞块的纵轴坐标
**/
private Integer blockY;
/**
* 图片获取位置
**/
private Integer place;
}

View File

@ -2,11 +2,13 @@ package com.yunan.dto;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Email; import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
@ApiModel("用户登入请求") @ApiModel("用户登入请求")
@Data
public class LoginDTO { public class LoginDTO {
@NotBlank(message = "用户名不能为空") @NotBlank(message = "用户名不能为空")

View File

@ -2,14 +2,16 @@ package com.yunan.dto;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Email; import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
@ApiModel("用户注册请求") @ApiModel("用户注册请求")
@Data
public class RegisterDTO { public class RegisterDTO {
@NotBlank(message = "用户名不能为空") @NotBlank(message = "用户名不能为空")
@ApiModelProperty(value = "用户名", required = true) @ApiModelProperty(value = "用户名")
private String username; private String username;
@Email(message = "请输入有效的邮箱") @Email(message = "请输入有效的邮箱")
@ -20,7 +22,7 @@ public class RegisterDTO {
@ApiModelProperty(value = "密码", required = true) @ApiModelProperty(value = "密码", required = true)
private String password; private String password;
@ApiModelProperty(value = "验证码") @ApiModelProperty(value = "验证码", required = true)
private String code; private String code;
private String token; private String token;

View File

@ -1,4 +1,4 @@
package com.yunan.pojo; package com.yunan.dto;
import java.io.Serializable; import java.io.Serializable;
@ -6,7 +6,7 @@ import java.io.Serializable;
* 用户实体类 * 用户实体类
*/ */
public class User implements Serializable { public class User implements Serializable {
private int id; private Integer id;
/** 用户名*/ /** 用户名*/
private String username; private String username;
/** 密码*/ /** 密码*/
@ -32,7 +32,7 @@ public class User implements Serializable {
'}'; '}';
} }
public int getId() { public Integer getId() {
return id; return id;
} }

View File

@ -1,8 +1,7 @@
package com.yunan.mapper; package com.yunan.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yunan.dto.RegisterDTO; import com.yunan.dto.User;
import com.yunan.pojo.User;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper

View File

@ -0,0 +1,70 @@
package com.yunan.response;
public class R {
private int code; // 状态码200代表成功其他代表失败
private String message; // 响应信息
private Object data; // 响应数据
// 默认构造函数
public R() {
}
// 构造函数
public R(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
// 成功返回
public static R ok(Object data) {
return new R(200, "成功", data); // 200代表成功
}
// 错误返回
public static R error(String message) {
return new R(500, message, null); // 500代表服务器错误message为错误信息
}
// 错误返回带code和message
public static R error(int code, String message) {
return new R(code, message, null); // 根据传入的code返回不同的错误
}
// get set 方法
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
// 重写toString方法方便调试输出
@Override
public String toString() {
return "R{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}

View File

@ -2,7 +2,6 @@ package com.yunan.service;
import com.yunan.dto.RegisterDTO; import com.yunan.dto.RegisterDTO;
import com.yunan.pojo.User;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import javax.mail.MessagingException; import javax.mail.MessagingException;

View File

@ -0,0 +1,88 @@
package com.yunan.service;
import com.github.xiaoymin.knife4j.core.util.StrUtil;
import com.yunan.dto.Captcha;
import com.yunan.util.CaptchaUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.awt.image.BufferedImage;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class CaptchaService {
/**
* 拼图验证码允许偏差
**/
private static Integer ALLOW_DEVIATION = 3;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 校验验证码
* @param imageKey
* @param imageCode
* @return boolean
**/
public String checkImageCode(String imageKey, String imageCode) {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
String text = ops.get("imageCode:" + imageKey);
if(StrUtil.isBlank(text)){
return "验证码已失效";
}
// 根据移动距离判断验证是否成功
if (Math.abs(Integer.parseInt(text) - Integer.parseInt(imageCode)) > ALLOW_DEVIATION) {
return "验证失败,请控制拼图对齐缺口";
}
return null;
}
/**
* 缓存验证码有效期15分钟
* @param key
* @param code
**/
public void saveImageCode(String key, String code) {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("imageCode:" + key, code, 15, TimeUnit.MINUTES);
}
/**
* 获取验证码拼图生成的抠图和带抠图阴影的大图及抠图坐标
**/
public Object getCaptcha(Captcha captcha) {
//参数校验
CaptchaUtils.checkCaptcha(captcha);
//获取画布的宽高
int canvasWidth = captcha.getCanvasWidth();
int canvasHeight = captcha.getCanvasHeight();
//获取阻塞块的宽高/半径
int blockWidth = captcha.getBlockWidth();
int blockHeight = captcha.getBlockHeight();
int blockRadius = captcha.getBlockRadius();
//获取资源图
BufferedImage canvasImage = CaptchaUtils.getBufferedImage(captcha.getPlace());
//调整原图到指定大小
canvasImage = CaptchaUtils.imageResize(canvasImage, canvasWidth, canvasHeight);
//随机生成阻塞块坐标
int blockX = CaptchaUtils.getNonceByRange(blockWidth, canvasWidth - blockWidth - 10);
int blockY = CaptchaUtils.getNonceByRange(10, canvasHeight - blockHeight + 1);
//阻塞块
BufferedImage blockImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
//新建的图像根据轮廓图颜色赋值源图生成遮罩
CaptchaUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY);
// 移动横坐标
String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
// 缓存
saveImageCode(nonceStr,String.valueOf(blockX));
//设置返回参数
captcha.setNonceStr(nonceStr);
captcha.setBlockY(blockY);
captcha.setBlockSrc(CaptchaUtils.toBase64(blockImage, "png"));
captcha.setCanvasSrc(CaptchaUtils.toBase64(canvasImage, "png"));
return captcha;
}
}

View File

@ -3,7 +3,7 @@ package com.yunan.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yunan.dto.RegisterDTO; import com.yunan.dto.RegisterDTO;
import com.yunan.mapper.AuthMapper; import com.yunan.mapper.AuthMapper;
import com.yunan.pojo.User; import com.yunan.dto.User;
import com.yunan.response.ErrorResponse; import com.yunan.response.ErrorResponse;
import com.yunan.service.AuthService; import com.yunan.service.AuthService;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;

View File

@ -0,0 +1,229 @@
package com.yunan.util;
import com.yunan.dto.Captcha;
import org.apache.commons.lang3.RandomUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Base64;
import java.util.Objects;
import java.util.Random;
public class CaptchaUtils {
/**
* 网络图片地址
**/
private final static String IMG_URL = "https://loyer.wang/view/ftp/wallpaper/%s.jpg";
/**
* 本地图片地址
**/
private final static String IMG_PATH = "C:/Users/NanYu/Pictures/ComputerPaper/%s.jpg";
/**
* 入参校验设置默认值
**/
public static void checkCaptcha(Captcha captcha) {
//设置画布宽度默认值
if (captcha.getCanvasWidth() == null) {
captcha.setCanvasWidth(320);
}
//设置画布高度默认值
if (captcha.getCanvasHeight() == null) {
captcha.setCanvasHeight(155);
}
//设置阻塞块宽度默认值
if (captcha.getBlockWidth() == null) {
captcha.setBlockWidth(65);
}
//设置阻塞块高度默认值
if (captcha.getBlockHeight() == null) {
captcha.setBlockHeight(55);
}
//设置阻塞块凹凸半径默认值
if (captcha.getBlockRadius() == null) {
captcha.setBlockRadius(9);
}
//设置图片来源默认值
if (captcha.getPlace() == null) {
captcha.setPlace(0);
}
}
/**
* 获取指定范围内的随机数
**/
public static int getNonceByRange(int start, int end) {
Random random = new Random();
return random.nextInt(end - start + 1) + start;
}
/**
* 获取验证码资源图
**/
public static BufferedImage getBufferedImage(Integer place) {
try {
//随机图片
// int nonce = getNonceByRange(0, 1000);
int nonce = 111;
//获取网络资源图片
if (0 == place) {
String imgUrl = String.format(IMG_URL, nonce);
URL url = new URL(imgUrl);
return ImageIO.read(url.openStream());
}
//获取本地图片
else {
String imgPath = String.format(IMG_PATH, nonce);
File file = new File(imgPath);
return ImageIO.read(file);
}
} catch (Exception e) {
System.out.println("获取拼图资源失败");
//异常处理
return null;
}
}
/**
* 调整图片大小
**/
public static BufferedImage imageResize(BufferedImage bufferedImage, int width, int height) {
Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = resultImage.createGraphics();
graphics2D.drawImage(image, 0, 0, null);
graphics2D.dispose();
return resultImage;
}
/**
* 抠图并生成阻塞块
**/
public static void cutByTemplate(BufferedImage canvasImage, BufferedImage blockImage, int blockWidth, int blockHeight, int blockRadius, int blockX, int blockY) {
BufferedImage waterImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
//阻塞块的轮廓图
int[][] blockData = getBlockData(blockWidth, blockHeight, blockRadius);
//创建阻塞块具体形状
for (int i = 0; i < blockWidth; i++) {
for (int j = 0; j < blockHeight; j++) {
try {
//原图中对应位置变色处理
if (blockData[i][j] == 1) {
//背景设置为黑色
waterImage.setRGB(i, j, Color.BLACK.getRGB());
blockImage.setRGB(i, j, canvasImage.getRGB(blockX + i, blockY + j));
//轮廓设置为白色取带像素和无像素的界点判断该点是不是临界轮廓点
if (blockData[i + 1][j] == 0 || blockData[i][j + 1] == 0 || blockData[i - 1][j] == 0 || blockData[i][j - 1] == 0) {
blockImage.setRGB(i, j, Color.WHITE.getRGB());
waterImage.setRGB(i, j, Color.WHITE.getRGB());
}
}
//这里把背景设为透明
else {
blockImage.setRGB(i, j, Color.TRANSLUCENT);
waterImage.setRGB(i, j, Color.TRANSLUCENT);
}
} catch (ArrayIndexOutOfBoundsException e) {
//防止数组下标越界异常
}
}
}
//在画布上添加阻塞块水印
addBlockWatermark(canvasImage, waterImage, blockX, blockY);
}
/**
* 构建拼图轮廓轨迹
**/
private static int[][] getBlockData(int blockWidth, int blockHeight, int blockRadius) {
int[][] data = new int[blockWidth][blockHeight];
double po = Math.pow(blockRadius, 2);
//随机生成两个圆的坐标在4个方向上 随机找到2个方向添加凸/
///凹1
int face1 = RandomUtils.nextInt(0,4);
///凹2
int face2;
//保证两个凸/凹不在同一位置
do {
face2 = RandomUtils.nextInt(0,4);
} while (face1 == face2);
//获取凸/凹起位置坐标
int[] circle1 = getCircleCoords(face1, blockWidth, blockHeight, blockRadius);
int[] circle2 = getCircleCoords(face2, blockWidth, blockHeight, blockRadius);
//随机凸/凹类型
int shape = getNonceByRange(0, 1);
//圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心a,b,半径为r的圆
//计算需要的小图轮廓用二维数组来表示二维数组有两张值0和1其中0表示没有颜色1有颜色
for (int i = 0; i < blockWidth; i++) {
for (int j = 0; j < blockHeight; j++) {
data[i][j] = 0;
//创建中间的方形区域
if ((i >= blockRadius && i <= blockWidth - blockRadius && j >= blockRadius && j <= blockHeight - blockRadius)) {
data[i][j] = 1;
}
double d1 = Math.pow(i - Objects.requireNonNull(circle1)[0], 2) + Math.pow(j - circle1[1], 2);
double d2 = Math.pow(i - Objects.requireNonNull(circle2)[0], 2) + Math.pow(j - circle2[1], 2);
//创建两个凸/
if (d1 <= po || d2 <= po) {
data[i][j] = shape;
}
}
}
return data;
}
/**
* 根据朝向获取圆心坐标
*/
private static int[] getCircleCoords(int face, int blockWidth, int blockHeight, int blockRadius) {
//
if (0 == face) {
return new int[]{blockWidth / 2 - 1, blockRadius};
}
//
else if (1 == face) {
return new int[]{blockRadius, blockHeight / 2 - 1};
}
//
else if (2 == face) {
return new int[]{blockWidth / 2 - 1, blockHeight - blockRadius - 1};
}
//
else if (3 == face) {
return new int[]{blockWidth - blockRadius - 1, blockHeight / 2 - 1};
}
return null;
}
/**
* 在画布上添加阻塞块水印
*/
private static void addBlockWatermark(BufferedImage canvasImage, BufferedImage blockImage, int x, int y) {
Graphics2D graphics2D = canvasImage.createGraphics();
graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.8f));
graphics2D.drawImage(blockImage, x, y, null);
graphics2D.dispose();
}
/**
* BufferedImage转BASE64
*/
public static String toBase64(BufferedImage bufferedImage, String type) {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, type, byteArrayOutputStream);
String base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return String.format("data:image/%s;base64,%s", type, base64);
} catch (IOException e) {
System.out.println("图片资源转换BASE64失败");
//异常处理
return null;
}
}
}

View File

@ -53,3 +53,6 @@ server:
logging: logging:
level: level:
org.springframework.boot.context.web: DEBUG org.springframework.boot.context.web: DEBUG

View File

@ -1,9 +1,9 @@
package com.yunan; package com.yunan;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest import org.springframework.boot.test.context.SpringBootTest;
import org.testng.annotations.Test;
class YuNanDemoApplicationTests { class YuNanDemoApplicationTests {
@Test @Test

View File

@ -10,7 +10,7 @@
<modules> <modules>
<!-- 子模块列表 --> <!-- 子模块列表 -->
<module>YuNan-demo</module> <module>YuNan-system-start</module>
</modules> </modules>
<parent> <parent>
@ -111,6 +111,8 @@
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId> <artifactId>commons-pool2</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>