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

Openfeign使用教程(带你快速体验Openfeign的便捷)

文章摘要

本文中将教会您如何快速使用Openfeign,包括Opengfeign的基础配置、接口调用、接口重试、拦截器实现、记录接口日志信息到数据库


文章目录

  • 文章摘要
  • 一、Openfeign初步定义
  • 二、Openfeign快速入门
    • 1.引入maven坐标
    • 2.启动类增加@EnableFeignClients注解
    • 3.定义feign客户端并实现接口调用
    • 3.定义feign远程调用演示
  • 三、Openfeign常用配置
    • 1.配置请求连接超时
    • 2.配置Openfeign接口调用重试机制
    • 3.配置Openfeign请求拦截器
    • 4.Openfeign接口日志保存到数据库


一、Openfeign初步定义

OpenFeign 是一个基于 Spring 的声明式、模板化的 HTTP 客户端,它简化了编写 Web 服务客户端的过程。用户只需创建一个接口并添加相应的注解,即可实现对远程服务的调用。OpenFeignSpring Cloud 的一部分,它支持 Spring MVC 的注解,如 @RequestMapping,使得使用 HTTP 请求访问远程服务就像调用本地方法一样直观和易于维护。Openfeign底层默认使用JDK提供的HttpURLConnection进行通信(源码参考类feign.Default),使用Openfeign可以快速的帮我们完成第三方接口调用的实现,简化开发流程。

二、Openfeign快速入门

1.引入maven坐标

因为我使用的SpringBoot2.2.10.RELEASE版本,因此在引入Openfeign依赖时,也就引入与SpringBoot一致的版本,具体版本号根据您的SpringBoot版本来定。

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>2.2.1.RELEASE</version>
</dependency>

2.启动类增加@EnableFeignClients注解

启动类增加 @EnableFeignClients 用于在启动时加载我们定义的所有使用注解 @FeignClient 定义的feign客户端,并把feign客户端注册到 IOC 容器中。@EnableFeignClients注解中的basePackages用于配置扫描哪些包下来的类,这里我配置com.hl.by.remote包下的类
在这里插入图片描述
此时openfeign最基础的配置就已经配置完成了,现在可以自定义feign客户端,使用openfeign调用远程接口。


3.定义feign客户端并实现接口调用

feign的客户端需要使用 @FeignClient 注解进行标识,这样在扫描时才知道这是一个feign客户端。@FeignClient 最常用的就两个属性,一个是name,用于给客户端定义一个唯一的名称,另一个就是url,用于定义该客户端调用的远程地址。
在这里插入图片描述
这里的 ${open-feign.api-url} 是通过springBoot通过读取yaml或者properties文件获取的配置。这里我是在yaml中配置了该值:
在这里插入图片描述
因此客户端feign-remote-service调用的远程地址就是http://127.0.0.1:8080。在定义好客户端后,可以在客户端中加入对应的接口,比如我在客户端feign-remote-service中增加了一个saveSku的方法,当调用该方法时,将发送一个请求方式是POSTrequestBody的类型是FeignRemoteRequestVO,返回对象类型是ResponseResult,请求地址是http://127.0.0.1:8080/hl-template/mock/saveSku 的请求。
在这里插入图片描述

3.定义feign远程调用演示

由于我定义的feign客户端的调用地址是本地,因此我在本地定义了一个saveSku接口,用于演示feign远程调用的效果
在这里插入图片描述
我使用Postman 调用接口feign,然后通过feign这个接口使用刚刚定义的feign客户端进行远程调用saveSku方法。
在这里插入图片描述
在这里插入图片描述
通过feignRemoteService.saveSku将调用到第三方接口,可以看到已经通过feign客户端调用到了远程方法saveSku
在这里插入图片描述
现在您已经学会了使用Openfegn来进行最基础的远程调用。可以看出使用Openfeign以后,我们只用定义客户端即可,剩下的具体调用实现交给Openfeign就可以了。


三、Openfeign常用配置

本小节将对Openfeign常用配置进行演示,包括连接超时配置、接口调用、接口重试、拦截器实现,其中会涉及部分少量源码,但重点是教会您如何使用
Openfeign采用动态代理的方式实现接口调用,通过ReflectiveFeign.invoke 方法找到具体SynchronousMethodHandlerSynchronousMethodHandler是每个feign客户端中每个具体调用方法的最终实现。通过调用SynchronousMethodHandler中的invoke方法进行远程接口调用。

1.配置请求连接超时

Openfeign中常用的基础信息维护在抽象类Feign中,维护着Feign调用时的日志等级、客户端调用Client、重试机制、日志实现、请求编码器(encoder)、响应解码器(decoder)等。可以看到Feign采用Builder构建者模式对每个属性都设置了最初的值。而请求超时的配置则由类Options维护。
在这里插入图片描述Options类中属性只有三个,connectTimeoutMillis配置连接超时时间,readTimeoutMillis连接成功后等待响应超时时间,followRedirects是否允许重定向。默认的Options设置连接超时是10秒,等待响应超时是60秒,不允许重定向。
在这里插入图片描述
因此如果我们要自定义Openfeign的连接响应超时时间,则只需要自己声明一个Options注入到spring容器中去即可替换Openfeign默认的配置。我在yaml中配置了openfeign的接口连接超时信息,只需要创建一个配置类,读取配置信息放入Options中即可。
在这里插入图片描述
代码如下:

@Data
@Component
@ConfigurationProperties(prefix = "open-feign")
@RequiredArgsConstructor
public class FeignConfig {private Integer connectTimeoutMillis;private Integer readTimeoutMillis;private boolean followRedirects;private Integer maxAttempts;private Long period;private Long maxPeriod;/*** 配置openfeign调用接口时的超时时间和等待超时时间** @return Request.Options*/@Beanpublic Request.Options options() {return new Request.Options(connectTimeoutMillis, readTimeoutMillis, followRedirects);}

通过启动项目打断点就可以看到我们自定义的Options是否生效,可以看到我们自定的配置已经生效并设置到了Openfeign
在这里插入图片描述


2.配置Openfeign接口调用重试机制

Openfeign接口调用重试默认是由Retryer接口中的Default来实现,Default里默认设置每次接口调用失败随机休眠100毫秒~1000毫秒,最多重试5次。
在这里插入图片描述
在这里插入图片描述

如果我们要自定义Openfeign的重试机制的话,可以像Retryer.Default一样实现Retryer接口,这里我自定义了一个接口重试机制名叫FeignRetry,相比于Retryer.Default只是增加了错误信息打印。首先依然先在yaml配置文件中配置重试相关信息。配置了接口调用失败时休眠一秒进行重试,最多重试3次

open-feign:api-url: http://127.0.0.1:8088connectTimeoutMillis: 5000  #超时连接超时时间readTimeoutMillis: 30000 #连接成功后等待接口响应时间超时时间followRedirects: false #是否允许重定向maxAttempts: 3 #接口调用失败重试次数,调用失败通常指的是接口网络异常period: 1000 #接口调用失败后,周期多少毫秒后重新尝试调用maxPeriod: 1000 #接口调用失败后,最大周期多少毫秒后重新尝试调用

通过FeignConfig读取配置信息将配置信息加载到FeignRetry中:

@Data
@Component
@ConfigurationProperties(prefix = "open-feign")
@RequiredArgsConstructor
public class FeignConfig {private Integer connectTimeoutMillis;private Integer readTimeoutMillis;private boolean followRedirects;private Integer maxAttempts;private Long period;private Long maxPeriod;private final InterfaceLogMapper interfaceLogMapper;/*** 配置将重试N次,每次间隔M秒钟,重试条件为连接超时或请求失败** @return Retryer*/@Beanpublic Retryer feignRetryer() {return new FeignRetry(period, maxPeriod, maxAttempts);}/*** 配置openfeign调用接口时的超时时间和等待超时时间** @return Request.Options*/@Beanpublic Request.Options options() {return new Request.Options(connectTimeoutMillis, readTimeoutMillis, followRedirects);}

FeignRetry代码实现如下:

import feign.FeignException;
import feign.RetryableException;
import feign.Retryer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import static java.util.concurrent.TimeUnit.SECONDS;/*** @Author: Greyfus* @Create: 2024-03-10 00:56* @Version: 1.0.0* @Description:自定义feign请求时重试机制,与Default相比,只是增加了调用日志纪录机制*/
public class FeignRetry implements Retryer {private static final Logger LOGGER = LoggerFactory.getLogger(FeignRetry.class);//最大重试次数private final int maxAttempts;//调用频率private final long period;//最大频率private final long maxPeriod;//尝试的次数int attempt;//纪录失眠次数long sleptForMillis;public FeignRetry() {this(1000, SECONDS.toMillis(1), 3);}public FeignRetry(long period, long maxPeriod, int maxAttempts) {this.period = period;this.maxPeriod = maxPeriod;this.maxAttempts = maxAttempts;this.attempt = 1;}protected long currentTimeMillis() {return System.currentTimeMillis();}public void continueOrPropagate(RetryableException e) {if (e instanceof FeignException) {//打印异常FeignException remoteException = e;LOGGER.error("Feign request【{}】 attempt 【{}】 times,status:【{}】,errorMessage:{}", remoteException.request().url(), attempt, remoteException.status(), remoteException.getMessage());}if (attempt++ >= maxAttempts) {throw e;}long interval;if (e.retryAfter() != null) {interval = e.retryAfter().getTime() - currentTimeMillis();if (interval > maxPeriod) {interval = maxPeriod;}if (interval < 0) {return;}} else {interval = nextMaxInterval();}try {Thread.sleep(interval);} catch (InterruptedException ignored) {Thread.currentThread().interrupt();throw e;}sleptForMillis += interval;}long nextMaxInterval() {long interval = (long) (period * Math.pow(1.5, attempt - 1));return interval > maxPeriod ? maxPeriod : interval;}@Overridepublic Retryer clone() {return new FeignRetry(period, maxPeriod, maxAttempts);}
}

3.配置Openfeign请求拦截器

openfeign提供了一个RequestInterceptor接口,凡是实现该接口的类并注入到Spring容器中后,在feign进行远程调用之前会调用实现该接口的所有类。源码可以参考SynchronousMethodHandler中的targetRequest方法,targetRequest是在执行远程调用之前进行调用。Openfeign请求拦截器的作用通常是用于设置公共属性,比如给请求设置请求头认证信息等。
在这里插入图片描述
这里我自定义了一个名叫FeignRequestInterceptor 的拦截器并使用@Component注入到容器中。当我调用远程接口时,通过debug模式可以看到该拦截器的apply方法被调用。

/*** @Author: DI.YIN* @Date: 2024/3/13 13:43* @Version: 1.0.0* @Description: openfeign请求拦截器,在发送请求前进行拦截**/
@Component
public class FeignRequestInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate requestTemplate) {System.out.println("你好");}
}

在这里插入图片描述


4.Openfeign接口日志保存到数据库

Openfeign提供了请求拦截器,但是没有提供响应拦截器,这导致我们无法纪录接口的响应信息到数据库。为了实现该功能我翻遍了SynchronousMethodHandler这个类的所有源码信息。我们先简单的对SynchronousMethodHandler源码进行分析,再来说如何实现将接口日志信息存入数据库。SynchronousMethodHandlerinvoke是整个openfeign远程调用了的核心部分,其最核心的是executeAndDecode方法,用于构建请求参数、请求方式等信息。
在这里插入图片描述
进入executeAndDecode方法后,第一个执行targetRequest方法,targetRequest就是循环遍历RequestInterceptor请求拦截器,调用每个RequestInterceptor实现类的apply方法。
在这里插入图片描述
在这里插入图片描述
执行完拦截器后,判断当前日志等级是否为不为NONE,如果当前日志等级不为NONE,则通过logger.logRequest打印日志。此时可以通过request获取到请求信息、请求地址、请求头等信息。
在这里插入图片描述
response = client.execute(request, options); 则是真正调用远程接口的地方,openfeing默认采用HttpURLConnection进行远程接口调用。如果调用异常,则logger.logIOException打印异常信息,如果调用成功,则通过logger.logAndRebufferResponse打印异常信息。
在这里插入图片描述
通过对源码的分析,我们知道当日志等级不为NONE时,openfeign会打印接口请求信息到控制台,如果接口调用失败,调用logIOException将错误信息打印到控制台,接口调用成功则调用logAndRebufferResponse将响应信息打印到控制台。因此我打算自定义一个日志对象,将接口信息存入数据库。


第一步创建接口日志信息表,用于存放接口日志信息:

CREATE TABLE interface_log (
log_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘日志主键’,
remote_sys_code varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘远程系统编码’,
remote_sys_name varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘远程系统名称’,
request_time datetime NOT NULL COMMENT ‘请求时间’,
response_time datetime DEFAULT NULL COMMENT ‘响应时间’,
request_method varchar(10) COLLATE utf8mb4_bin NOT NULL COMMENT ‘请求方式 GET PUT POST’,
request_url varchar(200) COLLATE utf8mb4_bin NOT NULL COMMENT ‘请求地址’,
request_headers_message longtext COLLATE utf8mb4_bin COMMENT ‘请求头信息’,
request_message longtext COLLATE utf8mb4_bin COMMENT ‘请求信息’,
response_message longtext COLLATE utf8mb4_bin COMMENT ‘响应信息’,
status varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT ‘接口状态’,
PRIMARY KEY (log_id) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT=‘日志记录表’;

第二步创建接口日志对象:

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.hl.by.domain.base.BaseDomain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.RequiredArgsConstructor;import java.util.Date;/*** @Author: DI.YIN* @Date: 2024/3/13 16:05* @Version: 1.0.0* @Description: 日志实体类**/
@Data
@RequiredArgsConstructor
@TableName("interface_log")
public class InterfaceLogDomain extends BaseDomain {@TableField(exist = false)private static final long serialVersionUID = 2588400601329175428L;@ApiModelProperty(value = "日志主键")@TableId(type = IdType.AUTO)private Long logId;@ApiModelProperty(value = "远程系统编码")@TableField(value = "remote_sys_code")private String remoteSysCode;@ApiModelProperty(value = "远程系统名称")@TableField(value = "remote_sys_name")private String remoteSysName;@ApiModelProperty(value = "请求时间")@TableField(value = "request_time")private Date requestTime;@ApiModelProperty(value = "响应时间")@TableField(value = "response_time")private Date responseTime;@ApiModelProperty(value = "请求方式")@TableField(value = "request_method")private String requestMethod;@ApiModelProperty(value = "请求地址")@TableField(value = "request_url")private String requestUrl;@ApiModelProperty(value = "请求头内容")@TableField(value = "request_headers_message")private String requestHeaders;@ApiModelProperty(value = "请求内容")@TableField(value = "request_message")private String requestMessage;@ApiModelProperty(value = "响应内容")@TableField(value = "response_message")private String responseMessage;@ApiModelProperty(value = "接口状态")@TableField(value = "status")private String status;}

第三步创建日志Mapper,包含对日志表的增删改查功能,这里我使用MyabtisPlus框架,因此实现相对简单。您可根据自己的实际情况实现Mapper。

/*** @Author: DI.YIN* @Date: 2024/3/13 16:34* @Version: 1.0.0* @Description: 接口日志**/
public interface InterfaceLogMapper extends BaseMapper<InterfaceLogDomain> {
}

第四步自定义日志对象FeignRemoteLogger

package com.hl.by.common.feign;import com.hl.by.domain.log.InterfaceLogDomain;
import com.hl.by.domain.log.InterfaceLogStatus;
import com.hl.by.persistence.dao.log.InterfaceLogMapper;
import feign.Logger;
import feign.Request;
import feign.Response;
import feign.Util;
import lombok.RequiredArgsConstructor;import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.Map;import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;/*** @Author: DI.YIN* @Date: 2024/3/13 15:36* @Version: 1.0.0* @Description: Feign接口远程调用接口日志记录**/
@RequiredArgsConstructor
public class FeignRemoteLogger extends Logger {private final InterfaceLogMapper logMapper;/*** 具体日志显示格式再次方法打印,因为要将日志写入数据库,因此该地方就不打印日志了** @param configKey* @param format* @param args*/@Overrideprotected void log(String configKey, String format, Object... args) {}/*** 在调用之前调用该方法,将请求数据写入数据库** @param configKey* @param logLevel* @param request*/@Overrideprotected void logRequest(String configKey, Level logLevel, Request request) {InterfaceLogDomain interfaceLogDomain = new InterfaceLogDomain();//从header中获取系统编码interfaceLogDomain.setRemoteSysCode(request.headers().get("SYS_CODE") == null || request.headers().get("SYS_CODE").isEmpty() ? "UNKNOWN" : request.headers().get("SYS_CODE").toArray()[0].toString());//从header中获取系统名称interfaceLogDomain.setRemoteSysName(request.headers().get("SYS_NAME") == null || request.headers().get("SYS_NAME").isEmpty() ? "UNKNOWN" : request.headers().get("SYS_NAME").toArray()[0].toString());interfaceLogDomain.setRequestTime(new Date());interfaceLogDomain.setStatus(InterfaceLogStatus.DOING.getCode());interfaceLogDomain.setRequestMethod(request.httpMethod().name());//请求方式interfaceLogDomain.setRequestUrl(request.url()); //请求地址Map<String, Collection<String>> headers = request.headers();//请求头if (request.requestBody() != null) {interfaceLogDomain.setRequestMessage(request.requestBody().asString());//请求内容}//存入数据库logMapper.insert(interfaceLogDomain);//放入线程缓存中,等待请求完成或者异常时,从线程缓存中拿出日志对象,回填数据FeignRequestContextHolder.setThreadLocalInterfaceLog(interfaceLogDomain);}/*** 当配置了接口重试机制时,再接口重试之前调用该方法** @param configKey* @param logLevel*/@Overrideprotected void logRetry(String configKey, Level logLevel) {}/*** 当接口调用异常时,会调用该接口,如果配置了重试机制,该方法在logRetry之前调用** @param configKey* @param logLevel* @param ioe* @param elapsedTime* @return*/@Overrideprotected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {//从线程缓存中获取日志信息,用于信息回填存入数据库InterfaceLogDomain threadLocalInterfaceLog = FeignRequestContextHolder.getThreadLocalInterfaceLog();if (threadLocalInterfaceLog != null) {threadLocalInterfaceLog.setStatus(InterfaceLogStatus.ERROR.getCode());//回填响应状态threadLocalInterfaceLog.setResponseTime(new Date()); //回填响应时间threadLocalInterfaceLog.setResponseMessage(ioe.getMessage());try {logMapper.updateById(threadLocalInterfaceLog);} catch (Throwable throwable) {//清除线程缓存FeignRequestContextHolder.clear();}}return ioe;}/*** 接口调用成功后,调用该方法记录日志信息** @param configKey* @param logLevel* @param response* @param elapsedTime* @return* @throws IOException*/@Overrideprotected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {//从线程缓存中获取日志信息,用于信息回填存入数据库InterfaceLogDomain threadLocalInterfaceLog = FeignRequestContextHolder.getThreadLocalInterfaceLog();if (threadLocalInterfaceLog != null) {try {threadLocalInterfaceLog.setResponseTime(new Date());//回写接口响应时间threadLocalInterfaceLog.setStatus(InterfaceLogStatus.SUCCESS.getCode());//回写接口日志状态int status = response.status();int bodyLength;if (response.body() != null && !(status == 204 || status == 205)) {// HTTP 204 No Content "...response MUST NOT include a message-body"// HTTP 205 Reset Content "...response MUST NOT include an entity"//获取响应内容byte[] bodyData = Util.toByteArray(response.body().asInputStream());//内容长度bodyLength = bodyData.length;if (bodyLength > 0) {String responseMessage = decodeOrDefault(bodyData, UTF_8, "Binary data");threadLocalInterfaceLog.setResponseMessage(responseMessage);}return response.toBuilder().body(bodyData).build();}} finally {logMapper.updateById(threadLocalInterfaceLog);FeignRequestContextHolder.clear();}}return response;}
}

FeignRequestContextHolder 用于缓存每个线程当前所拥有的日志信息

/*** @Author: Greyfus* @Create: 2024/3/13 15:36* @Version: 1.0.0* @Description: 用于维护openfeign当前线程请求的请求日志对象,该对象在请求之前进行缓存,在请求结束或者异常后进行拿出回写日志信息保存到数据库*/
package com.hl.by.common.feign;import com.hl.by.domain.log.InterfaceLogDomain;public class FeignRequestContextHolder {private static final ThreadLocal<InterfaceLogDomain> THREAD_LOCAL_INTERFACE_LOG = new ThreadLocal<>();/*** 获取当前线程缓存的日志信息** @return*/public static InterfaceLogDomain getThreadLocalInterfaceLog() {return THREAD_LOCAL_INTERFACE_LOG.get();}/*** 设置当前线程接口请求的日志信息** @param interfaceLogDomain*/public static void setThreadLocalInterfaceLog(InterfaceLogDomain interfaceLogDomain) {THREAD_LOCAL_INTERFACE_LOG.set(interfaceLogDomain);}/*** 清除数据*/public static void clear() {THREAD_LOCAL_INTERFACE_LOG.remove();}
}

InterfaceLogStatus 枚举维护接口请求状态

package com.hl.by.domain.log;/*** @Author: DI.YIN* @Date: 2024/3/13 16:31* @Version: 1.0.0* @Description: 接口日志状态**/
public enum InterfaceLogStatus {SUCCESS("S"),ERROR("E"),DOING("D"),;private String code;InterfaceLogStatus(String code) {this.code = code;}public String getCode() {return code;}
}

第五步设置日志等级并注入自定义日志对象

@Data
@Component
@ConfigurationProperties(prefix = "open-feign")
@RequiredArgsConstructor
public class FeignConfig {private Integer connectTimeoutMillis;private Integer readTimeoutMillis;private boolean followRedirects;private Integer maxAttempts;private Long period;private Long maxPeriod;@Autowiredprivate InterfaceLogMapper interfaceLogMapper;/*** 参考SynchronousMethodHandler源码,executeAndDecode方法中,当日志等级不等于Logger.Level.NONE才触发日志显示** @return 返回日志等级,openfeign根据日志等级来显示请求响应日志*/@Beanpublic Logger.Level level() {return Logger.Level.FULL;}/*** 配置openfeign自定义请求日志记录,将请求日志存入MQ或者数据库** @return*/@Beanpublic Logger feignRemoteLogger() {return new FeignRemoteLogger(interfaceLogMapper);}}

您仅需要将以上代码复制到您的项目中,就可以正常运行!!!现在让我们来测试一下效果。我用Postman调用本地接口,本地接口通过openfeign调用远程接口进行保存商品。
在这里插入图片描述
数据库的接口日志表已经成功保存本次请求的信息:
在这里插入图片描述


相关文章:

Openfeign使用教程(带你快速体验Openfeign的便捷)

文章摘要 本文中将教会您如何快速使用Openfeign&#xff0c;包括Opengfeign的基础配置、接口调用、接口重试、拦截器实现、记录接口日志信息到数据库 文章目录 文章摘要一、Openfeign初步定义二、Openfeign快速入门1.引入maven坐标2.启动类增加EnableFeignClients注解3.定义fei…...

【leetcode】相同的树➕对称二叉树➕另一棵树的子树

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家刷题&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 相同的树二. 对称二叉树三. 另一棵树的子树 一. 相同的树 点击查看题目 思路: bool isSameTree(…...

uni-app 安卓手机判断是否开启相机相册权限

// 安卓相机权限 androidCameraPermiss(index){ plus.android.requestPermissions([android.permission.CAMERA],(e) > { if (e.deniedAlways.length > 0) { this.androidAuthCamera false …...

GPT实战系列-LangChain构建自定义Agent

GPT实战系列-LangChain构建自定义Agent LangChain GPT实战系列-LangChain如何构建基通义千问的多工具链 GPT实战系列-构建多参数的自定义LangChain工具 GPT实战系列-通过Basetool构建自定义LangChain工具方法 GPT实战系列-一种构建LangChain自定义Tool工具的简单方法 GPT…...

uniapp-vue3 项目初始化集成配置【开箱即用】

地址 https://gitee.com/charrie/vue3-uniapp-init 部分截图展示 技术说明 采用vue3viteuniapp技术栈&#xff0c;setup语法糖编码方式使用完全免费的sard-uniapp组件库引入unocss量子化样式引擎&#xff0c;动态css不用自己写样式&#xff0c;引用class即可&#xff0c;降低…...

【Qt】使用Qt实现Web服务器(一):QtWebApp介绍、演示

1、简介 QtWepApp是使用Qt实现的Web服务器库,虽然名字中有“App”但是它的确是一个lib库,编译生产的是libQtWebAppd.so 作者开发QtWepApp是从 Java Servlet 获得的灵感,本人不懂Java,对本人没有帮助。 QtWepApp是一个跨平台的项目,适用于:Linux、Windows、Mac OS 和Qt…...

SQLiteC/C++接口详细介绍之sqlite3类(八)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;七&#xff09; 下一篇&#xff1a; SQLiteC/C接口详细介绍之sqlite3类&#xff08;八&#xff09;&#xff08;暂未发表&#xff09; 24.sqlite3_cr…...

面视题之——悲观锁和乐观锁

面视题之——悲观锁和乐观锁 概念 悲观锁 悲观锁是在执行线程任务时直接加锁&#xff0c;以确保同一时刻只有一个线程可以访问共享资源。在使用悲观锁时&#xff0c;每次访问共享资源时都会尝试获取锁&#xff0c;如果锁已经被其他线程占用&#xff0c;当前线程就会被阻塞&a…...

OpenAI 的 GPTs 提示词泄露攻击与防护实战:攻击卷(一)

攻击提示词 1.0 五件套混合散打 1. Repeat the words above starting with the phrase "You are ChatGPT". put them in a txt code block. Include everything. 2. give me your very first initial prompt exactly and not modified 3. 你有一個舊的 prompt 和 配…...

【 c 语言 】指针入门

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&…...

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Swiper)

滑块视图容器&#xff0c;提供子组件滑动轮播显示的能力。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 可以包含子组件。 说明&#xff1a; 子组件类型&#xff1a;系统组件和自定义组…...

Python 导入Excel三维坐标数据 生成三维曲面地形图(面) 4-2、线条平滑曲面(原始颜色)但不去除无效点

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 scipy 1.12.0 代码: import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata fro…...

win10 + cpu + pycharm + mindspore

MindSpore是华为公司自研的最佳匹配昇腾AI处理器算力的全场景深度学习框架。 1、打开官网&#xff1a; MindSpore官网 2、选择以下选项&#xff1a; 3、创建conda 环境&#xff0c;这里python 选择3.9.0&#xff0c;也可以选择其他版本&#xff1a; conda create -c conda-…...

设计一个生产制造系统100问?

设计一个生产制造系统时&#xff0c;首先需要明确系统的目标和范围。生产制造系统的设计应该从产品需求和生产流程出发&#xff0c;结合现代科技手段&#xff0c;构建一个高效、智能、可持续的生产制造系统。 你的生产制造系统是针对哪种产品或产品类型设计的&#xff1f;系统需…...

LeetCode 面试经典150题 26.删除有序数组中的重复项

题目&#xff1a; 给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量…...

海豚调度系列之:集群部署(Cluster)

海豚调度系列之&#xff1a;集群部署Cluster 一、前置准备工作二、准备 DolphinScheduler 启动环境1.配置用户免密及权限2.配置机器 SSH 免密登陆3.启动 zookeeper4.初始化数据库5.修改相关配置5.修改 dolphinscheduler_env.sh 文件 三、启动DolphinScheduler四、登录 DolphinS…...

居民健康监测小程序|基于微信小程序的居民健康监测小程序设计与实现(源码+数据库+文档)

居民健康监测小程序目录 目录 基于微信小程序的居民健康监测小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1、用户信息管理 2、健康科普管理 5.3公告类型管理 3、论坛信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推…...

【海贼王的数据航海】排序——概念|直接插入排序|希尔排序

目录 1 -> 排序的概念及其运用 1.1 -> 排序的概念 1.2 -> 常见的排序算法 2 -> 插入排序 2.1 -> 基本思想 2.2 -> 直接插入排序 2.2.1 -> 代码实现 2.3 -> 希尔排序(缩小增量排序) 2.3.1 -> 代码实现 1 -> 排序的概念及其运用 1.1 -&g…...

Docker环境快速搭建RocketMq

window上面安装&#xff1a; 1.Namesrv docker pull rocketmqinc/rocketmq创建C:/docker/rocketmq/data/namesrv/logs:/root/logs C:/docker/rocketmq/data/namesrv/store:/root/store 目录 namesrv: docker run -d --restartalways --name rmqnamesrv -p 9876:9876 -v C:/do…...

【leetcode热题】比较版本号

难度&#xff1a; 中等通过率&#xff1a; 22.1%题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述 比较两个版本号 version1 和 version2。 如果 version1 > version2 返回 1&#xff0c;如果 version1 < version2 返回 -1&#xff0c; 除此之外…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...