微服务--04--SpringCloudGateway 网关
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 1.网关路由
- 1.1 认识网关
- 在SpringCloud当中,提供了两种网关实现方案:
- 1.2.快速入门
- 1.3.路由过滤
- 2.网关登录校验
- 2.1.鉴权思路分析
- 2.2.网关过滤器
- 网关过滤器链中的过滤器有两种:
- 2.3.自定义过滤器
- 2.3.1.自定义GatewayFilter
- 2.3.2.自定义GlobalFilter
- 2.4.登录校验
- 2.4.1.JWT工具
- 2.4.2.登录校验过滤器
- 2.5.微服务获取用户
- 2.5.1.保存用户到请求头
- 2.5.2.拦截器获取用户
- 2.6.OpenFeign传递用户
1.网关路由
1.1 认识网关
什么是网关?
顾明思议,网关就是网络的关口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。
更通俗的来讲,网关就像是以前园区传达室的大爷。
- 外面的人要想进入园区,必须经过大爷的认可,如果你是不怀好意的人,肯定被直接拦截。
- 外面的人要传话或送信,要找大爷。大爷帮你带给目标人。

现在,微服务网关就起到同样的作用。前端请求不能直接访问微服务,而是要请求网关:
- 网关可以做安全控制,也就是登录身份校验,校验通过才放行
- 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去

在SpringCloud当中,提供了两种网关实现方案:
- Netflix Zuul:早期实现,目前已经淘汰
- SpringCloudGateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强

官网
https://spring.io/projects/spring-cloud-gateway#learn
1.2.快速入门
接下来,我们先看下如何利用网关实现请求路由。由于网关本身也是一个独立的微服务,因此也需要创建一个模块开发功能。大概步骤如下:
- 创建网关微服务
- 引入SpringCloudGateway、NacosDiscovery依赖
- 编写启动类
- 配置网关路由
1.2.1.创建项目
首先,我们要在hmall下创建一个新的module,命名为hm-gateway,作为网关微服务:

1.2.2.引入依赖
在hm-gateway模块的pom.xml文件中引入依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>hmall</artifactId><groupId>com.heima</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>hm-gateway</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><dependencies><!--common--><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--nacos discovery--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--负载均衡--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
1.2.3.启动类
在hm-gateway模块的com.hmall.gateway包下新建一个启动类:

代码如下:
package com.hmall.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
1.2.4.配置路由
接下来,在hm-gateway模块的resources目录新建一个application.yaml文件,内容如下
server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.150.101:8848gateway:routes:- id: item # 路由规则id,自定义,唯一uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务- Path=/items/**,/search/** # 这里是以请求路径作为判断规则- id: carturi: lb://cart-servicepredicates:- Path=/carts/**- id: useruri: lb://user-servicepredicates:- Path=/users/**,/addresses/**- id: tradeuri: lb://trade-servicepredicates:- Path=/orders/**- id: payuri: lb://pay-servicepredicates:- Path=/pay-orders/**
1.2.5.测试
启动GatewayApplication,以 http://localhost:8080 拼接微服务接口路径来测试。例如:
http://localhost:8080/items/page?pageNo=1&pageSize=1

此时,启动UserApplication、CartApplication,然后打开前端页面,发现相关功能都可以正常访问了:
1.3.路由过滤
路由规则的定义语法如下:
spring:cloud:gateway:routes:- id: itemuri: lb://item-servicepredicates:- Path=/items/**,/search/**
四个属性含义如下:
- id:路由的唯一标示
- predicates:路由断言,其实就是匹配条件
- filters:路由过滤条件,后面讲
- uri:路由目标地址,lb://代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
这里我们重点关注predicates,也就是路由断言。SpringCloudGateway中支持的断言类型有很多:

2.网关登录校验
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然不可取。
2.1.鉴权思路分析
我们的登录是基于JWT来实现的,校验JWT的算法复杂,而且需要用到秘钥。如果每个微服务都去做登录校验,这就存在着两大问题:
- 每个微服务都需要知道JWT的秘钥,不安全
- 每个微服务重复编写登录校验代码、权限校验代码,麻烦
既然网关是所有微服务的入口,一切请求都需要先经过网关。我们完全可以把登录校验的工作放到网关去做,这样之前说的问题就解决了:
- 只需要在网关和用户服务保存秘钥
- 只需要在网关开发登录校验功能
此时,登录校验的流程如图:

不过,这里存在几个问题:
- 网关路由是配置的,请求转发是Gateway内部代码,我们如何在转发之前做登录校验?
- 网关校验JWT之后,如何将用户信息传递给微服务?
- 微服务之间也会相互调用,这种调用不经过网关,又该如何传递用户信息?
2.2.网关过滤器
登录校验必须在请求转发到微服务之前做,否则就失去了意义。而网关的请求转发是Gateway内部代码实现的,要想在请求转发之前做登录校验,就必须了解Gateway内部工作的基本原理。

如图所示:
- 客户端请求进入网关后由HandlerMapping对请求做判断,找到与当前请求匹配的路由规则(Route),然后将请求交给WebHandler去处理。
- WebHandler则会加载当前路由下需要执行的过滤器链(Filter chain),然后按照顺序逐一执行过滤器(后面称为Filter)。
- 图中Filter被虚线分为左右两部分,是因为Filter内部的逻辑分为pre和post两部分,分别会在请求路由到微服务之前和之后被执行。
- 只有所有Filter的pre逻辑都依次顺序执行通过后,请求才会被路由到微服务。
- 微服务返回结果后,再倒序执行Filter的post逻辑。
- 最终把响应结果返回。
那么,该如何实现一个网关过滤器呢?
网关过滤器链中的过滤器有两种:
- GatewayFilter:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route.
- GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。

注意:过滤器链之外还有一种过滤器,HttpHeadersFilter,用来处理传递到下游微服务的请求头。例如org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter可以传递代理请求原本的host头到下游微服务。

Gateway中内置了很多的GatewayFilter,详情可以参考官方文档:https://docs.spring.io/spring-cloud-gateway/docs/3.1.7/reference/html/#gatewayfilter-factories
Gateway内置的GatewayFilter过滤器使用起来非常简单,无需编码,只要在yaml文件中简单配置即可。而且其作用范围也很灵活,配置在哪个Route下,就作用于哪个Route.
例如,有一个过滤器叫做AddRequestHeaderGatewayFilterFacotry,顾明思议,就是添加请求头的过滤器,可以给请求添加一个请求头并传递到下游微服务。
使用的使用只需要在application.yaml中这样配置:
spring:cloud:gateway:routes:- id: test_routeuri: lb://test-servicepredicates:-Path=/test/**filters:- AddRequestHeader=key, value # 逗号之前是请求头的key,逗号之后是value
如果想要让过滤器作用于所有的路由,则可以这样配置:
spring:cloud:gateway:default-filters: # default-filters下的过滤器可以作用于所有路由- AddRequestHeader=key, valueroutes:- id: test_routeuri: lb://test-servicepredicates:-Path=/test/**
2.3.自定义过滤器
无论是GatewayFilter还是GlobalFilter都支持自定义,只不过编码方式、使用方式略有差别。
2.3.1.自定义GatewayFilter
不是直接实现GatewayFilter,而是实现AbstractGatewayFilterFactory。最简单的方式是这样的:
@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Overridepublic GatewayFilter apply(Object config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取请求ServerHttpRequest request = exchange.getRequest();// 编写过滤器逻辑System.out.println("过滤器执行了");// 放行return chain.filter(exchange);}};}
}
注意:该类的名称一定要以GatewayFilterFactory为后缀!
然后在yaml配置中这样使用:
spring:cloud:gateway:default-filters:- PrintAny # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器
另外,这种过滤器还可以支持动态配置参数,不过实现起来比较复杂,示例:
@Component
public class PrintAnyGatewayFilterFactory // 父类泛型是内部类的Config类型extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {@Overridepublic GatewayFilter apply(Config config) {// OrderedGatewayFilter是GatewayFilter的子类,包含两个参数:// - GatewayFilter:过滤器// - int order值:值越小,过滤器执行优先级越高return new OrderedGatewayFilter(new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取config值String a = config.getA();String b = config.getB();String c = config.getC();// 编写过滤器逻辑System.out.println("a = " + a);System.out.println("b = " + b);System.out.println("c = " + c);// 放行return chain.filter(exchange);}}, 100);}// 自定义配置属性,成员变量名称很重要,下面会用到@Datastatic class Config{private String a;private String b;private String c;}// 将变量名称依次返回,顺序很重要,将来读取参数时需要按顺序获取@Overridepublic List<String> shortcutFieldOrder() {return List.of("a", "b", "c");}// 返回当前配置类的类型,也就是内部的Config@Overridepublic Class<Config> getConfigClass() {return Config.class;}}
然后在yaml文件中使用:
spring:cloud:gateway:default-filters:- PrintAny=1,2,3 # 注意,这里多个参数以","隔开,将来会按照shortcutFieldOrder()方法返回的参数顺序依次复制
上面这种配置方式参数必须严格按照shortcutFieldOrder()方法的返回参数名顺序来赋值。
还有一种用法,无需按照这个顺序,就是手动指定参数名:
spring:cloud:gateway:default-filters:- name: PrintAnyargs: # 手动指定参数名,无需按照参数顺序a: 1b: 2c: 3
2.3.2.自定义GlobalFilter
自定义GlobalFilter则简单很多,直接实现GlobalFilter即可,而且也无法设置动态参数:
@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 编写过滤器逻辑System.out.println("未登录,无法访问");// 放行// return chain.filter(exchange);// 拦截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete();}@Overridepublic int getOrder() {// 过滤器执行顺序,值越小,优先级越高return 0;}
}
2.4.登录校验
接下来,我们就利用自定义GlobalFilter来完成登录校验。
2.4.1.JWT工具
登录校验需要用到JWT,而且JWT的加密需要秘钥和加密工具。这些在hm-service中已经有了,我们直接拷贝过来:

具体作用如下:
- AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问
- JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置
- SecurityConfig:工具的自动装配
- JwtTool:JWT工具,其中包含了校验和解析token的功能
- hmall.jks:秘钥文件
其中AuthProperties和JwtProperties所需的属性要在application.yaml中配置:
hm:jwt:location: classpath:hmall.jks # 秘钥地址alias: hmall # 秘钥别名password: hmall123 # 秘钥文件密码tokenTTL: 30m # 登录有效期auth:excludePaths: # 无需登录校验的路径- /search/**- /users/login- /items/**
2.4.2.登录校验过滤器
接下来,我们定义一个登录校验的过滤器:

package com.hmall.gateway.filter;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final JwtTool jwtTool;private final AuthProperties authProperties;private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取RequestServerHttpRequest request = exchange.getRequest();// 2.判断是否不需要拦截if(isExclude(request.getPath().toString())){// 无需拦截,直接放行return chain.filter(exchange);}// 3.获取请求头中的tokenString token = null;List<String> headers = request.getHeaders().get("authorization");if (!CollUtils.isEmpty(headers)) {token = headers.get(0);}// 4.校验并解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果无效,拦截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete();}// TODO 5.如果有效,传递用户信息System.out.println("userId = " + userId);// 6.放行return chain.filter(exchange);}private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}


2.5.微服务获取用户
现在,网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?
由于网关发送请求到微服务依然采用的是Http请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。
据图流程图如下:

因此,接下来我们要做的事情有:
- 改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务
- 编写微服务拦截器,拦截请求获取用户信息,保存到ThreadLocal后放行
2.5.1.保存用户到请求头
首先,我们修改登录校验拦截器的处理逻辑,保存用户信息到请求头中:

2.5.2.拦截器获取用户
在hm-common中已经有一个用于保存登录用户的ThreadLocal工具:

其中已经提供了保存和获取用户的方法:

接下来,我们只需要编写拦截器,获取用户信息并保存到UserContext,然后放行即可。
由于每个微服务都有获取登录用户的需求,因此拦截器我们直接写在hm-common中,并写好自动装配。这样微服务只需要引入hm-common就可以直接具备拦截器功能,无需重复编写。
我们在hm-common模块下定义一个拦截器:

具体代码如下:
package com.hmall.common.interceptor;import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的用户信息String userInfo = request.getHeader("user-info");// 2.判断是否为空if (StrUtil.isNotBlank(userInfo)) {// 不为空,保存到ThreadLocalUserContext.setUser(Long.valueOf(userInfo));}// 3.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserContext.removeUser();}
}
接着在hm-common模块下编写SpringMVC的配置类,配置登录拦截器:

package com.hmall.common.config;import com.hmall.common.interceptor.UserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}
2.6.OpenFeign传递用户
前端发起的请求都会经过网关再到微服务,由于我们之前编写的过滤器和拦截器功能,微服务可以轻松获取登录用户信息。
但有些业务是比较复杂的,请求到达微服务后还需要调用其它多个微服务。比如下单业务,流程如下:


public interface RequestInterceptor {/*** Called for every request. * Add data using methods on the supplied {@link RequestTemplate}.*/void apply(RequestTemplate template);
}

在com.hmall.api.config.DefaultFeignConfig中添加一个Bean:
@Bean
public RequestInterceptor userInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 获取登录用户Long userId = UserContext.getUser();if(userId == null) {// 如果为空则直接跳过return;}// 如果不为空则放入请求头中,传递给下游微服务template.header("user-info", userId.toString());}};
}
好了,现在微服务之间通过OpenFeign调用时也会传递登录用户信息了。
相关文章:
微服务--04--SpringCloudGateway 网关
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1.网关路由1.1 认识网关在SpringCloud当中,提供了两种网关实现方案: 1.2.快速入门1.3.路由过滤 2.网关登录校验2.1.鉴权思路分析2.2.网关过滤…...
Java程序连接 nacos集群
我们在bootstrap.yml文件里可以直接连一个nacos集群的. 架构如下 没错,我们程序直连的是通过Nginx的,利用nginx的反向代理来做到连接nacos集群. 我们先把nginx的配置贴上来 upstream cluster{server 127.0.0.1:8848;server 127.0.0.1:8849;server 127.0.0.1:8850; }server{l…...
【深度学习】参数优化和训练技巧
寻找合适的学习率(learning rate) 学习率是一个非常非常重要的超参数,这个参数呢,面对不同规模、不同batch-size、不同优化方式、不同数据集,其最合适的值都是不确定的,我们无法光凭经验来准确地确定lr的值,我们唯一可…...
CeresPCL 曲线拟合之三次多项式
文章目录 一、简介2.1 实现步骤二、实现代码三、实现效果参考资料一、简介 2.1 实现步骤 (1)构建代价函数。假设我们得到了一组数据,也知晓该数据是用曲线方程: y = a x 3 + b x 2 + c x +...
小白备战蓝桥杯:Java基础语法
一、注释 IDEA注释快捷键:Ctrl / 单行注释: //注释信息 多行注释: /* 注释信息 */ 二、字面量 常用数据:整数、小数、字符串(双引号)、字符(单引号)、布尔值(tr…...
C#面向对象
过程类似函数只能执行没有返回值 函数不仅能执行,还可以返回结果 1、面向过程 a 把完成某一需求的所有步骤 从头到尾 逐步实现 b 根据开发需求,将某些 功能独立 的代码 封装 成一个又一个 函数 c 最后完成的代码就是顺序的调用不同的函数 特点 1、…...
智能优化算法应用:基于蝙蝠算法无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于蝙蝠算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于蝙蝠算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蝙蝠算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…...
【栈和队列(1)(逆波兰表达式)】
文章目录 前言什么是栈(Stack)栈方法栈的模拟实现链表也可以实现栈逆波兰表达式逆波兰表达式在栈中怎么使用 前言 什么是栈(Stack) 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶࿰…...
Blazor Table 实现获取当前选中行的功能
这里需要使用到OnClickRowCallBack事件 后台使用案例...
Flask Echarts 实现历史图形查询
Flask前后端数据动态交互涉及用户界面与服务器之间的灵活数据传递。用户界面使用ECharts图形库实时渲染数据。它提供了丰富多彩、交互性强的图表和地图,能够在网页上直观、生动地展示数据。ECharts支持各种常见的图表类型,包括折线图、柱状图、饼图、散点…...
【漫谈】信创
近些年来,自主创新绝对是高频词汇。 以往是供应链、芯片领域,现在终于到了信息领域。 近期,从上至下、从中央到地方、从政府到国企,各层面、各行业、各领域都在提及“信创”。 信创是个大工程,从计算机通用处理器、…...
linux wget --no-check-certificate
如果您希望每次使用wget命令时都跳过SSL证书检查,可以将–no-check-certificate参数添加到wget的默认配置文件中。 请按照以下步骤进行操作: vi ~/.wgetrc# 插入内容 check_certificate off保存并关闭文件。 现在,wget命令将在每次使用时自…...
mysql命令行连接数据库
有时项目连接不上数据库,报错鉴权失败,先用mysql工具连接下,容易发现问题。 直接输入mysql看是否已安装,如果没有就安装下。 # 注:直接mysql就行,不用-cli也不用-client,也不用-server…...
计算机丢失vcomp140.dll是什么意思,如何解决与修复(附教程)
vcomp140.dll缺失的5种解决方法以及vcomp140.dll缺失原因 引言: 在日常使用电脑的过程中,我们可能会遇到一些错误提示,其中之一就是“vcomp140.dll缺失”。这个错误提示通常出现在运行某些程序或游戏时,给使用者带来了困扰。本文…...
基于SSM实现的叮当书城
一、系统架构 前端:jsp | jquery | layui 后端:spring | springmvc | mybatis 环境:jdk1.7以上 | mysql | maven 二、代码与数据库 三、功能介绍 01. 系统首页 02. 商品分类 03. 热销 04. 新品 05. 注册 06. 登录 07. 购物车 08. 后台-首页 …...
python基础练习题库实验5
文章目录 题目1代码实验结果题目2代码实验结果题目3代码实验结果题目4代码实验结果题目总结题目1 编写一个程序,使用while循环语句和字符串格式显示以下精确输出。 例如: …...
JS手写instanceof(内含源码与详解)
前言 本文主要讲解JavaScript如何手写一个简易的instanceof,从而实现数据类型判断的作用.那么好,本文正式开始. instanceof作用 instanceOf的作用就是用来判断JavaScript中的数据类型是否是开发所输入的那种, 语法格式:obj instanceof objtype 左侧就是要判断的数据,而右侧就…...
无公网IP下,如何实现公网远程访问MongoDB文件数据库
文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 前言 MongoDB是一个基于分布式文件存储的数…...
初始化的内容写到析构函数中。。。。。。。
大概是,把应该在构造函数中初始化的堆栈窗体代码写到了析构函数中。。。。 不是因为没掌握构造/析构,而是。。。。 检查了四十多分钟没检查出来。。 被自己蠢哭。 #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) { }…...
git 使用过程错误集合
文章目录 1、git-credential-manager-core was renamed to git-credential-manager2、credential-manager-core is not a git command. See git --help. 1、git-credential-manager-core was renamed to git-credential-manager 出现以下提示建议尽快更新您的 Git 配置以使用新…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...


