1. 微服务网关整合 OAuth2.0 设计思路分析

网关整合 OAuth2.0  有两种思路,一种是授权服务器生成令牌,  所有请求统一 在网关层验证,判断权限等操作;另一种是由各资源服务处理,网关只做请求 转发   比较常用的是第一种,把 API 网关作为 OAuth2.0 的资源服务器角 色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微服务, 这样下游微服务就不需要关心令牌格式解析以及 OAuth2.0 相关机制了。

网关在认证授权体系里主要负责两件事

 1)作为 OAuth2.0 的资源服务器 角色,实现接入方访问权限拦截。

 (2)令牌解析并转发当前登录用户信息 (明文 token)给微服务

微服务拿到明文 token(明文 token 中包含登录用户的 身份和权限信息)后也需要做两件事

 1)用户授权拦截(看当前用户是否有 权访问该资源 

  (2)将用户信息存储进当前线程上下文(有利于后续业务逻 辑随时获取当前用户信息)

 

2. 搭建微服务授权中心

授权中心的认证依赖:

  •         第三方客户端的信息
  •         微服务的信息
  •         登录用户的信息

创建微服务 tulingmall-authcenter

2.1 引入依赖 

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId>
</dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency>

2.2 添加 yml 配置 

server:port: 9999
spring:application:name: tulingmall-authcenter#配置nacos注册中心地址cloud:nacos:discovery:server-addr: 192.168.65.103:8848  #注册中心地址namespace: 6cd8d896-4d19-4e33-9840-26e4bee9a618  #环境隔离datasource:url: jdbc:mysql://tuling.com:3306/tlmall_oauth?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8username: rootpassword: rootdruid:initial-size: 5 #连接池初始化大小min-idle: 10 #最小空闲连接数max-active: 20 #最大连接数web-stat-filter:exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" #不统计这些请求数据stat-view-servlet: #访问监控网页的登录用户名和密码login-username: druidlogin-password: druid

2.3 配置授权服务器

基于 DB 模式配置授权服务器存储第三方客户端的信息

@Configuration
@EnableAuthorizationServer
public class TulingAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {// 配置授权服务器存储第三方客户端的信息  基于DB存储   oauth_client_detailsclients.withClientDetails(clientDetails());}@Beanpublic ClientDetailsService clientDetails(){return new JdbcClientDetailsService(dataSource);}}

  oauth_client_details 中添加第三方客户端信息(client_id    client_secret     scope 等等)

CREATE TABLE `oauth_client_details`  (`client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`access_token_validity` int(11) NULL DEFAULT NULL,`refresh_token_validity` int(11) NULL DEFAULT NULL,`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

 

基于内存模式配置授权服务器存储第三方客户端的信息 

//TulingAuthorizationServerConfig.java
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {//  配置授权服务器存储第三方客户端的信息  基于DB存储   oauth_client_details// clients.withClientDetails(clientDetails());/***授权码模式*http://localhost:9999/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all** password模式*  http://localhost:8080/oauth/token?username=fox&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all**/clients.inMemory()//配置client_id.withClient("client")//配置client-secret.secret(passwordEncoder.encode("123123"))//配置访问token的有效期.accessTokenValiditySeconds(3600)//配置刷新token的有效期.refreshTokenValiditySeconds(864000)//配置redirect_uri,用于授权成功后跳转.redirectUris("http://www.baidu.com")//配置申请的权限范围.scopes("all")/*** 配置grant_type,表示授权类型* authorization_code: 授权码* password: 密码* refresh_token: 更新令牌*/.authorizedGrantTypes("authorization_code","password","refresh_token");}

2.4  配置 SpringSecurity

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Autowiredprivate TulingUserDetailsService tulingUserDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 实现UserDetailsService获取用户信息auth.userDetailsService(tulingUserDetailsService);}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {// oauth2 密码模式需要拿到这个beanreturn super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().permitAll().and().authorizeRequests().antMatchers("/oauth/**").permitAll().anyRequest().authenticated().and().logout().permitAll().and().csrf().disable();        }
}

 获取会员信息 ,此处通过 feign 从 tulingmall-member 获取会员信息 ,需要配置 feign ,核心代码:

@Slf4j
@Component
public class TulingUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 加载用户信息if(StringUtils.isEmpty(username)) {log.warn("用户登陆用户名为空:{}",username);throw new UsernameNotFoundException("用户名不能为空");}UmsMember umsMember = getByUsername(username);if(null == umsMember) {log.warn("根据用户名没有查询到对应的用户信息:{}",username);}log.info("根据用户名:{}获取用户登陆信息:{}",username,umsMember);// 会员信息的封装 implements UserDetailsMemberDetails memberDetails = new MemberDetails(umsMember);return memberDetails;}@Autowiredprivate UmsMemberFeignService umsMemberFeignService;public UmsMember getByUsername(String username) {// fegin获取会员信息CommonResult<UmsMember> umsMemberCommonResult = umsMemberFeignService.loadUserByUsername(username);return umsMemberCommonResult.getData();}
}@FeignClient(value = "tulingmall-member",path="/member/center")
public interface UmsMemberFeignService {@RequestMapping("/loadUmsMember")CommonResult<UmsMember> loadUserByUsername(@RequestParam("username") String username);
}public class MemberDetails implements UserDetails {private UmsMember umsMember;public MemberDetails(UmsMember umsMember) {this.umsMember = umsMember;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {//返回当前用户的权限return Arrays.asList(new SimpleGrantedAuthority("TEST"));}@Overridepublic String getPassword() {return umsMember.getPassword();}@Overridepublic String getUsername() {return umsMember.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return umsMember.getStatus()==1;}public UmsMember getUmsMember() {return umsMember;}
}

修改授权服务配置,支持密码模式

//TulingAuthorizationServerConfig.java @Autowiredprivate TulingUserDetailsService tulingUserDetailsService;@Autowiredprivate AuthenticationManager authenticationManagerBean;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//使用密码模式需要配置endpoints.authenticationManager(authenticationManagerBean).reuseRefreshTokens(false)  //refresh_token是否重复使用.userDetailsService(tulingUserDetailsService) //刷新令牌授权包含对用户信息的检查.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求}/*** 授权服务器安全配置* @param security* @throws Exception*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {//第三方客户端校验token需要带入 clientId 和clientSecret来校验security.checkTokenAccess("isAuthenticated()").tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecret//允许表单认证security.allowFormAuthenticationForClients();}

 2.5 测试模拟用户登录

授权码模式

授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然 后再用该码获取令牌。

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web   用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通 信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

适用场景: 目前市面上主流的第三方验证都是采用这种模式

它的步骤如下:

A)用户访问客户端,后者将前者导向授权服务器。

B)用户选择是否给予客户端授权。

(C)假设用户给予授权,授权服务器将用户导向客户端事先指定的"重定向URI"( redirection URI),同时附上一个授权码。

D)客户端收到授权码,附上早先的"重定向 URI" ,向授权服务器申请令 牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。

E)授权服务器核对了授权码和重定向 URI ,确认无误后,向客户端发送 访问令牌(access token)和更新令牌( refresh token)。

http://localhost:9999/oauth/authorize?response_type=code&client_id=client &redirect_uri=http://www.baidu.com&scope=all

获取到 code

 

密码模式

如果你高度信任某个应用,RFC 6749  也允许用户把用户名和密码,直接告诉该      用就 使 用你                "    " password)。

在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。 这通常用在用户对客户端高度信任的情况下, 比如客户端是操作系统的一部 分,或者由一个著名公司出品。而授权服务器只有在其他授权模式无法执行的 情况下,才能考虑使用这种模式。

适用场景: 自家公司搭建的授权服务器

测试获取 token

http://localhost:9999/oauth/token?username=test&password=test&grant_type=password&client_id=clien t&client_secret= 123123&scope=all

测试校验 token 接口 

 因为授权服务器的 security 配置需要携带 clientId  clientSecret ,可以采用 basic Auth  的方 式发请求

 注意: 传参是 token

2.6 配置资源服务器

@Configuration
@EnableResourceServer
public class TulingResourceServerConfig  extends ResourceServerConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated();}
}@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getCurrentUser")public Object getCurrentUser(Authentication authentication) {return authentication.getPrincipal();}
}

测试携带token 访问资源

或者请求头配置 Authorization

OAuth 2.0 是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管 理流程;而 JWT 是一种轻量级、 自包含的令牌,可用于在微服务间安全地传递用户信息。

2.7 Spring Security Oauth2 整合 JWT

JSON Web TokenJWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、 自包含的协议格式,用于在通信双方传递 json 对象,传递的信息经过数 字签名可以被验证和信任。JWT 可以使用 HMAC 算法或使用 RSA 公钥/私钥对 来签名,防止被篡改。 官网:JSON Web Tokens - jwt.io

JWT 令牌的优点:

  •         jwt 基于 json ,非常方便解析。
    •         可以在令牌中自定义丰富的内容,易扩展。
      •         通过非对称加密算法及数字签名技术,JWT 防止篡改,安全性高。
        •         资源服务使用JWT 可不依赖认证服务即可完成授权。

缺点:

        JWT 令牌较长, 占存储空间比较大。

JWT:指的是 JSON Web Token   header.payload.signture   组成。不存在签名的 JWT  不安全的,存在签名的 JWT 是不可窜改的。

JWS:指的是签过名的 JWT ,即拥有签名的 JWT

JWK:既然涉及到签名,就涉及到签名算法,对称加密还是非对称加密,那么就需要加密 的 密钥或者公私钥对。此处我们将 JWT  的密钥或者公私钥对统一称为 JSON WEB KEY ,即 JWK

JWT 组成

一个 JWT 实际上就是一个字符串,它由三部分组成,头部(header 、载荷 payload)与签名(signature)。

头部(header

头部用于描述关于该 JWT 的最基本的信息:类型(即 JWT)以及签名所用的 算法(如 HMACSHA256  RSA)等。

这也可以被表示成一个 JSON 对象:

{"alg": "HS256","typ": "JWT"
}

 然后将头部进行 base64 加密(该加密是可以对称解密的),构成了第一部分:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 

载荷(payload

第二部分是载荷,就是存放有效信息的地方。这个名字像是特指飞机上承载的 货品,这些有效信息包含三个部分:

  • 标准中注册的声明(建议但不强制使用)

        iss: jwt 签发者

        sub: jwt 所面向的用户

        aud: 接收 jwt 的一方

        exp: jwt 的过期时间,这个过期时间必须要大于签发时间

        nbf:  定义在什么时间之前,该 jwt 都是不可用的.

        iat: jwt 的签发时间

        jti: jwt 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击。

  • 公共的声明

        公共的声明可以添加任何的信息,一般添加用户的相关信息或 其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

  • 私有的声明

        私有声明是提供者和消费者所共同定义的声明,一般不建议存 放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文 信息。

定义一个 payload

{"sub": "1234567890","name": "John Doe","iat": 1516239022
}

 然后将其进行 base64 加密,得到 Jwt 的第二部分:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ​​​​​​​

签名(signature

jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64 后的)
  • payload (base64 后的)
  • secret(盐,一定要保密)

这个部分需要 base64 加密后的 header  base64 加密后的 payload 使用.连接 组成的字符串,然后通过 header 中声明的加密方式进行加盐 secret 组合加 密,然后就构成了 jwt 的第三部分:

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'fox'); // khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME

 将这三部分用.连接成一个完整的字符串,构成了最终的 jwt:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME

注意:secret 是保存在服务器端的,jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和 jwt 的验证,所以,它就是你服务端的私钥,在任何 场景都不应该流露出去。一旦客户端得知这个 secret,  那就意味着客户端是可以 自我签发 jwt 了。

JWT 应用场景​​​​​​​

  • 一次性验证

        比如用户注册后需要发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需 要具备以下的特性:能够标识用户,该链接具有时效性〈(通常只允许几小时之内激活) ,不 能被篡改以激活其他可能的账户这种场景就和 jwt 的特性非常贴近,jwt payload   中固定 的参数: iss 签发者和 exp 过期时间正是为其做准备的。

  • restful api 的无状态认证

        使用 jwt 来做 restful api 的身份认证也是值得推崇的一种使用方案。客户端和服务端共享 secret;过期时间由服务端校验,客户端定时刷新;签名信息不可被修改。

  • 使用 jwt 做单点登录+会话管理(不推荐)           token+redis

        jwt 是无状态的,在处理注销,续约问题上会变得非常复杂

引入依赖

<!--spring secuity对jwt的支持 spring cloud oauth2已经依赖,可以不配置-->
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.0.9.RELEASE</version>
</dependency>

 添加 JWT 配置

@Configuration
public class JwtTokenStoreConfig {@Beanpublic TokenStore jwtTokenStore(){return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(){JwtAccessTokenConverter accessTokenConverter = newJwtAccessTokenConverter();//配置JWT使用的秘钥accessTokenConverter.setSigningKey("123123");return accessTokenConverter;}
}

 在授权服务器配置中指定令牌的存储策略为 JWT

//TulingAuthorizationServerConfig.java@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;@Autowired
private TulingUserDetailsService tulingUserDetailsService;@Autowired
private AuthenticationManager authenticationManagerBean;@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//使用密码模式需要配置endpoints.authenticationManager(authenticationManagerBean).tokenStore(tokenStore)  //指定token存储策略是jwt.accessTokenConverter(jwtAccessTokenConverter).reuseRefreshTokens(false)  //refresh_token是否重复使用.userDetailsService(tulingUserDetailsService) //刷新令牌授权包含对用户信息的检查.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
}

密码模式测试:

http://localhost:9999/oauth/token?username=test&password=test&grant_type=password&client_id=clien t&client_secret=123123&scope=all

 access_token 复制到 JSON Web Tokens - jwt.io Encoded 中打开,可以看到会员认证信息

 测试校验 token

测试获取 token_key 

 

测试刷新 token

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

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

相关文章

学习Markdown

标题一 标题二 标题三 标题四 标题五 标题六这是一段引用文本直接编写&#xff0c;段落换行是末尾两个以上的空格&#xff0b;回车 或者在段落后加一个空行 粗体语法&#xff1a;使用两个星号 ** 或两个下划线 __ 包围文字&#xff1a;这是粗体文字使用星号 这是__粗体文字__使…

剧本杀系统 App 开发:科技赋能,重塑剧本杀游戏体验

在科技飞速发展的当下&#xff0c;各个行业都在积极寻求与科技的融合&#xff0c;以实现创新和升级。剧本杀行业也不例外&#xff0c;剧本杀系统 App 的开发正是科技赋能的生动体现&#xff0c;它重塑了传统的剧本杀游戏体验&#xff0c;为玩家带来了全新的感受。剧本杀系统 Ap…

wvp-gb28181-pro 只用jar运行

编译前端后npm install --global yarnyarn --registryhttps://registry.npmjs.org installyarn run build&#xff0c;生成的前端文件&#xff0c;会在wvp-GB28181-pro\src\main\resources\static&#xff0c;因为是在resources中&#xff0c;打maven打包后会一起打到jar中&…

深度学习(鱼书)day06--神经网络的学习(后两节)

深度学习&#xff08;鱼书&#xff09;day06–神经网络的学习&#xff08;后两节&#xff09;一、梯度 像 这样的由全部变量的偏导数汇总而成的向量称为梯度&#xff08;gradient&#xff09;。 梯度实现的代码&#xff1a; def numerical_gradient(f, x):h 1e-4 # 0.0001grad…

学习嵌入式的第三十四天-数据结构-(2025.7.29)数据库

数据库基础概念 数据库是用于存储和管理海量数据的应用程序&#xff0c;提供数据增删改查及统计功能&#xff08;如最大值、最小值、平均数等&#xff09;。通过SQL语句操作数据&#xff0c;以表格形式管理存储。 数据库分类 关系型数据库 Oracle&#xff08;大型&#xff0…

STM32——HAL库

总&#xff1a;STM32——学习总纲 一、简介 1.1 CMIS简介 所有厂家为了市场兼容性推出的标准 arm架构 1.2 HAL库简介 1.2.1 各种库优缺点 二、 STM32 Cube固件包 ST公司为CMSIS 中间层开发的pack&#xff0c;包含HAL。 2.1 获取方式 ST官网&#xff1a;st.com/content/st_c…

数据结构-图的相关定义

图-多对多Graph&#xff08;V,E&#xff09;&#xff0c;图&#xff08;顶点Vertex&#xff0c;边Edge&#xff09;图可以没有边&#xff0c;只有一个顶点也叫图&#xff0c;但是单独的一条边&#xff0c;或者一个顶点连一条边&#xff0c;不能叫图有向图&#xff1a;无向图&am…

B 站搜一搜关键词优化:精准触达用户的流量密码

在 B 站内容生态中&#xff0c;搜一搜功能是用户主动获取信息的重要渠道&#xff0c;而关键词优化则是让你的视频在搜索结果中脱颖而出的关键。通过合理优化关键词&#xff0c;能提升视频曝光率&#xff0c;吸引精准流量&#xff0c;为账号发展注入强劲动力。以下从关键词挖掘、…

Python爬虫实战:研究purl库相关技术

1. 引言 随着互联网数据量的爆炸式增长,网络爬虫已成为数据采集、舆情分析和学术研究的重要工具。Python 凭借其丰富的库生态和简洁语法,成为开发爬虫的首选语言。本文提出的爬虫系统结合 requests 进行 HTTP 请求、BeautifulSoup 解析 HTML,并创新性地引入 purl 库处理复杂…

OpenCV 学习探秘之三:从图像读取到特征识别,再到机器学习等函数接口的全面实战应用与解析

一、引言 1.1介绍 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个功能强大的开源计算机视觉库&#xff0c;广泛应用于图像和视频处理、目标检测、机器学习等领域。本文将全面解析 OpenCV 中常用的函数接口&#xff0c;帮助读者快速掌握 OpenCV 的…

Umi从零搭建Ant Design Pro项目(3)集成 openapi 插件

1. 安装插件 pnpm add umijs/max-plugin-openapi pnpm add swagger-ui-dist如果不安装swagger-ui-dist&#xff0c;不会影响运行。但会报错。 2.配置文件export default defineConfig({// umi插件配置plugins: [umijs/max-plugin-openapi],// openAPI配置openAPI: {requestLibP…

Flutter开发实战之状态管理深入解析

第4章:状态管理深入解析 前言 想象一下,你正在开发一个购物车应用。用户在商品页面添加商品,然后去购物车页面查看,最后到结算页面付款。在这个过程中,购物车的数据需要在多个页面之间保持同步和一致。这就是状态管理要解决的核心问题。 状态管理是Flutter开发中最重要…

组件化(一):重新思考“组件”:状态、视图和逻辑的“最佳”分离实践

组件化(一)&#xff1a;重新思考“组件”&#xff1a;状态、视图和逻辑的“最佳”分离实践 引子&#xff1a;组件的“内忧”与“外患” 至此&#xff0c;我们的前端内功修炼之旅已经硕果累累。我们掌握了组件化的架构思想&#xff0c;拥有了高效的渲染引擎&#xff0c;还探索…

【Redis】Redis 协议与连接

一、Redis 协议 1.1 RESP RESP 是 Redis 客户端与服务器之间的通信协议&#xff0c;采用文本格式&#xff08;基于 ASCII 字符&#xff09;&#xff0c;支持多种数据类型的序列化和反序列化 RESP 通过首字符区分数据类型&#xff0c;主要支持 5 种类型&#xff1a; 类型首字…

Android通知(Notification)全面解析:从基础到高级应用

一、Android通知概述通知(Notification)是Android系统中用于在应用之外向用户传递信息的重要机制。当应用需要告知用户某些事件或信息时&#xff0c;可以通过通知在状态栏显示图标&#xff0c;用户下拉通知栏即可查看详细信息。这种机制几乎被所有现代应用采用&#xff0c;用于…

VUE3(四)、组件通信

1、props作用&#xff1a;子组件之间的通信。父传子&#xff1a;属性值的非函数。子传父&#xff1a;属性值是函数。父组件&#xff1a;<template><div>{{ childeData }}</div>——————————————————————————————<child :pare…

【数据结构与算法】数据结构初阶:详解二叉树(六)——二叉树应用:二叉树选择题

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题 &#x1f349;学习方向&#xff1a;C/C方向 ⭐️人生格言&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为…

Android广播实验

【实验目的】了解使用Intent进行组件通信的原理&#xff1b;了解Intent过滤器的原理和匹配机制&#xff1b;掌握发送和接收广播的方法【实验内容】任务1、普通广播&#xff1b;任务2、系统广播&#xff1b;任务3、有序广播&#xff1b;【实验要求】1、练习使用静态方法和动态方…

html转word下载

一、插件使用//转html为wordnpm i html-docx-js //保存文件到本地npm i file-saver 注&#xff1a;vite 项目使用esm模式会报错&#xff0c;with方法错误&#xff0c;修改如下&#xff1a;//直接安装修复版本npm i html-docx-fixed二、封装导出 exportWord.jsimport htmlDocx f…

北方公司面试记录

避免被开盒&#xff0c;先称之为“北方公司”&#xff0c;有确定结果后再更名。 先说流程&#xff0c;线下面试&#xff0c;时间非常急&#xff0c;下午两点钟面试&#xff0c;中午十二点打电话让我去&#xff0c;带两份纸质简历。 和一般的菌工单位一样&#xff0c;先在传达室…