Commit 0f9882680421b4316aab41088d9349f55d2aa78a
Committed by
Gitee
!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
yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
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<T> extends BaseMapper<T> { |
| 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> | ... | ... |