SpringCloudAlibaba之Gateway

news2024/11/15 21:32:55
1、简介

        网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权、限流、路由、监控等功能。

2、Gateway主要功能
2.1、route 路由

路由是网关的最基本组成,由一个路由 id、一个目标地址 url,一组断言工厂及一组 filter 组成。若断言为 true,则请求将经由 filter 被路由到目标 url。

2.2、predicate 断言

断言即一个条件判断,根据当前的 http 请求进行指定规则的匹配,比如说 http 请求头,请求时间等。只有当匹配上规则时,断言才为 true,此时请求才会被直接路由到目标地址(目标服务器),或先路由到某过滤器链,经过过滤器链的层层处理后,再路由到相应的目标地址(目标服务器)。

2.3、filter 过滤器

对请求进行处理的逻辑部分。当请求的断言为 true 时,会被路由到设置好的过滤器,以对请求或响应进行处理。例如,可以为请求添加一个请求参数,或对请求 URI 进行修改,或为响应添加 header 等。总之,就是对请求或响应进行处理。

3、route路由使用配置示例
3.1、引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
3.2、修改配置文件(application.yml)
spring:
  application:
    name: gateway_8085
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:  # 使用gateway调用服务,服务不正常时,将状态码从503改成404
      loadbalancer:
        use404: true
        # 配置注册中心可以发现
      discovery:
        locator:
          enabled: true
      globalcors:  # 全局跨域设置
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
      routes:
        - id: my_route
          uri: https://www.cnblogs.com/
          predicates:
            - Path=/1114zwl
          metadata:  # 针对单个路由跨域配置 
            cors:
              allowedOrigins: "*"
              allowedMethods:
                - GET
                - POST
        - id: my_route1   # 可配置多个路由规则
          uri: https://www.cnblogs.com/
          predicates:
            - Path=/1114zwl
          metadata:  # 针对单个路由跨域配置 
          # 设置单个路由http请求超时时间
            response-timeout: 200
            connect-timeout: 200
            cors:
              allowedOrigins: "*"
              allowedMethods:
                - GET
                - POST
2.3、使用api方式配置路由(与配置文件同在时,api优先)
@SpringBootConfiguration
public class GatewayConfig {
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder){
        return builder.routes().route("my_route", ps -> ps.path("/**").uri("https://baidu.com")).route("my_route1", ps -> ps.path("/**").uri("https://taoao.com")).build();
    }
}
3、predicate 断言
3.1、After/Before/Between路由断言工厂

        这些断言工厂的参数是一个 UTC 格式的时间。其会将请求访问到 Gateway 的时间与该参数时间相比,若请求时间在参数时间之后/之前/之中,则匹配成功,断言为 true。

具体配置:

spring:
  application:
    name: gateway_8085
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:  
      routes:
        - id: my_route
          uri: https://www.cnblogs.com/
          predicates:
            - Path=/1114zwl
            - After=2023-01-21T17:42:47.789-07:00
            - Before=2023-12-21T17:42:47.789-07:00
            - Between=2023-01-21T17:42:47.789-07:00,2023-12-21T17:42:47.789-07:00
3.2、Cookie/Header/host路由断言工厂

该断言工厂中包含两个参数,分别是 cookie/header 的 key 与 value。当请求中携带了指定 key 与 value的 cookie/header 时,匹配成功,断言为 true。

spring:
  application:
    name: gateway_8085
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway: 
      httpclient: # 设置全局http请求超时时间
        connect-timeout: 1000
        response-timeout: 5s
      routes:
        - id: my_route
          uri: https://www.cnblogs.com/
          predicates:
            - Path=/1114zwl
            - After=2023-01-21T17:42:47.789-07:00
            - Before=2023-12-21T17:42:47.789-07:00
            - Between=2023-01-21T17:42:47.789-07:00,2023-12-21T17:42:47.789-07:00
            - Cookie=city,beijing   # 示例:Cookie: city=beijing
            - Header=X-Request-Id, \d+ # 示例:X-Request-Id=123
            - Host=myhost:9000, ...        # host: myhost:9000
            - Method=GET, POST  # 请求方式
3.3、path路由断言工厂

该断言工厂用于判断请求路径中是否包含指定的 uri。若包含,则匹配成功,断言为 true,此时会将该匹配上的 uri 拼接到要转向的目标 uri 的后面,形成一个统一的 uri

注:更多断言相关配置见官网:Spring Cloud Gateway

4、自定义异常处理类
@Component
@Order(-1)
public class CustomerErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
    public CustomerErrorWebExceptionHandler(ErrorAttributes errorAttributes, ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer){
        super(errorAttributes, new WebProperties.Resources(), applicationContext);
        super.setMessageReaders(serverCodecConfigurer.getReaders());
        super.setMessageWriters(serverCodecConfigurer.getWriters());
    }
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
    
    private Mono<ServerResponse> renderErrorResponse(final ServerRequest request){
        Map<String, Object> map = getErrorAttributes(request, ErrorAttributeOptions.defaults());
        return ServerResponse.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(map));
    }
}

//如果想要自定义map内容需要重写getErrorAttributes方法
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object>  map = new HashMap<>(16);
        map.put("status", HttpStatus.NOT_FOUND.value());
        map.put("msg", "对不起找不到资源");
        return map;
    }
}
5、自定义路由断言工厂

命名规则:该类类名由两部分构成:后面必须是 RoutePredicateFactory,前面为功能前缀,该前缀将来要用在配置文件中

5.1、自定义代码
import lombok.Data;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class AuthRouterPredicateFactory extends AbstractRoutePredicateFactory<AuthRouterPredicateFactory.Config> {
    public AuthRouterPredicateFactory(){
        super(Config.class);
    }
    @Data
    public static class Config {
        private String name;
        private String password;
    }
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name", "password");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return serverWebExchange -> {
            HttpHeaders headers = serverWebExchange.getRequest().getHeaders();
            List<String> pwds = headers.get(config.getName());
            if (pwds.contains(config.getPassword())){
                return true;
            }
            return false;
        };
    }
}
5.2、配置文件内容
spring:
    gateway: 
      routes:
        - id: my_route
          uri: https://www.cnblogs.com/
          predicates:
            - Path=/1114zwl
            - After=2023-01-21T17:42:47.789-07:00
            - Before=2023-12-21T17:42:47.789-07:00
            - Between=2023-01-21T17:42:47.789-07:00,2023-12-21T17:42:47.789-07:00
            - Auth=weilong,123456  # 自定义断言路由工厂配置

6、filter 过滤器
6.1、AddRequestHeader/AddRequestHeadersIfNotPresent/AddRequestParameter/AddResponseHeader过滤工厂

1)、AddRequestHeader过滤器工厂会对匹配上的请求添加指定的 header,可以出现重复请求头。

2)、AddRequestHeadersIfNotPresent过滤器工厂会对匹配上的请求添加指定的 header,前提是该 header 在当前请求中尚未出现过。

3)、AddRequestParameter过滤器工厂会对匹配上的请求添加指定的请求参数。

4)、AddResponseHeader过滤器工厂会给从网关返回的响应添加上指定的 header。

配置文件方式:

spring:
  application:
    name: gateway_8085
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:  # 使用gateway调用服务,服务不正常时,将状态码从503改成404
      loadbalancer:
        use404: true
      discovery:
        locator:
          enabled: true
      globalcors:  # 全局跨域设置
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
      routes:
        - id: my_route
          uri: https://www.cnblogs.com/
          predicates:
            - Path=/1114zwl
            - After=2023-01-21T17:42:47.789-07:00
            - Before=2023-12-21T17:42:47.789-07:00
            - Between=2023-01-21T17:42:47.789-07:00,2023-12-21T17:42:47.789-07:00
            - Auth=weilong,123456
          metadata:  # 针对单个路由跨域配置 
            cors:
              allowedOrigins: "*"
              allowedMethods:
                - GET
                - POST
        #  配置过滤器工厂
          filters:
            - AddRequestHeader=X-Request-Color,blue
            - AddRequestHeadersIfNotPresent=X-Request-Color:blue,City:beijing
            - AddRequestParameter=color,blue
            - AddRequestParameter=color,red
            - AddRequestParameter=size,red
6.2、CircuitBreaker过滤工厂

该过滤器工厂完成网关层的服务熔断与降级。

6.2.1、添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
6.2.2、降级配置
filters:
  - name: CircuitBreaker
    args:
      name: myCircuitBreaker
      fallbackUri: forward:/testFallback  # 在访问指定路由的时候会降级到指定的uri

注:

1)、第一个 name 属性用于指定要使用的 filter 类型,不能随意写。

2)、args 是对指定 filter 的配置参数。

3)、第二个 name 属性用于指定当前所使用断路器的名称,可以随意。fallbackUri 属性用于指定发生断路后要提交的降级 URI。

6.2.3、api方式
@Bean
    public RouteLocator routeFillerLocator(RouteLocatorBuilder builder){
        return builder.routes().route("my_route", ps -> ps.path("/**")
                .filters(fs -> fs.addRequestHeader("X-Request-Header","blue").circuitBreaker(conf -> {
                    conf.setName("myCircuitBreaker").setFallbackUri("forward:/testFallback");
                }))
                .uri("https://baidu.com")).build();
    }
6.3、PrefixPath/StripPrefix/RewritePath 过滤工厂

1)、PrefixPath过滤器工厂会为指定的 URI 自动添加上一个指定的 URI 前辍。

2)、StripPrefix过滤器工厂会为指定的 URI 去掉指定长度的前辍。

3)、RewritePath 过滤器工厂会将请求 URI 替换为另一个指定的 URI 进行访问。RewritePath 有两个参数,第一个是正则表达式,第二个是要替换为的目标表达式。

配置文件方式:

 #  配置过滤器工厂
filters:
  - PrefixPath=/test # 会将请求uri添加/test 前缀
  - StripPrefix=2  # 会将请求的uri去掉两个前缀
  - RewritePath=/test/t1, /test/t2   # 将请求的uri中/test/t1替换成 /test/t2,支持使用正则
6.4、RequestRateLimiterGateway过滤工厂(限流)

        该过滤器工厂通过令牌桶算法对进来的请求进行限流。这个限流器是基于 Redis 的,所以需要导入 Redis 的 Starter 依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

添加限流键解析器

        限流 key可以是用户、URI,也可以是目标服务器的 host 或 IP。本例指定的是根据请求要访问的目标服务器的 host 或 ip 进行限流。

filters:
  - name: RequestRateLimiter
    args:
      key-resolver: "#{@userKeyResolver}"
      redis-rate-limiter.replenishRate: 2
      redis-rate-limiter.burstCapacity: 5
      redis-rate-limiter.requestedTokens: 1
# redis配置
spring:
  data:
    redis:
      host: localhost
      port: 6379
6.5、全局过滤器配置
spring:
  cloud:
  	gateway:
      default-filters:
        - name: CircuitBreaker
          args:
            name: myCircuitBreaker
            fallbackUri: forward:/testFallback

注:对于相同 filter 工厂,在不同位置设置了不同的值,则优先级为:

局部 filter 的优先级高于默认 filter 的, API 式的 filter 优先级高于配置式 filter 的。

7、自定义网关过滤工厂

类名由两部分构成:后面必须是 GatewayFilterFactory,前面为功能前缀,该前缀将来要用在配置文件中。

@Component
public class AddHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    @Override
    public GatewayFilter apply(NameValueConfig config) {
        return (es, chain) -> {
            ServerHttpRequest request = es.getRequest().mutate().header(config.getName(), config.getValue()).build();
            ServerWebExchange webExchange = es.mutate().request(request).build();
            return chain.filter(webExchange);
        };
    }
}

配置文件:

filters:
  - AddHeader=new-color, red
8、负载均衡全局过滤器

添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

注:此服务要注册到注册中心,才能根据服务名进行负载均衡。

配置文件

spring:
  application:
    name: gateway_8085
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      # 使用gateway调用服务,服务不正常时,将状态码从503改成404
      loadbalancer:
        use404: true
        # 开启向注册中心注册功能
      discovery:
        locator:
          enabled: true
      routes:
        - id: my_route
          uri:lb://provider-service  # 负载均衡配置
          predicates:
            - Path=/1114zwl
9、自定义全局过滤器

Global Filter 不需要在任何具体的路由规则中进行注册,只需在类上添加@Compoment 注解,将其生命周期交给 Spring 容器来管理即可。

示例:判断请求中是否携带了 token 请求参数。

@Component
public class URLValidateFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (!StringUtils.hasText(token)){
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
10、跨域问题

为了安全,浏览器中启用了一种称为同源策略的安全机制,禁止从一个域名页面中请求另一个域名下的资源。当两个请求的访问协议、域名与端口号三者都相同时,才称它们是同源的。只要有一个不同,就称为跨域请求。

配置方式解决跨域问题:

spring:
  application:
    name: gateway_8085
  cloud:
      globalcors:  # 全局跨域设置
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
      routes:
        - id: my_route
          uri: https://www.cnblogs.com/
          predicates:
            - Path=/1114zwl
            - After=2023-01-21T17:42:47.789-07:00
            - Before=2023-12-21T17:42:47.789-07:00
            - Between=2023-01-21T17:42:47.789-07:00,2023-12-21T17:42:47.789-07:00
            - Auth=weilong,123456
          metadata:  # 针对单个路由局部跨域配置 
            cors:
              allowedOrigins: "*"
              allowedMethods:
                - GET
                - POST

总结:本文详细介绍Gateway的使用,路由、断言、过滤器使用都进行了详细介绍,帮助初学者快速入门Gateway的使用。

        本人是一个从小白自学计算机技术,对运维、后端、各种中间件技术、大数据等有一定的学习心得,想获取自学总结资料(pdf版本)或者希望共同学习,关注微信公众号:上了年纪的小男孩。后台回复相应技术名称/技术点即可获得。(本人学习宗旨:学会了就要免费分享)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

掌握静态S5:从入门到精通的指南

在现今的数据驱动时代&#xff0c;静态S5作为一款强大的数据分析工具&#xff0c;越来越受到各行各业的青睐。然而&#xff0c;如何从入门到精通&#xff0c;全面掌握静态S5的各项功能&#xff0c;成为了许多用户面临的挑战。本文将为你提供一份详尽的指南&#xff0c;助你顺利…

Lingo 17安装包下载及安装教程

Lingo 17下载链接&#xff1a;https://docs.qq.com/doc/DUndEVXd4WVVweGFR 1.鼠标右键解压到“Lingo 17.0” 2.双击打开【Setup】文件夹 3.选中Lingo 17.0&#xff0c;鼠标右键选择“以管理员身份运行” 4.点击“Next” 5.选中I accept the terms in the license agreement&…

【C程序设计】C循环

有的时候&#xff0c;我们可能需要多次执行同一块代码。一般情况下&#xff0c;语句是按顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多次执行一个…

Python 热力图的绘制(Matplotlib篇-12)

Python 热力图的绘制(Matplotlib篇-12)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

陆面过程模式CLM、地球系统模式CESM安装及快速运行

目录 专题一 CESM、CLM运行条件及Linux编译基础 专题二 CESM、CLM基础 专题三 CLM程序获取、结构及其功能 专题四 CLM移植、安装及快速运行 专题五 CLM配置选项及数据文件制备 专题六 CLM单点或区域运行 专题七 CLM结果处理、分析及可视化 专题八 CLM代码修改、发展及改…

二、UI文件设计与运行机制

一、UI文件设计与运行机制 1、创建工程 2、添加控件&#xff0c;实现按钮点击 &#xff08;1&#xff09;添加控件 &#xff08;2&#xff09;添加信号和槽 2、分析项目结构 test_02test_02.pro Qt工程文件Headerswidget.h 设计的窗体类的头文件Sourcesmain.cpp 主程序入…

diffusers 源码待理解之处

一、训练DreamBooth时&#xff0c;相关代码的细节小计 ** class_labels timesteps 时&#xff0c;模型的前向传播怎么走&#xff1f;待深入去看 ** 利用class_prompt去生成数据&#xff0c;而不是instance_prompt class DreamBoothDataset(Dataset):"""A dat…

H5C3练习心得 2024.01.03(文字加载动画效果)--transition,动画渲染,遮罩层

&#xff08;一&#xff09;transition&#xff08;过渡效果&#xff09; 1.详解 通常将css的属性值更改后&#xff0c;浏览器会立即更新新的样式&#xff0c;例如在鼠标悬停在元素上时&#xff0c;通过 :hover 选择器定义的样式会立即应用在元素上。 在 CSS3 中加入了一项过…

Java虚拟机介绍

JVM是一种用于计算设备的规范&#xff0c;它是一个虚拟出来的计算机&#xff0c;是通过在实际的计算机上仿真模拟计算机的各个功能来实现的。Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。每个Java虚拟机都着一个清晰的任务&#x…

5分钟理解什么是多模态

大家好&#xff0c;我是董董灿。 大模型越来越多了&#xff0c;大模型下沉的行业也越来越多。前几周一个在电厂工作的老哥发消息问我&#xff1a;大模型中所谓的多模态是什么意思&#xff1f; 我当时大概跟他解释了一下。 其实在人工智能领域&#xff0c;我们经常会听到&quo…

leetcode递归算法题总结

递归本质是找重复的子问题 本章目录 1.汉诺塔2.合并两个有序链表3.反转链表4.两两交换链表中的节点5.Pow(x,n) 1.汉诺塔 汉诺塔 //面试写法 class Solution { public:void hanota(vector<int>& a, vector<int>& b, vector<int>& c) {dfs(a,b…

[DevOps-02] Code编码阶段工具

一、简要说明 在code阶段,我们需要将不同版本的代码存储到一个仓库中,常见的版本控制工具就是SVN或者Git,这里我们采用Git作为版本控制工具,GitLab作为远程仓库。 Git安装安装GitLab配置GitLab登录账户二、Git安装 Git官网 Githttps://git-scm.com/

HarmonyOS-ArkTS基本语法及声明式UI描述

初识ArkTS语言 ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript&#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集。因此&#xff0c;在学习ArkTS语言之前&#xff0c;建议开发者具备TS语…

docker小白第十一天

docker小白第十一天 dockerfile分析 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。即构建新镜像时会用到。 构建三步骤&#xff1a;编写dockerfile文件-docker build命令构建镜像-docker run镜像 运行容器实例。即一…

Win32 基本程序设计原理总结

目录 1. Windows系统 基本原理 2. 需要什么函数库&#xff08;.LIB&#xff09; 2.1 C Runtimes&#xff1a; 2.2 Windows API 3. 需要什么头文件&#xff08;.H&#xff09; 4. Windows 程序运行的本质 5. 窗口类的注册与窗口的诞生 6.消息 6.1 消息分类&#xff1a;…

Vue3 结合typescript 组合式函数(2)

安装axios&#xff1a;npm install axios 1、hooks文件夹下新建useURLLoader 在APP.VUE中使用useURLLoader 使用Dog API 2、使用对象中的属性&#xff0c;必须使用toRefs&#xff0c;否则Reactive响应失效 3、使用泛型 结果&#xff1a;

VS2022 Android NativeActivity 开发指南

几年前最初使用VS时&#xff0c;记得是有Android NativeActivity的&#xff0c;今天更新到了2022最新版&#xff0c;发现找不到这个创建选项。 然后确保安装了C 跨平台开发工具后&#xff0c;开始排查原因。 Visual Studio 2022 中没有“本机活动应用程序” - android - SO中…

vue中$nextTick作用和实例

为什么要使用nextTick&#xff1f; vue中DOM更新是异步执行&#xff0c;相当于我们在修改数据的时候&#xff0c;视图是不会立即更新的&#xff0c;会先把新的数据攒一赞&#xff0c;例如假如v-for更新这三个数据item1和item2和item3&#xff0c;按照vue的特性dom更新的特性会…

SpingBoot的项目实战--模拟电商【4.订单及订单详情的生成】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringBoot电商项目的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.功能需求 二.代码编写 …

CSS与JavaScript的简单认识

CSS&#xff1a;是一门语言&#xff0c;用于控制网页表现&#xff0c;让页面更好看的。 CSS&#xff08;Cascading Style Sheet&#xff09;&#xff1a;层叠样式表 CSS与html结合的三种方式&#xff1a; 1、内部样式&#xff1a;用style标签&#xff0c;在标签内部定义CSS样式…