当前位置: 首页 > news >正文

若依vue -【 44】

44 服务监控讲解

1 需求

        显示CPU、内存、服务器信息、Java虚拟机信息、磁盘状态的信息

2 前端

  1. RuoYi-Vue\ruoyi-ui\src\views\monitor\server\index.vue
    <script>
    import { getServer } from "@/api/monitor/server";export default {name: "Server",data() {return {// 服务器信息server: []};},created() {this.getList();this.openLoading();},methods: {/** 查询服务器信息 */getList() {getServer().then(response => {this.server = response.data;this.$modal.closeLoading();});},// 打开加载层openLoading() {this.$modal.loading("正在加载服务监控数据,请稍候!");}}
    };
    </script>
  2. RuoYi-Vue\ruoyi-ui\src\api\monitor\server.js
    import request from '@/utils/request'// 获取服务信息
    export function getServer() {return request({url: '/monitor/server',method: 'get'})
    }

3 后端

  1. RuoYi-Vue\pom.xml:引入开源框架,获取cpu、内存、磁盘等信息。
    <!-- 获取系统信息 -->
    <dependency><groupId>com.github.oshi</groupId><artifactId>oshi-core</artifactId><version>${oshi.version}</version>
    </dependency>
  2. RuoYi-Vue\ruoyi-framework\pom.xml:引入开源框架,获取cpu、内存、磁盘等信息。
    <!-- 获取系统信息 -->
    <dependency><groupId>com.github.oshi</groupId><artifactId>oshi-core</artifactId>
    </dependency>
  3. ServerController#getInfo
    /*** 服务器监控* * @author ruoyi*/
    @RestController
    @RequestMapping("/monitor/server")
    public class ServerController
    {@PreAuthorize("@ss.hasPermi('monitor:server:list')")@GetMapping()public AjaxResult getInfo() throws Exception{// 实例化Server server = new Server();// 设置相关的值server.copyTo();// 返回给前端return AjaxResult.success(server);}
    }
  4. Server:返回数据
    /*** 服务器相关信息* * @author ruoyi*/
    public class Server
    {private static final int OSHI_WAIT_SECOND = 1000;/*** CPU相关信息*/private Cpu cpu = new Cpu();/*** 內存相关信息*/private Mem mem = new Mem();/*** JVM相关信息*/private Jvm jvm = new Jvm();/*** 服务器相关信息*/private Sys sys = new Sys();/*** 磁盘相关信息*/private List<SysFile> sysFiles = new LinkedList<SysFile>();
    }
  5. Server#copyTo:数据填充
        /*** 数据填充*/public void copyTo() throws Exception{// SystemInfo:框架apiSystemInfo si = new SystemInfo();// SystemInfo:框架apiHardwareAbstractionLayer hal = si.getHardware();// cpu信息,大多从开源框架的API中获取setCpuInfo(hal.getProcessor());// 内存信息setMemInfo(hal.getMemory());// 服务器信息setSysInfo();// 虚拟机信息,jdk的APIsetJvmInfo();// 磁盘信息,setSysFiles(si.getOperatingSystem());}
  6. Cpu.java
    /*** CPU相关信息* * @author ruoyi*/
    public class Cpu
    {/*** 核心数*/private int cpuNum;/*** CPU总的使用率*/private double total;/*** CPU系统使用率*/private double sys;/*** CPU用户使用率*/private double used;/*** CPU当前等待率*/private double wait;/*** CPU当前空闲率*/private double free;
    }
  7. Jvm.java
    /*** JVM相关信息* * @author ruoyi*/
    public class Jvm
    {/*** 当前JVM占用的内存总数(M)*/private double total;/*** JVM最大可用内存总数(M)*/private double max;/*** JVM空闲内存(M)*/private double free;/*** JDK版本*/private String version;/*** JDK路径*/private String home;
    }
  8. Mem.java
    /*** 內存相关信息* * @author ruoyi*/
    public class Mem
    {/*** 内存总量*/private double total;/*** 已用内存*/private double used;/*** 剩余内存*/private double free;
    }
  9. Sys.java
    /*** 系统相关信息* * @author ruoyi*/
    public class Sys
    {/*** 服务器名称*/private String computerName;/*** 服务器Ip*/private String computerIp;/*** 项目路径*/private String userDir;/*** 操作系统*/private String osName;/*** 系统架构*/private String osArch;
    }
  10. SysFile.java
    /*** 系统文件相关信息* * @author ruoyi*/
    public class SysFile
    {/*** 盘符路径*/private String dirName;/*** 盘符类型*/private String sysTypeName;/*** 文件类型*/private String typeName;/*** 总大小*/private String total;/*** 剩余大小*/private String free;/*** 已经使用量*/private String used;/*** 资源的使用率*/private double usage;
    }

    4 拓展:

4 拓展:集群

        如果要做成集群,就需要再扩展一下:

  1. 需要一个表去控制,把服务器的的名称、IP、地址等相关信息入库。
  2. 然后需要做实时的监控,比如使用websocket。

45 系统接口使用详解 

1 需求

        api文档

2 效果

3 使用

  1. 第一步:获取token
  2. 第二步:点击" Authorize ",配置token
  3. 第三步:传参数,调接口

4 拓展

  1. 可以使用很多第三方插件,把界面优化的更漂亮。

46 系统接口实现详解

1 RuoYi-Vue\pom.xml

    <properties><swagger.version>3.0.0</swagger.version></properties><!-- Swagger3依赖 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>${swagger.version}</version><exclusions><!--排除:它和前端的UI有冲突--><exclusion><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId></exclusion></exclusions></dependency>

2 RuoYi-Vue\ruoyi-admin\pom.xml

        <!-- swagger3界面是swagger ui渲染出来的。而sawagger ui的所有页面,都在它的jar包中。--><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId></dependency><!-- 因为与前端ui冲突,所以做了排除。并把版本降了一下。--><!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 --><dependency><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId><version>1.6.2</version></dependency>

3 前端

  1. ruoyi-ui\src\views\tool\swagger\index.vue
    <template><!--url指定了后台的接口地址 --><i-frame :src="url" />
    </template>
    <script>
    import iFrame from "@/components/iFrame/index";
    export default {name: "Swagger",components: { iFrame },data() {return {url: process.env.VUE_APP_BASE_API + "/swagger-ui/index.html"};},
    };
    </script>

4 后端

  1. ResourcesConfig#addResourceHandlers:swagger ui映射。swagger ui怎么去加载的呢?
        @Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry){/** 本地文件上传路径 */registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");/** swagger配置 *//*** 映射配置:*      "/swagger-ui/**":访问的地址。*      "classpath:/META-INF/resources/webjars/springfox-swagger-ui/":找到对应的路径(在jar包中)。*      因为默认情况下swagger ui首页是英文版的,但是可以对它做一些国际化的操作。*/registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/").setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());;}
  2. SwaggerConfig:swagger配置类
    package com.ruoyi.web.core.config;import java.util.ArrayList;
    import java.util.List;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import com.ruoyi.common.config.RuoYiConfig;
    import io.swagger.annotations.ApiOperation;
    import io.swagger.models.auth.In;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.ApiKey;
    import springfox.documentation.service.AuthorizationScope;
    import springfox.documentation.service.Contact;
    import springfox.documentation.service.SecurityReference;
    import springfox.documentation.service.SecurityScheme;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.service.contexts.SecurityContext;
    import springfox.documentation.spring.web.plugins.Docket;/*** Swagger2的接口配置*/
    @Configuration
    public class SwaggerConfig
    {/*** 系统基础配置。* 读取项目相关配置文件常用的几个属性。* */@Autowiredprivate RuoYiConfig ruoyiConfig;/** 是否开启swagger */@Value("${swagger.enabled}")private boolean enabled;/*** 设置:请求的统一前缀。* 即使用swagger ui页面调用接口进行测试时,请求url都会拼接这样的一个前缀。不然就映射不到后台来了,因为前端有对应的路由控制。* 为什么需要请求前缀呢?因为路由有映射。* 当然这个是可以改的,可根据实际情况去调整。* */@Value("${swagger.pathMapping}")private String pathMapping;/*** 创建API*/@Beanpublic Docket createRestApi(){// 版本:DocumentationType.OAS_30return new Docket(DocumentationType.OAS_30)// 是否启用Swagger.enable(enabled)// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息).apiInfo(apiInfo())// 设置哪些接口暴露给Swagger展示.select()// 暴露方式1:扫描所有有注解的api,用这种方式更灵活.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))// 暴露方式2:扫描指定包中的swagger注解// .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))// 暴露方式3:扫描所有 .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build()/* 设置安全模式,swagger可以设置访问token *//*** 默认情况下,直接访问会提示没有权限,所以需要设置一下安全模式(即页面中的” Authorize按钮 ”)。* 点击“ Authorize按钮 ”,可以看到,接口需要哪些属性。* 可以按实际情况传更多的参数。*/.securitySchemes(securitySchemes()).securityContexts(securityContexts()).pathMapping(pathMapping);}/*** 安全模式,这里指定token通过Authorization头请求头传递*/private List<SecurityScheme> securitySchemes(){List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));return apiKeyList;}/*** 安全上下文*/private List<SecurityContext> securityContexts(){List<SecurityContext> securityContexts = new ArrayList<>();securityContexts.add(SecurityContext.builder().securityReferences(defaultAuth()).operationSelector(o -> o.requestMappingPattern().matches("/.*")).build());return securityContexts;}/*** 默认的安全上引用*/private List<SecurityReference> defaultAuth(){AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];authorizationScopes[0] = authorizationScope;List<SecurityReference> securityReferences = new ArrayList<>();securityReferences.add(new SecurityReference("Authorization", authorizationScopes));return securityReferences;}/*** 添加摘要信息。* 自定义展示的api基本信息。*/private ApiInfo apiInfo(){// 用ApiInfoBuilder进行定制return new ApiInfoBuilder()// 设置标题.title("标题:若依管理系统_接口文档")// 描述.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")// 作者信息.contact(new Contact(ruoyiConfig.getName(), null, null))// 版本.version("版本号:" + ruoyiConfig.getVersion()).build();}
    }
    
    yH5BAAAAAAALAAAAAAOAA4AAAIMhI+py+0Po5y02qsKADs=
  3. RuoYiConfig:系统基础配置
    /*** 读取项目相关配置。* 基础配置,对应配置文件中的常用的几个属性。*/
    @Component
    @ConfigurationProperties(prefix = "ruoyi")
    public class RuoYiConfig
    {/** 项目名称 */private String name;/** 版本 */private String version;/** 版权年份 */private String copyrightYear;/** 实例演示开关 */private boolean demoEnabled;/** 上传路径 */private static String profile;/** 获取地址开关 */private static boolean addressEnabled;/** 验证码类型 */private static String captchaType;/*** 获取导入上传路径*/public static String getImportPath(){return getProfile() + "/import";}/*** 获取头像上传路径*/public static String getAvatarPath(){return getProfile() + "/avatar";}/*** 获取下载路径*/public static String getDownloadPath(){return getProfile() + "/download/";}/*** 获取上传路径*/public static String getUploadPath(){return getProfile() + "/upload";}
    }
    
  4. application.yml
    # 项目相关配置
    ruoyi:# 名称name: RuoYi# 版本version: 3.8.6# 版权年份copyrightYear: 2023# 实例演示开关demoEnabled: true# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)profile: D:/ruoyi/uploadPath# 获取ip地址开关addressEnabled: false# 验证码类型 math 数字计算 char 字符验证captchaType: math
  5. SwaggerConfig#securitySchemes:安全模式,这里指定token通过Authorization头请求头传递
        /*** 安全模式,这里指定token通过Authorization头请求头传递*/private List<SecurityScheme> securitySchemes(){List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));return apiKeyList;}

5 如何汉化系统接口Swagger

        想必很多小伙伴都曾经使用过Swagger,但是打开UI界面是纯英文的界面并不太友好,作为国人还是习惯中文界面。

  1. 找到m2/repository/io/springfox/springfox-swagger-ui/x.x.x/springfox-swagger-ui-x.x.x.jar
  2. 修改对应springfox-swagger-ui-x.x.x.jar包内resources目录下swagger-ui.html,添加如下JS代码
    <!-- 选择中文版 -->
    <script src='webjars/springfox-swagger-ui/lang/translator.js' type='text/javascript'></script>
    <script src='webjars/springfox-swagger-ui/lang/zh-cn.js' type='text/javascript'></script>
    
  3.  本地修改结束后,在覆盖压缩包文件重启就实现汉化了

6 编码案例


@Api("用户信息管理")
public class TestController extends BaseController{// 暴露注解@ApiOperation("获取用户列表")// 参数注解/*** name:参数* value:显示的文本* required:是否必填* dataType:数据类型* paramType:参数类型。如path对应的是getUser(@PathVariable Integer userId)中的@PathVariable传参方式。* dataTypeClass:数据类型*/@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)public R<UserEntity> getUser(@PathVariable Integer userId){List<UserEntity> userList = new ArrayList<UserEntity>(users.values());return R.ok(userList);}
}

 完整代码:

/*** swagger 用户测试方法* * @author ruoyi*/
@Api("用户信息管理")
@RestController
@RequestMapping("/test/user")
public class TestController extends BaseController
{private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();{users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));}@ApiOperation("获取用户列表")@GetMapping("/list")public R<List<UserEntity>> userList(){List<UserEntity> userList = new ArrayList<UserEntity>(users.values());return R.ok(userList);}@ApiOperation("获取用户详细")/*** name:参数* value:显示的文本* required:是否必填* dataType:数据类型* paramType:参数类型。如path对应的是getUser(@PathVariable Integer userId)中的@PathVariable传参方式。* dataTypeClass:数据类型*/@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)@GetMapping("/{userId}")public R<UserEntity> getUser(@PathVariable Integer userId){if (!users.isEmpty() && users.containsKey(userId)){return R.ok(users.get(userId));}else{return R.fail("用户不存在");}}@ApiOperation("新增用户")@ApiImplicitParams({@ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),@ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),@ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),@ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)})@PostMapping("/save")public R<String> save(UserEntity user){if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())){return R.fail("用户ID不能为空");}users.put(user.getUserId(), user);return R.ok();}@ApiOperation("更新用户")@PutMapping("/update")public R<String> update(@RequestBody UserEntity user){if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())){return R.fail("用户ID不能为空");}if (users.isEmpty() || !users.containsKey(user.getUserId())){return R.fail("用户不存在");}users.remove(user.getUserId());users.put(user.getUserId(), user);return R.ok();}@ApiOperation("删除用户信息")@ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)@DeleteMapping("/{userId}")public R<String> delete(@PathVariable Integer userId){if (!users.isEmpty() && users.containsKey(userId)){users.remove(userId);return R.ok();}else{return R.fail("用户不存在");}}
}/*** 参数类型为实体类*/
@ApiModel(value = "UserEntity", description = "用户实体")
class UserEntity
{// 注解:显示的文本。如果没有这个注解,默认使用属性名称。@ApiModelProperty("用户ID")private Integer userId;@ApiModelProperty("用户名称")private String username;@ApiModelProperty("用户密码")private String password;@ApiModelProperty("用户手机")private String mobile;public UserEntity(){}public UserEntity(Integer userId, String username, String password, String mobile){this.userId = userId;this.username = username;this.password = password;this.mobile = mobile;}// get/set方法
}

47 XSS脚本过滤详解

1 XSS攻击的定义

        跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。

2 模拟xss攻击示例1:页面效果

ruoyi-ui\src\views\system\config\index.vue:v-text效果,正确

<!-- 添加或修改参数配置对话框 --><el-dialog :title="title" :visible.sync="open" width="500px" append-to-body><el-form ref="form" :model="form" :rules="rules" label-width="80px"><el-form-item label="参数名称" prop="configName"><!--<el-input v-model="form.configName" placeholder="请输入参数名称" />--><!-- v-text其实是没有问题的,因为它默认会给我们做一些处理。 --><div v-text="form.configName"></div></el-form-item><el-form-item label="参数键名" prop="configKey"><el-input v-model="form.configKey" placeholder="请输入参数键名" /></el-form-item><el-form-item label="参数键值" prop="configValue"><el-input v-model="form.configValue" placeholder="请输入参数键值" /></el-form-item><el-form-item label="系统内置" prop="configType"><el-radio-group v-model="form.configType"><el-radiov-for="dict in dict.type.sys_yes_no":key="dict.value":label="dict.value">{{dict.label}}</el-radio></el-radio-group></el-form-item><el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button type="primary" @click="submitForm">确 定</el-button><el-button @click="cancel">取 消</el-button></div></el-dialog>

  

ruoyi-ui\src\views\system\config\index.vue:v-html效果,不正确。点击,出现脚本。这种情况下可能会造成恶意代码(没有经过特殊处理的脚本)注入到html里面去,进行攻击,

<!-- 添加或修改参数配置对话框 --><el-dialog :title="title" :visible.sync="open" width="500px" append-to-body><el-form ref="form" :model="form" :rules="rules" label-width="80px"><el-form-item label="参数名称" prop="configName"><!--<el-input v-model="form.configName" placeholder="请输入参数名称" />--><!-- v-text其实是没有问题的,因为它默认会给我们做一些处理。 --><!--<div v-text="form.configName"></div>--><!-- v-html有问题。v-html即支持标签为一个HTML类型。 --><div v-html="form.configName"></div></el-form-item><el-form-item label="参数键名" prop="configKey"><el-input v-model="form.configKey" placeholder="请输入参数键名" /></el-form-item><el-form-item label="参数键值" prop="configValue"><el-input v-model="form.configValue" placeholder="请输入参数键值" /></el-form-item><el-form-item label="系统内置" prop="configType"><el-radio-group v-model="form.configType"><el-radiov-for="dict in dict.type.sys_yes_no":key="dict.value":label="dict.value">{{dict.label}}</el-radio></el-radio-group></el-form-item><el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button type="primary" @click="submitForm">确 定</el-button><el-button @click="cancel">取 消</el-button></div></el-dialog>

3 模拟xss攻击示例2:数据库效果

数据库表: sys_conifg。如下图所示,如果没有做XSS处理在数据库里面就会完整的把标签和脚本进行入库,是非常危险的 :

4 如何在项目处理xss攻击?

  1. application.yml:xss处理开关
    # 防止XSS攻击
    xss:# 过滤开关enabled: true# 排除链接(多个用逗号分隔)# 即哪些不需要去过滤。# 因为通知公告的内容是富文本内容,可能会包含一些HTML内容。# 所以一般排除,就会排除一些富文本的url。# " * "表示所有excludes: /system/notice# 匹配链接# 即需要过滤的链接(/system/*:系统管理;/monitor/*:监控;/tool/*:工具)urlPatterns: /system/*,/monitor/*,/tool/*
  2. 重启项目,刷新页面
  3. 面对" 模拟xss攻击示例2 ":数据库效果
  4. 面对" 模拟xss攻击示例2 ":页面效果,点击不再有弹出框

 5 项目处理xss攻击实现原理

  1. FilterConfig:通用的过滤器的配置
    /*** Filter配置* 通用过滤器的配置。*/
    @Configuration
    public class FilterConfig
    {/*** 获取application.yaml中的xss相关参数配置*/@Value("${xss.excludes}")private String excludes;/*** 获取application.yaml中的xss相关参数配置*/@Value("${xss.urlPatterns}")private String urlPatterns;/*** 这里可以添加加很多过滤器(现在只有一个xss过滤器),* 后续有其他的过滤器直接往里面加就行了,* 这样方便统一去管理所有的过滤器。*/@SuppressWarnings({ "rawtypes", "unchecked" })@Bean@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")public FilterRegistrationBean xssFilterRegistration(){FilterRegistrationBean registration = new FilterRegistrationBean();/*** 配置:请求类型*/registration.setDispatcherTypes(DispatcherType.REQUEST);/*** 配置:xss核心过滤器*/registration.setFilter(new XssFilter());/*** 配置:需要过滤的链接*/registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));/*** 配置:xss核心过滤器的名称,随意*/registration.setName("xssFilter");/*** 配置:xss核心过滤器的优先级*/registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);/*** 配置:xss核心过滤器的初始化参数*/Map<String, String> initParameters = new HashMap<String, String>();/*** 配置:xss核心过滤器的初始化参数 —— 排除的链接(不需要过滤的链接)*/initParameters.put("excludes", excludes);registration.setInitParameters(initParameters);return registration;}
    }
  2. com.ruoyi.common.filter.XssFilter:防止XSS攻击的过滤器
    /*** 防止XSS攻击的过滤器* * @author ruoyi*/
    public class XssFilter implements Filter
    {/*** 排除链接*/public List<String> excludes = new ArrayList<>();/*** 初化方法*/@Overridepublic void init(FilterConfig filterConfig) throws ServletException{/*** 排除链接List*/String tempExcludes = filterConfig.getInitParameter("excludes");if (StringUtils.isNotEmpty(tempExcludes)){String[] url = tempExcludes.split(",");for (int i = 0; url != null && i < url.length; i++){excludes.add(url[i]);}}}/*** 核心处理* @param request* @param response* @param chain* @throws IOException* @throws ServletException*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException{HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;if (handleExcludeURL(req, resp)){/*** 排除链接,正常执行*/chain.doFilter(request, response);return;}/*** xss攻击的处理*/XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);chain.doFilter(xssRequest, response);}private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response){/*** 获取请求url*/String url = request.getServletPath();String method = request.getMethod();// GET DELETE 不过滤if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)){return true;}return StringUtils.matches(url, excludes);}@Overridepublic void destroy(){}
    }
  3. XssHttpServletRequestWrapper:XSS过滤处理
    /*** XSS过滤处理*/
    public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
    {/*** @param request*/public XssHttpServletRequestWrapper(HttpServletRequest request){super(request);}/*** 处理非json类型* @param name* @return*/@Overridepublic String[] getParameterValues(String name){/*** 获取所有请求参数值*/String[] values = super.getParameterValues(name);if (values != null){int length = values.length;String[] escapesValues = new String[length];for (int i = 0; i < length; i++){// 防xss攻击和过滤前后空格escapesValues[i] = EscapeUtil.clean(values[i]).trim();}return escapesValues;}return super.getParameterValues(name);}/*** 处理json类型*/@Overridepublic ServletInputStream getInputStream() throws IOException{// 非json类型,直接返回if (!isJsonRequest()){return super.getInputStream();}// 为空,直接返回String json = IOUtils.toString(super.getInputStream(), "utf-8");if (StringUtils.isEmpty(json)){return super.getInputStream();}// xss过滤json = EscapeUtil.clean(json).trim();byte[] jsonBytes = json.getBytes("utf-8");final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes);return new ServletInputStream(){@Overridepublic boolean isFinished(){return true;}@Overridepublic boolean isReady(){return true;}@Overridepublic int available() throws IOException{return jsonBytes.length;}@Overridepublic void setReadListener(ReadListener readListener){}@Overridepublic int read() throws IOException{return bis.read();}};}/*** 是否是Json请求* * @param request*/public boolean isJsonRequest(){String header = super.getHeader(HttpHeaders.CONTENT_TYPE);return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);}
    }
  4. EscapeUtil#clean:清除所有HTML标签,但是不删除标签内的内容
        /*** 清除所有HTML标签,但是不删除标签内的内容* * @param content 文本* @return 清除标签后的文本*/public static String clean(String content){return new HTMLFilter().filter(content);}

48 防止重复提交过滤详解

1 前端处理方案:自己研究

2 后端处理方案

  1. RepeatSubmit:自定义注解防止表单重复提交
    /*** 自定义注解防止表单重复提交**/
    @Inherited
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RepeatSubmit
    {/*** 间隔时间(ms),小于此时间视为重复提交*/public int interval() default 5000;/*** 提示消息*/public String message() default "不允许重复提交,请稍候再试";
    }
  2. SysConfigController#add:XxxController的接口方法上加上防重复提交注解
        /*** 新增参数配置*/@PreAuthorize("@ss.hasPermi('system:config:add')")@Log(title = "参数管理", businessType = BusinessType.INSERT)@PostMapping@RepeatSubmitpublic AjaxResult add(@Validated @RequestBody SysConfig config){if (!configService.checkConfigKeyUnique(config)){return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");}config.setCreateBy(getUsername());return toAjax(configService.insertConfig(config));}
  3. 重启项目,再走" 多次点击,产生重复提交 "
  4. 提示" 不允许重复提交,请稍后再试 "

3 后端处理实现原理

  1. FilterConfig#someFilterRegistration:过滤器统一配置
       /*** 后续新建过滤器后,在这里可以添加多个过滤器*/@Beanpublic FilterRegistrationBean someFilterRegistration(){FilterRegistrationBean registration = new FilterRegistrationBean();registration.setFilter(new RepeatableFilter());/*** 过滤所有的请求*/registration.addUrlPatterns("/*");/*** 过滤器的名称,随意*/registration.setName("repeatableFilter");/*** 优先级。* 防重复提交过滤的优先级比XSS低,因此所有请求都会先进到他XSS里面去校验,然后才走防重复提交过滤器。*/registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);return registration;}
  2. RepeatableFilter#doFilter
    /*** Repeatable 过滤器* * @author ruoyi*/
    public class RepeatableFilter implements Filter
    {@Overridepublic void init(FilterConfig filterConfig) throws ServletException{}/*** 方法中没有做太多的处理,简单来说,就只是说把request给包装了一下。*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException{ServletRequest requestWrapper = null;if (request instanceof HttpServletRequest&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)){requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);}if (null == requestWrapper){chain.doFilter(request, response);}else{chain.doFilter(requestWrapper, response);}}@Overridepublic void destroy(){}
    }
  3.  

 

相关文章:

若依vue -【 44】

44 服务监控讲解 1 需求 显示CPU、内存、服务器信息、Java虚拟机信息、磁盘状态的信息 2 前端 RuoYi-Vue\ruoyi-ui\src\views\monitor\server\index.vue <script> import { getServer } from "/api/monitor/server";export default {name: "Server&quo…...

React 基础篇(一)

&#x1f4bb; React 基础篇&#xff08;一&#xff09;&#x1f3e0;专栏&#xff1a;React &#x1f440;个人主页&#xff1a;繁星学编程&#x1f341; &#x1f9d1;个人简介&#xff1a;一个不断提高自我的平凡人&#x1f680; &#x1f50a;分享方向&#xff1a;目前主攻…...

Bean 的作用域和生命周期

目录 什么是 Bean 的作用域 ?Bean 的六种作用域Spring 的执行流程Bean 的生命周期 什么是 Bean 的作用域 ? Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式&#xff0c;⽐如 singleton 单例作⽤域&#xff0c;就表示 Bean 在整个 Spring 中只有⼀份&#xff0c…...

STP和MTP(第二十二课)

2、如何实现 1)在MSTP网络种,引入了域的概念,称为MST域 2)每一个MST域中包含一个或多个“生成树”称为“实例” 3)每个“实例生成树”都可以绑定vlan,实现vlan数据流的负载分担/负载均衡 4)默认情况下,所有的vlan都属于“实例树0:即:instance 0” 5)不同的“实例…...

Java-WebSocket

请点击下面工程名称&#xff0c;跳转到代码的仓库页面&#xff0c;将工程 下载下来 Demo Code 里有详细的注释 TestWebSocket...

elementui的el-date-picker选择日期范围第二个不能早于第一个

选择日期范围第二个不能早于第一个 <el-form-item label"预计施工时间:" required><el-form:model"form":rules"constructionDateRules"ref"constructionRef"inline:hide-required-asterisk"false"><el-form…...

【NLP】无服务器问答系统

一、说明 在NLP的眼见的应用&#xff0c;就是在“ 当你在谷歌上提出一个问题并立即得到答案时会发生什么&#xff1f;例如&#xff0c;如果我们在谷歌搜索中询问谁是美国总统&#xff0c;我们会得到以下回答&#xff1a;Joe Biden&#xff1b;这是一个搜索问题&#xff0c;同时…...

Dubbo

Dubbo 简介Dubbo的快速入门Dubbo的基本架构安装DubboAdmin入门案例Dubbo的最佳实践 Dubbo的高级特性启动检查多版本超时与重试负载均衡SpringCloud整合Dubbo案例 简介 Dubbo是阿里巴巴公司开源的一个高性能、轻量级的Java RPC框架。 致力于提高性能和透明化的RPC远程服务调用方…...

Java设计模式之策略(Strategy)模式

策略&#xff08;Strategy&#xff09;设计模式定义了一系列算法&#xff0c;将它们封装起来&#xff0c;并且可以相互替换使用&#xff0c;从而使得算法可以独立于使用它的客户而变化。 什么是策略模式 策略&#xff08;Strategy&#xff09;设计模式是一种行为型设计模式&a…...

Vue引入CDN JS或本地JS文件之后 使用报错

加载问题 正常情况 在public引入script - js文件加载 - 写入内存 - 使用 但使用之前 有可能这个文件还没执行写入内存或者还未加载完毕 此时 需要一个promiss解决 1. 引入script 在 public / index.html 文件内引入你的script标签 <script type"text/javascript"…...

NRF52832-扩展广播

nordic论坛 我想要设置广播名称为 “一二三四五”&#xff0c;当广播名称为FULL_NAME时&#xff0c;但是广播显示还是“一&#xff1f;”&#xff0c;“&#xff1f;”是乱码&#xff0c;后来打开nrf connect观察广播&#xff0c;在没连接的时候&#xff0c;点击一下&#xff0…...

springboot项目新增子module

1. 拉取项目 2. file-new-module 3. 选择版本 4. 1-2-3-4 5. 注释请求统一前缀 (SwaggerConfig.java)...

Python Web 开发及 Django 总结

title: Python Web 开发及 Django 总结 date: 2023-07-24 17:26:26 tags: PythonWeb categories:Python cover: https://cover.png feature: false Python 基础部分见&#xff1a;Python 基础总结 1. 创建项目 1.1 命令行 1、下载安装 Django 在终端输入 pip install djan…...

《向量数据库指南》:向量数据库Pinecone故障排除

目录 无法pip安装 空闲后索引丢失 上传缓慢或延迟高 批处理带来的高查询延迟 使用gRPC客户端进行Upsert限流 Pods已满 安全问题 CORS错误 本节介绍常见问题以及如何解决它们。需要帮助吗?在我们的支持论坛中提问。标准、企业和专用客户还可以联系支持人员寻求帮助。...

[86] 分割链表

题目链接&#xff1a;86. 分隔链表 - 力扣&#xff08;LeetCode&#xff09; 第一种方法&#xff1a;类似双指针 自己想的&#xff0c;不知道读者是否能看懂&#xff0c;参考注释 ListNode* partition(ListNode* head, int x) {ListNode* bigpos nullptr;ListNode* littlep…...

【python】 清空socket缓冲区

在Python中使用Socket进行网络通信时&#xff0c;可以通过调用socket.recv()函数来接收数据&#xff0c;数据会被存储在缓冲区中。有时候&#xff0c;可能想要先清空缓冲区&#xff0c;以便后续的数据不会被之前的数据影响。以下是一种清空Python Socket缓冲区的方法&#xff1…...

108、RocketMQ的底层实现原理(不需要长篇大论)

RocketMQ的底层实现原理 RocketMQ由NameServer集群、Producer集群、Consumer集群、Broker集群组成&#xff0c;消息生产和消费的大致原理如下: Broker在启动的时候向所有的NameServer注册&#xff0c;并保持长连接&#xff0c;每30s发送一次心跳Producer在发送消息的时候从Na…...

怎么把PDF转为word?1分钟解决难题

PDF文件在我们的电脑上应用非常广泛&#xff0c;由于其较高的安全性和兼容性&#xff0c;得到了广泛的认可。然而&#xff0c;对于一些人来说&#xff0c;PDF文件不能直接进行编辑和修改可能是一个问题。因此&#xff0c;通常我们需要将其转换为Word格式&#xff0c;以便在Word…...

Mysql权限-系统表user,db,talbes_priv,columns_priv详解

一、MySQL 权限场景 可以根据登录用户限制用户访问资源(库、表)可以根据登录用户限制用户的操作权限(能对哪些库、表执行增删改查操作)可以指定用户登录IP或者域名可以限制用户权限分配 二、Mysql五个层级权限级别分析 Mysql权限级别分为了五个层级&#xff0c;并且每个级别…...

GPT-4 模型详细教程

GPT-4&#xff08;Generative Pretrained Transformer 4&#xff09;是 OpenAI 的最新语言生成模型&#xff0c;其在各类文本生成任务中表现优秀&#xff0c;深受开发者和研究者喜爱。这篇教程将帮助你理解 GPT-4 的基本概念&#xff0c;并向你展示如何使用它来生成文本。 什么…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...

十二、【ESP32全栈开发指南: IDF开发环境下cJSON使用】

一、JSON简介 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;具有以下核心特性&#xff1a; 完全独立于编程语言的文本格式易于人阅读和编写易于机器解析和生成基于ECMAScript标准子集 1.1 JSON语法规则 {"name"…...

Python打卡训练营学习记录Day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

基于Java的离散数学题库系统设计与实现:附完整源码与论文

JAVASQL离散数学题库管理系统 一、系统概述 本系统采用Java Swing开发桌面应用&#xff0c;结合SQL Server数据库实现离散数学题库的高效管理。系统支持题型分类&#xff08;选择题、填空题、判断题等&#xff09;、难度分级、知识点关联&#xff0c;并提供智能组卷、在线测试…...