Commit 0f9882680421b4316aab41088d9349f55d2aa78a

Authored by 芋道源码
Committed by Gitee
2 parents e6774407 2aec7d6c

!374 优化 captcha 代码

Merge pull request !374 from xingyu/dev
Showing 74 changed files with 3540 additions and 174 deletions
README.md
... ... @@ -191,19 +191,19 @@ ps:核心功能已经实现,正在对接微信小程序中...
191 191 | [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.7 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
192 192 | [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
193 193 | [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.15 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
194   -| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.3 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
  194 +| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.3.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
195 195 | [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.6.1 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
196 196 | [Redis](https://redis.io/) | key-value 数据库 | 5.0 / 6.0 | |
197 197 | [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.18.0 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
198 198 | [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.24 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
199   -| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.7.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
  199 +| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.7.6 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
200 200 | [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.5 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
201 201 | [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.8.0 | [文档](https://doc.iocoder.cn/bpm/) |
202 202 | [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
203 203 | [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
204 204 | [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
205 205 | [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.12.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
206   -| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.7.9 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
  206 +| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.7.10 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
207 207 | [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | |
208 208 | [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.5.3.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
209 209 | [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.24 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
... ... @@ -227,7 +227,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
227 227 | [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.9.4 |
228 228 | [pinia](https://pinia.vuejs.org/) | vuex5 | 2.0.28 |
229 229 | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
230   -| [vxe-table](https://vxetable.cn/) | vue最强表单 | 4.3.7 |
  230 +| [vxe-table](https://vxetable.cn/) | vue最强表单 | 4.3.9 |
231 231  
232 232 ### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
233 233  
... ...
yudao-dependencies/pom.xml
... ... @@ -32,7 +32,7 @@
32 32 <resilience4j.version>1.7.1</resilience4j.version>
33 33 <!-- 监控相关 -->
34 34 <skywalking.version>8.12.0</skywalking.version>
35   - <spring-boot-admin.version>2.7.9</spring-boot-admin.version>
  35 + <spring-boot-admin.version>2.7.10</spring-boot-admin.version>
36 36 <opentracing.version>0.33.0</opentracing.version>
37 37 <!-- Test 测试相关 -->
38 38 <podam.version>7.2.11.RELEASE</podam.version>
... ... @@ -45,7 +45,7 @@
45 45 <lombok.version>1.18.24</lombok.version>
46 46 <mapstruct.version>1.5.3.Final</mapstruct.version>
47 47 <hutool.version>5.8.11</hutool.version>
48   - <easyexcel.verion>3.1.4</easyexcel.verion>
  48 + <easyexcel.verion>3.1.5</easyexcel.verion>
49 49 <velocity.version>2.3</velocity.version>
50 50 <screw.version>1.0.5</screw.version>
51 51 <fastjson.version>1.2.83</fastjson.version>
... ... @@ -55,16 +55,15 @@
55 55 <commons-net.version>3.8.0</commons-net.version>
56 56 <jsch.version>0.1.55</jsch.version>
57 57 <tika-core.version>2.6.0</tika-core.version>
58   - <aj-captcha.version>1.3.0</aj-captcha.version>
59 58 <netty-all.version>4.1.86.Final</netty-all.version>
60 59 <ip2region.version>2.6.6</ip2region.version>
61 60 <!-- 三方云服务相关 -->
62 61 <okio.version>3.0.0</okio.version>
63 62 <okhttp3.version>4.10.0</okhttp3.version>
64   - <minio.version>8.4.6</minio.version>
  63 + <minio.version>8.5.1</minio.version>
65 64 <aliyun-java-sdk-core.version>4.6.3</aliyun-java-sdk-core.version>
66 65 <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
67   - <tencentcloud-sdk-java.version>3.1.660</tencentcloud-sdk-java.version>
  66 + <tencentcloud-sdk-java.version>3.1.667</tencentcloud-sdk-java.version>
68 67 <justauth.version>1.4.0</justauth.version>
69 68 <jimureport.version>1.5.6</jimureport.version>
70 69 <xercesImpl.version>2.12.2</xercesImpl.version>
... ... @@ -448,12 +447,6 @@
448 447 </dependency>
449 448  
450 449 <dependency>
451   - <groupId>com.anji-plus</groupId>
452   - <artifactId>spring-boot-starter-captcha</artifactId>
453   - <version>${aj-captcha.version}</version>
454   - </dependency>
455   -
456   - <dependency>
457 450 <groupId>org.apache.velocity</groupId>
458 451 <artifactId>velocity-engine-core</artifactId>
459 452 <version>${velocity.version}</version>
... ...
yudao-framework/yudao-spring-boot-starter-captcha/pom.xml
... ... @@ -21,6 +21,13 @@
21 21 <dependency>
22 22 <groupId>org.springframework.boot</groupId>
23 23 <artifactId>spring-boot-starter</artifactId>
  24 + <scope>provided</scope>
  25 + </dependency>
  26 +
  27 + <dependency>
  28 + <groupId>org.springframework.boot</groupId>
  29 + <artifactId>spring-boot-starter-web</artifactId>
  30 + <scope>provided</scope>
24 31 </dependency>
25 32  
26 33 <!-- DB 相关 -->
... ... @@ -29,11 +36,6 @@
29 36 <artifactId>yudao-spring-boot-starter-redis</artifactId>
30 37 </dependency>
31 38  
32   - <!-- 验证码相关 -->
33   - <dependency>
34   - <groupId>com.anji-plus</groupId>
35   - <artifactId>spring-boot-starter-captcha</artifactId>
36   - </dependency>
37 39 </dependencies>
38 40  
39 41 </project>
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/config/AjCaptchaAutoConfiguration.java 0 → 100644
  1 +package com.anji.captcha.config;
  2 +
  3 +import com.anji.captcha.properties.AjCaptchaProperties;
  4 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  5 +import org.springframework.context.annotation.ComponentScan;
  6 +import org.springframework.context.annotation.Configuration;
  7 +import org.springframework.context.annotation.Import;
  8 +
  9 +@Configuration
  10 +@EnableConfigurationProperties(AjCaptchaProperties.class)
  11 +@ComponentScan("com.anji.captcha")
  12 +@Import({AjCaptchaServiceAutoConfiguration.class, AjCaptchaStorageAutoConfiguration.class})
  13 +public class AjCaptchaAutoConfiguration {
  14 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/config/AjCaptchaServiceAutoConfiguration.java 0 → 100644
  1 +package com.anji.captcha.config;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import com.anji.captcha.model.common.Const;
  5 +import com.anji.captcha.properties.AjCaptchaProperties;
  6 +import com.anji.captcha.service.CaptchaService;
  7 +import com.anji.captcha.service.impl.CaptchaServiceFactory;
  8 +import com.anji.captcha.util.ImageUtils;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  11 +import org.springframework.context.annotation.Bean;
  12 +import org.springframework.context.annotation.Configuration;
  13 +import org.springframework.core.io.Resource;
  14 +import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  15 +import org.springframework.core.io.support.ResourcePatternResolver;
  16 +import org.springframework.util.Base64Utils;
  17 +import org.springframework.util.FileCopyUtils;
  18 +
  19 +import java.util.HashMap;
  20 +import java.util.Map;
  21 +import java.util.Properties;
  22 +
  23 +@Slf4j
  24 +@Configuration
  25 +public class AjCaptchaServiceAutoConfiguration {
  26 +
  27 + @Bean
  28 + @ConditionalOnMissingBean
  29 + public CaptchaService captchaService(AjCaptchaProperties prop) {
  30 + log.info("自定义配置项:{}", prop.toString());
  31 + Properties config = new Properties();
  32 + config.put(Const.CAPTCHA_CACHETYPE, prop.getCacheType().name());
  33 + config.put(Const.CAPTCHA_WATER_MARK, prop.getWaterMark());
  34 + config.put(Const.CAPTCHA_FONT_TYPE, prop.getFontType());
  35 + config.put(Const.CAPTCHA_TYPE, prop.getType().getCodeValue());
  36 + config.put(Const.CAPTCHA_INTERFERENCE_OPTIONS, prop.getInterferenceOptions());
  37 + config.put(Const.ORIGINAL_PATH_JIGSAW, prop.getJigsaw());
  38 + config.put(Const.ORIGINAL_PATH_PIC_CLICK, prop.getPicClick());
  39 + config.put(Const.CAPTCHA_SLIP_OFFSET, prop.getSlipOffset());
  40 + config.put(Const.CAPTCHA_AES_STATUS, String.valueOf(prop.getAesStatus()));
  41 + config.put(Const.CAPTCHA_WATER_FONT, prop.getWaterFont());
  42 + config.put(Const.CAPTCHA_CACAHE_MAX_NUMBER, prop.getCacheNumber());
  43 + config.put(Const.CAPTCHA_TIMING_CLEAR_SECOND, prop.getTimingClear());
  44 +
  45 + config.put(Const.HISTORY_DATA_CLEAR_ENABLE, prop.isHistoryDataClearEnable() ? "1" : "0");
  46 +
  47 + config.put(Const.REQ_FREQUENCY_LIMIT_ENABLE, prop.getReqFrequencyLimitEnable() ? "1" : "0");
  48 + config.put(Const.REQ_GET_LOCK_LIMIT, prop.getReqGetLockLimit() + "");
  49 + config.put(Const.REQ_GET_LOCK_SECONDS, prop.getReqGetLockSeconds() + "");
  50 + config.put(Const.REQ_GET_MINUTE_LIMIT, prop.getReqGetMinuteLimit() + "");
  51 + config.put(Const.REQ_CHECK_MINUTE_LIMIT, prop.getReqCheckMinuteLimit() + "");
  52 + config.put(Const.REQ_VALIDATE_MINUTE_LIMIT, prop.getReqVerifyMinuteLimit() + "");
  53 +
  54 + config.put(Const.CAPTCHA_FONT_SIZE, prop.getFontSize() + "");
  55 + config.put(Const.CAPTCHA_FONT_STYLE, prop.getFontStyle() + "");
  56 + config.put(Const.CAPTCHA_WORD_COUNT, prop.getClickWordCount() + "");
  57 +
  58 + if ((StrUtil.isNotBlank(prop.getJigsaw()) && prop.getJigsaw().startsWith("classpath:"))
  59 + || (StrUtil.isNotBlank(prop.getPicClick()) && prop.getPicClick().startsWith("classpath:"))) {
  60 + //自定义resources目录下初始化底图
  61 + config.put(Const.CAPTCHA_INIT_ORIGINAL, "true");
  62 + initializeBaseMap(prop.getJigsaw(), prop.getPicClick());
  63 + }
  64 + return CaptchaServiceFactory.getInstance(config);
  65 + }
  66 +
  67 + private static void initializeBaseMap(String jigsaw, String picClick) {
  68 + ImageUtils.cacheBootImage(getResourcesImagesFile(jigsaw + "/original/*.png"),
  69 + getResourcesImagesFile(jigsaw + "/slidingBlock/*.png"),
  70 + getResourcesImagesFile(picClick + "/*.png"));
  71 + }
  72 +
  73 + public static Map<String, String> getResourcesImagesFile(String path) {
  74 + Map<String, String> imgMap = new HashMap<>();
  75 + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  76 + try {
  77 + Resource[] resources = resolver.getResources(path);
  78 + for (Resource resource : resources) {
  79 + byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
  80 + String string = Base64Utils.encodeToString(bytes);
  81 + String filename = resource.getFilename();
  82 + imgMap.put(filename, string);
  83 + }
  84 + } catch (Exception e) {
  85 + e.printStackTrace();
  86 + }
  87 + return imgMap;
  88 + }
  89 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/config/AjCaptchaStorageAutoConfiguration.java 0 → 100644
  1 +package com.anji.captcha.config;
  2 +
  3 +import com.anji.captcha.properties.AjCaptchaProperties;
  4 +import com.anji.captcha.service.CaptchaCacheService;
  5 +import com.anji.captcha.service.impl.CaptchaServiceFactory;
  6 +import org.springframework.context.annotation.Bean;
  7 +import org.springframework.context.annotation.Configuration;
  8 +
  9 +/**
  10 + * 存储策略自动配置.
  11 + */
  12 +@Configuration
  13 +public class AjCaptchaStorageAutoConfiguration {
  14 +
  15 + @Bean(name = "AjCaptchaCacheService")
  16 + public CaptchaCacheService captchaCacheService(AjCaptchaProperties ajCaptchaProperties) {
  17 + // 缓存类型redis/local/....
  18 + return CaptchaServiceFactory.getCache(ajCaptchaProperties.getCacheType().name());
  19 + }
  20 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/model/common/CaptchaBaseMapEnum.java 0 → 100644
  1 +package com.anji.captcha.model.common;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 底图类型枚举
  8 + */
  9 +@Getter
  10 +@AllArgsConstructor
  11 +public enum CaptchaBaseMapEnum {
  12 + ORIGINAL("ORIGINAL", "滑动拼图底图"),
  13 + SLIDING_BLOCK("SLIDING_BLOCK", "滑动拼图滑块底图"),
  14 + PIC_CLICK("PIC_CLICK", "文字点选底图");
  15 +
  16 + private final String codeValue;
  17 + private final String codeDesc;
  18 +
  19 + //根据codeValue获取枚举
  20 + public static CaptchaBaseMapEnum parseFromCodeValue(String codeValue) {
  21 + for (CaptchaBaseMapEnum e : CaptchaBaseMapEnum.values()) {
  22 + if (e.codeValue.equals(codeValue)) {
  23 + return e;
  24 + }
  25 + }
  26 + return null;
  27 + }
  28 +
  29 + //根据codeValue获取描述
  30 + public static String getCodeDescByCodeBalue(String codeValue) {
  31 + CaptchaBaseMapEnum enumItem = parseFromCodeValue(codeValue);
  32 + return enumItem == null ? "" : enumItem.getCodeDesc();
  33 + }
  34 +
  35 + //验证codeValue是否有效
  36 + public static boolean validateCodeValue(String codeValue) {
  37 + return parseFromCodeValue(codeValue) != null;
  38 + }
  39 +
  40 + //列出所有值字符串
  41 + public static String getString() {
  42 + StringBuffer buffer = new StringBuffer();
  43 + for (CaptchaBaseMapEnum e : CaptchaBaseMapEnum.values()) {
  44 + buffer.append(e.codeValue).append("--").append(e.getCodeDesc()).append(", ");
  45 + }
  46 + buffer.deleteCharAt(buffer.lastIndexOf(","));
  47 + return buffer.toString().trim();
  48 + }
  49 +
  50 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/model/common/CaptchaTypeEnum.java 0 → 100644
  1 +package com.anji.captcha.model.common;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +@Getter
  7 +@AllArgsConstructor
  8 +public enum CaptchaTypeEnum {
  9 + /**
  10 + * 滑块拼图.
  11 + */
  12 + BLOCKPUZZLE("blockPuzzle", "滑块拼图"),
  13 + /**
  14 + * 文字点选.
  15 + */
  16 + CLICKWORD("clickWord", "文字点选"),
  17 + /**
  18 + * 默认.
  19 + */
  20 + DEFAULT("default", "默认");
  21 +
  22 + private final String codeValue;
  23 + private final String codeDesc;
  24 +
  25 + //根据codeValue获取枚举
  26 + public static CaptchaTypeEnum parseFromCodeValue(String codeValue) {
  27 + for (CaptchaTypeEnum e : CaptchaTypeEnum.values()) {
  28 + if (e.codeValue.equals(codeValue)) {
  29 + return e;
  30 + }
  31 + }
  32 + return null;
  33 + }
  34 +
  35 + //根据codeValue获取描述
  36 + public static String getCodeDescByCodeBalue(String codeValue) {
  37 + CaptchaTypeEnum enumItem = parseFromCodeValue(codeValue);
  38 + return enumItem == null ? "" : enumItem.getCodeDesc();
  39 + }
  40 +
  41 + //验证codeValue是否有效
  42 + public static boolean validateCodeValue(String codeValue) {
  43 + return parseFromCodeValue(codeValue) != null;
  44 + }
  45 +
  46 + //列出所有值字符串
  47 + public static String getString() {
  48 + StringBuilder buffer = new StringBuilder();
  49 + for (CaptchaTypeEnum e : CaptchaTypeEnum.values()) {
  50 + buffer.append(e.codeValue).append("--").append(e.getCodeDesc()).append(", ");
  51 + }
  52 + buffer.deleteCharAt(buffer.lastIndexOf(","));
  53 + return buffer.toString().trim();
  54 + }
  55 +
  56 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/model/common/Const.java 0 → 100644
  1 +package com.anji.captcha.model.common;
  2 +
  3 +/***
  4 + * @author wongbin
  5 + */
  6 +public interface Const {
  7 +
  8 + /**
  9 + * 滑块底图路径
  10 + */
  11 + String ORIGINAL_PATH_JIGSAW = "captcha.captchaOriginalPath.jigsaw";
  12 +
  13 + /***
  14 + *点选底图路径
  15 + */
  16 + String ORIGINAL_PATH_PIC_CLICK = "captcha.captchaOriginalPath.pic-click";
  17 +
  18 + /**
  19 + * 缓存local/redis...
  20 + */
  21 + String CAPTCHA_CACHETYPE = "captcha.cacheType";
  22 +
  23 + /**
  24 + * 右下角水印文字(我的水印)
  25 + */
  26 + String CAPTCHA_WATER_MARK = "captcha.water.mark";
  27 +
  28 + /**
  29 + * 点选文字验证码的文字字体(宋体)
  30 + */
  31 + String CAPTCHA_FONT_TYPE = "captcha.font.type";
  32 + String CAPTCHA_FONT_STYLE = "captcha.font.style";
  33 + String CAPTCHA_FONT_SIZE = "captcha.font.size";
  34 +
  35 + /**
  36 + * 验证码类型default两种都实例化。
  37 + */
  38 + String CAPTCHA_TYPE = "captcha.type";
  39 +
  40 + /**
  41 + * 滑动干扰项(0/1/2)
  42 + */
  43 + String CAPTCHA_INTERFERENCE_OPTIONS = "captcha.interference.options";
  44 +
  45 + /**
  46 + * 底图自定义初始化
  47 + */
  48 + String CAPTCHA_INIT_ORIGINAL = "captcha.init.original";
  49 +
  50 + /**
  51 + * 滑动误差偏移量
  52 + */
  53 + String CAPTCHA_SLIP_OFFSET = "captcha.slip.offset";
  54 +
  55 + /**
  56 + * aes加密开关
  57 + */
  58 + String CAPTCHA_AES_STATUS = "captcha.aes.status";
  59 +
  60 + /**
  61 + * 右下角水印字体(宋体)
  62 + */
  63 + String CAPTCHA_WATER_FONT = "captcha.water.font";
  64 +
  65 + /**
  66 + * local缓存的阈值
  67 + */
  68 + String CAPTCHA_CACAHE_MAX_NUMBER = "captcha.cache.number";
  69 +
  70 + /**
  71 + * 定时清理过期local缓存,秒
  72 + */
  73 + String CAPTCHA_TIMING_CLEAR_SECOND = "captcha.timing.clear";
  74 +
  75 + /**
  76 + * 历史资源清除开关 0禁用,1 开启
  77 + */
  78 + String HISTORY_DATA_CLEAR_ENABLE = "captcha.history.data.clear.enable";
  79 +
  80 + /**
  81 + * 接口限流开关 0禁用 1启用
  82 + */
  83 + String REQ_FREQUENCY_LIMIT_ENABLE = "captcha.req.frequency.limit.enable";
  84 +
  85 + /**
  86 + * get 接口 一分钟请求次数限制
  87 + */
  88 + String REQ_GET_MINUTE_LIMIT = "captcha.req.get.minute.limit";
  89 +
  90 + /**
  91 + * 验证失败后,get接口锁定时间
  92 + */
  93 + String REQ_GET_LOCK_LIMIT = "captcha.req.get.lock.limit";
  94 + /**
  95 + * 验证失败后,get接口锁定时间
  96 + */
  97 + String REQ_GET_LOCK_SECONDS = "captcha.req.get.lock.seconds";
  98 +
  99 + /**
  100 + * verify 接口 一分钟请求次数限制
  101 + */
  102 + String REQ_VALIDATE_MINUTE_LIMIT = "captcha.req.verify.minute.limit";
  103 + /**
  104 + * check接口 一分钟请求次数限制
  105 + */
  106 + String REQ_CHECK_MINUTE_LIMIT = "captcha.req.check.minute.limit";
  107 +
  108 + /***
  109 + * 点选文字个数
  110 + */
  111 + String CAPTCHA_WORD_COUNT = "captcha.word.count";
  112 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/model/common/RepCodeEnum.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.model.common;
  8 +
  9 +import lombok.AllArgsConstructor;
  10 +import lombok.Getter;
  11 +
  12 +import java.text.MessageFormat;
  13 +
  14 +/**
  15 + * 返回应答码
  16 + *
  17 + * @author
  18 + */
  19 +@AllArgsConstructor
  20 +@Getter
  21 +public enum RepCodeEnum {
  22 +
  23 + /**
  24 + * 0001 - 0099 网关应答码
  25 + */
  26 + SUCCESS("0000", "成功"),
  27 + ERROR("0001", "操作失败"),
  28 + EXCEPTION("9999", "服务器内部异常"),
  29 +
  30 + BLANK_ERROR("0011", "{0}不能为空"),
  31 + NULL_ERROR("0011", "{0}不能为空"),
  32 + NOT_NULL_ERROR("0012", "{0}必须为空"),
  33 + NOT_EXIST_ERROR("0013", "{0}数据库中不存在"),
  34 + EXIST_ERROR("0014", "{0}数据库中已存在"),
  35 + PARAM_TYPE_ERROR("0015", "{0}类型错误"),
  36 + PARAM_FORMAT_ERROR("0016", "{0}格式错误"),
  37 +
  38 + API_CAPTCHA_INVALID("6110", "验证码已失效,请重新获取"),
  39 + API_CAPTCHA_COORDINATE_ERROR("6111", "验证失败"),
  40 + API_CAPTCHA_ERROR("6112", "获取验证码失败,请联系管理员"),
  41 + API_CAPTCHA_BASEMAP_NULL("6113", "底图未初始化成功,请检查路径"),
  42 +
  43 + API_REQ_LIMIT_GET_ERROR("6201", "get接口请求次数超限,请稍后再试!"),
  44 + API_REQ_INVALID("6206", "无效请求,请重新获取验证码"),
  45 + API_REQ_LOCK_GET_ERROR("6202", "接口验证失败数过多,请稍后再试"),
  46 + API_REQ_LIMIT_CHECK_ERROR("6204", "check接口请求次数超限,请稍后再试!"),
  47 + API_REQ_LIMIT_VERIFY_ERROR("6205", "verify请求次数超限!");
  48 + private final String code;
  49 + private final String desc;
  50 +
  51 +
  52 + /**
  53 + * 将入参fieldNames与this.desc组合成错误信息
  54 + * {fieldName}不能为空
  55 + *
  56 + * @param fieldNames
  57 + * @return
  58 + */
  59 + public ResponseModel parseError(Object... fieldNames) {
  60 + ResponseModel errorMessage = new ResponseModel();
  61 + String newDesc = MessageFormat.format(this.desc, fieldNames);
  62 +
  63 + errorMessage.setRepCode(this.code);
  64 + errorMessage.setRepMsg(newDesc);
  65 + return errorMessage;
  66 + }
  67 +
  68 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/model/common/RequestModel.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.model.common;
  8 +
  9 +
  10 +import cn.hutool.core.util.StrUtil;
  11 +import lombok.Data;
  12 +
  13 +import java.io.Serializable;
  14 +import java.util.HashMap;
  15 +import java.util.List;
  16 +
  17 +@Data
  18 +public class RequestModel implements Serializable {
  19 +
  20 + private static final long serialVersionUID = -5800786065305114784L;
  21 +
  22 + /**
  23 + * 当前请求接口路径 /business/accessUser/login
  24 + */
  25 + private String servletPath;
  26 +
  27 + /**
  28 + * {"reqData":{"password":"*****","userName":"admin"},"sign":"a304a7f296f565b6d2009797f68180f0","time":"1542456453355","token":""}
  29 + */
  30 + private String requestString;
  31 +
  32 + /**
  33 + * {"password":"****","userName":"admin"}
  34 + */
  35 + private HashMap reqData;
  36 +
  37 + private String token;
  38 +
  39 + private Long userId;
  40 +
  41 + private String userName;
  42 +
  43 + private List<Long> projectList;
  44 +
  45 + //拥有哪些分组
  46 + private List<Long> groupIdList;
  47 +
  48 + private String target;
  49 +
  50 + private String sign;
  51 +
  52 + private String time;
  53 +
  54 + private String sourceIP;
  55 +
  56 + /**
  57 + * 校验自身参数合法性
  58 + *
  59 + * @return
  60 + */
  61 + public boolean isVaildateRequest() {
  62 + if (StrUtil.isBlank(sign) || StrUtil.isBlank(time)) {
  63 + return false;
  64 + }
  65 + return true;
  66 + }
  67 +
  68 + public String getServletPath() {
  69 + return servletPath;
  70 + }
  71 +
  72 + public void setServletPath(String servletPath) {
  73 + this.servletPath = servletPath;
  74 + }
  75 +
  76 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/model/common/ResponseModel.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.model.common;
  8 +
  9 +import cn.hutool.core.util.StrUtil;
  10 +import lombok.Data;
  11 +
  12 +import java.io.Serializable;
  13 +
  14 +@Data
  15 +public class ResponseModel implements Serializable {
  16 +
  17 + private static final long serialVersionUID = 8445617032523881407L;
  18 +
  19 + private String repCode;
  20 +
  21 + private String repMsg;
  22 +
  23 + private Object repData;
  24 +
  25 + public ResponseModel() {
  26 + this.repCode = RepCodeEnum.SUCCESS.getCode();
  27 + }
  28 +
  29 + public ResponseModel(RepCodeEnum repCodeEnum) {
  30 + this.setRepCodeEnum(repCodeEnum);
  31 + }
  32 +
  33 + //成功
  34 + public static ResponseModel success() {
  35 + return ResponseModel.successMsg("成功");
  36 + }
  37 +
  38 + public static ResponseModel successMsg(String message) {
  39 + ResponseModel responseModel = new ResponseModel();
  40 + responseModel.setRepMsg(message);
  41 + return responseModel;
  42 + }
  43 +
  44 + public static ResponseModel successData(Object data) {
  45 + ResponseModel responseModel = new ResponseModel();
  46 + responseModel.setRepCode(RepCodeEnum.SUCCESS.getCode());
  47 + responseModel.setRepData(data);
  48 + return responseModel;
  49 + }
  50 +
  51 + //失败
  52 + public static ResponseModel errorMsg(RepCodeEnum message) {
  53 + ResponseModel responseModel = new ResponseModel();
  54 + responseModel.setRepCodeEnum(message);
  55 + return responseModel;
  56 + }
  57 +
  58 + public static ResponseModel errorMsg(String message) {
  59 + ResponseModel responseModel = new ResponseModel();
  60 + responseModel.setRepCode(RepCodeEnum.ERROR.getCode());
  61 + responseModel.setRepMsg(message);
  62 + return responseModel;
  63 + }
  64 +
  65 + public static ResponseModel errorMsg(RepCodeEnum repCodeEnum, String message) {
  66 + ResponseModel responseModel = new ResponseModel();
  67 + responseModel.setRepCode(repCodeEnum.getCode());
  68 + responseModel.setRepMsg(message);
  69 + return responseModel;
  70 + }
  71 +
  72 + public static ResponseModel exceptionMsg(String message) {
  73 + ResponseModel responseModel = new ResponseModel();
  74 + responseModel.setRepCode(RepCodeEnum.EXCEPTION.getCode());
  75 + responseModel.setRepMsg(RepCodeEnum.EXCEPTION.getDesc() + ": " + message);
  76 + return responseModel;
  77 + }
  78 +
  79 +
  80 + public boolean isSuccess() {
  81 + return StrUtil.equals(repCode, RepCodeEnum.SUCCESS.getCode());
  82 + }
  83 +
  84 + public String getRepCode() {
  85 + return repCode;
  86 + }
  87 +
  88 + public void setRepCodeEnum(RepCodeEnum repCodeEnum) {
  89 + this.repCode = repCodeEnum.getCode();
  90 + this.repMsg = repCodeEnum.getDesc();
  91 + }
  92 +
  93 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/model/vo/CaptchaVO.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.model.vo;
  8 +
  9 +import lombok.Data;
  10 +
  11 +import java.awt.*;
  12 +import java.io.Serializable;
  13 +import java.util.List;
  14 +
  15 +@Data
  16 +public class CaptchaVO implements Serializable {
  17 +
  18 + /**
  19 + * 验证码id(后台申请)
  20 + */
  21 + private String captchaId;
  22 +
  23 + private String projectCode;
  24 +
  25 + /**
  26 + * 验证码类型:(clickWord,blockPuzzle)
  27 + */
  28 + private String captchaType;
  29 +
  30 + private String captchaOriginalPath;
  31 +
  32 + private String captchaFontType;
  33 +
  34 + private Integer captchaFontSize;
  35 +
  36 + private String secretKey;
  37 +
  38 + /**
  39 + * 原生图片base64
  40 + */
  41 + private String originalImageBase64;
  42 +
  43 + /**
  44 + * 滑块点选坐标
  45 + */
  46 + private PointVO point;
  47 +
  48 + /**
  49 + * 滑块图片base64
  50 + */
  51 + private String jigsawImageBase64;
  52 +
  53 + /**
  54 + * 点选文字
  55 + */
  56 + private List<String> wordList;
  57 +
  58 + /**
  59 + * 点选坐标
  60 + */
  61 + private List<Point> pointList;
  62 +
  63 +
  64 + /**
  65 + * 点坐标(base64加密传输)
  66 + */
  67 + private String pointJson;
  68 +
  69 +
  70 + /**
  71 + * UUID(每次请求的验证码唯一标识)
  72 + */
  73 + private String token;
  74 +
  75 + /**
  76 + * 校验结果
  77 + */
  78 + private Boolean result = false;
  79 +
  80 + /**
  81 + * 后台二次校验参数
  82 + */
  83 + private String captchaVerification;
  84 +
  85 + /***
  86 + * 客户端UI组件id,组件初始化时设置一次,UUID
  87 + */
  88 + private String clientUid;
  89 + /***
  90 + * 客户端的请求时间,预留字段
  91 + */
  92 + private Long ts;
  93 +
  94 + /***
  95 + * 客户端ip+userAgent
  96 + */
  97 + private String browserInfo;
  98 +
  99 + public void resetClientFlag() {
  100 + this.browserInfo = null;
  101 + this.clientUid = null;
  102 + }
  103 +
  104 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/model/vo/PointVO.java 0 → 100644
  1 +package com.anji.captcha.model.vo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.Arrays;
  6 +import java.util.HashMap;
  7 +import java.util.Map;
  8 +import java.util.Objects;
  9 +import java.util.regex.Pattern;
  10 +
  11 +/**
  12 + * Created by raodeming on 2020/5/16.
  13 + */
  14 +@Data
  15 +public class PointVO {
  16 + private String secretKey;
  17 +
  18 + public int x;
  19 +
  20 + public int y;
  21 +
  22 + public PointVO(int x, int y, String secretKey) {
  23 + this.secretKey = secretKey;
  24 + this.x = x;
  25 + this.y = y;
  26 + }
  27 +
  28 + public PointVO() {
  29 + }
  30 +
  31 + public PointVO(int x, int y) {
  32 + this.x = x;
  33 + this.y = y;
  34 + }
  35 +
  36 + public String toJsonString() {
  37 + return String.format("{\"secretKey\":\"%s\",\"x\":%d,\"y\":%d}", secretKey, x, y);
  38 + }
  39 +
  40 + public PointVO parse(String jsonStr) {
  41 + Map<String, Object> m = new HashMap();
  42 + Arrays.stream(jsonStr
  43 + .replaceFirst(",\\{", "\\{")
  44 + .replaceFirst("\\{", "")
  45 + .replaceFirst("\\}", "")
  46 + .replaceAll("\"", "")
  47 + .split(",")).forEach(item -> {
  48 + m.put(item.split(":")[0], item.split(":")[1]);
  49 + });
  50 + //PointVO d = new PointVO();
  51 + setX(Double.valueOf("" + m.get("x")).intValue());
  52 + setY(Double.valueOf("" + m.get("y")).intValue());
  53 + setSecretKey(m.getOrDefault("secretKey", "") + "");
  54 + return this;
  55 + }
  56 +
  57 + @Override
  58 + public boolean equals(Object o) {
  59 + if (this == o) {
  60 + return true;
  61 + }
  62 + if (o == null || getClass() != o.getClass()) {
  63 + return false;
  64 + }
  65 + PointVO pointVO = (PointVO) o;
  66 + return x == pointVO.x && y == pointVO.y && Objects.equals(secretKey, pointVO.secretKey);
  67 + }
  68 +
  69 + @Override
  70 + public int hashCode() {
  71 +
  72 + return Objects.hash(secretKey, x, y);
  73 + }
  74 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/properties/AjCaptchaProperties.java 0 → 100644
  1 +package com.anji.captcha.properties;
  2 +
  3 +import com.anji.captcha.model.common.CaptchaTypeEnum;
  4 +import lombok.Data;
  5 +import org.springframework.boot.context.properties.ConfigurationProperties;
  6 +
  7 +import java.awt.*;
  8 +
  9 +import static com.anji.captcha.properties.AjCaptchaProperties.PREFIX;
  10 +import static com.anji.captcha.properties.AjCaptchaProperties.StorageType.local;
  11 +
  12 +@Data
  13 +@ConfigurationProperties(PREFIX)
  14 +public class AjCaptchaProperties {
  15 + public static final String PREFIX = "aj.captcha";
  16 +
  17 + /**
  18 + * 验证码类型.
  19 + */
  20 + private CaptchaTypeEnum type = CaptchaTypeEnum.DEFAULT;
  21 +
  22 + /**
  23 + * 滑动拼图底图路径.
  24 + */
  25 + private String jigsaw = "";
  26 +
  27 + /**
  28 + * 点选文字底图路径.
  29 + */
  30 + private String picClick = "";
  31 +
  32 +
  33 + /**
  34 + * 右下角水印文字(我的水印).
  35 + */
  36 + private String waterMark = "我的水印";
  37 +
  38 + /**
  39 + * 右下角水印字体(文泉驿正黑).
  40 + */
  41 + private String waterFont = "WenQuanZhengHei.ttf";
  42 +
  43 + /**
  44 + * 点选文字验证码的文字字体(文泉驿正黑).
  45 + */
  46 + private String fontType = "WenQuanZhengHei.ttf";
  47 +
  48 + /**
  49 + * 校验滑动拼图允许误差偏移量(默认5像素).
  50 + */
  51 + private String slipOffset = "5";
  52 +
  53 + /**
  54 + * aes加密坐标开启或者禁用(true|false).
  55 + */
  56 + private Boolean aesStatus = true;
  57 +
  58 + /**
  59 + * 滑块干扰项(0/1/2)
  60 + */
  61 + private String interferenceOptions = "0";
  62 +
  63 + /**
  64 + * local缓存的阈值
  65 + */
  66 + private String cacheNumber = "1000";
  67 +
  68 + /**
  69 + * 定时清理过期local缓存(单位秒)
  70 + */
  71 + private String timingClear = "180";
  72 +
  73 + /**
  74 + * 缓存类型redis/local/....
  75 + */
  76 + private StorageType cacheType = local;
  77 + /**
  78 + * 历史数据清除开关
  79 + */
  80 + private boolean historyDataClearEnable = false;
  81 +
  82 + /**
  83 + * 一分钟内接口请求次数限制 开关
  84 + */
  85 + private boolean reqFrequencyLimitEnable = false;
  86 +
  87 + /***
  88 + * 一分钟内check接口失败次数
  89 + */
  90 + private int reqGetLockLimit = 5;
  91 + /**
  92 + *
  93 + */
  94 + private int reqGetLockSeconds = 300;
  95 +
  96 + /***
  97 + * get接口一分钟内限制访问数
  98 + */
  99 + private int reqGetMinuteLimit = 100;
  100 + private int reqCheckMinuteLimit = 100;
  101 + private int reqVerifyMinuteLimit = 100;
  102 +
  103 + /**
  104 + * 点选字体样式
  105 + */
  106 + private int fontStyle = Font.BOLD;
  107 +
  108 + /**
  109 + * 点选字体大小
  110 + */
  111 + private int fontSize = 25;
  112 +
  113 + /**
  114 + * 点选文字个数,存在问题,暂不要使用
  115 + */
  116 + private int clickWordCount = 4;
  117 +
  118 + public boolean getReqFrequencyLimitEnable() {
  119 + return reqFrequencyLimitEnable;
  120 + }
  121 +
  122 + public enum StorageType {
  123 + /**
  124 + * 内存.
  125 + */
  126 + local,
  127 + /**
  128 + * redis.
  129 + */
  130 + redis,
  131 + /**
  132 + * 其他.
  133 + */
  134 + other,
  135 + }
  136 +
  137 + public static String getPrefix() {
  138 + return PREFIX;
  139 + }
  140 +
  141 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/CaptchaCacheService.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.service;
  8 +
  9 +/**
  10 + * 验证码缓存接口
  11 + *
  12 + * @author lide1202@hotmail.com
  13 + * @date 2018-08-21
  14 + */
  15 +public interface CaptchaCacheService {
  16 +
  17 + void set(String key, String value, long expiresInSeconds);
  18 +
  19 + boolean exists(String key);
  20 +
  21 + void delete(String key);
  22 +
  23 + String get(String key);
  24 +
  25 + /**
  26 + * 缓存类型-local/redis/memcache/..
  27 + * 通过java SPI机制,接入方可自定义实现类
  28 + *
  29 + * @return
  30 + */
  31 + String type();
  32 +
  33 + /***
  34 + *
  35 + * @param key
  36 + * @param val
  37 + * @return
  38 + */
  39 + default Long increment(String key, long val) {
  40 + return 0L;
  41 + }
  42 +
  43 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/CaptchaService.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.service;
  8 +
  9 +import com.anji.captcha.model.common.ResponseModel;
  10 +import com.anji.captcha.model.vo.CaptchaVO;
  11 +
  12 +import java.util.Properties;
  13 +
  14 +/**
  15 + * 验证码服务接口
  16 + *
  17 + * @author lide1202@hotmail.com
  18 + * @date 2020-05-12
  19 + */
  20 +public interface CaptchaService {
  21 + /**
  22 + * 配置初始化
  23 + */
  24 + void init(Properties config);
  25 +
  26 + /**
  27 + * 获取验证码
  28 + *
  29 + * @param captchaVO
  30 + * @return
  31 + */
  32 + ResponseModel get(CaptchaVO captchaVO);
  33 +
  34 + /**
  35 + * 核对验证码(前端)
  36 + *
  37 + * @param captchaVO
  38 + * @return
  39 + */
  40 + ResponseModel check(CaptchaVO captchaVO);
  41 +
  42 + /**
  43 + * 二次校验验证码(后端)
  44 + *
  45 + * @param captchaVO
  46 + * @return
  47 + */
  48 + ResponseModel verification(CaptchaVO captchaVO);
  49 +
  50 + /***
  51 + * 验证码类型
  52 + * 通过java SPI机制,接入方可自定义实现类,实现新的验证类型
  53 + * @return
  54 + */
  55 + String captchaType();
  56 +
  57 + /**
  58 + * 历史资源清除(过期的图片文件,生成的临时图片...)
  59 + *
  60 + * @param config 配置项 控制资源清理的粒度
  61 + */
  62 + void destroy(Properties config);
  63 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/AbstractCaptchaService.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.service.impl;
  8 +
  9 +import cn.hutool.core.util.StrUtil;
  10 +import com.anji.captcha.model.common.Const;
  11 +import com.anji.captcha.model.common.RepCodeEnum;
  12 +import com.anji.captcha.model.common.ResponseModel;
  13 +import com.anji.captcha.model.vo.CaptchaVO;
  14 +import com.anji.captcha.service.CaptchaCacheService;
  15 +import com.anji.captcha.service.CaptchaService;
  16 +import com.anji.captcha.util.*;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +
  19 +import java.awt.*;
  20 +import java.io.File;
  21 +import java.io.OutputStream;
  22 +import java.nio.charset.StandardCharsets;
  23 +import java.nio.file.Files;
  24 +import java.util.Base64;
  25 +import java.util.Objects;
  26 +import java.util.Properties;
  27 +
  28 +/**
  29 + * Created by raodeming on 2019/12/25.
  30 + */
  31 +@Slf4j
  32 +public abstract class AbstractCaptchaService implements CaptchaService {
  33 +
  34 + protected static final String IMAGE_TYPE_PNG = "png";
  35 +
  36 + protected static int HAN_ZI_SIZE = 25;
  37 +
  38 + protected static int HAN_ZI_SIZE_HALF = HAN_ZI_SIZE / 2;
  39 + //check校验坐标
  40 + protected static String REDIS_CAPTCHA_KEY = "RUNNING:CAPTCHA:%s";
  41 +
  42 + //后台二次校验坐标
  43 + protected static String REDIS_SECOND_CAPTCHA_KEY = "RUNNING:CAPTCHA:second-%s";
  44 +
  45 + protected static Long EXPIRESIN_SECONDS = 2 * 60L;
  46 +
  47 + protected static Long EXPIRESIN_THREE = 3 * 60L;
  48 +
  49 + protected static String waterMark = "我的水印";
  50 +
  51 + protected static String waterMarkFontStr = "WenQuanZhengHei.ttf";
  52 +
  53 + protected Font waterMarkFont;//水印字体
  54 +
  55 + protected static String slipOffset = "5";
  56 +
  57 + protected static Boolean captchaAesStatus = true;
  58 +
  59 + protected static String clickWordFontStr = "WenQuanZhengHei.ttf";
  60 +
  61 + protected Font clickWordFont;//点选文字字体
  62 +
  63 + protected static String cacheType = "local";
  64 +
  65 + protected static int captchaInterferenceOptions = 0;
  66 +
  67 + //判断应用是否实现了自定义缓存,没有就使用内存
  68 + @Override
  69 + public void init(final Properties config) {
  70 + //初始化底图
  71 + boolean aBoolean = Boolean.parseBoolean(config.getProperty(Const.CAPTCHA_INIT_ORIGINAL));
  72 + if (!aBoolean) {
  73 + ImageUtils.cacheImage(config.getProperty(Const.ORIGINAL_PATH_JIGSAW),
  74 + config.getProperty(Const.ORIGINAL_PATH_PIC_CLICK));
  75 + }
  76 + log.info("--->>>初始化验证码底图<<<---" + captchaType());
  77 + waterMark = config.getProperty(Const.CAPTCHA_WATER_MARK, "我的水印");
  78 + slipOffset = config.getProperty(Const.CAPTCHA_SLIP_OFFSET, "5");
  79 + waterMarkFontStr = config.getProperty(Const.CAPTCHA_WATER_FONT, "WenQuanZhengHei.ttf");
  80 + captchaAesStatus = Boolean.parseBoolean(config.getProperty(Const.CAPTCHA_AES_STATUS, "true"));
  81 + clickWordFontStr = config.getProperty(Const.CAPTCHA_FONT_TYPE, "WenQuanZhengHei.ttf");
  82 + //clickWordFontStr = config.getProperty(Const.CAPTCHA_FONT_TYPE, "SourceHanSansCN-Normal.otf");
  83 + cacheType = config.getProperty(Const.CAPTCHA_CACHETYPE, "local");
  84 + captchaInterferenceOptions = Integer.parseInt(
  85 + config.getProperty(Const.CAPTCHA_INTERFERENCE_OPTIONS, "0"));
  86 +
  87 + // 部署在linux中,如果没有安装中文字段,水印和点选文字,中文无法显示,
  88 + // 通过加载resources下的font字体解决,无需在linux中安装字体
  89 + loadWaterMarkFont();
  90 +
  91 + if ("local".equals(cacheType)) {
  92 + log.info("初始化local缓存...");
  93 + CacheUtil.init(Integer.parseInt(config.getProperty(Const.CAPTCHA_CACAHE_MAX_NUMBER, "1000")),
  94 + Long.parseLong(config.getProperty(Const.CAPTCHA_TIMING_CLEAR_SECOND, "180")));
  95 + }
  96 + if ("1".equals(config.getProperty(Const.HISTORY_DATA_CLEAR_ENABLE, "0"))) {
  97 + log.info("历史资源清除开关...开启..." + captchaType());
  98 + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
  99 + @Override
  100 + public void run() {
  101 + destroy(config);
  102 + }
  103 + }));
  104 + }
  105 + if ("1".equals(config.getProperty(Const.REQ_FREQUENCY_LIMIT_ENABLE, "0"))) {
  106 + if (limitHandler == null) {
  107 + log.info("接口分钟内限流开关...开启...");
  108 + limitHandler = new FrequencyLimitHandler.DefaultLimitHandler(config, getCacheService(cacheType));
  109 + }
  110 + }
  111 + }
  112 +
  113 + protected CaptchaCacheService getCacheService(String cacheType) {
  114 + return CaptchaServiceFactory.getCache(cacheType);
  115 + }
  116 +
  117 + @Override
  118 + public void destroy(Properties config) {
  119 +
  120 + }
  121 +
  122 + private static FrequencyLimitHandler limitHandler;
  123 +
  124 + @Override
  125 + public ResponseModel get(CaptchaVO captchaVO) {
  126 + if (limitHandler != null) {
  127 + captchaVO.setClientUid(getValidateClientId(captchaVO));
  128 + return limitHandler.validateGet(captchaVO);
  129 + }
  130 + return null;
  131 + }
  132 +
  133 + @Override
  134 + public ResponseModel check(CaptchaVO captchaVO) {
  135 + if (limitHandler != null) {
  136 + // 验证客户端
  137 + /* ResponseModel ret = limitHandler.validateCheck(captchaVO);
  138 + if(!validatedReq(ret)){
  139 + return ret;
  140 + }
  141 + // 服务端参数验证*/
  142 + captchaVO.setClientUid(getValidateClientId(captchaVO));
  143 + return limitHandler.validateCheck(captchaVO);
  144 + }
  145 + return null;
  146 + }
  147 +
  148 + @Override
  149 + public ResponseModel verification(CaptchaVO captchaVO) {
  150 + if (captchaVO == null) {
  151 + return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
  152 + }
  153 + if (StrUtil.isEmpty(captchaVO.getCaptchaVerification())) {
  154 + return RepCodeEnum.NULL_ERROR.parseError("captchaVerification");
  155 + }
  156 + if (limitHandler != null) {
  157 + return limitHandler.validateVerify(captchaVO);
  158 + }
  159 + return null;
  160 + }
  161 +
  162 + protected boolean validatedReq(ResponseModel resp) {
  163 + return resp == null || resp.isSuccess();
  164 + }
  165 +
  166 + protected String getValidateClientId(CaptchaVO req) {
  167 + // 以服务端获取的客户端标识 做识别标志
  168 + if (StrUtil.isNotEmpty(req.getBrowserInfo())) {
  169 + return MD5Util.md5(req.getBrowserInfo());
  170 + }
  171 + // 以客户端Ui组件id做识别标志
  172 + if (StrUtil.isNotEmpty(req.getClientUid())) {
  173 + return req.getClientUid();
  174 + }
  175 + return null;
  176 + }
  177 +
  178 + protected void afterValidateFail(CaptchaVO data) {
  179 + if (limitHandler != null) {
  180 + // 验证失败 分钟内计数
  181 + String fails = String.format(FrequencyLimitHandler.LIMIT_KEY, "FAIL", data.getClientUid());
  182 + CaptchaCacheService cs = getCacheService(cacheType);
  183 + if (!cs.exists(fails)) {
  184 + cs.set(fails, "1", 60);
  185 + }
  186 + cs.increment(fails, 1);
  187 + }
  188 + }
  189 +
  190 + /**
  191 + * 加载resources下的font字体,add by lide1202@hotmail.com
  192 + * 部署在linux中,如果没有安装中文字段,水印和点选文字,中文无法显示,
  193 + * 通过加载resources下的font字体解决,无需在linux中安装字体
  194 + */
  195 + private void loadWaterMarkFont() {
  196 + try {
  197 + if (waterMarkFontStr.toLowerCase().endsWith(".ttf") || waterMarkFontStr.toLowerCase().endsWith(".ttc")
  198 + || waterMarkFontStr.toLowerCase().endsWith(".otf")) {
  199 + this.waterMarkFont = Font.createFont(Font.TRUETYPE_FONT,
  200 + Objects.requireNonNull(getClass().getResourceAsStream("/fonts/" + waterMarkFontStr)))
  201 + .deriveFont(Font.BOLD, HAN_ZI_SIZE / 2);
  202 + } else {
  203 + this.waterMarkFont = new Font(waterMarkFontStr, Font.BOLD, HAN_ZI_SIZE / 2);
  204 + }
  205 +
  206 + } catch (Exception e) {
  207 + log.error("load font error:{}", e);
  208 + }
  209 + }
  210 +
  211 + public static boolean base64StrToImage(String imgStr, String path) {
  212 + if (imgStr == null) {
  213 + return false;
  214 + }
  215 +
  216 + Base64.Decoder decoder = Base64.getDecoder();
  217 + try {
  218 + // 解密
  219 + byte[] b = decoder.decode(imgStr);
  220 + // 处理数据
  221 + for (int i = 0; i < b.length; ++i) {
  222 + if (b[i] < 0) {
  223 + b[i] += 256;
  224 + }
  225 + }
  226 + //文件夹不存在则自动创建
  227 + File tempFile = new File(path);
  228 + if (!tempFile.getParentFile().exists()) {
  229 + tempFile.getParentFile().mkdirs();
  230 + }
  231 + OutputStream out = Files.newOutputStream(tempFile.toPath());
  232 + out.write(b);
  233 + out.flush();
  234 + out.close();
  235 + return true;
  236 + } catch (Exception e) {
  237 + return false;
  238 + }
  239 + }
  240 +
  241 + /**
  242 + * 解密前端坐标aes加密
  243 + *
  244 + * @param point
  245 + * @return
  246 + * @throws Exception
  247 + */
  248 + public static String decrypt(String point, String key) throws Exception {
  249 + return AESUtil.aesDecrypt(point, key);
  250 + }
  251 +
  252 + protected static int getEnOrChLength(String s) {
  253 + int enCount = 0;
  254 + int chCount = 0;
  255 + for (int i = 0; i < s.length(); i++) {
  256 + int length = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8).length;
  257 + if (length > 1) {
  258 + chCount++;
  259 + } else {
  260 + enCount++;
  261 + }
  262 + }
  263 + int chOffset = (HAN_ZI_SIZE / 2) * chCount + 5;
  264 + int enOffset = enCount * 8;
  265 + return chOffset + enOffset;
  266 + }
  267 +
  268 +
  269 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.service.impl;
  8 +
  9 +import cn.hutool.core.util.StrUtil;
  10 +import com.anji.captcha.model.common.CaptchaTypeEnum;
  11 +import com.anji.captcha.model.common.RepCodeEnum;
  12 +import com.anji.captcha.model.common.ResponseModel;
  13 +import com.anji.captcha.model.vo.CaptchaVO;
  14 +import com.anji.captcha.model.vo.PointVO;
  15 +import com.anji.captcha.util.*;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +
  18 +import javax.imageio.ImageIO;
  19 +import java.awt.*;
  20 +import java.awt.image.BufferedImage;
  21 +import java.io.ByteArrayOutputStream;
  22 +import java.util.Base64;
  23 +import java.util.Objects;
  24 +import java.util.Properties;
  25 +import java.util.Random;
  26 +
  27 +/**
  28 + * 滑动验证码
  29 + * <p>
  30 + * Created by raodeming on 2019/12/25.
  31 + */
  32 +@Slf4j
  33 +public class BlockPuzzleCaptchaServiceImpl extends AbstractCaptchaService {
  34 +
  35 + @Override
  36 + public void init(Properties config) {
  37 + super.init(config);
  38 + }
  39 +
  40 + @Override
  41 + public void destroy(Properties config) {
  42 + log.info("start-clear-history-data-", captchaType());
  43 + }
  44 +
  45 + @Override
  46 + public String captchaType() {
  47 + return CaptchaTypeEnum.BLOCKPUZZLE.getCodeValue();
  48 + }
  49 +
  50 + @Override
  51 + public ResponseModel get(CaptchaVO captchaVO) {
  52 + ResponseModel r = super.get(captchaVO);
  53 + if (!validatedReq(r)) {
  54 + return r;
  55 + }
  56 + //原生图片
  57 + BufferedImage originalImage = ImageUtils.getOriginal();
  58 + if (null == originalImage) {
  59 + log.error("滑动底图未初始化成功,请检查路径");
  60 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
  61 + }
  62 + //设置水印
  63 + Graphics backgroundGraphics = originalImage.getGraphics();
  64 + int width = originalImage.getWidth();
  65 + int height = originalImage.getHeight();
  66 + backgroundGraphics.setFont(waterMarkFont);
  67 + backgroundGraphics.setColor(Color.white);
  68 + backgroundGraphics.drawString(waterMark, width - getEnOrChLength(waterMark), height - (HAN_ZI_SIZE / 2) + 7);
  69 +
  70 + //抠图图片
  71 + String jigsawImageBase64 = ImageUtils.getslidingBlock();
  72 + BufferedImage jigsawImage = ImageUtils.getBase64StrToImage(jigsawImageBase64);
  73 + if (null == jigsawImage) {
  74 + log.error("滑动底图未初始化成功,请检查路径");
  75 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
  76 + }
  77 + CaptchaVO captcha = pictureTemplatesCut(originalImage, jigsawImage, jigsawImageBase64);
  78 + if (captcha == null
  79 + || StrUtil.isBlank(captcha.getJigsawImageBase64())
  80 + || StrUtil.isBlank(captcha.getOriginalImageBase64())) {
  81 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_ERROR);
  82 + }
  83 + return ResponseModel.successData(captcha);
  84 + }
  85 +
  86 + @Override
  87 + public ResponseModel check(CaptchaVO captchaVO) {
  88 + ResponseModel r = super.check(captchaVO);
  89 + if (!validatedReq(r)) {
  90 + return r;
  91 + }
  92 + //取坐标信息
  93 + String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
  94 + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
  95 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
  96 + }
  97 + String s = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
  98 + //验证码只用一次,即刻失效
  99 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
  100 + PointVO point = null;
  101 + PointVO point1 = null;
  102 + String pointJson = null;
  103 + try {
  104 + point = JsonUtil.parseObject(s, PointVO.class);
  105 + //aes解密
  106 + pointJson = decrypt(captchaVO.getPointJson(), point.getSecretKey());
  107 + point1 = JsonUtil.parseObject(pointJson, PointVO.class);
  108 + } catch (Exception e) {
  109 + log.error("验证码坐标解析失败", e);
  110 + afterValidateFail(captchaVO);
  111 + return ResponseModel.errorMsg(e.getMessage());
  112 + }
  113 + if (point.x - Integer.parseInt(slipOffset) > point1.x
  114 + || point1.x > point.x + Integer.parseInt(slipOffset)
  115 + || point.y != point1.y) {
  116 + afterValidateFail(captchaVO);
  117 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
  118 + }
  119 + //校验成功,将信息存入缓存
  120 + String secretKey = point.getSecretKey();
  121 + String value = null;
  122 + try {
  123 + value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(pointJson), secretKey);
  124 + } catch (Exception e) {
  125 + log.error("AES加密失败", e);
  126 + afterValidateFail(captchaVO);
  127 + return ResponseModel.errorMsg(e.getMessage());
  128 + }
  129 + String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
  130 + CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE);
  131 + captchaVO.setResult(true);
  132 + captchaVO.resetClientFlag();
  133 + return ResponseModel.successData(captchaVO);
  134 + }
  135 +
  136 + @Override
  137 + public ResponseModel verification(CaptchaVO captchaVO) {
  138 + ResponseModel r = super.verification(captchaVO);
  139 + if (!validatedReq(r)) {
  140 + return r;
  141 + }
  142 + try {
  143 + String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
  144 + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
  145 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
  146 + }
  147 + //二次校验取值后,即刻失效
  148 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
  149 + } catch (Exception e) {
  150 + log.error("验证码坐标解析失败", e);
  151 + return ResponseModel.errorMsg(e.getMessage());
  152 + }
  153 + return ResponseModel.success();
  154 + }
  155 +
  156 + /**
  157 + * 根据模板切图
  158 + *
  159 + * @throws Exception
  160 + */
  161 + public CaptchaVO pictureTemplatesCut(BufferedImage originalImage, BufferedImage jigsawImage, String jigsawImageBase64) {
  162 + try {
  163 + CaptchaVO dataVO = new CaptchaVO();
  164 +
  165 + int originalWidth = originalImage.getWidth();
  166 + int originalHeight = originalImage.getHeight();
  167 + int jigsawWidth = jigsawImage.getWidth();
  168 + int jigsawHeight = jigsawImage.getHeight();
  169 +
  170 + //随机生成拼图坐标
  171 + PointVO point = generateJigsawPoint(originalWidth, originalHeight, jigsawWidth, jigsawHeight);
  172 + int x = point.getX();
  173 + int y = point.getY();
  174 +
  175 + //生成新的拼图图像
  176 + BufferedImage newJigsawImage = new BufferedImage(jigsawWidth, jigsawHeight, jigsawImage.getType());
  177 + Graphics2D graphics = newJigsawImage.createGraphics();
  178 +
  179 + int bold = 5;
  180 + //如果需要生成RGB格式,需要做如下配置,Transparency 设置透明
  181 + newJigsawImage = graphics.getDeviceConfiguration().createCompatibleImage(jigsawWidth, jigsawHeight, Transparency.TRANSLUCENT);
  182 + // 新建的图像根据模板颜色赋值,源图生成遮罩
  183 + cutByTemplate(originalImage, jigsawImage, newJigsawImage, x, 0);
  184 + if (captchaInterferenceOptions > 0) {
  185 + int position = 0;
  186 + if (originalWidth - x - 5 > jigsawWidth * 2) {
  187 + //在原扣图右边插入干扰图
  188 + position = RandomUtils.getRandomInt(x + jigsawWidth + 5, originalWidth - jigsawWidth);
  189 + } else {
  190 + //在原扣图左边插入干扰图
  191 + position = RandomUtils.getRandomInt(100, x - jigsawWidth - 5);
  192 + }
  193 + while (true) {
  194 + String s = ImageUtils.getslidingBlock();
  195 + if (!jigsawImageBase64.equals(s)) {
  196 + interferenceByTemplate(originalImage, Objects.requireNonNull(ImageUtils.getBase64StrToImage(s)), position, 0);
  197 + break;
  198 + }
  199 + }
  200 + }
  201 + if (captchaInterferenceOptions > 1) {
  202 + while (true) {
  203 + String s = ImageUtils.getslidingBlock();
  204 + if (!jigsawImageBase64.equals(s)) {
  205 + Integer randomInt = RandomUtils.getRandomInt(jigsawWidth, 100 - jigsawWidth);
  206 + interferenceByTemplate(originalImage, Objects.requireNonNull(ImageUtils.getBase64StrToImage(s)),
  207 + randomInt, 0);
  208 + break;
  209 + }
  210 + }
  211 + }
  212 +
  213 +
  214 + // 设置“抗锯齿”的属性
  215 + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  216 + graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
  217 + graphics.drawImage(newJigsawImage, 0, 0, null);
  218 + graphics.dispose();
  219 +
  220 + ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
  221 + ImageIO.write(newJigsawImage, IMAGE_TYPE_PNG, os);//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。
  222 + byte[] jigsawImages = os.toByteArray();
  223 +
  224 + ByteArrayOutputStream oriImagesOs = new ByteArrayOutputStream();//新建流。
  225 + ImageIO.write(originalImage, IMAGE_TYPE_PNG, oriImagesOs);//利用ImageIO类提供的write方法,将bi以jpg图片的数据模式写入流。
  226 + byte[] oriCopyImages = oriImagesOs.toByteArray();
  227 + Base64.Encoder encoder = Base64.getEncoder();
  228 + dataVO.setOriginalImageBase64(encoder.encodeToString(oriCopyImages).replaceAll("\r|\n", ""));
  229 + //point信息不传到前端,只做后端check校验
  230 +// dataVO.setPoint(point);
  231 + dataVO.setJigsawImageBase64(encoder.encodeToString(jigsawImages).replaceAll("\r|\n", ""));
  232 + dataVO.setToken(RandomUtils.getUUID());
  233 + dataVO.setSecretKey(point.getSecretKey());
  234 +// base64StrToImage(encoder.encodeToString(oriCopyImages), "D:\\原图.png");
  235 +// base64StrToImage(encoder.encodeToString(jigsawImages), "D:\\滑动.png");
  236 +
  237 + //将坐标信息存入redis中
  238 + String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken());
  239 + CaptchaServiceFactory.getCache(cacheType).set(codeKey, JsonUtil.toJSONString(point), EXPIRESIN_SECONDS);
  240 + log.debug("token:{},point:{}", dataVO.getToken(), JsonUtil.toJSONString(point));
  241 + return dataVO;
  242 + } catch (Exception e) {
  243 + e.printStackTrace();
  244 + return null;
  245 + }
  246 + }
  247 +
  248 +
  249 + /**
  250 + * 随机生成拼图坐标
  251 + *
  252 + * @param originalWidth
  253 + * @param originalHeight
  254 + * @param jigsawWidth
  255 + * @param jigsawHeight
  256 + * @return
  257 + */
  258 + private static PointVO generateJigsawPoint(int originalWidth, int originalHeight, int jigsawWidth, int jigsawHeight) {
  259 + Random random = new Random();
  260 + int widthDifference = originalWidth - jigsawWidth;
  261 + int heightDifference = originalHeight - jigsawHeight;
  262 + int x, y = 0;
  263 + if (widthDifference <= 0) {
  264 + x = 5;
  265 + } else {
  266 + x = random.nextInt(originalWidth - jigsawWidth - 100) + 100;
  267 + }
  268 + if (heightDifference <= 0) {
  269 + y = 5;
  270 + } else {
  271 + y = random.nextInt(originalHeight - jigsawHeight) + 5;
  272 + }
  273 + String key = null;
  274 + if (captchaAesStatus) {
  275 + key = AESUtil.getKey();
  276 + }
  277 + return new PointVO(x, y, key);
  278 + }
  279 +
  280 + /**
  281 + * @param oriImage 原图
  282 + * @param templateImage 模板图
  283 + * @param newImage 新抠出的小图
  284 + * @param x 随机扣取坐标X
  285 + * @param y 随机扣取坐标y
  286 + * @throws Exception
  287 + */
  288 + private static void cutByTemplate(BufferedImage oriImage, BufferedImage templateImage, BufferedImage newImage, int x, int y) {
  289 + //临时数组遍历用于高斯模糊存周边像素值
  290 + int[][] martrix = new int[3][3];
  291 + int[] values = new int[9];
  292 +
  293 + int xLength = templateImage.getWidth();
  294 + int yLength = templateImage.getHeight();
  295 + // 模板图像宽度
  296 + for (int i = 0; i < xLength; i++) {
  297 + // 模板图片高度
  298 + for (int j = 0; j < yLength; j++) {
  299 + // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
  300 + int rgb = templateImage.getRGB(i, j);
  301 + if (rgb < 0) {
  302 + newImage.setRGB(i, j, oriImage.getRGB(x + i, y + j));
  303 +
  304 + //抠图区域高斯模糊
  305 + readPixel(oriImage, x + i, y + j, values);
  306 + fillMatrix(martrix, values);
  307 + oriImage.setRGB(x + i, y + j, avgMatrix(martrix));
  308 + }
  309 +
  310 + //防止数组越界判断
  311 + if (i == (xLength - 1) || j == (yLength - 1)) {
  312 + continue;
  313 + }
  314 + int rightRgb = templateImage.getRGB(i + 1, j);
  315 + int downRgb = templateImage.getRGB(i, j + 1);
  316 + //描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
  317 + if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0) || (rgb < 0 && downRgb >= 0)) {
  318 + newImage.setRGB(i, j, Color.white.getRGB());
  319 + oriImage.setRGB(x + i, y + j, Color.white.getRGB());
  320 + }
  321 + }
  322 + }
  323 +
  324 + }
  325 +
  326 +
  327 + /**
  328 + * 干扰抠图处理
  329 + *
  330 + * @param oriImage 原图
  331 + * @param templateImage 模板图
  332 + * @param x 随机扣取坐标X
  333 + * @param y 随机扣取坐标y
  334 + * @throws Exception
  335 + */
  336 + private static void interferenceByTemplate(BufferedImage oriImage, BufferedImage templateImage, int x, int y) {
  337 + //临时数组遍历用于高斯模糊存周边像素值
  338 + int[][] martrix = new int[3][3];
  339 + int[] values = new int[9];
  340 +
  341 + int xLength = templateImage.getWidth();
  342 + int yLength = templateImage.getHeight();
  343 + // 模板图像宽度
  344 + for (int i = 0; i < xLength; i++) {
  345 + // 模板图片高度
  346 + for (int j = 0; j < yLength; j++) {
  347 + // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
  348 + int rgb = templateImage.getRGB(i, j);
  349 + if (rgb < 0) {
  350 + //抠图区域高斯模糊
  351 + readPixel(oriImage, x + i, y + j, values);
  352 + fillMatrix(martrix, values);
  353 + oriImage.setRGB(x + i, y + j, avgMatrix(martrix));
  354 + }
  355 + //防止数组越界判断
  356 + if (i == (xLength - 1) || j == (yLength - 1)) {
  357 + continue;
  358 + }
  359 + int rightRgb = templateImage.getRGB(i + 1, j);
  360 + int downRgb = templateImage.getRGB(i, j + 1);
  361 + //描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
  362 + if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0) || (rgb < 0 && downRgb >= 0)) {
  363 + oriImage.setRGB(x + i, y + j, Color.white.getRGB());
  364 + }
  365 + }
  366 + }
  367 +
  368 + }
  369 +
  370 + private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
  371 + int xStart = x - 1;
  372 + int yStart = y - 1;
  373 + int current = 0;
  374 + for (int i = xStart; i < 3 + xStart; i++) {
  375 + for (int j = yStart; j < 3 + yStart; j++) {
  376 + int tx = i;
  377 + if (tx < 0) {
  378 + tx = -tx;
  379 +
  380 + } else if (tx >= img.getWidth()) {
  381 + tx = x;
  382 + }
  383 + int ty = j;
  384 + if (ty < 0) {
  385 + ty = -ty;
  386 + } else if (ty >= img.getHeight()) {
  387 + ty = y;
  388 + }
  389 + pixels[current++] = img.getRGB(tx, ty);
  390 +
  391 + }
  392 + }
  393 + }
  394 +
  395 + private static void fillMatrix(int[][] matrix, int[] values) {
  396 + int filled = 0;
  397 + for (int i = 0; i < matrix.length; i++) {
  398 + int[] x = matrix[i];
  399 + for (int j = 0; j < x.length; j++) {
  400 + x[j] = values[filled++];
  401 + }
  402 + }
  403 + }
  404 +
  405 + private static int avgMatrix(int[][] matrix) {
  406 + int r = 0;
  407 + int g = 0;
  408 + int b = 0;
  409 + for (int i = 0; i < matrix.length; i++) {
  410 + int[] x = matrix[i];
  411 + for (int j = 0; j < x.length; j++) {
  412 + if (j == 1) {
  413 + continue;
  414 + }
  415 + Color c = new Color(x[j]);
  416 + r += c.getRed();
  417 + g += c.getGreen();
  418 + b += c.getBlue();
  419 + }
  420 + }
  421 + return new Color(r / 8, g / 8, b / 8).getRGB();
  422 + }
  423 +
  424 +
  425 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/CaptchaCacheServiceMemImpl.java 0 → 100644
  1 +package com.anji.captcha.service.impl;
  2 +
  3 +import com.anji.captcha.service.CaptchaCacheService;
  4 +import com.anji.captcha.util.CacheUtil;
  5 +
  6 +import java.util.Objects;
  7 +
  8 +/**
  9 + * 对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis,参考service/spring-boot代码示例。
  10 + * 如果应用是单点的,也没有使用redis,那默认使用内存。
  11 + * 内存缓存只适合单节点部署的应用,否则验证码生产与验证在节点之间信息不同步,导致失败。
  12 + *
  13 + * @author lide1202@hotmail.com
  14 + * @Title: 默认使用内存当缓存
  15 + * @date 2020-05-12
  16 + */
  17 +public class CaptchaCacheServiceMemImpl implements CaptchaCacheService {
  18 + @Override
  19 + public void set(String key, String value, long expiresInSeconds) {
  20 +
  21 + CacheUtil.set(key, value, expiresInSeconds);
  22 + }
  23 +
  24 + @Override
  25 + public boolean exists(String key) {
  26 + return CacheUtil.exists(key);
  27 + }
  28 +
  29 + @Override
  30 + public void delete(String key) {
  31 + CacheUtil.delete(key);
  32 + }
  33 +
  34 + @Override
  35 + public String get(String key) {
  36 + return CacheUtil.get(key);
  37 + }
  38 +
  39 + @Override
  40 + public Long increment(String key, long val) {
  41 + Long ret = Long.parseLong(Objects.requireNonNull(CacheUtil.get(key))) + val;
  42 + CacheUtil.set(key, ret + "", 0);
  43 + return ret;
  44 + }
  45 +
  46 + @Override
  47 + public String type() {
  48 + return "local";
  49 + }
  50 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/CaptchaServiceFactory.java 0 → 100644
  1 +package com.anji.captcha.service.impl;
  2 +
  3 +import com.anji.captcha.model.common.Const;
  4 +import com.anji.captcha.service.CaptchaCacheService;
  5 +import com.anji.captcha.service.CaptchaService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +
  8 +import java.util.HashMap;
  9 +import java.util.Map;
  10 +import java.util.Properties;
  11 +import java.util.ServiceLoader;
  12 +
  13 +/**
  14 + * Created by raodeming on 2020/5/26.
  15 + */
  16 +@Slf4j
  17 +public class CaptchaServiceFactory {
  18 +
  19 + public static CaptchaService getInstance(Properties config) {
  20 + //先把所有CaptchaService初始化,通过init方法,实例字体等,add by lide1202@hotmail.com
  21 + /*try{
  22 + for(CaptchaService item: instances.values()){
  23 + item.init(config);
  24 + }
  25 + }catch (Exception e){
  26 + logger.warn("init captchaService fail:{}", e);
  27 + }*/
  28 +
  29 + String captchaType = config.getProperty(Const.CAPTCHA_TYPE, "default");
  30 + CaptchaService ret = instances.get(captchaType);
  31 + if (ret == null) {
  32 + throw new RuntimeException("unsupported-[captcha.type]=" + captchaType);
  33 + }
  34 + ret.init(config);
  35 + return ret;
  36 + }
  37 +
  38 + public static CaptchaCacheService getCache(String cacheType) {
  39 + return cacheService.get(cacheType);
  40 + }
  41 +
  42 + public volatile static Map<String, CaptchaService> instances = new HashMap<>();
  43 + public volatile static Map<String, CaptchaCacheService> cacheService = new HashMap<>();
  44 +
  45 + static {
  46 + ServiceLoader<CaptchaCacheService> cacheServices = ServiceLoader.load(CaptchaCacheService.class);
  47 + for (CaptchaCacheService item : cacheServices) {
  48 + cacheService.put(item.type(), item);
  49 + }
  50 + log.info("supported-captchaCache-service:{}", cacheService.keySet().toString());
  51 + ServiceLoader<CaptchaService> services = ServiceLoader.load(CaptchaService.class);
  52 + for (CaptchaService item : services) {
  53 + instances.put(item.captchaType(), item);
  54 + }
  55 + ;
  56 + log.info("supported-captchaTypes-service:{}", instances.keySet().toString());
  57 + }
  58 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/ClickWordCaptchaServiceImpl.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.service.impl;
  8 +
  9 +import cn.hutool.core.util.StrUtil;
  10 +import com.anji.captcha.model.common.CaptchaTypeEnum;
  11 +import com.anji.captcha.model.common.Const;
  12 +import com.anji.captcha.model.common.RepCodeEnum;
  13 +import com.anji.captcha.model.common.ResponseModel;
  14 +import com.anji.captcha.model.vo.CaptchaVO;
  15 +import com.anji.captcha.model.vo.PointVO;
  16 +import com.anji.captcha.util.*;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +
  19 +import java.awt.*;
  20 +import java.awt.geom.AffineTransform;
  21 +import java.awt.image.BufferedImage;
  22 +import java.util.List;
  23 +import java.util.*;
  24 +
  25 +/**
  26 + * 点选文字验证码
  27 + * <p>
  28 + * Created by raodeming on 2019/12/25.
  29 + */
  30 +@Slf4j
  31 +public class ClickWordCaptchaServiceImpl extends AbstractCaptchaService {
  32 +
  33 + public static String HAN_ZI = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6";
  34 +
  35 + protected static String clickWordFontStr = "NotoSerif-Light.ttf";
  36 +
  37 + protected Font clickWordFont;//点选文字字体
  38 +
  39 + @Override
  40 + public String captchaType() {
  41 + return CaptchaTypeEnum.CLICKWORD.getCodeValue();
  42 + }
  43 +
  44 + @Override
  45 + public void init(Properties config) {
  46 + super.init(config);
  47 + clickWordFontStr = config.getProperty(Const.CAPTCHA_FONT_TYPE, "SourceHanSansCN-Normal.otf");
  48 + try {
  49 + int size = Integer.parseInt(config.getProperty(Const.CAPTCHA_FONT_SIZE,HAN_ZI_SIZE+""));
  50 +
  51 + if (clickWordFontStr.toLowerCase().endsWith(".ttf")
  52 + || clickWordFontStr.toLowerCase().endsWith(".ttc")
  53 + || clickWordFontStr.toLowerCase().endsWith(".otf")) {
  54 + this.clickWordFont = Font.createFont(Font.TRUETYPE_FONT,
  55 + Objects.requireNonNull(getClass().getResourceAsStream("/fonts/" + clickWordFontStr)))
  56 + .deriveFont(Font.BOLD, size);
  57 + } else {
  58 + int style = Integer.parseInt(config.getProperty(Const.CAPTCHA_FONT_STYLE,Font.BOLD+""));
  59 + this.clickWordFont = new Font(clickWordFontStr, style, size);
  60 + }
  61 + } catch (Exception ex) {
  62 + log.error("load font error:{}", ex);
  63 + }
  64 + this.wordTotalCount = Integer.parseInt(config.getProperty(Const.CAPTCHA_WORD_COUNT,"4"));
  65 + }
  66 +
  67 + @Override
  68 + public void destroy(Properties config) {
  69 + log.info("start-clear-history-data-", captchaType());
  70 + }
  71 +
  72 + @Override
  73 + public ResponseModel get(CaptchaVO captchaVO) {
  74 + ResponseModel r = super.get(captchaVO);
  75 + if (!validatedReq(r)) {
  76 + return r;
  77 + }
  78 + BufferedImage bufferedImage = ImageUtils.getPicClick();
  79 + if (null == bufferedImage) {
  80 + log.error("滑动底图未初始化成功,请检查路径");
  81 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
  82 + }
  83 + CaptchaVO imageData = getImageData(bufferedImage);
  84 + if (imageData == null
  85 + || StrUtil.isBlank(imageData.getOriginalImageBase64())) {
  86 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_ERROR);
  87 + }
  88 + return ResponseModel.successData(imageData);
  89 + }
  90 +
  91 + @Override
  92 + public ResponseModel check(CaptchaVO captchaVO) {
  93 + ResponseModel r = super.check(captchaVO);
  94 + if (!validatedReq(r)) {
  95 + return r;
  96 + }
  97 + //取坐标信息
  98 + String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
  99 + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
  100 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
  101 + }
  102 + String s = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
  103 + //验证码只用一次,即刻失效
  104 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
  105 + List<PointVO> point = null;
  106 + List<PointVO> point1 = null;
  107 + String pointJson = null;
  108 + /**
  109 + * [
  110 + * {
  111 + * "x": 85.0,
  112 + * "y": 34.0
  113 + * },
  114 + * {
  115 + * "x": 129.0,
  116 + * "y": 56.0
  117 + * },
  118 + * {
  119 + * "x": 233.0,
  120 + * "y": 27.0
  121 + * }
  122 + * ]
  123 + */
  124 + try {
  125 + point = JsonUtil.parseArray(s, PointVO.class);
  126 + //aes解密
  127 + pointJson = decrypt(captchaVO.getPointJson(), point.get(0).getSecretKey());
  128 + point1 = JsonUtil.parseArray(pointJson, PointVO.class);
  129 + } catch (Exception e) {
  130 + log.error("验证码坐标解析失败", e);
  131 + afterValidateFail(captchaVO);
  132 + return ResponseModel.errorMsg(e.getMessage());
  133 + }
  134 + for (int i = 0; i < point.size(); i++) {
  135 + if (point.get(i).x - HAN_ZI_SIZE > point1.get(i).x
  136 + || point1.get(i).x > point.get(i).x + HAN_ZI_SIZE
  137 + || point.get(i).y - HAN_ZI_SIZE > point1.get(i).y
  138 + || point1.get(i).y > point.get(i).y + HAN_ZI_SIZE) {
  139 + afterValidateFail(captchaVO);
  140 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
  141 + }
  142 + }
  143 + //校验成功,将信息存入缓存
  144 + String secretKey = point.get(0).getSecretKey();
  145 + String value = null;
  146 + try {
  147 + value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(pointJson), secretKey);
  148 + } catch (Exception e) {
  149 + log.error("AES加密失败", e);
  150 + afterValidateFail(captchaVO);
  151 + return ResponseModel.errorMsg(e.getMessage());
  152 + }
  153 + String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
  154 + CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE);
  155 + captchaVO.setResult(true);
  156 + captchaVO.resetClientFlag();
  157 + return ResponseModel.successData(captchaVO);
  158 + }
  159 +
  160 + @Override
  161 + public ResponseModel verification(CaptchaVO captchaVO) {
  162 + /*if (captchaVO == null) {
  163 + return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
  164 + }
  165 + if (StrUtil.isEmpty(captchaVO.getCaptchaVerification())) {
  166 + return RepCodeEnum.NULL_ERROR.parseError("captchaVerification");
  167 + }*/
  168 + ResponseModel r = super.verification(captchaVO);
  169 + if (!validatedReq(r)) {
  170 + return r;
  171 + }
  172 + try {
  173 + String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
  174 + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
  175 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
  176 + }
  177 + //二次校验取值后,即刻失效
  178 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
  179 + } catch (Exception e) {
  180 + log.error("验证码坐标解析失败", e);
  181 + return ResponseModel.errorMsg(e.getMessage());
  182 + }
  183 + return ResponseModel.success();
  184 + }
  185 +
  186 + public int getWordTotalCount() {
  187 + return wordTotalCount;
  188 + }
  189 +
  190 + public void setWordTotalCount(int wordTotalCount) {
  191 + this.wordTotalCount = wordTotalCount;
  192 + }
  193 +
  194 + public boolean isFontColorRandom() {
  195 + return fontColorRandom;
  196 + }
  197 +
  198 + public void setFontColorRandom(boolean fontColorRandom) {
  199 + this.fontColorRandom = fontColorRandom;
  200 + }
  201 +
  202 + /**
  203 + * 点选文字 字体总个数
  204 + */
  205 + private int wordTotalCount = 4;
  206 + /**
  207 + * 点选文字 字体颜色是否随机
  208 + */
  209 + private boolean fontColorRandom = Boolean.TRUE;
  210 +
  211 + private CaptchaVO getImageData(BufferedImage backgroundImage) {
  212 + CaptchaVO dataVO = new CaptchaVO();
  213 + List<String> wordList = new ArrayList<String>();
  214 + List<PointVO> pointList = new ArrayList();
  215 +
  216 + Graphics backgroundGraphics = backgroundImage.getGraphics();
  217 + int width = backgroundImage.getWidth();
  218 + int height = backgroundImage.getHeight();
  219 +
  220 + int wordCount = getWordTotalCount();
  221 + //定义随机1到arr.length某一个字不参与校验
  222 + int num = RandomUtils.getRandomInt(1, wordCount);
  223 + Set<String> currentWords = getRandomWords(wordCount);
  224 + String secretKey = null;
  225 + if (captchaAesStatus) {
  226 + secretKey = AESUtil.getKey();
  227 + }
  228 + /*for (int i = 0; i < wordCount; i++) {
  229 + String word;
  230 + do {
  231 + word = RandomUtils.getRandomHan(HAN_ZI);
  232 + currentWords.add(word);
  233 + } while (!currentWords.contains(word));*/
  234 + int i = 0;
  235 + for (String word : currentWords) {
  236 + //随机字体坐标
  237 + PointVO point = randomWordPoint(width, height, i, wordCount);
  238 + point.setSecretKey(secretKey);
  239 + //随机字体颜色
  240 + if (isFontColorRandom()) {
  241 + backgroundGraphics.setColor(new Color(RandomUtils.getRandomInt(1, 255),
  242 + RandomUtils.getRandomInt(1, 255), RandomUtils.getRandomInt(1, 255)));
  243 + } else {
  244 + backgroundGraphics.setColor(Color.BLACK);
  245 + }
  246 + //设置角度
  247 + AffineTransform affineTransform = new AffineTransform();
  248 + affineTransform.rotate(Math.toRadians(RandomUtils.getRandomInt(-45, 45)), 0, 0);
  249 + Font rotatedFont = clickWordFont.deriveFont(affineTransform);
  250 + backgroundGraphics.setFont(rotatedFont);
  251 + backgroundGraphics.drawString(word, point.getX(), point.getY());
  252 +
  253 + if ((num - 1) != i) {
  254 + wordList.add(word);
  255 + pointList.add(point);
  256 + }
  257 + i++;
  258 + }
  259 +
  260 + backgroundGraphics.setFont(waterMarkFont);
  261 + backgroundGraphics.setColor(Color.white);
  262 + backgroundGraphics.drawString(waterMark, width - getEnOrChLength(waterMark), height - (HAN_ZI_SIZE / 2) + 7);
  263 +
  264 + //创建合并图片
  265 + BufferedImage combinedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
  266 + Graphics combinedGraphics = combinedImage.getGraphics();
  267 + combinedGraphics.drawImage(backgroundImage, 0, 0, null);
  268 +
  269 + dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(backgroundImage).replaceAll("\r|\n", ""));
  270 + //pointList信息不传到前端,只做后端check校验
  271 + //dataVO.setPointList(pointList);
  272 + dataVO.setWordList(wordList);
  273 + dataVO.setToken(RandomUtils.getUUID());
  274 + dataVO.setSecretKey(secretKey);
  275 + //将坐标信息存入redis中
  276 + String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken());
  277 + CaptchaServiceFactory.getCache(cacheType).set(codeKey, JsonUtil.toJSONString(pointList), EXPIRESIN_SECONDS);
  278 +// base64StrToImage(getImageToBase64Str(backgroundImage), "D:\\点击.png");
  279 + return dataVO;
  280 + }
  281 +
  282 + private Set<String> getRandomWords(int wordCount) {
  283 + Set<String> words = new HashSet<>();
  284 + int size = HAN_ZI.length();
  285 + for (; ; ) {
  286 + String t = HAN_ZI.charAt(RandomUtils.getRandomInt(size)) + "";
  287 + words.add(t);
  288 + if (words.size() >= wordCount) {
  289 + break;
  290 + }
  291 + }
  292 + return words;
  293 + }
  294 +
  295 + /**
  296 + * 随机字体循环排序下标
  297 + *
  298 + * @param imageWidth 图片宽度
  299 + * @param imageHeight 图片高度
  300 + * @param wordSortIndex 字体循环排序下标(i)
  301 + * @param wordCount 字数量
  302 + * @return
  303 + */
  304 + private static PointVO randomWordPoint(int imageWidth, int imageHeight, int wordSortIndex, int wordCount) {
  305 + int avgWidth = imageWidth / (wordCount + 1);
  306 + int x, y;
  307 + if (avgWidth < HAN_ZI_SIZE_HALF) {
  308 + x = RandomUtils.getRandomInt(1 + HAN_ZI_SIZE_HALF, imageWidth);
  309 + } else {
  310 + if (wordSortIndex == 0) {
  311 + x = RandomUtils.getRandomInt(1 + HAN_ZI_SIZE_HALF, avgWidth * (wordSortIndex + 1) - HAN_ZI_SIZE_HALF);
  312 + } else {
  313 + x = RandomUtils.getRandomInt(avgWidth * wordSortIndex + HAN_ZI_SIZE_HALF, avgWidth * (wordSortIndex + 1) - HAN_ZI_SIZE_HALF);
  314 + }
  315 + }
  316 + y = RandomUtils.getRandomInt(HAN_ZI_SIZE, imageHeight - HAN_ZI_SIZE);
  317 + return new PointVO(x, y, null);
  318 + }
  319 +
  320 +
  321 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/DefaultCaptchaServiceImpl.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.service.impl;
  8 +
  9 +import cn.hutool.core.util.StrUtil;
  10 +import com.anji.captcha.model.common.RepCodeEnum;
  11 +import com.anji.captcha.model.common.ResponseModel;
  12 +import com.anji.captcha.model.vo.CaptchaVO;
  13 +import com.anji.captcha.service.CaptchaService;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +
  16 +import java.util.Properties;
  17 +
  18 +/**
  19 + * Created by raodeming on 2019/12/25.
  20 + */
  21 +@Slf4j
  22 +public class DefaultCaptchaServiceImpl extends AbstractCaptchaService{
  23 +
  24 + @Override
  25 + public String captchaType() {
  26 + return "default";
  27 + }
  28 +
  29 + @Override
  30 + public void init(Properties config) {
  31 + for (String s : CaptchaServiceFactory.instances.keySet()) {
  32 + if(captchaType().equals(s)){
  33 + continue;
  34 + }
  35 + getService(s).init(config);
  36 + }
  37 + }
  38 +
  39 + @Override
  40 + public void destroy(Properties config) {
  41 + for (String s : CaptchaServiceFactory.instances.keySet()) {
  42 + if(captchaType().equals(s)){
  43 + continue;
  44 + }
  45 + getService(s).destroy(config);
  46 + }
  47 + }
  48 +
  49 + private CaptchaService getService(String captchaType){
  50 + return CaptchaServiceFactory.instances.get(captchaType);
  51 + }
  52 +
  53 + @Override
  54 + public ResponseModel get(CaptchaVO captchaVO) {
  55 + if (captchaVO == null) {
  56 + return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
  57 + }
  58 + if (StrUtil.isEmpty(captchaVO.getCaptchaType())) {
  59 + return RepCodeEnum.NULL_ERROR.parseError("类型");
  60 + }
  61 + return getService(captchaVO.getCaptchaType()).get(captchaVO);
  62 + }
  63 +
  64 + @Override
  65 + public ResponseModel check(CaptchaVO captchaVO) {
  66 + if (captchaVO == null) {
  67 + return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
  68 + }
  69 + if (StrUtil.isEmpty(captchaVO.getCaptchaType())) {
  70 + return RepCodeEnum.NULL_ERROR.parseError("类型");
  71 + }
  72 + if (StrUtil.isEmpty(captchaVO.getToken())) {
  73 + return RepCodeEnum.NULL_ERROR.parseError("token");
  74 + }
  75 + return getService(captchaVO.getCaptchaType()).check(captchaVO);
  76 + }
  77 +
  78 + @Override
  79 + public ResponseModel verification(CaptchaVO captchaVO) {
  80 + if (captchaVO == null) {
  81 + return RepCodeEnum.NULL_ERROR.parseError("captchaVO");
  82 + }
  83 + if (StrUtil.isEmpty(captchaVO.getCaptchaVerification())) {
  84 + return RepCodeEnum.NULL_ERROR.parseError("二次校验参数");
  85 + }
  86 + try {
  87 + String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
  88 + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
  89 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
  90 + }
  91 + //二次校验取值后,即刻失效
  92 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
  93 + } catch (Exception e) {
  94 + log.error("验证码坐标解析失败", e);
  95 + return ResponseModel.errorMsg(e.getMessage());
  96 + }
  97 + return ResponseModel.success();
  98 + }
  99 +
  100 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/FrequencyLimitHandler.java 0 → 100644
  1 +package com.anji.captcha.service.impl;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import com.anji.captcha.model.common.Const;
  5 +import com.anji.captcha.model.common.RepCodeEnum;
  6 +import com.anji.captcha.model.common.ResponseModel;
  7 +import com.anji.captcha.model.vo.CaptchaVO;
  8 +import com.anji.captcha.service.CaptchaCacheService;
  9 +
  10 +import java.util.Objects;
  11 +import java.util.Properties;
  12 +
  13 +/**
  14 + * @author WongBin
  15 + * @date 2021/1/21
  16 + */
  17 +public interface FrequencyLimitHandler {
  18 +
  19 + String LIMIT_KEY = "AJ.CAPTCHA.REQ.LIMIT-%s-%s";
  20 +
  21 + /**
  22 + * get 接口限流
  23 + *
  24 + * @param captchaVO
  25 + * @return
  26 + */
  27 + ResponseModel validateGet(CaptchaVO captchaVO);
  28 +
  29 + /**
  30 + * check接口限流
  31 + *
  32 + * @param captchaVO
  33 + * @return
  34 + */
  35 + ResponseModel validateCheck(CaptchaVO captchaVO);
  36 +
  37 + /**
  38 + * verify接口限流
  39 + *
  40 + * @param captchaVO
  41 + * @return
  42 + */
  43 + ResponseModel validateVerify(CaptchaVO captchaVO);
  44 +
  45 +
  46 + /***
  47 + * 验证码接口限流:
  48 + * 客户端ClientUid 组件实例化时设置一次,如:场景码+UUID,客户端可以本地缓存,保证一个组件只有一个值
  49 + *
  50 + * 针对同一个客户端的请求,做如下限制:
  51 + * get
  52 + * 1分钟内check失败5次,锁定5分钟
  53 + * 1分钟内不能超过120次。
  54 + * check:
  55 + * 1分钟内不超过600次
  56 + * verify:
  57 + * 1分钟内不超过600次
  58 + */
  59 + class DefaultLimitHandler implements FrequencyLimitHandler {
  60 + private Properties config;
  61 + private CaptchaCacheService cacheService;
  62 +
  63 + public DefaultLimitHandler(Properties config, CaptchaCacheService cacheService) {
  64 + this.config = config;
  65 + this.cacheService = cacheService;
  66 + }
  67 +
  68 + private String getClientCId(CaptchaVO input, String type) {
  69 + return String.format(LIMIT_KEY, type, input.getClientUid());
  70 + }
  71 +
  72 + @Override
  73 + public ResponseModel validateGet(CaptchaVO d) {
  74 + // 无客户端身份标识,不限制
  75 + if (StrUtil.isEmpty(d.getClientUid())) {
  76 + return null;
  77 + }
  78 + String getKey = getClientCId(d, "GET");
  79 + String lockKey = getClientCId(d, "LOCK");
  80 + // 失败次数过多,锁定
  81 + if (Objects.nonNull(cacheService.get(lockKey))) {
  82 + return ResponseModel.errorMsg(RepCodeEnum.API_REQ_LOCK_GET_ERROR);
  83 + }
  84 + String getCnts = cacheService.get(getKey);
  85 + if (Objects.isNull(getCnts)) {
  86 + cacheService.set(getKey, "1", 60);
  87 + getCnts = "1";
  88 + }
  89 + cacheService.increment(getKey, 1);
  90 + // 1分钟内请求次数过多
  91 + if (Long.parseLong(getCnts) > Long.parseLong(config.getProperty(Const.REQ_GET_MINUTE_LIMIT, "120"))) {
  92 + return ResponseModel.errorMsg(RepCodeEnum.API_REQ_LIMIT_GET_ERROR);
  93 + }
  94 +
  95 + // 失败次数验证
  96 + String failKey = getClientCId(d, "FAIL");
  97 + String failCnts = cacheService.get(failKey);
  98 + // 没有验证失败,通过校验
  99 + if (Objects.isNull(failCnts)) {
  100 + return null;
  101 + }
  102 + // 1分钟内失败5次
  103 + if (Long.parseLong(failCnts) > Long.parseLong(config.getProperty(Const.REQ_GET_LOCK_LIMIT, "5"))) {
  104 + // get接口锁定5分钟
  105 + cacheService.set(lockKey, "1", Long.parseLong(config.getProperty(Const.REQ_GET_LOCK_SECONDS, "300")));
  106 + return ResponseModel.errorMsg(RepCodeEnum.API_REQ_LOCK_GET_ERROR);
  107 + }
  108 + return null;
  109 + }
  110 +
  111 + @Override
  112 + public ResponseModel validateCheck(CaptchaVO d) {
  113 + // 无客户端身份标识,不限制
  114 + if (StrUtil.isEmpty(d.getClientUid())) {
  115 + return null;
  116 + }
  117 + /*String getKey = getClientCId(d, "GET");
  118 + if(Objects.isNull(cacheService.get(getKey))){
  119 + return ResponseModel.errorMsg(RepCodeEnum.API_REQ_INVALID);
  120 + }*/
  121 + String key = getClientCId(d, "CHECK");
  122 + String v = cacheService.get(key);
  123 + if (Objects.isNull(v)) {
  124 + cacheService.set(key, "1", 60);
  125 + v = "1";
  126 + }
  127 + cacheService.increment(key, 1);
  128 + if (Long.parseLong(v) > Long.parseLong(config.getProperty(Const.REQ_CHECK_MINUTE_LIMIT, "600"))) {
  129 + return ResponseModel.errorMsg(RepCodeEnum.API_REQ_LIMIT_CHECK_ERROR);
  130 + }
  131 + return null;
  132 + }
  133 +
  134 + @Override
  135 + public ResponseModel validateVerify(CaptchaVO d) {
  136 + /*String getKey = getClientCId(d, "GET");
  137 + if(Objects.isNull(cacheService.get(getKey))){
  138 + return ResponseModel.errorMsg(RepCodeEnum.API_REQ_INVALID);
  139 + }*/
  140 + String key = getClientCId(d, "VERIFY");
  141 + String v = cacheService.get(key);
  142 + if (Objects.isNull(v)) {
  143 + cacheService.set(key, "1", 60);
  144 + v = "1";
  145 + }
  146 + cacheService.increment(key, 1);
  147 + if (Long.parseLong(v) > Long.parseLong(config.getProperty(Const.REQ_VALIDATE_MINUTE_LIMIT, "600"))) {
  148 + return ResponseModel.errorMsg(RepCodeEnum.API_REQ_LIMIT_VERIFY_ERROR);
  149 + }
  150 + return null;
  151 + }
  152 + }
  153 +
  154 +}
0 155 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/util/AESUtil.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.util;
  8 +
  9 +
  10 +import cn.hutool.core.util.StrUtil;
  11 +
  12 +import javax.crypto.Cipher;
  13 +import javax.crypto.KeyGenerator;
  14 +import javax.crypto.spec.SecretKeySpec;
  15 +import java.math.BigInteger;
  16 +import java.nio.charset.StandardCharsets;
  17 +import java.util.Base64;
  18 +
  19 +
  20 +public class AESUtil {
  21 + //算法
  22 + private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
  23 +
  24 + /**
  25 + * 获取随机key
  26 + *
  27 + * @return
  28 + */
  29 + public static String getKey() {
  30 + return RandomUtils.getRandomString(16);
  31 + }
  32 +
  33 +
  34 + /**
  35 + * 将byte[]转为各种进制的字符串
  36 + *
  37 + * @param bytes byte[]
  38 + * @param radix 可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,超出范围后变为10进制
  39 + * @return 转换后的字符串
  40 + */
  41 + public static String binary(byte[] bytes, int radix) {
  42 + return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数
  43 + }
  44 +
  45 + /**
  46 + * base 64 encode
  47 + *
  48 + * @param bytes 待编码的byte[]
  49 + * @return 编码后的base 64 code
  50 + */
  51 + public static String base64Encode(byte[] bytes) {
  52 + //return Base64.encodeBase64String(bytes);
  53 + return Base64.getEncoder().encodeToString(bytes);
  54 + }
  55 +
  56 + /**
  57 + * base 64 decode
  58 + *
  59 + * @param base64Code 待解码的base 64 code
  60 + * @return 解码后的byte[]
  61 + * @throws Exception
  62 + */
  63 + public static byte[] base64Decode(String base64Code) throws Exception {
  64 + Base64.Decoder decoder = Base64.getDecoder();
  65 + return StrUtil.isEmpty(base64Code) ? null : decoder.decode(base64Code);
  66 + }
  67 +
  68 +
  69 + /**
  70 + * AES加密
  71 + *
  72 + * @param content 待加密的内容
  73 + * @param encryptKey 加密密钥
  74 + * @return 加密后的byte[]
  75 + * @throws Exception
  76 + */
  77 + public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
  78 + KeyGenerator kgen = KeyGenerator.getInstance("AES");
  79 + kgen.init(128);
  80 + Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
  81 + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
  82 +
  83 + return cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
  84 + }
  85 +
  86 +
  87 + /**
  88 + * AES加密为base 64 code
  89 + *
  90 + * @param content 待加密的内容
  91 + * @param encryptKey 加密密钥
  92 + * @return 加密后的base 64 code
  93 + * @throws Exception
  94 + */
  95 + public static String aesEncrypt(String content, String encryptKey) throws Exception {
  96 + if (StrUtil.isBlank(encryptKey)) {
  97 + return content;
  98 + }
  99 + return base64Encode(aesEncryptToBytes(content, encryptKey));
  100 + }
  101 +
  102 + /**
  103 + * AES解密
  104 + *
  105 + * @param encryptBytes 待解密的byte[]
  106 + * @param decryptKey 解密密钥
  107 + * @return 解密后的String
  108 + * @throws Exception
  109 + */
  110 + public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
  111 + KeyGenerator kgen = KeyGenerator.getInstance("AES");
  112 + kgen.init(128);
  113 +
  114 + Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
  115 + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
  116 + byte[] decryptBytes = cipher.doFinal(encryptBytes);
  117 + return new String(decryptBytes);
  118 + }
  119 +
  120 +
  121 + /**
  122 + * 将base 64 code AES解密
  123 + *
  124 + * @param encryptStr 待解密的base 64 code
  125 + * @param decryptKey 解密密钥
  126 + * @return 解密后的string
  127 + * @throws Exception
  128 + */
  129 + public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
  130 + if (StrUtil.isBlank(decryptKey)) {
  131 + return encryptStr;
  132 + }
  133 + return StrUtil.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
  134 + }
  135 +
  136 + /**
  137 + * 测试
  138 + */
  139 + public static void main(String[] args) throws Exception {
  140 + String randomString = RandomUtils.getRandomString(16);
  141 + String content = "hahhahaahhahni";
  142 + System.out.println("加密前:" + content);
  143 + System.out.println("加密密钥和解密密钥:" + randomString);
  144 + String encrypt = aesEncrypt(content, randomString);
  145 + System.out.println("加密后:" + encrypt);
  146 + String decrypt = aesDecrypt(encrypt, randomString);
  147 + System.out.println("解密后:" + decrypt);
  148 + }
  149 +
  150 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/util/CacheUtil.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.util;
  8 +
  9 +import org.slf4j.Logger;
  10 +import org.slf4j.LoggerFactory;
  11 +
  12 +import java.util.Map;
  13 +import java.util.concurrent.*;
  14 +
  15 +public final class CacheUtil {
  16 + private static final Logger logger = LoggerFactory.getLogger(CacheUtil.class);
  17 +
  18 + private static final Map<String, Object> CACHE_MAP = new ConcurrentHashMap<String, Object>();
  19 +
  20 + /**
  21 + * 缓存最大个数
  22 + */
  23 + private static Integer CACHE_MAX_NUMBER = 1000;
  24 +
  25 + /**
  26 + * 初始化
  27 + *
  28 + * @param cacheMaxNumber 缓存最大个数
  29 + * @param second 定时任务 秒执行清除过期缓存
  30 + */
  31 + public static void init(int cacheMaxNumber, long second) {
  32 + CACHE_MAX_NUMBER = cacheMaxNumber;
  33 + if (second > 0L) {
  34 + /*Timer timer = new Timer();
  35 + timer.schedule(new TimerTask() {
  36 + @Override
  37 + public void run() {
  38 + refresh();
  39 + }
  40 + }, 0, second * 1000);*/
  41 + ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
  42 + @Override
  43 + public Thread newThread(Runnable r) {
  44 + return new Thread(r, "thd-captcha-cache-clean");
  45 + }
  46 + }, new ThreadPoolExecutor.CallerRunsPolicy());
  47 + scheduledExecutor.scheduleAtFixedRate(new Runnable() {
  48 + @Override
  49 + public void run() {
  50 + refresh();
  51 + }
  52 + }, 10, second, TimeUnit.SECONDS);
  53 + }
  54 + }
  55 +
  56 + /**
  57 + * 缓存刷新,清除过期数据
  58 + */
  59 + public static void refresh() {
  60 + logger.debug("local缓存刷新,清除过期数据");
  61 + for (String key : CACHE_MAP.keySet()) {
  62 + exists(key);
  63 + }
  64 + }
  65 +
  66 +
  67 + public static void set(String key, String value, long expiresInSeconds) {
  68 + //设置阈值,达到即clear缓存
  69 + if (CACHE_MAP.size() > CACHE_MAX_NUMBER * 2) {
  70 + logger.info("CACHE_MAP达到阈值,clear map");
  71 + clear();
  72 + }
  73 + CACHE_MAP.put(key, value);
  74 + if (expiresInSeconds > 0) {
  75 + CACHE_MAP.put(key + "_HoldTime", System.currentTimeMillis() + expiresInSeconds * 1000);//缓存失效时间
  76 + }
  77 + }
  78 +
  79 + public static void delete(String key) {
  80 + CACHE_MAP.remove(key);
  81 + CACHE_MAP.remove(key + "_HoldTime");
  82 + }
  83 +
  84 + public static boolean exists(String key) {
  85 + Long cacheHoldTime = (Long) CACHE_MAP.get(key + "_HoldTime");
  86 + if (cacheHoldTime == null || cacheHoldTime == 0L) {
  87 + return false;
  88 + }
  89 + if (cacheHoldTime < System.currentTimeMillis()) {
  90 + delete(key);
  91 + return false;
  92 + }
  93 + return true;
  94 + }
  95 +
  96 +
  97 + public static String get(String key) {
  98 + if (exists(key)) {
  99 + return (String) CACHE_MAP.get(key);
  100 + }
  101 + return null;
  102 + }
  103 +
  104 + /**
  105 + * 删除所有缓存
  106 + */
  107 + public static void clear() {
  108 + logger.debug("have clean all key !");
  109 + CACHE_MAP.clear();
  110 + }
  111 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/util/FileCopyUtils.java 0 → 100644
  1 +package com.anji.captcha.util;
  2 +//
  3 +// Source code recreated from a .class file by IntelliJ IDEA
  4 +// (powered by Fernflower decompiler)
  5 +//
  6 +
  7 +import java.io.*;
  8 +import java.nio.file.Files;
  9 +
  10 +public abstract class FileCopyUtils {
  11 + public static final int BUFFER_SIZE = 4096;
  12 +
  13 + public FileCopyUtils() {
  14 + }
  15 +
  16 + public static int copy(File in, File out) throws IOException {
  17 + return copy(Files.newInputStream(in.toPath()), Files.newOutputStream(out.toPath()));
  18 + }
  19 +
  20 + public static void copy(byte[] in, File out) throws IOException {
  21 + copy((InputStream) (new ByteArrayInputStream(in)), (OutputStream) Files.newOutputStream(out.toPath()));
  22 + }
  23 +
  24 + public static byte[] copyToByteArray(File in) throws IOException {
  25 + return copyToByteArray(Files.newInputStream(in.toPath()));
  26 + }
  27 +
  28 + public static int copy(InputStream in, OutputStream out) throws IOException {
  29 + int var2;
  30 + try {
  31 + var2 = StreamUtils.copy(in, out);
  32 + } finally {
  33 + try {
  34 + in.close();
  35 + } catch (IOException var12) {
  36 + }
  37 +
  38 + try {
  39 + out.close();
  40 + } catch (IOException var11) {
  41 + }
  42 +
  43 + }
  44 +
  45 + return var2;
  46 + }
  47 +
  48 + public static void copy(byte[] in, OutputStream out) throws IOException {
  49 + try {
  50 + out.write(in);
  51 + } finally {
  52 + try {
  53 + out.close();
  54 + } catch (IOException var8) {
  55 + }
  56 +
  57 + }
  58 +
  59 + }
  60 +
  61 + public static byte[] copyToByteArray(InputStream in) throws IOException {
  62 + if (in == null) {
  63 + return new byte[0];
  64 + } else {
  65 + ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
  66 + copy((InputStream) in, (OutputStream) out);
  67 + return out.toByteArray();
  68 + }
  69 + }
  70 +
  71 + public static int copy(Reader in, Writer out) throws IOException {
  72 + try {
  73 + int byteCount = 0;
  74 + char[] buffer = new char[4096];
  75 +
  76 + int bytesRead;
  77 + for (boolean var4 = true; (bytesRead = in.read(buffer)) != -1; byteCount += bytesRead) {
  78 + out.write(buffer, 0, bytesRead);
  79 + }
  80 +
  81 + out.flush();
  82 + return byteCount;
  83 + } finally {
  84 + try {
  85 + in.close();
  86 + } catch (IOException var15) {
  87 + }
  88 +
  89 + try {
  90 + out.close();
  91 + } catch (IOException var14) {
  92 + }
  93 +
  94 + }
  95 + }
  96 +
  97 + public static void copy(String in, Writer out) throws IOException {
  98 + try {
  99 + out.write(in);
  100 + } finally {
  101 + try {
  102 + out.close();
  103 + } catch (IOException var8) {
  104 + }
  105 +
  106 + }
  107 +
  108 + }
  109 +
  110 + public static String copyToString(Reader in) throws IOException {
  111 + if (in == null) {
  112 + return "";
  113 + } else {
  114 + StringWriter out = new StringWriter();
  115 + copy((Reader) in, (Writer) out);
  116 + return out.toString();
  117 + }
  118 + }
  119 +}
  120 +
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/util/ImageUtils.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.util;
  8 +
  9 +import cn.hutool.core.util.StrUtil;
  10 +import com.anji.captcha.model.common.CaptchaBaseMapEnum;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.util.Base64Utils;
  13 +
  14 +import javax.imageio.ImageIO;
  15 +import java.awt.image.BufferedImage;
  16 +import java.io.*;
  17 +import java.util.Arrays;
  18 +import java.util.Base64;
  19 +import java.util.HashMap;
  20 +import java.util.Map;
  21 +import java.util.concurrent.ConcurrentHashMap;
  22 +
  23 +@Slf4j
  24 +public class ImageUtils {
  25 + private static Map<String, String> originalCacheMap = new ConcurrentHashMap(); //滑块底图
  26 + private static Map<String, String> slidingBlockCacheMap = new ConcurrentHashMap(); //滑块
  27 + private static Map<String, String> picClickCacheMap = new ConcurrentHashMap(); //点选文字
  28 + private static Map<String, String[]> fileNameMap = new ConcurrentHashMap<>();
  29 +
  30 + public static void cacheImage(String captchaOriginalPathJigsaw, String captchaOriginalPathClick) {
  31 + //滑动拼图
  32 + if (StrUtil.isBlank(captchaOriginalPathJigsaw)) {
  33 + originalCacheMap.putAll(getResourcesImagesFile("defaultImages/jigsaw/original"));
  34 + slidingBlockCacheMap.putAll(getResourcesImagesFile("defaultImages/jigsaw/slidingBlock"));
  35 + } else {
  36 + originalCacheMap.putAll(getImagesFile(captchaOriginalPathJigsaw + File.separator + "original"));
  37 + slidingBlockCacheMap.putAll(getImagesFile(captchaOriginalPathJigsaw + File.separator + "slidingBlock"));
  38 + }
  39 + //点选文字
  40 + if (StrUtil.isBlank(captchaOriginalPathClick)) {
  41 + picClickCacheMap.putAll(getResourcesImagesFile("defaultImages/pic-click"));
  42 + } else {
  43 + picClickCacheMap.putAll(getImagesFile(captchaOriginalPathClick));
  44 + }
  45 + fileNameMap.put(CaptchaBaseMapEnum.ORIGINAL.getCodeValue(), originalCacheMap.keySet().toArray(new String[0]));
  46 + fileNameMap.put(CaptchaBaseMapEnum.SLIDING_BLOCK.getCodeValue(), slidingBlockCacheMap.keySet().toArray(new String[0]));
  47 + fileNameMap.put(CaptchaBaseMapEnum.PIC_CLICK.getCodeValue(), picClickCacheMap.keySet().toArray(new String[0]));
  48 + log.info("初始化底图:{}", JsonUtil.toJSONString(fileNameMap));
  49 + }
  50 +
  51 + public static void cacheBootImage(Map<String, String> originalMap, Map<String, String> slidingBlockMap, Map<String, String> picClickMap) {
  52 + originalCacheMap.putAll(originalMap);
  53 + slidingBlockCacheMap.putAll(slidingBlockMap);
  54 + picClickCacheMap.putAll(picClickMap);
  55 + fileNameMap.put(CaptchaBaseMapEnum.ORIGINAL.getCodeValue(), originalCacheMap.keySet().toArray(new String[0]));
  56 + fileNameMap.put(CaptchaBaseMapEnum.SLIDING_BLOCK.getCodeValue(), slidingBlockCacheMap.keySet().toArray(new String[0]));
  57 + fileNameMap.put(CaptchaBaseMapEnum.PIC_CLICK.getCodeValue(), picClickCacheMap.keySet().toArray(new String[0]));
  58 + log.info("自定义resource底图:{}", JsonUtil.toJSONString(fileNameMap));
  59 + }
  60 +
  61 +
  62 + public static BufferedImage getOriginal() {
  63 + String[] strings = fileNameMap.get(CaptchaBaseMapEnum.ORIGINAL.getCodeValue());
  64 + if (null == strings || strings.length == 0) {
  65 + return null;
  66 + }
  67 + Integer randomInt = RandomUtils.getRandomInt(0, strings.length);
  68 + String s = originalCacheMap.get(strings[randomInt]);
  69 + return getBase64StrToImage(s);
  70 + }
  71 +
  72 + public static String getslidingBlock() {
  73 + String[] strings = fileNameMap.get(CaptchaBaseMapEnum.SLIDING_BLOCK.getCodeValue());
  74 + if (null == strings || strings.length == 0) {
  75 + return null;
  76 + }
  77 + Integer randomInt = RandomUtils.getRandomInt(0, strings.length);
  78 + return slidingBlockCacheMap.get(strings[randomInt]);
  79 + }
  80 +
  81 + public static BufferedImage getPicClick() {
  82 + String[] strings = fileNameMap.get(CaptchaBaseMapEnum.PIC_CLICK.getCodeValue());
  83 + if (null == strings || strings.length == 0) {
  84 + return null;
  85 + }
  86 + Integer randomInt = RandomUtils.getRandomInt(0, strings.length);
  87 + String s = picClickCacheMap.get(strings[randomInt]);
  88 + return getBase64StrToImage(s);
  89 + }
  90 +
  91 + /**
  92 + * 图片转base64 字符串
  93 + *
  94 + * @param templateImage
  95 + * @return
  96 + */
  97 + public static String getImageToBase64Str(BufferedImage templateImage) {
  98 + ByteArrayOutputStream baos = new ByteArrayOutputStream();
  99 + try {
  100 + ImageIO.write(templateImage, "png", baos);
  101 + } catch (IOException e) {
  102 + e.printStackTrace();
  103 + }
  104 + byte[] bytes = baos.toByteArray();
  105 +
  106 + Base64.Encoder encoder = Base64.getEncoder();
  107 +
  108 + return encoder.encodeToString(bytes).trim();
  109 + }
  110 +
  111 + /**
  112 + * base64 字符串转图片
  113 + *
  114 + * @param base64String
  115 + * @return
  116 + */
  117 + public static BufferedImage getBase64StrToImage(String base64String) {
  118 + try {
  119 + Base64.Decoder decoder = Base64.getDecoder();
  120 + byte[] bytes = decoder.decode(base64String);
  121 + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
  122 + return ImageIO.read(inputStream);
  123 + } catch (IOException e) {
  124 + e.printStackTrace();
  125 + }
  126 + return null;
  127 + }
  128 +
  129 +
  130 + private static Map<String, String> getResourcesImagesFile(String path) {
  131 + //默认提供六张底图
  132 + Map<String, String> imgMap = new HashMap<>();
  133 + ClassLoader classLoader = ImageUtils.class.getClassLoader();
  134 + for (int i = 1; i <= 6; i++) {
  135 + InputStream resourceAsStream = classLoader.getResourceAsStream(path.concat("/").concat(String.valueOf(i).concat(".png")));
  136 + byte[] bytes = new byte[0];
  137 + try {
  138 + bytes = FileCopyUtils.copyToByteArray(resourceAsStream);
  139 + } catch (IOException e) {
  140 + e.printStackTrace();
  141 + }
  142 + String string = Base64Utils.encodeToString(bytes);
  143 + String filename = String.valueOf(i).concat(".png");
  144 + imgMap.put(filename, string);
  145 + }
  146 + return imgMap;
  147 + }
  148 +
  149 + private static Map<String, String> getImagesFile(String path) {
  150 + Map<String, String> imgMap = new HashMap<>();
  151 + File file = new File(path);
  152 + if (!file.exists()) {
  153 + return new HashMap<>();
  154 + }
  155 + File[] files = file.listFiles();
  156 + Arrays.stream(files).forEach(item -> {
  157 + try {
  158 + FileInputStream fileInputStream = new FileInputStream(item);
  159 + byte[] bytes = FileCopyUtils.copyToByteArray(fileInputStream);
  160 + String string = Base64Utils.encodeToString(bytes);
  161 + imgMap.put(item.getName(), string);
  162 + } catch (IOException e) {
  163 + e.printStackTrace();
  164 + }
  165 + });
  166 + return imgMap;
  167 + }
  168 +
  169 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/util/JsonUtil.java 0 → 100644
  1 +package com.anji.captcha.util;
  2 +
  3 +import com.anji.captcha.model.vo.PointVO;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +
  6 +import java.util.ArrayList;
  7 +import java.util.List;
  8 +import java.util.Map;
  9 +
  10 +/**
  11 + * 替换掉fastjson,自定义实现相关方法
  12 + * note: 该实现不具有通用性,仅用于本项目。
  13 + *
  14 + * @author WongBin
  15 + * @date 2021/1/8
  16 + */
  17 +@Slf4j
  18 +public class JsonUtil {
  19 + public static List<PointVO> parseArray(String text, Class<PointVO> clazz) {
  20 + if (text == null) {
  21 + return null;
  22 + } else {
  23 + String[] arr = text.replaceFirst("\\[", "")
  24 + .replaceFirst("\\]", "").split("\\}");
  25 + List<PointVO> ret = new ArrayList<>(arr.length);
  26 + for (String s : arr) {
  27 + ret.add(parseObject(s, PointVO.class));
  28 + }
  29 + return ret;
  30 + }
  31 + }
  32 +
  33 + public static PointVO parseObject(String text, Class<PointVO> clazz) {
  34 + if (text == null) {
  35 + return null;
  36 + }
  37 + /*if(!clazz.isAssignableFrom(PointVO.class)) {
  38 + throw new UnsupportedOperationException("不支持的输入类型:"
  39 + + clazz.getSimpleName());
  40 + }*/
  41 + try {
  42 + PointVO ret = clazz.newInstance();
  43 + return ret.parse(text);
  44 + } catch (Exception ex) {
  45 + log.error("json解析异常", ex);
  46 +
  47 + }
  48 + return null;
  49 + }
  50 +
  51 + public static String toJSONString(Object object) {
  52 + if (object == null) {
  53 + return "{}";
  54 + }
  55 + if (object instanceof PointVO) {
  56 + PointVO t = (PointVO) object;
  57 + return t.toJsonString();
  58 + }
  59 + if (object instanceof List) {
  60 + List<PointVO> list = (List<PointVO>) object;
  61 + StringBuilder buf = new StringBuilder("[");
  62 + list.forEach(t -> {
  63 + buf.append(t.toJsonString()).append(",");
  64 + });
  65 + return buf.deleteCharAt(buf.lastIndexOf(",")).append("]").toString();
  66 + }
  67 + if (object instanceof Map) {
  68 + return ((Map) object).entrySet().toString();
  69 + }
  70 + throw new UnsupportedOperationException("不支持的输入类型:"
  71 + + object.getClass().getSimpleName());
  72 + }
  73 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/util/MD5Util.java 0 → 100644
  1 +package com.anji.captcha.util;
  2 +
  3 +import java.nio.charset.StandardCharsets;
  4 +import java.security.MessageDigest;
  5 +
  6 +/**
  7 + * @Title: MD5工具类
  8 + */
  9 +public abstract class MD5Util {
  10 + /**
  11 + * 获取指定字符串的md5值
  12 + *
  13 + * @param dataStr 明文
  14 + * @return String
  15 + */
  16 + public static String md5(String dataStr) {
  17 + try {
  18 + MessageDigest m = MessageDigest.getInstance("MD5");
  19 + m.update(dataStr.getBytes(StandardCharsets.UTF_8));
  20 + byte[] s = m.digest();
  21 + StringBuilder result = new StringBuilder();
  22 + for (byte b : s) {
  23 + result.append(Integer.toHexString((0x000000FF & b) | 0xFFFFFF00).substring(6));
  24 + }
  25 + return result.toString();
  26 + } catch (Exception e) {
  27 + e.printStackTrace();
  28 + }
  29 + return "";
  30 + }
  31 +
  32 + /**
  33 + * 获取指定字符串的md5值, md5(str+salt)
  34 + *
  35 + * @param dataStr 明文
  36 + * @return String
  37 + */
  38 + public static String md5WithSalt(String dataStr, String salt) {
  39 + return md5(dataStr + salt);
  40 + }
  41 +
  42 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/util/RandomUtils.java 0 → 100644
  1 +/*
  2 + *Copyright © 2018 anji-plus
  3 + *安吉加加信息技术有限公司
  4 + *http://www.anji-plus.com
  5 + *All rights reserved.
  6 + */
  7 +package com.anji.captcha.util;
  8 +
  9 +import java.io.UnsupportedEncodingException;
  10 +import java.util.Random;
  11 +import java.util.UUID;
  12 +import java.util.concurrent.ThreadLocalRandom;
  13 +
  14 +
  15 +public class RandomUtils {
  16 +
  17 + /**
  18 + * 生成UUID
  19 + *
  20 + * @return
  21 + */
  22 + public static String getUUID() {
  23 + String uuid = UUID.randomUUID().toString();
  24 + uuid = uuid.replace("-", "");
  25 + return uuid;
  26 + }
  27 +
  28 + /**
  29 + * 获取指定文字的随机中文
  30 + *
  31 + * @return
  32 + */
  33 + public static String getRandomHan(String hanZi) {
  34 + return hanZi.charAt(new Random().nextInt(hanZi.length())) + "";
  35 + }
  36 +
  37 + public static int getRandomInt(int bound) {
  38 + return ThreadLocalRandom.current().nextInt(bound);
  39 + }
  40 +
  41 + /**
  42 + * 获取随机中文
  43 + *
  44 + * @return
  45 + */
  46 + public static String getRandomHan() {
  47 + String str = "";
  48 + int highCode;
  49 + int lowCode;
  50 +
  51 + Random random = new Random();
  52 +
  53 + highCode = (176 + Math.abs(random.nextInt(39))); //B0 + 0~39(16~55) 一级汉字所占区
  54 + lowCode = (161 + Math.abs(random.nextInt(93))); //A1 + 0~93 每区有94个汉字
  55 +
  56 + byte[] b = new byte[2];
  57 + b[0] = (Integer.valueOf(highCode)).byteValue();
  58 + b[1] = (Integer.valueOf(lowCode)).byteValue();
  59 +
  60 + try {
  61 + str = new String(b, "GBK");
  62 + } catch (UnsupportedEncodingException e) {
  63 + e.printStackTrace();
  64 + }
  65 + return str;
  66 + }
  67 +
  68 + /**
  69 + * 随机范围内数字
  70 + *
  71 + * @param startNum
  72 + * @param endNum
  73 + * @return
  74 + */
  75 + public static Integer getRandomInt(int startNum, int endNum) {
  76 + return ThreadLocalRandom.current().nextInt(endNum - startNum) + startNum;
  77 + }
  78 +
  79 + /**
  80 + * 获取随机字符串
  81 + *
  82 + * @param length
  83 + * @return
  84 + */
  85 + public static String getRandomString(int length) {
  86 + String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  87 + Random random = new Random();
  88 + StringBuffer sb = new StringBuffer();
  89 + for (int i = 0; i < length; i++) {
  90 + int number = random.nextInt(62);
  91 + sb.append(str.charAt(number));
  92 + }
  93 + return sb.toString();
  94 + }
  95 +
  96 +}
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/util/StreamUtils.java 0 → 100644
  1 +package com.anji.captcha.util;
  2 +//
  3 +// Source code recreated from a .class file by IntelliJ IDEA
  4 +// (powered by Fernflower decompiler)
  5 +//
  6 +
  7 +import java.io.*;
  8 +import java.nio.charset.Charset;
  9 +
  10 +public abstract class StreamUtils {
  11 + public static final int BUFFER_SIZE = 4096;
  12 + private static final byte[] EMPTY_CONTENT = new byte[0];
  13 +
  14 + public StreamUtils() {
  15 + }
  16 +
  17 + public static byte[] copyToByteArray(InputStream in) throws IOException {
  18 + if (in == null) {
  19 + return new byte[0];
  20 + } else {
  21 + ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
  22 + copy((InputStream) in, out);
  23 + return out.toByteArray();
  24 + }
  25 + }
  26 +
  27 + public static String copyToString(InputStream in, Charset charset) throws IOException {
  28 + if (in == null) {
  29 + return "";
  30 + } else {
  31 + StringBuilder out = new StringBuilder();
  32 + InputStreamReader reader = new InputStreamReader(in, charset);
  33 + char[] buffer = new char[4096];
  34 +
  35 + int bytesRead;
  36 + while ((bytesRead = reader.read(buffer)) != -1) {
  37 + out.append(buffer, 0, bytesRead);
  38 + }
  39 +
  40 + return out.toString();
  41 + }
  42 + }
  43 +
  44 + public static void copy(byte[] in, OutputStream out) throws IOException {
  45 + out.write(in);
  46 + }
  47 +
  48 + public static void copy(String in, Charset charset, OutputStream out) throws IOException {
  49 + Writer writer = new OutputStreamWriter(out, charset);
  50 + writer.write(in);
  51 + writer.flush();
  52 + }
  53 +
  54 + public static int copy(InputStream in, OutputStream out) throws IOException {
  55 + int byteCount = 0;
  56 + byte[] buffer = new byte[4096];
  57 +
  58 + int bytesRead;
  59 + for (boolean var4 = true; (bytesRead = in.read(buffer)) != -1; byteCount += bytesRead) {
  60 + out.write(buffer, 0, bytesRead);
  61 + }
  62 +
  63 + out.flush();
  64 + return byteCount;
  65 + }
  66 +
  67 + public static long copyRange(InputStream in, OutputStream out, long start, long end) throws IOException {
  68 + long skipped = in.skip(start);
  69 + if (skipped < start) {
  70 + throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required");
  71 + } else {
  72 + long bytesToCopy = end - start + 1L;
  73 + byte[] buffer = new byte[4096];
  74 +
  75 + while (bytesToCopy > 0L) {
  76 + int bytesRead = in.read(buffer);
  77 + if (bytesRead == -1) {
  78 + break;
  79 + }
  80 +
  81 + if ((long) bytesRead <= bytesToCopy) {
  82 + out.write(buffer, 0, bytesRead);
  83 + bytesToCopy -= (long) bytesRead;
  84 + } else {
  85 + out.write(buffer, 0, (int) bytesToCopy);
  86 + bytesToCopy = 0L;
  87 + }
  88 + }
  89 +
  90 + return end - start + 1L - bytesToCopy;
  91 + }
  92 + }
  93 +
  94 + public static int drain(InputStream in) throws IOException {
  95 + byte[] buffer = new byte[4096];
  96 + int byteCount;
  97 + int bytesRead;
  98 + for (byteCount = 0; (bytesRead = in.read(buffer)) != -1; byteCount += bytesRead) {
  99 + }
  100 +
  101 + return byteCount;
  102 + }
  103 +
  104 + public static InputStream emptyInput() {
  105 + return new ByteArrayInputStream(EMPTY_CONTENT);
  106 + }
  107 +
  108 + public static InputStream nonClosing(InputStream in) {
  109 + return new NonClosingInputStream(in);
  110 + }
  111 +
  112 + public static OutputStream nonClosing(OutputStream out) {
  113 + return new NonClosingOutputStream(out);
  114 + }
  115 +
  116 + private static class NonClosingOutputStream extends FilterOutputStream {
  117 + public NonClosingOutputStream(OutputStream out) {
  118 + super(out);
  119 + }
  120 +
  121 + public void write(byte[] b, int off, int let) throws IOException {
  122 + this.out.write(b, off, let);
  123 + }
  124 +
  125 + public void close() throws IOException {
  126 + }
  127 + }
  128 +
  129 + private static class NonClosingInputStream extends FilterInputStream {
  130 + public NonClosingInputStream(InputStream in) {
  131 + super(in);
  132 + }
  133 +
  134 + public void close() throws IOException {
  135 + }
  136 + }
  137 +}
  138 +
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService 0 → 100644
  1 +com.anji.captcha.service.impl.BlockPuzzleCaptchaServiceImpl
  2 +com.anji.captcha.service.impl.ClickWordCaptchaServiceImpl
  3 +com.anji.captcha.service.impl.DefaultCaptchaServiceImpl
0 4 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  1 +com.anji.captcha.config.AjCaptchaAutoConfiguration
1 2 cn.iocoder.yudao.framework.captcha.config.YudaoCaptchaConfiguration
2 3 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/1.png 0 → 100644

85.5 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/2.png 0 → 100644

81.6 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/3.png 0 → 100644

77.1 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/4.png 0 → 100644

79.6 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/5.png 0 → 100644

57.8 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/6.png 0 → 100644

83.6 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/bg8.png 0 → 100644

72.5 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/1.png 0 → 100644

21.5 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/2.png 0 → 100644

20.9 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/3.png 0 → 100644

21.2 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/4.png 0 → 100644

21.4 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/5.png 0 → 100644

7.67 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/6.png 0 → 100644

7.78 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/1.png 0 → 100644

42.3 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/2.png 0 → 100644

70.1 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/3.png 0 → 100644

40.7 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/4.png 0 → 100644

48.2 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/5.png 0 → 100644

61.5 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/6.png 0 → 100644

60.4 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg10.png 0 → 100644

48.2 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg11.png 0 → 100644

39.7 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg12.png 0 → 100644

65.6 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg13.png 0 → 100644

78.9 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg14.png 0 → 100644

72.5 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg15.png 0 → 100644

58.9 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg16.png 0 → 100644

69.9 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg17.png 0 → 100644

82.4 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg18.png 0 → 100644

40.9 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg19.png 0 → 100644

8.1 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg20.png 0 → 100644

58 KB

yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/fonts/WenQuanZhengHei.ttf 0 → 100644
No preview for this file type
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/fonts/license.txt 0 → 100644
  1 +文泉驿是一个开源汉字字体项目
  2 +
  3 +由旅美学者房骞骞(FangQ)
  4 +
  5 +于2004年10月创建
  6 +
  7 +集中力量解决GNU/Linux
  8 +
  9 +高质量中文字体匮乏的状况
  10 +
  11 +目前,文泉驿已经开发并发布了
  12 +
  13 +第一个完整覆盖GB18030汉字
  14 +
  15 +(包含27000多个汉字)
  16 +
  17 +的多规格点阵汉字字型文件
  18 +
  19 +第一个覆盖GBK字符集的
  20 +
  21 +开源矢量字型文件(文泉驿正黑)
  22 +
  23 +并提供了目前包含字符数目最多的
  24 +
  25 +开源字体——GNU Unifont——中
  26 +
  27 +绝大多数中日韩文相关的符号
  28 +
  29 +这些字型文件已经逐渐成为
  30 +
  31 +主流Linux/Unix发行版
  32 +
  33 +中文桌面的首选中文字体
  34 +
  35 +目前Ubuntu、Fedora、Slackware
  36 +
  37 +Magic Linux、CDLinux
  38 +
  39 +使用文泉驿作为默认中文字体
  40 +
  41 +Debian、Gentoo、Mandriva
  42 +
  43 +ArchLinux、Frugalware
  44 +
  45 +则提供了官方源支持
  46 +
  47 +而FreeBSD则在其ports中有提供
  48 +
  49 +所以,今天我们所要分享的就是
  50 +
  51 +文泉驿正黑体
  52 +
  53 +可在Linux/UNIX,Windows
  54 +
  55 +Mac OS和嵌入式操作系统中使用
0 56 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.mybatis.core.mapper;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 5 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
7 6 import com.baomidou.mybatisplus.core.conditions.Wrapper;
8 7 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
... ... @@ -10,6 +9,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
10 9 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
11 10 import com.baomidou.mybatisplus.core.metadata.IPage;
12 11 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
  12 +import com.baomidou.mybatisplus.extension.toolkit.Db;
13 13 import org.apache.ibatis.annotations.Param;
14 14  
15 15 import java.util.Collection;
... ... @@ -92,8 +92,22 @@ public interface BaseMapperX&lt;T&gt; extends BaseMapper&lt;T&gt; {
92 92 entities.forEach(this::insert);
93 93 }
94 94  
  95 + /**
  96 + * 批量插入,适合大量数据插入
  97 + *
  98 + * @param entities 实体们
  99 + * @param size 插入数量 Db.saveBatch 默认为1000
  100 + */
  101 + default void insertBatch(Collection<T> entities, int size) {
  102 + Db.saveBatch(entities, size);
  103 + }
  104 +
95 105 default void updateBatch(T update) {
96 106 update(update, new QueryWrapper<>());
97 107 }
98 108  
  109 + default void updateBatch(Collection<T> entities, int size) {
  110 + Db.updateBatchById(entities, size);
  111 + }
  112 +
99 113 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java
1 1 package cn.iocoder.yudao.module.system.controller.admin.captcha;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
4 5 import com.anji.captcha.model.common.ResponseModel;
5 6 import com.anji.captcha.model.vo.CaptchaVO;
  7 +import com.anji.captcha.service.CaptchaService;
6 8 import io.swagger.annotations.Api;
7 9 import io.swagger.annotations.ApiOperation;
8 10 import org.springframework.web.bind.annotation.PostMapping;
... ... @@ -10,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestBody;
10 12 import org.springframework.web.bind.annotation.RequestMapping;
11 13 import org.springframework.web.bind.annotation.RestController;
12 14  
  15 +import javax.annotation.Resource;
13 16 import javax.annotation.security.PermitAll;
14 17 import javax.servlet.http.HttpServletRequest;
15 18  
... ... @@ -24,24 +27,46 @@ import javax.servlet.http.HttpServletRequest;
24 27 @Api(tags = "管理后台 - 验证码")
25 28 @RestController("adminCaptchaController")
26 29 @RequestMapping("/system/captcha")
27   -public class CaptchaController extends com.anji.captcha.controller.CaptchaController {
  30 +public class CaptchaController {
  31 +
  32 + @Resource
  33 + private CaptchaService captchaService;
28 34  
29 35 @PostMapping({"/get"})
30 36 @ApiOperation("获得验证码")
31 37 @PermitAll
32 38 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
33   - @Override
34 39 public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) {
35   - return super.get(data, request);
  40 + assert request.getRemoteHost() != null;
  41 + data.setBrowserInfo(getRemoteId(request));
  42 + return captchaService.get(data);
36 43 }
37 44  
38 45 @PostMapping("/check")
39 46 @ApiOperation("校验验证码")
40 47 @PermitAll
41 48 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
42   - @Override
43 49 public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) {
44   - return super.check(data, request);
  50 + data.setBrowserInfo(getRemoteId(request));
  51 + return captchaService.check(data);
  52 + }
  53 +
  54 + public static String getRemoteId(HttpServletRequest request) {
  55 + String xfwd = request.getHeader("X-Forwarded-For");
  56 + String ip = getRemoteIpFromXfwd(xfwd);
  57 + String ua = request.getHeader("user-agent");
  58 + if (StrUtil.isNotBlank(ip)) {
  59 + return ip + ua;
  60 + }
  61 + return request.getRemoteAddr() + ua;
  62 + }
  63 +
  64 + private static String getRemoteIpFromXfwd(String xfwd) {
  65 + if (StrUtil.isNotBlank(xfwd)) {
  66 + String[] ipList = xfwd.split(",");
  67 + return StrUtil.trim(ipList[0]);
  68 + }
  69 + return null;
45 70 }
46 71  
47 72 }
... ...
yudao-ui-admin-vue3/README.md
... ... @@ -34,7 +34,7 @@
34 34 | [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.9.4 |
35 35 | [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.28 |
36 36 | [vueuse](https://vueuse.org/) | 常用工具集 | 9.10.0 |
37   -| [vxe-table](https://vxetable.cn/) | vue 最强表单 | 4.3.7 |
  37 +| [vxe-table](https://vxetable.cn/) | vue 最强表单 | 4.3.9 |
38 38 | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
39 39 | [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.6 |
40 40 | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
... ...
yudao-ui-admin-vue3/package.json
1 1 {
2 2 "name": "yudao-ui-admin-vue3",
3   - "version": "1.6.6-snapshot.1901",
  3 + "version": "1.6.6-snapshot.1911",
4 4 "description": "基于vue3、vite4、element-plus、typesScript",
5 5 "author": "xingyu",
6 6 "private": false,
... ... @@ -50,14 +50,14 @@
50 50 "vue-i18n": "9.2.2",
51 51 "vue-router": "^4.1.6",
52 52 "vue-types": "^5.0.2",
53   - "vxe-table": "^4.3.7",
  53 + "vxe-table": "^4.3.9",
54 54 "web-storage-cache": "^1.1.1",
55 55 "xe-utils": "^3.5.7"
56 56 },
57 57 "devDependencies": {
58   - "@commitlint/cli": "^17.4.0",
  58 + "@commitlint/cli": "^17.4.1",
59 59 "@commitlint/config-conventional": "^17.4.0",
60   - "@iconify/json": "^2.2.2",
  60 + "@iconify/json": "^2.2.5",
61 61 "@intlify/unplugin-vue-i18n": "^0.8.1",
62 62 "@purge-icons/generated": "^0.9.0",
63 63 "@types/intro.js": "^5.1.0",
... ... @@ -66,8 +66,8 @@
66 66 "@types/nprogress": "^0.2.0",
67 67 "@types/qrcode": "^1.5.0",
68 68 "@types/qs": "^6.9.7",
69   - "@typescript-eslint/eslint-plugin": "^5.48.0",
70   - "@typescript-eslint/parser": "^5.48.0",
  69 + "@typescript-eslint/eslint-plugin": "^5.48.1",
  70 + "@typescript-eslint/parser": "^5.48.1",
71 71 "@vitejs/plugin-legacy": "^3.0.1",
72 72 "@vitejs/plugin-vue": "^4.0.0",
73 73 "@vitejs/plugin-vue-jsx": "^3.0.0",
... ... @@ -75,14 +75,14 @@
75 75 "consola": "^2.15.3",
76 76 "eslint": "^8.31.0",
77 77 "eslint-config-prettier": "^8.6.0",
78   - "eslint-define-config": "^1.13.0",
  78 + "eslint-define-config": "^1.14.0",
79 79 "eslint-plugin-prettier": "^4.2.1",
80 80 "eslint-plugin-vue": "^9.8.0",
81 81 "lint-staged": "^13.1.0",
82   - "postcss": "^8.4.20",
  82 + "postcss": "^8.4.21",
83 83 "postcss-html": "^1.5.0",
84 84 "postcss-scss": "^4.0.6",
85   - "prettier": "^2.8.1",
  85 + "prettier": "^2.8.2",
86 86 "rimraf": "^3.0.2",
87 87 "rollup": "^3.9.1",
88 88 "sass": "^1.57.1",
... ... @@ -91,7 +91,7 @@
91 91 "stylelint-config-prettier": "^9.0.4",
92 92 "stylelint-config-recommended": "^9.0.0",
93 93 "stylelint-config-standard": "^29.0.0",
94   - "stylelint-order": "^5.0.0",
  94 + "stylelint-order": "^6.0.1",
95 95 "terser": "^5.16.1",
96 96 "typescript": "4.9.4",
97 97 "vite": "4.0.4",
... ... @@ -104,7 +104,7 @@
104 104 "vite-plugin-svg-icons": "^2.0.1",
105 105 "vite-plugin-vue-setup-extend": "^0.4.0",
106 106 "vite-plugin-windicss": "^1.8.10",
107   - "vue-tsc": "^1.0.22",
  107 + "vue-tsc": "^1.0.24",
108 108 "windicss": "^3.5.6"
109 109 },
110 110 "engines": {
... ...
yudao-ui-admin-vue3/pnpm-lock.yaml
1 1 lockfileVersion: 5.4
2 2  
3 3 specifiers:
4   - '@commitlint/cli': ^17.4.0
  4 + '@commitlint/cli': ^17.4.1
5 5 '@commitlint/config-conventional': ^17.4.0
6 6 '@iconify/iconify': ^3.0.1
7   - '@iconify/json': ^2.2.2
  7 + '@iconify/json': ^2.2.5
8 8 '@intlify/unplugin-vue-i18n': ^0.8.1
9 9 '@purge-icons/generated': ^0.9.0
10 10 '@types/intro.js': ^5.1.0
... ... @@ -13,8 +13,8 @@ specifiers:
13 13 '@types/nprogress': ^0.2.0
14 14 '@types/qrcode': ^1.5.0
15 15 '@types/qs': ^6.9.7
16   - '@typescript-eslint/eslint-plugin': ^5.48.0
17   - '@typescript-eslint/parser': ^5.48.0
  16 + '@typescript-eslint/eslint-plugin': ^5.48.1
  17 + '@typescript-eslint/parser': ^5.48.1
18 18 '@vitejs/plugin-legacy': ^3.0.1
19 19 '@vitejs/plugin-vue': ^4.0.0
20 20 '@vitejs/plugin-vue-jsx': ^3.0.0
... ... @@ -34,7 +34,7 @@ specifiers:
34 34 element-plus: 2.2.28
35 35 eslint: ^8.31.0
36 36 eslint-config-prettier: ^8.6.0
37   - eslint-define-config: ^1.13.0
  37 + eslint-define-config: ^1.14.0
38 38 eslint-plugin-prettier: ^4.2.1
39 39 eslint-plugin-vue: ^9.8.0
40 40 intro.js: ^6.0.0
... ... @@ -44,10 +44,10 @@ specifiers:
44 44 mitt: ^3.0.0
45 45 nprogress: ^0.2.0
46 46 pinia: ^2.0.28
47   - postcss: ^8.4.20
  47 + postcss: ^8.4.21
48 48 postcss-html: ^1.5.0
49 49 postcss-scss: ^4.0.6
50   - prettier: ^2.8.1
  50 + prettier: ^2.8.2
51 51 qrcode: ^1.5.1
52 52 qs: ^6.11.0
53 53 rimraf: ^3.0.2
... ... @@ -58,7 +58,7 @@ specifiers:
58 58 stylelint-config-prettier: ^9.0.4
59 59 stylelint-config-recommended: ^9.0.0
60 60 stylelint-config-standard: ^29.0.0
61   - stylelint-order: ^5.0.0
  61 + stylelint-order: ^6.0.1
62 62 terser: ^5.16.1
63 63 typescript: 4.9.4
64 64 url: ^0.11.0
... ... @@ -75,9 +75,9 @@ specifiers:
75 75 vue: 3.2.45
76 76 vue-i18n: 9.2.2
77 77 vue-router: ^4.1.6
78   - vue-tsc: ^1.0.22
  78 + vue-tsc: ^1.0.24
79 79 vue-types: ^5.0.2
80   - vxe-table: ^4.3.7
  80 + vxe-table: ^4.3.9
81 81 web-storage-cache: ^1.1.1
82 82 windicss: ^3.5.6
83 83 xe-utils: ^3.5.7
... ... @@ -109,14 +109,14 @@ dependencies:
109 109 vue-i18n: 9.2.2_vue@3.2.45
110 110 vue-router: 4.1.6_vue@3.2.45
111 111 vue-types: 5.0.2_vue@3.2.45
112   - vxe-table: 4.3.7_vue@3.2.45+xe-utils@3.5.7
  112 + vxe-table: 4.3.9_vue@3.2.45+xe-utils@3.5.7
113 113 web-storage-cache: 1.1.1
114 114 xe-utils: 3.5.7
115 115  
116 116 devDependencies:
117   - '@commitlint/cli': 17.4.0_@types+node@18.11.18
  117 + '@commitlint/cli': 17.4.1
118 118 '@commitlint/config-conventional': 17.4.0
119   - '@iconify/json': 2.2.2
  119 + '@iconify/json': 2.2.5
120 120 '@intlify/unplugin-vue-i18n': 0.8.1_vue-i18n@9.2.2
121 121 '@purge-icons/generated': 0.9.0
122 122 '@types/intro.js': 5.1.0
... ... @@ -125,23 +125,23 @@ devDependencies:
125 125 '@types/nprogress': 0.2.0
126 126 '@types/qrcode': 1.5.0
127 127 '@types/qs': 6.9.7
128   - '@typescript-eslint/eslint-plugin': 5.48.0_k73wpmdolxikpyqun3p36akaaq
129   - '@typescript-eslint/parser': 5.48.0_iukboom6ndih5an6iafl45j2fe
  128 + '@typescript-eslint/eslint-plugin': 5.48.1_3jon24igvnqaqexgwtxk6nkpse
  129 + '@typescript-eslint/parser': 5.48.1_iukboom6ndih5an6iafl45j2fe
130 130 '@vitejs/plugin-legacy': 3.0.1_terser@5.16.1+vite@4.0.4
131 131 '@vitejs/plugin-vue': 4.0.0_vite@4.0.4+vue@3.2.45
132 132 '@vitejs/plugin-vue-jsx': 3.0.0_vite@4.0.4+vue@3.2.45
133   - autoprefixer: 10.4.13_postcss@8.4.20
  133 + autoprefixer: 10.4.13_postcss@8.4.21
134 134 consola: 2.15.3
135 135 eslint: 8.31.0
136 136 eslint-config-prettier: 8.6.0_eslint@8.31.0
137   - eslint-define-config: 1.13.0
138   - eslint-plugin-prettier: 4.2.1_32m5uc2milwdw3tnkcq5del26y
  137 + eslint-define-config: 1.14.0
  138 + eslint-plugin-prettier: 4.2.1_iu5s7nk6dw7o3tajefwfiqfmge
139 139 eslint-plugin-vue: 9.8.0_eslint@8.31.0
140 140 lint-staged: 13.1.0
141   - postcss: 8.4.20
  141 + postcss: 8.4.21
142 142 postcss-html: 1.5.0
143   - postcss-scss: 4.0.6_postcss@8.4.20
144   - prettier: 2.8.1
  143 + postcss-scss: 4.0.6_postcss@8.4.21
  144 + prettier: 2.8.2
145 145 rimraf: 3.0.2
146 146 rollup: 3.9.1
147 147 sass: 1.57.1
... ... @@ -150,7 +150,7 @@ devDependencies:
150 150 stylelint-config-prettier: 9.0.4_stylelint@14.16.1
151 151 stylelint-config-recommended: 9.0.0_stylelint@14.16.1
152 152 stylelint-config-standard: 29.0.0_stylelint@14.16.1
153   - stylelint-order: 5.0.0_stylelint@14.16.1
  153 + stylelint-order: 6.0.1_stylelint@14.16.1
154 154 terser: 5.16.1
155 155 typescript: 4.9.4
156 156 vite: 4.0.4_zxbrnrc4iyldik6mikh3pswz4i
... ... @@ -163,7 +163,7 @@ devDependencies:
163 163 vite-plugin-svg-icons: 2.0.1_vite@4.0.4
164 164 vite-plugin-vue-setup-extend: 0.4.0_vite@4.0.4
165 165 vite-plugin-windicss: 1.8.10_vite@4.0.4
166   - vue-tsc: 1.0.22_typescript@4.9.4
  166 + vue-tsc: 1.0.24_typescript@4.9.4
167 167 windicss: 3.5.6
168 168  
169 169 packages:
... ... @@ -506,14 +506,14 @@ packages:
506 506 '@babel/helper-validator-identifier': 7.19.1
507 507 to-fast-properties: 2.0.0
508 508  
509   - /@commitlint/cli/17.4.0_@types+node@18.11.18:
510   - resolution: {integrity: sha512-SEY4sYe8yVlgxPP7X0wJb96DBAGBPsCsy6QbqJt/UECbIAjDeDV5xXBV4jnS7T/qMC10sk6Ub9kDhEX0VWvblw==}
  509 + /@commitlint/cli/17.4.1:
  510 + resolution: {integrity: sha512-W8OJwz+izY+fVwyUt1HveCDmABMZNRVZHSVPw/Bh9Y62tp11SmmQaycgbsYLMiMy7JGn4mAJqEGlSHS9Uti9ZQ==}
511 511 engines: {node: '>=v14'}
512 512 hasBin: true
513 513 dependencies:
514 514 '@commitlint/format': 17.4.0
515 515 '@commitlint/lint': 17.4.0
516   - '@commitlint/load': 17.4.0_@types+node@18.11.18
  516 + '@commitlint/load': 17.4.1
517 517 '@commitlint/read': 17.4.0
518 518 '@commitlint/types': 17.4.0
519 519 execa: 5.1.1
... ... @@ -524,7 +524,6 @@ packages:
524 524 transitivePeerDependencies:
525 525 - '@swc/core'
526 526 - '@swc/wasm'
527   - - '@types/node'
528 527 dev: true
529 528  
530 529 /@commitlint/config-conventional/17.4.0:
... ... @@ -585,14 +584,15 @@ packages:
585 584 '@commitlint/types': 17.4.0
586 585 dev: true
587 586  
588   - /@commitlint/load/17.4.0_@types+node@18.11.18:
589   - resolution: {integrity: sha512-wDKNvAJqukqZqKmhRlf3KNo/12QGo1AQcd80EbV01SxtGvyHOsJ/g+/IbrZpopZv8rvzmEVktcpfDYH6ITepFA==}
  587 + /@commitlint/load/17.4.1:
  588 + resolution: {integrity: sha512-6A7/LhIaQpL4ieciIDcVvK2d5z/UI1GBrtDaHm6sQSCL0265clB2/F7XKQNTJHXv9yG4LByT2r+QCpM4GugIfw==}
590 589 engines: {node: '>=v14'}
591 590 dependencies:
592 591 '@commitlint/config-validator': 17.4.0
593 592 '@commitlint/execute-rule': 17.4.0
594 593 '@commitlint/resolve-extends': 17.4.0
595 594 '@commitlint/types': 17.4.0
  595 + '@types/node': 18.11.18
596 596 chalk: 4.1.2
597 597 cosmiconfig: 8.0.0
598 598 cosmiconfig-typescript-loader: 4.2.0_bxtyj3et3xbsdyxhh3oblnfbj4
... ... @@ -605,7 +605,6 @@ packages:
605 605 transitivePeerDependencies:
606 606 - '@swc/core'
607 607 - '@swc/wasm'
608   - - '@types/node'
609 608 dev: true
610 609  
611 610 /@commitlint/message/17.4.0:
... ... @@ -682,14 +681,14 @@ packages:
682 681 '@jridgewell/trace-mapping': 0.3.9
683 682 dev: true
684 683  
685   - /@csstools/selector-specificity/2.0.2_2xshye3abirqjlplmebvmaxyna:
  684 + /@csstools/selector-specificity/2.0.2_wajs5nedgkikc5pcuwett7legi:
686 685 resolution: {integrity: sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==}
687 686 engines: {node: ^12 || ^14 || >=16}
688 687 peerDependencies:
689 688 postcss: ^8.2
690 689 postcss-selector-parser: ^6.0.10
691 690 dependencies:
692   - postcss: 8.4.20
  691 + postcss: 8.4.21
693 692 postcss-selector-parser: 6.0.11
694 693 dev: true
695 694  
... ... @@ -964,8 +963,8 @@ packages:
964 963 dependencies:
965 964 '@iconify/types': 2.0.0
966 965  
967   - /@iconify/json/2.2.2:
968   - resolution: {integrity: sha512-G9HVJz3uvQGNEirk9oI7xYnWb7ygEfTUZ+PVp81qgNp8bu5UOtXaxjTGw78NyNAC2OlryH5tSEp95Dqbt4LLQQ==}
  966 + /@iconify/json/2.2.5:
  967 + resolution: {integrity: sha512-AIbJTRF9HJz7FJ8t7058huwDXHaFEY4a4f6uuPtctEQ8/I3ybrX66iZMgoLD1kLaamU4tzZPlZ6tSpA3pM8LZg==}
969 968 dependencies:
970 969 '@iconify/types': 2.0.0
971 970 pathe: 1.0.0
... ... @@ -986,8 +985,8 @@ packages:
986 985 vue-i18n:
987 986 optional: true
988 987 dependencies:
989   - '@intlify/message-compiler': 9.3.0-beta.12
990   - '@intlify/shared': 9.3.0-beta.12
  988 + '@intlify/message-compiler': 9.3.0-beta.14
  989 + '@intlify/shared': 9.3.0-beta.14
991 990 jsonc-eslint-parser: 1.4.1
992 991 source-map: 0.6.1
993 992 vue-i18n: 9.2.2_vue@3.2.45
... ... @@ -1016,11 +1015,11 @@ packages:
1016 1015 '@intlify/shared': 9.2.2
1017 1016 source-map: 0.6.1
1018 1017  
1019   - /@intlify/message-compiler/9.3.0-beta.12:
1020   - resolution: {integrity: sha512-A8/s7pb3v8nf6HG77qFPJntxgQKI9GXxGnkn7aO+b03/X/GkF/4WceDSAIk3i+yLeIgszeBn9GZ23tSg4sTEHA==}
  1018 + /@intlify/message-compiler/9.3.0-beta.14:
  1019 + resolution: {integrity: sha512-PlZ3pl+YYuql54Nq+26wv6ohIa8Ig6ALrvQI+f2zZKUtkupb49M4wyVN3bDQbFlgYVE7/u3n19BJSY8lEuX5Eg==}
1021 1020 engines: {node: '>= 14'}
1022 1021 dependencies:
1023   - '@intlify/shared': 9.3.0-beta.11
  1022 + '@intlify/shared': 9.3.0-beta.14
1024 1023 source-map: 0.6.1
1025 1024 dev: true
1026 1025  
... ... @@ -1028,13 +1027,8 @@ packages:
1028 1027 resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==}
1029 1028 engines: {node: '>= 14'}
1030 1029  
1031   - /@intlify/shared/9.3.0-beta.11:
1032   - resolution: {integrity: sha512-CtbotesxTRiC3bRyXyv1NG39fkqJ790f8z8xFaeIXSZpOdiyxoh5BIyypCzSFQZDGLwz0Q9gyWbW1XpxQJm68Q==}
1033   - engines: {node: '>= 14'}
1034   - dev: true
1035   -
1036   - /@intlify/shared/9.3.0-beta.12:
1037   - resolution: {integrity: sha512-WsmaS54sA8xuwezPKpa/OMoaX1v2VF2fCgAmYS6prDr2ir0CkUFWPm9A8ilmxzv4nkS61/v8+vf4lGGkn5uBdA==}
  1030 + /@intlify/shared/9.3.0-beta.14:
  1031 + resolution: {integrity: sha512-mJ/rFan+4uVsBAQSCAJnpQaPvSjQ49mJMNmGelTUbTDAmgf0oexYxwqtKOlFFyY3hmQ8lUDYaGQKuYrFgRuHnA==}
1038 1032 engines: {node: '>= 14'}
1039 1033 dev: true
1040 1034  
... ... @@ -1054,7 +1048,7 @@ packages:
1054 1048 optional: true
1055 1049 dependencies:
1056 1050 '@intlify/bundle-utils': 3.4.0_vue-i18n@9.2.2
1057   - '@intlify/shared': 9.3.0-beta.12
  1051 + '@intlify/shared': 9.3.0-beta.14
1058 1052 '@rollup/pluginutils': 4.2.1
1059 1053 '@vue/compiler-sfc': 3.2.45
1060 1054 debug: 4.3.4
... ... @@ -1285,8 +1279,8 @@ packages:
1285 1279 resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
1286 1280 dev: false
1287 1281  
1288   - /@typescript-eslint/eslint-plugin/5.48.0_k73wpmdolxikpyqun3p36akaaq:
1289   - resolution: {integrity: sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==}
  1282 + /@typescript-eslint/eslint-plugin/5.48.1_3jon24igvnqaqexgwtxk6nkpse:
  1283 + resolution: {integrity: sha512-9nY5K1Rp2ppmpb9s9S2aBiF3xo5uExCehMDmYmmFqqyxgenbHJ3qbarcLt4ITgaD6r/2ypdlcFRdcuVPnks+fQ==}
1290 1284 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1291 1285 peerDependencies:
1292 1286 '@typescript-eslint/parser': ^5.0.0
... ... @@ -1296,10 +1290,10 @@ packages:
1296 1290 typescript:
1297 1291 optional: true
1298 1292 dependencies:
1299   - '@typescript-eslint/parser': 5.48.0_iukboom6ndih5an6iafl45j2fe
1300   - '@typescript-eslint/scope-manager': 5.48.0
1301   - '@typescript-eslint/type-utils': 5.48.0_iukboom6ndih5an6iafl45j2fe
1302   - '@typescript-eslint/utils': 5.48.0_iukboom6ndih5an6iafl45j2fe
  1293 + '@typescript-eslint/parser': 5.48.1_iukboom6ndih5an6iafl45j2fe
  1294 + '@typescript-eslint/scope-manager': 5.48.1
  1295 + '@typescript-eslint/type-utils': 5.48.1_iukboom6ndih5an6iafl45j2fe
  1296 + '@typescript-eslint/utils': 5.48.1_iukboom6ndih5an6iafl45j2fe
1303 1297 debug: 4.3.4
1304 1298 eslint: 8.31.0
1305 1299 ignore: 5.2.1
... ... @@ -1312,8 +1306,8 @@ packages:
1312 1306 - supports-color
1313 1307 dev: true
1314 1308  
1315   - /@typescript-eslint/parser/5.48.0_iukboom6ndih5an6iafl45j2fe:
1316   - resolution: {integrity: sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==}
  1309 + /@typescript-eslint/parser/5.48.1_iukboom6ndih5an6iafl45j2fe:
  1310 + resolution: {integrity: sha512-4yg+FJR/V1M9Xoq56SF9Iygqm+r5LMXvheo6DQ7/yUWynQ4YfCRnsKuRgqH4EQ5Ya76rVwlEpw4Xu+TgWQUcdA==}
1317 1311 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1318 1312 peerDependencies:
1319 1313 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
... ... @@ -1322,9 +1316,9 @@ packages:
1322 1316 typescript:
1323 1317 optional: true
1324 1318 dependencies:
1325   - '@typescript-eslint/scope-manager': 5.48.0
1326   - '@typescript-eslint/types': 5.48.0
1327   - '@typescript-eslint/typescript-estree': 5.48.0_typescript@4.9.4
  1319 + '@typescript-eslint/scope-manager': 5.48.1
  1320 + '@typescript-eslint/types': 5.48.1
  1321 + '@typescript-eslint/typescript-estree': 5.48.1_typescript@4.9.4
1328 1322 debug: 4.3.4
1329 1323 eslint: 8.31.0
1330 1324 typescript: 4.9.4
... ... @@ -1332,16 +1326,16 @@ packages:
1332 1326 - supports-color
1333 1327 dev: true
1334 1328  
1335   - /@typescript-eslint/scope-manager/5.48.0:
1336   - resolution: {integrity: sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==}
  1329 + /@typescript-eslint/scope-manager/5.48.1:
  1330 + resolution: {integrity: sha512-S035ueRrbxRMKvSTv9vJKIWgr86BD8s3RqoRZmsSh/s8HhIs90g6UlK8ZabUSjUZQkhVxt7nmZ63VJ9dcZhtDQ==}
1337 1331 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1338 1332 dependencies:
1339   - '@typescript-eslint/types': 5.48.0
1340   - '@typescript-eslint/visitor-keys': 5.48.0
  1333 + '@typescript-eslint/types': 5.48.1
  1334 + '@typescript-eslint/visitor-keys': 5.48.1
1341 1335 dev: true
1342 1336  
1343   - /@typescript-eslint/type-utils/5.48.0_iukboom6ndih5an6iafl45j2fe:
1344   - resolution: {integrity: sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==}
  1337 + /@typescript-eslint/type-utils/5.48.1_iukboom6ndih5an6iafl45j2fe:
  1338 + resolution: {integrity: sha512-Hyr8HU8Alcuva1ppmqSYtM/Gp0q4JOp1F+/JH5D1IZm/bUBrV0edoewQZiEc1r6I8L4JL21broddxK8HAcZiqQ==}
1345 1339 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1346 1340 peerDependencies:
1347 1341 eslint: '*'
... ... @@ -1350,8 +1344,8 @@ packages:
1350 1344 typescript:
1351 1345 optional: true
1352 1346 dependencies:
1353   - '@typescript-eslint/typescript-estree': 5.48.0_typescript@4.9.4
1354   - '@typescript-eslint/utils': 5.48.0_iukboom6ndih5an6iafl45j2fe
  1347 + '@typescript-eslint/typescript-estree': 5.48.1_typescript@4.9.4
  1348 + '@typescript-eslint/utils': 5.48.1_iukboom6ndih5an6iafl45j2fe
1355 1349 debug: 4.3.4
1356 1350 eslint: 8.31.0
1357 1351 tsutils: 3.21.0_typescript@4.9.4
... ... @@ -1360,13 +1354,13 @@ packages:
1360 1354 - supports-color
1361 1355 dev: true
1362 1356  
1363   - /@typescript-eslint/types/5.48.0:
1364   - resolution: {integrity: sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==}
  1357 + /@typescript-eslint/types/5.48.1:
  1358 + resolution: {integrity: sha512-xHyDLU6MSuEEdIlzrrAerCGS3T7AA/L8Hggd0RCYBi0w3JMvGYxlLlXHeg50JI9Tfg5MrtsfuNxbS/3zF1/ATg==}
1365 1359 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1366 1360 dev: true
1367 1361  
1368   - /@typescript-eslint/typescript-estree/5.48.0_typescript@4.9.4:
1369   - resolution: {integrity: sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==}
  1362 + /@typescript-eslint/typescript-estree/5.48.1_typescript@4.9.4:
  1363 + resolution: {integrity: sha512-Hut+Osk5FYr+sgFh8J/FHjqX6HFcDzTlWLrFqGoK5kVUN3VBHF/QzZmAsIXCQ8T/W9nQNBTqalxi1P3LSqWnRA==}
1370 1364 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1371 1365 peerDependencies:
1372 1366 typescript: '*'
... ... @@ -1374,8 +1368,8 @@ packages:
1374 1368 typescript:
1375 1369 optional: true
1376 1370 dependencies:
1377   - '@typescript-eslint/types': 5.48.0
1378   - '@typescript-eslint/visitor-keys': 5.48.0
  1371 + '@typescript-eslint/types': 5.48.1
  1372 + '@typescript-eslint/visitor-keys': 5.48.1
1379 1373 debug: 4.3.4
1380 1374 globby: 11.1.0
1381 1375 is-glob: 4.0.3
... ... @@ -1386,17 +1380,17 @@ packages:
1386 1380 - supports-color
1387 1381 dev: true
1388 1382  
1389   - /@typescript-eslint/utils/5.48.0_iukboom6ndih5an6iafl45j2fe:
1390   - resolution: {integrity: sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==}
  1383 + /@typescript-eslint/utils/5.48.1_iukboom6ndih5an6iafl45j2fe:
  1384 + resolution: {integrity: sha512-SmQuSrCGUOdmGMwivW14Z0Lj8dxG1mOFZ7soeJ0TQZEJcs3n5Ndgkg0A4bcMFzBELqLJ6GTHnEU+iIoaD6hFGA==}
1391 1385 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1392 1386 peerDependencies:
1393 1387 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
1394 1388 dependencies:
1395 1389 '@types/json-schema': 7.0.11
1396 1390 '@types/semver': 7.3.13
1397   - '@typescript-eslint/scope-manager': 5.48.0
1398   - '@typescript-eslint/types': 5.48.0
1399   - '@typescript-eslint/typescript-estree': 5.48.0_typescript@4.9.4
  1391 + '@typescript-eslint/scope-manager': 5.48.1
  1392 + '@typescript-eslint/types': 5.48.1
  1393 + '@typescript-eslint/typescript-estree': 5.48.1_typescript@4.9.4
1400 1394 eslint: 8.31.0
1401 1395 eslint-scope: 5.1.1
1402 1396 eslint-utils: 3.0.0_eslint@8.31.0
... ... @@ -1406,11 +1400,11 @@ packages:
1406 1400 - typescript
1407 1401 dev: true
1408 1402  
1409   - /@typescript-eslint/visitor-keys/5.48.0:
1410   - resolution: {integrity: sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==}
  1403 + /@typescript-eslint/visitor-keys/5.48.1:
  1404 + resolution: {integrity: sha512-Ns0XBwmfuX7ZknznfXozgnydyR8F6ev/KEGePP4i74uL3ArsKbEhJ7raeKr1JSa997DBDwol/4a0Y+At82c9dA==}
1411 1405 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1412 1406 dependencies:
1413   - '@typescript-eslint/types': 5.48.0
  1407 + '@typescript-eslint/types': 5.48.1
1414 1408 eslint-visitor-keys: 3.3.0
1415 1409 dev: true
1416 1410  
... ... @@ -1498,30 +1492,30 @@ packages:
1498 1492 vue: 3.2.45
1499 1493 dev: true
1500 1494  
1501   - /@volar/language-core/1.0.22:
1502   - resolution: {integrity: sha512-hiJeCOqxNdtG/04FRGLGI9H9DVz2l6cTqPDBzwqplHXAWfMxjzUaGUrn9sfTG7YMFNZUgK4EYxJnRfhqdtbSFQ==}
  1495 + /@volar/language-core/1.0.24:
  1496 + resolution: {integrity: sha512-vTN+alJiWwK0Pax6POqrmevbtFW2dXhjwWiW/MW4f48eDYPLdyURWcr8TixO7EN/nHsUBj2udT7igFKPtjyAKg==}
1503 1497 dependencies:
1504   - '@volar/source-map': 1.0.22
  1498 + '@volar/source-map': 1.0.24
1505 1499 muggle-string: 0.1.0
1506 1500 dev: true
1507 1501  
1508   - /@volar/source-map/1.0.22:
1509   - resolution: {integrity: sha512-cv4gypHSP4MWVR82ed/+1IpI6794qAl0Q0+KJ+VGMVF8rVugsiF9QbyMCgjel9wNRsssQsazzsf6txOR9vHQiw==}
  1502 + /@volar/source-map/1.0.24:
  1503 + resolution: {integrity: sha512-Qsv/tkplx18pgBr8lKAbM1vcDqgkGKQzbChg6NW+v0CZc3G7FLmK+WrqEPzKlN7Cwdc6XVL559Nod8WKAfKr4A==}
1510 1504 dependencies:
1511 1505 muggle-string: 0.1.0
1512 1506 dev: true
1513 1507  
1514   - /@volar/typescript/1.0.22:
1515   - resolution: {integrity: sha512-VPyEicealSD4gqlE5/UQ1j3ietsO6Hfat40KtUEh/K+XEZ7h02b1KgFV64YEuBkBOaZ5hgvRW/WXKtQgXCl7Iw==}
  1508 + /@volar/typescript/1.0.24:
  1509 + resolution: {integrity: sha512-f8hCSk+PfKR1/RQHxZ79V1NpDImHoivqoizK+mstphm25tn/YJ/JnKNjZHB+o21fuW0yKlI26NV3jkVb2Cc/7A==}
1516 1510 dependencies:
1517   - '@volar/language-core': 1.0.22
  1511 + '@volar/language-core': 1.0.24
1518 1512 dev: true
1519 1513  
1520   - /@volar/vue-language-core/1.0.22:
1521   - resolution: {integrity: sha512-Ki0G/ZdBj2/GLw+/VVH3n9XR/JL6krMIth02EekFn6JV4PGN3mNxbvoh6lOPSDZLR6biOU5nJPnnjpKy8nuXhw==}
  1514 + /@volar/vue-language-core/1.0.24:
  1515 + resolution: {integrity: sha512-2NTJzSgrwKu6uYwPqLiTMuAzi7fAY3yFy5PJ255bGJc82If0Xr+cW8pC80vpjG0D/aVLmlwAdO4+Ya2BI8GdDg==}
1522 1516 dependencies:
1523   - '@volar/language-core': 1.0.22
1524   - '@volar/source-map': 1.0.22
  1517 + '@volar/language-core': 1.0.24
  1518 + '@volar/source-map': 1.0.24
1525 1519 '@vue/compiler-dom': 3.2.45
1526 1520 '@vue/compiler-sfc': 3.2.45
1527 1521 '@vue/reactivity': 3.2.45
... ... @@ -1530,11 +1524,11 @@ packages:
1530 1524 vue-template-compiler: 2.7.14
1531 1525 dev: true
1532 1526  
1533   - /@volar/vue-typescript/1.0.22:
1534   - resolution: {integrity: sha512-2T1o5z86PAev31OMtVOv/qp4P3ZVl9ln/2KTmykQE8Fh4A5F+868MW4nf5J7XQ6RNyx7RH9LhzgjvbqJpAfiYw==}
  1527 + /@volar/vue-typescript/1.0.24:
  1528 + resolution: {integrity: sha512-9a25oHDvGaNC0okRS47uqJI6FxY4hUQZUsxeOUFHcqVxZEv8s17LPuP/pMMXyz7jPygrZubB/qXqHY5jEu/akA==}
1535 1529 dependencies:
1536   - '@volar/typescript': 1.0.22
1537   - '@volar/vue-language-core': 1.0.22
  1530 + '@volar/typescript': 1.0.24
  1531 + '@volar/vue-language-core': 1.0.24
1538 1532 dev: true
1539 1533  
1540 1534 /@vue/babel-helper-vue-transform-on/1.0.2:
... ... @@ -1583,7 +1577,7 @@ packages:
1583 1577 '@vue/shared': 3.2.45
1584 1578 estree-walker: 2.0.2
1585 1579 magic-string: 0.25.9
1586   - postcss: 8.4.20
  1580 + postcss: 8.4.21
1587 1581 source-map: 0.6.1
1588 1582  
1589 1583 /@vue/compiler-ssr/3.2.45:
... ... @@ -2066,7 +2060,7 @@ packages:
2066 2060 hasBin: true
2067 2061 dev: true
2068 2062  
2069   - /autoprefixer/10.4.13_postcss@8.4.20:
  2063 + /autoprefixer/10.4.13_postcss@8.4.21:
2070 2064 resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
2071 2065 engines: {node: ^10 || ^12 || >=14}
2072 2066 hasBin: true
... ... @@ -2078,7 +2072,7 @@ packages:
2078 2072 fraction.js: 4.2.0
2079 2073 normalize-range: 0.1.2
2080 2074 picocolors: 1.0.0
2081   - postcss: 8.4.20
  2075 + postcss: 8.4.21
2082 2076 postcss-value-parser: 4.2.0
2083 2077 dev: true
2084 2078  
... ... @@ -3013,12 +3007,12 @@ packages:
3013 3007 eslint: 8.31.0
3014 3008 dev: true
3015 3009  
3016   - /eslint-define-config/1.13.0:
3017   - resolution: {integrity: sha512-d0BfmPGBcMusfiz6QY/piAhWaEyJriJtvk9SHfuJzI7am9k4ce07SfmPkpcR0ckXNyu4FBons10akOO2Tx+X+Q==}
  3010 + /eslint-define-config/1.14.0:
  3011 + resolution: {integrity: sha512-NREt5SzMwKmLAY28YdaqIiTSJxfPpuZ+1ZLJxY2Wbj02dYF4QX81z0q9MPMjZB8C+SlCu66qAhcPpFJyhXOiuA==}
3018 3012 engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13', pnpm: '>= 7.0.0'}
3019 3013 dev: true
3020 3014  
3021   - /eslint-plugin-prettier/4.2.1_32m5uc2milwdw3tnkcq5del26y:
  3015 + /eslint-plugin-prettier/4.2.1_iu5s7nk6dw7o3tajefwfiqfmge:
3022 3016 resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
3023 3017 engines: {node: '>=12.0.0'}
3024 3018 peerDependencies:
... ... @@ -3031,7 +3025,7 @@ packages:
3031 3025 dependencies:
3032 3026 eslint: 8.31.0
3033 3027 eslint-config-prettier: 8.6.0_eslint@8.31.0
3034   - prettier: 2.8.1
  3028 + prettier: 2.8.2
3035 3029 prettier-linter-helpers: 1.0.0
3036 3030 dev: true
3037 3031  
... ... @@ -4506,7 +4500,7 @@ packages:
4506 4500 dev: false
4507 4501  
4508 4502 /nanoid/3.3.4:
4509   - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/nanoid/-/nanoid-3.3.4.tgz}
  4503 + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
4510 4504 engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
4511 4505 hasBin: true
4512 4506  
... ... @@ -4848,8 +4842,8 @@ packages:
4848 4842 dependencies:
4849 4843 htmlparser2: 8.0.1
4850 4844 js-tokens: 8.0.0
4851   - postcss: 8.4.20
4852   - postcss-safe-parser: 6.0.0_postcss@8.4.20
  4845 + postcss: 8.4.21
  4846 + postcss-safe-parser: 6.0.0_postcss@8.4.21
4853 4847 dev: true
4854 4848  
4855 4849 /postcss-media-query-parser/0.2.3:
... ... @@ -4868,22 +4862,22 @@ packages:
4868 4862 resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==}
4869 4863 dev: true
4870 4864  
4871   - /postcss-safe-parser/6.0.0_postcss@8.4.20:
  4865 + /postcss-safe-parser/6.0.0_postcss@8.4.21:
4872 4866 resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==}
4873 4867 engines: {node: '>=12.0'}
4874 4868 peerDependencies:
4875 4869 postcss: ^8.3.3
4876 4870 dependencies:
4877   - postcss: 8.4.20
  4871 + postcss: 8.4.21
4878 4872 dev: true
4879 4873  
4880   - /postcss-scss/4.0.6_postcss@8.4.20:
  4874 + /postcss-scss/4.0.6_postcss@8.4.21:
4881 4875 resolution: {integrity: sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==}
4882 4876 engines: {node: '>=12.0'}
4883 4877 peerDependencies:
4884 4878 postcss: ^8.4.19
4885 4879 dependencies:
4886   - postcss: 8.4.20
  4880 + postcss: 8.4.21
4887 4881 dev: true
4888 4882  
4889 4883 /postcss-selector-parser/6.0.10:
... ... @@ -4902,12 +4896,12 @@ packages:
4902 4896 util-deprecate: 1.0.2
4903 4897 dev: true
4904 4898  
4905   - /postcss-sorting/7.0.1_postcss@8.4.20:
4906   - resolution: {integrity: sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==}
  4899 + /postcss-sorting/8.0.1_postcss@8.4.21:
  4900 + resolution: {integrity: sha512-go9Zoxx7KQH+uLrJ9xa5wRErFeXu01ydA6O8m7koPXkmAN7Ts//eRcIqjo0stBR4+Nir2gMYDOWAOx7O5EPUZA==}
4907 4901 peerDependencies:
4908   - postcss: ^8.3.9
  4902 + postcss: ^8.4.20
4909 4903 dependencies:
4910   - postcss: 8.4.20
  4904 + postcss: 8.4.21
4911 4905 dev: true
4912 4906  
4913 4907 /postcss-value-parser/4.2.0:
... ... @@ -4924,8 +4918,8 @@ packages:
4924 4918 supports-color: 3.2.3
4925 4919 dev: true
4926 4920  
4927   - /postcss/8.4.20:
4928   - resolution: {integrity: sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/postcss/-/postcss-8.4.20.tgz}
  4921 + /postcss/8.4.21:
  4922 + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
4929 4923 engines: {node: ^10 || ^12 || >=14}
4930 4924 dependencies:
4931 4925 nanoid: 3.3.4
... ... @@ -4983,8 +4977,8 @@ packages:
4983 4977 fast-diff: 1.2.0
4984 4978 dev: true
4985 4979  
4986   - /prettier/2.8.1:
4987   - resolution: {integrity: sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==}
  4980 + /prettier/2.8.2:
  4981 + resolution: {integrity: sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==}
4988 4982 engines: {node: '>=10.13.0'}
4989 4983 hasBin: true
4990 4984 dev: true
... ... @@ -5428,7 +5422,7 @@ packages:
5428 5422 dev: true
5429 5423  
5430 5424 /source-map-js/1.0.2:
5431   - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/source-map-js/-/source-map-js-1.0.2.tgz}
  5425 + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
5432 5426 engines: {node: '>=0.10.0'}
5433 5427  
5434 5428 /source-map-resolve/0.5.3:
... ... @@ -5636,13 +5630,13 @@ packages:
5636 5630 stylelint-config-recommended: 9.0.0_stylelint@14.16.1
5637 5631 dev: true
5638 5632  
5639   - /stylelint-order/5.0.0_stylelint@14.16.1:
5640   - resolution: {integrity: sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==}
  5633 + /stylelint-order/6.0.1_stylelint@14.16.1:
  5634 + resolution: {integrity: sha512-C9gJDZArRBZvn+4MPgggwYTp7dK49WPnYa5+6tBEkZnW/YWj4xBVNJdQjIik14w5orlF9RqFpYDHN0FPWIFOSQ==}
5641 5635 peerDependencies:
5642 5636 stylelint: ^14.0.0
5643 5637 dependencies:
5644   - postcss: 8.4.20
5645   - postcss-sorting: 7.0.1_postcss@8.4.20
  5638 + postcss: 8.4.21
  5639 + postcss-sorting: 8.0.1_postcss@8.4.21
5646 5640 stylelint: 14.16.1
5647 5641 dev: true
5648 5642  
... ... @@ -5651,7 +5645,7 @@ packages:
5651 5645 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
5652 5646 hasBin: true
5653 5647 dependencies:
5654   - '@csstools/selector-specificity': 2.0.2_2xshye3abirqjlplmebvmaxyna
  5648 + '@csstools/selector-specificity': 2.0.2_wajs5nedgkikc5pcuwett7legi
5655 5649 balanced-match: 2.0.0
5656 5650 colord: 2.9.3
5657 5651 cosmiconfig: 7.1.0
... ... @@ -5674,10 +5668,10 @@ packages:
5674 5668 micromatch: 4.0.5
5675 5669 normalize-path: 3.0.0
5676 5670 picocolors: 1.0.0
5677   - postcss: 8.4.20
  5671 + postcss: 8.4.21
5678 5672 postcss-media-query-parser: 0.2.3
5679 5673 postcss-resolve-nested-selector: 0.1.1
5680   - postcss-safe-parser: 6.0.0_postcss@8.4.20
  5674 + postcss-safe-parser: 6.0.0_postcss@8.4.21
5681 5675 postcss-selector-parser: 6.0.11
5682 5676 postcss-value-parser: 4.2.0
5683 5677 resolve-from: 5.0.0
... ... @@ -6211,7 +6205,7 @@ packages:
6211 6205 dependencies:
6212 6206 '@types/node': 18.11.18
6213 6207 esbuild: 0.16.5
6214   - postcss: 8.4.20
  6208 + postcss: 8.4.21
6215 6209 resolve: 1.22.1
6216 6210 rollup: 3.9.1
6217 6211 sass: 1.57.1
... ... @@ -6281,14 +6275,14 @@ packages:
6281 6275 he: 1.2.0
6282 6276 dev: true
6283 6277  
6284   - /vue-tsc/1.0.22_typescript@4.9.4:
6285   - resolution: {integrity: sha512-xSxwgWR3czhv7sLKHWu6lzj9Xq6AtsCURVL45AY4TLGFszv2L2YlMgygXvqslyCM5bz9cyoIKSaZnzHqHTHjzA==}
  6278 + /vue-tsc/1.0.24_typescript@4.9.4:
  6279 + resolution: {integrity: sha512-mmU1s5SAqE1nByQAiQnao9oU4vX+mSdsgI8H57SfKH6UVzq/jP9+Dbi2GaV+0b4Cn361d2ln8m6xeU60ApiEXg==}
6286 6280 hasBin: true
6287 6281 peerDependencies:
6288 6282 typescript: '*'
6289 6283 dependencies:
6290   - '@volar/vue-language-core': 1.0.22
6291   - '@volar/vue-typescript': 1.0.22
  6284 + '@volar/vue-language-core': 1.0.24
  6285 + '@volar/vue-typescript': 1.0.24
6292 6286 typescript: 4.9.4
6293 6287 dev: true
6294 6288  
... ... @@ -6314,8 +6308,8 @@ packages:
6314 6308 '@vue/server-renderer': 3.2.45_vue@3.2.45
6315 6309 '@vue/shared': 3.2.45
6316 6310  
6317   - /vxe-table/4.3.7_vue@3.2.45+xe-utils@3.5.7:
6318   - resolution: {integrity: sha512-v+d7eEQ5uqtVTQCts4xkW0S15LZcIuEukYHGXI53SdoUj2gLFggPYAxQr1y659CM/ESRWPz9LNVHpd97KkjGHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/vxe-table/-/vxe-table-4.3.7.tgz}
  6311 + /vxe-table/4.3.9_vue@3.2.45+xe-utils@3.5.7:
  6312 + resolution: {integrity: sha512-Ns7Ooa7lOHBpks90i0k0BMNyxfMpUo39ryxTgKE41X3xVnI9tGQs2U6+klfDlsuqYfmG3ibyzHN3OCrWbbKo4Q==}
6319 6313 peerDependencies:
6320 6314 vue: ^3.2.28
6321 6315 xe-utils: ^3.5.0
... ...
yudao-ui-admin-vue3/src/views/Profile/Index.vue
... ... @@ -8,7 +8,7 @@
8 8 </template>
9 9 <ProfileUser />
10 10 </el-card>
11   - <el-card class="w-2/3 user" style="margin-left: 10px" shadow="hover">
  11 + <el-card class="w-2/3 user ml-3" shadow="hover">
12 12 <template #header>
13 13 <div class="card-header">
14 14 <span>{{ t('profile.info.title') }}</span>
... ...
yudao-ui-admin-vue3/src/views/infra/codegen/components/Preview.vue
... ... @@ -13,7 +13,7 @@
13 13 />
14 14 </el-scrollbar>
15 15 </el-card>
16   - <el-card class="w-3/4" style="margin-left: 10px" :gutter="12" shadow="hover">
  16 + <el-card class="w-3/4 ml-3" :gutter="12" shadow="hover">
17 17 <el-tabs v-model="preview.activeName">
18 18 <el-tab-pane
19 19 v-for="item in previewCodegen"
... ...
yudao-ui-admin-vue3/src/views/system/dict/index.vue
... ... @@ -38,7 +38,7 @@
38 38 <!-- @星语:分页和列表重叠在一起了 -->
39 39 </el-card>
40 40 <!-- ====== 字典数据 ====== -->
41   - <el-card class="w-1/2 dict" style="margin-left: 10px" :gutter="12" shadow="hover">
  41 + <el-card class="w-1/2 dict ml-3" :gutter="12" shadow="hover">
42 42 <template #header>
43 43 <div class="card-header">
44 44 <span>字典数据</span>
... ...