【SpringBoot】28 API接口防刷(Redis + 拦截器)
Gitee仓库
https://gitee.com/Lin_DH/system
介绍
常用的 API 安全措施包括:防火墙、验证码、鉴权、IP限制、数据加密、限流、监控、网关等,以确保接口的安全性。
常见措施
1)防火墙
防火墙是网络安全中最基本的安全设备之一,主要用于防止未经授权的网络访问和攻击。
防火墙主要用于过滤和控制网络流量,以保护网络安全。
防火墙可以防止的攻击行为包括:
- 无效数据包:防火墙可以识别和过滤无效的数据包,如错误的 IP 地址、伪造的数据包、无法识别的协议等。
- DOS 和 DDOS 攻击:防火墙可以使用不同的技术来检测和阻止 DOS 和 DDOS 攻击,如阻止大量 TCP / UDP 连接、IP 地址过滤、流量限制等。
- 病毒和蠕虫攻击:防火墙可以使用特定的病毒和蠕虫检测技术,如签名检测、行为检测、模式识别等,来防止这些恶意软件的传播。
- 网络钓鱼和欺骗攻击:防火墙可以检测、防止网络钓鱼、欺骗攻击,如防止虚假登录页面、欺骗的网站等。
- 恶意流量攻击:防火墙可以检测和防止恶意流量攻击,如过滤带有恶意载荷的数据包和防止被黑客利用的端口。
- 网络侦察攻击:防火墙可以使用一些技术来防止网络侦察攻击,如防止扫描、端口扫描、漏洞利用等。
2)验证码
在特定接口上,要求用户在访问前先进行验证码验证,以确保发送该请求的为真实用户。
3)鉴权
要求用户在访问 API 时,进行身份认证,并根据用户的权限进行授权,只允许有权限的用户访问特定的接口。
4)IP限制
仅限特定 IP 范围对 API 的访问,例如允许内网或者加入 IP 白名单的能够访问特定 API 。
5)数据加密
对敏感数据进行加密传输,使用 HTTPS 协议保证数据传输的安全性。
以往很多接口都是使用 HTTP 协议(Hyper Text Transport Protocol,超文本传输协议),用于传输客户端和服务器端的数据。
HTTP 协议使用虽然简单方便,但也存在着问题:
- 使用明文通讯,传输内容容易被窃听
- 不验证通讯方的身份,容易遭到伪装
- 无法证明报文的完整性,报文容易被篡改
为了解决 HTTP 协议的一系列问题,出现了 HTTPS 协议。HTTPS 协议是在 HTTP 协议上添加了加密机制。
SSL(Secure Socket Layer,安全套接层)
TLS(Transport Layer Security,传输层安全)
HTTPS = HTTP + 加密 + 认证 + 完整性保护
为了安全性考虑,接口的协议需要使用 HTTPS 协议。
6)限流
设置访问频率限制,例如每分钟、每小时、每天只允许请求访问一定次数,超出限制则返回错误信息或者封禁 IP。
7)监控
监控 API 的访问日志,统计用户对接口的调用情况,对流量激增、某个IP频繁请求同一接口,则自动发送邮件等通知,及时采取相应的安全措施。
8)网关
在 API 和客户端之间引入 API 网关,对请求进行过滤、鉴权、限流等操作,保护后端 API 的安全。
IP限制方式(拦截器)
代码实现
第一步:定义 IP 限制拦截器
IPInterceptor.java
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;/*** IP拦截器* @author DUHAOLIN* @date 2024/11/13*/
@Component
public class IPInterceptor implements HandlerInterceptor {//IP白名单private static final List<String> ALLOWED_IPS = Arrays.asList("127.0.0.1", "0:0:0:0:0:0:0:1");@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String ipAddress = request.getRemoteAddr();//不允许访问的IP返回"Access denied"错误信息,并且设置响应的状态码为403(Forbidden)if (!ALLOWED_IPS.contains(ipAddress)) {response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.getWriter().write("Access denied");return false;}return true;}}
效果图
限制次数方式(Redis + 拦截器)
Windows安装Redis
Redis 下载链接:https://pan.baidu.com/s/1BMt4cIxjKTtyL3T0_iSC2w 密码:rkne
打开 CMD 命令窗口,在 Redis 安装目录执行如下命令:redis-server.exe redis.windows.conf
依赖
pom.xml
<!-- Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 分布式锁工具 --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>${redission.version}</version></dependency>
配置文件
application.yml
spring:redis:host: localhostport: 6379timeout: 10
代码实现
第一步:添加解析 Redis Key 前缀的接口
KeyPrefix.java
package com.lm.system.redis;/*** @author DUHAOLIN* @date 2024/11/13*/
public interface KeyPrefix {int expireSeconds();String getPrefix();
}
第二步:添加解析 Redis 基础前缀的抽象类
BasePrefix.java
package com.lm.system.redis;/*** @author DUHAOLIN* @date 2024/11/13*/
public abstract class BasePrefix implements KeyPrefix {public BasePrefix() {}public BasePrefix(String prefix) {this(0, prefix);}private int expireSeconds;private String prefix;public BasePrefix(int expireSeconds, String prefix) {this.expireSeconds = expireSeconds;this.prefix = prefix;}@Overridepublic int expireSeconds() {return 0; //默认永不过期}@Overridepublic String getPrefix() {String simpleName = this.getClass().getSimpleName();return simpleName + ":" + prefix;}}
第三步:添加解析用户Key的实现类
AccessKey.java
package com.lm.system.common;import com.lm.system.redis.BasePrefix;/*** @author DUHAOLIN* @date 2024/11/13*/
public class AccessKey extends BasePrefix {public AccessKey() {}public AccessKey(String prefix) {super(0, prefix);}public AccessKey(int expireSeconds, String prefix) {super(expireSeconds, prefix);}public static AccessKey withExpire(int expireSeconds) {return new AccessKey(expireSeconds, "prefix");}@Overridepublic int expireSeconds() {return super.expireSeconds();}@Overridepublic String getPrefix() {return super.getPrefix();}}
第四步:在需要限流的接口上,添加 @AccessLimit 注解。
注:
- 其他 User 实体类等可以查看 Gitee 仓库(https://gitee.com/Lin_DH/system)。
- Redis 不能和 cache 缓存一起使用,ServiceImpl中 users 方法使用需要了,需要注释掉 @Cacheable 注解。
UserController.java
@GetMapping("users")@ApiOperation("获取所有用户信息")@AccessLimit(seconds = 10, maxCount = 3)public String users() {List<User> users = userService.queryAllUser();return ResultBody.build(HttpStatus.OK).setData(users).setCount(users.size()).getReturn();}
第五步:启动类添加 @EnableCaching 注解
SystemApplication.java
package com.lm.system;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;@EnableCaching
@SpringBootApplication
@MapperScan("com.lm.system.mapper")
public class SystemApplication extends SpringBootServletInitializer {public static void main(String[] args) {SpringApplication.run(SystemApplication.class, args);}@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {return application.sources(SystemApplication.class);}}
第六步:添加 Redis 操作接口
RedisService.java
package com.lm.system.redis;import org.redisson.api.RReadWriteLock;import java.util.List;
import java.util.Map;
import java.util.Set;/*** @author DUHAOLIN* @date 2024/11/13*/
public interface RedisService {/*** 保存属性*/void set(String key, Object value, long time);/*** 保存属性*/void set(String key, Object value);/*** 获取属性*/Object get(String key);/*** 删除属性*/Boolean del(String key);/*** 批量删除属性*/Long del(List<String> keys);/*** 设置过期时间*/Boolean expire(String key, long time);/*** 获取过期时间*/Long getExpire(String key);/*** 判断是否有该属性*/Boolean hasKey(String key);/*** 按delta递增*/Long incr(String key, long delta);/*** 按delta递减*/Long decr(String key, long delta);/*** 获取Hash结构中的属性*/Object hGet(String key, String hashKey);/*** 向Hash结构中放入一个属性*/Boolean hSet(String key, String hashKey, Object value, long time);/*** 向Hash结构中放入一个属性*/void hSet(String key, String hashKey, Object value);/*** 直接获取整个Hash结构*/Map<Object, Object> hGetAll(String key);/*** 直接设置整个Hash结构*/Boolean hSetAll(String key, Map<String, Object> map, long time);/*** 直接设置整个Hash结构*/void hSetAll(String key, Map<String, ?> map);/*** 删除Hash结构中的属性*/void hDel(String key, Object... hashKey);/*** 判断Hash结构中是否有该属性*/Boolean hHasKey(String key, String hashKey);/*** Hash结构中属性递增*/Long hIncr(String key, String hashKey, Long delta);/*** Hash结构中属性递减*/Long hDecr(String key, String hashKey, Long delta);/*** 获取Set结构*/Set<Object> sMembers(String key);/*** 向Set结构中添加属性*/Long sAdd(String key, Object... values);/*** 向Set结构中添加属性*/Long sAdd(String key, long time, Object... values);/*** 是否为Set中的属性*/Boolean sIsMember(String key, Object value);/*** 获取Set结构的长度*/Long sSize(String key);/*** 删除Set结构中的属性*/Long sRemove(String key, Object... values);/*** 获取List结构中的属性*/List<Object> lRange(String key, long start, long end);/*** 获取List结构的长度*/Long lSize(String key);/*** 根据索引获取List中的属性*/Object lIndex(String key, long index);/*** 向List结构中添加属性*/Long lPush(String key, Object value);/*** 向List结构中添加属性*/Long lPush(String key, Object value, long time);/*** 向List结构中批量添加属性*/Long lPushAll(String key, Object... values);/*** 向List结构中批量添加属性*/Long lPushAll(String key, Long time, Object... values);/*** 从List结构中移除属性*/Long lRemove(String key, long count, Object value);/*** 尝试获取分布式锁* @param key* @param timeOut* @param expireTime* @return* @throws InterruptedException*/boolean tryLock(String key, long timeOut, long expireTime) throws InterruptedException;/*** 解锁* @param key* @return*/void unLock(String key);/*** 获取分布式读写锁对象* @param lockKey* @return*/RReadWriteLock getReadWriteLock(String lockKey);
}
第七步:添加 Redis 操作实现类
RedisServiceImpl.java
package com.lm.system.redis;import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;/*** @author DUHAOLIN* @date 2024/11/13*/
@Service
public class RedisServiceImpl implements RedisService {@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Resourceprivate RedissonClient redissonClient;@Overridepublic void set(String key, Object value, long time) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);}@Overridepublic void set(String key, Object value) {redisTemplate.opsForValue().set(key, value);}@Overridepublic Object get(String key) {return redisTemplate.opsForValue().get(key);}@Overridepublic Boolean del(String key) {return redisTemplate.delete(key);}@Overridepublic Long del(List<String> keys) {return redisTemplate.delete(keys);}@Overridepublic Boolean expire(String key, long time) {return redisTemplate.expire(key, time, TimeUnit.SECONDS);}@Overridepublic Long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}@Overridepublic Boolean hasKey(String key) {return redisTemplate.hasKey(key);}@Overridepublic Long incr(String key, long delta) {return redisTemplate.opsForValue().increment(key, delta);}@Overridepublic Long decr(String key, long delta) {return redisTemplate.opsForValue().increment(key, -delta);}@Overridepublic Object hGet(String key, String hashKey) {return redisTemplate.opsForHash().get(key, hashKey);}@Overridepublic Boolean hSet(String key, String hashKey, Object value, long time) {redisTemplate.opsForHash().put(key, hashKey, value);return expire(key, time);}@Overridepublic void hSet(String key, String hashKey, Object value) {redisTemplate.opsForHash().put(key, hashKey, value);}@Overridepublic Map<Object, Object> hGetAll(String key) {return redisTemplate.opsForHash().entries(key);}@Overridepublic Boolean hSetAll(String key, Map<String, Object> map, long time) {redisTemplate.opsForHash().putAll(key, map);return expire(key, time);}@Overridepublic void hSetAll(String key, Map<String, ?> map) {redisTemplate.opsForHash().putAll(key, map);}@Overridepublic void hDel(String key, Object... hashKey) {redisTemplate.opsForHash().delete(key, hashKey);}@Overridepublic Boolean hHasKey(String key, String hashKey) {return redisTemplate.opsForHash().hasKey(key, hashKey);}@Overridepublic Long hIncr(String key, String hashKey, Long delta) {return redisTemplate.opsForHash().increment(key, hashKey, delta);}@Overridepublic Long hDecr(String key, String hashKey, Long delta) {return redisTemplate.opsForHash().increment(key, hashKey, -delta);}@Overridepublic Set<Object> sMembers(String key) {return redisTemplate.opsForSet().members(key);}@Overridepublic Long sAdd(String key, Object... values) {return redisTemplate.opsForSet().add(key, values);}@Overridepublic Long sAdd(String key, long time, Object... values) {Long count = redisTemplate.opsForSet().add(key, values);expire(key, time);return count;}@Overridepublic Boolean sIsMember(String key, Object value) {return redisTemplate.opsForSet().isMember(key, value);}@Overridepublic Long sSize(String key) {return redisTemplate.opsForSet().size(key);}@Overridepublic Long sRemove(String key, Object... values) {return redisTemplate.opsForSet().remove(key, values);}@Overridepublic List<Object> lRange(String key, long start, long end) {return redisTemplate.opsForList().range(key, start, end);}@Overridepublic Long lSize(String key) {return redisTemplate.opsForList().size(key);}@Overridepublic Object lIndex(String key, long index) {return redisTemplate.opsForList().index(key, index);}@Overridepublic Long lPush(String key, Object value) {return redisTemplate.opsForList().rightPush(key, value);}@Overridepublic Long lPush(String key, Object value, long time) {Long index = redisTemplate.opsForList().rightPush(key, value);expire(key, time);return index;}@Overridepublic Long lPushAll(String key, Object... values) {return redisTemplate.opsForList().rightPushAll(key, values);}@Overridepublic Long lPushAll(String key, Long time, Object... values) {Long count = redisTemplate.opsForList().rightPushAll(key, values);expire(key, time);return count;}@Overridepublic Long lRemove(String key, long count, Object value) {return redisTemplate.opsForList().remove(key, count, value);}@Overridepublic boolean tryLock(String key, long timeOut, long expireTime) throws InterruptedException {RLock lock = redissonClient.getLock(key);return lock.tryLock(timeOut, expireTime, TimeUnit.SECONDS);}/*** 解锁* @param key* @return*/@Overridepublic void unLock(String key){RLock lock = redissonClient.getLock(key);lock.unlock();}@Overridepublic RReadWriteLock getReadWriteLock(String lockKey) {return redissonClient.getReadWriteLock(lockKey);}
}
第八步:添加访问频率限制拦截器
AntiBrushInterceptor.java
package com.lm.system.interceptor;import com.lm.system.annotation.AccessLimit;
import com.lm.system.common.AccessKey;
import com.lm.system.redis.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;/*** 访问频率限制拦截器* @author DUHAOLIN* @date 2024/11/13*/
@Slf4j
public class AntiBrushInterceptor implements HandlerInterceptor {private final RedisService redisService;public AntiBrushInterceptor(RedisService redisService) {this.redisService = redisService;}private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断请求是否属于方法请求if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;//获取方法中的注解,判断是否有该注解AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);if (accessLimit == null) {return true;}int seconds = accessLimit.seconds();int maxCount = accessLimit.maxCount();boolean needLogin = accessLimit.needLogin();String key = request.getRequestURI();if (needLogin) {//判断是否登录key += "_userId001"; //已登录,获取userId}//从redis中获取用户的访问次数AccessKey accessKey = AccessKey.withExpire(seconds);String realKey = accessKey.getPrefix() + key;Integer count = (Integer) redisService.get(realKey);//访问次数处理if (count == null) {//首次访问redisService.set(realKey, 1, 60);}else if (count < maxCount) {//加1redisService.incr(realKey, 1);}else {//超出访问次数log.info("进入服务降级,时间{}", LocalDateTime.now().format(FORMATTER));response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());response.getWriter().write("Too Many Requests. Number of visits: " + maxCount);return false;}}return true;}
}
效果图
前三次访问正常,第四次开始返回错误信息。
项目结构图
参考链接
如何防范API经常被人频繁调用【https://baijiahao.baidu.com/s?id=1791472081681790682&wfr=spider&for=pc】
API接口防刷的9种方案【https://baijiahao.baidu.com/s?id=1802852256970678261&wfr=spider&for=pc】
Spring Boot 项目的 API 接口防刷【https://www.iocoder.cn/Fight/Spring-Boot-project-API-anti-brush-interface/?self】
相关文章:
【SpringBoot】28 API接口防刷(Redis + 拦截器)
Gitee仓库 https://gitee.com/Lin_DH/system 介绍 常用的 API 安全措施包括:防火墙、验证码、鉴权、IP限制、数据加密、限流、监控、网关等,以确保接口的安全性。 常见措施 1)防火墙 防火墙是网络安全中最基本的安全设备之一,…...
IT运维专家给年轻人一些职业上的建议
运维工作在现代企业中是非常重要的一环,保证系统的稳定性、可用性以及安全性对企业的正常运营至关重要。以下是我给年轻人的一些职业发展建议,希望能够帮助你们在运维领域找到方向并取得成功。 1. 夯实基础,扎实技术功底 精通操作系统与网络:运维工作需要深入理解操作系统…...
Django基础之路由
一.前言 前面我们说了django的安装于基础配置,基础知识点我就细分下来,每天和大家讲一点,今天就要和大家说django的基础知识点了,我们今天先来讲路由,内容不多,希望大家记住 二.传统路由 路由就是前面一个…...
Python实例化中默认值的行为及应用
Python实例化中默认值的行为及应用 适合初学者阅读 本文要点 使用可变对象作为默认参数会导致所有实例共享同一对象,引发意外的数据修改。不可变对象作为默认参数时,每次实例化都会创建新的对象,不会共享数据。推荐使用None作为默认值&…...
【WRF后处理】WRF模拟效果评价及可视化:MB、RMSE、IOA、R
【WRF后处理】模拟效果评价及可视化 准备工作模型评价指标Python实现代码Python处理代码:导入站点及WRF模拟结果可视化图形及评价指标参考在气象和环境建模中(如使用 WRF 模型进行模拟),模型性能评价指标是用于定量评估模拟值与观测值之间偏差和拟合程度的重要工具。 本博客…...
ShenNiusModularity项目源码学习(4:身份认证)
ShenNiusModularity项目有两套启动方式,一种是ShenNius.Admin.Mvc项目启动,该项目为MVC模式,带前台页面,也有后台服务,另一种是ShenNius.Admin.Hosting,该项目启动后仅提供后台服务,供其它前台项…...
python+django自动化部署日志采用WebSocket前端实时展示
一、开发环境搭建和配置 # channels是一个用于在Django中实现WebSocket、HTTP/2和其他异步协议的库。 pip install channels#channels-redis是一个用于在Django Channels中使用Redis作为后台存储的库。它可以用于处理#WebSocket连接的持久化和消息传递。 pip install channels…...
flink学习(6)——自定义source和kafka
概述 SourceFunction:非并行数据源(并行度只能1) --接口 RichSourceFunction:多功能非并行数据源(并行度只能1) --类 ParallelSourceFunction:并行数据源(并行度能够>1) --接口 RichParallelSourceFunction:多功能并行数据源(并行度能够>1) --类 【建议使用的】 ——…...
开发常见问题及解决
1.DBeaver 报Public Key Retrieval is not allowed 在使用DBeaver连接数据库时出现“Public Key Retrieval is not allowed”错误,主要是因为数据库连接配置的安全策略导致的。以下是详细的解释和解决方法: 错误原因 这个错误通常出现在连接MySQL数据…...
python excel接口自动化测试框架!
今天采用Excel继续写一个接口自动化测试框架。 设计流程图 这张图是我的excel接口测试框架的一些设计思路。 首先读取excel文件,得到测试信息,然后通过封装的requests方法,用unittest进行测试。 其中,接口关联的参数通过正则进…...
mybatis:You have an error in your SQL syntax;
完整报错You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near false, false, false, false, false, false, false, false, false, false, false, at line 1 SQL: INSERT INTO user …...
使用 Maven 开发 IntelliJ IDEA 插件
使用 Maven 开发 IntelliJ IDEA 插件的完整流程 1. 创建 Maven 项目 1.1 使用 IntelliJ 创建 Maven 项目 打开 IntelliJ IDEA,点击 File > New > Project。选择 Maven,填写项目名称和 GroupId,例如: GroupId: com.exampl…...
Windows修复SSL/TLS协议信息泄露漏洞(CVE-2016-2183) --亲测
漏洞说明: 打开链接:https://docs.microsoft.com/zh-cn/troubleshoot/windows-server/windows-security/restrict-cryptographic-algorithms-protocols-schannel 可以看到: 找到:应通过配置密码套件顺序来控制 TLS/SSL 密码 我们…...
uniapp生命周期:应用生命周期和页面生命周期
文章目录 1.应用的生命周期2.页面的生命周期 1.应用的生命周期 生命周期的概念:一个对象从创建、运行、销毁的整个过程被称为生命周期 生命周期函数:在生命周期中每个阶段会伴随着每一个函数的出发,这些函数被称为生命周期函数 所有页面都…...
基于SSM的婴幼儿用品商城系统+LW示例参考
1.项目介绍 功能模块:管理员(产品管理、产品分类、会员管理、订单管理、秒杀活动、文章管理、数据统计等)、普通用户(登录注册、个人中心、购物车、我的收藏、各类信息查看等)技术选型:SSM,jsp…...
【工具变量】城市供应链创新试点数据(2007-2023年)
一、测算方式:参考C刊《经济管理》沈坤荣和乔刚老师(2024)的做法,使用“供应链创新与应用试点”的政策虚拟变量(TreatPost)表征。若样本城市为试点城市,则赋值为 1,否则为 0…...
【carla生成车辆时遇到的问题】carla显示的坐标和carlaworld中提取的坐标y值相反
项目需要重新运行了一下generate_car.py的脚本,发现死活生成不了,研究了半天,发现脚本里面生成车辆的坐标值y和carla_ros_bridge_with_example_ego_vehicle.launch脚本打开的驾驶操控界面里面的y值正好是相反数! y1-y2 因为,我运行…...
Jira使用笔记二 ScriptRunner 验证问题创建角色
背景 最近在对公司Jira工作流改造,收到这么一个要求:某些问题类型只有某些角色可以创建。本来是想通过Jira内建的权限控制来处理的。结果点到权限页面,心都凉透了。 好吧,那只能上脚本了。最终使用ScriptRunner的Simple scripte…...
Java线程的使用
Java中的线程是用来实现多任务并发执行的机制。在Java中,主要有两种方式来创建和使用线程:实现Runnable接口和继承Thread类。 实现Runnable接口: 创建一个类,实现Runnable接口,并重写run()方法。在run()方法中定义线程…...
自动化测试工具Ranorex Studio(四十三)-RANOREXPATH编辑器5
代码示例 下面的代码示例将讲解如何使用Ranorex API来编写代码模块,或者是使用用户代码来扩展录制的模块。 在代码中使用对象库 使用对象库等待UI元素 建立Adapter来访问更多的属性和方法 为对象库元素建立一组Adapter 使用Validate类 强制一个测试用例失败 设置aut…...
超高流量多级缓存架构设计!
文章内容已经收录在《面试进阶之路》,从原理出发,直击面试难点,实现更高维度的降维打击! 文章目录 电商-多级缓存架构设计多级缓存架构介绍多级缓存请求流程负载均衡算法的选择轮询负载均衡一致性哈希负载均衡算法选择 应用层 Ngi…...
数据结构(Java)—— ArrayList
1.线性表 线性表( linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在…...
实习冲刺第三十三天
102.二叉树的层序遍历 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]示例…...
Uniapp开发下拉刷新功能onPullDownRefresh/onReachBottom
文章目录 1.onPullDownRefresh2.onReachBottom 1.onPullDownRefresh 在 js 中定义 onPullDownRefresh 处理函数(和onLoad等生命周期函数同级),监听该页面用户下拉刷新事件。 需要在 pages.json 里,找到的当前页面的pages节点&am…...
什么是 C++ 中的函数对象?函数对象与普通函数有什么区别?如何定义和使用函数对象?
1) 什么是 C 中的函数对象?它有什么特点? 在 C 中,函数对象(也称为仿函数或 functor)是一种重载了 operator() 的对象。这意味着这些对象可以像函数一样被调用。函数对象通常用于需要传递行为(即代码&…...
PointNet++论文复现
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
【VUE】el-table表格内输入框或者其他控件规则校验实现
1、封装组件 1、规则校验一般基于form表单实现,因此需要给具体控件套一层form表单 新建组件input-required.vue,内容如下 <template><div><el-form ref"formRef" :model"form" :rules"formRules" label-…...
django开发中html继承模板样式
存在问题: django开发中,不同页面样式相同,如何共用一套母版,避免每个页面都重复写样式; 解决方案: 添加一个母版,如“layout.html”,在需要继承的位置添加{% block content %}{% e…...
MT6769/MTK6769核心板规格参数_联发科安卓主板开发板方案
MT6769安卓核心板具有集成的蓝牙、FM、WLAN和GPS模块,是一个高度集成的基带平台,结合了调制解调器和应用处理子系统,以支持LTE/LTE-A和C2K智能手机应用。 该芯片集成了两个工作频率高达2.0GHz的ARMCortex-A75内核、六个工作频率高达1.70GHz的…...
鸿蒙进阶篇-状态管理之@Provide与@Consume
大家好,这里是鸿蒙开天组,今天我们来学习一下状态管理中的Provide与Consume。 一、概述 嘿!大家还记得这张图吗?不记得也要记得哦,因为这张图里的东西,既是高频必考面试题,也是实际开发中&…...
wordpress post id/深圳网站建设推广
>0️⃣python数据结构与算法学习路线 >学习内容: >- 基本算法:枚举、排序、搜索、递归、分治、优先搜索、贪心、双指针、动态规划等... >- 数据结构:字符串(string)、列表(list)、元组(tuple)、字典(dictionary)、集合(set)、数组…...
怎样做网站文件验证/网络营销郑州优化推广公司
网友 OOXX 在找好用的 webmail,老苏觉得 Cypht 还不错 什么是 Cypht ? Cypht 是一个简单、轻量级和现代的 Webmail 客户端,它将多个帐户聚合到一个界面中。除了电子邮件帐户,它还支持 Atom/RSS 源。 安装 建数据库 数据库直接用…...
重庆学校网站建设/东莞百度快速排名优化
FillOval 和DrawOval 用来填充和绘制椭圆。下面例子显示椭圆的用法。 private void Ovals() { Color redColor new Color(0x96ff0000, true); Color greenColor new Color(0x00ff00); AffineTransform mat1; mat1 new AffineTransform(); mat1.Translate(30, 40); mat1.Rota…...
如何攻击织梦做的网站/百度小说风云榜排行榜官网
embOS是一个优先级控制的多任务系统,是专门为各种微控制器应用于实时系统应用的嵌入式操作系统.是一个具有最小RAM和ROM占用的、高速的、多功能的高性能工具。 特点: 贯穿embOS的整个开发过程,微控制器有限的资源一直是开发者所顾…...
知乎 wordpress插件/网络营销外包推广价格
Topic 如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。 例如,“tars” 和 “rats” 是相似的 (交换 0 与 2 的位置);…...
做邀请函的网站/网站推广的平台
为什么要使用构建器(Buildle模式) 静态工厂和构造方法都有共同的局限性:不能很好的扩展到大量的可选参数。 模拟一个场景: 一个Person对象,它有2个必要的参数(name,age),2个可选参数(sex,address),我们来获取它的实例。 1、静态工厂方法实现 - 重叠构造方…...