【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】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/479921.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java全栈day10--后端Web基础(基础知识)

引言&#xff1a;只要能通过浏览器访问的网站全是B/S架构&#xff0c;其中最常用的服务器就是Tomcat 在浏览器与服务器交互的时候采用的协议是HTTP协议 一、Tomcat服务器 1.1介绍 官网地址&#xff1a;Apache Tomcat - Welcome! 1.2基本使用(网上有安装教程&#xff0c;建议…

elasticsearch的索引模版使用方法

5 索引模版⭐️⭐️⭐️⭐️⭐️ 索引模板就是创建索引时要遵循的模板规则索引模板仅对新创建的索引有效&#xff0c;已经创建的索引并不受索引模板的影响 5.1 索引模版的基本使用 1.查看所有的索引模板 GET 10.0.0.91:9200/_index_template2.创建自定义索引模板 xixi &…

英语知识网站开发:Spring Boot框架应用

3系统分析 3.1可行性分析 通过对本英语知识应用网站实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本英语知识应用网站采用SSM框架&#xff0c;JAVA作为开发语…

Linux自动化构建-make/Makefile

目录 1. 背景2. 基本使用3. 推导过程4. 好用的操作5. 拓展语法 1. 背景 会不会写makefile&#xff0c;从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒⼀个⼯程中的源⽂件不计数&#xff0c;其按类型、功能、模块分别放在若⼲个⽬录中&#xff0c;makefile定义了⼀系列的规…

Ubuntu20.04+ROS 进行机械臂抓取仿真:环境搭建(一)

目录 一、从官网上下载UR机械臂 二、给UR机械臂添加夹爪 三、报错解决 本文详细介绍如何在Ubuntu20.04ROS环境中为Universal Robots的UR机械臂添加夹爪。首先从官方和第三方源下载必要的软件包&#xff0c;包括UR机械臂驱动、夹爪插件和相关依赖。然后&#xff0c;针对gazeb…

C++11(下)

C11&#xff08;下&#xff09; 1.条件变量2.包装器&#xff08;重要&#xff09;3.bind &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列专栏&#xff1a;【C的学习】 &#x1f4dd;&#x1f4…

【组件封装】uniapp vue3 封装一个自定义下拉刷新组件pullRefresh,带刷新时间和加载动画教程

文章目录 前言一、实现原理二、组件样式和功能设计三、scroll-view 自定义下拉刷新使用回顾相关属性&#xff1a;最终版完整代码&#xff1a; 前言 手把手教你封装一个移动端 自定义下拉刷新组件带更新时间和加载动画&#xff08;PullRefresh&#xff09;&#xff0c;以uniapp …

14、保存与加载PyTorch训练的模型和超参数

文章目录 1. state_dict2. 模型保存3. check_point4. 详细保存5. Docker6. 机器学习常用库 1. state_dict nn.Module 类是所有神经网络构建的基类&#xff0c;即自己构建一个深度神经网络也是需要继承自nn.Module类才行&#xff0c;并且nn.Module中的state_dict包含神经网络中…

【Threejs进阶教程-着色器篇】9.顶点着色器入门

【Threejs进阶教程-着色器篇】9.顶点着色器入门 本系列教程第一篇地址&#xff0c;建议按顺序学习认识顶点着色器varying介绍顶点着色器与片元着色器分别的作用Threejs在Shader中的内置变量各种矩阵gl_Position 尝试使用顶点着色器增加分段数增强效果 制作平面鼓包效果鼓包效果…

w058基于web的美发门店管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0…

leetcode 二叉树的最大深度

104. 二叉树的最大深度 已解答 简单 相关标签 相关企业 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3…

VMware ubuntu创建共享文件夹与Windows互传文件

1.如图1所示&#xff0c;点击虚拟机&#xff0c;点击设置&#xff1b; 图1 2.如图2所示&#xff0c;点击选项&#xff0c;点击共享文件夹&#xff0c;如图3所示&#xff0c;点击总是启用&#xff0c;点击添加&#xff1b; 图2 图3 3.如图4所示&#xff0c;出现命名共享文件夹…

matlab 实现混沌麻雀搜索算法的光伏MPPT控制仿真

1、内容简介 略 103-可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略

Unity3D 截图

使用 Unity3D 自带的截图接口&#xff0c;制作截图工具。 截图 有时候我们想对 Unity 的窗口进行截图&#xff0c;如果直接使用一些截图工具&#xff0c;很难截取到一张完整分辨率的图片&#xff08;例如&#xff0c;我们想要截取一张 1920 * 1080 的图片&#xff09;。 其实…

STM32F10x 定时器

使用定时器实现&#xff1a;B5 E5的开关 添加相关的.h路径文件 添加相关的.c配置文件 led.h文件 用于声明LED函数 #ifndef __LED_H //没有定义__LED_H #define __LED_H //就定义__LED_H #define LED1_ON GPIO_ResetBits(GPIOB,GPIO_Pin_5) #defi…

PMP好考吗,有多大的价值?

非常好考&#xff01;PMP目前大陆地区的笔试是只有选择题的&#xff0c;运气好的话 蒙一个都能对&#xff0c;所以PMP的通过率高&#xff0c;这也是很多人考了吐槽PMP没用&#xff0c;是“水证”&#xff0c;但是每年考PMP 的人不减反增&#xff0c;大家可以想一下&#xff0c;…

css:项目

这是一个完整的网站制作的流程 美工会先制作一个原型图&#xff1a; 原型图写的不详细&#xff0c;就是体现一个网页大致的布局 然后美工再做一个psd样例图片 然后再交给程序员 项目 模块化开发&#xff1a;把代码的不同的样式封装起来&#xff0c;需要用到相同样式的标签就…

VsCode 插件推荐(个人常用)

VsCode 插件推荐&#xff08;个人常用&#xff09;

黑马程序员Java项目实战《苍穹外卖》Day01

苍穹外卖-day01 课程内容 软件开发整体介绍苍穹外卖项目介绍开发环境搭建导入接口文档Swagger 项目整体效果展示&#xff1a; ​ 管理端-外卖商家使用 ​ 用户端-点餐用户使用 当我们完成该项目的学习&#xff0c;可以培养以下能力&#xff1a; 1. 软件开发整体介绍 作为一…

Python双向链表、循环链表、栈

一、双向链表 1.作用 双向链表也叫双面链表。 对于单向链表而言。只能通过头节点或者第一个节点出发&#xff0c;单向的访问后继节点&#xff0c;每个节点只能记录其后继节点的信息&#xff08;位置&#xff09;&#xff0c;不能向前遍历。 所以引入双向链表&#xff0c;双…