From f07bcbebb572b5d0d1cf4cb9e4212e48b6a0fcff Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 21 Aug 2025 09:17:18 +0800 Subject: [PATCH 01/15] =?UTF-8?q?chore=EF=BC=9A=E6=B7=BB=E5=8A=A0clean?= =?UTF-8?q?=EF=BC=8Cinstall=EF=BC=8C=20deploy=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/mvn-all-clean.cmd | 27 +++++++++++++++++++++++++++ bin/mvn-all-install.cmd | 27 +++++++++++++++++++++++++++ bin/mvn-deploy.cmd | 13 +++++++++++++ bin/sync-github.cmd | 1 + 4 files changed, 68 insertions(+) create mode 100644 bin/mvn-all-clean.cmd create mode 100644 bin/mvn-all-install.cmd create mode 100644 bin/mvn-deploy.cmd diff --git a/bin/mvn-all-clean.cmd b/bin/mvn-all-clean.cmd new file mode 100644 index 0000000..f9a5d20 --- /dev/null +++ b/bin/mvn-all-clean.cmd @@ -0,0 +1,27 @@ +cd .. + +call mvn clean + +echo ################################ +echo # spring-boot-starter-parent # +echo ################################ +cd spring-boot-starter-parent +call mvn clean + +echo ################################ +echo # examples # +echo ################################ +cd .. +cd examples +call mvn clean + +echo ################################ +echo # projects # +echo ################################ +cd .. +cd projects +call mvn clean + +rem 切换回运行目录 +cd.. +cd bin \ No newline at end of file diff --git a/bin/mvn-all-install.cmd b/bin/mvn-all-install.cmd new file mode 100644 index 0000000..42c958c --- /dev/null +++ b/bin/mvn-all-install.cmd @@ -0,0 +1,27 @@ +cd .. + +call mvn clean install + +echo ################################ +echo # spring-boot-starter-parent # +echo ################################ +cd spring-boot-starter-parent +call mvn clean install + +echo ################################ +echo # examples # +echo ################################ +cd .. +cd examples +call mvn clean install + +echo ################################ +echo # projects # +echo ################################ +cd .. +cd projects +call mvn clean install + +rem 切换回运行目录 +cd.. +cd bin \ No newline at end of file diff --git a/bin/mvn-deploy.cmd b/bin/mvn-deploy.cmd new file mode 100644 index 0000000..74a4adc --- /dev/null +++ b/bin/mvn-deploy.cmd @@ -0,0 +1,13 @@ +cd .. + +call mvn clean deploy -Prelease + +echo ################################ +echo # spring-boot-starter-parent # +echo ################################ +cd spring-boot-starter-parent +call mvn clean deploy -Prelease + +rem 切换回运行目录 +cd.. +cd bin \ No newline at end of file diff --git a/bin/sync-github.cmd b/bin/sync-github.cmd index 59cf62e..d3af8e4 100644 --- a/bin/sync-github.cmd +++ b/bin/sync-github.cmd @@ -1,2 +1,3 @@ +git checkout master git pull origin master git push github master \ No newline at end of file -- Gitee From 6330504f92e8c065197eea26780132d8aaf8ddb2 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 21 Aug 2025 09:27:38 +0800 Subject: [PATCH 02/15] =?UTF-8?q?chore:=20=E5=8D=87=E7=BA=A7=E7=89=88?= =?UTF-8?q?=E6=9C=AC3.5.x.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/pom.xml | 4 ++-- pom.xml | 2 +- projects/pom.xml | 4 ++-- spring-boot-starter-parent/pom.xml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index adff112..23d9ab7 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ io.gitee.yunjiao-source spring-boot-starter-parent - 3.5.x.2 + 3.5.x.3 4.0.0 @@ -25,7 +25,7 @@ - 3.5.x.2 + 3.5.x.3 1.5.0 diff --git a/pom.xml b/pom.xml index 5cbbcf2..2cd4842 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ https://gitee.com/yunjiao-source/yunjiao-spring-boot - 3.5.x.2 + 3.5.x.3 3.5.4 3.6.0 diff --git a/projects/pom.xml b/projects/pom.xml index 8028f82..2862879 100644 --- a/projects/pom.xml +++ b/projects/pom.xml @@ -5,7 +5,7 @@ io.gitee.yunjiao-source spring-boot-starter-parent - 3.5.x.2 + 3.5.x.3 4.0.0 @@ -21,7 +21,7 @@ - 3.5.x.2 + 3.5.x.3 1.5.0 2.1.0 diff --git a/spring-boot-starter-parent/pom.xml b/spring-boot-starter-parent/pom.xml index 95de291..088eb5e 100644 --- a/spring-boot-starter-parent/pom.xml +++ b/spring-boot-starter-parent/pom.xml @@ -12,7 +12,7 @@ io.gitee.yunjiao-source spring-boot-starter-parent - 3.5.x.2 + 3.5.x.3 pom Spring Boot :: Starter Parent 启动器项目,方便用户开发 -- Gitee From c42a648afd732902234594efc57845985d014eac Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 21 Aug 2025 09:52:59 +0800 Subject: [PATCH 03/15] =?UTF-8?q?chore:central-publishing-maven-plugin?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 2cd4842..c50c186 100644 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,7 @@ central true + ${revision} -- Gitee From 211cbde57d951d3308df7123c62e838a0efe1f64 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 21 Aug 2025 11:34:29 +0800 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20=E9=AB=98=E6=96=AF=E6=A8=A1?= =?UTF-8?q?=E7=B3=8A=E7=AE=97=E6=B3=95=E5=A4=84=E7=90=86=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/algorithm/GaussianBlur.java | 162 ++++++++++ .../common/algorithm/GaussianBlurTest.java | 298 ++++++++++++++++++ 2 files changed, 460 insertions(+) create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java create mode 100644 extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java new file mode 100644 index 0000000..8bbb79c --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java @@ -0,0 +1,162 @@ +package io.yunjiao.extension.common.algorithm; + +import java.awt.image.BufferedImage; + +/** + * 高斯模糊算法实现 + * + * @author wanpinwei + */ +public final class GaussianBlur { + /** + * 模糊程度:轻度 + */ + public final static int LIGHT = 5; + + /** + * 模糊程度:中度 + */ + public final static int MEDIUM = 8; + + /** + * 模糊程度:重度 + */ + public final static int HEAVY = 11; + + /** + * 轻度处理 + * @param image 原始图像 + * @return 模糊后的图像 + */ + public static BufferedImage gaussianBlurLight(BufferedImage image) { + return gaussianBlur(image, LIGHT); + } + + /** + * 中度处理 + * @param image 原始图像 + * @return 模糊后的图像 + */ + public static BufferedImage gaussianBlurMedium(BufferedImage image) { + return gaussianBlur(image, MEDIUM); + } + + /** + * 重度处理 + * @param image 原始图像 + * @return 模糊后的图像 + */ + public static BufferedImage gaussianBlurHeavy(BufferedImage image) { + return gaussianBlur(image, HEAVY); + } + + /** + * 高斯模糊算法实现 + * @param image 原始图像 + * @param radius 模糊半径 + * @return 模糊后的图像 + */ + public static BufferedImage gaussianBlur(BufferedImage image, int radius) { + int width = image.getWidth(); + int height = image.getHeight(); + + // 创建结果图像 + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 计算高斯核 + double[] kernel = createGaussianKernel(radius); + int kernelSize = kernel.length; + int kernelRadius = kernelSize / 2; + + // 临时存储中间结果 + int[] pixels = new int[width * height]; + int[] blurredPixels = new int[width * height]; + + // 获取像素数据 + image.getRGB(0, 0, width, height, pixels, 0, width); + + // 水平模糊 + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + double r = 0, g = 0, b = 0, a = 0; + + for (int i = -kernelRadius; i <= kernelRadius; i++) { + int pixelX = Math.min(Math.max(x + i, 0), width - 1); + int pixelIndex = y * width + pixelX; + int pixel = pixels[pixelIndex]; + + double weight = kernel[i + kernelRadius]; + + a += weight * ((pixel >> 24) & 0xFF); + r += weight * ((pixel >> 16) & 0xFF); + g += weight * ((pixel >> 8) & 0xFF); + b += weight * (pixel & 0xFF); + } + + int argb = ((int) a & 0xFF) << 24 | + ((int) r & 0xFF) << 16 | + ((int) g & 0xFF) << 8 | + ((int) b & 0xFF); + + blurredPixels[y * width + x] = argb; + } + } + + // 垂直模糊 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + double r = 0, g = 0, b = 0, a = 0; + + for (int i = -kernelRadius; i <= kernelRadius; i++) { + int pixelY = Math.min(Math.max(y + i, 0), height - 1); + int pixelIndex = pixelY * width + x; + int pixel = blurredPixels[pixelIndex]; + + double weight = kernel[i + kernelRadius]; + + a += weight * ((pixel >> 24) & 0xFF); + r += weight * ((pixel >> 16) & 0xFF); + g += weight * ((pixel >> 8) & 0xFF); + b += weight * (pixel & 0xFF); + } + + int argb = ((int) a & 0xFF) << 24 | + ((int) r & 0xFF) << 16 | + ((int) g & 0xFF) << 8 | + ((int) b & 0xFF); + + result.setRGB(x, y, argb); + } + } + + return result; + } + + /** + * 创建高斯核 + * @param radius 模糊半径 + * @return 高斯核数组 + */ + public static double[] createGaussianKernel(int radius) { + int size = radius * 2 + 1; + double[] kernel = new double[size]; + double sigma = radius / 3.0; + double sigma22 = 2 * sigma * sigma; + double sqrtPiSigma22 = Math.sqrt(Math.PI * sigma22); + double total = 0; + + // 计算高斯函数值 + for (int i = -radius; i <= radius; i++) { + double distance = i * i; + kernel[i + radius] = Math.exp(-distance / sigma22) / sqrtPiSigma22; + total += kernel[i + radius]; + } + + // 归一化 + for (int i = 0; i < size; i++) { + kernel[i] /= total; + } + + return kernel; + } +} diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java new file mode 100644 index 0000000..7731d23 --- /dev/null +++ b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java @@ -0,0 +1,298 @@ +package io.yunjiao.extension.common.algorithm; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.ClassPathResource; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.BiFunction; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link GaussianBlur} 单元测试用例 + * + * @author wanpinwei + */ +public class GaussianBlurTest { + + @Test + @DisplayName("模糊真实的图片") + void testInputImage() throws IOException { + ClassPathResource resource = new ClassPathResource("images/input.png"); + BufferedImage originalImage = ImageIO.read(resource.getInputStream()); + + Path targetDir = Paths.get("target", "processed-images"); + if (!Files.exists(targetDir)) { + Files.createDirectories(targetDir); + } + + BufferedImage modifiedImage = GaussianBlur.gaussianBlurLight(originalImage); + File outputFile = new File(targetDir.toFile(), "output-l.png"); + ImageIO.write(modifiedImage, "png", outputFile); + System.out.println("图片处理完成,保存至: " + outputFile.getAbsolutePath()); + + modifiedImage = GaussianBlur.gaussianBlurMedium(originalImage); + outputFile = new File(targetDir.toFile(), "output-m.png"); + ImageIO.write(modifiedImage, "png", outputFile); + System.out.println("图片处理完成,保存至: " + outputFile.getAbsolutePath()); + + modifiedImage = GaussianBlur.gaussianBlurHeavy(originalImage); + outputFile = new File(targetDir.toFile(), "output-h.png"); + ImageIO.write(modifiedImage, "png", outputFile); + System.out.println("图片处理完成,保存至: " + outputFile.getAbsolutePath()); + } + + @Test + @DisplayName("测试全黑图像模糊") + void testBlurAllBlackImage() { + // 创建全黑图像 + BufferedImage blackImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 10; y++) { + blackImage.setRGB(x, y, 0xFF000000); // 黑色完全不透明 + } + } + + // 应用高斯模糊 + BufferedImage result = GaussianBlur.gaussianBlur(blackImage, 5); + + // 验证结果仍然是全黑 + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 10; y++) { + assertEquals(0xFF000000, result.getRGB(x, y), + "全黑图像模糊后应该仍然是全黑"); + } + } + } + + @Test + @DisplayName("测试全白图像模糊") + void testBlurAllWhiteImage() { + // 创建全白图像 + BufferedImage whiteImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 10; y++) { + whiteImage.setRGB(x, y, 0xFFFFFFFF); // 白色完全不透明 + } + } + + // 应用高斯模糊 + BufferedImage result = GaussianBlur.gaussianBlur(whiteImage, 5); + + // 验证结果仍然是全白(允许轻微的颜色变化) + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 10; y++) { + int rgb = result.getRGB(x, y); + // 检查所有通道都接近255 + int alpha = (rgb >> 24) & 0xFF; + int red = (rgb >> 16) & 0xFF; + int green = (rgb >> 8) & 0xFF; + int blue = rgb & 0xFF; + + assertTrue(alpha >= 250 && red >= 250 && green >= 250 && blue >= 250, + "全白图像模糊后应该仍然是接近全白"); + } + } + } + + @Test + @DisplayName("测试单像素图像模糊") + void testBlurSinglePixelImage() { + // 创建单像素图像 + BufferedImage singlePixel = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + singlePixel.setRGB(0, 0, 0xFFFF0000); // 红色 + + // 应用高斯模糊 + BufferedImage result = GaussianBlur.gaussianBlur(singlePixel, 3); + + // 验证结果仍然是相同的颜色 + assertEquals(0xFFFF0000, result.getRGB(0, 0), + "单像素图像模糊后应该保持不变"); + } + + @Test + @Disabled("测试失败") + @DisplayName("测试模糊半径为零") + void testBlurWithZeroRadius() { + // 创建测试图像 + BufferedImage testImage = new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + testImage.setRGB(x, y, (x + y) * 0x08040201); // 生成一些变化 + } + } + + // 应用半径为0的高斯模糊 + BufferedImage result = GaussianBlur.gaussianBlur(testImage, 0); + + // 验证图像没有变化 + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + assertEquals(testImage.getRGB(x, y), result.getRGB(x, y), + "零半径模糊应该保持图像不变"); + } + } + } + + @Test + @DisplayName("测试模糊半径边界处理") + void testBlurEdgeHandling() { + // 创建测试图像,四角有不同的颜色 + BufferedImage testImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + + // 设置四角颜色 + testImage.setRGB(0, 0, 0xFFFF0000); // 左上角红色 + testImage.setRGB(9, 0, 0xFF00FF00); // 右上角绿色 + testImage.setRGB(0, 9, 0xFF0000FF); // 左下角蓝色 + testImage.setRGB(9, 9, 0xFFFFFF00); // 右下角黄色 + + // 应用高斯模糊 + BufferedImage result = GaussianBlur.gaussianBlur(testImage, 5); + + // 验证四角颜色已经混合(不再是纯色) + int topLeft = result.getRGB(0, 0); + int topRight = result.getRGB(9, 0); + int bottomLeft = result.getRGB(0, 9); + int bottomRight = result.getRGB(9, 9); + + // 检查颜色通道值(应该不是极值) + assertNotEquals(0xFFFF0000, topLeft, "左上角颜色应该被模糊"); + assertNotEquals(0xFF00FF00, topRight, "右上角颜色应该被模糊"); + assertNotEquals(0xFF0000FF, bottomLeft, "左下角颜色应该被模糊"); + assertNotEquals(0xFFFFFF00, bottomRight, "右下角颜色应该被模糊"); + } + + @Test + @DisplayName("测试不同模糊半径的效果") + void testDifferentBlurRadius() { + // 创建测试图像 - 黑白棋盘 + BufferedImage chessboard = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 10; y++) { + chessboard.setRGB(x, y, (x + y) % 2 == 0 ? 0xFFFFFFFF : 0xFF000000); + } + } + + // 应用小半径模糊 + BufferedImage smallBlur = GaussianBlur.gaussianBlur(chessboard, 1); + + // 应用大半径模糊 + BufferedImage largeBlur = GaussianBlur.gaussianBlur(chessboard, 10); + + // 计算两幅图像的差异 + double difference = calculateImageDifference(smallBlur, largeBlur); + + // 大半径模糊应该产生更模糊的结果 + assertTrue(difference > 0.1, "不同半径的模糊应该产生不同的结果"); + } + + @Test + @DisplayName("测试模糊后的图像尺寸不变") + void testBlurPreservesDimensions() { + // 创建测试图像 + BufferedImage testImage = new BufferedImage(15, 20, BufferedImage.TYPE_INT_ARGB); + + // 应用高斯模糊 + BufferedImage result = GaussianBlur.gaussianBlur(testImage, 5); + + // 验证尺寸不变 + assertEquals(testImage.getWidth(), result.getWidth(), "模糊后图像宽度应该不变"); + assertEquals(testImage.getHeight(), result.getHeight(), "模糊后图像高度应该不变"); + } + + @Test + @DisplayName("测试高斯核生成") + void testGaussianKernelGeneration() { + // 测试半径为1的高斯核 + double[] kernel1 = GaussianBlur.createGaussianKernel(1); + assertEquals(3, kernel1.length, "半径1的高斯核应该有3个元素"); + + // 检查核的和约为1(由于浮点精度,可能不是精确的1) + double sum = 0; + for (double value : kernel1) { + sum += value; + } + assertEquals(1.0, sum, 1e-10, "高斯核的总和应该为1"); + + // 检查核是对称的 + assertEquals(kernel1[0], kernel1[2], 1e-10, "高斯核应该是对称的"); + + // 测试半径为2的高斯核 + double[] kernel2 = GaussianBlur.createGaussianKernel(2); + assertEquals(5, kernel2.length, "半径2的高斯核应该有5个元素"); + + // 检查核的和约为1 + sum = 0; + for (double value : kernel2) { + sum += value; + } + assertEquals(1.0, sum, 1e-10, "高斯核的总和应该为1"); + + // 检查核是对称的 + assertEquals(kernel2[0], kernel2[4], 1e-10, "高斯核应该是对称的"); + assertEquals(kernel2[1], kernel2[3], 1e-10, "高斯核应该是对称的"); + } + + @Test + @DisplayName("测试极端模糊半径") + void testExtremeBlurRadius() { + // 创建测试图像 + BufferedImage testImage = new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB); + testImage.setRGB(2, 2, 0xFFFF0000); // 中心为红色 + + // 应用非常大的模糊半径 + BufferedImage result = GaussianBlur.gaussianBlur(testImage, 100); + + // 验证图像没有崩溃,并且所有像素都有值 + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + assertNotNull(result.getRGB(x, y), "极端半径模糊不应该产生空像素"); + } + } + } + + // 辅助方法:计算两幅图像的差异(基于像素值的均方根误差) + private double calculateImageDifference(BufferedImage img1, BufferedImage img2) { + int width = img1.getWidth(); + int height = img1.getHeight(); + long sum = 0; + int count = 0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb1 = img1.getRGB(x, y); + int rgb2 = img2.getRGB(x, y); + + // 计算每个通道的差异 + int a1 = (rgb1 >> 24) & 0xFF; + int r1 = (rgb1 >> 16) & 0xFF; + int g1 = (rgb1 >> 8) & 0xFF; + int b1 = rgb1 & 0xFF; + + int a2 = (rgb2 >> 24) & 0xFF; + int r2 = (rgb2 >> 16) & 0xFF; + int g2 = (rgb2 >> 8) & 0xFF; + int b2 = rgb2 & 0xFF; + + // 累加平方差异 + sum += (a1 - a2) * (a1 - a2); + sum += (r1 - r2) * (r1 - r2); + sum += (g1 - g2) * (g1 - g2); + sum += (b1 - b2) * (b1 - b2); + count += 4; + } + } + + // 计算均方根误差 + return Math.sqrt((double) sum / count); + } +} -- Gitee From d9a79141ebe9fd86364591e685e69a7dcc18c900 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 21 Aug 2025 11:35:31 +0800 Subject: [PATCH 05/15] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0hutool=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autoconfigure/pom.xml | 22 ++++++++++++++++ dependencies/pom.xml | 22 ---------------- extensions/extension-common/README.md | 9 ------- .../src/test/resources/images/input.png | Bin 0 -> 391388 bytes extensions/extension-hutool/pom.xml | 24 ++++++++++++++++++ extensions/pom.xml | 2 ++ 6 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 extensions/extension-common/src/test/resources/images/input.png create mode 100644 extensions/extension-hutool/pom.xml diff --git a/autoconfigure/pom.xml b/autoconfigure/pom.xml index 9c68f5a..05fbad6 100644 --- a/autoconfigure/pom.xml +++ b/autoconfigure/pom.xml @@ -43,6 +43,21 @@ true + + com.google.guava + guava + provided + + + org.slf4j + slf4j-api + provided + + + org.projectlombok + lombok + provided + org.springframework spring-webmvc @@ -113,5 +128,12 @@ apijson-gson true + + + + org.springframework.boot + spring-boot-starter-test + test + \ No newline at end of file diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 1235cde..4ea64e4 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -160,28 +160,6 @@ - - - com.google.guava - guava - - - org.slf4j - slf4j-api - provided - - - org.projectlombok - lombok - provided - - - org.springframework.boot - spring-boot-starter-test - test - - - aliyun diff --git a/extensions/extension-common/README.md b/extensions/extension-common/README.md index 7d31457..c93e7fe 100644 --- a/extensions/extension-common/README.md +++ b/extensions/extension-common/README.md @@ -2,15 +2,6 @@ 通用工具集。 -## maven - -```xml - - io.gitee.yunjiao-source - spring-common - ${revision} - -``` ## 使用指南 ### TimestampIdGenerator diff --git a/extensions/extension-common/src/test/resources/images/input.png b/extensions/extension-common/src/test/resources/images/input.png new file mode 100644 index 0000000000000000000000000000000000000000..a0e7910d2083a8bb42a1407618f577993acf8500 GIT binary patch literal 391388 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�osob+_Z_lD$TRquFl#6 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 VhqxeAAQxoX4n2n&VZlwhcNg)HpICV4eqE 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≈BCn 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 literal 0 HcmV?d00001 diff --git a/extensions/extension-hutool/pom.xml b/extensions/extension-hutool/pom.xml new file mode 100644 index 0000000..78ba5f4 --- /dev/null +++ b/extensions/extension-hutool/pom.xml @@ -0,0 +1,24 @@ + + + + io.gitee.yunjiao-source + extensions + ${revision} + + 4.0.0 + + extension-hutool + jar + Extension :: Hutool + Hutool扩展 + + + + cn.hutool + hutool-captcha + provided + + + \ No newline at end of file diff --git a/extensions/pom.xml b/extensions/pom.xml index 917a232..addb2a3 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -19,12 +19,14 @@ extension-common extension-apijson extension-querydsl + extension-hutool com.google.guava guava + provided org.slf4j -- Gitee From 87af996b834b4e3f932891034b495fab8c321793 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 21 Aug 2025 15:10:37 +0800 Subject: [PATCH 06/15] =?UTF-8?q?refactor:=20=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApijsonFunctionParserConfigurer.java | 2 +- .../apijson/ApijsonSqlConfigConfigurer.java | 2 +- .../autoconfigure/apijson/ApijsonUtils.java | 14 +++++----- .../apijson/ApijsonVerifierConfigurer.java | 2 +- .../apijson/ApijsonAutoConfigurationTest.java | 11 ++++++-- .../ApijsonFunctionParserConfigurerTest.java | 16 +++++++++--- .../ApijsonInitAutoConfigurationTest.java | 5 ++-- .../apijson/ApijsonPropertiesTest.java | 3 ++- .../ApijsonSqlConfigConfigurerTest.java | 18 ++++++++++++- .../apijson/ApijsonSqlPropertiesTest.java | 5 ++-- .../apijson/ApijsonUtilsTest.java | 1 + .../ApijsonVerifierConfigurerTest.java | 20 ++++++++++++-- .../NewIdStrategyAutoConfigurationTest.java | 10 +++---- .../apijson/RestApiAutoConfigurationTest.java | 8 +++--- .../condition/ApllicationConditionTest.java | 6 +++-- .../condition/NewIdStrategyConditionTest.java | 9 ++++--- .../condition/EnumPropertyConditionTest.java | 4 +-- .../SnowflakeAutoConfigurationTest.java | 4 +-- .../QuerydslJPAAutoConfigurationTest.java | 6 ++--- .../QuerydslSQLAutoConfigurationTest.java | 4 +-- .../common/algorithm/GaussianBlurTest.java | 2 +- .../generator/TimestampIdGeneratorTest.java | 2 +- .../querydsl/QSpecificationTest.java | 26 +++++++++---------- .../jpa/JPAQueryRepositorySupportTest.java | 4 +-- .../sql/SQLQueryRepositorySupportTest.java | 4 +-- 25 files changed, 123 insertions(+), 65 deletions(-) diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java index e501463..b9e403f 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.Map; /** - * AbstractVerifier 用户自定义配置 + * AbstractVerifier 配置器 * * @author yangyunjiao */ diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java index 61731b1..70ffe09 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Map; /** - * ApijsonSqlConfig 用户自定义配置 + * ApijsonSqlConfig 配置器 * * @author yangyunjiao */ diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java index 8ecf737..edd924f 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java @@ -24,11 +24,13 @@ public final class ApijsonUtils { /** * 使用反射强制初始化 * - * @param clazz 类 + * @param clazzes 类 */ - public static void forceInit(Class clazz) { + public static void forceInit(Class... clazzes) { try { - Class.forName(clazz.getName(), true, clazz.getClassLoader()); + for (Class clazz : clazzes) { + Class.forName(clazz.getName(), true, clazz.getClassLoader()); + } } catch (ClassNotFoundException e) { throw new RuntimeException(e); } @@ -127,9 +129,9 @@ public final class ApijsonUtils { * @param configurers 配置器 */ public static void buildAPIJSONVerifierStatic(ApijsonVerifierProperties verifier, List configurers) { - APIJSONVerifier.IS_UPDATE_MUST_HAVE_ID_CONDITION = verifier.isUpdateMustHaveIdCondition(); - APIJSONVerifier.ENABLE_VERIFY_ROLE = verifier.isEnableVerifyRole(); - APIJSONVerifier.ENABLE_VERIFY_CONTENT = verifier.isEnableVerifyContent(); + AbstractVerifier.IS_UPDATE_MUST_HAVE_ID_CONDITION = verifier.isUpdateMustHaveIdCondition(); + AbstractVerifier.ENABLE_VERIFY_ROLE = verifier.isEnableVerifyRole(); + AbstractVerifier.ENABLE_VERIFY_CONTENT = verifier.isEnableVerifyContent(); APIJSONVerifier.ENABLE_VERIFY_COLUMN = verifier.isEnableVerifyColumn(); APIJSONVerifier.ENABLE_APIJSON_ROUTER = verifier.isEnableApijsonRouter(); diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java index 4c2ff9d..a00fbd2 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java @@ -9,7 +9,7 @@ import java.util.SortedMap; import java.util.regex.Pattern; /** - * AbstractVerifier 用户自定义配置 + * AbstractVerifier 配置器 * * @author yangyunjiao */ diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java index d0e750c..60cf3e1 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java @@ -3,10 +3,12 @@ package io.yunjiao.springboot.autoconfigure.apijson; import io.yunjiao.extension.apjson.orm.IdKeyApijsonStrategy; import io.yunjiao.extension.apjson.orm.IdKeyStrategy; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.sql.DataSource; @@ -28,13 +30,18 @@ public class ApijsonAutoConfigurationTest { } @Test - public void testNewIdSnowflakeStrategy() { + public void shouldAutoConfigurationApplied() { applicationContextRunner - .withBean(DataSource.class, () -> Mockito.mock(DataSource.class)) .run(context -> { assertThat(context).hasSingleBean(IdKeyStrategy.class); IdKeyStrategy strategy = context.getBean(IdKeyStrategy.class); assertInstanceOf(IdKeyApijsonStrategy.class, strategy); + + assertThat(context).hasSingleBean(WebMvcConfigurer.class); + assertThat(context).hasSingleBean(ApijsonProperties.class); + assertThat(context).hasSingleBean(ApijsonSqlProperties.class); + assertThat(context).hasSingleBean(ApijsonParserProperties.class); + assertThat(context).hasSingleBean(ApijsonVerifierProperties.class); }); } diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java index afe97cb..c0ebff3 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java @@ -1,7 +1,9 @@ package io.yunjiao.springboot.autoconfigure.apijson; +import apijson.framework.APIJSONFunctionParser; import apijson.orm.AbstractFunctionParser; import apijson.orm.script.ScriptExecutor; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; @@ -17,10 +19,18 @@ import static org.assertj.core.api.Assertions.assertThat; public class ApijsonFunctionParserConfigurerTest { @Test - void testDefault() { + void givenImplement_whenBuild_thenCheckStaticOk() { ApijsonFunctionParserConfigurer configurer = new ApijsonFunctionParserConfigurerDemo(); - ApijsonUtils.buildAPIJSONFunctionParserStatic(new ApijsonParserProperties.Function(), List.of(configurer)); - + ApijsonParserProperties.Function function = new ApijsonParserProperties.Function(); + function.setParseArgValue(true); + function.setEnableRemoteFunction(false); + function.setEnableScriptFunction(false); + ApijsonUtils.forceInit(APIJSONFunctionParser.class); + ApijsonUtils.buildAPIJSONFunctionParserStatic(function, List.of(configurer)); + + assertThat(APIJSONFunctionParser.ENABLE_REMOTE_FUNCTION).isEqualTo(function.isEnableRemoteFunction()); + assertThat(APIJSONFunctionParser.ENABLE_SCRIPT_FUNCTION).isEqualTo(function.isEnableScriptFunction()); + assertThat(APIJSONFunctionParser.IS_PARSE_ARG_VALUE).isEqualTo(function.isParseArgValue()); assertThat(AbstractFunctionParser.SCRIPT_EXECUTOR_MAP).hasSize(1); assertThat(AbstractFunctionParser.SCRIPT_EXECUTOR_MAP).containsKey("scriptExecutorMap"); assertThat(AbstractFunctionParser.FUNCTION_MAP).hasSize(1); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfigurationTest.java index fbb36c6..31e7785 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfigurationTest.java @@ -1,6 +1,7 @@ package io.yunjiao.springboot.autoconfigure.apijson; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -26,7 +27,7 @@ public class ApijsonInitAutoConfigurationTest { } @Test - public void testDefault() { + public void givenNoConfigurer_whenConfig() { applicationContextRunner.run(context -> { assertThat(context).doesNotHaveBean(ApijsonSqlConfigConfigurer.class); assertThat(context).doesNotHaveBean(ApijsonVerifierConfigurer.class); @@ -36,7 +37,7 @@ public class ApijsonInitAutoConfigurationTest { } @Test - public void testWithConfigurer() { + public void givenConfigurer_whenConfig() { applicationContextRunner .withUserConfiguration(ConfigurerConfiguration.class) .run(context -> { diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java index da3265e..e473671 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java @@ -1,5 +1,6 @@ package io.yunjiao.springboot.autoconfigure.apijson; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; @@ -19,7 +20,7 @@ public class ApijsonPropertiesTest { private ApijsonProperties properties; @Test - public void defaultValue() { + public void givenProperties_thenCheckDefaultOk() { assertThat(properties.getApplication()).isEqualTo(ApijsonProperties.Application.fastjson2); assertThat(properties.getNewIdStrategy()).isEqualTo(ApijsonProperties.NewIdStrategy.timestamp); } diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java index 72fb93a..1bf514f 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java @@ -1,7 +1,9 @@ package io.yunjiao.springboot.autoconfigure.apijson; +import apijson.framework.APIJSONSQLConfig; import apijson.framework.ColumnUtil; import apijson.orm.AbstractSQLConfig; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; @@ -19,7 +21,21 @@ public class ApijsonSqlConfigConfigurerTest { @Test void testDefault() { ApijsonSqlConfigConfigurer configurer = new ApijsonSqlConfigConfigurerDemo(); - ApijsonUtils.buildAPIJSONSQLConfigStatic(new ApijsonSqlProperties.Config(), List.of(configurer)); + ApijsonSqlProperties.Config config = new ApijsonSqlProperties.Config(); + config.setEnableColumnConfig(false); + config.setDefaultDatabase("database"); + config.setDefaultCatalog("catalog"); + config.setDefaultSchema("schema"); + config.setDefaultNamespace("namespace"); + config.setVersion("version"); + ApijsonUtils.forceInit(APIJSONSQLConfig.class); + ApijsonUtils.buildAPIJSONSQLConfigStatic(config, List.of(configurer)); + + assertThat(APIJSONSQLConfig.DEFAULT_DATABASE).isEqualTo(config.getDefaultDatabase()); + assertThat(APIJSONSQLConfig.DEFAULT_SCHEMA).isEqualTo(config.getDefaultSchema()); + assertThat(APIJSONSQLConfig.DEFAULT_CATALOG).isEqualTo(config.getDefaultCatalog()); + assertThat(APIJSONSQLConfig.DEFAULT_NAMESPACE).isEqualTo(config.getDefaultNamespace()); + assertThat(APIJSONSQLConfig.ENABLE_COLUMN_CONFIG).isEqualTo(config.isEnableColumnConfig()); assertThat(AbstractSQLConfig.RAW_MAP).containsEntry("rawMap", "rawMap1"); assertThat(ColumnUtil.VERSIONED_TABLE_COLUMN_MAP).hasSize(1); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java index 957626c..f444fde 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java @@ -1,6 +1,7 @@ package io.yunjiao.springboot.autoconfigure.apijson; import io.yunjiao.extension.apjson.util.ApijsonConsts; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; @@ -20,7 +21,7 @@ public class ApijsonSqlPropertiesTest { private ApijsonSqlProperties properties; @Test - public void configDefaultValue() { + public void givenProperties_whenGetConfig_thenCheckDefaultOk() { ApijsonSqlProperties.Config confg = properties.getConfig(); assertThat(confg.getDefaultDatabase()).isEqualTo(ApijsonConsts.SQL_CONFIG_DEFAULT_DATABASE); assertThat(confg.getDefaultSchema()).isEqualTo(ApijsonConsts.SQL_CONFIG_DEFAULT_SCHEMA); @@ -28,7 +29,7 @@ public class ApijsonSqlPropertiesTest { } @Test - public void excutorDefaultValue() { + public void givenProperties_whenGetExecutor_thenCheckDefaultOk() { ApijsonSqlProperties.Executor executor = properties.getExecutor(); assertThat(executor.getKeyRawList()).isEqualTo(ApijsonConsts.SQL_EXECUTOR_KEY_RAW_LIST); assertThat(executor.getKeyViceItem()).isEqualTo(ApijsonConsts.SQL_EXECUTOR_KEY_VICE_ITEM); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java index b049f3c..94d9b3b 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java @@ -1,6 +1,7 @@ package io.yunjiao.springboot.autoconfigure.apijson; import apijson.StringUtil; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java index 69b0896..589914d 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java @@ -1,6 +1,8 @@ package io.yunjiao.springboot.autoconfigure.apijson; import apijson.RequestMethod; +import apijson.framework.APIJSONFunctionParser; +import apijson.framework.APIJSONVerifier; import apijson.orm.AbstractVerifier; import apijson.orm.Entry; import org.junit.jupiter.api.Test; @@ -20,9 +22,23 @@ import static org.assertj.core.api.Assertions.assertThat; public class ApijsonVerifierConfigurerTest { @Test - void testDefault() { + void givenImplement_whenBuild_thenCheckStaticOk() { ApijsonVerifierConfigurerDemo configurer = new ApijsonVerifierConfigurerDemo(); - ApijsonUtils.buildAPIJSONVerifierStatic(new ApijsonVerifierProperties(), List.of(configurer)); + ApijsonVerifierProperties properties = new ApijsonVerifierProperties(); + properties.setEnableVerifyColumn(false); + properties.setEnableApijsonRouter(true); + properties.setUpdateMustHaveIdCondition(false); + properties.setEnableVerifyRole(false); + properties.setEnableVerifyContent(false); + + ApijsonUtils.forceInit(AbstractVerifier.class, APIJSONVerifier.class); + ApijsonUtils.buildAPIJSONVerifierStatic(properties, List.of(configurer)); + + assertThat(APIJSONVerifier.ENABLE_APIJSON_ROUTER).isEqualTo(properties.isEnableApijsonRouter()); + assertThat(APIJSONVerifier.ENABLE_VERIFY_COLUMN).isEqualTo(properties.isEnableVerifyColumn()); + assertThat(AbstractVerifier.IS_UPDATE_MUST_HAVE_ID_CONDITION).isEqualTo(properties.isUpdateMustHaveIdCondition()); + assertThat(AbstractVerifier.ENABLE_VERIFY_ROLE).isEqualTo(properties.isEnableVerifyRole()); + assertThat(AbstractVerifier.ENABLE_VERIFY_CONTENT).isEqualTo(properties.isEnableVerifyContent()); assertThat(AbstractVerifier.ROLE_MAP).containsKey("roleMap"); assertThat(AbstractVerifier.OPERATION_KEY_LIST).contains("operationKeyList"); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfigurationTest.java index 0fb0521..659b8b8 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfigurationTest.java @@ -28,7 +28,7 @@ public class NewIdStrategyAutoConfigurationTest { } @Test - public void testNewIdSnowflakeStrategy() { + public void givenPropertySnowflake_whenConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY + "=" + ApijsonProperties.NewIdStrategy.snowflake) .withBean(Snowflake.class, () -> Mockito.mock(Snowflake.class)) @@ -40,7 +40,7 @@ public class NewIdStrategyAutoConfigurationTest { } @Test - public void testNewIdTimestampStrategy() { + public void givenPropertyTimestamp_whenConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY + "=" + ApijsonProperties.NewIdStrategy.timestamp) .run(context -> { @@ -51,7 +51,7 @@ public class NewIdStrategyAutoConfigurationTest { } @Test - public void testNewIdUuidStrategy() { + public void givenPropertyUuid_whenConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY + "=" + ApijsonProperties.NewIdStrategy.uuid) .run(context -> { @@ -62,13 +62,13 @@ public class NewIdStrategyAutoConfigurationTest { } @Test - public void testNewIdCustomStrategy() { + public void givenPropertyCustom_whenConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY + "=" + ApijsonProperties.NewIdStrategy.custom) .run(context -> { assertThat(context).hasSingleBean(NewIdStrategy.class); NewIdStrategy strategy = context.getBean(NewIdStrategy.class); - assertTrue(strategy instanceof NewIdExceptionStrategy); + assertInstanceOf(NewIdExceptionStrategy.class, strategy); }); } diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java index 8dd2882..74c2748 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java @@ -27,7 +27,7 @@ public class RestApiAutoConfigurationTest { } @Test - public void fastjson2RestApiEnabled() { + public void givenPropertyRestApiEnable_whenTrue_thenFastjson2Config() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=true") .withUserConfiguration(Fastjson2ApplicationAutoConfiguration.Fastjson2RestApiAutoConfiguration.class) @@ -38,7 +38,7 @@ public class RestApiAutoConfigurationTest { } @Test - public void fastjson2RestApiDisabled() { + public void givenPropertyRestApiEnable_whenFalse_thenFastjson2Config() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=false") .withUserConfiguration(Fastjson2ApplicationAutoConfiguration.Fastjson2RestApiAutoConfiguration.class) @@ -49,7 +49,7 @@ public class RestApiAutoConfigurationTest { } @Test - public void GsonRestApiEnabled() { + public void givenPropertyRestApiEnable_whenTrue_thenGsonConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=true") .withUserConfiguration(GsonApplicationAutoConfiguration.GsonRestApiAutoConfiguration.class) @@ -60,7 +60,7 @@ public class RestApiAutoConfigurationTest { } @Test - public void GsonRestApiDisabled() { + public void givenPropertyRestApiEnable_whenFalse_thenGsonConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=false") .withUserConfiguration(GsonApplicationAutoConfiguration.GsonRestApiAutoConfiguration.class) diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java index 346ffbd..c26df5c 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java @@ -2,6 +2,7 @@ package io.yunjiao.springboot.autoconfigure.apijson.condition; import io.yunjiao.springboot.autoconfigure.test.TestUtils; import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -24,7 +25,7 @@ public class ApllicationConditionTest { private AnnotatedTypeMetadata metadata; @Test - void shouldMatchWhenPropertyMatchesExpectedType_Gson() { + void shouldMatchWhenPropertyMatchesGson() { MockEnvironment env = new MockEnvironment(); env.setProperty(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_APPLICATION, "gson"); @@ -38,7 +39,8 @@ public class ApllicationConditionTest { } @Test - void shouldMatchWhenPropertyMatchesExpectedType_Fastjson2() { + @DisplayName("Fastjson2配置开启") + void shouldMatchWhenPropertyMatchesFastjson2() { MockEnvironment env = new MockEnvironment(); env.setProperty(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_APPLICATION, "fastjson2"); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java index fd81ec4..96516de 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java @@ -2,6 +2,7 @@ package io.yunjiao.springboot.autoconfigure.apijson.condition; import io.yunjiao.springboot.autoconfigure.test.TestUtils; import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -24,7 +25,7 @@ public class NewIdStrategyConditionTest { private AnnotatedTypeMetadata metadata; @Test - void shouldMatchWhenPropertyMatchesExpectedType_Database() { + void shouldMatchWhenPropertyMatchesDatabase() { MockEnvironment env = new MockEnvironment(); env.setProperty(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY, "database"); @@ -38,7 +39,7 @@ public class NewIdStrategyConditionTest { } @Test - void shouldMatchWhenPropertyMatchesExpectedType_Snowflake() { + void shouldMatchWhenPropertyMatchesSnowflake() { MockEnvironment env = new MockEnvironment(); env.setProperty(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY, "snowflake"); @@ -52,7 +53,7 @@ public class NewIdStrategyConditionTest { } @Test - void shouldMatchWhenPropertyMatchesExpectedType_Timestamp() { + void shouldMatchWhenPropertyMatchesTimestamp() { MockEnvironment env = new MockEnvironment(); env.setProperty(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY, "timestamp"); @@ -66,7 +67,7 @@ public class NewIdStrategyConditionTest { } @Test - void shouldMatchWhenPropertyMatchesExpectedType_Uuid() { + void shouldMatchWhenPropertyMatchesUuid() { MockEnvironment env = new MockEnvironment(); env.setProperty(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY, "uuid"); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java index 7b67edc..3f1d6be 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java @@ -24,7 +24,7 @@ public class EnumPropertyConditionTest { private AnnotatedTypeMetadata metadata; @Test - void giveCorrectValue_thenMatched() { + void givenCorrectValue_thenMatched() { // 准备测试环境 MockEnvironment env = new MockEnvironment(); env.setProperty(PROPERTY_NAME, "female"); @@ -43,7 +43,7 @@ public class EnumPropertyConditionTest { } @Test - void giveWrongValue_thenNotMatched() { + void givenWrongValue_thenNotMatched() { // 准备测试环境 MockEnvironment env = new MockEnvironment(); env.setProperty(PROPERTY_NAME, "Female"); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfigurationTest.java index 4250eb1..fec4053 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfigurationTest.java @@ -25,7 +25,7 @@ public class SnowflakeAutoConfigurationTest { } @Test - public void testDisable() { + public void givenPropertySnowflake_whenFalse_thenNotConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_HUTOOL + ".snowflake=false") .run(context -> { @@ -34,7 +34,7 @@ public class SnowflakeAutoConfigurationTest { } @Test - public void testEnable() { + public void givenDefault_thenConfig() { applicationContextRunner .run(context -> { assertThat(context).hasSingleBean(Snowflake.class); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java index 9d13784..f68a2bb 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java @@ -31,10 +31,10 @@ public class QuerydslJPAAutoConfigurationTest { } @Test - public void giveEntityManager_thenExist() { + public void givenEntityManager_thenConfig() { applicationContextRunner .withBean(EntityManager.class, () -> Mockito.mock(EntityManager.class)) - .withUserConfiguration(QuerydslJPAAutoConfigurationTest.ConfigurerConfiguration.class) + .withUserConfiguration(TestConfiguration.class) .run(context -> { assertThat(context).hasSingleBean(JPAQueryFactory.class); assertThat(context).hasSingleBean(JPAQueryFactoryConfigurer.class); @@ -43,7 +43,7 @@ public class QuerydslJPAAutoConfigurationTest { } @Configuration - static class ConfigurerConfiguration { + static class TestConfiguration { @Bean diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java index 0628e15..5eb1417 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java @@ -34,7 +34,7 @@ public class QuerydslSQLAutoConfigurationTest { } @Test - public void giveDataSource_thenExist() { + public void givenDefault_thenConfig() { applicationContextRunner .withBean(DataSource.class, () -> mock(DataSource.class)) .withUserConfiguration(TestConfiguration.class) @@ -49,7 +49,7 @@ public class QuerydslSQLAutoConfigurationTest { } @Test - public void giveH2Template_thenUsed() { + public void givenH2Template_thenConfig() { applicationContextRunner .withBean(DataSource.class, () -> mock(DataSource.class)) .withUserConfiguration(NewSQLTemplateConfiguration.class) diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java index 7731d23..89aedbf 100644 --- a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java +++ b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java @@ -26,7 +26,7 @@ public class GaussianBlurTest { @Test @DisplayName("模糊真实的图片") - void testInputImage() throws IOException { + void testImage() throws IOException { ClassPathResource resource = new ClassPathResource("images/input.png"); BufferedImage originalImage = ImageIO.read(resource.getInputStream()); diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/generator/TimestampIdGeneratorTest.java b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/generator/TimestampIdGeneratorTest.java index 88a69de..62047c3 100644 --- a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/generator/TimestampIdGeneratorTest.java +++ b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/generator/TimestampIdGeneratorTest.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class TimestampIdGeneratorTest { @Test - public void test() { + public void givenSzie_whenParllelGenerator_thenOk() { final int SIZE = 3000; Set idSet = IntStream.range(0, SIZE).parallel().mapToLong(i -> TimestampIdGenerator.next()).boxed() .collect(Collectors.toSet()); diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/QSpecificationTest.java b/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/QSpecificationTest.java index 18b8b68..c45c8f1 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/QSpecificationTest.java +++ b/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/QSpecificationTest.java @@ -18,19 +18,19 @@ import static org.assertj.core.api.Assertions.assertThat; public class QSpecificationTest { @Test - void givenOne_thenAnd() { + void givenOne_whenAnd() { QSpecification spec = name(); assertThat(create(spec).toString()).isEqualTo("users.name = abc"); } @Test - void givenTrue_thenAnd() { + void givenTwo_whenAnd() { QSpecification spec = name().and(age()); assertThat(create(spec).toString()).isEqualTo("users.name = abc && users.age >= 18"); } @Test - void givenThree_thenAnd() { + void givenThree_whenAnd() { QSpecification spec = name().and(age()).and(email()); assertThat(create(spec).toString()).isEqualTo("users.name = abc && users.age >= 18 && users.email = abc@qq.com"); @@ -39,7 +39,7 @@ public class QSpecificationTest { } @Test - void givenNull_thenAnd() { + void givenNull_whenAnd() { QSpecification spec = name().and(nullable()).and(age()).and(email()); assertThat(create(spec).toString()).isEqualTo("users.name = abc && users.age >= 18 && users.email = abc@qq.com"); @@ -48,7 +48,7 @@ public class QSpecificationTest { } @Test - void and() { + void givenNull_whenAndMulti() { QSpecification spec = nullable().and(age(), email(), birthDateEq()); assertThat(create(spec).toString()).isEqualTo("users.age >= 18 && users.email = abc@qq.com && users.birthDate = 1970-01-01"); @@ -57,7 +57,7 @@ public class QSpecificationTest { } @Test - void andNot() { + void whenAndNot() { QSpecification spec = name().and(age()).andNot(email()); assertThat(create(spec).toString()).isEqualTo("users.name = abc && users.age >= 18 && !(users.email = abc@qq.com)"); @@ -75,19 +75,19 @@ public class QSpecificationTest { } @Test - void andAnyOf() { + void whenAndAnyOf() { QSpecification spec = name().andAnyOf(age(), email()); assertThat(create(spec).toString()).isEqualTo("users.name = abc && (users.age >= 18 || users.email = abc@qq.com)"); } @Test - void orAllOf() { + void whenOrAllOf() { QSpecification spec = name().orAllOf(age(), email()); assertThat(create(spec).toString()).isEqualTo("users.name = abc || users.age >= 18 && users.email = abc@qq.com"); } @Test - void not() { + void whenNot() { QSpecification spec = QSpecification.not(name()); assertThat(create(spec).toString()).isEqualTo("!(users.name = abc)"); @@ -108,7 +108,7 @@ public class QSpecificationTest { } @Test - void or() { + void whenOr() { QSpecification spec = name(); assertThat(create(spec).toString()).isEqualTo("users.name = abc"); @@ -123,7 +123,7 @@ public class QSpecificationTest { } @Test - void orNot() { + void whenOrNot() { QSpecification spec = name().orNot(age()).orNot(email()).and(birthDateEq()); assertThat(create(spec).toString()).isEqualTo("(users.name = abc || !(users.age >= 18) || !(users.email = abc@qq.com)) && users.birthDate = 1970-01-01"); @@ -135,7 +135,7 @@ public class QSpecificationTest { } @Test - void orAnd() { + void whenOrAnd() { QSpecification spec = name().or(age()).and(email()); assertThat(create(spec).toString()).isEqualTo("(users.name = abc || users.age >= 18) && users.email = abc@qq.com"); @@ -153,7 +153,7 @@ public class QSpecificationTest { } @Test - void where() { + void whenWhere() { QSpecification spec = QSpecification.where(name()); assertThat(create(spec).toString()).isEqualTo("users.name = abc"); diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java b/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java index f487ae4..aa43818 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java +++ b/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java @@ -64,7 +64,7 @@ public class JPAQueryRepositorySupportTest { } @Test - void giveSingleName_whenFindSingleByName_thenOk() { + void givenSingleName_whenFindSingleByName_thenOk() { assertThat(repository.findSingleByName("zhangs")) .isPresent() .hasValueSatisfying(user -> { @@ -73,7 +73,7 @@ public class JPAQueryRepositorySupportTest { } @Test - void giveWrongName_whenFindSingleByName_thenOk() { + void givenWrongName_whenFindSingleByName_thenOk() { assertThat(repository.findSingleByName("lis")).isEmpty(); } diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupportTest.java b/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupportTest.java index d001b3e..60b35b1 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupportTest.java +++ b/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupportTest.java @@ -63,7 +63,7 @@ public class SQLQueryRepositorySupportTest { } @Test - void giveSingleName_whenFindSingleByName_thenOk() { + void givenSingleName_whenFindSingleByName_thenOk() { assertThat(repository.findSingleByName("zhangs")) .isPresent() .hasValueSatisfying(user -> { @@ -72,7 +72,7 @@ public class SQLQueryRepositorySupportTest { } @Test - void giveWrongName_whenFindSingleByName_thenOk() { + void givenWrongName_whenFindSingleByName_thenOk() { assertThat(repository.findSingleByName("lis")).isEmpty(); } -- Gitee From c9e2ddd248270d913ac7022a4d2bca2e2fcce0e8 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 21 Aug 2025 23:16:25 +0800 Subject: [PATCH 07/15] =?UTF-8?q?refactor:=20=E9=A1=B9=E7=9B=AE=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 +- .../hutool/SnowflakeAutoConfiguration.java | 2 +- dependencies/pom.xml | 10 +- doc/hutool/application-all.yml | 3 + examples/README.md | 33 ++ examples/example-apijson-fastjson2/pom.xml | 2 +- examples/example-apijson-gson/pom.xml | 2 +- examples/example-hutool/pom.xml | 2 +- examples/example-querydsl-jpa/pom.xml | 2 +- .../querydsl/jpa/UserJPARepository.java | 4 - examples/example-querydsl-sql/pom.xml | 2 +- extensions/README.md | 218 +++++++++++ extensions/extension-common/README.md | 21 - .../common/algorithm/GaussianBlur.java | 54 ++- extensions/extension-hutool/pom.xml | 4 + pom.xml | 6 +- projects/{rest-query-language => }/README.md | 16 +- projects/rest-query-language/pom.xml | 2 +- .../README.md | 54 --- spring-boot-starter-apijson-gson/README.md | 53 --- spring-boot-starter-hutool/README.md | 45 --- spring-boot-starter-querydsl-jpa/README.md | 364 ----------------- spring-boot-starter-querydsl-sql/README.md | 370 ------------------ starters/README.md | 121 ++++++ starters/pom.xml | 32 ++ .../starter-apijson-fastjson2}/pom.xml | 12 +- .../starter-apijson-gson}/pom.xml | 12 +- .../starter-hutool}/pom.xml | 11 +- .../starter-querydsl-jpa}/pom.xml | 11 +- .../starter-querydsl-sql}/pom.xml | 11 +- 30 files changed, 485 insertions(+), 1014 deletions(-) create mode 100644 doc/hutool/application-all.yml create mode 100644 examples/README.md create mode 100644 extensions/README.md delete mode 100644 extensions/extension-common/README.md rename projects/{rest-query-language => }/README.md (50%) delete mode 100644 spring-boot-starter-apijson-fastjson2/README.md delete mode 100644 spring-boot-starter-apijson-gson/README.md delete mode 100644 spring-boot-starter-hutool/README.md delete mode 100644 spring-boot-starter-querydsl-jpa/README.md delete mode 100644 spring-boot-starter-querydsl-sql/README.md create mode 100644 starters/README.md create mode 100644 starters/pom.xml rename {spring-boot-starter-apijson-fastjson2 => starters/starter-apijson-fastjson2}/pom.xml (74%) rename {spring-boot-starter-apijson-gson => starters/starter-apijson-gson}/pom.xml (75%) rename {spring-boot-starter-hutool => starters/starter-hutool}/pom.xml (69%) rename {spring-boot-starter-querydsl-jpa => starters/starter-querydsl-jpa}/pom.xml (76%) rename {spring-boot-starter-querydsl-sql => starters/starter-querydsl-sql}/pom.xml (77%) diff --git a/README.md b/README.md index b51b70c..746be57 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,14 @@ | 项目 | 说明 | |---------------------------------------|-----------------------------------| -| spring-boot-autoconfigure | 自动配置 | -| spring-boot-dependencies | 项目依赖 | -| spring-boot-projects | 应用 | -| spring-boot-examples | 示例项目 | -| spring-querydsl | QueryDSL 集成 | -| spring-apijson | APIJSON 集成 | -| config | 配置 | -| spring-boot-starter-querydsl-jpa | QueryDSL JPA Spring Boot 启动器 | -| spring-boot-starter-querydsl-sql | QueryDSL SQL Spring Boot 启动器 | -| spring-boot-starter-hutool | Hutool Spring Boot 启动器 | -| spring-boot-starter-apijson-fastjson2 | APIJSON Fastjson2 Spring Boot 启动器 | -| spring-boot-starter-apijson-gson | APIJSON Gson Spring Boot 启动器 | +| autoconfigure | 自动配置 | +| dependencies | 项目依赖 | +| projects | 应用 | +| examples | 示例项目 | +| extensions | 扩展 | +| config | 配置 | +| starters | 启动器 | + ## 如何编译 diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfiguration.java index 8c4cea2..acea727 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfiguration.java @@ -47,7 +47,7 @@ public class SnowflakeAutoConfiguration { final String SNOWFLAKE_DATACENTER_ID = "SNOWFLAKE_DATACENTER_ID"; if (log.isDebugEnabled()) { - log.debug("正在配置雪花算法,默认workerId=1,datacenterId=2。如需支持分布式,请设置系统环境变量:{} 与 {}", SNOWFLAKE_WORKER_ID, SNOWFLAKE_DATACENTER_ID); + log.debug("正在配置雪花算法,默认workerId=1,datacenterId=1。如需支持分布式,请设置系统环境变量:{} 与 {}", SNOWFLAKE_WORKER_ID, SNOWFLAKE_DATACENTER_ID); } long workerId = 1L; diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 4ea64e4..56f780f 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -67,27 +67,27 @@ io.gitee.yunjiao-source - spring-boot-starter-querydsl-jpa + starter-querydsl-jpa ${revision} io.gitee.yunjiao-source - spring-boot-starter-querydsl-sql + starter-querydsl-sql ${revision} io.gitee.yunjiao-source - spring-boot-starter-hutool + starter-hutool ${revision} io.gitee.yunjiao-source - spring-boot-starter-apijson-fastjson2 + starter-apijson-fastjson2 ${revision} io.gitee.yunjiao-source - spring-boot-starter-apijson-gson + starter-apijson-gson ${revision} diff --git a/doc/hutool/application-all.yml b/doc/hutool/application-all.yml new file mode 100644 index 0000000..55ae904 --- /dev/null +++ b/doc/hutool/application-all.yml @@ -0,0 +1,3 @@ +spring: + hutool: + snowflake: true # 默认true \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..ff94873 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,33 @@ +# examples + +示例项目 + +## example-apijson-fastjson2 + +基于`starter-apijson-fastjson2`示例项目。在启动项目之前,需要配置数据库,并运行脚本,具体查看`resources`目录。 +成功启动服务后,就可以访问服务提供的接口。在doc/apijson下,有apifox,postman工具文件。 + +## example-apijson-gson + +基于`starter-apijson-gson`示例项目。在启动项目之前,需要配置数据库,并运行脚本,具体查看`resources`目录。 +成功启动服务后,就可以访问服务提供的接口。在doc/apijson下,有apifox,postman工具文件。 + +** 注意 +在服务启动过程中,会出现异常,这是因为官方提供的gson插件还存在问题。有些接口请求也会出现异常 + +## example-hutool + +基于`starter-hutool`示例项目。 + +## example-querydsl-jpa + +基于`starter-querydsl-jpa`示例项目。需要配置数据库及运行数据库脚本,请查看`resources`目录。 + +## example-querydsl-sql + +基于`starter-querydsl-sql`示例项目。需要配置数据库及运行数据库脚本,请查看`resources`目录。 + + + + + diff --git a/examples/example-apijson-fastjson2/pom.xml b/examples/example-apijson-fastjson2/pom.xml index a620d10..acc6eaf 100644 --- a/examples/example-apijson-fastjson2/pom.xml +++ b/examples/example-apijson-fastjson2/pom.xml @@ -17,7 +17,7 @@ io.gitee.yunjiao-source - spring-boot-starter-apijson-fastjson2 + starter-apijson-fastjson2 diff --git a/examples/example-apijson-gson/pom.xml b/examples/example-apijson-gson/pom.xml index cb1e8f7..9f74d3a 100644 --- a/examples/example-apijson-gson/pom.xml +++ b/examples/example-apijson-gson/pom.xml @@ -17,7 +17,7 @@ io.gitee.yunjiao-source - spring-boot-starter-apijson-gson + starter-apijson-gson diff --git a/examples/example-hutool/pom.xml b/examples/example-hutool/pom.xml index 4c26451..381f29b 100644 --- a/examples/example-hutool/pom.xml +++ b/examples/example-hutool/pom.xml @@ -17,7 +17,7 @@ io.gitee.yunjiao-source - spring-boot-starter-hutool + starter-hutool diff --git a/examples/example-querydsl-jpa/pom.xml b/examples/example-querydsl-jpa/pom.xml index ebae07c..ac9f5d2 100644 --- a/examples/example-querydsl-jpa/pom.xml +++ b/examples/example-querydsl-jpa/pom.xml @@ -17,7 +17,7 @@ io.gitee.yunjiao-source - spring-boot-starter-querydsl-jpa + starter-querydsl-jpa diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPARepository.java b/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPARepository.java index fa6099c..2fbc6c1 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPARepository.java +++ b/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPARepository.java @@ -32,10 +32,6 @@ public class UserJPARepository extends JPAQueryRepositorySupport { private static final QUser qUser = QUser.user; private static final QOrder qOrder = QOrder.order; - @Autowired - private EntityManager entityManager; - - public UserJPARepository() { super(qUser); } diff --git a/examples/example-querydsl-sql/pom.xml b/examples/example-querydsl-sql/pom.xml index 6cf41be..be495c8 100644 --- a/examples/example-querydsl-sql/pom.xml +++ b/examples/example-querydsl-sql/pom.xml @@ -17,7 +17,7 @@ io.gitee.yunjiao-source - spring-boot-starter-querydsl-sql + starter-querydsl-sql diff --git a/extensions/README.md b/extensions/README.md new file mode 100644 index 0000000..daace06 --- /dev/null +++ b/extensions/README.md @@ -0,0 +1,218 @@ +# extensions + +开源的框架扩展,使其有利于`Spring Boot`集成 + +## extension-apijson + +[APIJSON](http://apijson.cn/) 实现实时零代码接口和文档JSON 协议 与 ORM 库 + +如何使用请参考启动器[starter-apijson-fastjson2](../starters/starter-apijson-fastjson2) 与 [starter-apijson-gson](../starters/starter-apijson-gson) + +### IdKeyStrategy接口 + +主键名称策略接口,接口只有一个方法 +```java +String getIdKey(String database, String schema, String datasource, String table); +``` +用于获取主键名称。一般情况下,数据库表的主键名称是`id`,但很多不是这样的,如:`User`表的主键名是`user_id`, `Order`表 +的主键名称是`order_id`,这些都是很正常的,通用的名称。 + +在表主键名称不一样的情况下,需要实现此接口,根据`table`参数判断,返回该表的主键名称。 + +如果表的主键都是一样的,且名称都是`id`,那么你可以直接使用`IdKeyApijsonStrategy`实现类 + +### NewIdStrategy接口 + +主键值策略,接口只有一个方法 + +```java +Serializable newId(RequestMethod method, String database, String schema, String datasource, String table); +``` + +接口有多个实现类 +* NewIdUuidStrategy:uuid主键策略 +* NewIdTimestampStrategy:时间戳主键策略 +* NewIdSnowflakeStrategy:雪花算法主键策略 +* NewIdDatabaseStrategy:数据库主键策略,使用数据库功能生成主键 + + +## extension-hutool + +[Hutool](https://doc.hutool.cn/)是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率, +使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 + +如何使用请参考启动器[starter-hutool](../starters/starter-hutool) + +## extension-querydsl + +[QueryDSL](http://querydsl.com/) 是一个框架,可以构建静态类型的 SQL 类查询。无需将查询编写为内联字符串或将它们外部化为 XML 文件, +它们可以通过 `Querydsl` 之类的流畅 API 构建。 + +如何使用请参考启动器[starter-querydsl-jpa](../starters/starter-querydsl-jpa) 与 [starter-querydsl-sql](../starters/starter-querydsl-sql) + +重要的扩展如下: +### QSpecification接口 + +仿`Spring`框架`Specification`接口,实现条件的组合 +```text +QSpecification spec = name().and(age()).and(email()); +"users.name = abc && users.age >= 18 && users.email = abc@qq.com" + +QSpecification spec = name().and(age()).andNot(email()); +"users.name = abc && users.age >= 18 && !(users.email = abc@qq.com)" + +QSpecification spec = name().andAnyOf(age(), email()); +"users.name = abc && (users.age >= 18 || users.email = abc@qq.com)" +``` +详细功能查看单元测试用例[QSpecificationTest](./extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/QSpecificationTest.java) + +### SQLQueryRepositorySupport抽象类 + +基于SQL的仓库支持,实现了对数据CURD操作,并且每个操作都添加了事务(Transactional)支持 + +* findOnlyOne:查询仅一条记录,如果有两条以上,抛出异常 +* findFirstOne:查询第一条记录 +* findMustOne:查询必须的一条记录,如果不存在记录,抛出异常 +* count:统计记录数 +* exist:是否存在记录 +* findList:列表查询,支持查询条件,排序 +* findTuple:指定字段列表查询,支持查询条件,排序 +* findPage:分页查询,支持查询条件,排序 +* update:更新操作 +* delete:删除操作 + +用户应该继承此类,便拥有以上的全部功能 + +```java + @Repository + static class DemoSQLQueryRepositorySupport extends SQLQueryRepositorySupport { + private final static QUsers user = QUsers.user; + private final static QOrders order = QOrders.order; + + // 主键查询 + public User findById(Long id) { + SQLQuery query = select(Projections.bean(User.class, user.all())) + .from(user) + .where(user.id.eq(id)); + return getCurdExecutor().findMustOne(query); + } + + // 多条件,排序查询 + public List findList(String name, Integer age, Date birthDate) { + SQLQuery query = select(Projections.bean(User.class, user.all())) + .from(user); + QSpecification spec = nameLike(name).and(ageGoe(age), birthDate(birthDate)); + QSort sort = QSort.by(user.age.asc(), user.birthDate.desc()); + return getCurdExecutor().findList(query, spec, sort); + } + + // 分页查询, 使用QPageRequest对象 + public Page findQPage(Integer age) { + SQLQuery query = select(Projections.bean(User.class, user.all())) + .from(user); + QSort sort = QSort.by(user.age.desc()); + return getCurdExecutor().findPage(query, QPageRequest.of(0, 5, sort), ageGoe(age)); + } + + // 分页查询, 使用PageRequest对象 + public Page findPage(Integer age) { + SQLQuery query = select(Projections.bean(User.class, user.all())) + .from(user); + QSort sort = QSort.by(user.age.asc()); + return findPage(query, PageRequest.of(0, 5, sort), ageGoe(age)); + } + + // 关联查询:查询已完成订单的用户信息 + public List findUserByOrderStatus() { + SQLQuery query = select(Projections.bean(User.class, user.all())) + .distinct() + .from(user) + .innerJoin(user.order, order) + .where(order.status.eq("completed")); + return getCurdExecutor().findList(query); + } + } +``` + +详细功能查看单元测试用例[SQLQueryRepositorySupportTest](./extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupportTest.java) + +### JPAQueryRepositorySupport抽象类 + +基于JPA的仓库支持,实现了对数据CURD操作,并且每个操作都添加了事务(Transactional)支持 +* findOnlyOne:查询仅一条记录,如果有两条以上,抛出异常 +* findFirstOne:查询第一条记录 +* findMustOne:查询必须的一条记录,如果不存在记录,抛出异常 +* count:统计记录数 +* exist:是否存在记录 +* findList:列表查询,支持查询条件,排序 +* findTuple:指定字段列表查询,支持查询条件,排序 +* findPage:分页查询,支持查询条件,排序 +* update:更新操作 +* delete:删除操作 + +用户应该继承此类,便拥有以上的全部功能 + +```java + @Repository + static class DemoJPAQueryRepositorySupport extends JPAQueryRepositorySupport { + // 主键查询 + public User findById(Long id) { + JPAQuery query = selectFrom(user) + .where(user.id.eq(id)); + return getCurdExecutor().findMustOne(query); + } + + // 多条件,排序查询 + public List findList(String name, Integer age, Date birthDate) { + JPAQuery query = selectFrom(user); + QSpecification spec = nameLike(name).and(ageGoe(age), birthDate(birthDate)); + QSort sort = QSort.by(user.age.asc(), user.birthDate.desc()); + return getCurdExecutor().findList(query, spec, sort); + } + + // 分页查询, 使用QPageRequest对象 + public Page findQPage(Integer age) { + JPAQuery query = selectFrom(user); + QSort sort = QSort.by(user.age.desc()); + return findPage(query, QPageRequest.of(0, 5, sort), ageGoe(age)); + } + + // 分页查询, 使用PageRequest对象 + public Page findPage(Integer age) { + JPAQuery query = selectFrom(user); + QSort sort = QSort.by(user.age.asc()); + return findPage(query, PageRequest.of(0, 5, sort), ageGoe(age)); + } + + // 关联查询:查询已完成订单的用户信息 + public List findUserByOrderStatus() { + JPAQuery query =selectFrom(user) + .distinct() + .innerJoin(user.orders, order) + .where(order.status.eq("completed")); + return getCurdExecutor().findList(query); + } + } +``` +详细功能查看单元测试用例[JPAQueryRepositorySupportTest](./extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java) + +## extension-common + +通用工具扩展 + +重要的扩展如下: +### TimestampIdGenerator + +当前时间戳的ID生成器,`Long类型`,提供静态方法生成ID。线程安全的,仅用于测试或示例 + +```text + final int SIZE = 3000; + Set idSet = IntStream.range(0, SIZE).parallel().mapToLong(i -> TimestampIdGenerator.next()).boxed() + .collect(Collectors.toSet()); + System.out.println(idSet.stream().skip(SIZE -10).collect(Collectors.toList())); +``` + +输出: +```text +[1755402971097584, 1755402971097585, 1755402971097598, 1755402971097599, 1755402971097596, 1755402971097597, 1755402971097594, 1755402971097595, 1755402971097592, 1755402971097593] +``` diff --git a/extensions/extension-common/README.md b/extensions/extension-common/README.md deleted file mode 100644 index c93e7fe..0000000 --- a/extensions/extension-common/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# spring-common - -通用工具集。 - -## 使用指南 - -### TimestampIdGenerator - -当前时间戳的ID生成器,`Long类型`,提供静态方法生成ID。线程安全的,仅用于测试或示例 - -```text - final int SIZE = 3000; - Set idSet = IntStream.range(0, SIZE).parallel().mapToLong(i -> TimestampIdGenerator.next()).boxed() - .collect(Collectors.toSet()); - System.out.println(idSet.stream().skip(SIZE -10).collect(Collectors.toList())); -``` - -输出: -```text -[1755402971097584, 1755402971097585, 1755402971097598, 1755402971097599, 1755402971097596, 1755402971097597, 1755402971097594, 1755402971097595, 1755402971097592, 1755402971097593] -``` diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java index 8bbb79c..9e82546 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java @@ -8,51 +8,40 @@ import java.awt.image.BufferedImage; * @author wanpinwei */ public final class GaussianBlur { - /** - * 模糊程度:轻度 - */ - public final static int LIGHT = 5; - - /** - * 模糊程度:中度 - */ - public final static int MEDIUM = 8; - - /** - * 模糊程度:重度 - */ - public final static int HEAVY = 11; - /** * 轻度处理 + * * @param image 原始图像 * @return 模糊后的图像 */ public static BufferedImage gaussianBlurLight(BufferedImage image) { - return gaussianBlur(image, LIGHT); + return gaussianBlur(image, 5); } /** * 中度处理 + * * @param image 原始图像 * @return 模糊后的图像 */ public static BufferedImage gaussianBlurMedium(BufferedImage image) { - return gaussianBlur(image, MEDIUM); + return gaussianBlur(image, 8); } /** * 重度处理 + * * @param image 原始图像 * @return 模糊后的图像 */ public static BufferedImage gaussianBlurHeavy(BufferedImage image) { - return gaussianBlur(image, HEAVY); + return gaussianBlur(image, 11); } /** * 高斯模糊算法实现 - * @param image 原始图像 + * + * @param image 原始图像 * @param radius 模糊半径 * @return 模糊后的图像 */ @@ -78,7 +67,10 @@ public final class GaussianBlur { // 水平模糊 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - double r = 0, g = 0, b = 0, a = 0; + double r = 0; + double g = 0; + double b = 0; + double a = 0; for (int i = -kernelRadius; i <= kernelRadius; i++) { int pixelX = Math.min(Math.max(x + i, 0), width - 1); @@ -93,10 +85,10 @@ public final class GaussianBlur { b += weight * (pixel & 0xFF); } - int argb = ((int) a & 0xFF) << 24 | - ((int) r & 0xFF) << 16 | - ((int) g & 0xFF) << 8 | - ((int) b & 0xFF); + int argb = ((int) a & 0xFF) << 24 + | ((int) r & 0xFF) << 16 + | ((int) g & 0xFF) << 8 + | ((int) b & 0xFF); blurredPixels[y * width + x] = argb; } @@ -105,7 +97,10 @@ public final class GaussianBlur { // 垂直模糊 for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - double r = 0, g = 0, b = 0, a = 0; + double r = 0; + double g = 0; + double b = 0; + double a = 0; for (int i = -kernelRadius; i <= kernelRadius; i++) { int pixelY = Math.min(Math.max(y + i, 0), height - 1); @@ -120,10 +115,10 @@ public final class GaussianBlur { b += weight * (pixel & 0xFF); } - int argb = ((int) a & 0xFF) << 24 | - ((int) r & 0xFF) << 16 | - ((int) g & 0xFF) << 8 | - ((int) b & 0xFF); + int argb = ((int) a & 0xFF) << 24 + | ((int) r & 0xFF) << 16 + | ((int) g & 0xFF) << 8 + | ((int) b & 0xFF); result.setRGB(x, y, argb); } @@ -134,6 +129,7 @@ public final class GaussianBlur { /** * 创建高斯核 + * * @param radius 模糊半径 * @return 高斯核数组 */ diff --git a/extensions/extension-hutool/pom.xml b/extensions/extension-hutool/pom.xml index 78ba5f4..b4a308c 100644 --- a/extensions/extension-hutool/pom.xml +++ b/extensions/extension-hutool/pom.xml @@ -15,6 +15,10 @@ Hutool扩展 + + cn.hutool + hutool-core + cn.hutool hutool-captcha diff --git a/pom.xml b/pom.xml index c50c186..89e79ab 100644 --- a/pom.xml +++ b/pom.xml @@ -27,11 +27,7 @@ extensions dependencies autoconfigure - spring-boot-starter-querydsl-sql - spring-boot-starter-querydsl-jpa - spring-boot-starter-hutool - spring-boot-starter-apijson-fastjson2 - spring-boot-starter-apijson-gson + starters diff --git a/projects/rest-query-language/README.md b/projects/README.md similarity index 50% rename from projects/rest-query-language/README.md rename to projects/README.md index 1a12925..3203b1b 100644 --- a/projects/rest-query-language/README.md +++ b/projects/README.md @@ -1,8 +1,15 @@ -# rest-query-language +# projects -[Building a REST Query Language](https://www.baeldung.com/spring-rest-api-query-search-language-tutorial) +很多有意思的应用,都来自网络。 -## 使用指南 +## rest-query-language + +“REST Query Language”通常指的是一种用于对RESTful API的资源进行查询、过滤、排序等操作的语言或语法规范。它的核心目的是让客户端能够更灵活、 +高效地获取他们需要的特定数据,而不是简单地获取服务器返回的完整资源表示。 + +参考文章[Building a REST Query Language](https://www.baeldung.com/spring-rest-api-query-search-language-tutorial) + +使用指南 http://localhost:8080/users/criteria?search=lastName:doe,age%3E25 http://localhost:8080/users/spec?search=lastName:doe,age%3E25 @@ -16,3 +23,6 @@ http://localhost:8080/users/api/querydsl?age=22&age=26 http://localhost:8080/users/spec/adv?search=( firstName:john OR firstName:tom ) AND age%3E22 + + + diff --git a/projects/rest-query-language/pom.xml b/projects/rest-query-language/pom.xml index db062e8..ea1c80d 100644 --- a/projects/rest-query-language/pom.xml +++ b/projects/rest-query-language/pom.xml @@ -17,7 +17,7 @@ io.gitee.yunjiao-source - spring-boot-starter-querydsl-jpa + starter-querydsl-jpa diff --git a/spring-boot-starter-apijson-fastjson2/README.md b/spring-boot-starter-apijson-fastjson2/README.md deleted file mode 100644 index 06c02cc..0000000 --- a/spring-boot-starter-apijson-fastjson2/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# spring-boot-starter-apijson-fastjson2 - - -## 使用指南 - -参考示例项目`spring-boot-examples/spring-boot-example-apijson-fastjson2` - -在pom.xml中添加 - -```xml - - - io.gitee.yunjiao-source - spring-boot-starter-apijson-fastjson2 - ${version} - -``` - -## 所有的配置属性 - -参考[application-all.yaml](../doc/apijson/application-all.yml) - -## 支持的接口 - -| 接口url | 方法 | 说明 | -|-----------------------|------|--------------------------------------------------| -| common/{method} | POST | 支持GET,HEAD,GETS,HEADS,POST,PUT,DELETE,CRUD等 | -| common/{method}/{tag} | POST | 增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率 | -| ext/reload | POST | 重新加载配置 | -| ext/post/verify | POST | 生成验证码 | -| ext/gets/verify | POST | 获取验证码 | -| ext/heads/verify | POST | 校验验证码 | -| ext/login | POST | 用户登录 | -| ext/logout | POST | 退出登录,清空session | -| ext/register | POST | 注册 | -| ext/put/password | POST | 设置密码 | - -请求示例 - -```curl -curl --location --request POST 'http://localhost:8080/api-json/common/get' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "Moment": { - "id": 12 - } -}' -``` - -* 接口默认是开启的,可以关闭这些接口,配置属性 `spring.apijson.rest-api.enable=false` -* 接口添加了统一的前缀,默认是`api-json`,配置属性`spring.apijson.rest-api.prefix=YOURE-PREFIX` -* 在`doc/apijson`目录下有`apifox`及`postman`工具文件,导入json文件 - - diff --git a/spring-boot-starter-apijson-gson/README.md b/spring-boot-starter-apijson-gson/README.md deleted file mode 100644 index d630ac6..0000000 --- a/spring-boot-starter-apijson-gson/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# spring-boot-starter-apijson-gson - - -## 使用指南 - -参考示例项目`spring-boot-examples/spring-boot-example-apijson-gson` - -在pom.xml中添加 - -```xml - - - io.gitee.yunjiao-source - spring-boot-starter-apijson-fastjson2 - ${version} - -``` - -## 所有的配置属性 - -参考[application-all.yaml](../doc/apijson/application-all.yml) - -## 支持的接口 - -| 接口url | 方法 | 说明 | -|-----------------------|------|--------------------------------------------------| -| common/{method} | POST | 支持GET,HEAD,GETS,HEADS,POST,PUT,DELETE,CRUD等 | -| common/{method}/{tag} | POST | 增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率 | -| ext/reload | POST | 重新加载配置 | -| ext/post/verify | POST | 生成验证码 | -| ext/gets/verify | POST | 获取验证码 | -| ext/heads/verify | POST | 校验验证码 | -| ext/login | POST | 用户登录 | -| ext/logout | POST | 退出登录,清空session | -| ext/register | POST | 注册 | -| ext/put/password | POST | 设置密码 | - -请求示例 - -```curl -curl --location --request POST 'http://localhost:8080/api-json/common/get' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "Moment": { - "id": 12 - } -}' -``` - -* 接口默认是开启的,可以关闭这些接口,配置属性 `spring.apijson.rest-api.enable=false` -* 接口添加了统一的前缀,默认是`api-json`,配置属性`spring.apijson.rest-api.prefix=YOURE-PREFIX` -* 在`doc/apijson`目录下有`apifox`及`postman`工具文件,导入json文件 - diff --git a/spring-boot-starter-hutool/README.md b/spring-boot-starter-hutool/README.md deleted file mode 100644 index 32296b9..0000000 --- a/spring-boot-starter-hutool/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# spring-boot-starter-hutool - -基于Hutool框架的自动配置Starter - - -## maven - -在`pom.xml`中添加依赖 - -```xml - - io.gitee.yunjiao-source - spring-boot-starter-hutool - -``` - -## 使用指南 - -### Snowflake(Twitter的Snowflake 算法) - -支持`Long`类型及字符类型。默认workerId=1,datacenterId=2, 如需支持分布式, -请设置系统环境变量:SNOWFLAKE_WORKER_ID 与 SNOWFLAKE_DATACENTER_ID - -自动注入 -```java - @Autowired - private Snowflake snowflake; - - @Override - public void run(String... args) throws Exception { - log.info("Snowflake = {}", snowflake.nextId()); - } -``` - - -关闭配置 - -```yaml -spring: - hutool: - snowflake: false # 默认true -``` - - - diff --git a/spring-boot-starter-querydsl-jpa/README.md b/spring-boot-starter-querydsl-jpa/README.md deleted file mode 100644 index 8122ff1..0000000 --- a/spring-boot-starter-querydsl-jpa/README.md +++ /dev/null @@ -1,364 +0,0 @@ -# spring-boot-starter-querydsl-jpa - -[QueryDSL](http://querydsl.com/) 是一个框架,可以构建静态类型的 SQL 类查询。无需将查询编写为内联字符串或将它们外部化为 XML 文件, -它们可以通过 `Querydsl` 之类的流畅 API 构建。 - -本项目集成`QueryDSL JPA`框架,实现`JPAQueryFactory`实例自动配置,也可以自由的自定义配置。提供`JPAQueryRepositorySupport`抽象类, -让CURD,分页查询更方便。 - - -## 使用指南 - -本指南使用`maven`构建工具 - -### 创建新项目 - -创建一个新项目,在pom.xml中定义: - -```text -...... - - - io.gitee.yunjiao-source - spring-boot-examples - ${你需要的版本} - - -...... - - - - io.gitee.yunjiao-source - spring-boot-starter-querydsl-jpa - - - - com.querydsl - querydsl-apt - jakarta - provided - - - - -...... -``` - -其他的依赖包请按需添加 - -### JPA的实体类 - -创建用户及订单的实体类,是一对多的关系 - -`Order.java` - -```java -@Data -@Entity -@Table(name = "orders") -public class Order { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ToString.Exclude - @ManyToOne(fetch = FetchType.LAZY) // 推荐懒加载 - @JoinColumn(name = "user_id") // 指定外键列名 - private User user; - - private String status; - - private BigDecimal amount; - - @Column(name="transaction_time") - private Date transactionTime; -} - -``` - -`User.java` - -```java -@Data -@Entity -@Table(name = "users") -public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - - private Integer age; - - private String email; - - @Column(name="birth_date") - private Date birthDate; - - @Column(name="created_at") - private Date createdAt; - - @ToString.Exclude - @OneToMany( - mappedBy = "user", // - cascade = CascadeType.ALL, // 级联所有操作(保存/更新/删除) - orphanRemoval = true // 删除孤儿数据(子实体与父实体断开时自动删除) - ) - private List orders = new ArrayList<>(); -} -``` - -创建完成后,执行编译命令`mvn clean compile`后,会在`target/generated-sources`目录 -下生成`QOrder`, `QUser`两个类 - -### 条件查询 - -`Spring boot` 框架中提供`Specification`接口,支持复杂查询,条件复用等功能。本框架中也提供`QSpecification` -接口 - -`UserSpec.java` - -```java -public final class UserSpec { - private static final QUser qUser = QUser.user; - - public static QSpecification idEq(Long id) { - return builder -> qUser.id.eq(id); - } - - public static QSpecification nameLike(String name) { - return builder -> qUser.name.like(name); - } - - public static QSpecification nameEq(String name) { - return builder -> qUser.name.eq(name); - } - - public static QSpecification ageGoe(Integer age) { - return builder -> qUser.age.goe(age); - } - public static QSpecification birthDate(Date birthDate) { - return builder -> qUser.birthDate.goe(birthDate); - } - -} -``` - -为了更方便的使用上面的条件,可以创建一个类,生成条件 - -`UserQuery.java` - -```java -Getter -@Setter -@Accessors(fluent = true, chain = true) -public class UserQuery { - private final QUser qUser = QUser.user; - private String name; - private Integer minAge; - private Boolean orderByCreateAt; - private Boolean orderByAge; - - - public QSpecification buildQSpecification() { - QSpecification spec = QSpecification.where(null); - if (StringUtils.hasText(name)) { - spec = spec.and(nameLike(name)); - } - if (Objects.nonNull(minAge)) { - spec = spec.and(ageGoe(minAge)); - } - return spec; - } - - public QSort buildQsort() { - QSort sort = new QSort(); - if (Objects.nonNull(orderByAge)) { - sort = sort.and(orderBy(orderByAge, qUser.age)); - } - if (Objects.nonNull(orderByCreateAt)) { - sort = sort.and(orderBy(orderByCreateAt, qUser.createdAt)); - } - return sort; - } -} -``` - -### 仓库 - -仓库类需要继承`JPAQueryRepositorySupport` - -`UserJPARepository.java` - -```java -@Repository -public class UserJPARepository extends JPAQueryRepositorySupport { - private static final QUser qUser = QUser.user; - private static final QOrder qOrder = QOrder.order; - - @Autowired - private EntityManager entityManager; - - - public UserJPARepository() { - super(qUser); - } - - // 主键查询 - public User findById(Long id) { - JPAQuery query = selectFrom(qUser) - .where(qUser.id.eq(id)); - return getCurdExecutor().findMustOne(query); - } - - public User findOrderById(Long id) { - JPAQuery query = selectFrom(qUser) - .leftJoin(qUser.orders, qOrder).fetchJoin() - .where(qUser.id.eq(id)); - return getCurdExecutor().findMustOne(query); - } - - // 使用名称查询唯一用户 - public Optional findSingleByName(String name) { - JPAQuery query = selectFrom(qUser); - return getCurdExecutor().findOnlyOne(query, nameEq(name)); - } - - // 多条件,排序查询 - public List findList(String name, Integer age, Date birthDate) { - JPAQuery query = selectFrom(qUser); - QSpecification spec = nameLike(name).and(ageGoe(age), birthDate(birthDate)); - QSort sort = QSort.by(qUser.age.asc(), qUser.birthDate.desc()); - return getCurdExecutor().findList(query, spec, sort); - } - - // 分页查询, 使用QPageRequest对象 - public Page findQPage(Integer age) { - JPAQuery countQuery = select(qUser.count()).from(qUser); - JPAQuery query = selectFrom(qUser); - QSort sort = QSort.by(qUser.age.desc()); - return getCurdExecutor().findPage(countQuery, query, QPageRequest.of(0, 5, sort), ageGoe(age)); - } - - // 分页查询, 使用PageRequest对象 - public Page findPage(Integer age) { - JPAQuery query = selectFrom(qUser); - QSort sort = QSort.by(qUser.age.asc()); - return findPage(query, PageRequest.of(0, 5, sort), ageGoe(age)); - } - - // 关联查询:查询已完成订单的用户信息 - public List findUserByOrderStatus() { - JPAQuery query = selectFrom(qUser) - .distinct() - .innerJoin(qUser.orders, qOrder) - .where(qOrder.status.eq("completed")); - return getCurdExecutor().findList(query); - } - - // 插入数据示例 - public long insertUser(User user) { - return insert(qUser) - .columns(qUser.name, qUser.age) - .values(user.getName(), user.getAge()) - .execute(); - } - - // 更新数据 - public long updateName(Long id, String newName) { - JPAUpdateClause update = update(qUser) - .set(qUser.name, newName); - return getCurdExecutor().update(update, idEq(id)); - } - - // 批量插入 - @Transactional(rollbackFor = Exception.class) - public void insertUserBatch(List users) { - long count = 0; - for (User user : users) { - JPAInsertClause insert = insert(qUser) - .columns(qUser.name, qUser.age); - insert.values(user.getName(), user.getAge()); - count += insert.execute(); - } - System.out.println("插入行数 " + count); - } - - // 模拟大量删除 - @Transactional(rollbackFor = Exception.class) - public void deleteUser(Integer age) { - final long maxLimit = 99L; - - // 查询 - JPAQuery queryClause = select(qUser.id).from(qUser).limit(maxLimit); - getCurdExecutor().applaySpecification(ageGoe(age), queryClause); - - while (true) { - List userIdList = queryClause.fetch(); - if (userIdList.isEmpty()) { - break; - } - - // 删除语句 - JPADeleteClause deleteClause = delete(qUser).where(qUser.id.in(userIdList)); - // 执行 - long count = deleteClause.execute(); - System.out.println("分批删除完成,影响行数: " + count); - if (count < maxLimit) { - break; - } - } - - } - - // 使用查询对象 - public List findByQuery(UserQuery query) { - return getCurdExecutor().findList(selectFrom(qUser), - query.buildQSpecification(), - query.buildQsort()); - } -} -``` - -### 服务 - -`UserJPAService.java` - -```java -@Getter -@Service -@RequiredArgsConstructor -public class UserJPAService { - private final UserJPARepository userJPARepository; - - @Transactional - public void insertUserBatch() { - List userList = IntStream.range(0, 1000).mapToObj(i -> { - User user = new User(); - user.setName("李四-" + i); - user.setAge(102); - - return user; - }).toList(); - userJPARepository.insertUserBatch(userList); - } - - @Transactional - public void deleteUserBatch() { - userJPARepository.deleteUser(101); - } - - public List findByName(String name) { - UserQuery query = new UserQuery().name(name) - .orderByAge(true); - return userJPARepository.findByQuery(query); - } - -} - -``` - -全部代码完成。详细的,可运行的代码请参考示例项目`spring-boot-examples/spring-boot-example-querydsl-jpa` - - diff --git a/spring-boot-starter-querydsl-sql/README.md b/spring-boot-starter-querydsl-sql/README.md deleted file mode 100644 index 5448590..0000000 --- a/spring-boot-starter-querydsl-sql/README.md +++ /dev/null @@ -1,370 +0,0 @@ -# spring-boot-starter-querydsl-sql - -[QueryDSL](http://querydsl.com/) 是一个框架,可以构建静态类型的 SQL 类查询。无需将查询编写为内联字符串或将它们外部化为 XML 文件, -它们可以通过 `Querydsl` 之类的流畅 API 构建。 - -本项目集成`QueryDSL SQL`框架,实现`SQLQueryFactory`实例自动配置,也可以自由的自定义配置。提供`SQLQueryRepositorySupport`抽象类, -让CURD,分页查询更方便。 - - -## 使用指南 - -本指南使用`maven`构建工具 - -### 创建新项目 - -创建一个新项目,在pom.xml中定义: - -```text -...... - - - io.gitee.yunjiao-source - spring-boot-examples - ${你需要的版本} - - -...... - - - - io.gitee.yunjiao-source - spring-boot-starter-querydsl-sql - - - - com.querydsl - querydsl-sql-codegen - provided - - - - -...... - - - - - com.querydsl - querydsl-maven-plugin - - - - export - - - - - - com.mysql.cj.jdbc.Driver - jdbc:mysql://localhost:3306/yunjiao?userSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8 - system - system - io.yunjiao.example.querydsl - ${project.basedir}/target/generated-sources/java - - - - - com.mysql - mysql-connector-j - 9.2.0 - - - - - - -...... -``` - -其他的依赖包请按需添加 - -### 自定义SQLQueryFactory - -创建一个配置Bean -```java - @Bean - SQLQueryFactoryConfigurer sqlQueryFactoryConfigurer() { - return sqlQueryFactory -> { - com.querydsl.sql.Configuration config = sqlQueryFactory.getConfiguration(); - log.info("配置:Configuration={}", config); - - SQLTemplates template = config.getTemplates(); - log.info("配置:SQLTemplates={}", template); - }; - } -``` - -### 实体类 - -创建用户及订单的实体类,是一对多的关系 - -`User.java` - -```java -@Data -public class User { - private Long id; - private String name; - private Integer age; - private String email; - private String birthData; - private Date createAt; -} - -``` - -创建完成后,执行编译命令`mvn clean compile`后,会在`target/generated-sources`目录 -下生成`QOrder`, `QUser`两个类。 - -注意:数据库中的所有表都会被创建Q类 - -### 条件查询 - -`Spring boot` 框架中提供`Specification`接口,支持复杂查询,条件复用等功能。本框架中也提供`QSpecification` -接口 - -`UserSpec.java` - -```java -public final class UserSpec { - private static final QUser qUser = QUser.user; - - public static QSpecification idEq(Long id) { - return builder -> qUser.id.eq(id); - } - - public static QSpecification nameLike(String name) { - return builder -> qUser.name.like(name); - } - - public static QSpecification nameEq(String name) { - return builder -> qUser.name.eq(name); - } - - public static QSpecification ageGoe(Integer age) { - return builder -> qUser.age.goe(age); - } - public static QSpecification birthDate(Date birthDate) { - return builder -> qUser.birthDate.goe(birthDate); - } - -} -``` - -为了更方便的使用上面的条件,可以创建一个类,生成条件 - -`UserQuery.java` - -```java -@Getter -@Setter -@Accessors(fluent = true, chain = true) -public class UserQuery { - private final QUser qUser = QUser.user; - private String name; - private Integer minAge; - private Boolean orderByCreateAt; - private Boolean orderByAge; - - - public QSpecification buildQSpecification() { - QSpecification spec = QSpecification.where(null); - if (StringUtils.hasText(name)) { - spec = spec.and(nameLike(name)); - } - if (Objects.nonNull(minAge)) { - spec = spec.and(ageGoe(minAge)); - } - return spec; - } - - public QSort buildQsort() { - QSort sort = new QSort(); - if (Objects.nonNull(orderByAge)) { - sort = sort.and(orderBy(orderByAge, qUser.age)); - } - if (Objects.nonNull(orderByCreateAt)) { - sort = sort.and(orderBy(orderByCreateAt, qUser.createdAt)); - } - return sort; - } -} - -``` - -### 仓库 - -仓库类需要继承`SQLQueryRepositorySupport` - -`UserSQLRepository.java` - -```java -@Repository -@Transactional -public class UserSQLRepository extends SQLQueryRepositorySupport { - private static final QUser qUser = QUser.user; - private static final QOrder qOrder = QOrder.order; - - - public UserSQLRepository() { - super(qUser); - } - - // 主键查询 - public User findById(Long id) { - SQLQuery query = select(Projections.bean(User.class, qUser.all())) - .from(qUser) - .where(qUser.id.eq(id)); - return getCurdExecutor().findMustOne(query); - } - - - // 使用名称查询唯一用户 - public Optional findSingleByName(String name) { - SQLQuery query = select(Projections.bean(User.class, qUser.all())) - .from(qUser); - return getCurdExecutor().findOnlyOne(query, nameEq(name)); - } - - // 多条件,排序查询 - public List findList(String name, Integer age, Date birthDate) { - SQLQuery query = select(Projections.bean(User.class, qUser.all())) - .from(qUser); - QSpecification spec = nameLike(name).and(ageGoe(age), birthDate(birthDate)); - QSort sort = QSort.by(qUser.age.asc(), qUser.birthDate.desc()); - return getCurdExecutor().findList(query, spec, sort); - } - - // 分页查询, 使用QPageRequest对象 - public Page findQPage(Integer age) { - SQLQuery query = select(Projections.bean(User.class, qUser.all())) - .from(qUser); - QSort sort = QSort.by(qUser.age.desc()); - return getCurdExecutor().findPage(query, QPageRequest.of(0, 5, sort), ageGoe(age)); - } - - // 分页查询, 使用PageRequest对象 - public Page findPage(Integer age) { - SQLQuery query = select(Projections.bean(User.class, qUser.all())) - .from(qUser); - QSort sort = QSort.by(qUser.age.asc()); - return findPage(query, PageRequest.of(0, 5, sort), ageGoe(age)); - } - - // 关联查询:查询已完成订单的用户信息 - public List findUserByOrderStatus() { - SQLQuery query = select(Projections.bean(User.class, qUser.all())) - .distinct() - .from(qUser) - .innerJoin(qUser.order, qOrder) - .where(qOrder.status.eq("completed")); - return getCurdExecutor().findList(query); - } - - // 插入数据示例 - public Long insertUser(User user) { - return insert(qUser) - .set(qUser.name, user.getName()) - .set(qUser.age, user.getAge()) - .executeWithKey(qUser.id); // 返回自增ID - } - - // 更新数据 - public long updateName(Long id, String newName) { - SQLUpdateClause update = update(qUser) - .set(qUser.name, newName); - return getCurdExecutor().update(update, idEq(id)); - } - - // 批量插入 - @Transactional(rollbackFor = Exception.class) - public void insertUserBatch(List users) { - List> batches = Lists.partition(users, 100); - batches.forEach(usersSub -> { - SQLInsertClause insert = insertBatch(qUser); - for (User user : usersSub) { - insert.populate(user) - .addBatch(); - } - - // 执行批处理并获取影响行数 - long batchResults = insert.execute(); - System.out.println("分批插入完成,影响行数: " + batchResults); - }); - } - - // 模拟大量删除 - @Transactional(rollbackFor = Exception.class) - public void deleteUser(Integer age) { - final long maxLimit = 99L; - - // 语句 - SQLDeleteClause delete = delete(qUser).limit(maxLimit); - // 添加条件 - getCurdExecutor().applaySpecification(ageGoe(age), delete); - while (true) { - // 执行 - long count = delete.execute(); - System.out.println("分批删除完成,影响行数: " + count); - if (count < maxLimit) { - break; - } - } - - } - - // 使用查询对象 - public List findByQuery(UserQuery query) { - return getCurdExecutor().findList(select(Projections.bean(User.class, qUser.all())).from(qUser), - query.buildQSpecification(), - query.buildQsort()); - } - - -} -``` - -### 服务 - -`UserSQLService.java` - -```java -@Getter -@Service -@RequiredArgsConstructor -public class UserSQLService { - private final UserSQLRepository userSQLRepository; - - @Transactional - public void insertUserBatch() { - List userList = IntStream.range(0, 1000).mapToObj(i -> { - User user = new User(); - user.setName("李四-" + i); - user.setAge(102); - - return user; - }).toList(); - userSQLRepository.insertUserBatch(userList); - } - - @Transactional - public void deleteUserBatch() { - userSQLRepository.deleteUser(101); - } - - public List findByName(String name) { - UserQuery query = new UserQuery().name(name) - .orderByAge(true); - return userSQLRepository.findByQuery(query); - } - -} - - -``` - -全部代码完成。详细的,可运行的代码请参考示例项目`spring-boot-examples/spring-boot-example-querydsl-sql` - - diff --git a/starters/README.md b/starters/README.md new file mode 100644 index 0000000..ddbbaec --- /dev/null +++ b/starters/README.md @@ -0,0 +1,121 @@ +# Starters + +基于Spring Boot的启动器 + +## starter-apijson-fastjson2 + +集成`APIJSON`框架的启动器,使用`apijson-fastjson2`插件 + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source + starter-apijson-fastjson2 + ${version} + +``` +所有的配置属性参考[application-all.yaml](../doc/apijson/application-all.yml) + +支持接口 + +| 接口url | 方法 | 说明 | +|-----------------------|------|--------------------------------------------------| +| common/{method} | POST | 支持GET,HEAD,GETS,HEADS,POST,PUT,DELETE,CRUD等 | +| common/{method}/{tag} | POST | 增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率 | +| ext/reload | POST | 重新加载配置 | +| ext/post/verify | POST | 生成验证码 | +| ext/gets/verify | POST | 获取验证码 | +| ext/heads/verify | POST | 校验验证码 | +| ext/login | POST | 用户登录 | +| ext/logout | POST | 退出登录,清空session | +| ext/register | POST | 注册 | +| ext/put/password | POST | 设置密码 | + +详细使用参考示例[example-apijson-fastjson2](../examples/example-apijson-fastjson2) + +## starter-apijson-gson + +集成`APIJSON`框架的启动器, 使用`apijson-gson`插件 + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source + starter-apijson-gson + ${version} + +``` + +所有的配置属性参考[application-all.yaml](../doc/apijson/application-all.yml) + +支持的接口 + +| 接口url | 方法 | 说明 | +|-----------------------|------|--------------------------------------------------| +| common/{method} | POST | 支持GET,HEAD,GETS,HEADS,POST,PUT,DELETE,CRUD等 | +| common/{method}/{tag} | POST | 增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率 | +| ext/reload | POST | 重新加载配置 | +| ext/post/verify | POST | 生成验证码 | +| ext/gets/verify | POST | 获取验证码 | +| ext/heads/verify | POST | 校验验证码 | +| ext/login | POST | 用户登录 | +| ext/logout | POST | 退出登录,清空session | +| ext/register | POST | 注册 | +| ext/put/password | POST | 设置密码 | + +详细使用参考示例[example-apijson-gson](../examples/example-apijson-gson) + +## start-hutool + +集成`Hutool`框架的启动器, 所有的配置属性参考[application-all.yaml](../doc/hutool/application-all.yml) + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source + starter-hutool + ${version} + +``` + +已配置的Bean列表 + +* Snowflake: 雪花算法。默认workerId=1,datacenterId=1。如需支持分布式,请设置系统环境变量:SNOWFLAKE_WORKER_ID 与 {SNOWFLAKE_DATACENTER_ID}。使用属性`spring.hutool.snowflak=false`可关闭配置 + +详细使用参考示例[example-hutool](../examples/example-hutool) + +## start-querydsl-jpa + +集成`QueryDSL JPA`框架的启动器。 + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source + starter-querydsl-jpa + ${version} + +``` + +已配置的Bean列表 +* JPAQueryFactory: 查询工厂 + +详细使用参考示例[example-querydsl-jpa](../examples/example-querydsl-jpa) + +## start-querydsl-sql + +集成`QueryDSL SQL`框架的启动器。 + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source + starter-querydsl-sql + ${version} + +``` + +已配置的Bean列表 +* SQLQueryFactory: 查询工厂 + +详细使用参考示例[example-querydsl-sql](../examples/example-querydsl-sql) diff --git a/starters/pom.xml b/starters/pom.xml new file mode 100644 index 0000000..5405810 --- /dev/null +++ b/starters/pom.xml @@ -0,0 +1,32 @@ + + + + io.gitee.yunjiao-source + dependencies + ${revision} + ../dependencies/pom.xml + + 4.0.0 + + starters + pom + Spring Boot:: Starters + Starters启动器 + + + starter-hutool + starter-querydsl-sql + starter-querydsl-jpa + starter-apijson-fastjson2 + starter-apijson-gson + + + + + org.springframework.boot + spring-boot-starter + + + \ No newline at end of file diff --git a/spring-boot-starter-apijson-fastjson2/pom.xml b/starters/starter-apijson-fastjson2/pom.xml similarity index 74% rename from spring-boot-starter-apijson-fastjson2/pom.xml rename to starters/starter-apijson-fastjson2/pom.xml index 6266849..1dba229 100644 --- a/spring-boot-starter-apijson-fastjson2/pom.xml +++ b/starters/starter-apijson-fastjson2/pom.xml @@ -4,23 +4,17 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> io.gitee.yunjiao-source - dependencies + starters ${revision} - ../dependencies/pom.xml 4.0.0 - spring-boot-starter-apijson-fastjson2 + starter-apijson-fastjson2 jar - Spring Boot :: Starter APIJSON Fastjson2 + Starter :: APIJSON Fastjson2 APIJSON Fastjson2 启动器 - - org.springframework.boot - spring-boot-starter - - io.gitee.yunjiao-source autoconfigure diff --git a/spring-boot-starter-apijson-gson/pom.xml b/starters/starter-apijson-gson/pom.xml similarity index 75% rename from spring-boot-starter-apijson-gson/pom.xml rename to starters/starter-apijson-gson/pom.xml index f2fa11e..08b0d0c 100644 --- a/spring-boot-starter-apijson-gson/pom.xml +++ b/starters/starter-apijson-gson/pom.xml @@ -4,23 +4,17 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> io.gitee.yunjiao-source - dependencies + starters ${revision} - ../dependencies/pom.xml 4.0.0 - spring-boot-starter-apijson-gson + starter-apijson-gson jar - Spring Boot :: Starter APIJSON Gson + Starter :: APIJSON Gson APIJSON Gson 启动器 - - org.springframework.boot - spring-boot-starter - - io.gitee.yunjiao-source autoconfigure diff --git a/spring-boot-starter-hutool/pom.xml b/starters/starter-hutool/pom.xml similarity index 69% rename from spring-boot-starter-hutool/pom.xml rename to starters/starter-hutool/pom.xml index d256509..b141d1c 100644 --- a/spring-boot-starter-hutool/pom.xml +++ b/starters/starter-hutool/pom.xml @@ -4,22 +4,17 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> io.gitee.yunjiao-source - dependencies + starters ${revision} - ../dependencies/pom.xml 4.0.0 - spring-boot-starter-hutool + starter-hutool jar - Spring Boot :: Starter Hutool + Starter :: Hutool 基于Hutool框架的 启动器 - - org.springframework.boot - spring-boot-starter - io.gitee.yunjiao-source autoconfigure diff --git a/spring-boot-starter-querydsl-jpa/pom.xml b/starters/starter-querydsl-jpa/pom.xml similarity index 76% rename from spring-boot-starter-querydsl-jpa/pom.xml rename to starters/starter-querydsl-jpa/pom.xml index 0548de7..44d11a6 100644 --- a/spring-boot-starter-querydsl-jpa/pom.xml +++ b/starters/starter-querydsl-jpa/pom.xml @@ -4,22 +4,17 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> io.gitee.yunjiao-source - dependencies + starters ${revision} - ../dependencies/pom.xml 4.0.0 - spring-boot-starter-querydsl-jpa + starter-querydsl-jpa jar - Spring Boot :: Starter QueryDSL JPA + Starter :: QueryDSL JPA QueryDSL JPA 启动器 - - org.springframework.boot - spring-boot-starter - org.springframework.boot spring-boot-starter-data-jpa diff --git a/spring-boot-starter-querydsl-sql/pom.xml b/starters/starter-querydsl-sql/pom.xml similarity index 77% rename from spring-boot-starter-querydsl-sql/pom.xml rename to starters/starter-querydsl-sql/pom.xml index 5ccb3be..1207ac2 100644 --- a/spring-boot-starter-querydsl-sql/pom.xml +++ b/starters/starter-querydsl-sql/pom.xml @@ -4,22 +4,17 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> io.gitee.yunjiao-source - dependencies + starters ${revision} - ../dependencies/pom.xml 4.0.0 - spring-boot-starter-querydsl-sql + starter-querydsl-sql jar - Spring Boot :: Starter QueryDSL SQL + Starter :: QueryDSL SQL QueryDSL SQL 启动器 - - org.springframework.boot - spring-boot-starter - io.gitee.yunjiao-source autoconfigure -- Gitee From ea9c765cf359e7ddffece84736f83ba0135e0a62 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Mon, 25 Aug 2025 16:39:04 +0800 Subject: [PATCH 08/15] feat: starter-captcha --- README.md | 9 +- autoconfigure/pom.xml | 10 + .../apijson/ApijsonAutoConfiguration.java | 15 +- ...ion.java => ApijsonInitConfiguration.java} | 6 +- ...=> Fastjson2ApplicationConfiguration.java} | 19 +- ...java => GsonApplicationConfiguration.java} | 19 +- ...n.java => NewIdStrategyConfiguration.java} | 14 +- .../captcha/CaptchaAutoConfiguration.java | 42 +++ .../captcha/CaptchaServiceFactory.java | 50 ++++ .../captcha/HutoolCaptchaConfiguration.java | 142 ++++++++++ .../captcha/HutoolCaptchaProperties.java | 248 ++++++++++++++++++ .../hutool/HutoolProperties.java | 16 -- .../HutoolIdConfiguration.java} | 18 +- .../IdAutoConfiguration.java} | 18 +- .../querydsl/QueryDSLAutoConfig.java | 4 +- .../util/PropertyNameConsts.java | 23 +- ...ot.autoconfigure.AutoConfiguration.imports | 3 +- .../apijson/ApijsonAutoConfigurationTest.java | 6 +- ...java => ApijsonInitConfigurationTest.java} | 7 +- ...va => NewIdStrategyConfigurationTest.java} | 6 +- .../apijson/RestApiAutoConfigurationTest.java | 8 +- .../condition/ApllicationConditionTest.java | 2 - .../captcha/CaptchaAutoConfigurationTest.java | 34 +++ .../captcha/IdCaptchaConfigurationTest.java | 39 +++ .../IdIdConfigurationTest.java} | 19 +- dependencies/pom.xml | 17 +- doc/hutool/application-all.yml | 3 - .../src/main/resources}/application-all.yml | 0 .../src/main/resources/application-all.yml | 43 +++ examples/example-captcha/pom.xml | 31 +++ .../captcha/CaptchaCommandLineRunner.java | 56 ++++ .../captcha/CaptchaExampleApplication.java | 16 ++ .../src/main/resources/application-all.yml | 69 +++++ .../src/main/resources/application.yml | 6 + .../{example-hutool => example-id}/pom.xml | 8 +- .../example/id/IdCommandLineRunner.java} | 4 +- .../example/id/IdExampleApplication.java} | 6 +- .../src/main/resources/application.yml | 0 examples/pom.xml | 3 +- extensions/README.md | 54 +++- .../io/yunjiao/extension/apjson/_APIJSON.java | 9 + .../pom.xml | 11 +- .../yunjiao/extension/captcha/_Captcha.java | 9 + .../hutool/AbstractCaptchaBuilder.java | 95 +++++++ .../hutool/AbstractCaptchaService.java | 77 ++++++ .../captcha/hutool/CaptchaException.java | 19 ++ .../captcha/hutool/CircleCaptchaBuilder.java | 16 ++ .../captcha/hutool/CircleCaptchaService.java | 49 ++++ .../captcha/hutool/CodeGeneratorType.java | 92 +++++++ .../captcha/hutool/GifCaptchaBuilder.java | 44 ++++ .../captcha/hutool/GifCaptchaService.java | 47 ++++ .../captcha/hutool/LineCaptchaBuilder.java | 15 ++ .../captcha/hutool/LineCaptchaService.java | 49 ++++ .../captcha/hutool/ShearCaptchaBuilder.java | 15 ++ .../captcha/hutool/ShearCaptchaService.java | 49 ++++ .../extension/captcha/CaptchaJFrameDemo.java | 167 ++++++++++++ .../common/algorithm/GaussianBlur.java | 10 +- .../common/captcha/CaptchaCategory.java | 50 ++++ .../extension/common/captcha/CaptchaData.java | 71 +++++ .../common/captcha/CaptchaService.java | 31 +++ .../extension/common/lang/EnumCache.java | 204 ++++++++++++++ .../extension/common/model/ColorType.java | 85 ++++++ .../extension/common/model/FontStyle.java | 34 +++ .../common/model/TransparencyType.java | 79 ++++++ .../common/util/CommonRuntimeException.java | 27 ++ .../algorithm/GaussianBlurJFrameDemo.java | 199 ++++++++++++++ .../common/algorithm/GaussianBlurTest.java | 56 +--- .../extension/common/lang/EnumCacheTest.java | 68 +++++ extensions/extension-id/pom.xml | 27 ++ .../java/io/yunjiao/extension/id/_Id.java | 9 + .../yunjiao/extension/querydsl/_Querydsl.java | 9 + extensions/pom.xml | 3 +- starters/README.md | 28 +- starters/pom.xml | 3 +- starters/starter-captcha/pom.xml | 29 ++ .../{starter-hutool => starter-id}/pom.xml | 10 +- 76 files changed, 2670 insertions(+), 218 deletions(-) rename autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/{ApijsonInitAutoConfiguration.java => ApijsonInitConfiguration.java} (95%) rename autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/{Fastjson2ApplicationAutoConfiguration.java => Fastjson2ApplicationConfiguration.java} (91%) rename autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/{GsonApplicationAutoConfiguration.java => GsonApplicationConfiguration.java} (86%) rename autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/{NewIdStrategyAutoConfiguration.java => NewIdStrategyConfiguration.java} (89%) create mode 100644 autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java create mode 100644 autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java create mode 100644 autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java create mode 100644 autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java delete mode 100644 autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/HutoolProperties.java rename autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/{hutool/SnowflakeAutoConfiguration.java => id/HutoolIdConfiguration.java} (74%) rename autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/{hutool/HutoolAutoConfiguration.java => id/IdAutoConfiguration.java} (51%) rename autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/{ApijsonInitAutoConfigurationTest.java => ApijsonInitConfigurationTest.java} (92%) rename autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/{NewIdStrategyAutoConfigurationTest.java => NewIdStrategyConfigurationTest.java} (95%) create mode 100644 autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java create mode 100644 autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java rename autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/{hutool/SnowflakeAutoConfigurationTest.java => id/IdIdConfigurationTest.java} (50%) delete mode 100644 doc/hutool/application-all.yml rename {doc/apijson => examples/example-apijson-fastjson2/src/main/resources}/application-all.yml (100%) create mode 100644 examples/example-apijson-gson/src/main/resources/application-all.yml create mode 100644 examples/example-captcha/pom.xml create mode 100644 examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaCommandLineRunner.java create mode 100644 examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaExampleApplication.java create mode 100644 examples/example-captcha/src/main/resources/application-all.yml create mode 100644 examples/example-captcha/src/main/resources/application.yml rename examples/{example-hutool => example-id}/pom.xml (81%) rename examples/{example-hutool/src/main/java/io/yunjiao/example/hutool/HutoolCommandLineRunner.java => example-id/src/main/java/io/yunjiao/example/id/IdCommandLineRunner.java} (82%) rename examples/{example-hutool/src/main/java/io/yunjiao/example/hutool/HutoolExampleApplication.java => example-id/src/main/java/io/yunjiao/example/id/IdExampleApplication.java} (63%) rename examples/{example-hutool => example-id}/src/main/resources/application.yml (100%) create mode 100644 extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/_APIJSON.java rename extensions/{extension-hutool => extension-captcha}/pom.xml (74%) create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/_Captcha.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaBuilder.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaService.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CaptchaException.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaBuilder.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaService.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CodeGeneratorType.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaBuilder.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaService.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaBuilder.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaService.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaBuilder.java create mode 100644 extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaService.java create mode 100644 extensions/extension-captcha/src/test/java/io/yunjiao/extension/captcha/CaptchaJFrameDemo.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaCategory.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaService.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/lang/EnumCache.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/ColorType.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/FontStyle.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/TransparencyType.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/util/CommonRuntimeException.java create mode 100644 extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurJFrameDemo.java create mode 100644 extensions/extension-common/src/test/java/io/yunjiao/extension/common/lang/EnumCacheTest.java create mode 100644 extensions/extension-id/pom.xml create mode 100644 extensions/extension-id/src/main/java/io/yunjiao/extension/id/_Id.java create mode 100644 extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/_Querydsl.java create mode 100644 starters/starter-captcha/pom.xml rename starters/{starter-hutool => starter-id}/pom.xml (75%) diff --git a/README.md b/README.md index 746be57..5cc7ba7 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,10 @@ mvn install ## 使用指南 -* spring-boot-starter-querydsl-jpa [使用指南](./spring-boot-starter-querydsl-jpa/README.md) [参考项目](./examples/example-querydsl-jpa) -* spring-boot-starter-querydsl-sql [使用指南](./spring-boot-starter-querydsl-sql/README.md) [参考项目](./examples/example-querydsl-sql) -* spring-boot-starter-hutool [使用指南](./spring-boot-starter-hutool/README.md) [参考项目](./examples/example-hutool) -* spring-boot-starter-apijson-fastjson2 [使用指南](./spring-boot-starter-apijson-fastjson2/README.md) [参考项目](./examples/example-apijson-fastjson2) -* spring-boot-starter-apijson-Gson [使用指南](./spring-boot-starter-apijson-gson/README.md) [参考项目](./examples/example-apijson-gons) +* extensions [使用指南](./extensions/README.md) +* starters [使用指南](./starters/README.md) +* examples [使用指南](./examples/README.md) +* projects [使用指南](./projects/README.md) ## 参考 diff --git a/autoconfigure/pom.xml b/autoconfigure/pom.xml index 05fbad6..c507b49 100644 --- a/autoconfigure/pom.xml +++ b/autoconfigure/pom.xml @@ -42,6 +42,16 @@ extension-querydsl true + + io.gitee.yunjiao-source + extension-captcha + true + + + io.gitee.yunjiao-source + extension-id + true + com.google.guava diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java index 6753291..d633a7d 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java @@ -1,11 +1,10 @@ package io.yunjiao.springboot.autoconfigure.apijson; -import apijson.framework.APIJSONApplication; +import io.yunjiao.extension.apjson._APIJSON; import io.yunjiao.extension.apjson.annotation.ApijsonRest; import io.yunjiao.extension.apjson.orm.IdKeyApijsonStrategy; import io.yunjiao.extension.apjson.orm.IdKeyStrategy; -import io.yunjiao.extension.apjson.orm.NewIdStrategy; import jakarta.annotation.Nonnull; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -26,15 +25,15 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; */ @Slf4j @AutoConfiguration -@ConditionalOnClass({NewIdStrategy.class, APIJSONApplication.class}) +@ConditionalOnClass({_APIJSON.class}) @RequiredArgsConstructor @EnableConfigurationProperties({ApijsonProperties.class, ApijsonSqlProperties.class, ApijsonParserProperties.class, ApijsonVerifierProperties.class}) @Import({ - NewIdStrategyAutoConfiguration.class, - ApijsonInitAutoConfiguration.class, - Fastjson2ApplicationAutoConfiguration.class, - GsonApplicationAutoConfiguration.class + NewIdStrategyConfiguration.class, + ApijsonInitConfiguration.class, + Fastjson2ApplicationConfiguration.class, + GsonApplicationConfiguration.class }) public class ApijsonAutoConfiguration { /** @@ -60,7 +59,7 @@ public class ApijsonAutoConfiguration { IdKeyStrategy idKeyApijsonStrategy() { IdKeyStrategy bean = new IdKeyApijsonStrategy(); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Id Key APIJSON Strategy]"); + log.debug("Configure Bean [Id Key APIJSON Strategy: {}]", bean); } return bean; } diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java similarity index 95% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfiguration.java rename to autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java index 602ce30..cefe1c2 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java @@ -15,14 +15,14 @@ import org.springframework.context.annotation.Configuration; @Slf4j @RequiredArgsConstructor @Configuration(proxyBeanMethods = false) -public class ApijsonInitAutoConfiguration { +public class ApijsonInitConfiguration { /** * {@link PostConstruct} 注解方法 */ @PostConstruct public void postConstruct() { - log.info("Apijson Init AutoConfiguration"); + log.info("Apijson Init Configuration"); } @@ -48,7 +48,7 @@ public class ApijsonInitAutoConfiguration { ApijsonInitializingBean bean = new ApijsonInitializingBean(parserProperties, verifierProperties, sqlProperties, sqlConfigConfigurers,apijsonVerifierConfigurers,apijsonFunctionParserConfigurers); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Apijson Initializing Bean]"); + log.debug("Configure Bean [Apijson Initializing Bean: {}]", bean); } return bean; } diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java similarity index 91% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationAutoConfiguration.java rename to autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java index 748777a..27c176d 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationAutoConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java @@ -1,6 +1,5 @@ package io.yunjiao.springboot.autoconfigure.apijson; -import apijson.fastjson2.APIJSONApplication; import io.yunjiao.extension.apjson.orm.IdKeyStrategy; import io.yunjiao.extension.apjson.orm.NewIdStrategy; import io.yunjiao.springboot.autoconfigure.apijson.condition.ApllicationCondition; @@ -11,7 +10,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -28,17 +26,16 @@ import javax.sql.DataSource; @Slf4j @RequiredArgsConstructor @Configuration(proxyBeanMethods = false) -@AutoConfigureAfter({ApijsonInitAutoConfiguration.class}) +@AutoConfigureAfter({ApijsonInitConfiguration.class}) @Conditional(ApllicationCondition.OnFastjson2.class) -@ConditionalOnClass({APIJSONApplication.class}) -public class Fastjson2ApplicationAutoConfiguration { +public class Fastjson2ApplicationConfiguration { /** * {@link PostConstruct} 注解方法 */ @PostConstruct public void postConstruct() { - log.info("Fastjson2 Application Auto Configuration"); + log.info("Fastjson2 Application Configuration"); } /** @@ -53,7 +50,7 @@ public class Fastjson2ApplicationAutoConfiguration { ApijsonSqlProperties sqlProperties) { Fastjson2Creator bean = new Fastjson2Creator(dataSource, sqlProperties); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Fastjson2 Creator]"); + log.debug("Configure Bean [Fastjson2 Creator: {}]", bean); } return bean; } @@ -70,7 +67,7 @@ public class Fastjson2ApplicationAutoConfiguration { NewIdStrategy newIdStrategy) { Fastjson2SimpleCallback bean = new Fastjson2SimpleCallback(idKeyStrategy, newIdStrategy); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Fastjson2 Simple Callback]"); + log.debug("Configure Bean [Fastjson2 Simple Callback: {}]", bean); } return bean; } @@ -88,7 +85,7 @@ public class Fastjson2ApplicationAutoConfiguration { ApijsonProperties properties) { Fastjson2InitializingBean bean = new Fastjson2InitializingBean(fastjson2SimpleCallback, fastjson2Creator, properties); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Fastjson2 Initializing Bean]"); + log.debug("Configure Bean [Fastjson2 Initializing Bean: {}]", bean); } return bean; } @@ -114,7 +111,7 @@ public class Fastjson2ApplicationAutoConfiguration { Fastjson2RestController fastjson2RestController() { Fastjson2RestController bean = new Fastjson2RestController(properties); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Fastjson2 Rest Controller]"); + log.debug("Configure Bean [Fastjson2 Rest Controller: {}]", bean); } return bean; } @@ -128,7 +125,7 @@ public class Fastjson2ApplicationAutoConfiguration { Fastjson2EXtRestController fastjson2ExtRestController() { Fastjson2EXtRestController bean = new Fastjson2EXtRestController(); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Fastjson2 Rest Ext Controller]"); + log.debug("Configure Bean [Fastjson2 Rest Ext Controller: {}]", bean); } return bean; } diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java similarity index 86% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationAutoConfiguration.java rename to autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java index 2a668da..4bb57e5 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationAutoConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java @@ -1,6 +1,5 @@ package io.yunjiao.springboot.autoconfigure.apijson; -import apijson.gson.APIJSONApplication; import io.yunjiao.extension.apjson.orm.IdKeyStrategy; import io.yunjiao.extension.apjson.orm.NewIdStrategy; import io.yunjiao.springboot.autoconfigure.apijson.condition.ApllicationCondition; @@ -11,7 +10,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -28,17 +26,16 @@ import javax.sql.DataSource; @Slf4j @RequiredArgsConstructor @Configuration(proxyBeanMethods = false) -@AutoConfigureAfter({ApijsonInitAutoConfiguration.class}) +@AutoConfigureAfter({ApijsonInitConfiguration.class}) @Conditional(ApllicationCondition.OnGson.class) -@ConditionalOnClass({APIJSONApplication.class}) -public class GsonApplicationAutoConfiguration { +public class GsonApplicationConfiguration { /** * {@link PostConstruct} 注解方法 */ @PostConstruct public void postConstruct() { - log.info("Gson Application Auto Configuration"); + log.info("Gson Application Configuration"); } /** @@ -53,7 +50,7 @@ public class GsonApplicationAutoConfiguration { ApijsonSqlProperties sqlProperties) { GsonCreator bean = new GsonCreator(dataSource, sqlProperties); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Gson Creator]"); + log.debug("Configure Bean [Gson Creator: {}]", bean); } return bean; } @@ -70,7 +67,7 @@ public class GsonApplicationAutoConfiguration { NewIdStrategy newIdStrategy) { GsonSimpleCallback bean = new GsonSimpleCallback(idKeyStrategy, newIdStrategy); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Gson Simple Callback]"); + log.debug("Configure Bean [Gson Simple Callback: {}]", bean); } return bean; } @@ -88,7 +85,7 @@ public class GsonApplicationAutoConfiguration { ApijsonProperties properties) { GsonInitializingBean bean = new GsonInitializingBean(gsonSimpleCallback, gsonCreator, properties); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Gson Initializing Bean]"); + log.debug("Configure Bean [Gson Initializing Bean: {}]", bean); } return bean; } @@ -109,7 +106,7 @@ public class GsonApplicationAutoConfiguration { GsonRestController gsonRestController() { GsonRestController bean = new GsonRestController(properties); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Gson Rest Controller]"); + log.debug("Configure Bean [Gson Rest Controller: {}]", bean); } return bean; } @@ -118,7 +115,7 @@ public class GsonApplicationAutoConfiguration { GsonEXtRestController gsonEXtRestController() { GsonEXtRestController bean = new GsonEXtRestController(); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Gson Ext Rest Controller]"); + log.debug("Configure Bean [Gson Ext Rest Controller: {}]", bean); } return bean; } diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java similarity index 89% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfiguration.java rename to autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java index 7d1b801..5eb0392 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java @@ -19,14 +19,14 @@ import org.springframework.context.annotation.Configuration; @Slf4j @Configuration(proxyBeanMethods = false) @ConditionalOnClass({NewIdStrategy.class}) -public class NewIdStrategyAutoConfiguration { +public class NewIdStrategyConfiguration { /** * {@link PostConstruct} 注解方法 */ @PostConstruct public void postConstruct() { - log.info("New Id Strategy Auto Configuration"); + log.info("New Id Strategy Configuration"); } @Bean @@ -34,7 +34,7 @@ public class NewIdStrategyAutoConfiguration { NewIdStrategy newIdDatabaseStrategy() { NewIdStrategy bean = new NewIdDatabaseStrategy(); if (log.isDebugEnabled()) { - log.debug("Configure Bean [New Id Database Strategy]"); + log.debug("Configure Bean [New Id Database Strategy: {}]", bean); } return bean; } @@ -44,7 +44,7 @@ public class NewIdStrategyAutoConfiguration { NewIdStrategy newIdSnowflakeStrategy(Snowflake snowflake) { NewIdStrategy bean = new NewIdSnowflakeStrategy(snowflake); if (log.isDebugEnabled()) { - log.debug("Configure Bean [New Id Snowflake Strategy]"); + log.debug("Configure Bean [New Id Snowflake Strategy: {}]", bean); } return bean; } @@ -54,7 +54,7 @@ public class NewIdStrategyAutoConfiguration { NewIdStrategy newIdTimestampStrategy() { NewIdStrategy bean = new NewIdTimestampStrategy(); if (log.isDebugEnabled()) { - log.debug("Configure Bean [New Id Timestamp Strategy]"); + log.debug("Configure Bean [New Id Timestamp Strategy: {}]", bean); } return bean; } @@ -64,7 +64,7 @@ public class NewIdStrategyAutoConfiguration { NewIdStrategy newIdUuidStrategy() { NewIdStrategy bean = new NewIdUuidStrategy(); if (log.isDebugEnabled()) { - log.debug("Configure Bean [New Id UUID Strategy]"); + log.debug("Configure Bean [New Id UUID Strategy: {}]", bean); } return bean; } @@ -75,7 +75,7 @@ public class NewIdStrategyAutoConfiguration { NewIdStrategy newIdCustomStrategy() { NewIdStrategy bean = new NewIdExceptionStrategy(); if (log.isDebugEnabled()) { - log.debug("Configure Bean [New Id Exception Strategy]"); + log.debug("Configure Bean [New Id Exception Strategy: {}]", bean); } return bean; } diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java new file mode 100644 index 0000000..fc8e580 --- /dev/null +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java @@ -0,0 +1,42 @@ +package io.yunjiao.springboot.autoconfigure.captcha; + +import io.yunjiao.extension.captcha._Captcha; +import io.yunjiao.extension.common.captcha.CaptchaService; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import java.util.Map; + +/** + * 验证码 自动配置 + * + * @author yangyunjiao + */ +@Slf4j +@AutoConfiguration +@ConditionalOnClass({_Captcha.class}) +@Import({ + HutoolCaptchaConfiguration.class +}) +public class CaptchaAutoConfiguration { + /** + * {@link PostConstruct} 注解方法 + */ + @PostConstruct + public void postConstruct() { + log.info("Captcha Auto Configuration"); + } + + @Bean + CaptchaServiceFactory captchaServiceFactory(Map services) { + CaptchaServiceFactory factory = new CaptchaServiceFactory(services); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Captcha Service Factory: {}]", factory); + } + return factory; + } +} diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java new file mode 100644 index 0000000..16c67c1 --- /dev/null +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java @@ -0,0 +1,50 @@ +package io.yunjiao.springboot.autoconfigure.captcha; + +import io.yunjiao.extension.captcha.hutool.CaptchaException; +import io.yunjiao.extension.common.captcha.CaptchaCategory; +import io.yunjiao.extension.common.captcha.CaptchaService; +import io.yunjiao.extension.common.lang.EnumCache; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Map; + +/** + * 验证码服务工厂 + * + * @author yangyunjiao + */ +@Getter +@RequiredArgsConstructor +public class CaptchaServiceFactory { + private final Map services; + + /** + * 根据分类代码,查找验证码服务 + * + * @param categoryCode 必须值 + * @return 实例 + */ + public CaptchaService findService(String categoryCode) { + CaptchaCategory category = EnumCache.findByValue(CaptchaCategory.class, categoryCode); + if (category == null) { + throw new CaptchaException("验证码分类代码不存在,代码是:" + categoryCode); + } + return findService(category); + } + + /** + * 根据分类,查找验证码服务 + * + * @param category 分类 + * @return 实例 + */ + public CaptchaService findService(CaptchaCategory category) { + CaptchaService service = services.get(category.getCode()); + if (service == null) { + throw new CaptchaException("根据分类查找验证码服务未找到, 分类是:" + category); + } + + return service; + } +} diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java new file mode 100644 index 0000000..1539805 --- /dev/null +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java @@ -0,0 +1,142 @@ +package io.yunjiao.springboot.autoconfigure.captcha; + +import cn.hutool.captcha.ICaptcha; +import cn.hutool.core.lang.Assert; +import io.yunjiao.extension.captcha.hutool.*; +import io.yunjiao.extension.common.captcha.CaptchaCategory; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.awt.*; + +/** + * Hutool验证码配置 + * + * @author yangyunjiao + */ +@Slf4j +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ICaptcha.class}) +@EnableConfigurationProperties({HutoolCaptchaProperties.class}) +public class HutoolCaptchaConfiguration { + /** + * {@link PostConstruct} 注解方法 + */ + @PostConstruct + public void postConstruct() { + log.info("Hutool Captcha Configuration"); + } + + @Bean(CaptchaCategory.HUTOOL_LINE_CAPTCHA) + LineCaptchaService lineCaptchaService(HutoolCaptchaProperties properties) { + HutoolCaptchaProperties.DrawingOptions options = properties.getLine(); + validate(options); + Font font = createFont(options.getFont()); + + LineCaptchaBuilder lcb = new LineCaptchaBuilder(); + fillBuilder(lcb, options); + + LineCaptchaService service = new LineCaptchaService(lcb); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Line Captcha Service:{}]", service); + } + return service; + } + + @Bean(CaptchaCategory.HUTOOL_CIRCLE_CAPTCHA) + CircleCaptchaService circleCaptchaService(HutoolCaptchaProperties properties) { + HutoolCaptchaProperties.DrawingOptions options = properties.getCircle(); + validate(options); + + CircleCaptchaBuilder ccb = new CircleCaptchaBuilder(); + fillBuilder(ccb, options); + + CircleCaptchaService service = new CircleCaptchaService(ccb); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Circle Captcha Service:{}]", service); + } + return service; + } + + @Bean(CaptchaCategory.HUTOOL_SHEAR_CAPTCHA) + ShearCaptchaService sheareCaptchaService(HutoolCaptchaProperties properties) { + HutoolCaptchaProperties.DrawingOptions options = properties.getShear(); + validate(options); + + ShearCaptchaBuilder scb = new ShearCaptchaBuilder(); + fillBuilder(scb, options); + + ShearCaptchaService service = new ShearCaptchaService(scb); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Shear Captcha Service:{}]", service); + } + return service; + } + + + @Bean(CaptchaCategory.HUTOOL_GIF_CAPTCHA) + GifCaptchaService gifCaptchaService(HutoolCaptchaProperties properties) { + HutoolCaptchaProperties.GifDrawingOptions options = properties.getGif(); + validate(options); + Assert.isTrue(options.getQuality() >= 1 && options.getQuality() <= 20, "验证码配置属性‘quality‘值必须在[1, 20]之间"); + Assert.isTrue(options.getRepeat() >= 0, "验证码配置属性‘repeat‘值必须大于0"); + Assert.isTrue(options.getMinColor() >= 0 && options.getMinColor() <= 255, "验证码配置属性‘minColor‘值必须在[0, 255]之间"); + Assert.isTrue(options.getMaxColor() >= 0 && options.getMaxColor() <= 255, "验证码配置属性‘maxColor‘值必须在[0, 255]之间"); + + GifCaptchaBuilder gcb = new GifCaptchaBuilder(); + fillBuilder(gcb, options); + gcb.setQuality(options.getQuality()); + gcb.setRepeat(options.getRepeat()); + gcb.setMinColor(options.getMinColor()); + gcb.setMaxColor(options.getMaxColor()); + + GifCaptchaService service = new GifCaptchaService(gcb); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Gif Captcha Service:{}]", service); + } + return service; + } + + private void validate(HutoolCaptchaProperties.DrawingOptions drawing) { + Assert.isTrue(drawing.getWidth() > 0, "验证码配置属性‘width‘值必须大于0"); + Assert.isTrue(drawing.getHeight() > 0, "验证码配置属性‘height‘值必须大于0"); + Assert.isTrue(drawing.getInterfereCount() > 0, "验证码配置属性‘interfereCount‘值必须大于0"); + + Float transparency = drawing.getTransparency(); + if (transparency != null) { + Assert.isTrue(drawing.getTransparency() >= 0 && drawing.getTransparency() <= 1, "验证码配置属性‘transparency‘值必须在[0, 1]之间"); + } + Assert.isTrue(drawing.getFuzziness() >= 0 && drawing.getFuzziness() <= 30, "验证码配置属性‘fuzziness‘值必须在[0, 30]之间"); + + + HutoolCaptchaProperties.CodeOptions code = drawing.getCode(); + Assert.isTrue(code.getLength() > 0, "验证码配置属性‘code.length‘值必须大于0"); + + HutoolCaptchaProperties.FontOptions font = drawing.getFont(); + Assert.isTrue(font.getSize() > 0, "验证码配置属性‘font.size‘值必须大于0"); + } + + private void fillBuilder(AbstractCaptchaBuilder builder, HutoolCaptchaProperties.DrawingOptions options) { + Font font = createFont(options.getFont()); + + builder.setWidth(options.getWidth()); + builder.setHeight(options.getHeight()); + builder.setInterfereCount(options.getInterfereCount()); + builder.setBackgroundColor(options.getBackgroundColor()); + builder.setFuzziness(options.getFuzziness()); + builder.setValidIgnoreCase(options.getValidIgnoreCase()); + builder.setFont(font); + + HutoolCaptchaProperties.CodeOptions code = options.getCode(); + builder.setGenerator(code.getGenerator().apply(code.getLength())); + } + + @SuppressWarnings({"all"}) + private Font createFont(HutoolCaptchaProperties.FontOptions options) { + return new Font(options.getName(), options.getStyle().getMapping(), options.getSize()); + } +} diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java new file mode 100644 index 0000000..e52743f --- /dev/null +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java @@ -0,0 +1,248 @@ +package io.yunjiao.springboot.autoconfigure.captcha; + +import io.yunjiao.extension.captcha.hutool.CodeGeneratorType; +import io.yunjiao.extension.common.model.ColorType; +import io.yunjiao.extension.common.model.FontStyle; +import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * Hutool验证码属性 + * + * @author yangyunjiao + */ +@Data +@ConfigurationProperties(prefix = PropertyNameConsts.PROPERTY_PREFIX_CAPTCHA_HUTOOL) +public class HutoolCaptchaProperties { + /** + * 线段干扰的验证码 + */ + @NestedConfigurationProperty + private DrawingOptions line = DrawingOptions.of(new DrawingOptions(), 250, 50, 60, + ColorType.white, null, 2, true, null, FontStyle.plain, 36, + CodeGeneratorType.numAndChar, 5); + + /** + * 圆圈干扰验证码 + */ + @NestedConfigurationProperty + private DrawingOptions circle = DrawingOptions.of(new DrawingOptions(), 250, 50, 30, + ColorType.white, null, 2, true, null, FontStyle.plain, 36, + CodeGeneratorType.numAndChar, 5); + + /** + * 扭曲干扰验证码 + */ + @NestedConfigurationProperty + private DrawingOptions shear = DrawingOptions.of(new DrawingOptions(), 250, 50, 4, + ColorType.white, null, 2, true, null, FontStyle.plain, 36, + CodeGeneratorType.numAndChar, 5); + + /** + * Gif验证码 + */ + @NestedConfigurationProperty + private GifDrawingOptions gif = GifDrawingOptions.of(new GifDrawingOptions(), 250, 50, 10, + ColorType.white, null, 2, true, null, FontStyle.plain, 36, + CodeGeneratorType.numAndChar, 5, 10, 0, 0, 255); + + @Data + @EqualsAndHashCode(callSuper = true) + public static class GifDrawingOptions extends DrawingOptions { + /** + * 量化器取样间隔, 1 ~ 20 值之间 - 默认是10ms + */ + private Integer quality; + + /** + * 帧循环次数,默认是 0, 意味着无限循环 + */ + private Integer repeat; + + /** + * 设置随机颜色时,最小的取色范围 + */ + private Integer minColor; + + /** + * 设置随机颜色时,最大的取色范围 + */ + private Integer maxColor; + + + public static GifDrawingOptions of(GifDrawingOptions options, int width, int height, int interfereCount, + ColorType backgroundColor, Float transparency, Integer fuzziness, Boolean validIgnoreCase, + String fontName, FontStyle fontStyle, Integer fontSize, + CodeGeneratorType generator, int length, + Integer quality, Integer repeat, Integer minColor, Integer maxColor) { + DrawingOptions.of(options, width, height, interfereCount, backgroundColor, transparency, fuzziness, validIgnoreCase, fontName, + fontStyle, fontSize, generator, length); + options.setQuality(quality); + options.setRepeat(repeat); + options.setMinColor(minColor); + options.setMaxColor(maxColor); + return options; + } + } + + /** + * 绘图选项 + */ + @Data + public static class DrawingOptions { + /** + * 图片的宽度 + */ + private Integer width; + + /** + * 图片的高度 + */ + private Integer height; + + /** + * 验证码干扰元素个数(干扰线宽度) + */ + private Integer interfereCount; + + /** + * 背景色 + */ + private ColorType backgroundColor; + + /** + * 文字透明度,取值0~1,1表示不透明 + */ + private Float transparency; + + /** + * 模糊度(0 - 30) + */ + private Integer fuzziness; + + /** + * 校验时忽略大小写 + */ + private Boolean validIgnoreCase; + + /** + * 字体选项 + */ + @NestedConfigurationProperty + private FontOptions font; + + /** + * 码选项 + */ + @NestedConfigurationProperty + private CodeOptions code; + + /** + * 创建实例 + * + * @param options 必须值 + * @param width 必须值 + * @param height 必须值 + * @param interfereCount 必须值 + * @param backgroundColor 必须值 + * @param transparency 必须值 + * @param fuzziness 必须值 + * @param fontName 可以空 + * @param fontStyle 必须值 + * @param fontSize 必须值 + * @param generator 必须值 + * @param length 必须值 + * @return 实例 + */ + public static DrawingOptions of(DrawingOptions options, int width, int height, int interfereCount, + ColorType backgroundColor, Float transparency, Integer fuzziness, Boolean validIgnoreCase, + String fontName, FontStyle fontStyle, Integer fontSize, + CodeGeneratorType generator, int length) { + FontOptions font = FontOptions.of(new FontOptions(), fontName, fontStyle, fontSize); + CodeOptions code = CodeOptions.of(new CodeOptions(), generator, length); + + options.setWidth(width); + options.setHeight(height); + options.setInterfereCount(interfereCount); + options.setBackgroundColor(backgroundColor); + options.setTransparency(transparency); + options.setFuzziness(fuzziness); + options.setValidIgnoreCase(validIgnoreCase); + + options.setFont(font); + options.setCode(code); + return options; + } + } + + /** + * 字体选项 + */ + @Data + public static class FontOptions { + /** + * 字体名称, 为空表示使用系统默认字体 + */ + private String name; + + /** + * 字体风格 + */ + private FontStyle style; + + /** + * 字体大小 + */ + private Integer size; + + /** + * 创建实例 + * + * @param options 必须值 + * @param name 可以空 + * @param style 必须值 + * @param size 必须值 + * @return 实例 + */ + public static FontOptions of(FontOptions options, String name, FontStyle style, Integer size) { + options.setName(name); + options.setStyle(style); + options.setSize(size); + return options; + } + + } + + /** + * 码选项 + */ + @Data + public static class CodeOptions { + /** + * 码生成类型 + */ + private CodeGeneratorType generator; + + /** + * 字符长度 + */ + private Integer length; + + /** + * 创建实例 + * + * @param options 必须值 + * @param generator 必须值 + * @param length 必须值 + * @return 实例 + */ + public static CodeOptions of(CodeOptions options, CodeGeneratorType generator, int length) { + options.setGenerator(generator); + options.setLength(length); + return options; + } + } +} diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/HutoolProperties.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/HutoolProperties.java deleted file mode 100644 index a1d0328..0000000 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/HutoolProperties.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.yunjiao.springboot.autoconfigure.hutool; - -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Hutool 配置属性 - * - * @author yangyunjiao - */ -@Data -@ConfigurationProperties(prefix = PropertyNameConsts.PROPERTY_PREFIX_HUTOOL) -public class HutoolProperties { - private Boolean snowflake; -} diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java similarity index 74% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfiguration.java rename to autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java index acea727..d51a71b 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java @@ -1,12 +1,10 @@ -package io.yunjiao.springboot.autoconfigure.hutool; +package io.yunjiao.springboot.autoconfigure.id; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.util.IdUtil; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -18,14 +16,13 @@ import org.springframework.core.env.Environment; */ @Slf4j @Configuration(proxyBeanMethods = false) -@ConditionalOnClass({Snowflake.class}) -public class SnowflakeAutoConfiguration { +public class HutoolIdConfiguration { /** * {@link PostConstruct} 注解方法 */ @PostConstruct public void postConstruct() { - log.info("Hutool Snowflake Auto Configuration"); + log.info("Hutool Id Auto Configuration"); } /** @@ -36,12 +33,7 @@ public class SnowflakeAutoConfiguration { * @return 实例 */ @Bean - @ConditionalOnProperty( - prefix = PropertyNameConsts.PROPERTY_PREFIX_HUTOOL, - name = "snowflake", - havingValue = "true", - matchIfMissing = true - ) + @ConditionalOnClass({Snowflake.class}) public Snowflake snowflake(Environment env) { final String SNOWFLAKE_WORKER_ID = "SNOWFLAKE_WORKER_ID"; final String SNOWFLAKE_DATACENTER_ID = "SNOWFLAKE_DATACENTER_ID"; @@ -68,7 +60,7 @@ public class SnowflakeAutoConfiguration { Snowflake snowflake = IdUtil.getSnowflake(workerId, datacenterId); if (log.isDebugEnabled()) { - log.debug("Configure Bean [Snowflake], workerId={}, datacenterId={}", workerId, datacenterId); + log.debug("Configure Bean [Snowflake: {}], workerId={}, datacenterId={}", snowflake, workerId, datacenterId); } return snowflake; } diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/HutoolAutoConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java similarity index 51% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/HutoolAutoConfiguration.java rename to autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java index b906dfe..e3f670f 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/hutool/HutoolAutoConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java @@ -1,33 +1,29 @@ -package io.yunjiao.springboot.autoconfigure.hutool; +package io.yunjiao.springboot.autoconfigure.id; -import cn.hutool.core.lang.Snowflake; +import io.yunjiao.extension.id._Id; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Import; /** - * 基于Hutool框架的自动配置 + * 验证码 自动配置 * * @author yangyunjiao */ @Slf4j @AutoConfiguration -@ConditionalOnClass({Snowflake.class}) -@EnableConfigurationProperties({HutoolProperties.class}) +@ConditionalOnClass({_Id.class}) @Import({ - SnowflakeAutoConfiguration.class + HutoolIdConfiguration.class }) -public class HutoolAutoConfiguration { - +public class IdAutoConfiguration { /** * {@link PostConstruct} 注解方法 */ @PostConstruct public void postConstruct() { - log.info("Hutool Auto Configuration"); + log.info("Id Auto Configuration"); } - } diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java index 3971b65..f5244ea 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java @@ -1,6 +1,6 @@ package io.yunjiao.springboot.autoconfigure.querydsl; -import com.querydsl.core.QueryFactory; +import io.yunjiao.extension.querydsl._Querydsl; import io.yunjiao.springboot.autoconfigure.querydsl.jpa.QuerydslJPAAutoConfiguration; import io.yunjiao.springboot.autoconfigure.querydsl.sql.QuerydslSQLAutoConfiguration; import jakarta.annotation.PostConstruct; @@ -17,7 +17,7 @@ import org.springframework.context.annotation.Import; */ @Slf4j @AutoConfiguration(after = DataSourceAutoConfiguration.class) -@ConditionalOnClass({QueryFactory.class}) +@ConditionalOnClass({_Querydsl.class}) @Import({ QuerydslJPAAutoConfiguration.class, QuerydslSQLAutoConfiguration.class diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java index 9fa85f8..46c7c29 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java @@ -17,9 +17,14 @@ public class PropertyNameConsts { public static final String PROPERTY_PREFIX_SPRING = "spring"; /** - * hutool 属性 + * 验证码属性 */ - public static final String PROPERTY_PREFIX_HUTOOL = PROPERTY_PREFIX_SPRING + ".hutool"; + public static final String PROPERTY_PREFIX_CAPTCHA = PROPERTY_PREFIX_SPRING + ".captcha"; + + /** + * 验证码属性 Hutool + */ + public static final String PROPERTY_PREFIX_CAPTCHA_HUTOOL = PROPERTY_PREFIX_CAPTCHA + ".hutool"; // APIJSON @@ -29,37 +34,37 @@ public class PropertyNameConsts { public static final String PROPERTY_PREFIX_APIJSON = PROPERTY_PREFIX_SPRING + ".apijson"; /** - * rest-api 属性 + * apijson rest-api 属性 */ public static final String PROPERTY_PREFIX_APIJSON_RESTAPI = PROPERTY_PREFIX_APIJSON + ".rest-api"; /** - * enabled 属性 + * apijson rest-api enabled 属性 */ public static final String PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE = PROPERTY_PREFIX_APIJSON_RESTAPI + PROPERTY_ENABLED; /** - * sql 属性 + * apijson sql 属性 */ public static final String PROPERTY_PREFIX_APIJSON_SQL = PROPERTY_PREFIX_APIJSON + ".sql"; /** - * parser 属性 + * apijson parser 属性 */ public static final String PROPERTY_PREFIX_APIJSON_PARSER = PROPERTY_PREFIX_APIJSON + ".parser"; /** - * verifier 属性 + * apijson verifier 属性 */ public static final String PROPERTY_PREFIX_APIJSON_VERIFIER = PROPERTY_PREFIX_APIJSON + ".verifier"; /** - * application 属性 + * apijson application 属性 */ public static final String PROPERTY_PREFIX_APIJSON_APPLICATION = PROPERTY_PREFIX_APIJSON + ".application"; /** - * newidstrategy 属性 + * apijson newidstrategy 属性 */ public static final String PROPERTY_PREFIX_APIJSON_NEWIDSTRATEGY = PROPERTY_PREFIX_APIJSON + ".new-id-strategy"; diff --git a/autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 74c3af4..4461435 100644 --- a/autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,4 @@ io.yunjiao.springboot.autoconfigure.querydsl.QueryDSLAutoConfig -io.yunjiao.springboot.autoconfigure.hutool.HutoolAutoConfiguration +io.yunjiao.springboot.autoconfigure.id.IdAutoConfiguration +io.yunjiao.springboot.autoconfigure.captcha.CaptchaAutoConfiguration io.yunjiao.springboot.autoconfigure.apijson.ApijsonAutoConfiguration \ No newline at end of file diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java index 60cf3e1..45d434c 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java @@ -3,20 +3,16 @@ package io.yunjiao.springboot.autoconfigure.apijson; import io.yunjiao.extension.apjson.orm.IdKeyApijsonStrategy; import io.yunjiao.extension.apjson.orm.IdKeyStrategy; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import javax.sql.DataSource; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertInstanceOf; /** - * {@link NewIdStrategyAutoConfiguration}单元测试用例 + * {@link NewIdStrategyConfiguration}单元测试用例 * * @author yangyunjiao */ diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java similarity index 92% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfigurationTest.java rename to autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java index 31e7785..b770af0 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java @@ -1,7 +1,6 @@ package io.yunjiao.springboot.autoconfigure.apijson; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -13,17 +12,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** - * {@link ApijsonInitAutoConfiguration} 单元测试用例 + * {@link ApijsonInitConfiguration} 单元测试用例 * * @author yangyunjiao */ -public class ApijsonInitAutoConfigurationTest { +public class ApijsonInitConfigurationTest { private ApplicationContextRunner applicationContextRunner; @BeforeEach public void setUp() { applicationContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ApijsonInitAutoConfiguration.class, TestConfiguration.class)); + .withConfiguration(AutoConfigurations.of(ApijsonInitConfiguration.class, TestConfiguration.class)); } @Test diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java similarity index 95% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfigurationTest.java rename to autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java index 659b8b8..8fcb8ae 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java @@ -14,17 +14,17 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; /** - * {@link NewIdStrategyAutoConfiguration}单元测试用例 + * {@link NewIdStrategyConfiguration}单元测试用例 * * @author yangyunjiao */ -public class NewIdStrategyAutoConfigurationTest { +public class NewIdStrategyConfigurationTest { private ApplicationContextRunner applicationContextRunner; @BeforeEach public void setUp() { applicationContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(NewIdStrategyAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(NewIdStrategyConfiguration.class)); } @Test diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java index 74c2748..bbfc673 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java @@ -30,7 +30,7 @@ public class RestApiAutoConfigurationTest { public void givenPropertyRestApiEnable_whenTrue_thenFastjson2Config() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=true") - .withUserConfiguration(Fastjson2ApplicationAutoConfiguration.Fastjson2RestApiAutoConfiguration.class) + .withUserConfiguration(Fastjson2ApplicationConfiguration.Fastjson2RestApiAutoConfiguration.class) .run(context -> { assertThat(context).hasSingleBean(Fastjson2RestController.class); assertThat(context).hasSingleBean(Fastjson2EXtRestController.class); @@ -41,7 +41,7 @@ public class RestApiAutoConfigurationTest { public void givenPropertyRestApiEnable_whenFalse_thenFastjson2Config() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=false") - .withUserConfiguration(Fastjson2ApplicationAutoConfiguration.Fastjson2RestApiAutoConfiguration.class) + .withUserConfiguration(Fastjson2ApplicationConfiguration.Fastjson2RestApiAutoConfiguration.class) .run(context -> { assertThat(context).doesNotHaveBean(Fastjson2RestController.class); assertThat(context).doesNotHaveBean(Fastjson2EXtRestController.class); @@ -52,7 +52,7 @@ public class RestApiAutoConfigurationTest { public void givenPropertyRestApiEnable_whenTrue_thenGsonConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=true") - .withUserConfiguration(GsonApplicationAutoConfiguration.GsonRestApiAutoConfiguration.class) + .withUserConfiguration(GsonApplicationConfiguration.GsonRestApiAutoConfiguration.class) .run(context -> { assertThat(context).hasSingleBean(GsonRestController.class); assertThat(context).hasSingleBean(GsonEXtRestController.class); @@ -63,7 +63,7 @@ public class RestApiAutoConfigurationTest { public void givenPropertyRestApiEnable_whenFalse_thenGsonConfig() { applicationContextRunner .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=false") - .withUserConfiguration(GsonApplicationAutoConfiguration.GsonRestApiAutoConfiguration.class) + .withUserConfiguration(GsonApplicationConfiguration.GsonRestApiAutoConfiguration.class) .run(context -> { assertThat(context).doesNotHaveBean(GsonRestController.class); assertThat(context).doesNotHaveBean(GsonEXtRestController.class); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java index c26df5c..c8bad07 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java @@ -2,7 +2,6 @@ package io.yunjiao.springboot.autoconfigure.apijson.condition; import io.yunjiao.springboot.autoconfigure.test.TestUtils; import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -39,7 +38,6 @@ public class ApllicationConditionTest { } @Test - @DisplayName("Fastjson2配置开启") void shouldMatchWhenPropertyMatchesFastjson2() { MockEnvironment env = new MockEnvironment(); env.setProperty(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_APPLICATION, "fastjson2"); diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java new file mode 100644 index 0000000..679340f --- /dev/null +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java @@ -0,0 +1,34 @@ +package io.yunjiao.springboot.autoconfigure.captcha; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link CaptchaAutoConfiguration} 单元测试用例 + * + * @author yangyunjiao + */ +public class CaptchaAutoConfigurationTest { + private ApplicationContextRunner applicationContextRunner; + + @BeforeEach + public void setUp() { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(CaptchaAutoConfiguration.class)); + } + + @Test + public void givenDefault_thenConfig() { + applicationContextRunner + .run(context -> { + assertThat(context).hasSingleBean(CaptchaServiceFactory.class); + + CaptchaServiceFactory factory = context.getBean(CaptchaServiceFactory.class); + assertThat(factory.getServices()).hasSize(4); + }); + } +} diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java new file mode 100644 index 0000000..d3f4a40 --- /dev/null +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java @@ -0,0 +1,39 @@ +package io.yunjiao.springboot.autoconfigure.captcha; + +import io.yunjiao.extension.captcha.hutool.CircleCaptchaService; +import io.yunjiao.extension.captcha.hutool.GifCaptchaService; +import io.yunjiao.extension.captcha.hutool.LineCaptchaService; +import io.yunjiao.extension.captcha.hutool.ShearCaptchaService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link HutoolCaptchaConfiguration} 单元测试用例 + * + * @author yangyunjiao + */ +public class IdCaptchaConfigurationTest { + private ApplicationContextRunner applicationContextRunner; + + @BeforeEach + public void setUp() { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(HutoolCaptchaConfiguration.class)); + } + + @Test + public void givenDefault_thenConfig() { + applicationContextRunner + .run(context -> { + assertThat(context).hasSingleBean(LineCaptchaService.class); + assertThat(context).hasSingleBean(CircleCaptchaService.class); + assertThat(context).hasSingleBean(ShearCaptchaService.class); + assertThat(context).hasSingleBean(GifCaptchaService.class); + + }); + } +} diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java similarity index 50% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfigurationTest.java rename to autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java index fec4053..b00e581 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/hutool/SnowflakeAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java @@ -1,8 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.hutool; +package io.yunjiao.springboot.autoconfigure.id; import cn.hutool.core.lang.Snowflake; -import io.yunjiao.springboot.autoconfigure.hutool.SnowflakeAutoConfiguration; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -11,26 +9,17 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * {@link SnowflakeAutoConfiguration} 单元测试用例 + * {@link HutoolIdConfiguration} 单元测试用例 * * @author yangyunjiao */ -public class SnowflakeAutoConfigurationTest { +public class IdIdConfigurationTest { private ApplicationContextRunner applicationContextRunner; @BeforeEach public void setUp() { applicationContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SnowflakeAutoConfiguration.class)); - } - - @Test - public void givenPropertySnowflake_whenFalse_thenNotConfig() { - applicationContextRunner - .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_HUTOOL + ".snowflake=false") - .run(context -> { - assertThat(context).doesNotHaveBean(Snowflake.class); - }); + .withConfiguration(AutoConfigurations.of(HutoolIdConfiguration.class)); } @Test diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 56f780f..f29007c 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -65,6 +65,16 @@ extension-apijson ${revision} + + io.gitee.yunjiao-source + extension-captcha + ${revision} + + + io.gitee.yunjiao-source + extension-id + ${revision} + io.gitee.yunjiao-source starter-querydsl-jpa @@ -77,7 +87,7 @@ io.gitee.yunjiao-source - starter-hutool + starter-id ${revision} @@ -90,6 +100,11 @@ starter-apijson-gson ${revision} + + io.gitee.yunjiao-source + starter-captcha + ${revision} + diff --git a/doc/hutool/application-all.yml b/doc/hutool/application-all.yml deleted file mode 100644 index 55ae904..0000000 --- a/doc/hutool/application-all.yml +++ /dev/null @@ -1,3 +0,0 @@ -spring: - hutool: - snowflake: true # 默认true \ No newline at end of file diff --git a/doc/apijson/application-all.yml b/examples/example-apijson-fastjson2/src/main/resources/application-all.yml similarity index 100% rename from doc/apijson/application-all.yml rename to examples/example-apijson-fastjson2/src/main/resources/application-all.yml diff --git a/examples/example-apijson-gson/src/main/resources/application-all.yml b/examples/example-apijson-gson/src/main/resources/application-all.yml new file mode 100644 index 0000000..974ae8a --- /dev/null +++ b/examples/example-apijson-gson/src/main/resources/application-all.yml @@ -0,0 +1,43 @@ +# 所有的配置属性 +spring: + apijson: + rest-api: + enable: true # 是否初始化Rest接口,默认true。框架已提供APIJSON统一接口实现 + prefix: api-json # Rest接口前缀,默认api-json。访问接口如:http://localhost:8080/api-json/common/get + application: gson # 集成apijson-fastjson2包 + new-id-strategy: timestamp # 主键生成策略,默认timestamp。支持:database(数据库自增),uuid(uuid字符串), timestamp(当前时间毫秒数),snowflake(雪花算法),custom(用户自定义) + need-verify-login: true # 每次访问Rest接口时是否需要校验登录 + need-verify-role: true # 每次访问Rest接口时是否需要校验角色权限 + need-verify-content: true # 每次访问Rest接口时开启校验请求传参内容 + enable-on-startup: false # 在启动时初始化,如:APIJSONVerifier(校验器)初始化 + shutdown-when-server-error: true # 启动遇到异常时停止 + log-debug: false # 日志 + sql: + config: + enable-column-config: false # 支持 !key 反选字段 和 字段名映射, 默认false + default-database: MYSQL # 默认的数据库类型, 默认MYSQL。支持多种数据库,请参考SQLConfig类定义,注意名称全大写 + default-schema: sys # 默认数据库名/模式,默认sys。 设置含有APIJSON系统表的数据库 + default-catalog: + default-namespace: + version: 5.7.22 # 数据库版本, 默认'5.7.22' + executor: + enable-output-null-column: false # 是否返回 值为null的字段, 默认false + key-raw-list: '@RAW@LIST' + key-vice-item: '@VICE@ITEM' + parser: + function: + parse-arg-value: false # 是否解析参数 key 的对应的值 + enable-remote-function: true # 开启支持远程函数 + enable-script-function: true # 开启支持远程函数中的 JavaScript 脚本形式 + request: + print-request-string-log: false # 是否打印关键的接口请求内容 + print-big-log: false # 打印大数据量日志的标识 + print-request-endtime-log: false # 是否打印关键的接口请求结束时间 + return-stack-trace: true # 控制返回 trace:stack 字段 + start-from1: false # 分页页码是否从 1 开始,默认为从 0 开始 + verifier: + enable-verify-column: true + enable-apijson-router: false + update-must-have-id-condition: true # 为 PUT, DELETE 强制要求必须有 id/id{}/id{}@ 条件 + enable-verify-role: true # 开启校验请求角色权限 + enable-verify-content: true # 开启校验请求传参内容 diff --git a/examples/example-captcha/pom.xml b/examples/example-captcha/pom.xml new file mode 100644 index 0000000..4c990ae --- /dev/null +++ b/examples/example-captcha/pom.xml @@ -0,0 +1,31 @@ + + + + io.gitee.yunjiao-source + examples + ${revision} + + 4.0.0 + + example-captcha + jar + Example :: Captcha + Captcha 示例应用 + + + + io.gitee.yunjiao-source + starter-captcha + + + + org.springframework.boot + spring-boot-starter-web + + + + + + \ No newline at end of file diff --git a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaCommandLineRunner.java b/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaCommandLineRunner.java new file mode 100644 index 0000000..820f187 --- /dev/null +++ b/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaCommandLineRunner.java @@ -0,0 +1,56 @@ +package io.yunjiao.example.captcha; + +import io.yunjiao.extension.common.captcha.CaptchaCategory; +import io.yunjiao.extension.common.captcha.CaptchaData; +import io.yunjiao.extension.common.captcha.CaptchaService; +import io.yunjiao.springboot.autoconfigure.captcha.CaptchaServiceFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * 示例 + * + * @author yangyunjiao + */ +@Slf4j +@Component +public class CaptchaCommandLineRunner implements CommandLineRunner { + @Autowired + private CaptchaServiceFactory factory; + + @Override + public void run(String... args) throws Exception { + CaptchaService circleService = factory.findService(CaptchaCategory.hutoolCircle); + CaptchaData data = circleService.draw(); + saveImage(data); + + CaptchaService lineService = factory.findService(CaptchaCategory.hutoolLine); + data = lineService.draw(); + saveImage(data); + + CaptchaService gifService = factory.findService(CaptchaCategory.hutoolGif); + data = gifService.draw(); + saveImage(data); + + CaptchaService shearService = factory.findService(CaptchaCategory.hutoolShear); + data = shearService.draw(); + saveImage(data); + } + + private void saveImage(CaptchaData data) throws IOException { + Path targetDir = Paths.get("target", "processed-images"); + if (!Files.exists(targetDir)) { + Files.createDirectories(targetDir); + } + + Path filePath = targetDir.resolve(data.getCategory().getCode() + "." + data.getCategory().getExt()); + Files.write(filePath, data.getCaptchaImage()); + } +} diff --git a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaExampleApplication.java b/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaExampleApplication.java new file mode 100644 index 0000000..3a05594 --- /dev/null +++ b/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaExampleApplication.java @@ -0,0 +1,16 @@ +package io.yunjiao.example.captcha; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 程序入口 + * + * @author yangyunjiao + */ +@SpringBootApplication +public class CaptchaExampleApplication { + public static void main(String[] args) { + SpringApplication.run(CaptchaExampleApplication.class, args); + } +} diff --git a/examples/example-captcha/src/main/resources/application-all.yml b/examples/example-captcha/src/main/resources/application-all.yml new file mode 100644 index 0000000..ef67a59 --- /dev/null +++ b/examples/example-captcha/src/main/resources/application-all.yml @@ -0,0 +1,69 @@ +spring: + captcha: + # + hutool: + line: + width: 250 # 图片的宽度, 默认:250 + height: 50 # 图片的高度, 默认: 50 + interfere-count: 60 # 验证码干扰元素个数, 默认:60 + background-color: white # 背景色, 默认:white + #transparency: # 文字透明度,取值0~1,1表示不透明, 默认:null + fuzziness: 2 # 模糊度(0 - 30), 默认:2 + valid-ignore-case: true # 校验时忽略大小写 , 默认:true + font: # 字体 + #name: # 名称, 为空表示使用系统默认字体 ,默认: null + style: plain # 风格, 默认:plain + size: 36 # 大小, 默认:36 + code: # 码 + generator: numandchar # 码生成类型,默认:numandchar + length: 5 # 字符长度, 默认:5 + circle: + width: 250 # 图片的宽度, 默认:250 + height: 50 # 图片的高度, 默认: 50 + interfere-count: 30 # 验证码干扰元素个数, 默认:30 + background-color: white # 背景色, 默认:white + #transparency: # 文字透明度,取值0~1,1表示不透明, 默认:null + fuzziness: 2 # 模糊度(0 - 30), 默认:2 + valid-ignore-case: true # 校验时忽略大小写 , 默认:true + font: # 字体 + #name: # 名称, 为空表示使用系统默认字体 ,默认: null + style: plain # 风格, 默认:plain + size: 36 # 大小, 默认:36 + code: # 码 + generator: numandchar # 码生成类型,默认:numandchar + length: 5 # 字符长度, 默认:5 + shear: + width: 250 # 图片的宽度, 默认:250 + height: 50 # 图片的高度, 默认: 50 + interfere-count: 4 # 验证码干扰线宽度, 默认:4 + background-color: white # 背景色, 默认:white + #transparency: # 文字透明度,取值0~1,1表示不透明, 默认:null + fuzziness: 2 # 模糊度(0 - 30), 默认:2 + valid-ignore-case: true # 校验时忽略大小写 , 默认:true + font: # 字体 + #name: # 名称, 为空表示使用系统默认字体 ,默认: null + style: plain # 风格, 默认:plain + size: 36 # 大小, 默认:36 + code: # 码 + generator: numandchar # 码生成类型,默认:numandchar + length: 5 # 字符长度, 默认:5 + gif: + width: 250 # 图片的宽度, 默认:250 + height: 50 # 图片的高度, 默认: 50 + interfere-count: 10 # 验证码干扰元素个数, 默认:10 + background-color: white # 背景色, 默认:white + #transparency: # 文字透明度,取值0~1,1表示不透明, 默认:null + fuzziness: 2 # 模糊度(0 - 30), 默认:2 + valid-ignore-case: true # 校验时忽略大小写 , 默认:true + font: # 字体 + #name: # 名称, 为空表示使用系统默认字体 ,默认: null + style: plain # 风格, 默认:plain + size: 36 # 大小, 默认:36 + code: # 码 + generator: numandchar # 码生成类型,默认:numandchar + length: 5 # 字符长度, 默认:5 + quality: 10 # 量化器取样间隔, 1 ~ 20 值之间 - 默认是10 + repeat: 0 # 帧循环次数,默认是 0, 意味着无限循环 + min-color: 0 # 设置随机颜色时,最小的取色范围 + max-color: 255 # 设置随机颜色时,最大的取色范围 + diff --git a/examples/example-captcha/src/main/resources/application.yml b/examples/example-captcha/src/main/resources/application.yml new file mode 100644 index 0000000..5700544 --- /dev/null +++ b/examples/example-captcha/src/main/resources/application.yml @@ -0,0 +1,6 @@ +logging: + level: + root: info + org.springframework.boot.autoconfigure: info + io.yunjiao: debug + diff --git a/examples/example-hutool/pom.xml b/examples/example-id/pom.xml similarity index 81% rename from examples/example-hutool/pom.xml rename to examples/example-id/pom.xml index 381f29b..918ce0b 100644 --- a/examples/example-hutool/pom.xml +++ b/examples/example-id/pom.xml @@ -9,15 +9,15 @@ 4.0.0 - example-hutool + example-id jar - Example :: Hutool - Hutool 示例应用 + Example :: Id + Id 示例应用 io.gitee.yunjiao-source - starter-hutool + starter-id diff --git a/examples/example-hutool/src/main/java/io/yunjiao/example/hutool/HutoolCommandLineRunner.java b/examples/example-id/src/main/java/io/yunjiao/example/id/IdCommandLineRunner.java similarity index 82% rename from examples/example-hutool/src/main/java/io/yunjiao/example/hutool/HutoolCommandLineRunner.java rename to examples/example-id/src/main/java/io/yunjiao/example/id/IdCommandLineRunner.java index a05ee39..ecd2bd0 100644 --- a/examples/example-hutool/src/main/java/io/yunjiao/example/hutool/HutoolCommandLineRunner.java +++ b/examples/example-id/src/main/java/io/yunjiao/example/id/IdCommandLineRunner.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.hutool; +package io.yunjiao.example.id; import cn.hutool.core.lang.Snowflake; import lombok.extern.slf4j.Slf4j; @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; */ @Slf4j @Component -public class HutoolCommandLineRunner implements CommandLineRunner { +public class IdCommandLineRunner implements CommandLineRunner { @Autowired private Snowflake snowflake; diff --git a/examples/example-hutool/src/main/java/io/yunjiao/example/hutool/HutoolExampleApplication.java b/examples/example-id/src/main/java/io/yunjiao/example/id/IdExampleApplication.java similarity index 63% rename from examples/example-hutool/src/main/java/io/yunjiao/example/hutool/HutoolExampleApplication.java rename to examples/example-id/src/main/java/io/yunjiao/example/id/IdExampleApplication.java index ae10978..6672e4c 100644 --- a/examples/example-hutool/src/main/java/io/yunjiao/example/hutool/HutoolExampleApplication.java +++ b/examples/example-id/src/main/java/io/yunjiao/example/id/IdExampleApplication.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.hutool; +package io.yunjiao.example.id; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -9,8 +9,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; * @author yangyunjiao */ @SpringBootApplication -public class HutoolExampleApplication { +public class IdExampleApplication { public static void main(String[] args) { - SpringApplication.run(HutoolExampleApplication.class, args); + SpringApplication.run(IdExampleApplication.class, args); } } diff --git a/examples/example-hutool/src/main/resources/application.yml b/examples/example-id/src/main/resources/application.yml similarity index 100% rename from examples/example-hutool/src/main/resources/application.yml rename to examples/example-id/src/main/resources/application.yml diff --git a/examples/pom.xml b/examples/pom.xml index 23d9ab7..76b2318 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -17,11 +17,12 @@ 示例项目 - example-hutool + example-id example-querydsl-sql example-querydsl-jpa example-apijson-fastjson2 example-apijson-gson + example-captcha diff --git a/extensions/README.md b/extensions/README.md index daace06..29c53e3 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -36,12 +36,11 @@ Serializable newId(RequestMethod method, String database, String schema, String * NewIdDatabaseStrategy:数据库主键策略,使用数据库功能生成主键 -## extension-hutool +## extension-id -[Hutool](https://doc.hutool.cn/)是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率, -使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 +ID扩展,集成`Hutool`框架中`SnowFlake`类。 -如何使用请参考启动器[starter-hutool](../starters/starter-hutool) +如何使用请参考启动器[starter-id](../starters/starter-id) ## extension-querydsl @@ -200,7 +199,6 @@ QSpecification spec = name().andAnyOf(age(), email()); 通用工具扩展 -重要的扩展如下: ### TimestampIdGenerator 当前时间戳的ID生成器,`Long类型`,提供静态方法生成ID。线程安全的,仅用于测试或示例 @@ -216,3 +214,49 @@ QSpecification spec = name().andAnyOf(age(), email()); ```text [1755402971097584, 1755402971097585, 1755402971097598, 1755402971097599, 1755402971097596, 1755402971097597, 1755402971097594, 1755402971097595, 1755402971097592, 1755402971097593] ``` + +### GaussianBlur + +高斯模糊算法,用于图片,可以设置模糊程度 + +### EnumCache + +枚举缓存,实现高效的枚举转换,如:名称转枚举,代码转枚举等 + +在定义枚举时 +```text +enum StatusEnum { + ...... + + static { + // 通过名称构建缓存,通过EnumCache.findByName(StatusEnum.class,"SUCCESS",null);调用能获取枚举 + EnumCache.registerByName(StatusEnum.class, StatusEnum.values()); + // 通过code构建缓存,通过EnumCache.findByValue(StatusEnum.class,"S",null);调用能获取枚举 + EnumCache.registerByValue(StatusEnum.class, StatusEnum.values(), StatusEnum::getCode); + } + } +``` + +### CaptchaService + +验证码服务接口,主要功能包括验证码生成,校验等 + +* CaptchaData draw() : 验证码生成 +* boolean verify(Object orignalCode, Object userCode):验证码校验 +* CaptchaCategory getCategory():分类 + + + +## extension-captcha + +验证码扩展,集成Hutool-captcha框架 + +如何使用请参考启动器[starter-captcha](../starters/starter-captcha) + +集成Hutool框架验证码 +* CircleCaptchaService: 线段干扰的验证码 +* GifCaptchaService: 圆圈干扰验证码 +* LineCaptchaService: gif验证码 +* ShearCaptchaService: 扭曲干扰验证码 + + diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/_APIJSON.java b/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/_APIJSON.java new file mode 100644 index 0000000..98abc48 --- /dev/null +++ b/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/_APIJSON.java @@ -0,0 +1,9 @@ +package io.yunjiao.extension.apjson; + +/** + * _APIJSON + * + * @author yangyunjiao + */ +public class _APIJSON { +} diff --git a/extensions/extension-hutool/pom.xml b/extensions/extension-captcha/pom.xml similarity index 74% rename from extensions/extension-hutool/pom.xml rename to extensions/extension-captcha/pom.xml index b4a308c..7333f16 100644 --- a/extensions/extension-hutool/pom.xml +++ b/extensions/extension-captcha/pom.xml @@ -9,20 +9,19 @@ 4.0.0 - extension-hutool + extension-captcha jar - Extension :: Hutool - Hutool扩展 + Extension :: Captcha + 验证码扩展 - cn.hutool - hutool-core + io.gitee.yunjiao-source + extension-common cn.hutool hutool-captcha - provided \ No newline at end of file diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/_Captcha.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/_Captcha.java new file mode 100644 index 0000000..f0fec34 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/_Captcha.java @@ -0,0 +1,9 @@ +package io.yunjiao.extension.captcha; + +/** + * _Captcha + * + * @author yangyunjiao + */ +public class _Captcha { +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaBuilder.java new file mode 100644 index 0000000..e54d4f0 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaBuilder.java @@ -0,0 +1,95 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.AbstractCaptcha; +import cn.hutool.captcha.generator.CodeGenerator; +import io.yunjiao.extension.common.model.ColorType; +import lombok.Getter; +import lombok.Setter; + +import java.awt.*; + +/** + * 抽象的验证码创建者 + * + * @author yangyunjiao + */ +@Getter +@Setter +public abstract class AbstractCaptchaBuilder { + /** + * 图片的宽度 + */ + private Integer width; + + /** + * 图片的高度 + */ + private Integer height; + + /** + * 验证码干扰元素个数(干扰线宽度) + */ + private Integer interfereCount; + + /** + * 背景色 + */ + private ColorType backgroundColor; + + /** + * 文字透明度,取值0~1,1表示不透明 + */ + private Float transparency; + + /** + * 模糊度(0 - 30) + */ + private Integer fuzziness; + + /** + * 字体 + */ + private Font font; + + /** + * 码生成器 + */ + private CodeGenerator generator; + + /** + * 校验时是否忽略大小写 + */ + private Boolean validIgnoreCase; + + /** + * 创建验证码工具,子类实现 + * + * @return 实例 + */ + protected abstract C createCaptcha(); + + /** + * 填充验证码工具 + * @param captcha 必须值 + */ + protected void fill(AbstractCaptcha captcha) { + captcha.setFont(font); + captcha.setBackground(backgroundColor.getMapping()); + + if (transparency != null) { + captcha.setTextAlpha(transparency); + } + } + + /** + * 创建验证码工具 + * + * @return 实例 + */ + public C build() { + C captcha = createCaptcha(); + fill(captcha); + return captcha; + } + +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaService.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaService.java new file mode 100644 index 0000000..2bc19d0 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaService.java @@ -0,0 +1,77 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.util.IdUtil; +import io.yunjiao.extension.common.algorithm.GaussianBlur; +import io.yunjiao.extension.common.captcha.CaptchaData; +import io.yunjiao.extension.common.captcha.CaptchaService; +import lombok.extern.slf4j.Slf4j; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * 抽象的验证码 服务 + * + * @author yangyunjiao + */ +@Slf4j +public abstract class AbstractCaptchaService implements CaptchaService { + + /** + * 获取 校验时是否忽略大小写 + * @return 校验时是否忽略大小写 + */ + protected abstract Boolean getValidIgnoreCase(); + + protected abstract Integer getFuzziness(); + + protected void handleFuzziness(BufferedImage image) { + Integer fuzziness = getFuzziness(); + if (fuzziness != null && fuzziness > 0) { + image = GaussianBlur.execute(image, fuzziness); + } + } + + protected CaptchaData createCaptchaData(String code, BufferedImage image) { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + ImgUtil.writePng(image, out); + byte[] imageBytes = out.toByteArray(); + + return CaptchaData.builder() + .key(IdUtil.fastSimpleUUID()) + .code(code) + .category(getCategory()) + .captchaImage(imageBytes) + .build(); + } catch (IOException e) { + throw new CaptchaException("生成验证码图片异常", e); + } + + + + } + + @Override + public boolean verify(Object orignalCode, Object userCode) { + if(orignalCode == null || userCode == null) { + return false; + } + + if (orignalCode instanceof String orginalStr + && userCode instanceof String userStr ) { + // 是否忽略大写校验 + Boolean ignoreCase = getValidIgnoreCase(); + if (Boolean.TRUE.equals(ignoreCase)) { + return orginalStr.equalsIgnoreCase(userStr); + } else { + return orginalStr.equals(userStr); + } + } else { + log.error("验证码应该是字符串类型,实际是{}和{}类型", orignalCode.getClass().getSimpleName(), + userCode.getClass().getSimpleName()); + } + return false; + } +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CaptchaException.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CaptchaException.java new file mode 100644 index 0000000..acbe384 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CaptchaException.java @@ -0,0 +1,19 @@ +package io.yunjiao.extension.captcha.hutool; + +import io.yunjiao.extension.common.util.CommonRuntimeException; + +/** + * 验证码异常 + * + * @author yangyunjiao + */ +public class CaptchaException extends CommonRuntimeException { + + public CaptchaException(String message) { + super(message); + } + + public CaptchaException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaBuilder.java new file mode 100644 index 0000000..38615b3 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaBuilder.java @@ -0,0 +1,16 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.CircleCaptcha; + +/** + * 圆圈干扰验证码 创建器 + * + * @author yangyunjiao + */ +public class CircleCaptchaBuilder extends AbstractCaptchaBuilder { + + @Override + protected CircleCaptcha createCaptcha() { + return new CircleCaptcha(getWidth(), getHeight(), getGenerator(), getInterfereCount()); + } +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaService.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaService.java new file mode 100644 index 0000000..567eb68 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaService.java @@ -0,0 +1,49 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.CircleCaptcha; +import io.yunjiao.extension.common.captcha.CaptchaCategory; +import io.yunjiao.extension.common.captcha.CaptchaData; +import lombok.RequiredArgsConstructor; + +import java.awt.image.BufferedImage; + +/** + * 圆圈干扰验证码 服务 + * + * @author yangyunjiao + */ +@RequiredArgsConstructor +public class CircleCaptchaService extends AbstractCaptchaService { + /** + * 创建器 + */ + private final CircleCaptchaBuilder builder; + + @Override + public CaptchaData draw() { + CircleCaptcha captcha = builder.build(); + // 生成码 + String code = captcha.getGenerator().generate(); + // 生成图片 + BufferedImage image = (BufferedImage)captcha.createImage(code); + + handleFuzziness(image); + return createCaptchaData(code, image); + } + + @Override + public CaptchaCategory getCategory() { + return CaptchaCategory.hutoolCircle; + } + + @Override + protected Boolean getValidIgnoreCase() { + return builder.getValidIgnoreCase(); + } + + @Override + protected Integer getFuzziness() { + return builder.getFuzziness(); + } + +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CodeGeneratorType.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CodeGeneratorType.java new file mode 100644 index 0000000..12ada76 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CodeGeneratorType.java @@ -0,0 +1,92 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.captcha.generator.MathGenerator; +import cn.hutool.captcha.generator.RandomGenerator; +import cn.hutool.core.util.RandomUtil; + +import java.util.function.Function; + +/** + * 验证码生成类型。实现{@link Function}接口,创建验证码生成器 + * + * @author yangyunjiao + */ +public enum CodeGeneratorType implements Function { + /** + * 算术验证码,如 2 + 3 = ? + */ + math { + @Override + public CodeGenerator apply(Integer length) { + return new MathGenerator(length); + } + }, + + /** + * 数字+大小写字母 验证码 + */ + numAndChar { + @Override + public CodeGenerator apply(Integer length) { + return new RandomGenerator(length); + } + }, + + /** + * 数字+大写字母 验证码 + */ + numAndUpperChar { + @Override + public CodeGenerator apply(Integer length) { + return new RandomGenerator(RandomUtil.BASE_NUMBER + RandomUtil.BASE_CHAR.toUpperCase(), length); + } + }, + + /** + * 数字+小写字母 验证码 + */ + numAndLowerChar { + @Override + public CodeGenerator apply(Integer length) { + return new RandomGenerator(RandomUtil.BASE_CHAR_NUMBER_LOWER, length); + } + }, + + /** + * 数字 验证码 + */ + num { + @Override + public CodeGenerator apply(Integer length) { + return new RandomGenerator(RandomUtil.BASE_NUMBER, length); + } + }, + + /** + * 大写字母 验证码 + */ + upperChar { + @Override + public CodeGenerator apply(Integer length) { + return new RandomGenerator(RandomUtil.BASE_CHAR.toUpperCase(), length); + } + }, + + /** + * 小写写字母 验证码 + */ + lowerChar { + @Override + public CodeGenerator apply(Integer length) { + return new RandomGenerator(RandomUtil.BASE_CHAR, length); + } + }, + + upperAndLowerChar { + @Override + public CodeGenerator apply(Integer length) { + return new RandomGenerator(RandomUtil.BASE_CHAR + RandomUtil.BASE_CHAR.toUpperCase(), length); + } + } +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaBuilder.java new file mode 100644 index 0000000..99d13d9 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaBuilder.java @@ -0,0 +1,44 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.GifCaptcha; +import lombok.Getter; +import lombok.Setter; + +/** + * Gif验证码 创建器 + * + * @author yangyunjiao + */ +@Getter +@Setter +public class GifCaptchaBuilder extends AbstractCaptchaBuilder { + /** + * 量化器取样间隔, 1 ~ 20 值之间 - 默认是10ms + */ + private Integer quality; + + /** + * 帧循环次数,默认是 0, 意味着无限循环 + */ + private Integer repeat; + + /** + * 设置随机颜色时,最小的取色范围 + */ + private Integer minColor; + + /** + * 设置随机颜色时,最大的取色范围 + */ + private Integer maxColor; + + @Override + protected GifCaptcha createCaptcha() { + GifCaptcha gifCaptcha = new GifCaptcha(getWidth(), getHeight(), getGenerator(), getInterfereCount()); + gifCaptcha.setQuality(quality); + gifCaptcha.setRepeat(repeat); + gifCaptcha.setMaxColor(maxColor); + gifCaptcha.setMinColor(minColor); + return gifCaptcha; + } +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaService.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaService.java new file mode 100644 index 0000000..23011f7 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaService.java @@ -0,0 +1,47 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.GifCaptcha; +import cn.hutool.core.util.IdUtil; +import io.yunjiao.extension.common.captcha.CaptchaCategory; +import io.yunjiao.extension.common.captcha.CaptchaData; +import lombok.RequiredArgsConstructor; + +/** + * gif验证码 服务 + * + * @author yangyunjiao + */ +@RequiredArgsConstructor +public class GifCaptchaService extends AbstractCaptchaService { + /** + * 创建器 + */ + private final GifCaptchaBuilder builder; + + @Override + public CaptchaData draw() { + GifCaptcha captcha = builder.build(); + String code = captcha.getCode(); + return CaptchaData.builder().key(IdUtil.fastSimpleUUID()) + .code(code) + .category(getCategory()) + .captchaImage(captcha.getImageBytes()) + .build(); + } + + @Override + public CaptchaCategory getCategory() { + return CaptchaCategory.hutoolGif; + } + + @Override + protected Boolean getValidIgnoreCase() { + return builder.getValidIgnoreCase(); + } + + @Override + protected Integer getFuzziness() { + return builder.getFuzziness(); + } + +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaBuilder.java new file mode 100644 index 0000000..99191cd --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaBuilder.java @@ -0,0 +1,15 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.LineCaptcha; + +/** + * 线段干扰的验证码 创建器 + * + * @author yangyunjiao + */ +public class LineCaptchaBuilder extends AbstractCaptchaBuilder { + @Override + protected LineCaptcha createCaptcha() { + return new LineCaptcha(getWidth(), getHeight(), getGenerator(), getInterfereCount()); + } +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaService.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaService.java new file mode 100644 index 0000000..cc4fe37 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaService.java @@ -0,0 +1,49 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.LineCaptcha; +import io.yunjiao.extension.common.captcha.CaptchaCategory; +import io.yunjiao.extension.common.captcha.CaptchaData; +import lombok.RequiredArgsConstructor; + +import java.awt.image.BufferedImage; + +/** + * 线段干扰的验证码 服务 + * + * @author yangyunjiao + */ +@RequiredArgsConstructor +public class LineCaptchaService extends AbstractCaptchaService { + /** + * 创建器 + */ + private final LineCaptchaBuilder builder; + + @Override + public CaptchaData draw() { + LineCaptcha captcha = builder.build(); + // 生成码 + String code = captcha.getGenerator().generate(); + // 生成图片 + BufferedImage image = (BufferedImage)captcha.createImage(code); + + handleFuzziness(image); + return createCaptchaData(code, image); + } + + @Override + public CaptchaCategory getCategory() { + return CaptchaCategory.hutoolLine; + } + + @Override + protected Boolean getValidIgnoreCase() { + return builder.getValidIgnoreCase(); + } + + @Override + protected Integer getFuzziness() { + return builder.getFuzziness(); + } + +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaBuilder.java new file mode 100644 index 0000000..03b4d63 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaBuilder.java @@ -0,0 +1,15 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.ShearCaptcha; + +/** + * 扭曲干扰验证码 创建器 + * + * @author yangyunjiao + */ +public class ShearCaptchaBuilder extends AbstractCaptchaBuilder { + @Override + protected ShearCaptcha createCaptcha() { + return new ShearCaptcha(getWidth(), getHeight(), getGenerator(), getInterfereCount()); + } +} diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaService.java b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaService.java new file mode 100644 index 0000000..2150edc --- /dev/null +++ b/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaService.java @@ -0,0 +1,49 @@ +package io.yunjiao.extension.captcha.hutool; + +import cn.hutool.captcha.ShearCaptcha; +import io.yunjiao.extension.common.captcha.CaptchaCategory; +import io.yunjiao.extension.common.captcha.CaptchaData; +import lombok.RequiredArgsConstructor; + +import java.awt.image.BufferedImage; + +/** + * 扭曲干扰验证码 服务 + * + * @author yangyunjiao + */ +@RequiredArgsConstructor +public class ShearCaptchaService extends AbstractCaptchaService { + /** + * 创建器 + */ + private final ShearCaptchaBuilder builder; + + @Override + public CaptchaData draw() { + ShearCaptcha captcha = builder.build(); + // 生成码 + String code = captcha.getGenerator().generate(); + // 生成图片 + BufferedImage image = (BufferedImage)captcha.createImage(code); + + handleFuzziness(image); + return createCaptchaData(code, image); + } + + @Override + public CaptchaCategory getCategory() { + return CaptchaCategory.hutoolShear; + } + + @Override + protected Boolean getValidIgnoreCase() { + return builder.getValidIgnoreCase(); + } + + @Override + protected Integer getFuzziness() { + return builder.getFuzziness(); + } + +} diff --git a/extensions/extension-captcha/src/test/java/io/yunjiao/extension/captcha/CaptchaJFrameDemo.java b/extensions/extension-captcha/src/test/java/io/yunjiao/extension/captcha/CaptchaJFrameDemo.java new file mode 100644 index 0000000..9706c6b --- /dev/null +++ b/extensions/extension-captcha/src/test/java/io/yunjiao/extension/captcha/CaptchaJFrameDemo.java @@ -0,0 +1,167 @@ +package io.yunjiao.extension.captcha; + +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.LineCaptcha; +import io.yunjiao.extension.captcha.hutool.*; +import io.yunjiao.extension.common.captcha.CaptchaData; +import io.yunjiao.extension.common.model.ColorType; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * 可视化界面 + * + * @author yangyunjiao + */ +public class CaptchaJFrameDemo extends JFrame { + private JPanel captchaPanel; + + private JButton generateButton; + + private LineCaptchaService lineCaptchaService; + + private CircleCaptchaService circleCaptchaService; + + private ShearCaptchaService shearCaptchaService; + + private GifCaptchaService gifCaptchaService; + + public CaptchaJFrameDemo() { + setTitle("验证码生成器"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(800, 300); + setLocationRelativeTo(null); + + initUI(); + initService(); + + // 初始生成验证码 + generateCaptchas(); + } + + private void initService() { + Font font = new Font(null, Font.PLAIN, 36); + LineCaptchaBuilder lcb = new LineCaptchaBuilder(); + lcb.setWidth(250); + lcb.setHeight(50); + lcb.setInterfereCount(60); + lcb.setBackgroundColor(ColorType.white); + lcb.setFuzziness(2); + lcb.setValidIgnoreCase(true); + lcb.setFont(font); + lcb.setGenerator(CodeGeneratorType.numAndChar.apply(5)); + lineCaptchaService = new LineCaptchaService(lcb); + + CircleCaptchaBuilder ccb = new CircleCaptchaBuilder(); + ccb.setWidth(250); + ccb.setHeight(50); + ccb.setInterfereCount(30); + ccb.setBackgroundColor(ColorType.white); + ccb.setFuzziness(2); + ccb.setValidIgnoreCase(true); + ccb.setFont(font); + ccb.setGenerator(CodeGeneratorType.numAndChar.apply(5)); + circleCaptchaService = new CircleCaptchaService(ccb); + + ShearCaptchaBuilder scb = new ShearCaptchaBuilder(); + scb.setWidth(250); + scb.setHeight(50); + scb.setInterfereCount(4); + scb.setBackgroundColor(ColorType.white); + scb.setFuzziness(2); + scb.setValidIgnoreCase(true); + scb.setFont(font); + scb.setGenerator(CodeGeneratorType.numAndChar.apply(5)); + + shearCaptchaService = new ShearCaptchaService(scb); + + GifCaptchaBuilder gcb = new GifCaptchaBuilder(); + gcb.setWidth(250); + gcb.setHeight(50); + gcb.setInterfereCount(10); + gcb.setBackgroundColor(ColorType.white); + gcb.setFuzziness(2); + gcb.setValidIgnoreCase(true); + gcb.setFont(font); + gcb.setGenerator(CodeGeneratorType.numAndChar.apply(5)); + gcb.setQuality(10); + gcb.setRepeat(0); + gcb.setMinColor(0); + gcb.setMaxColor(255); + + gifCaptchaService = new GifCaptchaService(gcb); + } + + private void initUI() { + setLayout(new BorderLayout()); + + // 创建验证码显示面板 + captchaPanel = new JPanel(new GridLayout(3, 2, 10, 10)); + captchaPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + add(captchaPanel, BorderLayout.CENTER); + + // 创建按钮面板 + JPanel buttonPanel = new JPanel(); + generateButton = new JButton("生成验证码"); + generateButton.setFont(new Font("Microsoft YaHei", Font.BOLD, 16)); + generateButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + generateCaptchas(); + } + }); + buttonPanel.add(generateButton); + add(buttonPanel, BorderLayout.SOUTH); + } + + private void generateCaptchas() { + captchaPanel.removeAll(); + + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(250, 50); + ImageIcon icon = new ImageIcon(lineCaptcha.getImageBytes()); + + JLabel label = new JLabel(icon); + label.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1)); + captchaPanel.add(label); + + CaptchaData captchaData = lineCaptchaService.draw(); + decodeImage(captchaData); + + captchaData = circleCaptchaService.draw(); + decodeImage(captchaData); + + captchaData = shearCaptchaService.draw(); + decodeImage(captchaData); + + captchaData = gifCaptchaService.draw(); + decodeImage(captchaData); + + captchaPanel.revalidate(); + captchaPanel.repaint(); + } + + private void decodeImage(CaptchaData captchaData) { + ImageIcon icon = new ImageIcon(captchaData.getCaptchaImage()); + + JLabel label = new JLabel(icon); + label.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1)); + captchaPanel.add(label); + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + e.printStackTrace(); + } + new CaptchaJFrameDemo().setVisible(true); + } + }); + } +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java index 9e82546..b4b2e8f 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java @@ -5,7 +5,7 @@ import java.awt.image.BufferedImage; /** * 高斯模糊算法实现 * - * @author wanpinwei + * @author yangyunjiao */ public final class GaussianBlur { /** @@ -15,7 +15,7 @@ public final class GaussianBlur { * @return 模糊后的图像 */ public static BufferedImage gaussianBlurLight(BufferedImage image) { - return gaussianBlur(image, 5); + return execute(image, 5); } /** @@ -25,7 +25,7 @@ public final class GaussianBlur { * @return 模糊后的图像 */ public static BufferedImage gaussianBlurMedium(BufferedImage image) { - return gaussianBlur(image, 8); + return execute(image, 8); } /** @@ -35,7 +35,7 @@ public final class GaussianBlur { * @return 模糊后的图像 */ public static BufferedImage gaussianBlurHeavy(BufferedImage image) { - return gaussianBlur(image, 11); + return execute(image, 11); } /** @@ -45,7 +45,7 @@ public final class GaussianBlur { * @param radius 模糊半径 * @return 模糊后的图像 */ - public static BufferedImage gaussianBlur(BufferedImage image, int radius) { + public static BufferedImage execute(BufferedImage image, int radius) { int width = image.getWidth(); int height = image.getHeight(); diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaCategory.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaCategory.java new file mode 100644 index 0000000..b30b956 --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaCategory.java @@ -0,0 +1,50 @@ +package io.yunjiao.extension.common.captcha; + +import io.yunjiao.extension.common.lang.EnumCache; +import lombok.Getter; +import lombok.ToString; + +/** + * 验证码类别 + * + * @author yangyunjiao + */ +@Getter +@ToString +public enum CaptchaCategory { + hutoolLine(CaptchaCategory.HUTOOL_LINE_CAPTCHA, "png", "Hutool线段干扰验证码"), + hutoolCircle(CaptchaCategory.HUTOOL_CIRCLE_CAPTCHA, "png", "Hutool圆圈干扰验证码"), + hutoolShear(CaptchaCategory.HUTOOL_SHEAR_CAPTCHA, "png", "Hutool扭曲干扰验证码"), + hutoolGif(CaptchaCategory.HUTOOL_GIF_CAPTCHA, "gif", "Hutool GIF验证码"); + + public static final String HUTOOL_LINE_CAPTCHA = "HUTOOL_LINE"; + public static final String HUTOOL_CIRCLE_CAPTCHA = "HUTOOL_CIRCLE"; + public static final String HUTOOL_SHEAR_CAPTCHA = "HUTOOL_SHEAR"; + public static final String HUTOOL_GIF_CAPTCHA = "HUTOOL_GIF"; + + /** + * 代码 + */ + private final String code; + + /** + * 描述 + */ + private final String description; + + /** + * 扩展名 + */ + private final String ext; + + CaptchaCategory(String code, String ext, String description) { + this.code = code; + this.ext = ext; + this.description = description; + } + + static { + EnumCache.registerByName(CaptchaCategory.class, CaptchaCategory.values()); + EnumCache.registerByValue(CaptchaCategory.class, CaptchaCategory.values(), CaptchaCategory::getCode); + } +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java new file mode 100644 index 0000000..08188b1 --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java @@ -0,0 +1,71 @@ +package io.yunjiao.extension.common.captcha; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.io.Serializable; +import java.util.Base64; + +/** + * 验证码数据 + * + * @author yangyunjiao + */ +@Getter +@Builder +@ToString +public class CaptchaData implements Serializable { + /** + * 验证码唯一标识,通常是uuid字符 + */ + private String key; + + /** + * 验证码图片 + */ + private byte[] captchaImage; + + /** + * 背景图片 + */ + private byte[] backgroundImage; + + /** + * 验证码 + */ + private String code; + + /** + * 分类 + */ + private CaptchaCategory category; + + /** + * 转换成图片字符串 + * + * @return 可能空 + */ + public String getCaptchaImageBase64() { + if (captchaImage == null) { + return null; + } + + String base64 = Base64.getEncoder().encodeToString(captchaImage); + return "data:" + category.getExt() + ";base64," + base64; + } + + /** + * 转换成背景图片字符串 + * + * @return 可能空 + */ + public String getBackgroundImage() { + if (backgroundImage == null) { + return null; + } + + String base64 = Base64.getEncoder().encodeToString(backgroundImage); + return "data:" + category.getExt() + ";base64," + base64; + } +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaService.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaService.java new file mode 100644 index 0000000..8d06dec --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaService.java @@ -0,0 +1,31 @@ +package io.yunjiao.extension.common.captcha; + +/** + * 验证码服务接口 + * + * @author yangyunjiao + */ +public interface CaptchaService { + /** + * 验证码绘制 + * + * @return 验证码信息 + */ + CaptchaData draw(); + + /** + * 校验验证码 + * + * @param orignalCode 原始验证码 + * @param userCode 用户输入验证码 + * @return 相同返回true,否则false + */ + boolean verify(Object orignalCode, Object userCode); + + /** + * 获取分类 + * + * @return 验证码类别 + */ + CaptchaCategory getCategory(); +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/lang/EnumCache.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/lang/EnumCache.java new file mode 100644 index 0000000..cb4e88f --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/lang/EnumCache.java @@ -0,0 +1,204 @@ +package io.yunjiao.extension.common.lang; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 枚举缓存 + *

+ *

+ * 参考文章:如何高效优雅的使用java枚举 + * + * @author yangyunjiao + */ +public class EnumCache { + + /** + * 以枚举任意值构建的缓存结构 + **/ + static final Map>, Map>> CACHE_BY_VALUE = new ConcurrentHashMap<>(); + + /** + * 以枚举名称构建的缓存结构 + **/ + static final Map>, Map>> CACHE_BY_NAME = new ConcurrentHashMap<>(); + + /** + * 枚举静态块加载标识缓存结构 + */ + static final Map>, Boolean> LOADED = new ConcurrentHashMap<>(); + + /** + * 锁对象 + */ + private static final Object lock = new Object(); + + /** + * 以枚举名称构建缓存,在枚举的静态块里面调用 + * + * @param clazz 必须值 + * @param values 必须值 + * @param 枚举类型 + */ + public static > void registerByName(Class clazz, E[] values) { + Map> map = new ConcurrentHashMap<>(); + for (E v : values) { + map.put(v.name(), v); + } + CACHE_BY_NAME.put(clazz, map); + } + + /** + * 以枚举转换出的任意值构建缓存,在枚举的静态块里面调用 + * + * @param clazz 必须值 + * @param values 必须值 + * @param enumMapping 必须值 + * @param 枚举类型 + */ + public static > void registerByValue(Class clazz, E[] values, EnumMapping enumMapping) { + if (CACHE_BY_VALUE.containsKey(clazz)) { + throw new RuntimeException(String.format("枚举%s已经构建过value缓存,不允许重复构建", clazz.getSimpleName())); + } + Map> map = new ConcurrentHashMap<>(); + for (E v : values) { + Object value = enumMapping.value(v); + if (map.containsKey(value)) { + throw new RuntimeException(String.format("枚举%s存在相同的值%s映射同一个枚举%s.%s", clazz.getSimpleName(), value, clazz.getSimpleName(), v)); + } + map.put(value, v); + } + CACHE_BY_VALUE.put(clazz, map); + } + + /** + * 从以枚举名称构建的缓存中通过枚举名获取枚举 + * + * @param clazz 必须值 + * @param name 可以空 + * @return 实例 + * @param 枚举类型 + */ + public static > E findByName(Class clazz, String name) { + return find(clazz, name, CACHE_BY_NAME, null); + } + + /** + * 从以枚举名称构建的缓存中通过枚举名获取枚举 + * + * @param clazz 必须值 + * @param name 可以空 + * @param defaultEnum 可以空 + * @param 枚举类型 + * @return 实例 + */ + public static > E findByName(Class clazz, String name, E defaultEnum) { + return find(clazz, name, CACHE_BY_NAME, defaultEnum); + } + + /** + * 从以枚举转换值构建的缓存中通过枚举转换值获取枚举 + * + * @param clazz 必须值 + * @param value 可以空 + * @param 枚举类型 + * @return 实例 + */ + public static > E findByValue(Class clazz, Object value) { + return find(clazz, value, CACHE_BY_VALUE, null); + } + + /** + * 从以枚举转换值构建的缓存中通过枚举转换值获取枚举 + * + * @param clazz 必须值 + * @param value 可以空 + * @param defaultEnum 可以空 + * @param 枚举类型 + * @return 实例 + */ + public static > E findByValue(Class clazz, Object value, E defaultEnum) { + return find(clazz, value, CACHE_BY_VALUE, defaultEnum); + } + + /** + * + * @param clazz 必须值 + * @param obj 可以空 + * @param cache 必须值 + * @param defaultEnum 可以空 + * @return 实例 + * @param 枚举类型 + */ + private static > E find(Class clazz, Object obj, Map>, Map>> cache, E defaultEnum) { + Map> map = cache.get(clazz); + if (map == null) { + executeEnumStatic(clazz);// 触发枚举静态块执行 + map = cache.get(clazz);// 执行枚举静态块后重新获取缓存 + } + if (map == null) { + String msg = null; + if (cache == CACHE_BY_NAME) { + msg = String.format( + "枚举%s还没有注册到枚举缓存中,请在%s.static代码块中加入如下代码 : EnumCache.registerByName(%s.class, %s.values());", + clazz.getSimpleName(), + clazz.getSimpleName(), + clazz.getSimpleName(), + clazz.getSimpleName() + ); + } + if (cache == CACHE_BY_VALUE) { + msg = String.format( + "枚举%s还没有注册到枚举缓存中,请在%s.static代码块中加入如下代码 : EnumCache.registerByValue(%s.class, %s.values(), %s::getXxx);", + clazz.getSimpleName(), + clazz.getSimpleName(), + clazz.getSimpleName(), + clazz.getSimpleName(), + clazz.getSimpleName() + ); + } + throw new RuntimeException(msg); + } + if (obj == null) { + return defaultEnum; + } + Enum result = map.get(obj); + return result == null ? defaultEnum : (E) result; + } + + /** + * + * @param clazz 必须值 + * @param 枚举类型 + */ + private static > void executeEnumStatic(Class clazz) { + if (!LOADED.containsKey(clazz)) { + synchronized (lock) { + if (!LOADED.containsKey(clazz)) { + try { + // 目的是让枚举类的static块运行,static块没有执行完是会阻塞在此的 + Class.forName(clazz.getName()); + LOADED.put(clazz, true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + } + + /** + * 枚举缓存映射器函数式接口 + */ + @FunctionalInterface + public interface EnumMapping> { + /** + * 自定义映射器 + * + * @param e 枚举 + * @return 映射关系,最终体现到缓存中 + */ + Object value(E e); + } + +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/ColorType.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/ColorType.java new file mode 100644 index 0000000..155ff6d --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/ColorType.java @@ -0,0 +1,85 @@ +package io.yunjiao.extension.common.model; + +import lombok.Getter; + +import java.awt.*; + +/** + * 颜色类型 + * + * @author yangyunjiao + */ +@Getter +public enum ColorType { + /** + * 白色 + */ + white(Color.WHITE), + + /** + * 浅灰色 + */ + lightGray(Color.LIGHT_GRAY), + + /** + * 灰色 + */ + gray(Color.GRAY), + + /** + * 深灰色 + */ + darkGray(Color.DARK_GRAY), + + + /** + * 黑色 + */ + black(Color.BLACK), + + /** + * 红色 + */ + red(Color.RED), + + /** + * 粉红色 + */ + pink(Color.PINK), + + /** + * 橘色 + */ + orange(Color.ORANGE), + + /** + * 黄色 + */ + yellow(Color.YELLOW), + + /** + * 绿色 + */ + green(Color.GREEN), + + /** + * 洋红色 + */ + magenta(Color.MAGENTA), + + /** + * 青色 + */ + cyan(Color.CYAN), + + /** + * 蓝色 + */ + blue(Color.BLUE); + + private final Color mapping; + + ColorType(Color mapping) { + this.mapping = mapping; + } +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/FontStyle.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/FontStyle.java new file mode 100644 index 0000000..49b6505 --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/FontStyle.java @@ -0,0 +1,34 @@ +package io.yunjiao.extension.common.model; + +import lombok.Getter; + +import java.awt.*; + +/** + * 字体风格 + * + * @author yangyunjiao + */ +@Getter +public enum FontStyle { + /** + * 正常体 + */ + plain(Font.PLAIN), + + /** + * 粗体 + */ + bold(Font.BOLD), + + /** + * 斜体 + */ + italic(Font.ITALIC); + + private final int mapping; + + FontStyle(int mapping) { + this.mapping = mapping; + } +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/TransparencyType.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/TransparencyType.java new file mode 100644 index 0000000..724043f --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/TransparencyType.java @@ -0,0 +1,79 @@ +package io.yunjiao.extension.common.model; + +import lombok.Getter; + +import java.awt.*; + +/** + * 透明度类型 + * + * @author yangyunjiao + */ +@Getter +public enum TransparencyType { + /** + * {@link AlphaComposite#Clear} + */ + clear(AlphaComposite.Clear), + + /** + * {@link AlphaComposite#Src} + */ + src(AlphaComposite.Src), + + /** + * {@link AlphaComposite#Dst} + */ + dst(AlphaComposite.Dst), + + /** + * {@link AlphaComposite#SrcOver} + */ + srcOver(AlphaComposite.SrcOver), + + /** + * {@link AlphaComposite#DstOver} + */ + dstOver(AlphaComposite.DstOver), + + /** + * {@link AlphaComposite#SrcIn} + */ + srcIn(AlphaComposite.SrcIn), + + /** + * {@link AlphaComposite#DstIn} + */ + dstIn(AlphaComposite.DstIn), + + /** + * {@link AlphaComposite#SrcOut} + */ + srcOut(AlphaComposite.SrcOut), + + /** + * {@link AlphaComposite#DstOut} + */ + dstOut(AlphaComposite.DstOut), + + /** + * {@link AlphaComposite#SrcAtop} + */ + srcAtop(AlphaComposite.SrcAtop), + + /** + * {@link AlphaComposite#DstAtop} + */ + dstAtop(AlphaComposite.DstAtop), + + /** + * {@link AlphaComposite#Xor} + */ + xOr(AlphaComposite.Xor); + + private final AlphaComposite mapping; + + TransparencyType(AlphaComposite mapping) { + this.mapping = mapping; + } +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/util/CommonRuntimeException.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/util/CommonRuntimeException.java new file mode 100644 index 0000000..93e142f --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/util/CommonRuntimeException.java @@ -0,0 +1,27 @@ +package io.yunjiao.extension.common.util; + +/** + * 通用异常 + * + * @author yangyunjiao + */ +public class CommonRuntimeException extends RuntimeException { + public CommonRuntimeException() { + } + + public CommonRuntimeException(String message) { + super(message); + } + + public CommonRuntimeException(String message, Throwable cause) { + super(message, cause); + } + + public CommonRuntimeException(Throwable cause) { + super(cause); + } + + public CommonRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurJFrameDemo.java b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurJFrameDemo.java new file mode 100644 index 0000000..0dbb3f4 --- /dev/null +++ b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurJFrameDemo.java @@ -0,0 +1,199 @@ +package io.yunjiao.extension.common.algorithm; + +import lombok.Getter; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +/** + * {@link GaussianBlur} 可视化界面 + * + * @author yangyunjiao + */ +public class GaussianBlurJFrameDemo extends JFrame { + private BufferedImage originalImage; + private BufferedImage blurredImage; + private final JLabel imageLabel; + private final JSlider radiusSlider; + private final JComboBox intensityComboBox; + + // 模糊强度枚举 + @Getter + public enum BlurIntensity { + LIGHT(1, 3), + MEDIUM(4, 7), + HEAVY(8, 15); + + private final int minRadius; + private final int maxRadius; + + BlurIntensity(int minRadius, int maxRadius) { + this.minRadius = minRadius; + this.maxRadius = maxRadius; + } + + public int getRadius() { + return (minRadius + maxRadius) / 2; // 返回中间值作为默认半径 + } + + } + + public GaussianBlurJFrameDemo() { + setTitle("Java高斯模糊算法 - 支持强度设置"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(1000, 700); + setLocationRelativeTo(null); + + // 创建界面组件 + imageLabel = new JLabel(); + imageLabel.setHorizontalAlignment(SwingConstants.CENTER); + JScrollPane scrollPane = new JScrollPane(imageLabel); + + // 模糊强度选择 + intensityComboBox = new JComboBox<>(new String[]{"轻度模糊", "中度模糊", "重度模糊", "自定义"}); + intensityComboBox.setSelectedIndex(1); // 默认选择中度模糊 + intensityComboBox.addActionListener(e -> updateRadiusBasedOnIntensity()); + + // 半径滑块 + radiusSlider = new JSlider(1, 30, BlurIntensity.MEDIUM.getRadius()); + radiusSlider.setMajorTickSpacing(5); + radiusSlider.setMinorTickSpacing(1); + radiusSlider.setPaintTicks(true); + radiusSlider.setPaintLabels(true); + radiusSlider.setBorder(BorderFactory.createTitledBorder("模糊半径")); + + // 更新滑块范围基于强度选择 + //updateRadiusRange(); + + JButton openButton = new JButton("打开图片"); + JButton saveButton = new JButton("保存图片"); + + // 按钮面板 + JPanel buttonPanel = new JPanel(); + buttonPanel.add(openButton); + buttonPanel.add(saveButton); + + // 强度选择面板 + JPanel intensityPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + intensityPanel.add(new JLabel("模糊强度:")); + intensityPanel.add(intensityComboBox); + + // 控制面板 + JPanel controlPanel = new JPanel(new BorderLayout()); + controlPanel.add(intensityPanel, BorderLayout.NORTH); + controlPanel.add(radiusSlider, BorderLayout.CENTER); + controlPanel.add(buttonPanel, BorderLayout.SOUTH); + + // 主布局 + setLayout(new BorderLayout()); + add(scrollPane, BorderLayout.CENTER); + add(controlPanel, BorderLayout.SOUTH); + + // 添加事件监听器 + openButton.addActionListener(e -> openImage()); + saveButton.addActionListener(e -> saveImage()); + radiusSlider.addChangeListener(e -> { + System.out.println(e); + applyBlur(); + }); + } + + private void updateRadiusBasedOnIntensity() { + int selectedIndex = intensityComboBox.getSelectedIndex(); + //updateRadiusRange(); + + if (selectedIndex < 3) { // 不是"自定义" + BlurIntensity intensity = BlurIntensity.values()[selectedIndex]; + radiusSlider.setValue(intensity.getRadius()); + } + } + + private void updateRadiusRange() { + int selectedIndex = intensityComboBox.getSelectedIndex(); + if (selectedIndex < 3) { + BlurIntensity intensity = BlurIntensity.values()[selectedIndex]; + radiusSlider.setMinimum(intensity.getMinRadius()); + radiusSlider.setMaximum(intensity.getMaxRadius()); + } else { + // 自定义模式下使用完整范围 + radiusSlider.setMinimum(1); + radiusSlider.setMaximum(30); + } + } + + private void openImage() { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("选择图片"); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + + int result = fileChooser.showOpenDialog(this); + if (result == JFileChooser.APPROVE_OPTION) { + try { + File file = fileChooser.getSelectedFile(); + originalImage = ImageIO.read(file); + displayImage(originalImage); + applyBlur(); + } catch (IOException ex) { + JOptionPane.showMessageDialog(this, "无法加载图片: " + ex.getMessage(), + "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + + private void saveImage() { + if (blurredImage == null) { + JOptionPane.showMessageDialog(this, "没有图片可保存", + "警告", JOptionPane.WARNING_MESSAGE); + return; + } + + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("保存图片"); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + + int result = fileChooser.showSaveDialog(this); + if (result == JFileChooser.APPROVE_OPTION) { + try { + File file = fileChooser.getSelectedFile(); + ImageIO.write(blurredImage, "png", file); + JOptionPane.showMessageDialog(this, "图片保存成功!", + "成功", JOptionPane.INFORMATION_MESSAGE); + } catch (IOException ex) { + JOptionPane.showMessageDialog(this, "无法保存图片: " + ex.getMessage(), + "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + + private void applyBlur() { + if (originalImage == null) return; + + int radius = radiusSlider.getValue(); + long startTime = System.currentTimeMillis(); + blurredImage = GaussianBlur.execute(originalImage, radius); + long stopTome = System.currentTimeMillis(); + System.out.println("耗时:" + (stopTome - startTime)/1000); + displayImage(blurredImage); + } + + private void displayImage(BufferedImage image) { + Image scaledImage = image.getScaledInstance( + Math.min(800, image.getWidth()), + Math.min(600, image.getHeight()), + Image.SCALE_SMOOTH + ); + ImageIcon icon = new ImageIcon(scaledImage); + imageLabel.setIcon(icon); + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + GaussianBlurJFrameDemo app = new GaussianBlurJFrameDemo(); + app.setVisible(true); + }); + } +} diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java index 89aedbf..99e0cce 100644 --- a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java +++ b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java @@ -1,56 +1,20 @@ package io.yunjiao.extension.common.algorithm; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.core.io.ClassPathResource; -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.function.BiFunction; import static org.junit.jupiter.api.Assertions.*; /** * {@link GaussianBlur} 单元测试用例 * - * @author wanpinwei + * @author yangyunjiao */ public class GaussianBlurTest { - @Test - @DisplayName("模糊真实的图片") - void testImage() throws IOException { - ClassPathResource resource = new ClassPathResource("images/input.png"); - BufferedImage originalImage = ImageIO.read(resource.getInputStream()); - - Path targetDir = Paths.get("target", "processed-images"); - if (!Files.exists(targetDir)) { - Files.createDirectories(targetDir); - } - - BufferedImage modifiedImage = GaussianBlur.gaussianBlurLight(originalImage); - File outputFile = new File(targetDir.toFile(), "output-l.png"); - ImageIO.write(modifiedImage, "png", outputFile); - System.out.println("图片处理完成,保存至: " + outputFile.getAbsolutePath()); - - modifiedImage = GaussianBlur.gaussianBlurMedium(originalImage); - outputFile = new File(targetDir.toFile(), "output-m.png"); - ImageIO.write(modifiedImage, "png", outputFile); - System.out.println("图片处理完成,保存至: " + outputFile.getAbsolutePath()); - - modifiedImage = GaussianBlur.gaussianBlurHeavy(originalImage); - outputFile = new File(targetDir.toFile(), "output-h.png"); - ImageIO.write(modifiedImage, "png", outputFile); - System.out.println("图片处理完成,保存至: " + outputFile.getAbsolutePath()); - } - @Test @DisplayName("测试全黑图像模糊") void testBlurAllBlackImage() { @@ -63,7 +27,7 @@ public class GaussianBlurTest { } // 应用高斯模糊 - BufferedImage result = GaussianBlur.gaussianBlur(blackImage, 5); + BufferedImage result = GaussianBlur.execute(blackImage, 5); // 验证结果仍然是全黑 for (int x = 0; x < 10; x++) { @@ -86,7 +50,7 @@ public class GaussianBlurTest { } // 应用高斯模糊 - BufferedImage result = GaussianBlur.gaussianBlur(whiteImage, 5); + BufferedImage result = GaussianBlur.execute(whiteImage, 5); // 验证结果仍然是全白(允许轻微的颜色变化) for (int x = 0; x < 10; x++) { @@ -112,7 +76,7 @@ public class GaussianBlurTest { singlePixel.setRGB(0, 0, 0xFFFF0000); // 红色 // 应用高斯模糊 - BufferedImage result = GaussianBlur.gaussianBlur(singlePixel, 3); + BufferedImage result = GaussianBlur.execute(singlePixel, 3); // 验证结果仍然是相同的颜色 assertEquals(0xFFFF0000, result.getRGB(0, 0), @@ -132,7 +96,7 @@ public class GaussianBlurTest { } // 应用半径为0的高斯模糊 - BufferedImage result = GaussianBlur.gaussianBlur(testImage, 0); + BufferedImage result = GaussianBlur.execute(testImage, 0); // 验证图像没有变化 for (int x = 0; x < 5; x++) { @@ -156,7 +120,7 @@ public class GaussianBlurTest { testImage.setRGB(9, 9, 0xFFFFFF00); // 右下角黄色 // 应用高斯模糊 - BufferedImage result = GaussianBlur.gaussianBlur(testImage, 5); + BufferedImage result = GaussianBlur.execute(testImage, 5); // 验证四角颜色已经混合(不再是纯色) int topLeft = result.getRGB(0, 0); @@ -183,10 +147,10 @@ public class GaussianBlurTest { } // 应用小半径模糊 - BufferedImage smallBlur = GaussianBlur.gaussianBlur(chessboard, 1); + BufferedImage smallBlur = GaussianBlur.execute(chessboard, 1); // 应用大半径模糊 - BufferedImage largeBlur = GaussianBlur.gaussianBlur(chessboard, 10); + BufferedImage largeBlur = GaussianBlur.execute(chessboard, 10); // 计算两幅图像的差异 double difference = calculateImageDifference(smallBlur, largeBlur); @@ -202,7 +166,7 @@ public class GaussianBlurTest { BufferedImage testImage = new BufferedImage(15, 20, BufferedImage.TYPE_INT_ARGB); // 应用高斯模糊 - BufferedImage result = GaussianBlur.gaussianBlur(testImage, 5); + BufferedImage result = GaussianBlur.execute(testImage, 5); // 验证尺寸不变 assertEquals(testImage.getWidth(), result.getWidth(), "模糊后图像宽度应该不变"); @@ -250,7 +214,7 @@ public class GaussianBlurTest { testImage.setRGB(2, 2, 0xFFFF0000); // 中心为红色 // 应用非常大的模糊半径 - BufferedImage result = GaussianBlur.gaussianBlur(testImage, 100); + BufferedImage result = GaussianBlur.execute(testImage, 100); // 验证图像没有崩溃,并且所有像素都有值 for (int x = 0; x < 5; x++) { diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/lang/EnumCacheTest.java b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/lang/EnumCacheTest.java new file mode 100644 index 0000000..61aa7d6 --- /dev/null +++ b/extensions/extension-common/src/test/java/io/yunjiao/extension/common/lang/EnumCacheTest.java @@ -0,0 +1,68 @@ +package io.yunjiao.extension.common.lang; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link EnumCache} 单元测试用例 + * + * @author yangyunjiao + */ +public class EnumCacheTest { + + @Test + void givenName_whenFindByName_thenReturnOk() { + StatusEnum status = EnumCache.findByName(StatusEnum.class, "SUCCESS"); + assertThat(status).isEqualTo(StatusEnum.SUCCESS); + } + + @Test + void givenWrongName_whenFindByName_thenReturnNull() { + StatusEnum status = EnumCache.findByName(StatusEnum.class, "SUCCESS1"); + assertThat(status).isNull(); + } + + @Test + void givenValue_whenFindByValue_thenReturnOk() { + StatusEnum status = EnumCache.findByValue(StatusEnum.class, "S"); + assertThat(status).isEqualTo(StatusEnum.SUCCESS); + } + + @Test + void givenWrongValue_whenFindByName_thenReturnNull() { + StatusEnum status = EnumCache.findByName(StatusEnum.class, "s"); + assertThat(status).isNull(); + } + + + enum StatusEnum { + INIT("I", "初始化"), + PROCESSING("P", "处理中"), + SUCCESS("S", "成功"), + FAIL("F", "失败"); + + private String code; + private String desc; + + StatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + static { + // 通过名称构建缓存,通过EnumCache.findByName(StatusEnum.class,"SUCCESS",null);调用能获取枚举 + EnumCache.registerByName(StatusEnum.class, StatusEnum.values()); + // 通过code构建缓存,通过EnumCache.findByValue(StatusEnum.class,"S",null);调用能获取枚举 + EnumCache.registerByValue(StatusEnum.class, StatusEnum.values(), StatusEnum::getCode); + } + } +} diff --git a/extensions/extension-id/pom.xml b/extensions/extension-id/pom.xml new file mode 100644 index 0000000..2e2f9b7 --- /dev/null +++ b/extensions/extension-id/pom.xml @@ -0,0 +1,27 @@ + + + + io.gitee.yunjiao-source + extensions + ${revision} + + 4.0.0 + + extension-id + jar + Extension :: Id + 身份标识号扩展 + + + + io.gitee.yunjiao-source + extension-common + + + cn.hutool + hutool-core + + + \ No newline at end of file diff --git a/extensions/extension-id/src/main/java/io/yunjiao/extension/id/_Id.java b/extensions/extension-id/src/main/java/io/yunjiao/extension/id/_Id.java new file mode 100644 index 0000000..65e38c1 --- /dev/null +++ b/extensions/extension-id/src/main/java/io/yunjiao/extension/id/_Id.java @@ -0,0 +1,9 @@ +package io.yunjiao.extension.id; + +/** + * _Id + * + * @author yangyunjiao + */ +public class _Id { +} diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/_Querydsl.java b/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/_Querydsl.java new file mode 100644 index 0000000..8360cf3 --- /dev/null +++ b/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/_Querydsl.java @@ -0,0 +1,9 @@ +package io.yunjiao.extension.querydsl; + +/** + * _Querydsl + * + * @author yangyunjiao + */ +public class _Querydsl { +} diff --git a/extensions/pom.xml b/extensions/pom.xml index addb2a3..05be5f4 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -19,7 +19,8 @@ extension-common extension-apijson extension-querydsl - extension-hutool + extension-captcha + extension-id diff --git a/starters/README.md b/starters/README.md index ddbbaec..8f3db5d 100644 --- a/starters/README.md +++ b/starters/README.md @@ -14,7 +14,7 @@ ${version} ``` -所有的配置属性参考[application-all.yaml](../doc/apijson/application-all.yml) +所有的配置属性参考[application-all.yaml](../examples/example-apijson-fastjson2/src/main/resources/application-all.yml) 支持接口 @@ -46,7 +46,7 @@ ``` -所有的配置属性参考[application-all.yaml](../doc/apijson/application-all.yml) +所有的配置属性参考[application-all.yaml](../examples/example-apijson-gson/src/main/resources/application-all.yml) 支持的接口 @@ -65,9 +65,7 @@ 详细使用参考示例[example-apijson-gson](../examples/example-apijson-gson) -## start-hutool - -集成`Hutool`框架的启动器, 所有的配置属性参考[application-all.yaml](../doc/hutool/application-all.yml) +## start-id 在`pom.xml`中添加依赖 ```xml @@ -119,3 +117,23 @@ * SQLQueryFactory: 查询工厂 详细使用参考示例[example-querydsl-sql](../examples/example-querydsl-sql) + +## start-captcha + +验证码启动器, 在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source + starter-captcha + ${version} + +``` + +已配置的Bean列表 +* CaptchaServiceFactory: 查询工厂 +* LineCaptchaService:线段干扰的验证码服务 +* CircleCaptchaService:圆圈干扰验证码服务 +* ShearCaptchaService:扭曲干扰验证码服务 +* GifCaptchaService:gif验证码服务 + +详细使用参考示例[example-captcha](../examples/example-captcha) \ No newline at end of file diff --git a/starters/pom.xml b/starters/pom.xml index 5405810..95f68b1 100644 --- a/starters/pom.xml +++ b/starters/pom.xml @@ -16,11 +16,12 @@ Starters启动器 - starter-hutool + starter-id starter-querydsl-sql starter-querydsl-jpa starter-apijson-fastjson2 starter-apijson-gson + starter-captcha diff --git a/starters/starter-captcha/pom.xml b/starters/starter-captcha/pom.xml new file mode 100644 index 0000000..28b12b8 --- /dev/null +++ b/starters/starter-captcha/pom.xml @@ -0,0 +1,29 @@ + + + + io.gitee.yunjiao-source + starters + ${revision} + + 4.0.0 + + starter-captcha + jar + Starter :: Captcha + 验证码 启动器 + + + + io.gitee.yunjiao-source + autoconfigure + + + io.gitee.yunjiao-source + extension-captcha + + + + + \ No newline at end of file diff --git a/starters/starter-hutool/pom.xml b/starters/starter-id/pom.xml similarity index 75% rename from starters/starter-hutool/pom.xml rename to starters/starter-id/pom.xml index b141d1c..15b48cb 100644 --- a/starters/starter-hutool/pom.xml +++ b/starters/starter-id/pom.xml @@ -9,10 +9,10 @@ 4.0.0 - starter-hutool + starter-id jar - Starter :: Hutool - 基于Hutool框架的 启动器 + Starter :: Id + Id(身份标识号) 启动器 @@ -20,8 +20,8 @@ autoconfigure - cn.hutool - hutool-core + io.gitee.yunjiao-source + extension-id -- Gitee From bf33865a3af2ba1f5df341a057b7ba029fb4f742 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Mon, 25 Aug 2025 17:08:49 +0800 Subject: [PATCH 09/15] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/Fastjson2ApplicationConfiguration.java | 2 +- .../apijson/GsonApplicationConfiguration.java | 2 +- .../springboot/autoconfigure/util/PropertyNameConsts.java | 2 +- .../apijson/RestApiAutoConfigurationTest.java | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java index 27c176d..0f46924 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java @@ -95,7 +95,7 @@ public class Fastjson2ApplicationConfiguration { */ @RequiredArgsConstructor @AutoConfiguration - @ConditionalOnProperty(name = {PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE}, + @ConditionalOnProperty(name = {PropertyNameConsts.PROPERTY_APIJSON_RESTAPI_ENABLE}, havingValue = "true", matchIfMissing = true) static diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java index 4bb57e5..b96a2fb 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java @@ -95,7 +95,7 @@ public class GsonApplicationConfiguration { */ @RequiredArgsConstructor @AutoConfiguration - @ConditionalOnProperty(name = {PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE}, + @ConditionalOnProperty(name = {PropertyNameConsts.PROPERTY_APIJSON_RESTAPI_ENABLE}, havingValue = "true", matchIfMissing = true) static diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java index 46c7c29..7ddc7be 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java +++ b/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java @@ -41,7 +41,7 @@ public class PropertyNameConsts { /** * apijson rest-api enabled 属性 */ - public static final String PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE = PROPERTY_PREFIX_APIJSON_RESTAPI + PROPERTY_ENABLED; + public static final String PROPERTY_APIJSON_RESTAPI_ENABLE = PROPERTY_PREFIX_APIJSON_RESTAPI + PROPERTY_ENABLED; /** * apijson sql 属性 diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java index bbfc673..c870a2c 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java @@ -29,7 +29,7 @@ public class RestApiAutoConfigurationTest { @Test public void givenPropertyRestApiEnable_whenTrue_thenFastjson2Config() { applicationContextRunner - .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=true") + .withPropertyValues(PropertyNameConsts.PROPERTY_APIJSON_RESTAPI_ENABLE + "=true") .withUserConfiguration(Fastjson2ApplicationConfiguration.Fastjson2RestApiAutoConfiguration.class) .run(context -> { assertThat(context).hasSingleBean(Fastjson2RestController.class); @@ -40,7 +40,7 @@ public class RestApiAutoConfigurationTest { @Test public void givenPropertyRestApiEnable_whenFalse_thenFastjson2Config() { applicationContextRunner - .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=false") + .withPropertyValues(PropertyNameConsts.PROPERTY_APIJSON_RESTAPI_ENABLE + "=false") .withUserConfiguration(Fastjson2ApplicationConfiguration.Fastjson2RestApiAutoConfiguration.class) .run(context -> { assertThat(context).doesNotHaveBean(Fastjson2RestController.class); @@ -51,7 +51,7 @@ public class RestApiAutoConfigurationTest { @Test public void givenPropertyRestApiEnable_whenTrue_thenGsonConfig() { applicationContextRunner - .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=true") + .withPropertyValues(PropertyNameConsts.PROPERTY_APIJSON_RESTAPI_ENABLE + "=true") .withUserConfiguration(GsonApplicationConfiguration.GsonRestApiAutoConfiguration.class) .run(context -> { assertThat(context).hasSingleBean(GsonRestController.class); @@ -62,7 +62,7 @@ public class RestApiAutoConfigurationTest { @Test public void givenPropertyRestApiEnable_whenFalse_thenGsonConfig() { applicationContextRunner - .withPropertyValues(PropertyNameConsts.PROPERTY_PREFIX_APIJSON_RESTAPI_ENABLE + "=false") + .withPropertyValues(PropertyNameConsts.PROPERTY_APIJSON_RESTAPI_ENABLE + "=false") .withUserConfiguration(GsonApplicationConfiguration.GsonRestApiAutoConfiguration.class) .run(context -> { assertThat(context).doesNotHaveBean(GsonRestController.class); -- Gitee From d9f6812e9eccbff79424efa839581746e1147f7f Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Mon, 25 Aug 2025 17:09:18 +0800 Subject: [PATCH 10/15] =?UTF-8?q?feat:=20=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=8C=E5=93=8D=E5=BA=94=E5=AE=9E=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/captcha/CaptchaReponse.java | 41 +++++++++++++++++++ .../common/captcha/CaptchaValidate.java | 27 ++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaReponse.java create mode 100644 extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaValidate.java diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaReponse.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaReponse.java new file mode 100644 index 0000000..863bddb --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaReponse.java @@ -0,0 +1,41 @@ +package io.yunjiao.extension.common.captcha; + +import lombok.Data; + +/** + * 验证码响应 + * + * @author yangyunjiao + */ +@Data +public class CaptchaReponse { + /** + * 验证码唯一标识,通常是uuid字符 + */ + private String key; + + /** + * 验证码图片 + */ + private String captchaImageBase64; + + /** + * 背景图片 + */ + private String backgroundImageBase64; + + + /** + * 分类 + */ + private CaptchaCategory category; + + public static CaptchaReponse of(CaptchaData data) { + CaptchaReponse reponse = new CaptchaReponse(); + reponse.setCategory(data.getCategory()); + reponse.setKey(data.getKey()); + reponse.setBackgroundImageBase64(data.getBackgroundImageBase64()); + reponse.setCaptchaImageBase64(data.getCaptchaImageBase64()); + return reponse; + } +} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaValidate.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaValidate.java new file mode 100644 index 0000000..69bc1a1 --- /dev/null +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaValidate.java @@ -0,0 +1,27 @@ +package io.yunjiao.extension.common.captcha; + +import lombok.Data; + +/** + * 验证码校验对象 + * + * @author yangyunjiao + */ +@Data +public class CaptchaValidate { + /** + * 验证码唯一标识,通常是uuid字符 + */ + private String key; + + /** + * 验证码 + */ + private Object code; + + + /** + * 分类 + */ + private CaptchaCategory category; +} -- Gitee From 6933eaf64b0b5319242b3f2cd31ee2aa2f8e6abe Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Mon, 25 Aug 2025 17:10:30 +0800 Subject: [PATCH 11/15] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/yunjiao/extension/common/captcha/CaptchaData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java index 08188b1..521e3f2 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java +++ b/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java @@ -60,7 +60,7 @@ public class CaptchaData implements Serializable { * * @return 可能空 */ - public String getBackgroundImage() { + public String getBackgroundImageBase64() { if (backgroundImage == null) { return null; } -- Gitee From a0b68f4cb33d2b9f517fcf98a8c189d60edff80f Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Mon, 25 Aug 2025 22:23:27 +0800 Subject: [PATCH 12/15] =?UTF-8?q?feat:=20=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=8F=8A=E6=A0=A1=E9=AA=8C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/README.md | 7 ++- examples/example-captcha/pom.xml | 9 ++++ .../example/captcha/CaptchaController.java | 52 +++++++++++++++++++ .../src/main/resources/application.yml | 1 + 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaController.java diff --git a/examples/README.md b/examples/README.md index ff94873..af700d0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,9 +15,9 @@ ** 注意 在服务启动过程中,会出现异常,这是因为官方提供的gson插件还存在问题。有些接口请求也会出现异常 -## example-hutool +## example-id -基于`starter-hutool`示例项目。 +基于`starter-id`示例项目。 ## example-querydsl-jpa @@ -27,6 +27,9 @@ 基于`starter-querydsl-sql`示例项目。需要配置数据库及运行数据库脚本,请查看`resources`目录。 +## example-captcha + +验证码示例项目 diff --git a/examples/example-captcha/pom.xml b/examples/example-captcha/pom.xml index 4c990ae..6cbfffd 100644 --- a/examples/example-captcha/pom.xml +++ b/examples/example-captcha/pom.xml @@ -20,10 +20,19 @@ starter-captcha + + cn.hutool + hutool-cache + + org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-webflux + diff --git a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaController.java b/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaController.java new file mode 100644 index 0000000..ce4f463 --- /dev/null +++ b/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaController.java @@ -0,0 +1,52 @@ +package io.yunjiao.example.captcha; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import io.yunjiao.extension.common.captcha.*; +import io.yunjiao.springboot.autoconfigure.captcha.CaptchaServiceFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +/** + * 接口 + * + * @author yangyunjiao + */ +@RestController +@RequiredArgsConstructor +public class CaptchaController { + private final CaptchaServiceFactory factory; + private final TimedCache timedCache = CacheUtil.newTimedCache(30 * 1000); + + @GetMapping("/captcha") + public CaptchaReponse get(@RequestParam(name = "category") CaptchaCategory category) { + CaptchaService service = factory.findService(category); + CaptchaData data = service.draw(); + + CaptchaReponse reponse = new CaptchaReponse(); + reponse.setKey(data.getKey()); + reponse.setCategory(service.getCategory()); + reponse.setCaptchaImageBase64(data.getCaptchaImageBase64()); + + timedCache.put(data.getKey(), data.getCode()); + System.out.println("code=" + data.getCode()); + return reponse; + } + + @PostMapping("/captcha") + public String post(@RequestBody CaptchaValidate validate) { + String cacheCode = timedCache.get(validate.getKey()); + if (!StringUtils.hasText(cacheCode)) { + return "验证码校验失败"; + } + + CaptchaService service = factory.findService(validate.getCategory()); + boolean pass = service.verify(cacheCode, validate.getCode()); + if (pass) { + return "验证码校验成功"; + } else { + return "验证码校验失败"; + } + } +} diff --git a/examples/example-captcha/src/main/resources/application.yml b/examples/example-captcha/src/main/resources/application.yml index 5700544..686d41e 100644 --- a/examples/example-captcha/src/main/resources/application.yml +++ b/examples/example-captcha/src/main/resources/application.yml @@ -1,3 +1,4 @@ + logging: level: root: info -- Gitee From 9bda2a057c4e533c77255e73e82d86a49126f0d7 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Tue, 26 Aug 2025 10:41:00 +0800 Subject: [PATCH 13/15] =?UTF-8?q?refactor:=20=E9=A1=B9=E7=9B=AE=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FAQ.md | 79 ++++++++++++++++++- README.md | 8 +- autoconfigure/pom.xml | 12 +-- .../apijson/ApijsonAutoConfiguration.java | 10 +-- .../ApijsonFunctionParserConfigurer.java | 2 +- .../apijson/ApijsonInitConfiguration.java | 2 +- .../apijson/ApijsonInitializingBean.java | 2 +- .../apijson/ApijsonParserProperties.java | 4 +- .../apijson/ApijsonProperties.java | 4 +- .../apijson/ApijsonSqlConfigConfigurer.java | 2 +- .../apijson/ApijsonSqlProperties.java | 6 +- .../autoconfigure/apijson/ApijsonUtils.java | 2 +- .../apijson/ApijsonVerifierConfigurer.java | 2 +- .../apijson/ApijsonVerifierProperties.java | 4 +- .../Fastjson2ApplicationConfiguration.java | 12 +-- .../apijson/GsonApplicationConfiguration.java | 12 +-- .../apijson/NewIdStrategyConfiguration.java | 6 +- .../condition/ApllicationCondition.java | 8 +- .../condition/NewIdStrategyCondition.java | 8 +- .../apijson/fastjson2/Fastjson2Creator.java | 4 +- .../fastjson2/Fastjson2EXtRestController.java | 10 +-- .../fastjson2/Fastjson2FunctionParser.java | 2 +- .../fastjson2/Fastjson2InitializingBean.java | 6 +- .../fastjson2/Fastjson2ObjectParser.java | 2 +- .../apijson/fastjson2/Fastjson2Parser.java | 2 +- .../fastjson2/Fastjson2RestController.java | 6 +- .../fastjson2/Fastjson2SimpleCallback.java | 6 +- .../apijson/fastjson2/Fastjson2SqlConfig.java | 4 +- .../fastjson2/Fastjson2SqlExecutor.java | 4 +- .../apijson/fastjson2/Fastjson2Verifier.java | 2 +- .../apijson/fastjson2/model/Privacy.java | 2 +- .../apijson/fastjson2/model/User.java | 2 +- .../apijson/fastjson2/model/Verify.java | 2 +- .../apijson/gson/GsonCreator.java | 4 +- .../apijson/gson/GsonEXtRestController.java | 12 +-- .../apijson/gson/GsonFunctionParser.java | 2 +- .../apijson/gson/GsonInitializingBean.java | 6 +- .../apijson/gson/GsonObjectParser.java | 2 +- .../apijson/gson/GsonParser.java | 2 +- .../apijson/gson/GsonRestController.java | 6 +- .../apijson/gson/GsonSimpleCallback.java | 6 +- .../apijson/gson/GsonSqlConfig.java | 4 +- .../apijson/gson/GsonSqlExecutor.java | 4 +- .../apijson/gson/GsonVerifier.java | 2 +- .../apijson/gson/model/Privacy.java | 2 +- .../apijson/gson/model/User.java | 2 +- .../apijson/gson/model/Verify.java | 2 +- .../captcha/CaptchaAutoConfiguration.java | 6 +- .../captcha/CaptchaServiceFactory.java | 10 +-- .../captcha/HutoolCaptchaConfiguration.java | 6 +- .../captcha/HutoolCaptchaProperties.java | 10 +-- .../condition/EnumPropertyCondition.java | 2 +- .../id/HutoolIdConfiguration.java | 2 +- .../autoconfigure/id/IdAutoConfiguration.java | 4 +- .../querydsl/QueryDSLAutoConfig.java | 8 +- .../jpa/JPAQueryFactoryConfigurer.java | 2 +- .../jpa/QuerydslJPAAutoConfiguration.java | 4 +- .../sql/QuerydslSQLAutoConfiguration.java | 4 +- .../sql/SQLQueryFactoryConfigurer.java | 2 +- .../util/PropertyNameConsts.java | 2 +- ...ot.autoconfigure.AutoConfiguration.imports | 8 +- .../apijson/ApijsonAutoConfigurationTest.java | 6 +- .../ApijsonFunctionParserConfigurerTest.java | 3 +- .../apijson/ApijsonInitConfigurationTest.java | 2 +- .../apijson/ApijsonPropertiesTest.java | 3 +- .../ApijsonSqlConfigConfigurerTest.java | 3 +- .../apijson/ApijsonSqlPropertiesTest.java | 5 +- .../apijson/ApijsonUtilsTest.java | 3 +- .../ApijsonVerifierConfigurerTest.java | 3 +- .../NewIdStrategyConfigurationTest.java | 6 +- .../apijson/RestApiAutoConfigurationTest.java | 12 +-- .../condition/ApllicationConditionTest.java | 6 +- .../condition/NewIdStrategyConditionTest.java | 7 +- .../captcha/CaptchaAutoConfigurationTest.java | 2 +- .../captcha/IdCaptchaConfigurationTest.java | 10 +-- .../condition/EnumPropertyConditionTest.java | 4 +- .../id/IdIdConfigurationTest.java | 2 +- .../QuerydslJPAAutoConfigurationTest.java | 8 +- .../QuerydslSQLAutoConfigurationTest.java | 8 +- .../autoconfigure/test/TestUtils.java | 2 +- .../src/test/resources/logback-test.xml | 2 +- dependencies/pom.xml | 26 +++--- examples/example-apijson-fastjson2/pom.xml | 4 +- .../ApijsonFastjson2Configuration.java | 8 +- .../ApijsonFastjson2ExampleApplication.java | 2 +- .../CustomFunctionParserConfigurer.java | 4 +- .../config/CustomSqlConfigConfigurer.java | 4 +- .../config/CustomVerifierConfigurer.java | 4 +- .../fastjson2/CustomFastjson2Creator.java | 6 +- .../CustomFastjson2FunctionParser.java | 10 +-- .../src/main/resources/application.yml | 2 +- examples/example-apijson-gson/pom.xml | 4 +- .../apijson/ApijsonGsonConfiguration.java | 8 +- .../ApijsonGsonExampleApplication.java | 2 +- .../CustomFunctionParserConfigurer.java | 4 +- .../config/CustomSqlConfigConfigurer.java | 4 +- .../config/CustomVerifierConfigurer.java | 4 +- .../apijson/gson/CustomGsonCreator.java | 6 +- .../gson/CustomGsonFunctionParser.java | 8 +- .../src/main/resources/application.yml | 2 +- examples/example-captcha/pom.xml | 4 +- .../captcha/CaptchaCommandLineRunner.java | 10 +-- .../example/captcha/CaptchaController.java | 6 +- .../captcha/CaptchaExampleApplication.java | 2 +- .../src/main/resources/application.yml | 2 +- examples/example-id/pom.xml | 4 +- .../example/id/IdCommandLineRunner.java | 2 +- .../example/id/IdExampleApplication.java | 2 +- .../src/main/resources/application.yml | 2 +- examples/example-querydsl-jpa/pom.xml | 4 +- .../querydsl/QueryDSLJPAConfiguration.java | 4 +- .../QueryDSLJPAExampleApplication.java | 2 +- .../querydsl/UserCommandLineRunner.java | 6 +- .../example/querydsl/jpa/Order.java | 2 +- .../example/querydsl/jpa/User.java | 2 +- .../querydsl/jpa/UserJPARepository.java | 22 +++--- .../example/querydsl/jpa/UserJPAService.java | 2 +- .../example/querydsl/jpa/UserQuery.java | 8 +- .../example/querydsl/jpa/UserSpec.java | 4 +- .../src/main/resources/application.yml | 2 +- examples/example-querydsl-sql/pom.xml | 73 +++++++++-------- .../querydsl/QueryDSLSQLConfiguration.java | 4 +- .../QueryDSLSQLExampleApplication.java | 2 +- .../querydsl/UserCommandLineRunner.java | 6 +- .../example/querydsl/sql/QOrders.java | 2 +- .../example/querydsl/sql/QUsers.java | 2 +- .../example/querydsl/sql/User.java | 2 +- .../example/querydsl/sql/UserPre.java | 4 +- .../example/querydsl/sql/UserQuery.java | 8 +- .../querydsl/sql/UserSQLRepository.java | 8 +- .../example/querydsl/sql/UserSQLService.java | 2 +- .../example/querydsl/sql/UserSpec.java | 4 +- .../src/main/resources/application.yml | 2 +- examples/pom.xml | 4 +- extensions/extension-apijson/pom.xml | 4 +- .../extension/apjson/_APIJSON.java | 2 +- .../apjson/annotation/ApijsonRest.java | 2 +- .../extension/apjson/orm/GsonMap.java | 2 +- .../apjson/orm/IdKeyApijsonStrategy.java | 2 +- .../extension/apjson/orm/IdKeyStrategy.java | 2 +- .../apjson/orm/NewIdDatabaseStrategy.java | 2 +- .../apjson/orm/NewIdExceptionStrategy.java | 2 +- .../apjson/orm/NewIdSnowflakeStrategy.java | 2 +- .../extension/apjson/orm/NewIdStrategy.java | 2 +- .../apjson/orm/NewIdTimestampStrategy.java | 4 +- .../apjson/orm/NewIdUuidStrategy.java | 2 +- .../apjson/orm/SqlConnectProvider.java | 2 +- .../extension/apjson/util/ApijsonConsts.java | 2 +- extensions/extension-captcha/pom.xml | 4 +- .../extension/captcha/_Captcha.java | 2 +- .../hutool/AbstractCaptchaBuilder.java | 4 +- .../hutool/AbstractCaptchaService.java | 8 +- .../captcha/hutool/CaptchaException.java | 4 +- .../captcha/hutool/CircleCaptchaBuilder.java | 2 +- .../captcha/hutool/CircleCaptchaService.java | 6 +- .../captcha/hutool/CodeGeneratorType.java | 2 +- .../captcha/hutool/GifCaptchaBuilder.java | 2 +- .../captcha/hutool/GifCaptchaService.java | 6 +- .../captcha/hutool/LineCaptchaBuilder.java | 2 +- .../captcha/hutool/LineCaptchaService.java | 6 +- .../captcha/hutool/ShearCaptchaBuilder.java | 2 +- .../captcha/hutool/ShearCaptchaService.java | 6 +- .../extension/captcha/CaptchaJFrameDemo.java | 8 +- extensions/extension-common/pom.xml | 2 +- .../common/algorithm/GaussianBlur.java | 2 +- .../common/captcha/CaptchaCategory.java | 4 +- .../extension/common/captcha/CaptchaData.java | 2 +- .../common/captcha/CaptchaReponse.java | 2 +- .../common/captcha/CaptchaService.java | 2 +- .../common/captcha/CaptchaValidate.java | 2 +- .../generator/TimestampIdGenerator.java | 2 +- .../extension/common/lang/EnumCache.java | 2 +- .../extension/common/model/ColorType.java | 2 +- .../extension/common/model/FontStyle.java | 2 +- .../common/model/TransparencyType.java | 2 +- .../common/util/CommonRuntimeException.java | 2 +- .../algorithm/GaussianBlurJFrameDemo.java | 2 +- .../common/algorithm/GaussianBlurTest.java | 2 +- .../generator/TimestampIdGeneratorTest.java | 2 +- .../extension/common/lang/EnumCacheTest.java | 2 +- extensions/extension-id/pom.xml | 4 +- .../springboot}/extension/id/_Id.java | 2 +- extensions/extension-querydsl/pom.xml | 2 +- .../extension/querydsl/QSpecification.java | 2 +- .../extension/querydsl/QueryDSLUtils.java | 2 +- .../querydsl/SpecificationComposition.java | 2 +- .../extension/querydsl/_Querydsl.java | 2 +- .../querydsl/jpa/JPAQueryCurdExecutor.java | 4 +- .../jpa/JPAQueryRepositorySupport.java | 6 +- .../querydsl/sql/SQLQueryCurdExecutor.java | 4 +- .../sql/SQLQueryRepositorySupport.java | 6 +- .../querydsl/QSpecificationTest.java | 4 +- .../jpa/JPAQueryRepositorySupportTest.java | 6 +- .../extension/querydsl/jpa/Order.java | 2 +- .../extension/querydsl/jpa/User.java | 2 +- .../extension/querydsl/sql/Order.java | 2 +- .../extension/querydsl/sql/QOrders.java | 2 +- .../extension/querydsl/sql/QUsers.java | 2 +- .../sql/SQLQueryRepositorySupportTest.java | 19 ++--- .../extension/querydsl/sql/User.java | 2 +- .../src/test/resources/logback-test.xml | 2 +- extensions/pom.xml | 2 +- pom.xml | 2 +- projects/pom.xml | 4 +- projects/rest-query-language/pom.xml | 4 +- .../rql/DataInitCommandLineRunner.java | 10 +-- .../project/rql/RqlProjectApplication.java | 2 +- .../dao/GenericSpecificationsBuilder.java | 6 +- .../rql/persistence/dao/MyUserPredicate.java | 6 +- .../dao/MyUserPredicatesBuilder.java | 4 +- .../rql/persistence/dao/MyUserRepository.java | 6 +- .../rql/persistence/dao/UserRepository.java | 6 +- .../dao/UserSearchQueryCriteriaConsumer.java | 4 +- .../persistence/dao/UserSpecification.java | 6 +- .../dao/UserSpecificationsBuilder.java | 8 +- .../dao/rsql/CustomRsqlVisitor.java | 2 +- .../dao/rsql/GenericRsqlSpecBuilder.java | 2 +- .../dao/rsql/GenericRsqlSpecification.java | 2 +- .../dao/rsql/RsqlSearchOperation.java | 2 +- .../project/rql/persistence/model/MyUser.java | 2 +- .../project/rql/persistence/model/User.java | 2 +- .../project/rql/persistence/model/User_.java | 2 +- .../rql/web/controller/UserController.java | 16 ++-- .../RestResponseEntityExceptionHandler.java | 4 +- .../MyResourceNotFoundException.java | 2 +- .../project/rql/web/util/CriteriaParser.java | 2 +- .../project/rql/web/util/SearchCriteria.java | 2 +- .../project/rql/web/util/SearchOperation.java | 2 +- .../rql/web/util/SpecSearchCriteria.java | 2 +- .../src/main/resources/application.yml | 2 +- .../JPACriteriaQueryIntegrationTest.java | 8 +- .../query/JPAQuerydslIntegrationTest.java | 8 +- .../JPASpecificationIntegrationTest.java | 20 ++--- .../query/RsqlIntegrationTest.java | 8 +- .../pom.xml | 6 +- starters/README.md | 12 +-- starters/pom.xml | 4 +- starters/starter-apijson-fastjson2/pom.xml | 6 +- starters/starter-apijson-gson/pom.xml | 6 +- starters/starter-captcha/pom.xml | 6 +- starters/starter-id/pom.xml | 6 +- starters/starter-querydsl-jpa/pom.xml | 6 +- starters/starter-querydsl-sql/pom.xml | 6 +- 243 files changed, 645 insertions(+), 573 deletions(-) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java (88%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java (91%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java (97%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonInitializingBean.java (98%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonParserProperties.java (94%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonProperties.java (95%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java (92%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlProperties.java (92%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java (99%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java (95%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierProperties.java (86%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java (92%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java (91%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java (93%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationCondition.java (77%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyCondition.java (86%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Creator.java (88%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2EXtRestController.java (98%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2FunctionParser.java (86%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2InitializingBean.java (89%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2ObjectParser.java (96%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Parser.java (95%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2RestController.java (89%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SimpleCallback.java (90%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlConfig.java (81%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlExecutor.java (89%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Verifier.java (79%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Privacy.java (98%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/User.java (98%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Verify.java (97%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonCreator.java (88%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonEXtRestController.java (98%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonFunctionParser.java (86%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonInitializingBean.java (88%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonObjectParser.java (96%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonParser.java (95%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonRestController.java (89%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonSimpleCallback.java (89%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlConfig.java (81%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlExecutor.java (89%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/GsonVerifier.java (80%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/model/Privacy.java (97%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/model/User.java (97%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/apijson/gson/model/Verify.java (97%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java (86%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java (79%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java (97%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java (95%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/condition/EnumPropertyCondition.java (98%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java (97%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java (87%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java (75%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/querydsl/jpa/JPAQueryFactoryConfigurer.java (83%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/querydsl/jpa/QuerydslJPAAutoConfiguration.java (93%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/querydsl/sql/QuerydslSQLAutoConfiguration.java (95%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/querydsl/sql/SQLQueryFactoryConfigurer.java (83%) rename autoconfigure/src/main/java/{io => }/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java (97%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java (90%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java (95%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java (98%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java (90%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java (96%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java (90%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java (94%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java (96%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java (94%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java (86%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java (90%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java (93%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java (95%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java (77%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java (95%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java (95%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java (85%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java (90%) rename autoconfigure/src/test/java/{io => }/yunjiao/springboot/autoconfigure/test/TestUtils.java (90%) rename examples/example-apijson-fastjson2/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/ApijsonFastjson2Configuration.java (84%) rename examples/example-apijson-fastjson2/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/ApijsonFastjson2ExampleApplication.java (89%) rename examples/{example-apijson-gson/src/main/java/io/yunjiao => example-apijson-fastjson2/src/main/java/yunjiao/springboot}/example/apijson/config/CustomFunctionParserConfigurer.java (82%) rename examples/example-apijson-fastjson2/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/config/CustomSqlConfigConfigurer.java (97%) rename examples/{example-apijson-gson/src/main/java/io/yunjiao => example-apijson-fastjson2/src/main/java/yunjiao/springboot}/example/apijson/config/CustomVerifierConfigurer.java (87%) rename examples/example-apijson-fastjson2/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/fastjson2/CustomFastjson2Creator.java (71%) rename examples/example-apijson-fastjson2/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/fastjson2/CustomFastjson2FunctionParser.java (94%) rename examples/example-apijson-gson/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/ApijsonGsonConfiguration.java (85%) rename examples/example-apijson-gson/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/ApijsonGsonExampleApplication.java (89%) rename examples/{example-apijson-fastjson2/src/main/java/io/yunjiao => example-apijson-gson/src/main/java/yunjiao/springboot}/example/apijson/config/CustomFunctionParserConfigurer.java (82%) rename examples/example-apijson-gson/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/config/CustomSqlConfigConfigurer.java (97%) rename examples/{example-apijson-fastjson2/src/main/java/io/yunjiao => example-apijson-gson/src/main/java/yunjiao/springboot}/example/apijson/config/CustomVerifierConfigurer.java (87%) rename examples/example-apijson-gson/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/gson/CustomGsonCreator.java (72%) rename examples/example-apijson-gson/src/main/java/{io/yunjiao => yunjiao/springboot}/example/apijson/gson/CustomGsonFunctionParser.java (95%) rename examples/example-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/example/captcha/CaptchaCommandLineRunner.java (83%) rename examples/example-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/example/captcha/CaptchaController.java (90%) rename examples/example-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/example/captcha/CaptchaExampleApplication.java (89%) rename examples/example-id/src/main/java/{io/yunjiao => yunjiao/springboot}/example/id/IdCommandLineRunner.java (93%) rename examples/example-id/src/main/java/{io/yunjiao => yunjiao/springboot}/example/id/IdExampleApplication.java (89%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/QueryDSLJPAConfiguration.java (82%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/QueryDSLJPAExampleApplication.java (89%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/UserCommandLineRunner.java (94%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/jpa/Order.java (92%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/jpa/User.java (94%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/jpa/UserJPARepository.java (86%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/jpa/UserJPAService.java (95%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/jpa/UserQuery.java (82%) rename examples/example-querydsl-jpa/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/jpa/UserSpec.java (86%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/QueryDSLSQLConfiguration.java (86%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/QueryDSLSQLExampleApplication.java (89%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/UserCommandLineRunner.java (94%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/sql/QOrders.java (98%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/sql/QUsers.java (98%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/sql/User.java (84%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/sql/UserPre.java (86%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/sql/UserQuery.java (86%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/sql/UserSQLRepository.java (95%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/sql/UserSQLService.java (95%) rename examples/example-querydsl-sql/src/main/java/{io/yunjiao => yunjiao/springboot}/example/querydsl/sql/UserSpec.java (86%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/_APIJSON.java (61%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/annotation/ApijsonRest.java (87%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/GsonMap.java (97%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/IdKeyApijsonStrategy.java (85%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/IdKeyStrategy.java (90%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/NewIdDatabaseStrategy.java (88%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/NewIdExceptionStrategy.java (89%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/NewIdSnowflakeStrategy.java (91%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/NewIdStrategy.java (92%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/NewIdTimestampStrategy.java (81%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/NewIdUuidStrategy.java (89%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/orm/SqlConnectProvider.java (96%) rename extensions/extension-apijson/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/apjson/util/ApijsonConsts.java (93%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/_Captcha.java (61%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/AbstractCaptchaBuilder.java (93%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/AbstractCaptchaService.java (89%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/CaptchaException.java (70%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/CircleCaptchaBuilder.java (87%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/CircleCaptchaService.java (84%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/CodeGeneratorType.java (97%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/GifCaptchaBuilder.java (95%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/GifCaptchaService.java (84%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/LineCaptchaBuilder.java (86%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/LineCaptchaService.java (84%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/ShearCaptchaBuilder.java (86%) rename extensions/extension-captcha/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/hutool/ShearCaptchaService.java (84%) rename extensions/extension-captcha/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/captcha/CaptchaJFrameDemo.java (95%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/algorithm/GaussianBlur.java (98%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/captcha/CaptchaCategory.java (92%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/captcha/CaptchaData.java (96%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/captcha/CaptchaReponse.java (93%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/captcha/CaptchaService.java (91%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/captcha/CaptchaValidate.java (86%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/generator/TimestampIdGenerator.java (88%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/lang/EnumCache.java (99%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/model/ColorType.java (95%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/model/FontStyle.java (88%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/model/TransparencyType.java (96%) rename extensions/extension-common/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/common/util/CommonRuntimeException.java (92%) rename extensions/extension-common/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/common/algorithm/GaussianBlurJFrameDemo.java (99%) rename extensions/extension-common/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/common/algorithm/GaussianBlurTest.java (99%) rename extensions/extension-common/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/common/generator/TimestampIdGeneratorTest.java (92%) rename extensions/extension-common/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/common/lang/EnumCacheTest.java (97%) rename extensions/extension-id/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/id/_Id.java (60%) rename extensions/extension-querydsl/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/QSpecification.java (99%) rename extensions/extension-querydsl/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/QueryDSLUtils.java (97%) rename extensions/extension-querydsl/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/SpecificationComposition.java (97%) rename extensions/extension-querydsl/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/_Querydsl.java (61%) rename extensions/extension-querydsl/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/jpa/JPAQueryCurdExecutor.java (99%) rename extensions/extension-querydsl/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/jpa/JPAQueryRepositorySupport.java (97%) rename extensions/extension-querydsl/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/sql/SQLQueryCurdExecutor.java (99%) rename extensions/extension-querydsl/src/main/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/sql/SQLQueryRepositorySupport.java (97%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/QSpecificationTest.java (98%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java (98%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/jpa/Order.java (91%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/jpa/User.java (94%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/sql/Order.java (84%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/sql/QOrders.java (98%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/sql/QUsers.java (98%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/sql/SQLQueryRepositorySupportTest.java (93%) rename extensions/extension-querydsl/src/test/java/{io/yunjiao => yunjiao/springboot}/extension/querydsl/sql/User.java (84%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/DataInitCommandLineRunner.java (89%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/RqlProjectApplication.java (88%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/GenericSpecificationsBuilder.java (94%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/MyUserPredicate.java (89%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/MyUserPredicatesBuilder.java (93%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/MyUserRepository.java (84%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/UserRepository.java (82%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/UserSearchQueryCriteriaConsumer.java (92%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/UserSpecification.java (90%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/UserSpecificationsBuilder.java (90%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/rsql/CustomRsqlVisitor.java (93%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/rsql/GenericRsqlSpecBuilder.java (96%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/rsql/GenericRsqlSpecification.java (98%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/dao/rsql/RsqlSearchOperation.java (94%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/model/MyUser.java (87%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/model/User.java (84%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/model/User_.java (89%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/web/controller/UserController.java (90%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/web/error/RestResponseEntityExceptionHandler.java (96%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/web/exception/MyResourceNotFoundException.java (89%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/web/util/CriteriaParser.java (98%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/web/util/SearchCriteria.java (94%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/web/util/SearchOperation.java (95%) rename projects/rest-query-language/src/main/java/{io/yunjiao => yunjiao/springboot}/project/rql/web/util/SpecSearchCriteria.java (97%) rename projects/rest-query-language/src/test/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/query/JPACriteriaQueryIntegrationTest.java (92%) rename projects/rest-query-language/src/test/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/query/JPAQuerydslIntegrationTest.java (91%) rename projects/rest-query-language/src/test/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/query/JPASpecificationIntegrationTest.java (90%) rename projects/rest-query-language/src/test/java/{io/yunjiao => yunjiao/springboot}/project/rql/persistence/query/RsqlIntegrationTest.java (92%) rename {spring-boot-starter-parent => starter-parent}/pom.xml (97%) diff --git a/FAQ.md b/FAQ.md index b169ecb..5fd20df 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,5 +1,6 @@ # FAQ +* Table "USERS" not found ```text Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "USERS" not found (candidates are: "user"); SQL statement: select user.age, user.birthDate, user.created_at, user.email, user.id, user.name @@ -10,6 +11,7 @@ limit ? [42103-232] 使用`H2`作为单元测试时,表名称大小写的问题,`from "user"` 这种写法是小写的表, `from user`是大写的表。 在`url`上添加`;DATABASE_TO_UPPER=FALSE`可以解决问题 +* EntityManager 没有初始化 ```text JPAQueryFactoryAutoConfiguration: Did not match: @@ -19,6 +21,7 @@ limit ? [42103-232] ``` 尽量不要在配置类上使用 `@ConditionalOnSingleCandidate` 或 `@ConditionalOnMissingBean`, spring会自动判断Bean初始化的顺序 +* [ERROR] Some problems were encountered while processing the POMs ```text [ERROR] [ERROR] Some problems were encountered while processing the POMs: [FATAL] Non-resolvable parent POM for io.gitee.yunjiao-source:spring-boot-examples:${revision}: The following artifacts could not be resolved: io.gitee.yunjiao-source:spring-boot-starter-parent:pom:${revision} (absent): io.gitee.yunjiao-source:spring-boot-starter-parent:pom:${revision} was not found in https://repo.maven.apache.org/maven2 during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until the update interval of central has elapsed or updates are forced and 'parent.relativePath' points at no local POM @ line 5, column 13 @@ -40,6 +43,7 @@ flatten命令必须成功 mvn install ``` +* examples项目install时出现错误 ```text examples项目install时出现错误 @@ -62,9 +66,82 @@ examples项目install时出现错误 ``` 这是因为`QueryDsl SQL`编译时会生成Q类,这需要数据库连接,在`pom.xml`中修改数据库连接信息 +* Attempt to recreate a file for type yunjiao.springboot.example.querydsl.jpa.QOrder ```text -Attempt to recreate a file for type io.yunjiao.example.querydsl.jpa.QOrder +Attempt to recreate a file for type yunjiao.springboot.example.querydsl.jpa.QOrder ``` 通常在编译,安装项目时出现此异常,解决方法是`pom.xml` 文件中移除 `apt-maven-plugin` 即可 +* 运行`extension-querydsl`测试时异常 +```text +java.lang.IllegalArgumentException: org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'User' + + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:143) + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:167) + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:173) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:886) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:796) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:143) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy2/jdk.proxy2.$Proxy57.createQuery(Unknown Source) + at com.querydsl.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:132) + at com.querydsl.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:125) + at com.querydsl.jpa.impl.AbstractJPAQuery.fetch(AbstractJPAQuery.java:242) + at yunjiao.springboot.extension.querydsl.jpa.JPAQueryCurdExecutor.findList(JPAQueryCurdExecutor.java:393) + at yunjiao.springboot.extension.querydsl.jpa.JPAQueryCurdExecutor.findList(JPAQueryCurdExecutor.java:346) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:360) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at yunjiao.springboot.extension.querydsl.jpa.JPAQueryCurdExecutor$$SpringCGLIB$$0.findList() + at yunjiao.springboot.extension.querydsl.jpa.JPAQueryRepositorySupportTest$DemoJPAQueryRepositorySupport.findUserByOrderStatus(JPAQueryRepositorySupportTest.java:193) + at yunjiao.springboot.extension.querydsl.jpa.JPAQueryRepositorySupportTest.whenFindUserByOrderStatus(JPAQueryRepositorySupportTest.java:124) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) +Caused by: org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'User' + at org.hibernate.query.hql.internal.SemanticQueryBuilder.resolveRootEntity(SemanticQueryBuilder.java:2129) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitRootEntity(SemanticQueryBuilder.java:2059) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitRootEntity(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$RootEntityContext.accept(HqlParser.java:2814) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitEntityWithJoins(SemanticQueryBuilder.java:2029) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitFromClause(SemanticQueryBuilder.java:2016) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuery(SemanticQueryBuilder.java:1248) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:1040) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$QuerySpecExpressionContext.accept(HqlParser.java:2134) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:1025) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$SimpleQueryGroupContext.accept(HqlParser.java:2005) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectStatement(SemanticQueryBuilder.java:492) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitStatement(SemanticQueryBuilder.java:451) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.buildSemanticModel(SemanticQueryBuilder.java:324) + at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:71) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.createHqlInterpretation(QueryInterpretationCacheStandardImpl.java:145) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.resolveHqlInterpretation(QueryInterpretationCacheStandardImpl.java:132) + at org.hibernate.query.spi.QueryEngine.interpretHql(QueryEngine.java:54) + at org.hibernate.internal.AbstractSharedSessionContract.interpretHql(AbstractSharedSessionContract.java:832) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:878) + ... 26 more +``` + +测试用例JPAQueryRepositorySupportTest中,hibernate扫描路径需要修改 + +```text + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { +...... + em.setPackagesToScan("io.yunjiao"); + 改成 + em.setPackagesToScan("yunjiao.springboot"); +...... + return em; + }``` \ No newline at end of file diff --git a/README.md b/README.md index 5cc7ba7..b859b44 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ mvn install ```text - io.gitee.yunjiao-source - spring-boot-starter-parent + io.gitee.yunjiao-source.spring-boot + starter-parent ${你需要的版本} @@ -70,8 +70,8 @@ mvn install - io.gitee.yunjiao-source - spring-boot-dependencies + io.gitee.yunjiao-source.spring-boot + dependencies ${你需要的版本} pom import diff --git a/autoconfigure/pom.xml b/autoconfigure/pom.xml index c507b49..83dac48 100644 --- a/autoconfigure/pom.xml +++ b/autoconfigure/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot dependencies ${revision} ../dependencies/pom.xml @@ -28,27 +28,27 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-common true - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-apijson true - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-querydsl true - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-captcha true - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-id true diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java similarity index 88% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java index d633a7d..d59e506 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfiguration.java @@ -1,10 +1,10 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.extension.apjson._APIJSON; -import io.yunjiao.extension.apjson.annotation.ApijsonRest; -import io.yunjiao.extension.apjson.orm.IdKeyApijsonStrategy; -import io.yunjiao.extension.apjson.orm.IdKeyStrategy; +import yunjiao.springboot.extension.apjson._APIJSON; +import yunjiao.springboot.extension.apjson.annotation.ApijsonRest; +import yunjiao.springboot.extension.apjson.orm.IdKeyApijsonStrategy; +import yunjiao.springboot.extension.apjson.orm.IdKeyStrategy; import jakarta.annotation.Nonnull; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java similarity index 91% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java index b9e403f..c42e8ea 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurer.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import apijson.orm.script.ScriptExecutor; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java similarity index 97% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java index cefe1c2..b637995 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfiguration.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitializingBean.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitializingBean.java similarity index 98% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitializingBean.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitializingBean.java index 416a036..0be497b 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitializingBean.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitializingBean.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import apijson.framework.*; import lombok.RequiredArgsConstructor; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonParserProperties.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonParserProperties.java similarity index 94% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonParserProperties.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonParserProperties.java index d90e96e..51c910d 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonParserProperties.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonParserProperties.java @@ -1,6 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonProperties.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonProperties.java similarity index 95% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonProperties.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonProperties.java index 45b67dd..ab85bfb 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonProperties.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonProperties.java @@ -1,6 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java similarity index 92% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java index 70ffe09..c9d2732 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurer.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import java.util.List; import java.util.Map; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlProperties.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlProperties.java similarity index 92% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlProperties.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlProperties.java index 44a6f3c..d919bed 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlProperties.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlProperties.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.extension.apjson.util.ApijsonConsts; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.extension.apjson.util.ApijsonConsts; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java similarity index 99% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java index edd924f..d161e3e 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonUtils.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import apijson.RequestMethod; import apijson.framework.*; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java similarity index 95% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java index a00fbd2..0ae797c 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurer.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import apijson.RequestMethod; import apijson.orm.Entry; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierProperties.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierProperties.java similarity index 86% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierProperties.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierProperties.java index 318765f..1b1d309 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierProperties.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierProperties.java @@ -1,6 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java similarity index 92% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java index 0f46924..6bd0086 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/Fastjson2ApplicationConfiguration.java @@ -1,10 +1,10 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.extension.apjson.orm.IdKeyStrategy; -import io.yunjiao.extension.apjson.orm.NewIdStrategy; -import io.yunjiao.springboot.autoconfigure.apijson.condition.ApllicationCondition; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.*; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.extension.apjson.orm.IdKeyStrategy; +import yunjiao.springboot.extension.apjson.orm.NewIdStrategy; +import yunjiao.springboot.autoconfigure.apijson.condition.ApllicationCondition; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.*; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java similarity index 91% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java index b96a2fb..9fff3d6 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/GsonApplicationConfiguration.java @@ -1,10 +1,10 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.extension.apjson.orm.IdKeyStrategy; -import io.yunjiao.extension.apjson.orm.NewIdStrategy; -import io.yunjiao.springboot.autoconfigure.apijson.condition.ApllicationCondition; -import io.yunjiao.springboot.autoconfigure.apijson.gson.*; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.extension.apjson.orm.IdKeyStrategy; +import yunjiao.springboot.extension.apjson.orm.NewIdStrategy; +import yunjiao.springboot.autoconfigure.apijson.condition.ApllicationCondition; +import yunjiao.springboot.autoconfigure.apijson.gson.*; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java similarity index 93% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java index 5eb0392..e8369a2 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfiguration.java @@ -1,8 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import cn.hutool.core.lang.Snowflake; -import io.yunjiao.extension.apjson.orm.*; -import io.yunjiao.springboot.autoconfigure.apijson.condition.NewIdStrategyCondition; +import yunjiao.springboot.autoconfigure.apijson.condition.NewIdStrategyCondition; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -10,6 +9,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import yunjiao.springboot.extension.apjson.orm.*; /** * {@link NewIdStrategy}实现类自动配置 diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationCondition.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationCondition.java similarity index 77% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationCondition.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationCondition.java index 91f3bbc..f853da3 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationCondition.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationCondition.java @@ -1,9 +1,9 @@ -package io.yunjiao.springboot.autoconfigure.apijson.condition; +package yunjiao.springboot.autoconfigure.apijson.condition; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; -import io.yunjiao.springboot.autoconfigure.condition.EnumPropertyCondition; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; +import yunjiao.springboot.autoconfigure.condition.EnumPropertyCondition; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; /** * 应用程序类型条件 diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyCondition.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyCondition.java similarity index 86% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyCondition.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyCondition.java index 536fd8d..20e4abd 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyCondition.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyCondition.java @@ -1,8 +1,8 @@ -package io.yunjiao.springboot.autoconfigure.apijson.condition; +package yunjiao.springboot.autoconfigure.apijson.condition; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; -import io.yunjiao.springboot.autoconfigure.condition.EnumPropertyCondition; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; +import yunjiao.springboot.autoconfigure.condition.EnumPropertyCondition; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; /** * 主键生成策略条件 diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Creator.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Creator.java similarity index 88% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Creator.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Creator.java index 7544771..05b06b5 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Creator.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Creator.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.fastjson2.*; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; import lombok.RequiredArgsConstructor; import javax.sql.DataSource; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2EXtRestController.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2EXtRestController.java similarity index 98% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2EXtRestController.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2EXtRestController.java index e2e765c..9d66cc0 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2EXtRestController.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2EXtRestController.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.Log; import apijson.StringUtil; @@ -9,10 +9,10 @@ import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; import apijson.orm.exception.NotExistException; import com.alibaba.fastjson2.JSONObject; -import io.yunjiao.extension.apjson.annotation.ApijsonRest; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.model.Privacy; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.model.User; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.model.Verify; +import yunjiao.springboot.extension.apjson.annotation.ApijsonRest; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.model.Privacy; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.model.User; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.model.Verify; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2FunctionParser.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2FunctionParser.java similarity index 86% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2FunctionParser.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2FunctionParser.java index f4bfa6b..680d912 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2FunctionParser.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2FunctionParser.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.fastjson2.APIJSONFunctionParser; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2InitializingBean.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2InitializingBean.java similarity index 89% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2InitializingBean.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2InitializingBean.java index 41d1405..cd25562 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2InitializingBean.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2InitializingBean.java @@ -1,9 +1,9 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.Log; import apijson.fastjson2.*; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonUtils; +import yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; +import yunjiao.springboot.autoconfigure.apijson.ApijsonUtils; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.InitializingBean; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2ObjectParser.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2ObjectParser.java similarity index 96% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2ObjectParser.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2ObjectParser.java index 85a3080..ce84f46 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2ObjectParser.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2ObjectParser.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.RequestMethod; import apijson.fastjson2.APIJSONObjectParser; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Parser.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Parser.java similarity index 95% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Parser.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Parser.java index d9aa07c..6d7905a 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Parser.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Parser.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.RequestMethod; import apijson.fastjson2.APIJSONObjectParser; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2RestController.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2RestController.java similarity index 89% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2RestController.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2RestController.java index 39f4bbc..eab62df 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2RestController.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2RestController.java @@ -1,10 +1,10 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.RequestMethod; import apijson.fastjson2.APIJSONController; import apijson.fastjson2.APIJSONParser; -import io.yunjiao.extension.apjson.annotation.ApijsonRest; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; +import yunjiao.springboot.extension.apjson.annotation.ApijsonRest; +import yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SimpleCallback.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SimpleCallback.java similarity index 90% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SimpleCallback.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SimpleCallback.java index 1a45d6a..e04683c 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SimpleCallback.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SimpleCallback.java @@ -1,11 +1,11 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.RequestMethod; import apijson.fastjson2.APIJSONApplication; import apijson.orm.AbstractSQLConfig; import apijson.orm.SQLConfig; -import io.yunjiao.extension.apjson.orm.IdKeyStrategy; -import io.yunjiao.extension.apjson.orm.NewIdStrategy; +import yunjiao.springboot.extension.apjson.orm.IdKeyStrategy; +import yunjiao.springboot.extension.apjson.orm.NewIdStrategy; import lombok.RequiredArgsConstructor; import java.io.Serializable; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlConfig.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlConfig.java similarity index 81% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlConfig.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlConfig.java index 870f1f5..64cf5de 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlConfig.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlConfig.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.fastjson2.APIJSONSQLConfig; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; import lombok.RequiredArgsConstructor; import java.io.Serializable; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlExecutor.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlExecutor.java similarity index 89% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlExecutor.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlExecutor.java index f392a53..7cda3f2 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlExecutor.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2SqlExecutor.java @@ -1,10 +1,10 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.fastjson2.APIJSONSQLExecutor; import apijson.orm.SQLConfig; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; -import io.yunjiao.extension.apjson.orm.SqlConnectProvider; +import yunjiao.springboot.extension.apjson.orm.SqlConnectProvider; import lombok.RequiredArgsConstructor; import javax.sql.DataSource; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Verifier.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Verifier.java similarity index 79% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Verifier.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Verifier.java index 6b9abe4..abfd701 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Verifier.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/Fastjson2Verifier.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2; +package yunjiao.springboot.autoconfigure.apijson.fastjson2; import apijson.fastjson2.APIJSONVerifier; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Privacy.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Privacy.java similarity index 98% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Privacy.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Privacy.java index 6b751f6..f034b40 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Privacy.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Privacy.java @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2.model; +package yunjiao.springboot.autoconfigure.apijson.fastjson2.model; import apijson.MethodAccess; import apijson.framework.BaseModel; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/User.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/User.java similarity index 98% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/User.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/User.java index f3a6176..225bc5c 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/User.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/User.java @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2.model; +package yunjiao.springboot.autoconfigure.apijson.fastjson2.model; import apijson.MethodAccess; import apijson.framework.BaseModel; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Verify.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Verify.java similarity index 97% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Verify.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Verify.java index 28f1a10..0f01e47 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Verify.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/fastjson2/model/Verify.java @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ -package io.yunjiao.springboot.autoconfigure.apijson.fastjson2.model; +package yunjiao.springboot.autoconfigure.apijson.fastjson2.model; import apijson.MethodAccess; import apijson.framework.BaseModel; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonCreator.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonCreator.java similarity index 88% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonCreator.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonCreator.java index 2dfab5f..ad86e85 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonCreator.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonCreator.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.gson.*; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; import lombok.RequiredArgsConstructor; import javax.sql.DataSource; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonEXtRestController.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonEXtRestController.java similarity index 98% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonEXtRestController.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonEXtRestController.java index 3334a48..180ddb3 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonEXtRestController.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonEXtRestController.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.Log; import apijson.StringUtil; @@ -10,11 +10,11 @@ import apijson.gson.JSONResponse; import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; import apijson.orm.exception.NotExistException; -import io.yunjiao.extension.apjson.annotation.ApijsonRest; -import io.yunjiao.extension.apjson.orm.GsonMap; -import io.yunjiao.springboot.autoconfigure.apijson.gson.model.Privacy; -import io.yunjiao.springboot.autoconfigure.apijson.gson.model.User; -import io.yunjiao.springboot.autoconfigure.apijson.gson.model.Verify; +import yunjiao.springboot.extension.apjson.annotation.ApijsonRest; +import yunjiao.springboot.extension.apjson.orm.GsonMap; +import yunjiao.springboot.autoconfigure.apijson.gson.model.Privacy; +import yunjiao.springboot.autoconfigure.apijson.gson.model.User; +import yunjiao.springboot.autoconfigure.apijson.gson.model.Verify; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonFunctionParser.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonFunctionParser.java similarity index 86% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonFunctionParser.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonFunctionParser.java index 8f4df51..69a74a2 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonFunctionParser.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonFunctionParser.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.gson.APIJSONFunctionParser; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonInitializingBean.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonInitializingBean.java similarity index 88% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonInitializingBean.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonInitializingBean.java index 2f7cbe5..06adb5b 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonInitializingBean.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonInitializingBean.java @@ -1,8 +1,8 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.gson.*; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonUtils; +import yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; +import yunjiao.springboot.autoconfigure.apijson.ApijsonUtils; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.InitializingBean; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonObjectParser.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonObjectParser.java similarity index 96% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonObjectParser.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonObjectParser.java index 987781d..5cc7701 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonObjectParser.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonObjectParser.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.RequestMethod; import apijson.gson.APIJSONObjectParser; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonParser.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonParser.java similarity index 95% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonParser.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonParser.java index d88f511..cac03cb 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonParser.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonParser.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.RequestMethod; import apijson.gson.APIJSONObjectParser; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonRestController.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonRestController.java similarity index 89% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonRestController.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonRestController.java index 395bde8..08c1e24 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonRestController.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonRestController.java @@ -1,10 +1,10 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.RequestMethod; import apijson.gson.APIJSONController; import apijson.gson.APIJSONParser; -import io.yunjiao.extension.apjson.annotation.ApijsonRest; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; +import yunjiao.springboot.extension.apjson.annotation.ApijsonRest; +import yunjiao.springboot.autoconfigure.apijson.ApijsonProperties; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSimpleCallback.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSimpleCallback.java similarity index 89% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSimpleCallback.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSimpleCallback.java index ad2ddae..9fd4f68 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSimpleCallback.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSimpleCallback.java @@ -1,12 +1,12 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.RequestMethod; import apijson.fastjson2.APIJSONApplication; import apijson.gson.APIJSONSQLConfig; import apijson.orm.AbstractSQLConfig; import apijson.orm.SQLConfig; -import io.yunjiao.extension.apjson.orm.IdKeyStrategy; -import io.yunjiao.extension.apjson.orm.NewIdStrategy; +import yunjiao.springboot.extension.apjson.orm.IdKeyStrategy; +import yunjiao.springboot.extension.apjson.orm.NewIdStrategy; import lombok.RequiredArgsConstructor; import java.io.Serializable; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlConfig.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlConfig.java similarity index 81% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlConfig.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlConfig.java index 2c64d97..cdbd6c6 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlConfig.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlConfig.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.gson.APIJSONSQLConfig; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; import lombok.RequiredArgsConstructor; import java.io.Serializable; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlExecutor.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlExecutor.java similarity index 89% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlExecutor.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlExecutor.java index 71db32d..f0dd05b 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlExecutor.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonSqlExecutor.java @@ -1,8 +1,8 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.gson.APIJSONSQLExecutor; import apijson.orm.SQLConfig; -import io.yunjiao.extension.apjson.orm.SqlConnectProvider; +import yunjiao.springboot.extension.apjson.orm.SqlConnectProvider; import lombok.RequiredArgsConstructor; import javax.sql.DataSource; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonVerifier.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonVerifier.java similarity index 80% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonVerifier.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonVerifier.java index ed1a38b..aeb276d 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/GsonVerifier.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/GsonVerifier.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson.gson; +package yunjiao.springboot.autoconfigure.apijson.gson; import apijson.gson.APIJSONVerifier; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/Privacy.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/Privacy.java similarity index 97% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/Privacy.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/Privacy.java index e23f579..ffed20c 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/Privacy.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/Privacy.java @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ -package io.yunjiao.springboot.autoconfigure.apijson.gson.model; +package yunjiao.springboot.autoconfigure.apijson.gson.model; import apijson.MethodAccess; import apijson.framework.BaseModel; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/User.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/User.java similarity index 97% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/User.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/User.java index 5a5881f..1fbed76 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/User.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/User.java @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ -package io.yunjiao.springboot.autoconfigure.apijson.gson.model; +package yunjiao.springboot.autoconfigure.apijson.gson.model; import apijson.MethodAccess; import apijson.framework.BaseModel; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/Verify.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/Verify.java similarity index 97% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/Verify.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/Verify.java index 19acb81..eb48d13 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/apijson/gson/model/Verify.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/apijson/gson/model/Verify.java @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ -package io.yunjiao.springboot.autoconfigure.apijson.gson.model; +package yunjiao.springboot.autoconfigure.apijson.gson.model; import apijson.MethodAccess; import apijson.framework.BaseModel; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java similarity index 86% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java index fc8e580..087990d 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.captcha; +package yunjiao.springboot.autoconfigure.captcha; -import io.yunjiao.extension.captcha._Captcha; -import io.yunjiao.extension.common.captcha.CaptchaService; +import yunjiao.springboot.extension.captcha._Captcha; +import yunjiao.springboot.extension.common.captcha.CaptchaService; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfiguration; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java similarity index 79% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java index 16c67c1..74597c6 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java @@ -1,9 +1,9 @@ -package io.yunjiao.springboot.autoconfigure.captcha; +package yunjiao.springboot.autoconfigure.captcha; -import io.yunjiao.extension.captcha.hutool.CaptchaException; -import io.yunjiao.extension.common.captcha.CaptchaCategory; -import io.yunjiao.extension.common.captcha.CaptchaService; -import io.yunjiao.extension.common.lang.EnumCache; +import yunjiao.springboot.extension.captcha.hutool.CaptchaException; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaService; +import yunjiao.springboot.extension.common.lang.EnumCache; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java similarity index 97% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java index 1539805..73bdf58 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java @@ -1,15 +1,15 @@ -package io.yunjiao.springboot.autoconfigure.captcha; +package yunjiao.springboot.autoconfigure.captcha; import cn.hutool.captcha.ICaptcha; import cn.hutool.core.lang.Assert; -import io.yunjiao.extension.captcha.hutool.*; -import io.yunjiao.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import yunjiao.springboot.extension.captcha.hutool.*; import java.awt.*; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java similarity index 95% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java index e52743f..09cf3a7 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaProperties.java @@ -1,9 +1,9 @@ -package io.yunjiao.springboot.autoconfigure.captcha; +package yunjiao.springboot.autoconfigure.captcha; -import io.yunjiao.extension.captcha.hutool.CodeGeneratorType; -import io.yunjiao.extension.common.model.ColorType; -import io.yunjiao.extension.common.model.FontStyle; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.extension.captcha.hutool.CodeGeneratorType; +import yunjiao.springboot.extension.common.model.ColorType; +import yunjiao.springboot.extension.common.model.FontStyle; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyCondition.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/condition/EnumPropertyCondition.java similarity index 98% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyCondition.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/condition/EnumPropertyCondition.java index 4458c84..cabe14f 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyCondition.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/condition/EnumPropertyCondition.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.condition; +package yunjiao.springboot.autoconfigure.condition; import lombok.extern.slf4j.Slf4j; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java similarity index 97% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java index d51a71b..21c758f 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.id; +package yunjiao.springboot.autoconfigure.id; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.util.IdUtil; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java similarity index 87% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java index e3f670f..abaf545 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/IdAutoConfiguration.java @@ -1,6 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.id; +package yunjiao.springboot.autoconfigure.id; -import io.yunjiao.extension.id._Id; +import yunjiao.springboot.extension.id._Id; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfiguration; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java similarity index 75% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java index f5244ea..f187620 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/QueryDSLAutoConfig.java @@ -1,8 +1,8 @@ -package io.yunjiao.springboot.autoconfigure.querydsl; +package yunjiao.springboot.autoconfigure.querydsl; -import io.yunjiao.extension.querydsl._Querydsl; -import io.yunjiao.springboot.autoconfigure.querydsl.jpa.QuerydslJPAAutoConfiguration; -import io.yunjiao.springboot.autoconfigure.querydsl.sql.QuerydslSQLAutoConfiguration; +import yunjiao.springboot.extension.querydsl._Querydsl; +import yunjiao.springboot.autoconfigure.querydsl.jpa.QuerydslJPAAutoConfiguration; +import yunjiao.springboot.autoconfigure.querydsl.sql.QuerydslSQLAutoConfiguration; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfiguration; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/jpa/JPAQueryFactoryConfigurer.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/jpa/JPAQueryFactoryConfigurer.java similarity index 83% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/jpa/JPAQueryFactoryConfigurer.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/jpa/JPAQueryFactoryConfigurer.java index d5107f1..ffb3f99 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/jpa/JPAQueryFactoryConfigurer.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/jpa/JPAQueryFactoryConfigurer.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.querydsl.jpa; +package yunjiao.springboot.autoconfigure.querydsl.jpa; import com.querydsl.jpa.impl.JPAQueryFactory; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/jpa/QuerydslJPAAutoConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/jpa/QuerydslJPAAutoConfiguration.java similarity index 93% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/jpa/QuerydslJPAAutoConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/jpa/QuerydslJPAAutoConfiguration.java index 2052dd5..df1294a 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/jpa/QuerydslJPAAutoConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/jpa/QuerydslJPAAutoConfiguration.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.querydsl.jpa; +package yunjiao.springboot.autoconfigure.querydsl.jpa; import com.querydsl.jpa.impl.JPAQueryFactory; -import io.yunjiao.extension.querydsl.jpa.JPAQueryCurdExecutor; +import yunjiao.springboot.extension.querydsl.jpa.JPAQueryCurdExecutor; import jakarta.annotation.PostConstruct; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/sql/QuerydslSQLAutoConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/sql/QuerydslSQLAutoConfiguration.java similarity index 95% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/sql/QuerydslSQLAutoConfiguration.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/sql/QuerydslSQLAutoConfiguration.java index f65e35c..d7442ec 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/sql/QuerydslSQLAutoConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/sql/QuerydslSQLAutoConfiguration.java @@ -1,11 +1,11 @@ -package io.yunjiao.springboot.autoconfigure.querydsl.sql; +package yunjiao.springboot.autoconfigure.querydsl.sql; import com.querydsl.sql.MySQLTemplates; import com.querydsl.sql.SQLQueryFactory; import com.querydsl.sql.SQLTemplates; import com.querydsl.sql.spring.SpringConnectionProvider; import com.querydsl.sql.spring.SpringExceptionTranslator; -import io.yunjiao.extension.querydsl.sql.SQLQueryCurdExecutor; +import yunjiao.springboot.extension.querydsl.sql.SQLQueryCurdExecutor; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/sql/SQLQueryFactoryConfigurer.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/sql/SQLQueryFactoryConfigurer.java similarity index 83% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/sql/SQLQueryFactoryConfigurer.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/sql/SQLQueryFactoryConfigurer.java index 38b6301..18a1ded 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/querydsl/sql/SQLQueryFactoryConfigurer.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/querydsl/sql/SQLQueryFactoryConfigurer.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.querydsl.sql; +package yunjiao.springboot.autoconfigure.querydsl.sql; import com.querydsl.sql.SQLQueryFactory; diff --git a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java similarity index 97% rename from autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java rename to autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java index 7ddc7be..9db5cea 100644 --- a/autoconfigure/src/main/java/io/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.util; +package yunjiao.springboot.autoconfigure.util; /** * 属性名称常量定义 diff --git a/autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 4461435..f693d01 100644 --- a/autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,4 +1,4 @@ -io.yunjiao.springboot.autoconfigure.querydsl.QueryDSLAutoConfig -io.yunjiao.springboot.autoconfigure.id.IdAutoConfiguration -io.yunjiao.springboot.autoconfigure.captcha.CaptchaAutoConfiguration -io.yunjiao.springboot.autoconfigure.apijson.ApijsonAutoConfiguration \ No newline at end of file +yunjiao.springboot.autoconfigure.querydsl.QueryDSLAutoConfig +yunjiao.springboot.autoconfigure.id.IdAutoConfiguration +yunjiao.springboot.autoconfigure.captcha.CaptchaAutoConfiguration +yunjiao.springboot.autoconfigure.apijson.ApijsonAutoConfiguration \ No newline at end of file diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java similarity index 90% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java index 45d434c..c118988 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonAutoConfigurationTest.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.extension.apjson.orm.IdKeyApijsonStrategy; -import io.yunjiao.extension.apjson.orm.IdKeyStrategy; +import yunjiao.springboot.extension.apjson.orm.IdKeyApijsonStrategy; +import yunjiao.springboot.extension.apjson.orm.IdKeyStrategy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java similarity index 95% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java index c0ebff3..0736b33 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonFunctionParserConfigurerTest.java @@ -1,9 +1,8 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import apijson.framework.APIJSONFunctionParser; import apijson.orm.AbstractFunctionParser; import apijson.orm.script.ScriptExecutor; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java similarity index 98% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java index b770af0..8ca7d33 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonInitConfigurationTest.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java similarity index 90% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java index e473671..e6a8746 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonPropertiesTest.java @@ -1,6 +1,5 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java similarity index 96% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java index 1bf514f..6cc802b 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlConfigConfigurerTest.java @@ -1,9 +1,8 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import apijson.framework.APIJSONSQLConfig; import apijson.framework.ColumnUtil; import apijson.orm.AbstractSQLConfig; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java similarity index 90% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java index f444fde..7b492d8 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonSqlPropertiesTest.java @@ -1,7 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.extension.apjson.util.ApijsonConsts; -import org.junit.jupiter.api.DisplayName; +import yunjiao.springboot.extension.apjson.util.ApijsonConsts; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java similarity index 94% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java index 94d9b3b..ab10655 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonUtilsTest.java @@ -1,7 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import apijson.StringUtil; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java similarity index 96% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java index 589914d..4e41c58 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/ApijsonVerifierConfigurerTest.java @@ -1,7 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import apijson.RequestMethod; -import apijson.framework.APIJSONFunctionParser; import apijson.framework.APIJSONVerifier; import apijson.orm.AbstractVerifier; import apijson.orm.Entry; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java similarity index 94% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java index 8fcb8ae..6bb1bcf 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/NewIdStrategyConfigurationTest.java @@ -1,13 +1,13 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; import cn.hutool.core.lang.Snowflake; -import io.yunjiao.extension.apjson.orm.*; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import yunjiao.springboot.extension.apjson.orm.*; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertInstanceOf; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java similarity index 86% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java index c870a2c..b630572 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/RestApiAutoConfigurationTest.java @@ -1,10 +1,10 @@ -package io.yunjiao.springboot.autoconfigure.apijson; +package yunjiao.springboot.autoconfigure.apijson; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2EXtRestController; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2RestController; -import io.yunjiao.springboot.autoconfigure.apijson.gson.GsonEXtRestController; -import io.yunjiao.springboot.autoconfigure.apijson.gson.GsonRestController; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2EXtRestController; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2RestController; +import yunjiao.springboot.autoconfigure.apijson.gson.GsonEXtRestController; +import yunjiao.springboot.autoconfigure.apijson.gson.GsonRestController; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java similarity index 90% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java index c8bad07..4e5c4da 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/condition/ApllicationConditionTest.java @@ -1,7 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson.condition; +package yunjiao.springboot.autoconfigure.apijson.condition; -import io.yunjiao.springboot.autoconfigure.test.TestUtils; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.autoconfigure.test.TestUtils; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java similarity index 93% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java index 96516de..df99d88 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/apijson/condition/NewIdStrategyConditionTest.java @@ -1,8 +1,7 @@ -package io.yunjiao.springboot.autoconfigure.apijson.condition; +package yunjiao.springboot.autoconfigure.apijson.condition; -import io.yunjiao.springboot.autoconfigure.test.TestUtils; -import io.yunjiao.springboot.autoconfigure.util.PropertyNameConsts; -import org.junit.jupiter.api.DisplayName; +import yunjiao.springboot.autoconfigure.test.TestUtils; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java similarity index 95% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java index 679340f..22d23dd 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.captcha; +package yunjiao.springboot.autoconfigure.captcha; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java similarity index 77% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java index d3f4a40..60c1ceb 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java @@ -1,9 +1,9 @@ -package io.yunjiao.springboot.autoconfigure.captcha; +package yunjiao.springboot.autoconfigure.captcha; -import io.yunjiao.extension.captcha.hutool.CircleCaptchaService; -import io.yunjiao.extension.captcha.hutool.GifCaptchaService; -import io.yunjiao.extension.captcha.hutool.LineCaptchaService; -import io.yunjiao.extension.captcha.hutool.ShearCaptchaService; +import yunjiao.springboot.extension.captcha.hutool.CircleCaptchaService; +import yunjiao.springboot.extension.captcha.hutool.GifCaptchaService; +import yunjiao.springboot.extension.captcha.hutool.LineCaptchaService; +import yunjiao.springboot.extension.captcha.hutool.ShearCaptchaService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java similarity index 95% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java index 3f1d6be..7042d5b 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/condition/EnumPropertyConditionTest.java @@ -1,6 +1,6 @@ -package io.yunjiao.springboot.autoconfigure.condition; +package yunjiao.springboot.autoconfigure.condition; -import io.yunjiao.springboot.autoconfigure.test.TestUtils; +import yunjiao.springboot.autoconfigure.test.TestUtils; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java similarity index 95% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java index b00e581..6393e70 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/id/IdIdConfigurationTest.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.id; +package yunjiao.springboot.autoconfigure.id; import cn.hutool.core.lang.Snowflake; import org.junit.jupiter.api.BeforeEach; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java similarity index 85% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java index f68a2bb..94d4dc9 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/querydsl/QuerydslJPAAutoConfigurationTest.java @@ -1,9 +1,9 @@ -package io.yunjiao.springboot.autoconfigure.querydsl; +package yunjiao.springboot.autoconfigure.querydsl; import com.querydsl.jpa.impl.JPAQueryFactory; -import io.yunjiao.extension.querydsl.jpa.JPAQueryCurdExecutor; -import io.yunjiao.springboot.autoconfigure.querydsl.jpa.JPAQueryFactoryConfigurer; -import io.yunjiao.springboot.autoconfigure.querydsl.jpa.QuerydslJPAAutoConfiguration; +import yunjiao.springboot.extension.querydsl.jpa.JPAQueryCurdExecutor; +import yunjiao.springboot.autoconfigure.querydsl.jpa.JPAQueryFactoryConfigurer; +import yunjiao.springboot.autoconfigure.querydsl.jpa.QuerydslJPAAutoConfiguration; import jakarta.persistence.EntityManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java similarity index 90% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java index 5eb1417..9906224 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/querydsl/QuerydslSQLAutoConfigurationTest.java @@ -1,12 +1,12 @@ -package io.yunjiao.springboot.autoconfigure.querydsl; +package yunjiao.springboot.autoconfigure.querydsl; import com.querydsl.sql.H2Templates; import com.querydsl.sql.MySQLTemplates; import com.querydsl.sql.SQLQueryFactory; import com.querydsl.sql.SQLTemplates; -import io.yunjiao.extension.querydsl.sql.SQLQueryCurdExecutor; -import io.yunjiao.springboot.autoconfigure.querydsl.sql.QuerydslSQLAutoConfiguration; -import io.yunjiao.springboot.autoconfigure.querydsl.sql.SQLQueryFactoryConfigurer; +import yunjiao.springboot.extension.querydsl.sql.SQLQueryCurdExecutor; +import yunjiao.springboot.autoconfigure.querydsl.sql.QuerydslSQLAutoConfiguration; +import yunjiao.springboot.autoconfigure.querydsl.sql.SQLQueryFactoryConfigurer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; diff --git a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/test/TestUtils.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/test/TestUtils.java similarity index 90% rename from autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/test/TestUtils.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/test/TestUtils.java index 52b624c..ac7dda2 100644 --- a/autoconfigure/src/test/java/io/yunjiao/springboot/autoconfigure/test/TestUtils.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/test/TestUtils.java @@ -1,4 +1,4 @@ -package io.yunjiao.springboot.autoconfigure.test; +package yunjiao.springboot.autoconfigure.test; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; diff --git a/autoconfigure/src/test/resources/logback-test.xml b/autoconfigure/src/test/resources/logback-test.xml index 689414f..93c3a1c 100644 --- a/autoconfigure/src/test/resources/logback-test.xml +++ b/autoconfigure/src/test/resources/logback-test.xml @@ -1,7 +1,7 @@ - + diff --git a/dependencies/pom.xml b/dependencies/pom.xml index f29007c..2e7ff19 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -6,7 +6,7 @@ 4.0.0 - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot spring-boot ${revision} ../pom.xml @@ -46,62 +46,62 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot autoconfigure ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-common ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-querydsl ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-apijson ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-captcha ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-id ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-querydsl-jpa ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-querydsl-sql ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-id ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-apijson-fastjson2 ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-apijson-gson ${revision} - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-captcha ${revision} diff --git a/examples/example-apijson-fastjson2/pom.xml b/examples/example-apijson-fastjson2/pom.xml index acc6eaf..37c439f 100644 --- a/examples/example-apijson-fastjson2/pom.xml +++ b/examples/example-apijson-fastjson2/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot examples ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-apijson-fastjson2 diff --git a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/ApijsonFastjson2Configuration.java b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/ApijsonFastjson2Configuration.java similarity index 84% rename from examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/ApijsonFastjson2Configuration.java rename to examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/ApijsonFastjson2Configuration.java index f6b2532..bcb39de 100644 --- a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/ApijsonFastjson2Configuration.java +++ b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/ApijsonFastjson2Configuration.java @@ -1,8 +1,8 @@ -package io.yunjiao.example.apijson; +package yunjiao.springboot.example.apijson; -import io.yunjiao.example.apijson.fastjson2.CustomFastjson2Creator; -import io.yunjiao.extension.apjson.util.ApijsonConsts; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; +import yunjiao.springboot.example.apijson.fastjson2.CustomFastjson2Creator; +import yunjiao.springboot.extension.apjson.util.ApijsonConsts; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; import jakarta.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; diff --git a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/ApijsonFastjson2ExampleApplication.java b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/ApijsonFastjson2ExampleApplication.java similarity index 89% rename from examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/ApijsonFastjson2ExampleApplication.java rename to examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/ApijsonFastjson2ExampleApplication.java index 1df154e..2ae8211 100644 --- a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/ApijsonFastjson2ExampleApplication.java +++ b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/ApijsonFastjson2ExampleApplication.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.apijson; +package yunjiao.springboot.example.apijson; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomFunctionParserConfigurer.java b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomFunctionParserConfigurer.java similarity index 82% rename from examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomFunctionParserConfigurer.java rename to examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomFunctionParserConfigurer.java index 411575d..ec3d9ed 100644 --- a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomFunctionParserConfigurer.java +++ b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomFunctionParserConfigurer.java @@ -1,8 +1,8 @@ -package io.yunjiao.example.apijson.config; +package yunjiao.springboot.example.apijson.config; import apijson.orm.script.JavaScriptExecutor; import apijson.orm.script.ScriptExecutor; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonFunctionParserConfigurer; +import yunjiao.springboot.autoconfigure.apijson.ApijsonFunctionParserConfigurer; import org.springframework.context.annotation.Configuration; import java.util.List; diff --git a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomSqlConfigConfigurer.java b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomSqlConfigConfigurer.java similarity index 97% rename from examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomSqlConfigConfigurer.java rename to examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomSqlConfigConfigurer.java index 7c27253..d3a6ee0 100644 --- a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomSqlConfigConfigurer.java +++ b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomSqlConfigConfigurer.java @@ -1,7 +1,7 @@ -package io.yunjiao.example.apijson.config; +package yunjiao.springboot.example.apijson.config; import apijson.StringUtil; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlConfigConfigurer; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlConfigConfigurer; import org.springframework.context.annotation.Configuration; import java.util.Arrays; diff --git a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomVerifierConfigurer.java b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomVerifierConfigurer.java similarity index 87% rename from examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomVerifierConfigurer.java rename to examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomVerifierConfigurer.java index a0f8a0c..f74268e 100644 --- a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomVerifierConfigurer.java +++ b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/config/CustomVerifierConfigurer.java @@ -1,9 +1,9 @@ -package io.yunjiao.example.apijson.config; +package yunjiao.springboot.example.apijson.config; import apijson.RequestMethod; import apijson.StringUtil; import apijson.orm.Entry; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonVerifierConfigurer; +import yunjiao.springboot.autoconfigure.apijson.ApijsonVerifierConfigurer; import org.springframework.context.annotation.Configuration; import java.util.List; diff --git a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/fastjson2/CustomFastjson2Creator.java b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/fastjson2/CustomFastjson2Creator.java similarity index 71% rename from examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/fastjson2/CustomFastjson2Creator.java rename to examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/fastjson2/CustomFastjson2Creator.java index bc16192..995363a 100644 --- a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/fastjson2/CustomFastjson2Creator.java +++ b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/fastjson2/CustomFastjson2Creator.java @@ -1,8 +1,8 @@ -package io.yunjiao.example.apijson.fastjson2; +package yunjiao.springboot.example.apijson.fastjson2; import apijson.fastjson2.APIJSONFunctionParser; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2Creator; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2Creator; import javax.sql.DataSource; import java.io.Serializable; diff --git a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/fastjson2/CustomFastjson2FunctionParser.java b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/fastjson2/CustomFastjson2FunctionParser.java similarity index 94% rename from examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/fastjson2/CustomFastjson2FunctionParser.java rename to examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/fastjson2/CustomFastjson2FunctionParser.java index eb5e6bc..a827117 100644 --- a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/fastjson2/CustomFastjson2FunctionParser.java +++ b/examples/example-apijson-fastjson2/src/main/java/yunjiao/springboot/example/apijson/fastjson2/CustomFastjson2FunctionParser.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.apijson.fastjson2; +package yunjiao.springboot.example.apijson.fastjson2; import apijson.NotNull; import apijson.RequestMethod; @@ -10,10 +10,10 @@ import apijson.orm.AbstractVerifier; import apijson.orm.Visitor; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonUtils; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2FunctionParser; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2Parser; -import io.yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2Verifier; +import yunjiao.springboot.autoconfigure.apijson.ApijsonUtils; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2FunctionParser; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2Parser; +import yunjiao.springboot.autoconfigure.apijson.fastjson2.Fastjson2Verifier; import java.io.Serializable; import java.util.ArrayList; diff --git a/examples/example-apijson-fastjson2/src/main/resources/application.yml b/examples/example-apijson-fastjson2/src/main/resources/application.yml index 824b509..eabfdfa 100644 --- a/examples/example-apijson-fastjson2/src/main/resources/application.yml +++ b/examples/example-apijson-fastjson2/src/main/resources/application.yml @@ -22,5 +22,5 @@ logging: level: root: info org.springframework.boot.autoconfigure: info - io.yunjiao: debug + yunjiao.springboot: debug diff --git a/examples/example-apijson-gson/pom.xml b/examples/example-apijson-gson/pom.xml index 9f74d3a..59de71f 100644 --- a/examples/example-apijson-gson/pom.xml +++ b/examples/example-apijson-gson/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot examples ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-apijson-gson diff --git a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/ApijsonGsonConfiguration.java b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/ApijsonGsonConfiguration.java similarity index 85% rename from examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/ApijsonGsonConfiguration.java rename to examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/ApijsonGsonConfiguration.java index 863f690..1801349 100644 --- a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/ApijsonGsonConfiguration.java +++ b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/ApijsonGsonConfiguration.java @@ -1,8 +1,8 @@ -package io.yunjiao.example.apijson; +package yunjiao.springboot.example.apijson; -import io.yunjiao.example.apijson.gson.CustomGsonCreator; -import io.yunjiao.extension.apjson.util.ApijsonConsts; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; +import yunjiao.springboot.example.apijson.gson.CustomGsonCreator; +import yunjiao.springboot.extension.apjson.util.ApijsonConsts; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; import jakarta.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; diff --git a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/ApijsonGsonExampleApplication.java b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/ApijsonGsonExampleApplication.java similarity index 89% rename from examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/ApijsonGsonExampleApplication.java rename to examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/ApijsonGsonExampleApplication.java index 1f86e68..c6e4609 100644 --- a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/ApijsonGsonExampleApplication.java +++ b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/ApijsonGsonExampleApplication.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.apijson; +package yunjiao.springboot.example.apijson; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomFunctionParserConfigurer.java b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomFunctionParserConfigurer.java similarity index 82% rename from examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomFunctionParserConfigurer.java rename to examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomFunctionParserConfigurer.java index 411575d..ec3d9ed 100644 --- a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomFunctionParserConfigurer.java +++ b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomFunctionParserConfigurer.java @@ -1,8 +1,8 @@ -package io.yunjiao.example.apijson.config; +package yunjiao.springboot.example.apijson.config; import apijson.orm.script.JavaScriptExecutor; import apijson.orm.script.ScriptExecutor; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonFunctionParserConfigurer; +import yunjiao.springboot.autoconfigure.apijson.ApijsonFunctionParserConfigurer; import org.springframework.context.annotation.Configuration; import java.util.List; diff --git a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomSqlConfigConfigurer.java b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomSqlConfigConfigurer.java similarity index 97% rename from examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomSqlConfigConfigurer.java rename to examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomSqlConfigConfigurer.java index 7c27253..d3a6ee0 100644 --- a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/config/CustomSqlConfigConfigurer.java +++ b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomSqlConfigConfigurer.java @@ -1,7 +1,7 @@ -package io.yunjiao.example.apijson.config; +package yunjiao.springboot.example.apijson.config; import apijson.StringUtil; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlConfigConfigurer; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlConfigConfigurer; import org.springframework.context.annotation.Configuration; import java.util.Arrays; diff --git a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomVerifierConfigurer.java b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomVerifierConfigurer.java similarity index 87% rename from examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomVerifierConfigurer.java rename to examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomVerifierConfigurer.java index a0f8a0c..f74268e 100644 --- a/examples/example-apijson-fastjson2/src/main/java/io/yunjiao/example/apijson/config/CustomVerifierConfigurer.java +++ b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/config/CustomVerifierConfigurer.java @@ -1,9 +1,9 @@ -package io.yunjiao.example.apijson.config; +package yunjiao.springboot.example.apijson.config; import apijson.RequestMethod; import apijson.StringUtil; import apijson.orm.Entry; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonVerifierConfigurer; +import yunjiao.springboot.autoconfigure.apijson.ApijsonVerifierConfigurer; import org.springframework.context.annotation.Configuration; import java.util.List; diff --git a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/gson/CustomGsonCreator.java b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/gson/CustomGsonCreator.java similarity index 72% rename from examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/gson/CustomGsonCreator.java rename to examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/gson/CustomGsonCreator.java index 704d6c1..50465d7 100644 --- a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/gson/CustomGsonCreator.java +++ b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/gson/CustomGsonCreator.java @@ -1,8 +1,8 @@ -package io.yunjiao.example.apijson.gson; +package yunjiao.springboot.example.apijson.gson; import apijson.gson.APIJSONFunctionParser; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; -import io.yunjiao.springboot.autoconfigure.apijson.gson.GsonCreator; +import yunjiao.springboot.autoconfigure.apijson.ApijsonSqlProperties; +import yunjiao.springboot.autoconfigure.apijson.gson.GsonCreator; import javax.sql.DataSource; import java.io.Serializable; diff --git a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/gson/CustomGsonFunctionParser.java b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/gson/CustomGsonFunctionParser.java similarity index 95% rename from examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/gson/CustomGsonFunctionParser.java rename to examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/gson/CustomGsonFunctionParser.java index f931dac..d4e534c 100644 --- a/examples/example-apijson-gson/src/main/java/io/yunjiao/example/apijson/gson/CustomGsonFunctionParser.java +++ b/examples/example-apijson-gson/src/main/java/yunjiao/springboot/example/apijson/gson/CustomGsonFunctionParser.java @@ -1,13 +1,13 @@ -package io.yunjiao.example.apijson.gson; +package yunjiao.springboot.example.apijson.gson; import apijson.JSONRequest; import apijson.NotNull; import apijson.StringUtil; import apijson.orm.AbstractVerifier; import apijson.orm.Visitor; -import io.yunjiao.springboot.autoconfigure.apijson.ApijsonUtils; -import io.yunjiao.springboot.autoconfigure.apijson.gson.GsonFunctionParser; -import io.yunjiao.springboot.autoconfigure.apijson.gson.GsonVerifier; +import yunjiao.springboot.autoconfigure.apijson.ApijsonUtils; +import yunjiao.springboot.autoconfigure.apijson.gson.GsonFunctionParser; +import yunjiao.springboot.autoconfigure.apijson.gson.GsonVerifier; import java.io.Serializable; import java.util.ArrayList; diff --git a/examples/example-apijson-gson/src/main/resources/application.yml b/examples/example-apijson-gson/src/main/resources/application.yml index 36bae44..ecf479d 100644 --- a/examples/example-apijson-gson/src/main/resources/application.yml +++ b/examples/example-apijson-gson/src/main/resources/application.yml @@ -23,5 +23,5 @@ logging: level: root: info org.springframework.boot.autoconfigure: info - io.yunjiao: debug + yunjiao.springboot: debug diff --git a/examples/example-captcha/pom.xml b/examples/example-captcha/pom.xml index 6cbfffd..3e88317 100644 --- a/examples/example-captcha/pom.xml +++ b/examples/example-captcha/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot examples ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-captcha diff --git a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaCommandLineRunner.java b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaCommandLineRunner.java similarity index 83% rename from examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaCommandLineRunner.java rename to examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaCommandLineRunner.java index 820f187..d7fd782 100644 --- a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaCommandLineRunner.java +++ b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaCommandLineRunner.java @@ -1,9 +1,9 @@ -package io.yunjiao.example.captcha; +package yunjiao.springboot.example.captcha; -import io.yunjiao.extension.common.captcha.CaptchaCategory; -import io.yunjiao.extension.common.captcha.CaptchaData; -import io.yunjiao.extension.common.captcha.CaptchaService; -import io.yunjiao.springboot.autoconfigure.captcha.CaptchaServiceFactory; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.CaptchaService; +import yunjiao.springboot.autoconfigure.captcha.CaptchaServiceFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; diff --git a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaController.java b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaController.java similarity index 90% rename from examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaController.java rename to examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaController.java index ce4f463..baf631a 100644 --- a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaController.java +++ b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaController.java @@ -1,12 +1,12 @@ -package io.yunjiao.example.captcha; +package yunjiao.springboot.example.captcha; import cn.hutool.cache.CacheUtil; import cn.hutool.cache.impl.TimedCache; -import io.yunjiao.extension.common.captcha.*; -import io.yunjiao.springboot.autoconfigure.captcha.CaptchaServiceFactory; +import yunjiao.springboot.autoconfigure.captcha.CaptchaServiceFactory; import lombok.RequiredArgsConstructor; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; +import yunjiao.springboot.extension.common.captcha.*; /** * 接口 diff --git a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaExampleApplication.java b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaExampleApplication.java similarity index 89% rename from examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaExampleApplication.java rename to examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaExampleApplication.java index 3a05594..f53fb74 100644 --- a/examples/example-captcha/src/main/java/io/yunjiao/example/captcha/CaptchaExampleApplication.java +++ b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaExampleApplication.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.captcha; +package yunjiao.springboot.example.captcha; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/example-captcha/src/main/resources/application.yml b/examples/example-captcha/src/main/resources/application.yml index 686d41e..0ce114f 100644 --- a/examples/example-captcha/src/main/resources/application.yml +++ b/examples/example-captcha/src/main/resources/application.yml @@ -3,5 +3,5 @@ logging: level: root: info org.springframework.boot.autoconfigure: info - io.yunjiao: debug + yunjiao.springboot: debug diff --git a/examples/example-id/pom.xml b/examples/example-id/pom.xml index 918ce0b..bd84146 100644 --- a/examples/example-id/pom.xml +++ b/examples/example-id/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot examples ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-id diff --git a/examples/example-id/src/main/java/io/yunjiao/example/id/IdCommandLineRunner.java b/examples/example-id/src/main/java/yunjiao/springboot/example/id/IdCommandLineRunner.java similarity index 93% rename from examples/example-id/src/main/java/io/yunjiao/example/id/IdCommandLineRunner.java rename to examples/example-id/src/main/java/yunjiao/springboot/example/id/IdCommandLineRunner.java index ecd2bd0..cea4bcd 100644 --- a/examples/example-id/src/main/java/io/yunjiao/example/id/IdCommandLineRunner.java +++ b/examples/example-id/src/main/java/yunjiao/springboot/example/id/IdCommandLineRunner.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.id; +package yunjiao.springboot.example.id; import cn.hutool.core.lang.Snowflake; import lombok.extern.slf4j.Slf4j; diff --git a/examples/example-id/src/main/java/io/yunjiao/example/id/IdExampleApplication.java b/examples/example-id/src/main/java/yunjiao/springboot/example/id/IdExampleApplication.java similarity index 89% rename from examples/example-id/src/main/java/io/yunjiao/example/id/IdExampleApplication.java rename to examples/example-id/src/main/java/yunjiao/springboot/example/id/IdExampleApplication.java index 6672e4c..293e27e 100644 --- a/examples/example-id/src/main/java/io/yunjiao/example/id/IdExampleApplication.java +++ b/examples/example-id/src/main/java/yunjiao/springboot/example/id/IdExampleApplication.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.id; +package yunjiao.springboot.example.id; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/example-id/src/main/resources/application.yml b/examples/example-id/src/main/resources/application.yml index 1b52d75..4d7b1b2 100644 --- a/examples/example-id/src/main/resources/application.yml +++ b/examples/example-id/src/main/resources/application.yml @@ -6,5 +6,5 @@ logging: level: root: info org.springframework.boot.autoconfigure: info - io.yunjiao: debug + yunjiao.springboot: debug diff --git a/examples/example-querydsl-jpa/pom.xml b/examples/example-querydsl-jpa/pom.xml index ac9f5d2..b67e779 100644 --- a/examples/example-querydsl-jpa/pom.xml +++ b/examples/example-querydsl-jpa/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot examples ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-querydsl-jpa diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/QueryDSLJPAConfiguration.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLJPAConfiguration.java similarity index 82% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/QueryDSLJPAConfiguration.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLJPAConfiguration.java index 7918e0f..1390373 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/QueryDSLJPAConfiguration.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLJPAConfiguration.java @@ -1,6 +1,6 @@ -package io.yunjiao.example.querydsl; +package yunjiao.springboot.example.querydsl; -import io.yunjiao.springboot.autoconfigure.querydsl.jpa.JPAQueryFactoryConfigurer; +import yunjiao.springboot.autoconfigure.querydsl.jpa.JPAQueryFactoryConfigurer; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/QueryDSLJPAExampleApplication.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLJPAExampleApplication.java similarity index 89% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/QueryDSLJPAExampleApplication.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLJPAExampleApplication.java index 9f0baea..9954b2b 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/QueryDSLJPAExampleApplication.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLJPAExampleApplication.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl; +package yunjiao.springboot.example.querydsl; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/UserCommandLineRunner.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/UserCommandLineRunner.java similarity index 94% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/UserCommandLineRunner.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/UserCommandLineRunner.java index 634d329..135f603 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/UserCommandLineRunner.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/UserCommandLineRunner.java @@ -1,7 +1,7 @@ -package io.yunjiao.example.querydsl; +package yunjiao.springboot.example.querydsl; -import io.yunjiao.example.querydsl.jpa.User; -import io.yunjiao.example.querydsl.jpa.UserJPAService; +import yunjiao.springboot.example.querydsl.jpa.User; +import yunjiao.springboot.example.querydsl.jpa.UserJPAService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/Order.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/Order.java similarity index 92% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/Order.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/Order.java index f8aca97..d4b23fa 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/Order.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/Order.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl.jpa; +package yunjiao.springboot.example.querydsl.jpa; import jakarta.persistence.*; import lombok.Data; diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/User.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/User.java similarity index 94% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/User.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/User.java index eac913f..4205350 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/User.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/User.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl.jpa; +package yunjiao.springboot.example.querydsl.jpa; import jakarta.persistence.*; import lombok.Data; diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPARepository.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserJPARepository.java similarity index 86% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPARepository.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserJPARepository.java index 2fbc6c1..0682b69 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPARepository.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserJPARepository.java @@ -1,13 +1,11 @@ -package io.yunjiao.example.querydsl.jpa; +package yunjiao.springboot.example.querydsl.jpa; import com.querydsl.jpa.impl.JPADeleteClause; import com.querydsl.jpa.impl.JPAInsertClause; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAUpdateClause; -import io.yunjiao.extension.querydsl.QSpecification; -import io.yunjiao.extension.querydsl.jpa.JPAQueryRepositorySupport; -import jakarta.persistence.EntityManager; -import org.springframework.beans.factory.annotation.Autowired; +import yunjiao.springboot.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.jpa.JPAQueryRepositorySupport; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.querydsl.QPageRequest; @@ -19,8 +17,6 @@ import java.sql.Date; import java.util.List; import java.util.Optional; -import static io.yunjiao.example.querydsl.jpa.UserSpec.*; - /** * 仓库类 * @@ -53,13 +49,13 @@ public class UserJPARepository extends JPAQueryRepositorySupport { // 使用名称查询唯一用户 public Optional findSingleByName(String name) { JPAQuery query = selectFrom(qUser); - return getCurdExecutor().findOnlyOne(query, nameEq(name)); + return getCurdExecutor().findOnlyOne(query, UserSpec.nameEq(name)); } // 多条件,排序查询 public List findList(String name, Integer age, Date birthDate) { JPAQuery query = selectFrom(qUser); - QSpecification spec = nameLike(name).and(ageGoe(age), birthDate(birthDate)); + QSpecification spec = UserSpec.nameLike(name).and(UserSpec.ageGoe(age), UserSpec.birthDate(birthDate)); QSort sort = QSort.by(qUser.age.asc(), qUser.birthDate.desc()); return getCurdExecutor().findList(query, spec, sort); } @@ -69,14 +65,14 @@ public class UserJPARepository extends JPAQueryRepositorySupport { JPAQuery countQuery = select(qUser.count()).from(qUser); JPAQuery query = selectFrom(qUser); QSort sort = QSort.by(qUser.age.desc()); - return getCurdExecutor().findPage(countQuery, query, QPageRequest.of(0, 5, sort), ageGoe(age)); + return getCurdExecutor().findPage(countQuery, query, QPageRequest.of(0, 5, sort), UserSpec.ageGoe(age)); } // 分页查询, 使用PageRequest对象 public Page findPage(Integer age) { JPAQuery query = selectFrom(qUser); QSort sort = QSort.by(qUser.age.asc()); - return findPage(query, PageRequest.of(0, 5, sort), ageGoe(age)); + return findPage(query, PageRequest.of(0, 5, sort), UserSpec.ageGoe(age)); } // 关联查询:查询已完成订单的用户信息 @@ -100,7 +96,7 @@ public class UserJPARepository extends JPAQueryRepositorySupport { public long updateName(Long id, String newName) { JPAUpdateClause update = update(qUser) .set(qUser.name, newName); - return getCurdExecutor().update(update, idEq(id)); + return getCurdExecutor().update(update, UserSpec.idEq(id)); } // 批量插入 @@ -123,7 +119,7 @@ public class UserJPARepository extends JPAQueryRepositorySupport { // 查询 JPAQuery queryClause = select(qUser.id).from(qUser).limit(maxLimit); - getCurdExecutor().applaySpecification(ageGoe(age), queryClause); + getCurdExecutor().applaySpecification(UserSpec.ageGoe(age), queryClause); while (true) { List userIdList = queryClause.fetch(); diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPAService.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserJPAService.java similarity index 95% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPAService.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserJPAService.java index ea91cd2..d1364a3 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserJPAService.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserJPAService.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl.jpa; +package yunjiao.springboot.example.querydsl.jpa; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserQuery.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserQuery.java similarity index 82% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserQuery.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserQuery.java index c05dcfd..3435ab9 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserQuery.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserQuery.java @@ -1,6 +1,6 @@ -package io.yunjiao.example.querydsl.jpa; +package yunjiao.springboot.example.querydsl.jpa; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -9,8 +9,8 @@ import org.springframework.util.StringUtils; import java.util.Objects; -import static io.yunjiao.example.querydsl.jpa.UserSpec.*; -import static io.yunjiao.extension.querydsl.QueryDSLUtils.orderBy; +import static yunjiao.springboot.example.querydsl.jpa.UserSpec.*; +import static yunjiao.springboot.extension.querydsl.QueryDSLUtils.orderBy; /** * 用户查询条件 diff --git a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserSpec.java b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserSpec.java similarity index 86% rename from examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserSpec.java rename to examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserSpec.java index 51135b7..d0296d6 100644 --- a/examples/example-querydsl-jpa/src/main/java/io/yunjiao/example/querydsl/jpa/UserSpec.java +++ b/examples/example-querydsl-jpa/src/main/java/yunjiao/springboot/example/querydsl/jpa/UserSpec.java @@ -1,6 +1,6 @@ -package io.yunjiao.example.querydsl.jpa; +package yunjiao.springboot.example.querydsl.jpa; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import java.sql.Date; diff --git a/examples/example-querydsl-jpa/src/main/resources/application.yml b/examples/example-querydsl-jpa/src/main/resources/application.yml index 91718c4..9c5515a 100644 --- a/examples/example-querydsl-jpa/src/main/resources/application.yml +++ b/examples/example-querydsl-jpa/src/main/resources/application.yml @@ -12,6 +12,6 @@ logging: level: root: info org.springframework.boot.autoconfigure: info - io.yunjiao: debug + yunjiao.springboot: debug com.querydsl.jpa: debug # 打印SQL语句 diff --git a/examples/example-querydsl-sql/pom.xml b/examples/example-querydsl-sql/pom.xml index be495c8..95c1070 100644 --- a/examples/example-querydsl-sql/pom.xml +++ b/examples/example-querydsl-sql/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot examples ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-querydsl-sql @@ -41,39 +41,44 @@ - - - - com.querydsl - querydsl-maven-plugin - - - - export - - - - - - com.mysql.cj.jdbc.Driver - jdbc:mysql://localhost:3306/yunjiao?userSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8 - system - system - io.yunjiao.example.querydsl - ${project.basedir}/target/generated-sources/java - - - - - com.mysql - mysql-connector-j - 9.2.0 - - - - - + + + querdsl-sql-export + + + + com.querydsl + querydsl-maven-plugin + + + + export + + + + + + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/yunjiao?userSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8 + system + system + io.yunjiao.example.querydsl + ${project.basedir}/target/generated-sources/java + + + + + com.mysql + mysql-connector-j + 9.2.0 + + + + + + + \ No newline at end of file diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/QueryDSLSQLConfiguration.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLSQLConfiguration.java similarity index 86% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/QueryDSLSQLConfiguration.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLSQLConfiguration.java index 1d664d3..c60c60c 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/QueryDSLSQLConfiguration.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLSQLConfiguration.java @@ -1,7 +1,7 @@ -package io.yunjiao.example.querydsl; +package yunjiao.springboot.example.querydsl; import com.querydsl.sql.SQLTemplates; -import io.yunjiao.springboot.autoconfigure.querydsl.sql.SQLQueryFactoryConfigurer; +import yunjiao.springboot.autoconfigure.querydsl.sql.SQLQueryFactoryConfigurer; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/QueryDSLSQLExampleApplication.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLSQLExampleApplication.java similarity index 89% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/QueryDSLSQLExampleApplication.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLSQLExampleApplication.java index 4a77632..eb4ef51 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/QueryDSLSQLExampleApplication.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/QueryDSLSQLExampleApplication.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl; +package yunjiao.springboot.example.querydsl; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/UserCommandLineRunner.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/UserCommandLineRunner.java similarity index 94% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/UserCommandLineRunner.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/UserCommandLineRunner.java index cc19414..22855ac 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/UserCommandLineRunner.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/UserCommandLineRunner.java @@ -1,7 +1,7 @@ -package io.yunjiao.example.querydsl; +package yunjiao.springboot.example.querydsl; -import io.yunjiao.example.querydsl.sql.User; -import io.yunjiao.example.querydsl.sql.UserSQLService; +import yunjiao.springboot.example.querydsl.sql.User; +import yunjiao.springboot.example.querydsl.sql.UserSQLService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/QOrders.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/QOrders.java similarity index 98% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/QOrders.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/QOrders.java index f3c1ff6..ff222fd 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/QOrders.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/QOrders.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl.sql; +package yunjiao.springboot.example.querydsl.sql; import com.querydsl.core.types.Path; import com.querydsl.core.types.PathMetadata; diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/QUsers.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/QUsers.java similarity index 98% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/QUsers.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/QUsers.java index 6c7c62d..c72b5d5 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/QUsers.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/QUsers.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl.sql; +package yunjiao.springboot.example.querydsl.sql; import com.querydsl.core.types.Path; import com.querydsl.core.types.PathMetadata; diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/User.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/User.java similarity index 84% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/User.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/User.java index c727ece..8fa7698 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/User.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/User.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl.sql; +package yunjiao.springboot.example.querydsl.sql; import lombok.Data; diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserPre.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserPre.java similarity index 86% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserPre.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserPre.java index 41d4afd..0754c6e 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserPre.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserPre.java @@ -1,7 +1,7 @@ -package io.yunjiao.example.querydsl.sql; +package yunjiao.springboot.example.querydsl.sql; import com.querydsl.core.types.Predicate; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import java.sql.Date; diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserQuery.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserQuery.java similarity index 86% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserQuery.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserQuery.java index 26c22c6..dedf036 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserQuery.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserQuery.java @@ -1,8 +1,8 @@ -package io.yunjiao.example.querydsl.sql; +package yunjiao.springboot.example.querydsl.sql; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -11,8 +11,8 @@ import org.springframework.util.StringUtils; import java.util.Objects; -import static io.yunjiao.example.querydsl.sql.UserSpec.*; -import static io.yunjiao.extension.querydsl.QueryDSLUtils.orderBy; +import static yunjiao.springboot.example.querydsl.sql.UserSpec.*; +import static yunjiao.springboot.extension.querydsl.QueryDSLUtils.orderBy; /** * 用户查询条件 diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSQLRepository.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSQLRepository.java similarity index 95% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSQLRepository.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSQLRepository.java index 35a5fce..3b6f57b 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSQLRepository.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSQLRepository.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl.sql; +package yunjiao.springboot.example.querydsl.sql; import com.google.common.collect.Lists; import com.querydsl.core.types.Projections; @@ -6,8 +6,8 @@ import com.querydsl.sql.SQLQuery; import com.querydsl.sql.dml.SQLDeleteClause; import com.querydsl.sql.dml.SQLInsertClause; import com.querydsl.sql.dml.SQLUpdateClause; -import io.yunjiao.extension.querydsl.QSpecification; -import io.yunjiao.extension.querydsl.sql.SQLQueryRepositorySupport; +import yunjiao.springboot.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.sql.SQLQueryRepositorySupport; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.querydsl.QPageRequest; @@ -19,7 +19,7 @@ import java.sql.Date; import java.util.List; import java.util.Optional; -import static io.yunjiao.example.querydsl.sql.UserSpec.*; +import static yunjiao.springboot.example.querydsl.sql.UserSpec.*; /** * 仓库类 diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSQLService.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSQLService.java similarity index 95% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSQLService.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSQLService.java index 36ce342..f1fce6f 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSQLService.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSQLService.java @@ -1,4 +1,4 @@ -package io.yunjiao.example.querydsl.sql; +package yunjiao.springboot.example.querydsl.sql; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSpec.java b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSpec.java similarity index 86% rename from examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSpec.java rename to examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSpec.java index 8a014a2..bdf1227 100644 --- a/examples/example-querydsl-sql/src/main/java/io/yunjiao/example/querydsl/sql/UserSpec.java +++ b/examples/example-querydsl-sql/src/main/java/yunjiao/springboot/example/querydsl/sql/UserSpec.java @@ -1,6 +1,6 @@ -package io.yunjiao.example.querydsl.sql; +package yunjiao.springboot.example.querydsl.sql; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import java.sql.Date; diff --git a/examples/example-querydsl-sql/src/main/resources/application.yml b/examples/example-querydsl-sql/src/main/resources/application.yml index ca9b067..d7d4629 100644 --- a/examples/example-querydsl-sql/src/main/resources/application.yml +++ b/examples/example-querydsl-sql/src/main/resources/application.yml @@ -10,6 +10,6 @@ logging: level: root: info org.springframework.boot.autoconfigure: debug - io.yunjiao: debug + yunjiao.springboot: debug com.querydsl.sql: debug # 打印SQL语句 diff --git a/examples/pom.xml b/examples/pom.xml index 76b2318..bd01f61 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -3,8 +3,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source - spring-boot-starter-parent + io.gitee.yunjiao-source.spring-boot + starter-parent 3.5.x.3 diff --git a/extensions/extension-apijson/pom.xml b/extensions/extension-apijson/pom.xml index 769fffb..943fda7 100644 --- a/extensions/extension-apijson/pom.xml +++ b/extensions/extension-apijson/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extensions ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-common diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/_APIJSON.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/_APIJSON.java similarity index 61% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/_APIJSON.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/_APIJSON.java index 98abc48..ec490a4 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/_APIJSON.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/_APIJSON.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson; +package yunjiao.springboot.extension.apjson; /** * _APIJSON diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/annotation/ApijsonRest.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/annotation/ApijsonRest.java similarity index 87% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/annotation/ApijsonRest.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/annotation/ApijsonRest.java index 204b495..067d032 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/annotation/ApijsonRest.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/annotation/ApijsonRest.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.annotation; +package yunjiao.springboot.extension.apjson.annotation; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/GsonMap.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/GsonMap.java similarity index 97% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/GsonMap.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/GsonMap.java index 74567b0..f757c81 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/GsonMap.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/GsonMap.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import java.util.LinkedHashMap; import java.util.Map; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/IdKeyApijsonStrategy.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/IdKeyApijsonStrategy.java similarity index 85% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/IdKeyApijsonStrategy.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/IdKeyApijsonStrategy.java index b0d5568..ab1bd44 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/IdKeyApijsonStrategy.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/IdKeyApijsonStrategy.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import apijson.JSONMap; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/IdKeyStrategy.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/IdKeyStrategy.java similarity index 90% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/IdKeyStrategy.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/IdKeyStrategy.java index aa13c6b..79def43 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/IdKeyStrategy.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/IdKeyStrategy.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; /** * 主键名称策略 diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdDatabaseStrategy.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdDatabaseStrategy.java similarity index 88% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdDatabaseStrategy.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdDatabaseStrategy.java index 667ad6c..985025a 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdDatabaseStrategy.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdDatabaseStrategy.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import apijson.RequestMethod; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdExceptionStrategy.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdExceptionStrategy.java similarity index 89% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdExceptionStrategy.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdExceptionStrategy.java index d0de902..4ce4df2 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdExceptionStrategy.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdExceptionStrategy.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import apijson.RequestMethod; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdSnowflakeStrategy.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdSnowflakeStrategy.java similarity index 91% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdSnowflakeStrategy.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdSnowflakeStrategy.java index dd21d74..5b011b9 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdSnowflakeStrategy.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdSnowflakeStrategy.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import apijson.RequestMethod; import cn.hutool.core.lang.Snowflake; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdStrategy.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdStrategy.java similarity index 92% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdStrategy.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdStrategy.java index 215b9cc..4624c3a 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdStrategy.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdStrategy.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import apijson.RequestMethod; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdTimestampStrategy.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdTimestampStrategy.java similarity index 81% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdTimestampStrategy.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdTimestampStrategy.java index e7ef04d..56dda6c 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdTimestampStrategy.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdTimestampStrategy.java @@ -1,7 +1,7 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import apijson.RequestMethod; -import io.yunjiao.extension.common.generator.TimestampIdGenerator; +import yunjiao.springboot.extension.common.generator.TimestampIdGenerator; import java.io.Serializable; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdUuidStrategy.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdUuidStrategy.java similarity index 89% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdUuidStrategy.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdUuidStrategy.java index 5406751..7452789 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/NewIdUuidStrategy.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/NewIdUuidStrategy.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import apijson.RequestMethod; import cn.hutool.core.util.IdUtil; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/SqlConnectProvider.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/SqlConnectProvider.java similarity index 96% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/SqlConnectProvider.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/SqlConnectProvider.java index 3607dac..eddfb42 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/orm/SqlConnectProvider.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/orm/SqlConnectProvider.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.orm; +package yunjiao.springboot.extension.apjson.orm; import apijson.NotNull; import org.slf4j.Logger; diff --git a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/util/ApijsonConsts.java b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/util/ApijsonConsts.java similarity index 93% rename from extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/util/ApijsonConsts.java rename to extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/util/ApijsonConsts.java index c3438fb..0057b60 100644 --- a/extensions/extension-apijson/src/main/java/io/yunjiao/extension/apjson/util/ApijsonConsts.java +++ b/extensions/extension-apijson/src/main/java/yunjiao/springboot/extension/apjson/util/ApijsonConsts.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.apjson.util; +package yunjiao.springboot.extension.apjson.util; import apijson.orm.SQLConfig; diff --git a/extensions/extension-captcha/pom.xml b/extensions/extension-captcha/pom.xml index 7333f16..4979e11 100644 --- a/extensions/extension-captcha/pom.xml +++ b/extensions/extension-captcha/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extensions ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-common diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/_Captcha.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/_Captcha.java similarity index 61% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/_Captcha.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/_Captcha.java index f0fec34..a2c389f 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/_Captcha.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/_Captcha.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.captcha; +package yunjiao.springboot.extension.captcha; /** * _Captcha diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaBuilder.java similarity index 93% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaBuilder.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaBuilder.java index e54d4f0..55dbeb4 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaBuilder.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaBuilder.java @@ -1,8 +1,8 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.AbstractCaptcha; import cn.hutool.captcha.generator.CodeGenerator; -import io.yunjiao.extension.common.model.ColorType; +import yunjiao.springboot.extension.common.model.ColorType; import lombok.Getter; import lombok.Setter; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaService.java similarity index 89% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaService.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaService.java index 2bc19d0..bc701a1 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/AbstractCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaService.java @@ -1,10 +1,10 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.util.IdUtil; -import io.yunjiao.extension.common.algorithm.GaussianBlur; -import io.yunjiao.extension.common.captcha.CaptchaData; -import io.yunjiao.extension.common.captcha.CaptchaService; +import yunjiao.springboot.extension.common.algorithm.GaussianBlur; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.CaptchaService; import lombok.extern.slf4j.Slf4j; import java.awt.image.BufferedImage; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CaptchaException.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CaptchaException.java similarity index 70% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CaptchaException.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CaptchaException.java index acbe384..efce16d 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CaptchaException.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CaptchaException.java @@ -1,6 +1,6 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; -import io.yunjiao.extension.common.util.CommonRuntimeException; +import yunjiao.springboot.extension.common.util.CommonRuntimeException; /** * 验证码异常 diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaBuilder.java similarity index 87% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaBuilder.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaBuilder.java index 38615b3..8ce6b74 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaBuilder.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaBuilder.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.CircleCaptcha; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaService.java similarity index 84% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaService.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaService.java index 567eb68..1449571 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CircleCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaService.java @@ -1,8 +1,8 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.CircleCaptcha; -import io.yunjiao.extension.common.captcha.CaptchaCategory; -import io.yunjiao.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; import lombok.RequiredArgsConstructor; import java.awt.image.BufferedImage; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CodeGeneratorType.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CodeGeneratorType.java similarity index 97% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CodeGeneratorType.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CodeGeneratorType.java index 12ada76..d78f24f 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/CodeGeneratorType.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CodeGeneratorType.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.generator.CodeGenerator; import cn.hutool.captcha.generator.MathGenerator; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaBuilder.java similarity index 95% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaBuilder.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaBuilder.java index 99d13d9..7a9cc06 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaBuilder.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaBuilder.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.GifCaptcha; import lombok.Getter; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaService.java similarity index 84% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaService.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaService.java index 23011f7..a7864d0 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/GifCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaService.java @@ -1,10 +1,10 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.GifCaptcha; import cn.hutool.core.util.IdUtil; -import io.yunjiao.extension.common.captcha.CaptchaCategory; -import io.yunjiao.extension.common.captcha.CaptchaData; import lombok.RequiredArgsConstructor; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; /** * gif验证码 服务 diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaBuilder.java similarity index 86% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaBuilder.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaBuilder.java index 99191cd..e09d3d3 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaBuilder.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaBuilder.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.LineCaptcha; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaService.java similarity index 84% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaService.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaService.java index cc4fe37..dc551a2 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/LineCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaService.java @@ -1,8 +1,8 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.LineCaptcha; -import io.yunjiao.extension.common.captcha.CaptchaCategory; -import io.yunjiao.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; import lombok.RequiredArgsConstructor; import java.awt.image.BufferedImage; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaBuilder.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaBuilder.java similarity index 86% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaBuilder.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaBuilder.java index 03b4d63..b84942c 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaBuilder.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaBuilder.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.ShearCaptcha; diff --git a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaService.java similarity index 84% rename from extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaService.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaService.java index 2150edc..b159e58 100644 --- a/extensions/extension-captcha/src/main/java/io/yunjiao/extension/captcha/hutool/ShearCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaService.java @@ -1,8 +1,8 @@ -package io.yunjiao.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.captcha.ShearCaptcha; -import io.yunjiao.extension.common.captcha.CaptchaCategory; -import io.yunjiao.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; import lombok.RequiredArgsConstructor; import java.awt.image.BufferedImage; diff --git a/extensions/extension-captcha/src/test/java/io/yunjiao/extension/captcha/CaptchaJFrameDemo.java b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/CaptchaJFrameDemo.java similarity index 95% rename from extensions/extension-captcha/src/test/java/io/yunjiao/extension/captcha/CaptchaJFrameDemo.java rename to extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/CaptchaJFrameDemo.java index 9706c6b..a61aa8f 100644 --- a/extensions/extension-captcha/src/test/java/io/yunjiao/extension/captcha/CaptchaJFrameDemo.java +++ b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/CaptchaJFrameDemo.java @@ -1,10 +1,10 @@ -package io.yunjiao.extension.captcha; +package yunjiao.springboot.extension.captcha; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.LineCaptcha; -import io.yunjiao.extension.captcha.hutool.*; -import io.yunjiao.extension.common.captcha.CaptchaData; -import io.yunjiao.extension.common.model.ColorType; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.model.ColorType; +import yunjiao.springboot.extension.captcha.hutool.*; import javax.swing.*; import java.awt.*; diff --git a/extensions/extension-common/pom.xml b/extensions/extension-common/pom.xml index f96697e..7ee9a7b 100644 --- a/extensions/extension-common/pom.xml +++ b/extensions/extension-common/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extensions ${revision} diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/algorithm/GaussianBlur.java similarity index 98% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/algorithm/GaussianBlur.java index b4b2e8f..b944ad5 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/algorithm/GaussianBlur.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/algorithm/GaussianBlur.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.algorithm; +package yunjiao.springboot.extension.common.algorithm; import java.awt.image.BufferedImage; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaCategory.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaCategory.java similarity index 92% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaCategory.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaCategory.java index b30b956..cd48d0b 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaCategory.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaCategory.java @@ -1,6 +1,6 @@ -package io.yunjiao.extension.common.captcha; +package yunjiao.springboot.extension.common.captcha; -import io.yunjiao.extension.common.lang.EnumCache; +import yunjiao.springboot.extension.common.lang.EnumCache; import lombok.Getter; import lombok.ToString; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaData.java similarity index 96% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaData.java index 521e3f2..1d75f22 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaData.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaData.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.captcha; +package yunjiao.springboot.extension.common.captcha; import lombok.Builder; import lombok.Getter; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaReponse.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaReponse.java similarity index 93% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaReponse.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaReponse.java index 863bddb..22d0bf7 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaReponse.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaReponse.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.captcha; +package yunjiao.springboot.extension.common.captcha; import lombok.Data; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaService.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaService.java similarity index 91% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaService.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaService.java index 8d06dec..a4caa3a 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaService.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaService.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.captcha; +package yunjiao.springboot.extension.common.captcha; /** * 验证码服务接口 diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaValidate.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaValidate.java similarity index 86% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaValidate.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaValidate.java index 69bc1a1..8230c49 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/captcha/CaptchaValidate.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaValidate.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.captcha; +package yunjiao.springboot.extension.common.captcha; import lombok.Data; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/generator/TimestampIdGenerator.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/generator/TimestampIdGenerator.java similarity index 88% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/generator/TimestampIdGenerator.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/generator/TimestampIdGenerator.java index 2131aba..5b9b9ed 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/generator/TimestampIdGenerator.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/generator/TimestampIdGenerator.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.generator; +package yunjiao.springboot.extension.common.generator; import java.util.concurrent.atomic.AtomicLong; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/lang/EnumCache.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/lang/EnumCache.java similarity index 99% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/lang/EnumCache.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/lang/EnumCache.java index cb4e88f..9417e63 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/lang/EnumCache.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/lang/EnumCache.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.lang; +package yunjiao.springboot.extension.common.lang; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/ColorType.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/ColorType.java similarity index 95% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/ColorType.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/ColorType.java index 155ff6d..5d868d5 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/ColorType.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/ColorType.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.model; +package yunjiao.springboot.extension.common.model; import lombok.Getter; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/FontStyle.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/FontStyle.java similarity index 88% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/FontStyle.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/FontStyle.java index 49b6505..45cf7ba 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/FontStyle.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/FontStyle.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.model; +package yunjiao.springboot.extension.common.model; import lombok.Getter; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/TransparencyType.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/TransparencyType.java similarity index 96% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/TransparencyType.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/TransparencyType.java index 724043f..ea36929 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/model/TransparencyType.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/model/TransparencyType.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.model; +package yunjiao.springboot.extension.common.model; import lombok.Getter; diff --git a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/util/CommonRuntimeException.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/util/CommonRuntimeException.java similarity index 92% rename from extensions/extension-common/src/main/java/io/yunjiao/extension/common/util/CommonRuntimeException.java rename to extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/util/CommonRuntimeException.java index 93e142f..5ff6661 100644 --- a/extensions/extension-common/src/main/java/io/yunjiao/extension/common/util/CommonRuntimeException.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/util/CommonRuntimeException.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.util; +package yunjiao.springboot.extension.common.util; /** * 通用异常 diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurJFrameDemo.java b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/algorithm/GaussianBlurJFrameDemo.java similarity index 99% rename from extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurJFrameDemo.java rename to extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/algorithm/GaussianBlurJFrameDemo.java index 0dbb3f4..febb0bd 100644 --- a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurJFrameDemo.java +++ b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/algorithm/GaussianBlurJFrameDemo.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.algorithm; +package yunjiao.springboot.extension.common.algorithm; import lombok.Getter; diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/algorithm/GaussianBlurTest.java similarity index 99% rename from extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java rename to extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/algorithm/GaussianBlurTest.java index 99e0cce..02091e6 100644 --- a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/algorithm/GaussianBlurTest.java +++ b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/algorithm/GaussianBlurTest.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.algorithm; +package yunjiao.springboot.extension.common.algorithm; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/generator/TimestampIdGeneratorTest.java b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/generator/TimestampIdGeneratorTest.java similarity index 92% rename from extensions/extension-common/src/test/java/io/yunjiao/extension/common/generator/TimestampIdGeneratorTest.java rename to extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/generator/TimestampIdGeneratorTest.java index 62047c3..809f354 100644 --- a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/generator/TimestampIdGeneratorTest.java +++ b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/generator/TimestampIdGeneratorTest.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.generator; +package yunjiao.springboot.extension.common.generator; import org.junit.jupiter.api.Test; diff --git a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/lang/EnumCacheTest.java b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/lang/EnumCacheTest.java similarity index 97% rename from extensions/extension-common/src/test/java/io/yunjiao/extension/common/lang/EnumCacheTest.java rename to extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/lang/EnumCacheTest.java index 61aa7d6..3d188ad 100644 --- a/extensions/extension-common/src/test/java/io/yunjiao/extension/common/lang/EnumCacheTest.java +++ b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/lang/EnumCacheTest.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.common.lang; +package yunjiao.springboot.extension.common.lang; import org.junit.jupiter.api.Test; diff --git a/extensions/extension-id/pom.xml b/extensions/extension-id/pom.xml index 2e2f9b7..900dfda 100644 --- a/extensions/extension-id/pom.xml +++ b/extensions/extension-id/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extensions ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-common diff --git a/extensions/extension-id/src/main/java/io/yunjiao/extension/id/_Id.java b/extensions/extension-id/src/main/java/yunjiao/springboot/extension/id/_Id.java similarity index 60% rename from extensions/extension-id/src/main/java/io/yunjiao/extension/id/_Id.java rename to extensions/extension-id/src/main/java/yunjiao/springboot/extension/id/_Id.java index 65e38c1..8af7028 100644 --- a/extensions/extension-id/src/main/java/io/yunjiao/extension/id/_Id.java +++ b/extensions/extension-id/src/main/java/yunjiao/springboot/extension/id/_Id.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.id; +package yunjiao.springboot.extension.id; /** * _Id diff --git a/extensions/extension-querydsl/pom.xml b/extensions/extension-querydsl/pom.xml index 35d0657..9f3a8f8 100644 --- a/extensions/extension-querydsl/pom.xml +++ b/extensions/extension-querydsl/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extensions ${revision} diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/QSpecification.java b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/QSpecification.java similarity index 99% rename from extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/QSpecification.java rename to extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/QSpecification.java index 6077727..ad14e50 100644 --- a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/QSpecification.java +++ b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/QSpecification.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl; +package yunjiao.springboot.extension.querydsl; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/QueryDSLUtils.java b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/QueryDSLUtils.java similarity index 97% rename from extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/QueryDSLUtils.java rename to extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/QueryDSLUtils.java index bb4740e..58e1714 100644 --- a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/QueryDSLUtils.java +++ b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/QueryDSLUtils.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl; +package yunjiao.springboot.extension.querydsl; import com.querydsl.core.types.Expression; import com.querydsl.core.types.Order; diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/SpecificationComposition.java b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/SpecificationComposition.java similarity index 97% rename from extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/SpecificationComposition.java rename to extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/SpecificationComposition.java index 919e24e..7adb35f 100644 --- a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/SpecificationComposition.java +++ b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/SpecificationComposition.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl; +package yunjiao.springboot.extension.querydsl; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/_Querydsl.java b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/_Querydsl.java similarity index 61% rename from extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/_Querydsl.java rename to extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/_Querydsl.java index 8360cf3..7c60585 100644 --- a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/_Querydsl.java +++ b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/_Querydsl.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl; +package yunjiao.springboot.extension.querydsl; /** * _Querydsl diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/jpa/JPAQueryCurdExecutor.java b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryCurdExecutor.java similarity index 99% rename from extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/jpa/JPAQueryCurdExecutor.java rename to extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryCurdExecutor.java index 4ca430b..12135ef 100644 --- a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/jpa/JPAQueryCurdExecutor.java +++ b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryCurdExecutor.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.jpa; +package yunjiao.springboot.extension.querydsl.jpa; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.NonUniqueResultException; @@ -8,7 +8,7 @@ import com.querydsl.core.types.Predicate; import com.querydsl.jpa.impl.JPADeleteClause; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAUpdateClause; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import jakarta.persistence.EntityNotFoundException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.domain.Page; diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupport.java b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryRepositorySupport.java similarity index 97% rename from extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupport.java rename to extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryRepositorySupport.java index 9eea118..ae7036e 100644 --- a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupport.java +++ b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryRepositorySupport.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.jpa; +package yunjiao.springboot.extension.querydsl.jpa; import com.querydsl.core.Tuple; import com.querydsl.core.types.EntityPath; @@ -7,8 +7,8 @@ import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.EntityPathBase; import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.jpa.impl.*; -import io.yunjiao.extension.querydsl.QSpecification; -import io.yunjiao.extension.querydsl.QueryDSLUtils; +import yunjiao.springboot.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QueryDSLUtils; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/sql/SQLQueryCurdExecutor.java b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryCurdExecutor.java similarity index 99% rename from extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/sql/SQLQueryCurdExecutor.java rename to extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryCurdExecutor.java index 894f9a8..a0e40e0 100644 --- a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/sql/SQLQueryCurdExecutor.java +++ b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryCurdExecutor.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.sql; +package yunjiao.springboot.extension.querydsl.sql; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.NonUniqueResultException; @@ -8,7 +8,7 @@ import com.querydsl.core.types.Predicate; import com.querydsl.sql.SQLQuery; import com.querydsl.sql.dml.SQLDeleteClause; import com.querydsl.sql.dml.SQLUpdateClause; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import jakarta.persistence.EntityNotFoundException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.domain.Page; diff --git a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupport.java b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryRepositorySupport.java similarity index 97% rename from extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupport.java rename to extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryRepositorySupport.java index c3441b4..4981e9e 100644 --- a/extensions/extension-querydsl/src/main/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupport.java +++ b/extensions/extension-querydsl/src/main/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryRepositorySupport.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.sql; +package yunjiao.springboot.extension.querydsl.sql; import com.querydsl.core.Tuple; import com.querydsl.core.types.Expression; @@ -12,8 +12,8 @@ import com.querydsl.sql.dml.SQLDeleteClause; import com.querydsl.sql.dml.SQLInsertClause; import com.querydsl.sql.dml.SQLMergeClause; import com.querydsl.sql.dml.SQLUpdateClause; -import io.yunjiao.extension.querydsl.QSpecification; -import io.yunjiao.extension.querydsl.QueryDSLUtils; +import yunjiao.springboot.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QueryDSLUtils; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/QSpecificationTest.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/QSpecificationTest.java similarity index 98% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/QSpecificationTest.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/QSpecificationTest.java index c45c8f1..15953aa 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/QSpecificationTest.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/QSpecificationTest.java @@ -1,8 +1,8 @@ -package io.yunjiao.extension.querydsl; +package yunjiao.springboot.extension.querydsl; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; -import io.yunjiao.extension.querydsl.sql.QUsers; +import yunjiao.springboot.extension.querydsl.sql.QUsers; import org.junit.jupiter.api.Test; import java.sql.Date; diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java similarity index 98% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java index aa43818..2990dd5 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/JPAQueryRepositorySupportTest.java @@ -1,8 +1,8 @@ -package io.yunjiao.extension.querydsl.jpa; +package yunjiao.springboot.extension.querydsl.jpa; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.EntityNotFoundException; @@ -237,7 +237,7 @@ public class JPAQueryRepositorySupportTest { public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource()); - em.setPackagesToScan("io.yunjiao"); + em.setPackagesToScan("yunjiao.springboot"); em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); Properties properties = new Properties(); diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/Order.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/Order.java similarity index 91% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/Order.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/Order.java index 70af3b2..068813e 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/Order.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/Order.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.jpa; +package yunjiao.springboot.extension.querydsl.jpa; import jakarta.persistence.*; import lombok.Data; diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/User.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/User.java similarity index 94% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/User.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/User.java index 01c0df8..9c9b670 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/jpa/User.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/jpa/User.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.jpa; +package yunjiao.springboot.extension.querydsl.jpa; import jakarta.persistence.*; import lombok.Data; diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/Order.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/Order.java similarity index 84% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/Order.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/Order.java index ce5a2c7..a835fb9 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/Order.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/Order.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.sql; +package yunjiao.springboot.extension.querydsl.sql; import lombok.Data; diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/QOrders.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/QOrders.java similarity index 98% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/QOrders.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/QOrders.java index d790e0f..9d0dc24 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/QOrders.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/QOrders.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.sql; +package yunjiao.springboot.extension.querydsl.sql; import com.querydsl.core.types.Path; import com.querydsl.core.types.PathMetadata; diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/QUsers.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/QUsers.java similarity index 98% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/QUsers.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/QUsers.java index 6a75061..bd9b490 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/QUsers.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/QUsers.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.sql; +package yunjiao.springboot.extension.querydsl.sql; import com.querydsl.core.types.Path; import com.querydsl.core.types.PathMetadata; diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupportTest.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryRepositorySupportTest.java similarity index 93% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupportTest.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryRepositorySupportTest.java index 60b35b1..432b7dc 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/SQLQueryRepositorySupportTest.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/SQLQueryRepositorySupportTest.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.sql; +package yunjiao.springboot.extension.querydsl.sql; import com.querydsl.core.types.Projections; import com.querydsl.sql.H2Templates; @@ -6,8 +6,9 @@ import com.querydsl.sql.SQLQuery; import com.querydsl.sql.SQLQueryFactory; import com.querydsl.sql.spring.SpringConnectionProvider; import com.querydsl.sql.spring.SpringExceptionTranslator; -import io.yunjiao.extension.querydsl.QSpecification; +import yunjiao.springboot.extension.querydsl.QSpecification; import jakarta.persistence.EntityNotFoundException; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -64,7 +65,7 @@ public class SQLQueryRepositorySupportTest { @Test void givenSingleName_whenFindSingleByName_thenOk() { - assertThat(repository.findSingleByName("zhangs")) + Assertions.assertThat(repository.findSingleByName("zhangs")) .isPresent() .hasValueSatisfying(user -> { assertThat(user.getName()).isEqualTo("zhangs"); @@ -73,7 +74,7 @@ public class SQLQueryRepositorySupportTest { @Test void givenWrongName_whenFindSingleByName_thenOk() { - assertThat(repository.findSingleByName("lis")).isEmpty(); + Assertions.assertThat(repository.findSingleByName("lis")).isEmpty(); } @Test @@ -88,7 +89,7 @@ public class SQLQueryRepositorySupportTest { @Test void whenFindList() { - assertThat(repository.findList("Michael Miller", 23, new Date(0))).hasSize(2); + Assertions.assertThat(repository.findList("Michael Miller", 23, new Date(0))).hasSize(2); } @Test @@ -97,7 +98,7 @@ public class SQLQueryRepositorySupportTest { assertThat(users.getTotalPages()).isEqualTo(3); assertThat(users.getNumberOfElements()).isEqualTo(5); assertThat(users.getTotalElements()).isEqualTo(15); - assertThat(users.getContent()) + Assertions.assertThat(users.getContent()) .hasSize(5) .first() .extracting(User::getName) @@ -110,7 +111,7 @@ public class SQLQueryRepositorySupportTest { assertThat(users.getTotalPages()).isEqualTo(3); assertThat(users.getNumberOfElements()).isEqualTo(5); assertThat(users.getTotalElements()).isEqualTo(15); - assertThat(users.getContent()) + Assertions.assertThat(users.getContent()) .hasSize(5) .first() .extracting(User::getName) @@ -121,7 +122,7 @@ public class SQLQueryRepositorySupportTest { @Test void whenFindUserByOrderStatus() { List users = repository.findUserByOrderStatus(); - assertThat(users).hasSize(1).first().extracting(User::getName).isEqualTo("Michael Miller"); + Assertions.assertThat(users).hasSize(1).first().extracting(User::getName).isEqualTo("Michael Miller"); } @Test @@ -129,7 +130,7 @@ public class SQLQueryRepositorySupportTest { Pair> uo = repository.findUserAndOrders(5L); assertThat(uo).isNotNull(); assertThat(uo.getFirst()).extracting(User::getName).isEqualTo("Michael Miller"); - assertThat(uo.getSecond()).hasSize(2); + Assertions.assertThat(uo.getSecond()).hasSize(2); } @Repository diff --git a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/User.java b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/User.java similarity index 84% rename from extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/User.java rename to extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/User.java index c67c520..abb9fa0 100644 --- a/extensions/extension-querydsl/src/test/java/io/yunjiao/extension/querydsl/sql/User.java +++ b/extensions/extension-querydsl/src/test/java/yunjiao/springboot/extension/querydsl/sql/User.java @@ -1,4 +1,4 @@ -package io.yunjiao.extension.querydsl.sql; +package yunjiao.springboot.extension.querydsl.sql; import lombok.Data; diff --git a/extensions/extension-querydsl/src/test/resources/logback-test.xml b/extensions/extension-querydsl/src/test/resources/logback-test.xml index 8f4c6ad..5563ef3 100644 --- a/extensions/extension-querydsl/src/test/resources/logback-test.xml +++ b/extensions/extension-querydsl/src/test/resources/logback-test.xml @@ -1,6 +1,6 @@ - + diff --git a/extensions/pom.xml b/extensions/pom.xml index 05be5f4..2aef749 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot dependencies ${revision} ../dependencies/pom.xml diff --git a/pom.xml b/pom.xml index 89e79ab..c0963ea 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot spring-boot ${revision} pom diff --git a/projects/pom.xml b/projects/pom.xml index 2862879..45aa79d 100644 --- a/projects/pom.xml +++ b/projects/pom.xml @@ -3,8 +3,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source - spring-boot-starter-parent + io.gitee.yunjiao-source.spring-boot + starter-parent 3.5.x.3 diff --git a/projects/rest-query-language/pom.xml b/projects/rest-query-language/pom.xml index ea1c80d..2e76c3a 100644 --- a/projects/rest-query-language/pom.xml +++ b/projects/rest-query-language/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot projects ${revision} @@ -16,7 +16,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-querydsl-jpa diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/DataInitCommandLineRunner.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/DataInitCommandLineRunner.java similarity index 89% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/DataInitCommandLineRunner.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/DataInitCommandLineRunner.java index df894f6..eefa6a7 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/DataInitCommandLineRunner.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/DataInitCommandLineRunner.java @@ -1,9 +1,9 @@ -package io.yunjiao.project.rql; +package yunjiao.springboot.project.rql; -import io.yunjiao.project.rql.persistence.dao.MyUserRepository; -import io.yunjiao.project.rql.persistence.dao.UserRepository; -import io.yunjiao.project.rql.persistence.model.MyUser; -import io.yunjiao.project.rql.persistence.model.User; +import yunjiao.springboot.project.rql.persistence.dao.MyUserRepository; +import yunjiao.springboot.project.rql.persistence.dao.UserRepository; +import yunjiao.springboot.project.rql.persistence.model.MyUser; +import yunjiao.springboot.project.rql.persistence.model.User; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/RqlProjectApplication.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/RqlProjectApplication.java similarity index 88% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/RqlProjectApplication.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/RqlProjectApplication.java index 596065a..7599663 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/RqlProjectApplication.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/RqlProjectApplication.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql; +package yunjiao.springboot.project.rql; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/GenericSpecificationsBuilder.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/GenericSpecificationsBuilder.java similarity index 94% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/GenericSpecificationsBuilder.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/GenericSpecificationsBuilder.java index ae97afa..4c43320 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/GenericSpecificationsBuilder.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/GenericSpecificationsBuilder.java @@ -1,7 +1,7 @@ -package io.yunjiao.project.rql.persistence.dao; +package yunjiao.springboot.project.rql.persistence.dao; -import io.yunjiao.project.rql.web.util.SearchOperation; -import io.yunjiao.project.rql.web.util.SpecSearchCriteria; +import yunjiao.springboot.project.rql.web.util.SearchOperation; +import yunjiao.springboot.project.rql.web.util.SpecSearchCriteria; import org.springframework.data.jpa.domain.Specification; import java.util.*; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserPredicate.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserPredicate.java similarity index 89% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserPredicate.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserPredicate.java index 2fcf487..2947097 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserPredicate.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserPredicate.java @@ -1,11 +1,11 @@ -package io.yunjiao.project.rql.persistence.dao; +package yunjiao.springboot.project.rql.persistence.dao; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.NumberPath; import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.core.types.dsl.StringPath; -import io.yunjiao.project.rql.persistence.model.MyUser; -import io.yunjiao.project.rql.web.util.SearchCriteria; +import yunjiao.springboot.project.rql.persistence.model.MyUser; +import yunjiao.springboot.project.rql.web.util.SearchCriteria; import lombok.Getter; import lombok.Setter; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserPredicatesBuilder.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserPredicatesBuilder.java similarity index 93% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserPredicatesBuilder.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserPredicatesBuilder.java index cfeae96..af40ca6 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserPredicatesBuilder.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserPredicatesBuilder.java @@ -1,8 +1,8 @@ -package io.yunjiao.project.rql.persistence.dao; +package yunjiao.springboot.project.rql.persistence.dao; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; -import io.yunjiao.project.rql.web.util.SearchCriteria; +import yunjiao.springboot.project.rql.web.util.SearchCriteria; import org.springframework.lang.NonNull; import java.util.ArrayList; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserRepository.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserRepository.java similarity index 84% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserRepository.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserRepository.java index e7eaa68..d117598 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/MyUserRepository.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/MyUserRepository.java @@ -1,7 +1,6 @@ -package io.yunjiao.project.rql.persistence.dao; +package yunjiao.springboot.project.rql.persistence.dao; -import io.yunjiao.project.rql.persistence.model.MyUser; -import io.yunjiao.project.rql.persistence.model.QMyUser; +import yunjiao.springboot.project.rql.persistence.model.MyUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer; @@ -11,6 +10,7 @@ import org.springframework.data.querydsl.binding.SingleValueBinding; import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.core.types.dsl.StringPath; import org.springframework.stereotype.Repository; +import yunjiao.springboot.project.rql.persistence.model.QMyUser; @Repository public interface MyUserRepository extends JpaRepository, QuerydslPredicateExecutor, QuerydslBinderCustomizer { diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserRepository.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserRepository.java similarity index 82% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserRepository.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserRepository.java index 5ad2170..a5d7f78 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserRepository.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserRepository.java @@ -1,7 +1,7 @@ -package io.yunjiao.project.rql.persistence.dao; +package yunjiao.springboot.project.rql.persistence.dao; -import io.yunjiao.project.rql.persistence.model.User; -import io.yunjiao.project.rql.web.util.SearchCriteria; +import yunjiao.springboot.project.rql.persistence.model.User; +import yunjiao.springboot.project.rql.web.util.SearchCriteria; import jakarta.persistence.criteria.Predicate; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSearchQueryCriteriaConsumer.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSearchQueryCriteriaConsumer.java similarity index 92% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSearchQueryCriteriaConsumer.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSearchQueryCriteriaConsumer.java index eb9b1ad..3b11224 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSearchQueryCriteriaConsumer.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSearchQueryCriteriaConsumer.java @@ -1,6 +1,6 @@ -package io.yunjiao.project.rql.persistence.dao; +package yunjiao.springboot.project.rql.persistence.dao; -import io.yunjiao.project.rql.web.util.SearchCriteria; +import yunjiao.springboot.project.rql.web.util.SearchCriteria; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSpecification.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecification.java similarity index 90% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSpecification.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecification.java index 57bcdcd..f06c94c 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSpecification.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecification.java @@ -1,7 +1,7 @@ -package io.yunjiao.project.rql.persistence.dao; +package yunjiao.springboot.project.rql.persistence.dao; -import io.yunjiao.project.rql.persistence.model.User; -import io.yunjiao.project.rql.web.util.SpecSearchCriteria; +import yunjiao.springboot.project.rql.persistence.model.User; +import yunjiao.springboot.project.rql.web.util.SpecSearchCriteria; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSpecificationsBuilder.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecificationsBuilder.java similarity index 90% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSpecificationsBuilder.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecificationsBuilder.java index 2a26c49..ecdbb13 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/UserSpecificationsBuilder.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecificationsBuilder.java @@ -1,8 +1,8 @@ -package io.yunjiao.project.rql.persistence.dao; +package yunjiao.springboot.project.rql.persistence.dao; -import io.yunjiao.project.rql.persistence.model.User; -import io.yunjiao.project.rql.web.util.SearchOperation; -import io.yunjiao.project.rql.web.util.SpecSearchCriteria; +import yunjiao.springboot.project.rql.persistence.model.User; +import yunjiao.springboot.project.rql.web.util.SearchOperation; +import yunjiao.springboot.project.rql.web.util.SpecSearchCriteria; import org.springframework.data.jpa.domain.Specification; import org.springframework.lang.NonNull; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/CustomRsqlVisitor.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/CustomRsqlVisitor.java similarity index 93% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/CustomRsqlVisitor.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/CustomRsqlVisitor.java index 4476965..96d335d 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/CustomRsqlVisitor.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/CustomRsqlVisitor.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.persistence.dao.rsql; +package yunjiao.springboot.project.rql.persistence.dao.rsql; import org.springframework.data.jpa.domain.Specification; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/GenericRsqlSpecBuilder.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/GenericRsqlSpecBuilder.java similarity index 96% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/GenericRsqlSpecBuilder.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/GenericRsqlSpecBuilder.java index fdc3c7e..5cbced6 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/GenericRsqlSpecBuilder.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/GenericRsqlSpecBuilder.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.persistence.dao.rsql; +package yunjiao.springboot.project.rql.persistence.dao.rsql; import cz.jirutka.rsql.parser.ast.ComparisonNode; import cz.jirutka.rsql.parser.ast.LogicalNode; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/GenericRsqlSpecification.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/GenericRsqlSpecification.java similarity index 98% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/GenericRsqlSpecification.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/GenericRsqlSpecification.java index c35d7fd..6c67f75 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/GenericRsqlSpecification.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/GenericRsqlSpecification.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.persistence.dao.rsql; +package yunjiao.springboot.project.rql.persistence.dao.rsql; import cz.jirutka.rsql.parser.ast.ComparisonOperator; import jakarta.persistence.criteria.CriteriaBuilder; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/RsqlSearchOperation.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/RsqlSearchOperation.java similarity index 94% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/RsqlSearchOperation.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/RsqlSearchOperation.java index 95102be..18b4933 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/dao/rsql/RsqlSearchOperation.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/rsql/RsqlSearchOperation.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.persistence.dao.rsql; +package yunjiao.springboot.project.rql.persistence.dao.rsql; import cz.jirutka.rsql.parser.ast.ComparisonOperator; import cz.jirutka.rsql.parser.ast.RSQLOperators; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/MyUser.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/MyUser.java similarity index 87% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/MyUser.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/MyUser.java index bfbde77..9ca0f34 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/MyUser.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/MyUser.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.persistence.model; +package yunjiao.springboot.project.rql.persistence.model; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/User.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/User.java similarity index 84% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/User.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/User.java index a92c1eb..3648936 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/User.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/User.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.persistence.model; +package yunjiao.springboot.project.rql.persistence.model; import jakarta.persistence.*; import lombok.Data; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/User_.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/User_.java similarity index 89% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/User_.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/User_.java index ca62bce..d09b02b 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/persistence/model/User_.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/model/User_.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.persistence.model; +package yunjiao.springboot.project.rql.persistence.model; import jakarta.persistence.metamodel.SingularAttribute; import jakarta.persistence.metamodel.StaticMetamodel; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/controller/UserController.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/controller/UserController.java similarity index 90% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/controller/UserController.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/controller/UserController.java index eb456ff..7b6dfe1 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/controller/UserController.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/controller/UserController.java @@ -1,17 +1,17 @@ -package io.yunjiao.project.rql.web.controller; +package yunjiao.springboot.project.rql.web.controller; import com.google.common.base.Joiner; import com.querydsl.core.types.Predicate; import com.querydsl.core.types.dsl.BooleanExpression; import cz.jirutka.rsql.parser.RSQLParser; import cz.jirutka.rsql.parser.ast.Node; -import io.yunjiao.project.rql.persistence.dao.*; -import io.yunjiao.project.rql.persistence.dao.rsql.CustomRsqlVisitor; -import io.yunjiao.project.rql.persistence.model.MyUser; -import io.yunjiao.project.rql.persistence.model.User; -import io.yunjiao.project.rql.web.util.CriteriaParser; -import io.yunjiao.project.rql.web.util.SearchCriteria; -import io.yunjiao.project.rql.web.util.SearchOperation; +import yunjiao.springboot.project.rql.persistence.dao.*; +import yunjiao.springboot.project.rql.persistence.dao.rsql.CustomRsqlVisitor; +import yunjiao.springboot.project.rql.persistence.model.MyUser; +import yunjiao.springboot.project.rql.persistence.model.User; +import yunjiao.springboot.project.rql.web.util.CriteriaParser; +import yunjiao.springboot.project.rql.web.util.SearchCriteria; +import yunjiao.springboot.project.rql.web.util.SearchOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.querydsl.binding.QuerydslPredicate; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/error/RestResponseEntityExceptionHandler.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/error/RestResponseEntityExceptionHandler.java similarity index 96% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/error/RestResponseEntityExceptionHandler.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/error/RestResponseEntityExceptionHandler.java index 43fb5b5..7db25b6 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/error/RestResponseEntityExceptionHandler.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/error/RestResponseEntityExceptionHandler.java @@ -1,6 +1,6 @@ -package io.yunjiao.project.rql.web.error; +package yunjiao.springboot.project.rql.web.error; -import io.yunjiao.project.rql.web.exception.MyResourceNotFoundException; +import yunjiao.springboot.project.rql.web.exception.MyResourceNotFoundException; import jakarta.persistence.EntityNotFoundException; import org.hibernate.exception.ConstraintViolationException; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/exception/MyResourceNotFoundException.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/exception/MyResourceNotFoundException.java similarity index 89% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/exception/MyResourceNotFoundException.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/exception/MyResourceNotFoundException.java index 444274d..a4d28d4 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/exception/MyResourceNotFoundException.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/exception/MyResourceNotFoundException.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.web.exception; +package yunjiao.springboot.project.rql.web.exception; public final class MyResourceNotFoundException extends RuntimeException { diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/CriteriaParser.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/CriteriaParser.java similarity index 98% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/CriteriaParser.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/CriteriaParser.java index 8a8f05c..91f4fa5 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/CriteriaParser.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/CriteriaParser.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.web.util; +package yunjiao.springboot.project.rql.web.util; import com.google.common.base.Joiner; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SearchCriteria.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SearchCriteria.java similarity index 94% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SearchCriteria.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SearchCriteria.java index 05e60e2..c4544e5 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SearchCriteria.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SearchCriteria.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.web.util; +package yunjiao.springboot.project.rql.web.util; public class SearchCriteria { diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SearchOperation.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SearchOperation.java similarity index 95% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SearchOperation.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SearchOperation.java index 10233f2..a2f1e9c 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SearchOperation.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SearchOperation.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.web.util; +package yunjiao.springboot.project.rql.web.util; public enum SearchOperation { EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS; diff --git a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SpecSearchCriteria.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SpecSearchCriteria.java similarity index 97% rename from projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SpecSearchCriteria.java rename to projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SpecSearchCriteria.java index 5d804bc..6d1a025 100644 --- a/projects/rest-query-language/src/main/java/io/yunjiao/project/rql/web/util/SpecSearchCriteria.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/web/util/SpecSearchCriteria.java @@ -1,4 +1,4 @@ -package io.yunjiao.project.rql.web.util; +package yunjiao.springboot.project.rql.web.util; public class SpecSearchCriteria { diff --git a/projects/rest-query-language/src/main/resources/application.yml b/projects/rest-query-language/src/main/resources/application.yml index d43db6f..87bfd51 100644 --- a/projects/rest-query-language/src/main/resources/application.yml +++ b/projects/rest-query-language/src/main/resources/application.yml @@ -18,5 +18,5 @@ logging: root: info org.springframework.boot.autoconfigure: info org.springframework.context: debug - io.yunjiao: debug + yunjiao.springboot: debug diff --git a/projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPACriteriaQueryIntegrationTest.java b/projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPACriteriaQueryIntegrationTest.java similarity index 92% rename from projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPACriteriaQueryIntegrationTest.java rename to projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPACriteriaQueryIntegrationTest.java index 0ca089d..e2e97e0 100644 --- a/projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPACriteriaQueryIntegrationTest.java +++ b/projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPACriteriaQueryIntegrationTest.java @@ -1,8 +1,8 @@ -package io.yunjiao.project.rql.persistence.query; +package yunjiao.springboot.project.rql.persistence.query; -import io.yunjiao.project.rql.persistence.dao.UserRepository; -import io.yunjiao.project.rql.persistence.model.User; -import io.yunjiao.project.rql.web.util.SearchCriteria; +import yunjiao.springboot.project.rql.persistence.dao.UserRepository; +import yunjiao.springboot.project.rql.persistence.model.User; +import yunjiao.springboot.project.rql.web.util.SearchCriteria; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPAQuerydslIntegrationTest.java b/projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPAQuerydslIntegrationTest.java similarity index 91% rename from projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPAQuerydslIntegrationTest.java rename to projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPAQuerydslIntegrationTest.java index e8cbc3f..a39fe6b 100644 --- a/projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPAQuerydslIntegrationTest.java +++ b/projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPAQuerydslIntegrationTest.java @@ -1,8 +1,8 @@ -package io.yunjiao.project.rql.persistence.query; +package yunjiao.springboot.project.rql.persistence.query; -import io.yunjiao.project.rql.persistence.dao.MyUserPredicatesBuilder; -import io.yunjiao.project.rql.persistence.dao.MyUserRepository; -import io.yunjiao.project.rql.persistence.model.MyUser; +import yunjiao.springboot.project.rql.persistence.dao.MyUserPredicatesBuilder; +import yunjiao.springboot.project.rql.persistence.dao.MyUserRepository; +import yunjiao.springboot.project.rql.persistence.model.MyUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPASpecificationIntegrationTest.java b/projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPASpecificationIntegrationTest.java similarity index 90% rename from projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPASpecificationIntegrationTest.java rename to projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPASpecificationIntegrationTest.java index 536ce28..8e3c55a 100644 --- a/projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/JPASpecificationIntegrationTest.java +++ b/projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/JPASpecificationIntegrationTest.java @@ -1,13 +1,13 @@ -package io.yunjiao.project.rql.persistence.query; - -import io.yunjiao.project.rql.persistence.dao.GenericSpecificationsBuilder; -import io.yunjiao.project.rql.persistence.dao.UserRepository; -import io.yunjiao.project.rql.persistence.dao.UserSpecification; -import io.yunjiao.project.rql.persistence.dao.UserSpecificationsBuilder; -import io.yunjiao.project.rql.persistence.model.User; -import io.yunjiao.project.rql.web.util.CriteriaParser; -import io.yunjiao.project.rql.web.util.SearchOperation; -import io.yunjiao.project.rql.web.util.SpecSearchCriteria; +package yunjiao.springboot.project.rql.persistence.query; + +import yunjiao.springboot.project.rql.persistence.dao.GenericSpecificationsBuilder; +import yunjiao.springboot.project.rql.persistence.dao.UserRepository; +import yunjiao.springboot.project.rql.persistence.dao.UserSpecification; +import yunjiao.springboot.project.rql.persistence.dao.UserSpecificationsBuilder; +import yunjiao.springboot.project.rql.persistence.model.User; +import yunjiao.springboot.project.rql.web.util.CriteriaParser; +import yunjiao.springboot.project.rql.web.util.SearchOperation; +import yunjiao.springboot.project.rql.web.util.SpecSearchCriteria; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/RsqlIntegrationTest.java b/projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/RsqlIntegrationTest.java similarity index 92% rename from projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/RsqlIntegrationTest.java rename to projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/RsqlIntegrationTest.java index 5502130..b6e9b2a 100644 --- a/projects/rest-query-language/src/test/java/io/yunjiao/project/rql/persistence/query/RsqlIntegrationTest.java +++ b/projects/rest-query-language/src/test/java/yunjiao/springboot/project/rql/persistence/query/RsqlIntegrationTest.java @@ -1,10 +1,10 @@ -package io.yunjiao.project.rql.persistence.query; +package yunjiao.springboot.project.rql.persistence.query; import cz.jirutka.rsql.parser.RSQLParser; import cz.jirutka.rsql.parser.ast.Node; -import io.yunjiao.project.rql.persistence.dao.UserRepository; -import io.yunjiao.project.rql.persistence.dao.rsql.CustomRsqlVisitor; -import io.yunjiao.project.rql.persistence.model.User; +import yunjiao.springboot.project.rql.persistence.dao.UserRepository; +import yunjiao.springboot.project.rql.persistence.dao.rsql.CustomRsqlVisitor; +import yunjiao.springboot.project.rql.persistence.model.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-boot-starter-parent/pom.xml b/starter-parent/pom.xml similarity index 97% rename from spring-boot-starter-parent/pom.xml rename to starter-parent/pom.xml index 088eb5e..fb2112d 100644 --- a/spring-boot-starter-parent/pom.xml +++ b/starter-parent/pom.xml @@ -10,8 +10,8 @@ 4.0.0 - io.gitee.yunjiao-source - spring-boot-starter-parent + io.gitee.yunjiao-source.spring-boot + starter-parent 3.5.x.3 pom Spring Boot :: Starter Parent @@ -59,7 +59,7 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot dependencies ${yunjiao-spring-boot.version} pom diff --git a/starters/README.md b/starters/README.md index 8f3db5d..7891c03 100644 --- a/starters/README.md +++ b/starters/README.md @@ -9,7 +9,7 @@ 在`pom.xml`中添加依赖 ```xml - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-apijson-fastjson2 ${version} @@ -40,7 +40,7 @@ 在`pom.xml`中添加依赖 ```xml - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-apijson-gson ${version} @@ -70,7 +70,7 @@ 在`pom.xml`中添加依赖 ```xml - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-hutool ${version} @@ -89,7 +89,7 @@ 在`pom.xml`中添加依赖 ```xml - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-querydsl-jpa ${version} @@ -107,7 +107,7 @@ 在`pom.xml`中添加依赖 ```xml - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-querydsl-sql ${version} @@ -123,7 +123,7 @@ 验证码启动器, 在`pom.xml`中添加依赖 ```xml - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starter-captcha ${version} diff --git a/starters/pom.xml b/starters/pom.xml index 95f68b1..c488c87 100644 --- a/starters/pom.xml +++ b/starters/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot dependencies ${revision} ../dependencies/pom.xml @@ -12,7 +12,7 @@ starters pom - Spring Boot:: Starters + Spring Boot :: Starters Starters启动器 diff --git a/starters/starter-apijson-fastjson2/pom.xml b/starters/starter-apijson-fastjson2/pom.xml index 1dba229..8fd1253 100644 --- a/starters/starter-apijson-fastjson2/pom.xml +++ b/starters/starter-apijson-fastjson2/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starters ${revision} @@ -16,11 +16,11 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot autoconfigure - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-apijson diff --git a/starters/starter-apijson-gson/pom.xml b/starters/starter-apijson-gson/pom.xml index 08b0d0c..8eeee18 100644 --- a/starters/starter-apijson-gson/pom.xml +++ b/starters/starter-apijson-gson/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starters ${revision} @@ -16,11 +16,11 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot autoconfigure - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-apijson diff --git a/starters/starter-captcha/pom.xml b/starters/starter-captcha/pom.xml index 28b12b8..c418882 100644 --- a/starters/starter-captcha/pom.xml +++ b/starters/starter-captcha/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starters ${revision} @@ -16,11 +16,11 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot autoconfigure - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-captcha diff --git a/starters/starter-id/pom.xml b/starters/starter-id/pom.xml index 15b48cb..5d2d6d3 100644 --- a/starters/starter-id/pom.xml +++ b/starters/starter-id/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starters ${revision} @@ -16,11 +16,11 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot autoconfigure - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-id diff --git a/starters/starter-querydsl-jpa/pom.xml b/starters/starter-querydsl-jpa/pom.xml index 44d11a6..79658d7 100644 --- a/starters/starter-querydsl-jpa/pom.xml +++ b/starters/starter-querydsl-jpa/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starters ${revision} @@ -21,11 +21,11 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot autoconfigure - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-querydsl diff --git a/starters/starter-querydsl-sql/pom.xml b/starters/starter-querydsl-sql/pom.xml index 1207ac2..85cc3fc 100644 --- a/starters/starter-querydsl-sql/pom.xml +++ b/starters/starter-querydsl-sql/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot starters ${revision} @@ -16,11 +16,11 @@ - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot autoconfigure - io.gitee.yunjiao-source + io.gitee.yunjiao-source.spring-boot extension-querydsl -- Gitee From 4bed8a9e3e98bbe877bb6dddb12209f375638d5e Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 28 Aug 2025 09:05:38 +0800 Subject: [PATCH 14/15] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90aj-captcha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +- .../captcha/AnjiCaptchaConfiguration.java | 178 ++++++++++++++++++ .../captcha/AnjiCaptchaProperties.java | 95 ++++++++++ .../captcha/CaptchaAutoConfiguration.java | 15 +- .../captcha/CaptchaServiceFactory.java | 19 +- .../captcha/HutoolCaptchaConfiguration.java | 18 +- .../id/HutoolIdConfiguration.java | 8 +- .../util/PropertyNameConsts.java | 5 + .../captcha/AnjiCaptchaConfigurationTest.java | 40 ++++ .../captcha/CaptchaAutoConfigurationTest.java | 2 +- ...va => HutoolCaptchaConfigurationTest.java} | 3 +- bin/mvn-all-clean.cmd | 14 +- bin/mvn-all-install.cmd | 14 +- bin/mvn-deploy.cmd | 6 +- dependencies/pom.xml | 38 ++-- .../captcha/CaptchaCommandLineRunner.java | 25 ++- .../example/captcha/CaptchaController.java | 9 +- examples/pom.xml | 4 +- extensions/README.md | 2 +- extensions/extension-captcha/pom.xml | 7 +- .../{hutool => }/CaptchaException.java | 2 +- .../captcha/anji/BaseCaptchaService.java | 61 ++++++ .../anji/BlockPuzzleCaptchaService.java | 55 ++++++ .../captcha/anji/ClickWorkCaptchaService.java | 65 +++++++ .../anji/RotatePluzzleCaptchaService.java | 44 +++++ .../hutool/AbstractCaptchaService.java | 35 ++-- .../captcha/hutool/CircleCaptchaService.java | 4 +- .../captcha/hutool/GifCaptchaService.java | 8 +- .../captcha/hutool/LineCaptchaService.java | 4 +- .../captcha/hutool/ShearCaptchaService.java | 4 +- .../extension/captcha/CaptchaJFrameDemo.java | 58 +++++- .../anji/BlockPuzzleCaptchaServiceTest.java | 81 ++++++++ .../anji/ClickWorkCaptchaServiceTest.java | 98 ++++++++++ .../hutool/CircleCaptchaServiceTest.java | 62 ++++++ .../captcha/hutool/GifCaptchaServiceTest.java | 67 +++++++ .../hutool/LineCaptchaServiceTest.java | 62 ++++++ .../hutool/ShearCaptchaServiceTest.java | 62 ++++++ extensions/extension-common/pom.xml | 19 +- .../common/captcha/CaptchaCategory.java | 25 +-- .../extension/common/captcha/CaptchaData.java | 14 +- .../common/captcha/CaptchaReponse.java | 8 +- .../common/captcha/CaptchaService.java | 4 +- .../common/captcha/CaptchaValidate.java | 2 +- .../extension/common/captcha/Point.java | 9 + .../extension/common/util/GsonUtils.java | 122 ++++++++++++ .../extension/common/util/GsonUtilsTest.java | 154 +++++++++++++++ pom.xml | 4 +- projects/pom.xml | 4 +- .../persistence/dao/UserSpecification.java | 23 +-- .../dao/UserSpecificationsBuilder.java | 2 +- starter-parent/pom.xml | 4 +- 51 files changed, 1501 insertions(+), 181 deletions(-) create mode 100644 autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaConfiguration.java create mode 100644 autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaProperties.java create mode 100644 autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaConfigurationTest.java rename autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/{IdCaptchaConfigurationTest.java => HutoolCaptchaConfigurationTest.java} (96%) rename extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/{hutool => }/CaptchaException.java (87%) create mode 100644 extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/BaseCaptchaService.java create mode 100644 extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/BlockPuzzleCaptchaService.java create mode 100644 extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/ClickWorkCaptchaService.java create mode 100644 extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/RotatePluzzleCaptchaService.java create mode 100644 extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/anji/BlockPuzzleCaptchaServiceTest.java create mode 100644 extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/anji/ClickWorkCaptchaServiceTest.java create mode 100644 extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaServiceTest.java create mode 100644 extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaServiceTest.java create mode 100644 extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaServiceTest.java create mode 100644 extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaServiceTest.java create mode 100644 extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/Point.java create mode 100644 extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/util/GsonUtils.java create mode 100644 extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/util/GsonUtilsTest.java diff --git a/README.md b/README.md index b859b44..01fe8e3 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,18 @@ 基于`Spring Boot`框架,集成其他框架开发的`Starter` -## 版本说明 +## 发布版本 -第一个的版本是`3.5.x.1`,前面两位是`Spring Boot`的版本,随着`Spring Boot`的版本升级而变。最后一位是本框架的版本,随着框架的扩展而增加,如:1,2,3,...x -不会因`Spring Boot`版本变动而变动。 +| 项目版本 | String Boot 版本 | +|--------------|----------------| +| <=0.3.0 | 3.0.13 | -如果需要 `Spring Boot` 3.5之前的包,请`fork`本项目,克隆代码,自己编译需要的版本。 + + +如果需要 `Spring Boot` 3.5之前的包,请`fork`本项目,克隆代码,自己编译需要的版本。例如编译`Spring Boot 3.5.4`的版本 +```shell +mvn clean install -P spring-boot-3.5.4 +``` ## 项目列表 diff --git a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaConfiguration.java new file mode 100644 index 0000000..1eeae23 --- /dev/null +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaConfiguration.java @@ -0,0 +1,178 @@ +package yunjiao.springboot.autoconfigure.captcha; + +import com.anji.captcha.model.common.Const; +import com.anji.captcha.service.CaptchaCacheService; +import com.anji.captcha.service.CaptchaService; +import com.anji.captcha.service.impl.CaptchaServiceFactory; +import com.anji.captcha.util.Base64Utils; +import com.anji.captcha.util.FileCopyUtils; +import com.anji.captcha.util.ImageUtils; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.StringUtils; +import yunjiao.springboot.extension.captcha.anji.BlockPuzzleCaptchaService; +import yunjiao.springboot.extension.captcha.anji.ClickWorkCaptchaService; +import yunjiao.springboot.extension.captcha.anji.RotatePluzzleCaptchaService; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * Anji 验证码自动配置 + * + * @author yangyunjiao + */ +@Slf4j +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({CaptchaService.class}) +@EnableConfigurationProperties({AnjiCaptchaProperties.class}) +public class AnjiCaptchaConfiguration { + /** + * {@link PostConstruct} 注解方法 + */ + @PostConstruct + public void postConstruct() { + log.info("Anji Captcha Configuration"); + } + + @Bean + CaptchaCacheService captchaCacheService(){ + CaptchaCacheService service = CaptchaServiceFactory.getCache("local"); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Captcha Cache Service:{}]", service); + } + return service; + } + + @Bean + CaptchaService captchaService(AnjiCaptchaProperties properties) { + Properties config = new Properties(); + config.put(Const.CAPTCHA_WATER_MARK, properties.getWaterMark()); + config.put(Const.CAPTCHA_FONT_TYPE, properties.getClickWord().getFontType()); + config.put(Const.CAPTCHA_TYPE, "default"); + config.put(Const.CAPTCHA_INTERFERENCE_OPTIONS, properties.getBlockPuzzle().getInterferenceOptions()); + config.put(Const.ORIGINAL_PATH_JIGSAW, properties.getBlockPuzzle().getJigsaw()); + config.put(Const.ORIGINAL_PATH_PIC_CLICK, properties.getClickWord().getPicClick()); + config.put(Const.CAPTCHA_SLIP_OFFSET, properties.getBlockPuzzle().getSlipOffset()); + config.put(Const.CAPTCHA_AES_STATUS, "false"); + config.put(Const.CAPTCHA_WATER_FONT, properties.getWaterFont()); + + + config.put(Const.CAPTCHA_FONT_SIZE, properties.getClickWord().getFontSize()); + config.put(Const.CAPTCHA_FONT_STYLE, properties.getClickWord().getFontStyle().getMapping()); + config.put(Const.CAPTCHA_WORD_COUNT, properties.getClickWord().getClickWordCount()); + + fixBugCache(config); + fixBugResource(config); + + // 加载自定义 + initJigsawResource(properties.getBlockPuzzle().getJigsaw()); + initPicClickResource(properties.getClickWord().getPicClick()); + CaptchaService service = CaptchaServiceFactory.getInstance(config); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Captcha Service:{}]", service); + } + return service; + } + + /** + * 初始化时,加载默认资源执行多次, 资源重复加载。解决: 手工加载默认资源 + * + * @param config 必须值 + */ + private void fixBugResource(Properties config) { + // 框架问题: 方向反了 + config.put(Const.CAPTCHA_INIT_ORIGINAL, "true"); + + // 加载默认资源 + ImageUtils.cacheImage(null, null, null); + } + + /** + * 初始化缓存回执行多次,造成CacheUtil中任务泄露。解决:停用任务,手工删除缓存 + * + * @param config 必须值 + */ + private void fixBugCache(Properties config) { + config.put(Const.CAPTCHA_CACHETYPE, "local"); + config.put(Const.CAPTCHA_TIMING_CLEAR_SECOND, "0"); + } + + @Bean + yunjiao.springboot.extension.common.captcha.CaptchaService blockPuzzleCaptchaService(CaptchaService captchaService, + CaptchaCacheService captchaCacheService, + AnjiCaptchaProperties properties) { + BlockPuzzleCaptchaService service = new BlockPuzzleCaptchaService(captchaService, + captchaCacheService, + properties.getBlockPuzzle().getSlipOffset()); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Block Puzzle CaptchaService:{}]", service); + } + return service; + } + + @Bean + yunjiao.springboot.extension.common.captcha.CaptchaService clickWorkCaptchaService(CaptchaService captchaService, + CaptchaCacheService captchaCacheService, + AnjiCaptchaProperties properties) { + ClickWorkCaptchaService service = new ClickWorkCaptchaService(captchaService, + captchaCacheService, + properties.getBlockPuzzle().getSlipOffset()); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Click Work CaptchaService:{}]", service); + } + return service; + } + + @Bean + yunjiao.springboot.extension.common.captcha.CaptchaService rotatePluzzleCaptchaService(CaptchaService captchaService, + CaptchaCacheService captchaCacheService) { + RotatePluzzleCaptchaService service = new RotatePluzzleCaptchaService(captchaService, + captchaCacheService); + if (log.isDebugEnabled()) { + log.debug("Configure Bean [Rotate Pluzzle CaptchaService:{}]", service); + } + return service; + } + + private void initJigsawResource(String jigsaw) { + if (StringUtils.hasText(jigsaw) && jigsaw.startsWith("classpath")) { + ImageUtils.cacheBootImage(getResourcesImagesFile(jigsaw + "/original/*.png"), + getResourcesImagesFile(jigsaw + "/slidingBlock/*.png"), + Collections.emptyMap()); + } + } + + private void initPicClickResource(String picClick) { + if (StringUtils.hasText(picClick) && picClick.startsWith("classpath")) { + ImageUtils.cacheBootImage(Collections.emptyMap(), Collections.emptyMap(), + getResourcesImagesFile(picClick + "/*.png")); + } + } + + public Map getResourcesImagesFile(String path) { + Map imgMap = new HashMap<>(); + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + try { + Resource[] resources = resolver.getResources(path); + for (Resource resource : resources) { + byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); + String string = Base64Utils.encodeToString(bytes); + String filename = resource.getFilename(); + imgMap.put(filename, string); + } + } catch (Exception e) { + log.error("初始化Anji验证码码资源异常", e); + } + return imgMap; + } +} diff --git a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaProperties.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaProperties.java new file mode 100644 index 0000000..d141df3 --- /dev/null +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaProperties.java @@ -0,0 +1,95 @@ +package yunjiao.springboot.autoconfigure.captcha; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import yunjiao.springboot.autoconfigure.util.PropertyNameConsts; +import yunjiao.springboot.extension.common.model.FontStyle; + +/** + * anji验证码属性 + * + * @author yangyunjiao + */ +@Data +@ConfigurationProperties(prefix = PropertyNameConsts.PROPERTY_PREFIX_CAPTCHA_ANJI) +public class AnjiCaptchaProperties { + /** + * 右下角水印文字(我的水印). + */ + private String waterMark = "我的水印"; + + /** + * 右下角水印字体(文泉驿正黑). + */ + private String waterFont = "WenQuanZhengHei.ttf"; + + /** + * 滑动验证码配置 + */ + @NestedConfigurationProperty + private BlockPuzzleOptions blockPuzzle = new BlockPuzzleOptions(); + + /** + * 点选文字验证码配置 + */ + @NestedConfigurationProperty + private ClickWordOptions clickWord = new ClickWordOptions(); + + /** + * 滑动验证码 属性 + */ + @Data + public static class BlockPuzzleOptions { + /** + * 滑动拼图底图路径. + */ + private String jigsaw = ""; + + /** + * 校验滑动拼图允许误差偏移量(默认5像素). + */ + private Integer slipOffset = 5; + + /** + * 滑块干扰项(0/1/2) + */ + private Integer interferenceOptions = 0; + } + + /** + * 点选文字验证码 属性 + */ + @Data + public static class ClickWordOptions { + /** + * 点选文字底图路径. + */ + private String picClick = ""; + + /** + * 点选文字验证码的文字字体(文泉驿正黑). + */ + private String fontType = "WenQuanZhengHei.ttf"; + + /** + * 点选字体样式 + */ + private FontStyle fontStyle = FontStyle.bold; + + /** + * 点选字体大小 + */ + private Integer fontSize = 25; + + /** + * 点选文字个数 + */ + private Integer clickWordCount = 4; + + /** + * 校验拼图允许误差(默认13像素). + */ + private Integer slipOffset = 13; + } +} diff --git a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java index 087990d..1a9ea94 100644 --- a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfiguration.java @@ -1,15 +1,18 @@ package yunjiao.springboot.autoconfigure.captcha; -import yunjiao.springboot.extension.captcha._Captcha; -import yunjiao.springboot.extension.common.captcha.CaptchaService; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import yunjiao.springboot.extension.captcha._Captcha; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaService; import java.util.Map; +import java.util.stream.Collectors; /** * 验证码 自动配置 @@ -20,7 +23,8 @@ import java.util.Map; @AutoConfiguration @ConditionalOnClass({_Captcha.class}) @Import({ - HutoolCaptchaConfiguration.class + HutoolCaptchaConfiguration.class, + AnjiCaptchaConfiguration.class }) public class CaptchaAutoConfiguration { /** @@ -32,7 +36,10 @@ public class CaptchaAutoConfiguration { } @Bean - CaptchaServiceFactory captchaServiceFactory(Map services) { + CaptchaServiceFactory captchaServiceFactory(ObjectProvider captchaServices) { + Map services = + captchaServices.stream() + .collect(Collectors.toMap(CaptchaService::getCategory, m -> m)); CaptchaServiceFactory factory = new CaptchaServiceFactory(services); if (log.isDebugEnabled()) { log.debug("Configure Bean [Captcha Service Factory: {}]", factory); diff --git a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java index 74597c6..4e47897 100644 --- a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/CaptchaServiceFactory.java @@ -1,11 +1,9 @@ package yunjiao.springboot.autoconfigure.captcha; -import yunjiao.springboot.extension.captcha.hutool.CaptchaException; +import yunjiao.springboot.extension.captcha.CaptchaException; import yunjiao.springboot.extension.common.captcha.CaptchaCategory; import yunjiao.springboot.extension.common.captcha.CaptchaService; import yunjiao.springboot.extension.common.lang.EnumCache; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import java.util.Map; @@ -14,21 +12,18 @@ import java.util.Map; * * @author yangyunjiao */ -@Getter -@RequiredArgsConstructor -public class CaptchaServiceFactory { - private final Map services; +public record CaptchaServiceFactory(Map services) { /** * 根据分类代码,查找验证码服务 * - * @param categoryCode 必须值 + * @param categoryName 分类名称,必须值 * @return 实例 */ - public CaptchaService findService(String categoryCode) { - CaptchaCategory category = EnumCache.findByValue(CaptchaCategory.class, categoryCode); + public CaptchaService findService(String categoryName) { + CaptchaCategory category = EnumCache.findByName(CaptchaCategory.class, categoryName); if (category == null) { - throw new CaptchaException("验证码分类代码不存在,代码是:" + categoryCode); + throw new CaptchaException("验证码分类代码不存在,名称是:" + categoryName); } return findService(category); } @@ -40,7 +35,7 @@ public class CaptchaServiceFactory { * @return 实例 */ public CaptchaService findService(CaptchaCategory category) { - CaptchaService service = services.get(category.getCode()); + CaptchaService service = services.get(category); if (service == null) { throw new CaptchaException("根据分类查找验证码服务未找到, 分类是:" + category); } diff --git a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java index 73bdf58..ec5835e 100644 --- a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfiguration.java @@ -2,7 +2,6 @@ package yunjiao.springboot.autoconfigure.captcha; import cn.hutool.captcha.ICaptcha; import cn.hutool.core.lang.Assert; -import yunjiao.springboot.extension.common.captcha.CaptchaCategory; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -10,6 +9,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import yunjiao.springboot.extension.captcha.hutool.*; +import yunjiao.springboot.extension.common.captcha.CaptchaService; import java.awt.*; @@ -31,8 +31,8 @@ public class HutoolCaptchaConfiguration { log.info("Hutool Captcha Configuration"); } - @Bean(CaptchaCategory.HUTOOL_LINE_CAPTCHA) - LineCaptchaService lineCaptchaService(HutoolCaptchaProperties properties) { + @Bean + CaptchaService lineCaptchaService(HutoolCaptchaProperties properties) { HutoolCaptchaProperties.DrawingOptions options = properties.getLine(); validate(options); Font font = createFont(options.getFont()); @@ -47,8 +47,8 @@ public class HutoolCaptchaConfiguration { return service; } - @Bean(CaptchaCategory.HUTOOL_CIRCLE_CAPTCHA) - CircleCaptchaService circleCaptchaService(HutoolCaptchaProperties properties) { + @Bean + CaptchaService circleCaptchaService(HutoolCaptchaProperties properties) { HutoolCaptchaProperties.DrawingOptions options = properties.getCircle(); validate(options); @@ -62,8 +62,8 @@ public class HutoolCaptchaConfiguration { return service; } - @Bean(CaptchaCategory.HUTOOL_SHEAR_CAPTCHA) - ShearCaptchaService sheareCaptchaService(HutoolCaptchaProperties properties) { + @Bean + CaptchaService sheareCaptchaService(HutoolCaptchaProperties properties) { HutoolCaptchaProperties.DrawingOptions options = properties.getShear(); validate(options); @@ -78,8 +78,8 @@ public class HutoolCaptchaConfiguration { } - @Bean(CaptchaCategory.HUTOOL_GIF_CAPTCHA) - GifCaptchaService gifCaptchaService(HutoolCaptchaProperties properties) { + @Bean + CaptchaService gifCaptchaService(HutoolCaptchaProperties properties) { HutoolCaptchaProperties.GifDrawingOptions options = properties.getGif(); validate(options); Assert.isTrue(options.getQuality() >= 1 && options.getQuality() <= 20, "验证码配置属性‘quality‘值必须在[1, 20]之间"); diff --git a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java index 21c758f..467b0b7 100644 --- a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/id/HutoolIdConfiguration.java @@ -38,10 +38,6 @@ public class HutoolIdConfiguration { final String SNOWFLAKE_WORKER_ID = "SNOWFLAKE_WORKER_ID"; final String SNOWFLAKE_DATACENTER_ID = "SNOWFLAKE_DATACENTER_ID"; - if (log.isDebugEnabled()) { - log.debug("正在配置雪花算法,默认workerId=1,datacenterId=1。如需支持分布式,请设置系统环境变量:{} 与 {}", SNOWFLAKE_WORKER_ID, SNOWFLAKE_DATACENTER_ID); - } - long workerId = 1L; try { String workIdEnv = env.getProperty(SNOWFLAKE_WORKER_ID); @@ -62,6 +58,10 @@ public class HutoolIdConfiguration { if (log.isDebugEnabled()) { log.debug("Configure Bean [Snowflake: {}], workerId={}, datacenterId={}", snowflake, workerId, datacenterId); } + + if (workerId == 1L && datacenterId == 1L) { + log.info("雪花算法配置参数workerId=1,datacenterId=1。如需支持分布式,请设置系统环境变量:{} 与 {}", SNOWFLAKE_WORKER_ID, SNOWFLAKE_DATACENTER_ID); + } return snowflake; } } diff --git a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java index 9db5cea..bbe6000 100644 --- a/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java +++ b/autoconfigure/src/main/java/yunjiao/springboot/autoconfigure/util/PropertyNameConsts.java @@ -26,6 +26,11 @@ public class PropertyNameConsts { */ public static final String PROPERTY_PREFIX_CAPTCHA_HUTOOL = PROPERTY_PREFIX_CAPTCHA + ".hutool"; + /** + * 验证码属性 Hutool + */ + public static final String PROPERTY_PREFIX_CAPTCHA_ANJI = PROPERTY_PREFIX_CAPTCHA + ".anji"; + // APIJSON /** diff --git a/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaConfigurationTest.java new file mode 100644 index 0000000..6445404 --- /dev/null +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/AnjiCaptchaConfigurationTest.java @@ -0,0 +1,40 @@ +package yunjiao.springboot.autoconfigure.captcha; + +import com.anji.captcha.service.CaptchaCacheService; +import com.anji.captcha.service.CaptchaService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import yunjiao.springboot.extension.captcha.anji.BlockPuzzleCaptchaService; +import yunjiao.springboot.extension.captcha.anji.ClickWorkCaptchaService; +import yunjiao.springboot.extension.captcha.anji.RotatePluzzleCaptchaService; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link AnjiCaptchaConfiguration} 单元测试用例 + * + * @author yangyunjiao + */ +public class AnjiCaptchaConfigurationTest { + private ApplicationContextRunner applicationContextRunner; + + @BeforeEach + public void setUp() { + applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(AnjiCaptchaConfiguration.class)); + } + + @Test + public void givenDefault_thenConfig() { + applicationContextRunner + .run(context -> { + assertThat(context).hasSingleBean(CaptchaCacheService.class); + assertThat(context).hasSingleBean(CaptchaService.class); + assertThat(context).hasSingleBean(BlockPuzzleCaptchaService.class); + assertThat(context).hasSingleBean(ClickWorkCaptchaService.class); + assertThat(context).hasSingleBean(RotatePluzzleCaptchaService.class); + }); + } +} diff --git a/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java index 22d23dd..9d4a3b2 100644 --- a/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/CaptchaAutoConfigurationTest.java @@ -28,7 +28,7 @@ public class CaptchaAutoConfigurationTest { assertThat(context).hasSingleBean(CaptchaServiceFactory.class); CaptchaServiceFactory factory = context.getBean(CaptchaServiceFactory.class); - assertThat(factory.getServices()).hasSize(4); + assertThat(factory.services()).hasSize(7); }); } } diff --git a/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfigurationTest.java similarity index 96% rename from autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java rename to autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfigurationTest.java index 60c1ceb..580dd9b 100644 --- a/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/IdCaptchaConfigurationTest.java +++ b/autoconfigure/src/test/java/yunjiao/springboot/autoconfigure/captcha/HutoolCaptchaConfigurationTest.java @@ -16,7 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author yangyunjiao */ -public class IdCaptchaConfigurationTest { +public class HutoolCaptchaConfigurationTest { private ApplicationContextRunner applicationContextRunner; @BeforeEach @@ -33,7 +33,6 @@ public class IdCaptchaConfigurationTest { assertThat(context).hasSingleBean(CircleCaptchaService.class); assertThat(context).hasSingleBean(ShearCaptchaService.class); assertThat(context).hasSingleBean(GifCaptchaService.class); - }); } } diff --git a/bin/mvn-all-clean.cmd b/bin/mvn-all-clean.cmd index f9a5d20..6ccf615 100644 --- a/bin/mvn-all-clean.cmd +++ b/bin/mvn-all-clean.cmd @@ -2,22 +2,16 @@ cd .. call mvn clean -echo ################################ -echo # spring-boot-starter-parent # -echo ################################ -cd spring-boot-starter-parent +echo [starter-parent] +cd starter-parent call mvn clean -echo ################################ -echo # examples # -echo ################################ +echo [examples] cd .. cd examples call mvn clean -echo ################################ -echo # projects # -echo ################################ +echo [projects] cd .. cd projects call mvn clean diff --git a/bin/mvn-all-install.cmd b/bin/mvn-all-install.cmd index 42c958c..345f98e 100644 --- a/bin/mvn-all-install.cmd +++ b/bin/mvn-all-install.cmd @@ -2,22 +2,16 @@ cd .. call mvn clean install -echo ################################ -echo # spring-boot-starter-parent # -echo ################################ -cd spring-boot-starter-parent +echo [starter-parent] +cd starter-parent call mvn clean install -echo ################################ -echo # examples # -echo ################################ +echo [examples] cd .. cd examples call mvn clean install -echo ################################ -echo # projects # -echo ################################ +echo [projects] cd .. cd projects call mvn clean install diff --git a/bin/mvn-deploy.cmd b/bin/mvn-deploy.cmd index 74a4adc..3a7d728 100644 --- a/bin/mvn-deploy.cmd +++ b/bin/mvn-deploy.cmd @@ -2,10 +2,8 @@ cd .. call mvn clean deploy -Prelease -echo ################################ -echo # spring-boot-starter-parent # -echo ################################ -cd spring-boot-starter-parent +echo [starter-parent] +cd starter-parent call mvn clean deploy -Prelease rem 切换回运行目录 diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 2e7ff19..4a9c596 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -19,13 +19,14 @@ 依赖项目 - 1.2.25 5.1.0 33.4.8-jre + 2.12.1 5.8.38 7.2.2 1.0.0 1.0.2 + 1.4.0 17 ${java.version} @@ -33,12 +34,12 @@ UTF-8 UTF-8 - 3.0.2 - 3.8.1 - 3.0.1 - 3.1.2 - 0.8.2 - 2.5.3 + 3.2.2 + 3.12.1 + 3.3.1 + 3.2.5 + 0.8.12 + 3.1.1 3.6.0 @@ -122,19 +123,22 @@ import - - - com.alibaba - druid-spring-boot-starter - ${druid-spring-boot-starter.version} - - com.google.guava guava ${guava.version} + + com.google.code.gson + gson + ${gson.version} + + + com.anji-plus + captcha + ${ajcaptcha.version} + @@ -343,6 +347,9 @@ spring-boot-3.0.13 + + true + 3.0.13 @@ -378,9 +385,6 @@ spring-boot-3.5.4 - - true - 3.5.4 diff --git a/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaCommandLineRunner.java b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaCommandLineRunner.java index d7fd782..ccb1e2f 100644 --- a/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaCommandLineRunner.java +++ b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaCommandLineRunner.java @@ -27,21 +27,29 @@ public class CaptchaCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { - CaptchaService circleService = factory.findService(CaptchaCategory.hutoolCircle); + CaptchaService circleService = factory.findService(CaptchaCategory.circle); CaptchaData data = circleService.draw(); saveImage(data); - CaptchaService lineService = factory.findService(CaptchaCategory.hutoolLine); + CaptchaService lineService = factory.findService(CaptchaCategory.line); data = lineService.draw(); saveImage(data); - CaptchaService gifService = factory.findService(CaptchaCategory.hutoolGif); + CaptchaService gifService = factory.findService(CaptchaCategory.gif); data = gifService.draw(); saveImage(data); - CaptchaService shearService = factory.findService(CaptchaCategory.hutoolShear); + CaptchaService shearService = factory.findService(CaptchaCategory.shear); data = shearService.draw(); saveImage(data); + + CaptchaService blockPuzzleService = factory.findService(CaptchaCategory.blockPuzzle); + data = blockPuzzleService.draw(); + saveImage(data); + + CaptchaService clickWordService = factory.findService(CaptchaCategory.clickWord); + data = clickWordService.draw(); + saveImage(data); } private void saveImage(CaptchaData data) throws IOException { @@ -50,7 +58,12 @@ public class CaptchaCommandLineRunner implements CommandLineRunner { Files.createDirectories(targetDir); } - Path filePath = targetDir.resolve(data.getCategory().getCode() + "." + data.getCategory().getExt()); - Files.write(filePath, data.getCaptchaImage()); + Path filePath = targetDir.resolve(data.category().name() + "-captcha." + data.category().getExt()); + Files.write(filePath, data.captchaImage()); + + if (data.backgroundImage() != null) { + filePath = targetDir.resolve(data.category().name() + "-background." + data.category().getExt()); + Files.write(filePath, data.backgroundImage()); + } } } diff --git a/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaController.java b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaController.java index baf631a..ee506fa 100644 --- a/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaController.java +++ b/examples/example-captcha/src/main/java/yunjiao/springboot/example/captcha/CaptchaController.java @@ -25,12 +25,13 @@ public class CaptchaController { CaptchaData data = service.draw(); CaptchaReponse reponse = new CaptchaReponse(); - reponse.setKey(data.getKey()); + reponse.setKey(data.key()); reponse.setCategory(service.getCategory()); - reponse.setCaptchaImageBase64(data.getCaptchaImageBase64()); + reponse.setCaptchaImageBase64(data.captchaImageBase64Url()); + reponse.setBackgroundImageBase64(data.backgroundImageBase64Url()); - timedCache.put(data.getKey(), data.getCode()); - System.out.println("code=" + data.getCode()); + timedCache.put(data.key(), data.code()); + System.out.println("code=" + data.code()); return reponse; } diff --git a/examples/pom.xml b/examples/pom.xml index bd01f61..3f4aed3 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ io.gitee.yunjiao-source.spring-boot starter-parent - 3.5.x.3 + 0.3.0 4.0.0 @@ -26,7 +26,7 @@ - 3.5.x.3 + 0.3.0 1.5.0 diff --git a/extensions/README.md b/extensions/README.md index 29c53e3..670bc0f 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -242,7 +242,7 @@ enum StatusEnum { 验证码服务接口,主要功能包括验证码生成,校验等 * CaptchaData draw() : 验证码生成 -* boolean verify(Object orignalCode, Object userCode):验证码校验 +* boolean verify(String originalCode, String userCode):验证码校验 * CaptchaCategory getCategory():分类 diff --git a/extensions/extension-captcha/pom.xml b/extensions/extension-captcha/pom.xml index 4979e11..66916ba 100644 --- a/extensions/extension-captcha/pom.xml +++ b/extensions/extension-captcha/pom.xml @@ -12,7 +12,7 @@ extension-captcha jar Extension :: Captcha - 验证码扩展 + 验证码扩展,集成Hutool,aj-captcha @@ -23,5 +23,10 @@ cn.hutool hutool-captcha + + com.anji-plus + captcha + + \ No newline at end of file diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CaptchaException.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/CaptchaException.java similarity index 87% rename from extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CaptchaException.java rename to extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/CaptchaException.java index efce16d..6569cfc 100644 --- a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CaptchaException.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/CaptchaException.java @@ -1,4 +1,4 @@ -package yunjiao.springboot.extension.captcha.hutool; +package yunjiao.springboot.extension.captcha; import yunjiao.springboot.extension.common.util.CommonRuntimeException; diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/BaseCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/BaseCaptchaService.java new file mode 100644 index 0000000..a11672e --- /dev/null +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/BaseCaptchaService.java @@ -0,0 +1,61 @@ +package yunjiao.springboot.extension.captcha.anji; + +import com.anji.captcha.model.common.CaptchaTypeEnum; +import com.anji.captcha.model.common.ResponseModel; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaCacheService; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.util.StringUtils; +import yunjiao.springboot.extension.captcha.CaptchaException; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.CaptchaService; + +/** + * 抽象的验证码 + * + * @author yangyunjiao + */ +@Getter +@RequiredArgsConstructor +public abstract class BaseCaptchaService implements CaptchaService { + protected static final String CACHE_TYPE = "local"; + + protected static final String REDIS_CAPTCHA_KEY = "RUNNING:CAPTCHA:%s"; + + private final com.anji.captcha.service.CaptchaService captchaService; + + private final CaptchaCacheService captchaCacheService; + + protected abstract CaptchaTypeEnum getCaptchaType(); + + protected abstract CaptchaData convert(CaptchaData data, CaptchaVO vo); + + protected boolean between(int target, int start, int end) { + return start <= target && target <= end; + } + + @Override + public CaptchaData draw() { + // 准备参数 + CaptchaVO vo = new CaptchaVO(); + vo.setCaptchaType(getCaptchaType().getCodeValue()); + + // 生成验证码 + ResponseModel model = captchaService.get(vo); + if (!model.isSuccess()) { + throw new CaptchaException(model.getRepMsg()); + } + + CaptchaVO captcha = (CaptchaVO)model.getRepData(); + String token = captcha.getToken(); + String codeKey = String.format(REDIS_CAPTCHA_KEY, token); + + String code = captchaCacheService.get(codeKey); + if (StringUtils.hasText(code)) { + captchaCacheService.delete(codeKey); + } + + return convert(new CaptchaData().key(token).code(code).category(getCategory()), captcha); + } +} diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/BlockPuzzleCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/BlockPuzzleCaptchaService.java new file mode 100644 index 0000000..04545c5 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/BlockPuzzleCaptchaService.java @@ -0,0 +1,55 @@ +package yunjiao.springboot.extension.captcha.anji; + +import com.anji.captcha.model.common.CaptchaTypeEnum; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaCacheService; +import com.anji.captcha.service.CaptchaService; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.Point; +import yunjiao.springboot.extension.common.util.GsonUtils; + +import java.util.Base64; + +/** + * 滑动验证码 服务 + * + * @author yangyunjiao + */ +public class BlockPuzzleCaptchaService extends BaseCaptchaService { + private final Integer slipOffset; + + public BlockPuzzleCaptchaService(CaptchaService captchaService, CaptchaCacheService captchaCacheService, Integer slipOffset) { + super(captchaService, captchaCacheService); + this.slipOffset = slipOffset; + } + + + @Override + public boolean verify(String originalCode, String userCode) { + Point original = GsonUtils.fromJson(originalCode, Point.class); + Point user = GsonUtils.fromJson(userCode, Point.class); + + return between(user.x(), original.x() - slipOffset, original.x() + slipOffset) + && between(user.y(), original.y() - slipOffset, original.y() + slipOffset); + } + + @Override + public CaptchaCategory getCategory() { + return CaptchaCategory.blockPuzzle; + } + + @Override + protected CaptchaTypeEnum getCaptchaType() { + return CaptchaTypeEnum.BLOCKPUZZLE; + } + + @Override + protected CaptchaData convert(CaptchaData data, CaptchaVO vo) { + String backgroundImageBase64 = vo.getOriginalImageBase64(); + String captchaImageBase64 = vo.getJigsawImageBase64(); + + return data.backgroundImage(Base64.getDecoder().decode(backgroundImageBase64)) + .captchaImage(Base64.getDecoder().decode(captchaImageBase64)); + } +} diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/ClickWorkCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/ClickWorkCaptchaService.java new file mode 100644 index 0000000..da8b960 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/ClickWorkCaptchaService.java @@ -0,0 +1,65 @@ +package yunjiao.springboot.extension.captcha.anji; + +import com.anji.captcha.model.common.CaptchaTypeEnum; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaCacheService; +import com.anji.captcha.service.CaptchaService; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.Point; +import yunjiao.springboot.extension.common.util.GsonUtils; + +import java.util.Base64; +import java.util.List; + +/** + * 滑块拼图验证码 服务 + * + * @author yangyunjiao + */ +public class ClickWorkCaptchaService extends BaseCaptchaService { + private final Integer slipOffset; + + public ClickWorkCaptchaService(CaptchaService captchaService, CaptchaCacheService captchaCacheService, Integer slipOffset) { + super(captchaService, captchaCacheService); + this.slipOffset = slipOffset; + } + + + @Override + public boolean verify(String originalCode, String userCode) { + List originalList = GsonUtils.toList(originalCode, Point.class); + List userList = GsonUtils.toList(userCode, Point.class); + if (originalList.size() != userList.size()) { + return false; + } + + for (int i = 0; i < originalList.size(); i++) { + Point original = originalList.get(i); + Point user = userList.get(i); + boolean passed = between(user.x(), original.x() - slipOffset, original.x() + slipOffset) + && between(user.y(), original.y() - slipOffset, original.y() + slipOffset); + if (!passed) { + return false; + } + } + return true; + } + + @Override + public CaptchaCategory getCategory() { + return CaptchaCategory.clickWord; + } + + @Override + protected CaptchaTypeEnum getCaptchaType() { + return CaptchaTypeEnum.CLICKWORD; + } + + @Override + protected CaptchaData convert(CaptchaData data, CaptchaVO vo) { + String captchaImageBase64 = vo.getOriginalImageBase64(); + + return data.captchaImage(Base64.getDecoder().decode(captchaImageBase64)); + } +} diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/RotatePluzzleCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/RotatePluzzleCaptchaService.java new file mode 100644 index 0000000..54686c0 --- /dev/null +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/anji/RotatePluzzleCaptchaService.java @@ -0,0 +1,44 @@ +package yunjiao.springboot.extension.captcha.anji; + +import com.anji.captcha.model.common.CaptchaTypeEnum; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaCacheService; +import com.anji.captcha.service.CaptchaService; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; + +import java.util.Base64; + +/** + * 旋转拼图验证码 服务 + * + * @author yangyunjiao + */ +public class RotatePluzzleCaptchaService extends BaseCaptchaService { + + public RotatePluzzleCaptchaService(CaptchaService captchaService, CaptchaCacheService captchaCacheService) { + super(captchaService, captchaCacheService); + } + + @Override + public boolean verify(String originalCode, String userCode) { + return false; + } + + @Override + public CaptchaCategory getCategory() { + return CaptchaCategory.rotatePuzzle; + } + + @Override + protected CaptchaTypeEnum getCaptchaType() { + return CaptchaTypeEnum.ROTATEPUZZLE; + } + + @Override + protected CaptchaData convert(CaptchaData data, CaptchaVO vo) { + String captchaImageBase64 = vo.getOriginalImageBase64(); + + return data.captchaImage(Base64.getDecoder().decode(captchaImageBase64)); + } +} diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaService.java index bc701a1..5fe41d3 100644 --- a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/AbstractCaptchaService.java @@ -2,10 +2,12 @@ package yunjiao.springboot.extension.captcha.hutool; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.util.IdUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import yunjiao.springboot.extension.captcha.CaptchaException; import yunjiao.springboot.extension.common.algorithm.GaussianBlur; import yunjiao.springboot.extension.common.captcha.CaptchaData; import yunjiao.springboot.extension.common.captcha.CaptchaService; -import lombok.extern.slf4j.Slf4j; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; @@ -27,11 +29,12 @@ public abstract class AbstractCaptchaService implements CaptchaService { protected abstract Integer getFuzziness(); - protected void handleFuzziness(BufferedImage image) { + protected BufferedImage handleFuzziness(BufferedImage image) { Integer fuzziness = getFuzziness(); if (fuzziness != null && fuzziness > 0) { - image = GaussianBlur.execute(image, fuzziness); + return GaussianBlur.execute(image, fuzziness); } + return image; } protected CaptchaData createCaptchaData(String code, BufferedImage image) { @@ -39,39 +42,27 @@ public abstract class AbstractCaptchaService implements CaptchaService { ImgUtil.writePng(image, out); byte[] imageBytes = out.toByteArray(); - return CaptchaData.builder() + return new CaptchaData() .key(IdUtil.fastSimpleUUID()) .code(code) .category(getCategory()) - .captchaImage(imageBytes) - .build(); + .captchaImage(imageBytes); } catch (IOException e) { throw new CaptchaException("生成验证码图片异常", e); } - - - } @Override - public boolean verify(Object orignalCode, Object userCode) { - if(orignalCode == null || userCode == null) { - return false; - } - - if (orignalCode instanceof String orginalStr - && userCode instanceof String userStr ) { - // 是否忽略大写校验 + public boolean verify(String originalCode, String userCode) { + if(StringUtils.hasText(originalCode) && StringUtils.hasText(userCode)) { Boolean ignoreCase = getValidIgnoreCase(); if (Boolean.TRUE.equals(ignoreCase)) { - return orginalStr.equalsIgnoreCase(userStr); + return originalCode.equalsIgnoreCase(userCode); } else { - return orginalStr.equals(userStr); + return originalCode.equals(userCode); } - } else { - log.error("验证码应该是字符串类型,实际是{}和{}类型", orignalCode.getClass().getSimpleName(), - userCode.getClass().getSimpleName()); } + return false; } } diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaService.java index 1449571..9201e1b 100644 --- a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaService.java @@ -27,13 +27,13 @@ public class CircleCaptchaService extends AbstractCaptchaService { // 生成图片 BufferedImage image = (BufferedImage)captcha.createImage(code); - handleFuzziness(image); + image = handleFuzziness(image); return createCaptchaData(code, image); } @Override public CaptchaCategory getCategory() { - return CaptchaCategory.hutoolCircle; + return CaptchaCategory.circle; } @Override diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaService.java index a7864d0..46400c7 100644 --- a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaService.java @@ -22,16 +22,16 @@ public class GifCaptchaService extends AbstractCaptchaService { public CaptchaData draw() { GifCaptcha captcha = builder.build(); String code = captcha.getCode(); - return CaptchaData.builder().key(IdUtil.fastSimpleUUID()) + return new CaptchaData() + .key(IdUtil.fastSimpleUUID()) .code(code) .category(getCategory()) - .captchaImage(captcha.getImageBytes()) - .build(); + .captchaImage(captcha.getImageBytes()); } @Override public CaptchaCategory getCategory() { - return CaptchaCategory.hutoolGif; + return CaptchaCategory.gif; } @Override diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaService.java index dc551a2..6349cfa 100644 --- a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaService.java @@ -27,13 +27,13 @@ public class LineCaptchaService extends AbstractCaptchaService { // 生成图片 BufferedImage image = (BufferedImage)captcha.createImage(code); - handleFuzziness(image); + image = handleFuzziness(image); return createCaptchaData(code, image); } @Override public CaptchaCategory getCategory() { - return CaptchaCategory.hutoolLine; + return CaptchaCategory.line; } @Override diff --git a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaService.java b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaService.java index b159e58..65841ce 100644 --- a/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaService.java +++ b/extensions/extension-captcha/src/main/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaService.java @@ -27,13 +27,13 @@ public class ShearCaptchaService extends AbstractCaptchaService { // 生成图片 BufferedImage image = (BufferedImage)captcha.createImage(code); - handleFuzziness(image); + image = handleFuzziness(image); return createCaptchaData(code, image); } @Override public CaptchaCategory getCategory() { - return CaptchaCategory.hutoolShear; + return CaptchaCategory.shear; } @Override diff --git a/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/CaptchaJFrameDemo.java b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/CaptchaJFrameDemo.java index a61aa8f..ed75a5e 100644 --- a/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/CaptchaJFrameDemo.java +++ b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/CaptchaJFrameDemo.java @@ -2,6 +2,12 @@ package yunjiao.springboot.extension.captcha; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.LineCaptcha; +import com.anji.captcha.model.common.Const; +import com.anji.captcha.service.CaptchaCacheService; +import com.anji.captcha.service.CaptchaService; +import com.anji.captcha.service.impl.CaptchaServiceFactory; +import yunjiao.springboot.extension.captcha.anji.BlockPuzzleCaptchaService; +import yunjiao.springboot.extension.captcha.anji.ClickWorkCaptchaService; import yunjiao.springboot.extension.common.captcha.CaptchaData; import yunjiao.springboot.extension.common.model.ColorType; import yunjiao.springboot.extension.captcha.hutool.*; @@ -10,6 +16,7 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.Properties; /** * 可视化界面 @@ -29,10 +36,14 @@ public class CaptchaJFrameDemo extends JFrame { private GifCaptchaService gifCaptchaService; + private BlockPuzzleCaptchaService blockPuzzleCaptchaService; + + private ClickWorkCaptchaService clickWorkCaptchaService; + public CaptchaJFrameDemo() { setTitle("验证码生成器"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(800, 300); + setSize(800, 800); setLocationRelativeTo(null); initUI(); @@ -93,13 +104,40 @@ public class CaptchaJFrameDemo extends JFrame { gcb.setMaxColor(255); gifCaptchaService = new GifCaptchaService(gcb); + + Properties config = new Properties(); + config.put(Const.CAPTCHA_CACHETYPE, "local"); + config.put(Const.CAPTCHA_WATER_MARK, "我的水印"); + config.put(Const.CAPTCHA_FONT_TYPE, "WenQuanZhengHei.ttf"); + config.put(Const.CAPTCHA_TYPE, "default"); + config.put(Const.CAPTCHA_INTERFERENCE_OPTIONS, "0"); + config.put(Const.ORIGINAL_PATH_JIGSAW, ""); + config.put(Const.ORIGINAL_PATH_PIC_CLICK, ""); + config.put(Const.CAPTCHA_SLIP_OFFSET, "5"); + config.put(Const.CAPTCHA_AES_STATUS, "false"); + config.put(Const.CAPTCHA_WATER_FONT, "WenQuanZhengHei.ttf"); + // CacheUtil的定时任务存在泄露的问题 + config.put(Const.CAPTCHA_TIMING_CLEAR_SECOND, "0"); + + config.put(Const.CAPTCHA_FONT_SIZE, "25"); + config.put(Const.CAPTCHA_FONT_STYLE, Font.BOLD); + config.put(Const.CAPTCHA_WORD_COUNT, "4"); + + CaptchaService captchaService = CaptchaServiceFactory.getInstance(config); + CaptchaCacheService captchaCacheService = CaptchaServiceFactory.getCache("local"); + blockPuzzleCaptchaService = new BlockPuzzleCaptchaService(captchaService, + captchaCacheService, + 5); + clickWorkCaptchaService = new ClickWorkCaptchaService(captchaService, + captchaCacheService, + 5); } private void initUI() { setLayout(new BorderLayout()); // 创建验证码显示面板 - captchaPanel = new JPanel(new GridLayout(3, 2, 10, 10)); + captchaPanel = new JPanel(new GridLayout(3, 3, 10, 10)); captchaPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); add(captchaPanel, BorderLayout.CENTER); @@ -139,16 +177,30 @@ public class CaptchaJFrameDemo extends JFrame { captchaData = gifCaptchaService.draw(); decodeImage(captchaData); + captchaData = blockPuzzleCaptchaService.draw(); + decodeImage(captchaData); + + captchaData = clickWorkCaptchaService.draw(); + decodeImage(captchaData); + captchaPanel.revalidate(); captchaPanel.repaint(); } private void decodeImage(CaptchaData captchaData) { - ImageIcon icon = new ImageIcon(captchaData.getCaptchaImage()); + ImageIcon icon = new ImageIcon(captchaData.captchaImage()); JLabel label = new JLabel(icon); label.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1)); captchaPanel.add(label); + + if (captchaData.backgroundImage() != null) { + icon = new ImageIcon(captchaData.backgroundImage()); + + label = new JLabel(icon); + label.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1)); + captchaPanel.add(label); + } } public static void main(String[] args) { diff --git a/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/anji/BlockPuzzleCaptchaServiceTest.java b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/anji/BlockPuzzleCaptchaServiceTest.java new file mode 100644 index 0000000..d9e504f --- /dev/null +++ b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/anji/BlockPuzzleCaptchaServiceTest.java @@ -0,0 +1,81 @@ +package yunjiao.springboot.extension.captcha.anji; + +import com.anji.captcha.model.common.Const; +import com.anji.captcha.service.CaptchaCacheService; +import com.anji.captcha.service.CaptchaService; +import com.anji.captcha.service.impl.CaptchaServiceFactory; +import org.junit.jupiter.api.Test; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.Point; +import yunjiao.springboot.extension.common.util.GsonUtils; + +import java.awt.*; +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link BlockPuzzleCaptchaService} 单元测试用例 + * + * @author yangyunjiao + */ +public class BlockPuzzleCaptchaServiceTest { + private static final BlockPuzzleCaptchaService blockPuzzleCaptchaService; + + static { + Properties config = new Properties(); + config.put(Const.CAPTCHA_CACHETYPE, "local"); + config.put(Const.CAPTCHA_WATER_MARK, "我的水印"); + config.put(Const.CAPTCHA_FONT_TYPE, "WenQuanZhengHei.ttf"); + config.put(Const.CAPTCHA_TYPE, "default"); + config.put(Const.CAPTCHA_INTERFERENCE_OPTIONS, "0"); + config.put(Const.ORIGINAL_PATH_JIGSAW, ""); + config.put(Const.ORIGINAL_PATH_PIC_CLICK, ""); + config.put(Const.CAPTCHA_SLIP_OFFSET, "5"); + config.put(Const.CAPTCHA_AES_STATUS, "false"); + config.put(Const.CAPTCHA_WATER_FONT, "WenQuanZhengHei.ttf"); + // CacheUtil的定时任务存在泄露的问题 + config.put(Const.CAPTCHA_TIMING_CLEAR_SECOND, "0"); + + config.put(Const.CAPTCHA_FONT_SIZE, "25"); + config.put(Const.CAPTCHA_FONT_STYLE, Font.BOLD); + config.put(Const.CAPTCHA_WORD_COUNT, "4"); + + CaptchaService captchaService = CaptchaServiceFactory.getInstance(config); + CaptchaCacheService captchaCacheService = CaptchaServiceFactory.getCache("local"); + blockPuzzleCaptchaService = new BlockPuzzleCaptchaService(captchaService, + captchaCacheService, + 6); + } + + @Test + void whenDraw_thenOk() { + CaptchaData data = blockPuzzleCaptchaService.draw(); + assertThat(data.key()).isNotBlank(); + assertThat(data.backgroundImage()).isNotNull(); + assertThat(data.captchaImage()).isNotNull(); + assertThat(data.category()).isEqualTo(CaptchaCategory.blockPuzzle); + } + + @Test + void givenPoint_whenVerify_thenOK() { + CaptchaData data = blockPuzzleCaptchaService.draw(); + assertThat(blockPuzzleCaptchaService.verify(data.code(), data.code())).isTrue(); + + // 范围内 + Point original = GsonUtils.fromJson(data.code(), Point.class); + String userCode = GsonUtils.toJson(new Point(original.x() - 5, original.y() + 5)); + assertThat(blockPuzzleCaptchaService.verify(data.code(), userCode)).isTrue(); + + // x超出范围 + userCode = GsonUtils.toJson(new Point(original.x() - 7, original.y())); + assertThat(blockPuzzleCaptchaService.verify(data.code(), userCode)).isFalse(); + + // y超出范围 + userCode = GsonUtils.toJson(new Point(original.x(), original.y() + 7)); + assertThat(blockPuzzleCaptchaService.verify(data.code(), userCode)).isFalse(); + } + + +} diff --git a/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/anji/ClickWorkCaptchaServiceTest.java b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/anji/ClickWorkCaptchaServiceTest.java new file mode 100644 index 0000000..7b713c5 --- /dev/null +++ b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/anji/ClickWorkCaptchaServiceTest.java @@ -0,0 +1,98 @@ +package yunjiao.springboot.extension.captcha.anji; + +import com.anji.captcha.model.common.Const; +import com.anji.captcha.service.CaptchaCacheService; +import com.anji.captcha.service.CaptchaService; +import com.anji.captcha.service.impl.CaptchaServiceFactory; +import org.junit.jupiter.api.Test; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.captcha.Point; +import yunjiao.springboot.extension.common.util.GsonUtils; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link ClickWorkCaptchaService} 单元测试用例 + * + * @author yangyunjiao + */ +public class ClickWorkCaptchaServiceTest { + private static final ClickWorkCaptchaService clickWorkCaptchaService; + + static { + Properties config = new Properties(); + config.put(Const.CAPTCHA_CACHETYPE, "local"); + config.put(Const.CAPTCHA_WATER_MARK, "我的水印"); + config.put(Const.CAPTCHA_FONT_TYPE, "WenQuanZhengHei.ttf"); + config.put(Const.CAPTCHA_TYPE, "default"); + config.put(Const.CAPTCHA_INTERFERENCE_OPTIONS, "0"); + config.put(Const.ORIGINAL_PATH_JIGSAW, ""); + config.put(Const.ORIGINAL_PATH_PIC_CLICK, ""); + config.put(Const.CAPTCHA_SLIP_OFFSET, "5"); + config.put(Const.CAPTCHA_AES_STATUS, "false"); + config.put(Const.CAPTCHA_WATER_FONT, "WenQuanZhengHei.ttf"); + // CacheUtil的定时任务存在泄露的问题 + config.put(Const.CAPTCHA_TIMING_CLEAR_SECOND, "0"); + + config.put(Const.CAPTCHA_FONT_SIZE, "25"); + config.put(Const.CAPTCHA_FONT_STYLE, Font.BOLD); + config.put(Const.CAPTCHA_WORD_COUNT, "4"); + + CaptchaService captchaService = CaptchaServiceFactory.getInstance(config); + CaptchaCacheService captchaCacheService = CaptchaServiceFactory.getCache("local"); + clickWorkCaptchaService = new ClickWorkCaptchaService(captchaService, + captchaCacheService, + 6); + } + + @Test + void whenDraw_thenOk() { + CaptchaData data = clickWorkCaptchaService.draw(); + assertThat(data.key()).isNotBlank(); + assertThat(data.backgroundImage()).isNull(); + assertThat(data.captchaImage()).isNotNull(); + assertThat(data.category()).isEqualTo(CaptchaCategory.clickWord); + } + + @Test + void givenPoint_whenVerify_thenOK() { + CaptchaData data = clickWorkCaptchaService.draw(); + assertThat(clickWorkCaptchaService.verify(data.code(), data.code())).isTrue(); + + // 范围内 + List originalList = GsonUtils.toList(data.code(), Point.class); + List userList = new ArrayList<>(); + for (int i = 0; i < originalList.size(); i++) { + Point original = originalList.get(i); + Point newPoint = new Point(original.x() - i, original.y() + i); + userList.add(newPoint); + } + assertThat(clickWorkCaptchaService.verify(data.code(), GsonUtils.toJson(userList))).isTrue(); + + // x超出范围 + userList.clear(); + for (int i = 0; i < originalList.size(); i++) { + Point original = originalList.get(i); + Point newPoint = new Point(original.x() - i*4, original.y()); + userList.add(newPoint); + } + assertThat(clickWorkCaptchaService.verify(data.code(), GsonUtils.toJson(userList))).isFalse(); + + // y超出范围 + userList.clear(); + for (int i = 0; i < originalList.size(); i++) { + Point original = originalList.get(i); + Point newPoint = new Point(original.x(), original.y() + i*4); + userList.add(newPoint); + } + assertThat(clickWorkCaptchaService.verify(data.code(), GsonUtils.toJson(userList))).isFalse(); + } + + +} diff --git a/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaServiceTest.java b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaServiceTest.java new file mode 100644 index 0000000..9b7384a --- /dev/null +++ b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/CircleCaptchaServiceTest.java @@ -0,0 +1,62 @@ +package yunjiao.springboot.extension.captcha.hutool; + +import org.junit.jupiter.api.Test; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.model.ColorType; + +import java.awt.*; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link CircleCaptchaService} 单元测试用例 + * + * @author yangyunjiao + */ +public class CircleCaptchaServiceTest { + private static final CircleCaptchaService circleCaptchaService; + private static final CircleCaptchaBuilder builder; + + static { + Font font = new Font(null, Font.PLAIN, 36); + builder = new CircleCaptchaBuilder(); + builder.setWidth(250); + builder.setHeight(50); + builder.setInterfereCount(30); + builder.setBackgroundColor(ColorType.white); + builder.setFuzziness(2); + builder.setValidIgnoreCase(true); + builder.setFont(font); + builder.setGenerator(CodeGeneratorType.numAndChar.apply(6)); + circleCaptchaService = new CircleCaptchaService(builder); + } + + @Test + void whenDraw_thenOk() { + CaptchaData data = circleCaptchaService.draw(); + assertThat(data.key()).isNotBlank(); + assertThat(data.backgroundImage()).isNull(); + assertThat(data.captchaImage()).isNotNull(); + assertThat(data.category()).isEqualTo(CaptchaCategory.circle); + assertThat(data.code()).hasSize(6); + } + + @Test + void givenIgnoreCaseTrue_whenVerify_thenOK() { + builder.setValidIgnoreCase(true); + CaptchaData data = circleCaptchaService.draw(); + + String uperCode = data.code().toUpperCase(); + assertThat(circleCaptchaService.verify(data.code(), uperCode)).isTrue(); + } + + @Test + void givenIgnoreCaseFalse_whenVerify_thenOK() { + builder.setValidIgnoreCase(false); + CaptchaData data = circleCaptchaService.draw(); + + String uperCode = data.code().toUpperCase(); + assertThat(circleCaptchaService.verify(data.code(), uperCode)).isFalse(); + } +} diff --git a/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaServiceTest.java b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaServiceTest.java new file mode 100644 index 0000000..80cf999 --- /dev/null +++ b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/GifCaptchaServiceTest.java @@ -0,0 +1,67 @@ +package yunjiao.springboot.extension.captcha.hutool; + +import org.junit.jupiter.api.Test; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.model.ColorType; + +import java.awt.*; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link LineCaptchaService} 单元测试用例 + * + * @author yangyunjiao + */ +public class GifCaptchaServiceTest { + private static final GifCaptchaService gifCaptchaService; + private static final GifCaptchaBuilder builder; + + static { + Font font = new Font(null, Font.PLAIN, 36); + builder = new GifCaptchaBuilder(); + builder.setWidth(250); + builder.setHeight(50); + builder.setInterfereCount(10); + builder.setBackgroundColor(ColorType.white); + builder.setFuzziness(2); + builder.setValidIgnoreCase(true); + builder.setFont(font); + builder.setGenerator(CodeGeneratorType.lowerChar.apply(6)); + builder.setQuality(10); + builder.setRepeat(0); + builder.setMinColor(0); + builder.setMaxColor(255); + + gifCaptchaService = new GifCaptchaService(builder); + } + + @Test + void whenDraw_thenOk() { + CaptchaData data = gifCaptchaService.draw(); + assertThat(data.key()).isNotBlank(); + assertThat(data.backgroundImage()).isNull(); + assertThat(data.captchaImage()).isNotNull(); + assertThat(data.category()).isEqualTo(CaptchaCategory.gif); + assertThat(data.code()).hasSize(6); + } + + @Test + void givenIgnoreCaseTrue_whenVerify_thenOK() { + builder.setValidIgnoreCase(true); + CaptchaData data = gifCaptchaService.draw(); + + String uperCode = data.code().toUpperCase(); + assertThat(gifCaptchaService.verify(data.code(), uperCode)).isTrue(); + } + + @Test + void givenIgnoreCaseFalse_whenVerify_thenOK() { + builder.setValidIgnoreCase(false); + CaptchaData data = gifCaptchaService.draw(); + + String uperCode = data.code().toUpperCase(); + assertThat(gifCaptchaService.verify(data.code(), uperCode)).isFalse(); + } +} diff --git a/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaServiceTest.java b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaServiceTest.java new file mode 100644 index 0000000..7ef0042 --- /dev/null +++ b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/LineCaptchaServiceTest.java @@ -0,0 +1,62 @@ +package yunjiao.springboot.extension.captcha.hutool; + +import org.junit.jupiter.api.Test; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.model.ColorType; + +import java.awt.*; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link LineCaptchaService} 单元测试用例 + * + * @author yangyunjiao + */ +public class LineCaptchaServiceTest { + private static final LineCaptchaService lineCaptchaService; + private static final LineCaptchaBuilder builder; + + static { + Font font = new Font(null, Font.PLAIN, 36); + builder = new LineCaptchaBuilder(); + builder.setWidth(250); + builder.setHeight(50); + builder.setInterfereCount(60); + builder.setBackgroundColor(ColorType.white); + builder.setFuzziness(2); + builder.setValidIgnoreCase(true); + builder.setFont(font); + builder.setGenerator(CodeGeneratorType.lowerChar.apply(6)); + lineCaptchaService = new LineCaptchaService(builder); + } + + @Test + void whenDraw_thenOk() { + CaptchaData data = lineCaptchaService.draw(); + assertThat(data.key()).isNotBlank(); + assertThat(data.backgroundImage()).isNull(); + assertThat(data.captchaImage()).isNotNull(); + assertThat(data.category()).isEqualTo(CaptchaCategory.line); + assertThat(data.code()).hasSize(6); + } + + @Test + void givenIgnoreCaseTrue_whenVerify_thenOK() { + builder.setValidIgnoreCase(true); + CaptchaData data = lineCaptchaService.draw(); + + String uperCode = data.code().toUpperCase(); + assertThat(lineCaptchaService.verify(data.code(), uperCode)).isTrue(); + } + + @Test + void givenIgnoreCaseFalse_whenVerify_thenOK() { + builder.setValidIgnoreCase(false); + CaptchaData data = lineCaptchaService.draw(); + + String uperCode = data.code().toUpperCase(); + assertThat(lineCaptchaService.verify(data.code(), uperCode)).isFalse(); + } +} diff --git a/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaServiceTest.java b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaServiceTest.java new file mode 100644 index 0000000..ba35f6c --- /dev/null +++ b/extensions/extension-captcha/src/test/java/yunjiao/springboot/extension/captcha/hutool/ShearCaptchaServiceTest.java @@ -0,0 +1,62 @@ +package yunjiao.springboot.extension.captcha.hutool; + +import org.junit.jupiter.api.Test; +import yunjiao.springboot.extension.common.captcha.CaptchaCategory; +import yunjiao.springboot.extension.common.captcha.CaptchaData; +import yunjiao.springboot.extension.common.model.ColorType; + +import java.awt.*; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link ShearCaptchaService} 单元测试用例 + * + * @author yangyunjiao + */ +public class ShearCaptchaServiceTest { + private static final ShearCaptchaService shearCaptchaService; + private static final ShearCaptchaBuilder builder; + + static { + Font font = new Font(null, Font.PLAIN, 36); + builder = new ShearCaptchaBuilder(); + builder.setWidth(250); + builder.setHeight(50); + builder.setInterfereCount(4); + builder.setBackgroundColor(ColorType.white); + builder.setFuzziness(2); + builder.setValidIgnoreCase(true); + builder.setFont(font); + builder.setGenerator(CodeGeneratorType.numAndChar.apply(6)); + shearCaptchaService = new ShearCaptchaService(builder); + } + + @Test + void whenDraw_thenOk() { + CaptchaData data = shearCaptchaService.draw(); + assertThat(data.key()).isNotBlank(); + assertThat(data.backgroundImage()).isNull(); + assertThat(data.captchaImage()).isNotNull(); + assertThat(data.category()).isEqualTo(CaptchaCategory.shear); + assertThat(data.code()).hasSize(6); + } + + @Test + void givenIgnoreCaseTrue_whenVerify_thenOK() { + builder.setValidIgnoreCase(true); + CaptchaData data = shearCaptchaService.draw(); + + String uperCode = data.code().toUpperCase(); + assertThat(shearCaptchaService.verify(data.code(), uperCode)).isTrue(); + } + + @Test + void givenIgnoreCaseFalse_whenVerify_thenOK() { + builder.setValidIgnoreCase(false); + CaptchaData data = shearCaptchaService.draw(); + + String uperCode = data.code().toUpperCase(); + assertThat(shearCaptchaService.verify(data.code(), uperCode)).isFalse(); + } +} diff --git a/extensions/extension-common/pom.xml b/extensions/extension-common/pom.xml index 7ee9a7b..80b9692 100644 --- a/extensions/extension-common/pom.xml +++ b/extensions/extension-common/pom.xml @@ -14,5 +14,22 @@ Extension :: Common 通用工具 - + + + com.google.guava + guava + + + org.springframework + spring-core + + + cn.hutool + hutool-core + + + com.google.code.gson + gson + + \ No newline at end of file diff --git a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaCategory.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaCategory.java index cd48d0b..fd31015 100644 --- a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaCategory.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaCategory.java @@ -12,20 +12,13 @@ import lombok.ToString; @Getter @ToString public enum CaptchaCategory { - hutoolLine(CaptchaCategory.HUTOOL_LINE_CAPTCHA, "png", "Hutool线段干扰验证码"), - hutoolCircle(CaptchaCategory.HUTOOL_CIRCLE_CAPTCHA, "png", "Hutool圆圈干扰验证码"), - hutoolShear(CaptchaCategory.HUTOOL_SHEAR_CAPTCHA, "png", "Hutool扭曲干扰验证码"), - hutoolGif(CaptchaCategory.HUTOOL_GIF_CAPTCHA, "gif", "Hutool GIF验证码"); - - public static final String HUTOOL_LINE_CAPTCHA = "HUTOOL_LINE"; - public static final String HUTOOL_CIRCLE_CAPTCHA = "HUTOOL_CIRCLE"; - public static final String HUTOOL_SHEAR_CAPTCHA = "HUTOOL_SHEAR"; - public static final String HUTOOL_GIF_CAPTCHA = "HUTOOL_GIF"; - - /** - * 代码 - */ - private final String code; + line("png", "线段干扰验证码"), + circle("png", "圆圈干扰验证码"), + shear("png", "扭曲干扰验证码"), + gif("gif", "GIF验证码"), + blockPuzzle("png", "滑块拼图验证码"), + clickWord("png", "文字点选验证码"), + rotatePuzzle("png", "旋转拼图验证码"); /** * 描述 @@ -37,14 +30,12 @@ public enum CaptchaCategory { */ private final String ext; - CaptchaCategory(String code, String ext, String description) { - this.code = code; + CaptchaCategory(String ext, String description) { this.ext = ext; this.description = description; } static { EnumCache.registerByName(CaptchaCategory.class, CaptchaCategory.values()); - EnumCache.registerByValue(CaptchaCategory.class, CaptchaCategory.values(), CaptchaCategory::getCode); } } diff --git a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaData.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaData.java index 1d75f22..3dcf82b 100644 --- a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaData.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaData.java @@ -1,8 +1,9 @@ package yunjiao.springboot.extension.common.captcha; -import lombok.Builder; import lombok.Getter; +import lombok.Setter; import lombok.ToString; +import lombok.experimental.Accessors; import java.io.Serializable; import java.util.Base64; @@ -13,8 +14,9 @@ import java.util.Base64; * @author yangyunjiao */ @Getter -@Builder +@Setter @ToString +@Accessors(fluent = true, chain = true) public class CaptchaData implements Serializable { /** * 验证码唯一标识,通常是uuid字符 @@ -46,13 +48,13 @@ public class CaptchaData implements Serializable { * * @return 可能空 */ - public String getCaptchaImageBase64() { + public String captchaImageBase64Url() { if (captchaImage == null) { return null; } String base64 = Base64.getEncoder().encodeToString(captchaImage); - return "data:" + category.getExt() + ";base64," + base64; + return "data:image/" + category.getExt() + ";base64," + base64; } /** @@ -60,12 +62,12 @@ public class CaptchaData implements Serializable { * * @return 可能空 */ - public String getBackgroundImageBase64() { + public String backgroundImageBase64Url() { if (backgroundImage == null) { return null; } String base64 = Base64.getEncoder().encodeToString(backgroundImage); - return "data:" + category.getExt() + ";base64," + base64; + return "data:image/" + category.getExt() + ";base64," + base64; } } diff --git a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaReponse.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaReponse.java index 22d0bf7..31700de 100644 --- a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaReponse.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaReponse.java @@ -32,10 +32,10 @@ public class CaptchaReponse { public static CaptchaReponse of(CaptchaData data) { CaptchaReponse reponse = new CaptchaReponse(); - reponse.setCategory(data.getCategory()); - reponse.setKey(data.getKey()); - reponse.setBackgroundImageBase64(data.getBackgroundImageBase64()); - reponse.setCaptchaImageBase64(data.getCaptchaImageBase64()); + reponse.setCategory(data.category()); + reponse.setKey(data.key()); + reponse.setBackgroundImageBase64(data.backgroundImageBase64Url()); + reponse.setCaptchaImageBase64(data.captchaImageBase64Url()); return reponse; } } diff --git a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaService.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaService.java index a4caa3a..056bad7 100644 --- a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaService.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaService.java @@ -16,11 +16,11 @@ public interface CaptchaService { /** * 校验验证码 * - * @param orignalCode 原始验证码 + * @param originalCode 原始验证码 * @param userCode 用户输入验证码 * @return 相同返回true,否则false */ - boolean verify(Object orignalCode, Object userCode); + boolean verify(String originalCode, String userCode); /** * 获取分类 diff --git a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaValidate.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaValidate.java index 8230c49..5749d63 100644 --- a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaValidate.java +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/CaptchaValidate.java @@ -17,7 +17,7 @@ public class CaptchaValidate { /** * 验证码 */ - private Object code; + private String code; /** diff --git a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/Point.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/Point.java new file mode 100644 index 0000000..ece8979 --- /dev/null +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/captcha/Point.java @@ -0,0 +1,9 @@ +package yunjiao.springboot.extension.common.captcha; + +/** + * 坐标 + * + * @author yangyunjiao + */ +public record Point(Integer x, Integer y) { +} diff --git a/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/util/GsonUtils.java b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/util/GsonUtils.java new file mode 100644 index 0000000..05da869 --- /dev/null +++ b/extensions/extension-common/src/main/java/yunjiao/springboot/extension/common/util/GsonUtils.java @@ -0,0 +1,122 @@ +package yunjiao.springboot.extension.common.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; + +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + * {@link Gson} 工具箱 + * + * @author yangyunjiao + */ +public final class GsonUtils { + + private static final Gson GSON = new GsonBuilder() + .setDateFormat("yyyy-MM-dd HH:mm:ss") // 设置日期格式 + .disableHtmlEscaping() // 禁止转义HTML标签 + .setPrettyPrinting() // 格式化输出 + .create(); + + private GsonUtils() { + // 工具类,防止实例化 + } + + /** + * 将对象转换为JSON字符串 + * @param obj 要转换的对象 + * @return JSON字符串 + */ + public static String toJson(Object obj) { + return GSON.toJson(obj); + } + + /** + * 将JSON字符串转换为指定类型的对象 + * @param json JSON字符串 + * @param clazz 目标类型 + * @param 泛型类型 + * @return 转换后的对象 + */ + public static T fromJson(String json, Class clazz) { + return GSON.fromJson(json, clazz); + } + + /** + * 将JSON字符串转换为指定Type的对象 + * @param json JSON字符串 + * @param type 目标Type + * @param 泛型类型 + * @return 转换后的对象 + */ + public static T fromJson(String json, Type type) { + return GSON.fromJson(json, type); + } + + /** + * 从Reader读取JSON并转换为指定类型的对象 + * @param reader Reader对象 + * @param clazz 目标类型 + * @param 泛型类型 + * @return 转换后的对象 + */ + public static T fromJson(Reader reader, Class clazz) { + return GSON.fromJson(reader, clazz); + } + + /** + * 将JSON字符串转换为List + * @param json JSON字符串 + * @param clazz List中元素的类型 + * @param 泛型类型 + * @return 转换后的List + */ + public static List toList(String json, Class clazz) { + Type type = TypeToken.getParameterized(List.class, clazz).getType(); + return GSON.fromJson(json, type); + } + + /** + * 将JSON字符串转换为Map + * @param json JSON字符串 + * @param keyClass Map中key的类型 + * @param valueClass Map中value的类型 + * @param key的泛型类型 + * @param value的泛型类型 + * @return 转换后的Map + */ + public static Map toMap(String json, Class keyClass, Class valueClass) { + Type type = TypeToken.getParameterized(Map.class, keyClass, valueClass).getType(); + return GSON.fromJson(json, type); + } + + /** + * 格式化JSON字符串 + * @param json 未格式化的JSON字符串 + * @return 格式化后的JSON字符串 + */ + public static String formatJson(String json) { + JsonElement jsonElement = JsonParser.parseString(json); + return GSON.toJson(jsonElement); + } + + /** + * 判断字符串是否为有效的JSON + * @param json 要检查的字符串 + * @return 是否为有效的JSON + */ + public static boolean isValidJson(String json) { + try { + JsonElement jsonElement = JsonParser.parseString(json); + return jsonElement != null; + } catch (Exception e) { + return false; + } + } +} diff --git a/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/util/GsonUtilsTest.java b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/util/GsonUtilsTest.java new file mode 100644 index 0000000..32ee098 --- /dev/null +++ b/extensions/extension-common/src/test/java/yunjiao/springboot/extension/common/util/GsonUtilsTest.java @@ -0,0 +1,154 @@ +package yunjiao.springboot.extension.common.util; + +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import lombok.Data; +import org.junit.jupiter.api.Test; + +import java.io.StringReader; +import java.lang.reflect.Type; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link GsonUtils} 单元测试用例 + * + * @author yangyunjiao + */ +public class GsonUtilsTest { + // 测试用的简单对象 + @Data + static class TestUser { + // getters and setters + private String name; + private int age; + private Date birthDate; + + public TestUser() {} + + public TestUser(String name, int age, Date birthDate) { + this.name = name; + this.age = age; + this.birthDate = birthDate; + } + } + + @Test + public void testToJson() { + TestUser user = new TestUser("张三", 25, new Date(91, 0, 1)); + String json = GsonUtils.toJson(user); + + assertThat(json).contains("\"name\": \"张三\""); + assertThat(json).contains("\"age\": 25"); + assertThat(json).contains("\"birthDate\": "); + } + + @Test + public void testFromJson() { + String json = "{\"name\":\"李四\",\"age\":30,\"birthDate\":\"1991-01-01 00:00:00\"}"; + TestUser user = GsonUtils.fromJson(json, TestUser.class); + + assertEquals("李四", user.getName()); + assertEquals(30, user.getAge()); + assertNotNull(user.getBirthDate()); + } + + @Test + public void testFromJsonWithReader() { + String json = "{\"name\":\"王五\",\"age\":35,\"birthDate\":\"1991-01-01 00:00:00\"}"; + StringReader reader = new StringReader(json); + TestUser user = GsonUtils.fromJson(reader, TestUser.class); + + assertEquals("王五", user.getName()); + assertEquals(35, user.getAge()); + assertNotNull(user.getBirthDate()); + } + + @Test + public void testToList() { + String json = "[{\"name\":\"张三\",\"age\":25,\"birthDate\":\"1991-01-01 00:00:00\"}," + + "{\"name\":\"李四\",\"age\":30,\"birthDate\":\"1991-01-01 00:00:00\"}]"; + + List users = GsonUtils.toList(json, TestUser.class); + + assertEquals(2, users.size()); + assertEquals("张三", users.get(0).getName()); + assertEquals("李四", users.get(1).getName()); + } + + @Test + public void testToMap() { + String json = "{\"user1\":{\"name\":\"张三\",\"age\":25,\"birthDate\":\"1991-01-01 00:00:00\"}," + + "\"user2\":{\"name\":\"李四\",\"age\":30,\"birthDate\":\"1991-01-01 00:00:00\"}}"; + + Map userMap = GsonUtils.toMap(json, String.class, TestUser.class); + + assertEquals(2, userMap.size()); + assertTrue(userMap.containsKey("user1")); + assertTrue(userMap.containsKey("user2")); + assertEquals("张三", userMap.get("user1").getName()); + assertEquals("李四", userMap.get("user2").getName()); + } + + @Test + public void testFormatJson() { + String unformattedJson = "{\"name\":\"张三\",\"age\":25,\"birthDate\":\"1991-01-01 00:00:00\"}"; + String formattedJson = GsonUtils.formatJson(unformattedJson); + + assertTrue(formattedJson.contains("\n")); + assertTrue(formattedJson.contains(" ")); // 检查是否有缩进 + } + + @Test + public void testIsValidJson() { + // 测试有效JSON + assertTrue(GsonUtils.isValidJson("{\"name\":\"张三\"}")); + assertTrue(GsonUtils.isValidJson("[1, 2, 3]")); + + // 测试无效JSON + assertFalse(GsonUtils.isValidJson("{\"name\":\"张三\"")); + assertFalse(GsonUtils.isValidJson("invalid json")); + } + + @Test + public void testFromJsonWithType() { + String json = "[{\"name\":\"张三\",\"age\":25,\"birthDate\":\"1991-01-01 00:00:00\"}," + + "{\"name\":\"李四\",\"age\":30,\"birthDate\":\"1991-01-01 00:00:00\"}]"; + + Type listType = TypeToken.getParameterized(List.class, TestUser.class).getType(); + List users = GsonUtils.fromJson(json, listType); + + assertEquals(2, users.size()); + assertEquals("张三", users.get(0).getName()); + assertEquals("李四", users.get(1).getName()); + } + + @Test + public void testNullHandling() { + // 测试null对象转JSON + String json = GsonUtils.toJson(null); + assertEquals("null", json); + + // 测试null字符串转对象 + TestUser user = GsonUtils.fromJson((String) null, TestUser.class); + assertNull(user); + + // 测试空字符串转对象 + user = GsonUtils.fromJson("", TestUser.class); + assertNull(user); + } + + @Test + public void testInvalidJsonConversion() { + String invalidJson = "{\"name\":\"张三\",\"age\":}"; // 无效的JSON + + assertThatThrownBy(() -> GsonUtils.fromJson(invalidJson, TestUser.class)) + .isInstanceOf(JsonSyntaxException.class) + .hasMessageContaining("Expected value at line"); + } +} diff --git a/pom.xml b/pom.xml index c0963ea..804cc8c 100644 --- a/pom.xml +++ b/pom.xml @@ -14,8 +14,8 @@ https://gitee.com/yunjiao-source/yunjiao-spring-boot - 3.5.x.3 - 3.5.4 + 0.3.0 + 3.0.13 3.6.0 1.6 diff --git a/projects/pom.xml b/projects/pom.xml index 45aa79d..cd714e9 100644 --- a/projects/pom.xml +++ b/projects/pom.xml @@ -5,7 +5,7 @@ io.gitee.yunjiao-source.spring-boot starter-parent - 3.5.x.3 + 0.3.0 4.0.0 @@ -21,7 +21,7 @@ - 3.5.x.3 + 0.3.0 1.5.0 2.1.0 diff --git a/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecification.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecification.java index f06c94c..bdb20e1 100644 --- a/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecification.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecification.java @@ -1,5 +1,6 @@ package yunjiao.springboot.project.rql.persistence.dao; +import lombok.Getter; import yunjiao.springboot.project.rql.persistence.model.User; import yunjiao.springboot.project.rql.web.util.SpecSearchCriteria; import jakarta.persistence.criteria.CriteriaBuilder; @@ -8,25 +9,15 @@ import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import org.springframework.data.jpa.domain.Specification; -public class UserSpecification implements Specification { - - private SpecSearchCriteria criteria; - - public UserSpecification(final SpecSearchCriteria criteria) { - super(); - this.criteria = criteria; - } - - public SpecSearchCriteria getCriteria() { - return criteria; - } +@Getter +public record UserSpecification(SpecSearchCriteria criteria) implements Specification { public static Specification empty() { - return Specification.unrestricted(); + return (root, query, builder) -> null; } - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, final CriteriaBuilder builder) { + @Override + public Predicate toPredicate(final Root root, final CriteriaQuery query, final CriteriaBuilder builder) { return switch (criteria.getOperation()) { case EQUALITY -> builder.equal(root.get(criteria.getKey()), criteria.getValue()); case NEGATION -> builder.notEqual(root.get(criteria.getKey()), criteria.getValue()); @@ -38,6 +29,6 @@ public class UserSpecification implements Specification { case CONTAINS -> builder.like(root.get(criteria.getKey()), "%" + criteria.getValue() + "%"); default -> null; }; - } + } } diff --git a/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecificationsBuilder.java b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecificationsBuilder.java index ecdbb13..f32672b 100644 --- a/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecificationsBuilder.java +++ b/projects/rest-query-language/src/main/java/yunjiao/springboot/project/rql/persistence/dao/UserSpecificationsBuilder.java @@ -60,7 +60,7 @@ public final class UserSpecificationsBuilder { } public final UserSpecificationsBuilder with(UserSpecification spec) { - params.add(spec.getCriteria()); + params.add(spec.criteria()); return this; } diff --git a/starter-parent/pom.xml b/starter-parent/pom.xml index fb2112d..0aab6f0 100644 --- a/starter-parent/pom.xml +++ b/starter-parent/pom.xml @@ -5,14 +5,14 @@ org.springframework.boot spring-boot-starter-parent - 3.5.4 + 3.0.13 4.0.0 io.gitee.yunjiao-source.spring-boot starter-parent - 3.5.x.3 + 0.3.0 pom Spring Boot :: Starter Parent 启动器项目,方便用户开发 -- Gitee From ce3629fb87ed914d1c1c63d9070c94f9b70a56d4 Mon Sep 17 00:00:00 2001 From: zk_wpw Date: Thu, 28 Aug 2025 10:37:58 +0800 Subject: [PATCH 15/15] =?UTF-8?q?doc:=20md=E6=96=87=E6=A1=A3=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- doc/md/STARTER-APIJSON-FASTJSON2.md | 37 +++++ doc/md/STARTER-APIJSON-GSON.md | 37 +++++ doc/md/STARTER-CAPTCHA.md | 23 +++ doc/md/STARTER-ID.md | 23 +++ doc/md/STARTER-QUERYDSL-JPA.md | 21 +++ doc/md/STARTER-QUERYDSL-SQL.md | 21 +++ .../src/main/resources/application-all.yml | 4 +- starters/README.md | 139 ------------------ 9 files changed, 170 insertions(+), 145 deletions(-) create mode 100644 doc/md/STARTER-APIJSON-FASTJSON2.md create mode 100644 doc/md/STARTER-APIJSON-GSON.md create mode 100644 doc/md/STARTER-CAPTCHA.md create mode 100644 doc/md/STARTER-ID.md create mode 100644 doc/md/STARTER-QUERYDSL-JPA.md create mode 100644 doc/md/STARTER-QUERYDSL-SQL.md delete mode 100644 starters/README.md diff --git a/README.md b/README.md index 01fe8e3..4881293 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,12 @@ mvn install ## 使用指南 -* extensions [使用指南](./extensions/README.md) -* starters [使用指南](./starters/README.md) -* examples [使用指南](./examples/README.md) -* projects [使用指南](./projects/README.md) +* starter-apijson-fastjson2 [使用指南](./doc/md/STARTER-APIJSON-FASTJSON2.md) +* starter-apijson-gson [使用指南](./doc/md/STARTER-APIJSON-GSON.md) +* starter-captcha [使用指南](./doc/md/STARTER-CAPTCHA.md) +* starter-id [使用指南](./doc/md/STARTER-ID.md) +* starter-querydsl-jpa [使用指南](./doc/md/STARTER-QUERYDSL-JPA.md) +* starter-querydsl-sql [使用指南](./doc/md/STARTER-QUERYDSL-SQL.md) ## 参考 diff --git a/doc/md/STARTER-APIJSON-FASTJSON2.md b/doc/md/STARTER-APIJSON-FASTJSON2.md new file mode 100644 index 0000000..5e64d1c --- /dev/null +++ b/doc/md/STARTER-APIJSON-FASTJSON2.md @@ -0,0 +1,37 @@ +# starter-apijson-fastjson2 + +集成`APIJSON`框架的启动器, 使用`apijson-fastjson2`插件 + +* 方便配置。在`application.yml`中配置参数,避免写死在程序中。所有的配置属性参考[application-all.yaml](../../examples/example-apijson-fastjson2/src/main/resources/application-all.yml) +* 支持数据库连接池 +* 提供多个接口,如:CRUD,登录, 登出等 + +详细使用参考示例[example-apijson-fastjson2](../../examples/example-apijson-fastjson2) + +## 使用Maven +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source.spring-boot + starter-apijson-fastjson2 + ${version} + +``` + +## 支持的接口 + + +| 接口url | 方法 | 说明 | +|-----------------------|------|--------------------------------------------------| +| common/{method} | POST | 支持GET,HEAD,GETS,HEADS,POST,PUT,DELETE,CRUD等 | +| common/{method}/{tag} | POST | 增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率 | +| ext/reload | POST | 重新加载配置 | +| ext/post/verify | POST | 生成验证码 | +| ext/gets/verify | POST | 获取验证码 | +| ext/heads/verify | POST | 校验验证码 | +| ext/login | POST | 用户登录 | +| ext/logout | POST | 退出登录,清空session | +| ext/register | POST | 注册 | +| ext/put/password | POST | 设置密码 | + + diff --git a/doc/md/STARTER-APIJSON-GSON.md b/doc/md/STARTER-APIJSON-GSON.md new file mode 100644 index 0000000..96f5e76 --- /dev/null +++ b/doc/md/STARTER-APIJSON-GSON.md @@ -0,0 +1,37 @@ +# starter-apijson-gson + +集成`APIJSON`框架的启动器, 使用`apijson-gson`插件 + +* 方便配置。在`application.yml`中配置参数,避免写死在程序中。所有的配置属性参考[application-all.yaml](../../examples/example-apijson-gson/src/main/resources/application-all.yml) +* 支持数据库连接池 +* 提供多个接口,如:CRUD,登录, 登出等 + +详细使用参考示例[example-apijson-gson](../../examples/example-apijson-gson) + +## 使用Maven +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source.spring-boot + starter-apijson-gson + ${version} + +``` + +## 支持的接口 + + +| 接口url | 方法 | 说明 | +|-----------------------|------|--------------------------------------------------| +| common/{method} | POST | 支持GET,HEAD,GETS,HEADS,POST,PUT,DELETE,CRUD等 | +| common/{method}/{tag} | POST | 增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率 | +| ext/reload | POST | 重新加载配置 | +| ext/post/verify | POST | 生成验证码 | +| ext/gets/verify | POST | 获取验证码 | +| ext/heads/verify | POST | 校验验证码 | +| ext/login | POST | 用户登录 | +| ext/logout | POST | 退出登录,清空session | +| ext/register | POST | 注册 | +| ext/put/password | POST | 设置密码 | + + diff --git a/doc/md/STARTER-CAPTCHA.md b/doc/md/STARTER-CAPTCHA.md new file mode 100644 index 0000000..1f6161b --- /dev/null +++ b/doc/md/STARTER-CAPTCHA.md @@ -0,0 +1,23 @@ +# start-captcha + +验证码生成启动器,集成`Hutool`,`aj-captcha`等框架 + +* 零配置。无需在yaml中配置参数,框架提供默认值 +* 支持多种验证码,包括:线段干扰验证码,圆圈干扰验证码,扭曲干扰验证码,GIF验证码,滑块拼图验证码,文字点选验证码,旋转拼图验证码 +* 提供丰富的配置参数,支持用户自定义。所有的配置参数参考[application-all.yml](../../examples/example-captcha/src/main/resources/application-all.yml) + +详细使用参考示例[example-captcha](../../examples/example-captcha) + +## 使用Maven + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source.spring-boot + starter-captcha + ${version} + +``` + + + diff --git a/doc/md/STARTER-ID.md b/doc/md/STARTER-ID.md new file mode 100644 index 0000000..c5acf50 --- /dev/null +++ b/doc/md/STARTER-ID.md @@ -0,0 +1,23 @@ +## start-id + +ID生成启动器,集成`Hutool`等框架 + +* Snowflake: 雪花算法。默认workerId=1,datacenterId=1。如需支持分布式,请设置系统环境变量:SNOWFLAKE_WORKER_ID 与 {SNOWFLAKE_DATACENTER_ID}。使用属性`spring.hutool.snowflak=false`可关闭配置 + +详细使用参考示例[example-id](../../examples/example-id) + +## 使用Maven + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source.spring-boot + starter-id + ${version} + +``` + + + + + diff --git a/doc/md/STARTER-QUERYDSL-JPA.md b/doc/md/STARTER-QUERYDSL-JPA.md new file mode 100644 index 0000000..ef0706c --- /dev/null +++ b/doc/md/STARTER-QUERYDSL-JPA.md @@ -0,0 +1,21 @@ +## start-querydsl-jpa + +集成`QueryDSL JPA`框架的启动器。 + +* `JPAQueryRepositorySupport`仓库类,用户继承此类,集成功能:单个查询,列表查询,查询分页,查询Tuple,统计记录数,更新,删除等。支持数据库事务 +* `QSpecification`接口,用于复杂条件查询,支持:and, or, not等。类似`Spring`框架的`Specification` + + +详细使用参考示例[example-querydsl-jpa](../../examples/example-querydsl-jpa) + +## 使用Maven + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source.spring-boot + starter-querydsl-jpa + ${version} + +``` + diff --git a/doc/md/STARTER-QUERYDSL-SQL.md b/doc/md/STARTER-QUERYDSL-SQL.md new file mode 100644 index 0000000..43d48d6 --- /dev/null +++ b/doc/md/STARTER-QUERYDSL-SQL.md @@ -0,0 +1,21 @@ +## start-querydsl-sql + +集成`QueryDSL SQL`框架的启动器。 + +* `SQLQueryRepositorySupport`仓库类,用户继承此类,集成功能:单个查询,列表查询,查询分页,查询Tuple,统计记录数,更新,删除等。支持数据库事务 +* `QSpecification`接口,用于复杂条件查询,支持:and, or, not等。类似`Spring`框架的`Specification` + + +详细使用参考示例[example-querydsl-sql](../../examples/example-querydsl-sql) + +## 使用Maven + +在`pom.xml`中添加依赖 +```xml + + io.gitee.yunjiao-source.spring-boot + starter-querydsl-sql + ${version} + +``` + diff --git a/examples/example-captcha/src/main/resources/application-all.yml b/examples/example-captcha/src/main/resources/application-all.yml index ef67a59..e2b2161 100644 --- a/examples/example-captcha/src/main/resources/application-all.yml +++ b/examples/example-captcha/src/main/resources/application-all.yml @@ -14,8 +14,8 @@ spring: #name: # 名称, 为空表示使用系统默认字体 ,默认: null style: plain # 风格, 默认:plain size: 36 # 大小, 默认:36 - code: # 码 - generator: numandchar # 码生成类型,默认:numandchar + code: # 码属性 + generator: numandchar # 码生成类型,默认:numandchar length: 5 # 字符长度, 默认:5 circle: width: 250 # 图片的宽度, 默认:250 diff --git a/starters/README.md b/starters/README.md deleted file mode 100644 index 7891c03..0000000 --- a/starters/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# Starters - -基于Spring Boot的启动器 - -## starter-apijson-fastjson2 - -集成`APIJSON`框架的启动器,使用`apijson-fastjson2`插件 - -在`pom.xml`中添加依赖 -```xml - - io.gitee.yunjiao-source.spring-boot - starter-apijson-fastjson2 - ${version} - -``` -所有的配置属性参考[application-all.yaml](../examples/example-apijson-fastjson2/src/main/resources/application-all.yml) - -支持接口 - -| 接口url | 方法 | 说明 | -|-----------------------|------|--------------------------------------------------| -| common/{method} | POST | 支持GET,HEAD,GETS,HEADS,POST,PUT,DELETE,CRUD等 | -| common/{method}/{tag} | POST | 增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率 | -| ext/reload | POST | 重新加载配置 | -| ext/post/verify | POST | 生成验证码 | -| ext/gets/verify | POST | 获取验证码 | -| ext/heads/verify | POST | 校验验证码 | -| ext/login | POST | 用户登录 | -| ext/logout | POST | 退出登录,清空session | -| ext/register | POST | 注册 | -| ext/put/password | POST | 设置密码 | - -详细使用参考示例[example-apijson-fastjson2](../examples/example-apijson-fastjson2) - -## starter-apijson-gson - -集成`APIJSON`框架的启动器, 使用`apijson-gson`插件 - -在`pom.xml`中添加依赖 -```xml - - io.gitee.yunjiao-source.spring-boot - starter-apijson-gson - ${version} - -``` - -所有的配置属性参考[application-all.yaml](../examples/example-apijson-gson/src/main/resources/application-all.yml) - -支持的接口 - -| 接口url | 方法 | 说明 | -|-----------------------|------|--------------------------------------------------| -| common/{method} | POST | 支持GET,HEAD,GETS,HEADS,POST,PUT,DELETE,CRUD等 | -| common/{method}/{tag} | POST | 增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率 | -| ext/reload | POST | 重新加载配置 | -| ext/post/verify | POST | 生成验证码 | -| ext/gets/verify | POST | 获取验证码 | -| ext/heads/verify | POST | 校验验证码 | -| ext/login | POST | 用户登录 | -| ext/logout | POST | 退出登录,清空session | -| ext/register | POST | 注册 | -| ext/put/password | POST | 设置密码 | - -详细使用参考示例[example-apijson-gson](../examples/example-apijson-gson) - -## start-id - -在`pom.xml`中添加依赖 -```xml - - io.gitee.yunjiao-source.spring-boot - starter-hutool - ${version} - -``` - -已配置的Bean列表 - -* Snowflake: 雪花算法。默认workerId=1,datacenterId=1。如需支持分布式,请设置系统环境变量:SNOWFLAKE_WORKER_ID 与 {SNOWFLAKE_DATACENTER_ID}。使用属性`spring.hutool.snowflak=false`可关闭配置 - -详细使用参考示例[example-hutool](../examples/example-hutool) - -## start-querydsl-jpa - -集成`QueryDSL JPA`框架的启动器。 - -在`pom.xml`中添加依赖 -```xml - - io.gitee.yunjiao-source.spring-boot - starter-querydsl-jpa - ${version} - -``` - -已配置的Bean列表 -* JPAQueryFactory: 查询工厂 - -详细使用参考示例[example-querydsl-jpa](../examples/example-querydsl-jpa) - -## start-querydsl-sql - -集成`QueryDSL SQL`框架的启动器。 - -在`pom.xml`中添加依赖 -```xml - - io.gitee.yunjiao-source.spring-boot - starter-querydsl-sql - ${version} - -``` - -已配置的Bean列表 -* SQLQueryFactory: 查询工厂 - -详细使用参考示例[example-querydsl-sql](../examples/example-querydsl-sql) - -## start-captcha - -验证码启动器, 在`pom.xml`中添加依赖 -```xml - - io.gitee.yunjiao-source.spring-boot - starter-captcha - ${version} - -``` - -已配置的Bean列表 -* CaptchaServiceFactory: 查询工厂 -* LineCaptchaService:线段干扰的验证码服务 -* CircleCaptchaService:圆圈干扰验证码服务 -* ShearCaptchaService:扭曲干扰验证码服务 -* GifCaptchaService:gif验证码服务 - -详细使用参考示例[example-captcha](../examples/example-captcha) \ No newline at end of file -- Gitee