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

手动搭建gateway,项目集成gateway实现Token效果

目录

  • 背景
  • 步骤
    • 1、首先创建springboot项目
    • 2、引入依赖
    • 3、配置文件!!!!!(超级重要!!!根据自己的需要进行配置)
    • 4、相关类
    • 我们在服务中进行的白名单中接口的操作如下
  • 测试
    • 存:
    • 拿:
  • 总结

背景

现在想要进行token校验,故引入gateway服务。
首先阅读官网,知道概念:Gateway官网详解

步骤

1、首先创建springboot项目

看整体的目录结构
在这里插入图片描述

2、引入依赖

 <dependencies><!--gateway的依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--nacos注册与发现--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--nacos配置中心来做配置管理--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><dependency><groupId>com.tfjybj</groupId><artifactId>fewCode-common-redis</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId></dependency></dependencies>

3、配置文件!!!!!(超级重要!!!根据自己的需要进行配置)

server:port: 8088servlet:context-path: /apispring:cloud:gateway:discovery:locator:enabled: trueroutes:- id: login_routeuri: lb://fewCode-provider-login#uri: http://localhost:8001/predicates:- Path=/login/**,/Curriculum/**,/classes/**filters:- RewritePath=/(?<segment>.*),/$\{segment}
leyou:filter:  #需要进行过滤的白名单allowPaths:- /login/checkLogin- /login/user- /login/userLogin- /upload/filetokenExpire: 1440

这份配置是用于Spring Cloud Gateway的配置文件,用于构建API网关。让我来解释一下每个部分的含义:

服务器配置:

服务器监听端口为8088。
Servlet的上下文路径设置为"/api",意味着所有API的端点都将具有这个前缀。
Spring Cloud Gateway配置:

服务发现定位器:启用,这意味着网关将使用服务发现来查找后端服务的路由。在动态的微服务环境中非常有用。
路由:路由配置指定了网关如何将传入的请求路由到后端服务。在这里,定义了一个路由:
id: login_route - 这是该路由的唯一标识符。
uri: lb://fewCode-provider-login - 后端服务的URI,用于处理具有负载均衡器(lb)的请求。服务名为"fewCode-provider-login",网关将使用服务发现来定位该服务。
predicates: 定义了应用此路由的条件。在这里,该路由将用于以"/login/“、”/Curriculum/“或”/classes/"开头的路径的请求。
filters: 对请求应用的过滤器,在将其转发到后端服务之前。在这里,使用了一个RewritePath过滤器,用于删除路径末尾的斜杠。
自定义配置:

leyou:这似乎是一些与"leyou"相关的自定义配置,用于特定的过滤逻辑。
filter:这是一个允许白名单路径的配置。
allowPaths:列出的路径将无需任何额外过滤而被允许。这些路径的请求不会受到任何额外的检查。
tokenExpire:设置令牌过期的时间限制(以分钟为单位),这里设置为1440分钟(24小时)。
总体而言,这份配置将Spring Cloud Gateway配置成将传入的请求路由到名为"fewCode-provider-login"的后端服务,前提是请求路径以"/login/“、”/Curriculum/“或”/classes/"开头。同时,它提供了一个白名单,允许无需任何额外过滤的路径。

4、相关类

package com.tfjybj.gateway.config;import com.tfjybj.gateway.filter.AuthorizeFilter;
import com.tfjybj.gateway.filter.RenewFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FilterConfig {@Beanpublic AuthorizeFilter authGatewayFilter() {//配置拦截器参数//order:序号,设置拦截器执行顺序,AuthGatewayFilter为1return new AuthorizeFilter(0);}@Beanpublic RenewFilter renewFilter(){// 续约Tokenreturn new RenewFilter(5);}}
package com.tfjybj.gateway.config;import com.tfjybj.gateway.util.FilterProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** 请求白名单**/
@Component
public class IgnorePath {@Autowiredprivate FilterProperties filterProperties;public boolean isAllowPath(String path) {//遍历白名单for (String allowPath : filterProperties.getAllowPaths()) {//判断是否允许if(path.startsWith(allowPath)){return true;}}return  false;}}
package com.tfjybj.gateway.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;@Configuration
public class LapCorsConfiguration {@Beanpublic CorsWebFilter corsWebFilter(){UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();//1、配置跨域corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");corsConfiguration.addAllowedOrigin("*");corsConfiguration.setAllowCredentials(true);source.registerCorsConfiguration("/**",corsConfiguration);return new CorsWebFilter(source);}
}
package com.tfjybj.gateway.filter;import com.alibaba.fastjson.JSON;import com.tfjybj.gateway.config.IgnorePath;
import com.tfjybj.gateway.result.ResultMsgEnum;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.HashMap;public class AuthorizeFilter implements GlobalFilter, Ordered {private static final Logger log = LogManager.getLogger();@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate IgnorePath ignorePath;private final int order;public AuthorizeFilter(int order) {this.order = order;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();HttpHeaders headers = request.getHeaders();//获取请求的url路径String path = request.getURI().getPath();boolean flag=ignorePath.isAllowPath(path);if (flag) {log.info("请求在白名单中,metaverse.filter: {}",path);return chain.filter(exchange);} else {//获取token值String authorization = headers.getFirst("Authorization");log.info("Authorization值{}", authorization);authorization = authorization.split("Bearer ")[1];//判断redis中是否有tokenBoolean aBoolean = redisTemplate.hasKey("fewCode:userinfo:" + authorization);if (aBoolean){return chain.filter(exchange);}else {//声明变量ServerHttpResponse response = exchange.getResponse();HashMap map = new HashMap();String resp;DataBuffer bodyDataBuffer;//设置响应头response.setStatusCode(HttpStatus.FORBIDDEN);map.put("code", "403");map.put("message", ResultMsgEnum.AUTH_FAILED.getMsg());resp = JSON.toJSONString(map);bodyDataBuffer = response.bufferFactory().wrap(resp.getBytes());response.getHeaders().add("Content-Type","application/json;charset=UTF-8");return response.writeWith(Mono.just(bodyDataBuffer));}}}@Overridepublic int getOrder() {return this.order;}}
package com.tfjybj.gateway.filter;import com.tfjybj.gateway.config.IgnorePath;
import com.tfjybj.gateway.util.FilterProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.concurrent.TimeUnit;/*** token续约的逻辑**/
public class RenewFilter implements GlobalFilter, Ordered {private static final Logger log = LogManager.getLogger();private final int order;@Autowiredprivate StringRedisTemplate stringRedisTemplate;public RenewFilter(int order) {this.order = order;}@Autowiredprivate IgnorePath ignorePath;@Autowiredprivate FilterProperties filterProperties;@Overridepublic int getOrder() {return this.order;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();HttpHeaders headers = request.getHeaders();//获取请求的url路径String path = request.getURI().getPath();boolean flag=ignorePath.isAllowPath(path);if (flag) {log.info("请求在白名单中,metaverse.filter: {}", path);return chain.filter(exchange);}//token值String authorization = headers.getFirst("Authorization");authorization = authorization.split("Bearer ")[1];log.info("Authorization值{}", authorization);//TOKEN续活//解析TOKEN//根据uuid,延长用户信息String uuid = authorization;String key = "fewCode:userinfo:" + uuid;stringRedisTemplate.expire(key, filterProperties.getTokenExpire(), TimeUnit.MINUTES);return chain.filter(exchange);}}
package com.tfjybj.gateway.result;public enum ResultMsgEnum {FIND_SUCCESS("查询成功!"),FIND_FAIL("查询失败!"),UPDATE_SUCCESS("更新成功"),UPDATE_FAIL("更新失败"),DELETE_SUCCESS("删除成功"),DELETE_FAIL("删除失败"),SEND_SUCCESS("发送成功"),SEND_FAIL("发送失败"),EXECUTE_SUCCESS("执行成功!"),EXECUTE_FAIL("执行失败!"),AUTH_FAILED("权限认证失败"),AUTH_SUCCESS("权限认证成功");private final String msg;ResultMsgEnum(String msg) {this.msg = msg;}public String getMsg() {return msg;}
}
package com.tfjybj.gateway.util;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;import java.util.List;@Component
@RefreshScope
@ConfigurationProperties(prefix = "leyou.filter")
public class FilterProperties {public void setAllowPaths(List<String> allowPaths) {this.allowPaths = allowPaths;}public List<String> getAllowPaths() {return allowPaths;}private List<String> allowPaths;/*** token过期时间*/private Integer tokenExpire;public Integer getTokenExpire() {return tokenExpire;}public void setTokenExpire(Integer tokenExpire) {this.tokenExpire = tokenExpire;}
}
package com.tfjybj.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
//@EnableDiscoveryClientpublic class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}}

我们在服务中进行的白名单中接口的操作如下

(主要是根据传过来的信息生成token,然后以键值对的形式存入redis中,方便后续通过redis根据token拿人的信息):

RestController
@RequestMapping("/login")
public class LoginController {@Autowiredprivate Actor actorInfo;@ApiOperation("学生登录验证")@RequestMapping(value="checkLogin",method= RequestMethod.POST)@Transactional(rollbackFor = Exception.class)public Actor checkLoginInfo(@RequestBody Actor actor){return actorInfo.notifyStudentCheckLoginInfo(actor);}
}
public Actor notifyStudentCheckLoginInfo(Actor student){Actor actor;for (Actor studentInfo:allStudent){actor=studentInfo.checkLoginInfo(student);if (!ObjectUtils.isEmpty(actor)){//生成UUIDString uuid = CreateUUID.createUUID();//存入redissaveRedis(uuid, actor);//生成token,封装到请求头putHeader(uuid);return actor;}}return null;}
public class CreateUUID {public CreateUUID() {}public static String createUUID() {String preUuid = UUID.randomUUID().toString();String newUUID = preUuid.replace("-", "");return newUUID;}
}
   private void saveRedis(String uuid, Object userInfo) {//拼接key,user信息序列化,存入redis,过期时间在nacos中设置String key = "fewCode:userinfo:" + uuid;String userJson = JSONObject.toJSONString(userInfo);redisTemplate.opsForValue().set(key, userJson);redisTemplate.expire(key, 1440, TimeUnit.MINUTES);}
   private void putHeader(String token) {ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletResponse response = sra.getResponse();response.setHeader("Authorization", token);}

测试

存:

在这里插入图片描述

在这里插入图片描述
将断点打到这里,可以观察到我们要请求的服务IP+端口号还有url地址,如下
相当手动访问

在这里插入图片描述

调通后存入redis中如下

在这里插入图片描述

拿:

然后拿着根据token获取信息

在这里插入图片描述

在这里插入图片描述

package com.tfjybj.login.service.impl;import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;@Service
public class AnalysisTokenService {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 获取当前登陆人id** @return*/public String getUserId() {JSONObject userData = getUserData();return userData.get("id").toString();}/*** 获取用户code** @return*/public String getUserAccount() {JSONObject userData = getUserData();return userData.get("account").toString();}/*** 获取当前登陆人name** @return*/public String getUserName() {JSONObject userData = getUserData();return userData.get("name").toString();}public JSONObject getUserData() {//从请求头获取tokenServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();String token = sra.getRequest().getHeader("Authorization");token = token.split("Bearer ")[1];//解析tokenJSONObject userJson = this.analysisToken(token);return userJson;}public JSONObject analysisToken  (String token){//解析tokenString key= "fewCode:userinfo:"+token;String userInfoStr = redisTemplate.opsForValue().get(key);JSONObject userJson = JSONObject.parseObject(userInfoStr);
//        String data = jsonObject.get("data").toString();
//        JSONObject userJson = JSONObject.parseObject(data);return userJson;}}

总结

1、搞懂gateway是干嘛的
2、知道配置文件中各个参数是什么

相关文章:

手动搭建gateway,项目集成gateway实现Token效果

目录 背景步骤1、首先创建springboot项目2、引入依赖3、配置文件&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff08;超级重要&#xff01;&#xff01;&#xff01;根据自己的需要进行配置&#xff09;4、相关类我们在服务中进行的白名单中接口的操作如…...

linux下SVN服务器搭建

在本教程中&#xff0c;我们将介绍如何在Linux系统下搭建Subversion&#xff08;SVN&#xff09;服务器。Subversion是一种流行的版本控制系统&#xff0c;它允许多个人在同一项目上进行协作&#xff0c;同时避免了他们各自的更改发生冲突。 安装SVN 在大多数Linux发行版中&am…...

技术等级 TRL 定义

“不同环境、不同目标下TRL表述不一样” 技术等级 TRL 定义 TRL1 基本原理提出和发现 TRL2 技术应用研究 TRL3 完成概念验证&#xff0c;如叶栅试验、燃烧室头部试验等 TRL4 完成模拟部件试验&#xff0e;如压气机性能试验&#xff0c;燃烧室扇形试验 TRL5 完…...

DHorse v1.3.0 发布,基于k8s的发布平台

综述 DHorse是一个简单易用、以应用为中心的云原生DevOps系统&#xff0c;具有持续集成、持续部署、微服务治理等功能&#xff0c;无需安装依赖Docker、Maven、Node等环境即可发布Java、Vue、React应用&#xff0c;主要特点&#xff1a;部署简单、操作简洁、功能快速。 新增特…...

Redis - 缓存的双写一致性

概念&#xff1a; 当修改了数据库的数据也要同时更新缓存的数据&#xff0c;缓存和数据库的数据要保持一致 那为什么会有不一致的情况呢&#xff1f; 如果不追求一致性&#xff0c;正常有两种做法 先修改数据库 后删除旧的缓存先删除旧的缓存 再修改数据库 我们以先删除旧的…...

opencv03-Mat矩阵API的使用

opencv03-Mat矩阵API的使用 构造方法(具体介绍看API文档) int main() {Mat m1 Mat(200, 100, CV_8UC1);imshow("o1", m1);Mat m2 Mat(Size(100, 200), CV_8UC1);imshow("o2", m2);Mat m3 Mat(200, 100, CV_8UC3, Scalar(255, 0, 0));imshow("o3&…...

2023届浙江大学MPA提面A资格经验总结分享

本人是去年报考的浙大MPA项目&#xff0c;并通过提面获得了A资格&#xff0c;新一年浙大MPA项目提前批面试已经开始了&#xff0c;受达立易考周老师邀请来分享下我的提面经验&#xff0c;希望我的经验能对还在迷茫中的小伙伴有所帮助。 点开提面通知&#xff0c;首先看到…...

BugKu CTF(杂项篇MISC)—想要种子吗

BugKu CTF(杂项篇MISC)—想要种子吗 提 示: 描 述:flag{} 题目下载后是一张图片&#xff0c;打开如下。 一、工具 十六进制编辑器010 editor kali系统文件分离工具binwalk或者foremost 维吉尼亚密码 STEGHIDE图片隐写工具 文章所需的软件下载地址 ARCHPR压缩包密码破解…...

类之间的关系

1、类关系 继承、实现、依赖、组合、聚合 继承&#xff1a;一个类继承另一个类&#xff1b; 实现&#xff1a;一个类实现另一个接口&#xff1b; 依赖&#xff1a;一个类作为另一个的局部变量&#xff0c;方法的参数&#xff0c;临时对象等&#xff1b; 组合&#xff1a;一个类…...

【蓝图】p40-p43对象引用、变量有效性、实现键盘控制物体自转、简单点名系统

p40-p43对象引用、变量有效性、实现键盘控制物体自转、简单点名系统 p40对象引用、变量有效性p41实现键盘控制物体自转创建bool值控制旋转实现通过键盘控制自转 p42p43简单点名系统Get All Actors Of Class&#xff08;获得场景中所有该类的actor演员&#xff09;getFor Each L…...

vscode设置远程登录和免密登录

首先&#xff0c;我们去官网下载VScode 安装过程比较简单&#xff0c;大家自行安装即可&#xff0c;注意建议安装在除C盘外的其他盘中。 安装完成后&#xff0c;打开我们下载好的VScode&#xff0c;点击左侧的Extensions选项&#xff0c;搜索Remote&#xff0c;Install第一项R…...

今日头条面试真题及答案,软件测试工程师面试秘籍

试题1&#xff0e;在浏览器地址栏里输入一个网址&#xff0c;接下来会发生什么&#xff1f; 答案&#xff1a;发生的操作如下。 &#xff08;1&#xff09;浏览器查找该网址的IP地址。 &#xff08;2&#xff09;浏览器根据解析得到的IP地址向Web服务器发送一个HTTP请求。 &am…...

JavaScript Windows 浏览器对象模型

Window 对象 BOM 的核心就是 window 对象所有浏览器都支持 window 对象。它表示浏览器窗口。所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员。全局变量是 window 对象的属性。全局函数是 window 对象的方法。HTML DOM 的 document 也是 window 对象的属…...

【uniapp 获取缓存及清除缓存】

小程序及H5 获取缓存&#xff1a; 使用uniapp中的wx.getStorageInfoSync()方法可以获取当前小程序或H5应用的本地缓存信息&#xff0c;如下所示&#xff1a; let storageInfo uni.getStorageInfoSync() console.log(storageInfo)其中&#xff0c;storageInfo是一个对象&…...

【vim 学习系列文章 2 - vim 常用插件配置】

文章目录 1.1 vim 常用插件1.1.1 vim 插件 Pathogen 管理1.1.2 vim 常用插件推荐1.1.3 vim Leaderf1.1.4 vim ripgrep 工具1.1.5 vim Leaderf 配合 rg1.1.6 vim autocmd 配置 1.2 其它类型文件 vimrc 配置1.2.1 System Verilog vimrc 配置 上篇文章&#xff1a;vim 学习系列文章…...

【外卖系统】修改菜品

需求分析 在菜品管理列表页面点击修改按钮&#xff0c;跳转到修改页面&#xff0c;在修改页面回显菜品相关信息并进行修改&#xff0c;在最后点击确定按钮完成修改操作 代码设计 页面发送ajax请求&#xff0c;请求服务端获取分类数据&#xff0c;用于菜品分类下拉框中数据显…...

【暑期每日一练】 day11

目录 选择题 &#xff08;1&#xff09; 解析&#xff1a; &#xff08;2&#xff09; 解析&#xff1a; &#xff08;3&#xff09; 解析&#xff1a; &#xff08;4&#xff09; 解析&#xff1a; &#xff08;5&#xff09; 解析&#xff1a; 编程题 题一 描…...

神经概率语言模型

本文主要参考《A Neural Probabilistic Language Model》这是一篇很重要的语言模型论文,发表于2003年。主要贡献如下: 提出了一种基于神经网络的语言模型&#xff0c;是较早将神经网络应用于语言模型领域的工作之一&#xff0c;具有里程碑意义。采用神经网络模型预测下一个单词…...

什么是shadow DOM?

Shadow DOM&#xff08;影子DOM&#xff09;是一种用于在Web组件中封装HTML、CSS和JavaScript的技术。它是Web组件的一个重要特性&#xff0c;旨在将组件的结构、样式和行为封装在一个独立的、隔离的DOM树中&#xff0c;从而与主文档的DOM树相互隔离。 传统的Web开发中&#x…...

我的 365 天创作纪念日

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…...

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

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

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

Matlab实现任意伪彩色图像可视化显示

Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中&#xff0c;如何展示好看的实验结果图像非常重要&#xff01;&#xff01;&#xff01; 1、灰度原始图像 灰度图像每个像素点只有一个数值&#xff0c;代表该点的​​亮度&#xff08;或…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)

引言 在嵌入式系统中&#xff0c;用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例&#xff0c;介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单&#xff0c;执行相应操作&#xff0c;并提供平滑的滚动动画效果。 本文设计了一个…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...