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
+```
+
+
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
+```
+
+
+
+
+
+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群: [](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) [](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) [](https://jq.qq.com/?_wv=1027&k=qdT1Ojpz) [](https://jq.qq.com/?_wv=1027&k=nw3OiyXs) [](https://jq.qq.com/?_wv=1027&k=kiU5WDls) [](https://jq.qq.com/?_wv=1027&k=MtBy6YfT) [](https://jq.qq.com/?_wv=1027&k=FqImHgH2) [](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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+