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 服务客户端的过程。用户只需创建一个接口并添加相应的注解,即可实现对远程服务的调用。OpenFeign 是 Spring Cloud 的一部分,它支持 Spring MVC 的注解,如 @RequestMapping
,使得使用 HTTP 请求访问远程服务就像调用本地方法一样直观和易于维护。Openfeign底层默认使用JDK提供的HttpURLConnection进行通信(源码参考类feign.Default
),使用Openfeign可以快速的帮我们完成第三方接口调用的实现,简化开发流程。
二、Openfeign快速入门
1.引入maven坐标
因为我使用的SpringBoot是2.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的方法,当调用该方法时,将发送一个请求方式是POST,requestBody的类型是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 方法找到具体SynchronousMethodHandler。SynchronousMethodHandler是每个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源码进行分析,再来说如何实现将接口日志信息存入数据库。SynchronousMethodHandler的invoke是整个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,包括Opengfeign的基础配置、接口调用、接口重试、拦截器实现、记录接口日志信息到数据库 文章目录 文章摘要一、Openfeign初步定义二、Openfeign快速入门1.引入maven坐标2.启动类增加EnableFeignClients注解3.定义fei…...

【leetcode】相同的树➕对称二叉树➕另一棵树的子树
大家好,我是苏貝,本篇博客带大家刷题,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 目录 一. 相同的树二. 对称二叉树三. 另一棵树的子树 一. 相同的树 点击查看题目 思路: 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技术栈,setup语法糖编码方式使用完全免费的sard-uniapp组件库引入unocss量子化样式引擎,动态css不用自己写样式,引用class即可,降低…...
【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类(八)
返回目录:SQLite—免费开源数据库系列文章目录 上一篇:SQLiteC/C接口详细介绍之sqlite3类(七) 下一篇: SQLiteC/C接口详细介绍之sqlite3类(八)(暂未发表) 24.sqlite3_cr…...
面视题之——悲观锁和乐观锁
面视题之——悲观锁和乐观锁 概念 悲观锁 悲观锁是在执行线程任务时直接加锁,以确保同一时刻只有一个线程可以访问共享资源。在使用悲观锁时,每次访问共享资源时都会尝试获取锁,如果锁已经被其他线程占用,当前线程就会被阻塞&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 语言 】指针入门
🎈个人主页:豌豆射手^ 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:C语言 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步&…...

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Swiper)
滑块视图容器,提供子组件滑动轮播显示的能力。 说明: 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 子组件 可以包含子组件。 说明: 子组件类型:系统组件和自定义组…...

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、打开官网: MindSpore官网 2、选择以下选项: 3、创建conda 环境,这里python 选择3.9.0,也可以选择其他版本: conda create -c conda-…...
设计一个生产制造系统100问?
设计一个生产制造系统时,首先需要明确系统的目标和范围。生产制造系统的设计应该从产品需求和生产流程出发,结合现代科技手段,构建一个高效、智能、可持续的生产制造系统。 你的生产制造系统是针对哪种产品或产品类型设计的?系统需…...

LeetCode 面试经典150题 26.删除有序数组中的重复项
题目: 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量…...
海豚调度系列之:集群部署(Cluster)
海豚调度系列之:集群部署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上面安装: 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热题】比较版本号
难度: 中等通过率: 22.1%题目链接:. - 力扣(LeetCode) 题目描述 比较两个版本号 version1 和 version2。 如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...