diff --git a/README.md b/README.md index 46fafa6d..bae2ec6b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ + +# 源码更新技巧 +## 1、切换同步源码地址 (保持与官方同步),然后更新本地代码 + +```bash +git remote set-url origin https://gitee.com/czq1ac/RuoYi-Cloud_2023.git +``` +## 2、更新本地代码成功后,切换会原先的仓库地址,push更新 + +```bash +git remote set-url origin https://gitee.com/czq1ac/RuoYi-Cloud.git +``` + +

logo

diff --git a/pom.xml b/pom.xml index 04e0c993..5e508250 100644 --- a/pom.xml +++ b/pom.xml @@ -216,6 +216,7 @@ ruoyi-modules ruoyi-api ruoyi-common + ruoyi-web pom diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteFileService.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteFileService.java index ae56a54a..931f87e1 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteFileService.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteFileService.java @@ -15,7 +15,7 @@ import com.ruoyi.system.api.factory.RemoteFileFallbackFactory; * * @author ruoyi */ -@FeignClient(contextId = "remoteFileService", value = ServiceNameConstants.FILE_SERVICE, fallbackFactory = RemoteFileFallbackFactory.class) +@FeignClient(contextId = "remoteFileService",url = "${feign.debug.url.file:}", value = ServiceNameConstants.FILE_SERVICE, fallbackFactory = RemoteFileFallbackFactory.class) public interface RemoteFileService { /** diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteLogService.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteLogService.java index 3402afad..51d3147f 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteLogService.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteLogService.java @@ -16,7 +16,7 @@ import com.ruoyi.system.api.factory.RemoteLogFallbackFactory; * * @author ruoyi */ -@FeignClient(contextId = "remoteLogService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteLogFallbackFactory.class) +@FeignClient(contextId = "remoteLogService",url = "${feign.debug.url.system:}", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteLogFallbackFactory.class) public interface RemoteLogService { /** diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java index e7fe34c5..04ceb259 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java @@ -18,7 +18,7 @@ import com.ruoyi.system.api.model.LoginUser; * * @author ruoyi */ -@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteUserFallbackFactory.class) +@FeignClient(contextId = "remoteUserService",url = "${feign.debug.url.system:}", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteUserFallbackFactory.class) public interface RemoteUserService { /** diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index b70798a7..f6c22a86 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -5,7 +5,7 @@ "author": "若依", "license": "MIT", "scripts": { - "dev": "vue-cli-service serve", + "dev": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve", "build:prod": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", "preview": "node build/index.js --preview", diff --git a/ruoyi-ui/src/views/login.vue b/ruoyi-ui/src/views/login.vue index 773f52c0..05f96e70 100644 --- a/ruoyi-ui/src/views/login.vue +++ b/ruoyi-ui/src/views/login.vue @@ -87,11 +87,11 @@ export default { ], code: [{ required: true, trigger: "change", message: "请输入验证码" }] }, - loading: false, + // loading: true, // 验证码开关 captchaEnabled: true, // 注册开关 - register: false, + register: true, redirect: undefined }; }, diff --git a/ruoyi-web/pom.xml b/ruoyi-web/pom.xml new file mode 100644 index 00000000..e4c85def --- /dev/null +++ b/ruoyi-web/pom.xml @@ -0,0 +1,23 @@ + + + + ruoyi + com.ruoyi + 3.6.3 + + 4.0.0 + + ruoyi-web + pom + + + ruoyi-单体应用业务模块 + + + ruoyi-web-admin + + + + \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/README.md b/ruoyi-web/ruoyi-web-admin/README.md new file mode 100644 index 00000000..30c6b603 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/README.md @@ -0,0 +1,176 @@ +# RuoYi-Cloud SPRING BOOT单体项目改造 问题记录 +## 1、项目配置集中管理;删除无用不配置;不注册服务中心 +## 2、项目单体路由规则修改,和cloud项目路由规则保持一致 + 参考 com.ruoyi.web.admin.config.AutoPrefixUrlMapping [AutoPrefixUrlMapping](src/main/java/com/ruoyi/web/admin/config/AutoPrefixUrlMapping.java) +```java + // # 认证中心 uri: lb://ruoyi-auth + routesMap.put("com.ruoyi.auth.controller", "/auth"); + // # 代码生成 uri: lb://ruoyi-gen + routesMap.put("com.ruoyi.gen.controller", "/code"); + // # 定时任务 uri: lb://ruoyi-job + routesMap.put("com.ruoyi.job.controller", "/schedule"); + // # 系统模块 uri: lb://ruoyi-system + routesMap.put("com.ruoyi.system.controller", "/system"); + // # 文件服务 uri: lb://ruoyi-file + routesMap.put("com.ruoyi.file.controller", "/file"); + +``` +## 3、fegin 本地调用配置切换;增加url 属性 + +```java +@FeignClient(contextId = "remoteFileService",url = "${feign.debug.url.file:}", value = ServiceNameConstants.FILE_SERVICE, fallbackFactory = RemoteFileFallbackFactory.class) +public interface RemoteFileService +{ + +} +``` +## 3、gateway 验证码生成和校验功能迁移 +## 4、gateway 权限认证过滤器功能迁移 +## 5、解决验证码校验问题和 filter异常处理问题 +## 6、验证码防止重复刷新漏洞登录采用字符验证码;开启注册 + + +# 源码更新技巧 +## 1、切换同步源码地址 (保持与官方同步),然后更新本地代码 + +```bash +git remote set-url origin https://gitee.com/czq1ac/RuoYi-Cloud_2023.git +``` +## 2、更新本地代码成功后,切换会原先的仓库地址,push更新 + +```bash +git remote set-url origin https://gitee.com/czq1ac/RuoYi-Cloud.git +``` + + +

+ logo +

+

RuoYi v3.6.3

+

基于 Vue/Element UI 和 Spring Boot/Spring Cloud & Alibaba 前后端分离的分布式微服务架构

+

+ + + +

+ +## 平台简介 + +若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 + +* 采用前后端分离的模式,微服务版本前端(基于 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue))。 +* 后端采用Spring Boot、Spring Cloud & Alibaba。 +* 注册中心、配置中心选型Nacos,权限认证使用Redis。 +* 流量控制框架选型Sentinel,分布式事务选型Seata。 +* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Cloud-Vue3](https://github.com/yangzongzhuan/RuoYi-Cloud-Vue3),保持同步更新。 +* 如需不分离应用,请移步 [RuoYi](https://gitee.com/y_project/RuoYi),如需分离应用,请移步 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) +* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)   +* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)   + +#### 友情链接 [若依/RuoYi-Cloud](https://gitee.com/zhangmrit/ruoyi-cloud) Ant Design版本。 + +## 系统模块 + +~~~ +com.ruoyi +├── ruoyi-ui // 前端框架 [80] +├── ruoyi-gateway // 网关模块 [8080] +├── ruoyi-auth // 认证中心 [9200] +├── ruoyi-api // 接口模块 +│ └── ruoyi-api-system // 系统接口 +├── ruoyi-common // 通用模块 +│ └── ruoyi-common-core // 核心模块 +│ └── ruoyi-common-datascope // 权限范围 +│ └── ruoyi-common-datasource // 多数据源 +│ └── ruoyi-common-log // 日志记录 +│ └── ruoyi-common-redis // 缓存服务 +│ └── ruoyi-common-seata // 分布式事务 +│ └── ruoyi-common-security // 安全模块 +│ └── ruoyi-common-swagger // 系统接口 +├── ruoyi-modules // 业务模块 +│ └── ruoyi-system // 系统模块 [9201] +│ └── ruoyi-gen // 代码生成 [9202] +│ └── ruoyi-job // 定时任务 [9203] +│ └── ruoyi-file // 文件服务 [9300] +├── ruoyi-visual // 图形化管理模块 +│ └── ruoyi-visual-monitor // 监控中心 [9100] +├──pom.xml // 公共依赖 +~~~ + +## 架构图 + + + +## 内置功能 + +1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +3. 岗位管理:配置系统用户所属担任职务。 +4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7. 参数管理:对系统动态配置常用参数。 +8. 通知公告:系统通知公告信息发布维护。 +9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +10. 登录日志:系统登录日志记录查询包含登录异常。 +11. 在线用户:当前系统中活跃用户状态监控。 +12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 +14. 系统接口:根据业务代码自动生成相关的api接口文档。 +15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 +16. 在线构建器:拖动表单元素生成相应的HTML代码。 +17. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 + +## 在线体验 + +- admin/admin123 +- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 + +演示地址:http://ruoyi.vip +文档地址:http://doc.ruoyi.vip + +## 演示图 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +## 若依微服务交流群 + +QQ群: [![加入QQ群](https://img.shields.io/badge/已满-42799195-blue.svg)](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [![加入QQ群](https://img.shields.io/badge/已满-170157040-blue.svg)](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [![加入QQ群](https://img.shields.io/badge/已满-130643120-blue.svg)](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [![加入QQ群](https://img.shields.io/badge/已满-225920371-blue.svg)](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) [![加入QQ群](https://img.shields.io/badge/已满-201705537-blue.svg)](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) [![加入QQ群](https://img.shields.io/badge/已满-236543183-blue.svg)](https://jq.qq.com/?_wv=1027&k=qdT1Ojpz) [![加入QQ群](https://img.shields.io/badge/已满-213618602-blue.svg)](https://jq.qq.com/?_wv=1027&k=nw3OiyXs) [![加入QQ群](https://img.shields.io/badge/已满-148794840-blue.svg)](https://jq.qq.com/?_wv=1027&k=kiU5WDls) [![加入QQ群](https://img.shields.io/badge/已满-118752664-blue.svg)](https://jq.qq.com/?_wv=1027&k=MtBy6YfT) [![加入QQ群](https://img.shields.io/badge/已满-101038945-blue.svg)](https://jq.qq.com/?_wv=1027&k=FqImHgH2) [![加入QQ群](https://img.shields.io/badge/128355254-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G4jZ4EtdT50PhnMBudTnEwgonxkXOscJ&authKey=FkGHYfoTKlGE6wHdKdjH9bVoOgQjtLP9WM%2Fj7pqGY1msoqw9uxDiBo39E2mLgzYg&noverify=0&group_code=128355254) 点击按钮入群。 \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/pom.xml b/ruoyi-web/ruoyi-web-admin/pom.xml new file mode 100644 index 00000000..24d00986 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/pom.xml @@ -0,0 +1,58 @@ + + + + ruoyi-web + com.ruoyi + 3.6.3 + + 4.0.0 + + ruoyi-web-admin + + + + + + com.ruoyi + ruoyi-modules-system + ${ruoyi.version} + + + + + com.ruoyi + ruoyi-modules-job + ${ruoyi.version} + + + + + com.ruoyi + ruoyi-modules-gen + ${ruoyi.version} + + + + + com.ruoyi + ruoyi-modules-file + ${ruoyi.version} + + + + + com.ruoyi + ruoyi-auth + ${ruoyi.version} + + + + + pro.fessional + kaptcha + + + + \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/RuoYiWebAdminApplication.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/RuoYiWebAdminApplication.java new file mode 100644 index 00000000..1317af2c --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/RuoYiWebAdminApplication.java @@ -0,0 +1,37 @@ +package com.ruoyi.web.admin; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +import com.ruoyi.common.security.annotation.EnableCustomConfig; +import com.ruoyi.common.security.annotation.EnableRyFeignClients; +import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2; + +/** + * 系统模块 + * + * @author ruoyi + */ +@EnableCustomConfig +@EnableCustomSwagger2 +@EnableRyFeignClients +@SpringBootApplication +@ComponentScan("com.ruoyi") +public class RuoYiWebAdminApplication +{ + public static void main(String[] args) + { + SpringApplication.run(RuoYiWebAdminApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 系统模块启动成功 ლ(´ڡ`ლ)゙ \n" + + " .-------. ____ __ \n" + + " | _ _ \\ \\ \\ / / \n" + + " | ( ' ) | \\ _. / ' \n" + + " |(_ o _) / _( )_ .' \n" + + " | (_,_).' __ ___(_ o _)' \n" + + " | |\\ \\ | || |(_,_)' \n" + + " | | \\ `' /| `-' / \n" + + " | | \\ / \\ / \n" + + " ''-' `'-' `-..-' "); + } +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/AutoPrefixConfiguration.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/AutoPrefixConfiguration.java new file mode 100644 index 00000000..0664e5be --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/AutoPrefixConfiguration.java @@ -0,0 +1,20 @@ +package com.ruoyi.web.admin.config; + +import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + + +/** + * 自动补全路由前缀配置类 + * @author 1763113879@qq.com + * @version V2.1 + * @since 2.1.0 2023/11/15 14:48 + */ +@Component +public class AutoPrefixConfiguration implements WebMvcRegistrations { + @Override + public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { + return new AutoPrefixUrlMapping(); + } +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/AutoPrefixUrlMapping.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/AutoPrefixUrlMapping.java new file mode 100644 index 00000000..bff6ff76 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/AutoPrefixUrlMapping.java @@ -0,0 +1,84 @@ +package com.ruoyi.web.admin.config; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +/** + * 按照目录结构/包名添加前缀 + * + * @author 1763113879@qq.com + * @version V2.1 + * @since 2.1.0 2023/11/15 14:20 + */ +public class AutoPrefixUrlMapping extends RequestMappingHandlerMapping { + + // 路由映射 + static Map routesMap = new HashMap(); + + static { + // # 认证中心 uri: lb://ruoyi-auth + routesMap.put("com.ruoyi.auth.controller", "/auth"); + // # 代码生成 uri: lb://ruoyi-gen + routesMap.put("com.ruoyi.gen.controller", "/code"); + // # 定时任务 uri: lb://ruoyi-job + routesMap.put("com.ruoyi.job.controller", "/schedule"); + // # 系统模块 uri: lb://ruoyi-system + routesMap.put("com.ruoyi.system.controller", "/system"); + // # 文件服务 uri: lb://ruoyi-file + routesMap.put("com.ruoyi.file.controller", "/file"); + + } + + /** + * 重写方法路由获取 + * + * @param method + * @param handlerType + * @return + */ + @Override + protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { + RequestMappingInfo mappingInfo = super.getMappingForMethod(method, handlerType); + + if (Objects.nonNull(mappingInfo)) { + String prefix = this.getPrefix(handlerType); + + if (prefix != null) { + String[] paths = mappingInfo.getPatternValues() + .stream() + .map(path -> prefix + path) + .toArray(String[]::new); + + return mappingInfo.mutate() + .paths(paths) + .build(); + } + } + + return mappingInfo; + } + + /** + * 获取方法路由前缀 + * + * @param handleType + * @return + */ + private String getPrefix(Class handleType) { + String packageName = handleType.getPackage() + .getName(); + // 使用foreach循环遍历HashMap 符合路由规则的添加前缀 + for (String key : routesMap.keySet()) { + if (packageName.startsWith(key)) { + return routesMap.get(key); + } + } + return null; + } + +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/CaptchaConfig.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/CaptchaConfig.java new file mode 100644 index 00000000..0633e21d --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/CaptchaConfig.java @@ -0,0 +1,99 @@ +package com.ruoyi.web.admin.config; + +import java.util.Properties; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; + +import static com.google.code.kaptcha.Constants.KAPTCHA_BORDER; +import static com.google.code.kaptcha.Constants.KAPTCHA_BORDER_COLOR; +import static com.google.code.kaptcha.Constants.KAPTCHA_IMAGE_HEIGHT; +import static com.google.code.kaptcha.Constants.KAPTCHA_IMAGE_WIDTH; +import static com.google.code.kaptcha.Constants.KAPTCHA_NOISE_COLOR; +import static com.google.code.kaptcha.Constants.KAPTCHA_NOISE_IMPL; +import static com.google.code.kaptcha.Constants.KAPTCHA_OBSCURIFICATOR_IMPL; +import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_CONFIG_KEY; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_IMPL; + +/** + * 验证码配置 + * + * @author ruoyi + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.web.admin.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/CustomHttpServletRequest.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/CustomHttpServletRequest.java new file mode 100644 index 00000000..7a9e5b21 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/CustomHttpServletRequest.java @@ -0,0 +1,58 @@ +package com.ruoyi.web.admin.config; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/** + * @author 1763113879@qq.com + * @version V2.1 + * @since 2.1.0 2023/11/16 13:07 + */ +public class CustomHttpServletRequest extends HttpServletRequestWrapper { + + private Map headers=new HashMap<>(); + + public CustomHttpServletRequest(HttpServletRequest request){ + super(request); + } + + public void addHeader(String name,String value){ + headers.put(name, value); + } + + @Override + public String getHeader(String name) { + String value=super.getHeader(name); + + if (headers.containsKey(name)){ + value=headers.get(name); + } + + return value; + } + + @Override + public Enumeration getHeaderNames() { + List names= Collections.list(super.getHeaderNames()); + names.addAll(headers.keySet()); + + return Collections.enumeration(names); + } + + @Override + public Enumeration getHeaders(String name) { + List list= Collections.list(super.getHeaders(name)); + + if (headers.containsKey(name)){ + list.add(headers.get(name)); + } + + return Collections.enumeration(list); + } +} \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/KaptchaTextCreator.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/KaptchaTextCreator.java new file mode 100644 index 00000000..aba30e8c --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/KaptchaTextCreator.java @@ -0,0 +1,76 @@ +package com.ruoyi.web.admin.config; + +import java.util.Random; + +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author ruoyi + */ +public class KaptchaTextCreator extends DefaultTextCreator +{ + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + Random random = new Random(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = random.nextInt(3); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if ((x != 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else if (randomoperands == 2) + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/CaptchaProperties.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/CaptchaProperties.java new file mode 100644 index 00000000..1259eca0 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/CaptchaProperties.java @@ -0,0 +1,45 @@ +package com.ruoyi.web.admin.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * 验证码配置 + * + * @author ruoyi + */ +@Configuration +@ConfigurationProperties(prefix = "security.captcha") +public class CaptchaProperties +{ + /** + * 验证码开关 + */ + private Boolean enabled; + + /** + * 验证码类型(math 数组计算 char 字符) + */ + private String type; + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public String getType() + { + return type; + } + + public void setType(String type) + { + this.type = type; + } +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/IgnoreWhiteProperties.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/IgnoreWhiteProperties.java new file mode 100644 index 00000000..03e4c5d2 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/IgnoreWhiteProperties.java @@ -0,0 +1,33 @@ +package com.ruoyi.web.admin.config.properties; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * 放行白名单配置 + * + * @author ruoyi + */ +@Configuration +@ConfigurationProperties(prefix = "security.ignore") +public class IgnoreWhiteProperties +{ + /** + * 放行白名单配置,网关不校验此处的白名单 + */ + private List whites = new ArrayList<>(); + + public List getWhites() + { + return whites; + } + + public void setWhites(List whites) + { + this.whites = whites; + } +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/XssProperties.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/XssProperties.java new file mode 100644 index 00000000..fa713a24 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/config/properties/XssProperties.java @@ -0,0 +1,48 @@ +package com.ruoyi.web.admin.config.properties; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * XSS跨站脚本配置 + * + * @author ruoyi + */ +@Configuration +@ConfigurationProperties(prefix = "security.xss") +public class XssProperties +{ + /** + * Xss开关 + */ + private Boolean enabled; + + /** + * 排除路径 + */ + private List excludeUrls = new ArrayList<>(); + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public List getExcludeUrls() + { + return excludeUrls; + } + + public void setExcludeUrls(List excludeUrls) + { + this.excludeUrls = excludeUrls; + } +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/controller/CaptchaController.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/controller/CaptchaController.java new file mode 100644 index 00000000..728c9e6b --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/controller/CaptchaController.java @@ -0,0 +1,32 @@ +package com.ruoyi.web.admin.controller; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ruoyi.common.core.web.domain.AjaxResult; +import com.ruoyi.web.admin.service.ValidateCodeService; + +/** + * 验证码操作处理 + * + * @author ruoyi + */ +@RestController +public class CaptchaController { + + @Autowired + private ValidateCodeService validateCodeService; + + /** + * 生成验证码 + */ + @GetMapping("/code") + public AjaxResult getCode(HttpServletResponse response) throws IOException { + return validateCodeService.createCaptcha(); + } +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/ContentCachingRequestWrapperNew.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/ContentCachingRequestWrapperNew.java new file mode 100644 index 00000000..088871d6 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/ContentCachingRequestWrapperNew.java @@ -0,0 +1,98 @@ +package com.ruoyi.web.admin.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.util.ContentCachingRequestWrapper; + +/** + * @author 1763113879@qq.com + * @version V2.1 + * @since 2.1.0 2023/11/16 14:59 + */ +//继承ContentCachingRequestWrapper +public class ContentCachingRequestWrapperNew extends ContentCachingRequestWrapper { + + //原子变量,用来区分首次读取还是非首次 + private AtomicBoolean isFirst = new AtomicBoolean(true); + + public ContentCachingRequestWrapperNew(HttpServletRequest request) { + super(request); + } + + public ContentCachingRequestWrapperNew(HttpServletRequest request, int contentCacheLimit) { + super(request, contentCacheLimit); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + + if(isFirst.get()){ + //首次读取直接调父类的方法,这一次执行完之后 缓存流中有数据了 + //后续读取就读缓存流里的。 + isFirst.set(false); + return super.getInputStream(); + } + + //用缓存流构建一个新的输入流 + return new ServletInputStreamNew(super.getContentAsByteArray()); + } + + //参考自 DelegatingServletInputStream + class ServletInputStreamNew extends ServletInputStream{ + + private InputStream sourceStream; + + private boolean finished = false; + + + + public ServletInputStreamNew(byte [] bytes) { + //构建一个普通的输入流 + this.sourceStream = new ByteArrayInputStream(bytes); + } + + + @Override + public int read() throws IOException { + int data = this.sourceStream.read(); + if (data == -1) { + this.finished = true; + } + return data; + } + + @Override + public int available() throws IOException { + return this.sourceStream.available(); + } + + @Override + public void close() throws IOException { + super.close(); + this.sourceStream.close(); + } + + @Override + public boolean isFinished() { + return this.finished; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + throw new UnsupportedOperationException(); + } + } + +} \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/JwtAuthenticationTokenFilter.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 00000000..2ea878b0 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,209 @@ +package com.ruoyi.web.admin.filter; + +import java.io.BufferedReader; +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.core.constant.CacheConstants; +import com.ruoyi.common.core.constant.SecurityConstants; +import com.ruoyi.common.core.constant.TokenConstants; +import com.ruoyi.common.core.context.SecurityContextHolder; +import com.ruoyi.common.core.exception.ServiceException; +import com.ruoyi.common.core.utils.JwtUtils; +import com.ruoyi.common.core.utils.ServletUtils; +import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.redis.service.RedisService; +import com.ruoyi.common.security.auth.AuthUtil; +import com.ruoyi.common.security.service.TokenService; +import com.ruoyi.system.api.model.LoginUser; +import com.ruoyi.web.admin.config.CustomHttpServletRequest; +import com.ruoyi.web.admin.config.properties.CaptchaProperties; +import com.ruoyi.web.admin.config.properties.IgnoreWhiteProperties; +import com.ruoyi.web.admin.service.ValidateCodeService; + +import io.jsonwebtoken.Claims; + +/** + * token过滤器 验证token有效性 + * + * @author ruoyi + */ +@Component +@Order(-200) +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { + + private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class); + + // 排除过滤的 uri 地址,nacos自行添加 + @Autowired + private IgnoreWhiteProperties ignoreWhite; + + @Autowired + private RedisService redisService; + + @Autowired + private TokenService tokenService; + + @Autowired + private ValidateCodeService validateCodeService; + + @Autowired + private CaptchaProperties captchaProperties; + + private static final String CODE = "code"; + + private static final String UUID = "uuid"; + + private final static String[] VALIDATE_URL = new String[] {"/auth/login", "/auth/register"}; + + @Autowired + @Qualifier("handlerExceptionResolver") + private HandlerExceptionResolver resolver; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + String source = request.getHeader(SecurityConstants.FROM_SOURCE); + // 内部请求验证 不拦截 + if (StringUtils.equals(SecurityConstants.INNER, source)) { + chain.doFilter(request, response); + return; + } + + String requestURI = request.getRequestURI(); + + // 非登录/注册请求或验证码关闭,不处理 + if (StringUtils.equalsAnyIgnoreCase(requestURI, VALIDATE_URL) && captchaProperties.getEnabled()) { + + //,HttpServletRequst中的body内容只会读取一次,但是可能某些情境下可能会读取多次,替换request + ContentCachingRequestWrapperNew wrappedRequest = new ContentCachingRequestWrapperNew(request); + try { + String rspStr = resolveBodyFromRequest(wrappedRequest); + JSONObject obj = JSON.parseObject(rspStr); + validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID)); + } catch (Exception e) { + // throw new ServiceException(e.getMessage()); + resolver.resolveException(request, response, null, e); + return; + + } + + + request = wrappedRequest; + } + + // 跳过不需要验证的路径 + if (!StringUtils.matches(requestURI, ignoreWhite.getWhites())) { + + String token = getToken(request); + if (StringUtils.isEmpty(token)) { + throw new ServiceException("令牌不能为空"); + + } + Claims claims = JwtUtils.parseToken(token); + if (claims == null) { + throw new ServiceException("令牌已过期或验证不正确"); + + } + String userkey = JwtUtils.getUserKey(claims); + boolean islogin = redisService.hasKey(getTokenKey(userkey)); + if (!islogin) { + throw new ServiceException("登录状态已过期"); + } + String userid = JwtUtils.getUserId(claims); + String username = JwtUtils.getUserName(claims); + if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) { + throw new ServiceException("令牌验证失败"); + + } + + SecurityContextHolder.setUserId(userid); + SecurityContextHolder.setUserName(username); + SecurityContextHolder.setUserKey(userkey); + + CustomHttpServletRequest customHttpServletRequest = new CustomHttpServletRequest(request); + // 设置用户信息到请求 + addHeader(customHttpServletRequest, SecurityConstants.USER_KEY, userkey); + addHeader(customHttpServletRequest, SecurityConstants.DETAILS_USER_ID, userid); + addHeader(customHttpServletRequest, SecurityConstants.DETAILS_USERNAME, username); + //转换为自定义的request + request = customHttpServletRequest; + + if (StringUtils.isNotEmpty(token)) { + LoginUser loginUser = AuthUtil.getLoginUser(token); + if (StringUtils.isNotNull(loginUser)) { + AuthUtil.verifyLoginUserExpire(loginUser); + SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser); + } + } + + } + + chain.doFilter(request, response); + } + + /** + * 获取缓存key + */ + private String getTokenKey(String token) { + return CacheConstants.LOGIN_TOKEN_KEY + token; + } + + /** + * 获取请求token + */ + private String getToken(HttpServletRequest request) { + String token = request.getHeader(TokenConstants.AUTHENTICATION); + // 如果前端设置了令牌前缀,则裁剪掉前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) { + token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY); + } + return token; + } + + private void addHeader(CustomHttpServletRequest customHttpServletRequest, String name, Object value) { + if (value == null) { + return; + } + String valueStr = value.toString(); + String valueEncode = ServletUtils.urlEncode(valueStr); + customHttpServletRequest.addHeader(name, valueEncode); + } + + private String resolveBodyFromRequest(HttpServletRequest request) throws IOException { + + // 直接从HttpServletRequest的Reader流中获取RequestBody + BufferedReader reader = request.getReader(); + StringBuilder builder = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + builder.append(line); + line = reader.readLine(); + } + reader.close(); + String reqBody = builder.toString(); + return reqBody; + + // String requestBody = new String(request.getContentAsByteArray()); + // return requestBody; + + } + + +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/handler/GlobalAdminWebExceptionHandler.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/handler/GlobalAdminWebExceptionHandler.java new file mode 100644 index 00000000..ccdc165f --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/handler/GlobalAdminWebExceptionHandler.java @@ -0,0 +1,35 @@ +package com.ruoyi.web.admin.handler; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.ruoyi.common.core.exception.CaptchaException; +import com.ruoyi.common.core.web.domain.AjaxResult; + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +public class GlobalAdminWebExceptionHandler +{ + private static final Logger log = LoggerFactory.getLogger(GlobalAdminWebExceptionHandler.class); + + + /** + * 验证码异常 + */ + @ExceptionHandler(CaptchaException.class) + public AjaxResult handleCaptchaException(CaptchaException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}'", requestURI, e.getMessage()); + return AjaxResult.error(e.getMessage()); + } + +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/service/ValidateCodeService.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/service/ValidateCodeService.java new file mode 100644 index 00000000..c11713f4 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/service/ValidateCodeService.java @@ -0,0 +1,24 @@ +package com.ruoyi.web.admin.service; + +import java.io.IOException; + +import com.ruoyi.common.core.exception.CaptchaException; +import com.ruoyi.common.core.web.domain.AjaxResult; + +/** + * 验证码处理 + * + * @author ruoyi + */ +public interface ValidateCodeService +{ + /** + * 生成验证码 + */ + public AjaxResult createCaptcha() throws IOException, CaptchaException; + + /** + * 校验验证码 + */ + public void checkCaptcha(String key, String value) throws CaptchaException; +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/service/impl/ValidateCodeServiceImpl.java b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/service/impl/ValidateCodeServiceImpl.java new file mode 100644 index 00000000..b4733070 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/java/com/ruoyi/web/admin/service/impl/ValidateCodeServiceImpl.java @@ -0,0 +1,122 @@ +package com.ruoyi.web.admin.service.impl; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.FastByteArrayOutputStream; + +import com.google.code.kaptcha.Producer; +import com.ruoyi.common.core.constant.CacheConstants; +import com.ruoyi.common.core.constant.Constants; +import com.ruoyi.common.core.exception.CaptchaException; +import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.core.utils.sign.Base64; +import com.ruoyi.common.core.utils.uuid.IdUtils; +import com.ruoyi.common.core.web.domain.AjaxResult; +import com.ruoyi.common.redis.service.RedisService; +import com.ruoyi.web.admin.config.properties.CaptchaProperties; +import com.ruoyi.web.admin.service.ValidateCodeService; + +/** + * 验证码实现处理 + * + * @author ruoyi + */ +@Service +public class ValidateCodeServiceImpl implements ValidateCodeService +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisService redisService; + + @Autowired + private CaptchaProperties captchaProperties; + + /** + * 生成验证码 + */ + @Override + public AjaxResult createCaptcha() throws IOException, CaptchaException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = captchaProperties.getEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + String captchaType = captchaProperties.getType(); + // 生成验证码 + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } + + /** + * 校验验证码 + */ + @Override + public void checkCaptcha(String code, String uuid) throws CaptchaException + { + if (StringUtils.isEmpty(code)) + { + throw new CaptchaException("验证码不能为空"); + } + if (StringUtils.isEmpty(uuid)) + { + throw new CaptchaException("验证码已失效"); + } + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + String captcha = redisService.getCacheObject(verifyKey); + redisService.deleteObject(verifyKey); + + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException("验证码错误"); + } + } +} diff --git a/ruoyi-web/ruoyi-web-admin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-web/ruoyi-web-admin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..e69de29b diff --git a/ruoyi-web/ruoyi-web-admin/src/main/resources/application-local.yml b/ruoyi-web/ruoyi-web-admin/src/main/resources/application-local.yml new file mode 100644 index 00000000..ea0c8d4e --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/resources/application-local.yml @@ -0,0 +1,243 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: ruoyi-web-admin + # 版本 + version: 3.8.6 + # 版权年份 + copyrightYear: 2023 + # 实例演示开关 + demoEnabled: true + # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: ruoyi/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8080 + servlet: + # 应用的访问路径 + context-path: / + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 + +# 日志配置 +logging: + level: + com.ruoyi: debug + org.springframework: warn + +# 用户配置 +user: + password: + # 密码最大错误次数 + maxRetryCount: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + +# Spring配置 +spring: + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 10MB + # 设置总上传的文件大小 + max-request-size: 20MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + datasource: + druid: + stat-view-servlet: + enabled: true + loginUsername: admin + loginPassword: 123456 + dynamic: + druid: + initial-size: 5 + min-idle: 5 + maxActive: 20 + maxWait: 60000 + timeBetweenEvictionRunsMillis: 60000 + minEvictableIdleTimeMillis: 300000 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + filters: stat,slf4j + connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 + datasource: + # 主库数据源 + master: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/ry-config?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: chenroot + # 备库数据源 + slave: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/ry-config?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: chenroot + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 + password: + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + #禁用nacos,yml文件 + cloud: + nacos: + config: + enabled: false + refresh-enabled: false + discovery: + enabled: false + instance-enabled: false + + main: + allow-bean-definition-overriding: true +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 30 + +# MyBatis配置 +mybatis: + # 搜索指定包别名 + typeAliasesPackage: com.ruoyi.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + #configLocation: classpath:mybatis/mybatis-config.xml +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* +##################### 文件模块 ########################################### +# 本地文件上传 +file: + domain: http://127.0.0.1:9300 + path: ruoyi/uploadPath + prefix: /statics +# FastDFS配置 +fdfs: + domain: http://8.129.231.12 + soTimeout: 3000 + connectTimeout: 2000 + trackerList: 8.129.231.12:22122 +# Minio配置 +minio: + url: http://8.129.231.12:9000 + accessKey: minioadmin + secretKey: minioadmin + bucketName: test +######################## 文件模块 ######################################## +# feign 配置 +feign: + sentinel: + enabled: true + okhttp: + enabled: true + httpclient: + enabled: false + client: + config: + default: + connectTimeout: 10000 + readTimeout: 10000 + compression: + request: + enabled: true + response: + enabled: true + # 设置feign本地调用地址 + debug: + url: + system: http://localhost:${server.port}/system + file: http://localhost:${server.port}/file +# 暴露监控端点 +management: + endpoints: + web: + exposure: + include: '*' +# 安全配置 +security: + # 验证码 + captcha: + enabled: true + type: char + # 防止XSS攻击 + xss: + enabled: true + excludeUrls: + - /system/notice + # 不校验白名单 + ignore: + whites: + - /auth/logout + - /auth/login + - /auth/register + - /*/v2/api-docs + - /csrf + - /code +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /dev-api + title: 系统模块接口文档 + license: Powered By ruoyi + licenseUrl: https://ruoyi.vip diff --git a/ruoyi-web/ruoyi-web-admin/src/main/resources/banner.txt b/ruoyi-web/ruoyi-web-admin/src/main/resources/banner.txt new file mode 100644 index 00000000..fbd45f53 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/resources/banner.txt @@ -0,0 +1,10 @@ +Spring Boot Version: ${spring-boot.version} +Spring Application Name: ${spring.application.name} + _ _ + (_) | | + _ __ _ _ ___ _ _ _ ______ ___ _ _ ___ | |_ ___ _ __ ___ +| '__|| | | | / _ \ | | | || ||______|/ __|| | | |/ __|| __| / _ \| '_ ` _ \ +| | | |_| || (_) || |_| || | \__ \| |_| |\__ \| |_ | __/| | | | | | +|_| \__,_| \___/ \__, ||_| |___/ \__, ||___/ \__| \___||_| |_| |_| + __/ | __/ | + |___/ |___/ \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/src/main/resources/bootstrap.yml b/ruoyi-web/ruoyi-web-admin/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..605addad --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/resources/bootstrap.yml @@ -0,0 +1,34 @@ +# Tomcat +server: + port: 8080 + +# Spring +spring: + application: + # 应用名称 + name: ruoyi-web-admin + profiles: + # 环境配置 + active: local + + # Spring Web 公共配置 + autoconfigure: + exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure + mvc: + pathmatch: + matching-strategy: ant_path_matcher + cloud: + gateway: + discovery: + locator: + lowerCaseServiceId: true + enabled: true + discovery: + client: + simple: + instances: + ruoyi-file: + - uri: http://localhost:8080 + ruoyi-system: + - uri: http://localhost:9090 + diff --git a/ruoyi-web/ruoyi-web-admin/src/main/resources/logback.xml b/ruoyi-web/ruoyi-web-admin/src/main/resources/logback.xml new file mode 100644 index 00000000..a987a9af --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-web/ruoyi-web-admin/src/main/resources/mybatis/mybatis-config.xml b/ruoyi-web/ruoyi-web-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 00000000..ac47c038 --- /dev/null +++ b/ruoyi-web/ruoyi-web-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + +