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

【微服务】接口幂等性常用解决方案

一、前言

在微服务开发中,接口幂等性问题是一个常见却容易被忽视的问题,同时对于微服务架构设计来讲,好的幂等性设计方案可以让程序更好的应对一些高并发场景下的数据一致性问题。

二、幂等性介绍

2.1 什么是幂等性

通常我们说的幂等性,大多数情况下指的是服务端的接口幂等性。所以接口幂等性指的是,用户对于同一个操作发起的一次请求,或者多次请求结果是一致的。

举例来说:一个用户在手机APP提200块钱,一不小心点击了两次,理论上应该只取出200块钱,而不应该出来400(当然,真实场景下取钱操作是一个复杂事务,不可能一个接口点击就出来了)。在这种场景下,即使用户点了两次也应该只取出一次的钱,这就是接口幂等性。

2.2 幂等性问题产生原因

什么情况下会出现接口幂等性问题呢,通常来说主要有下面几个原因:

  • 用户重复提交,当客户端发起多次相同的请求时,服务器未能正确处理重复请求,导致产生幂等性问题。

    • 一般指用户填写好表单信息后,由于服请求响应较慢,从而多次点击提交按钮。

  • 非法调用,比如像第三方,通过逆向手段调试到了接口地址,然后通过爬虫或接口工具多次调用。

  • 并发操作

    • 在高并发环境下,可能出现多个请求同时到达服务器,如果服务器未能正确处理并发请求,可能会导致幂等性问题的产生。

  • 事务处理不当

    • 如果接口需要进行事务处理,并且没有正确地管理事务的提交和回滚,也可能导致幂等性问题的产生。

  • 失败重试

    • 在分布式项目中,被调用方出现超时或异常时,触发了调用方的重试补偿机制。

  • 重复消息

    • 通常是指引入MQ的项目中,对于同一个消息,生产者多次发送,或消费者重复消费。

2.3 为什么需要接口幂等

接口幂等性对于系统设计和开发具有重要意义,尤其是在电商、金融、交易等数据一致性要求比较严苛的场景下,幂等性的保障就显得格外重要。具体来说,幂等性的作用主要如下。

2.3.1 减少重复操作的影响

在网络通信中,可能由于各种原因导致请求的重复发送,如果接口是幂等的,即使接收到了重复的请求,系统也可以保持一致的状态,避免产生额外的副作用。

比如服务A调用服务B接口进行转账,假设A调用B时超时了,一般来说,超时的原因可能是网络传输丢包,也可能是处理请求的服务还没有接收到请求,或者接收到请求了但是还未来得及处理,或者请求处理了但是在结果返回的途中丢了。如果此时A进行重试的话,师傅会发生多笔转账呢?所以在这种情况下,如果下游的B服务接口如果没有做好幂等性保障的话,就会出现很严重的问题。

2.3.2 提高系统可靠性

当系统中接口具有幂等性时,即使出现异常情况或故障,系统也可以更容易恢复到正常状态,从而降低系统崩溃的风险。

举例来说,在mysql表中设计了基于version的字段,在每次对表的数据进行update时,为了保障接口幂等性,可以基于version先查,然后更新,在这种方式下,即便应用程序意外宕机或故障,也可以方便的根据version值进行回溯,快速恢复之前的数据。

2.3.3 简化客户端调用逻辑

对于客户端来说,无需关心接口的幂等性,只需按照业务需求发送请求,降低了客户端的复杂度和错误率。

当服务A调用服务B的时候,对于服务A来说就是客户端,并不打算因为调用B失败而特意做其他的业务处理,在这种情况下就需要B服务,即被调用方做好接口的幂等性处理,从而A在调用时难度和复杂性就降低了。

2.3.4 便于系统扩展和集成

当接口具有幂等性时,系统可以更容易地进行横向扩展和集成,不必担心多次请求会破坏系统状态。

三、接口幂等性与防重复提交

3.1 接口幂等性与防重复提交概念

这是在实际应用中很多同学混淆的概念,但是两者都是在开发中服务端需要解决的问题,其实来说接口幂等性和防重复提交是两个不同但相关的概念。具体来说:

  • 接口幂等性是指一个接口的多次重复调用所产生的影响与一次调用的影响相同。换句话说,无论某个接口被调用多少次,其结果都是一致的。这样设计的接口可以更容易处理各种问题,比如网络超时、断网重试等情况,而不会导致数据错误或状态混乱。

  • 防重复提交是指在用户提交数据或请求时,系统需要确保同样的数据或请求不会被重复处理多次。这通常涉及到在前端或后端做一些措施,比如生成唯一标识、使用Token或者设置时间间隔来避免用户多次提交同样的请求。

在实际应用中,接口幂等性和防重提交一般会结合起来使用,以确保系统稳定性和数据准确性。接口设计时考虑到幂等性可以简化系统的逻辑处理,而防重复提交则是为了提升用户体验和数据的完整性。

3.2 接口幂等性与防重复提交异同点

相同点:

  • 都与接口或请求的重复操作有关;

  • 都涉及到处理系统中重复请求可能带来的问题,如数据不一致、资源浪费等;

不同点:

  • 接口幂等性是指一个接口多次调用所产生的影响与一次调用的影响相同。接口本身具有幂等性的特点,无论调用多少次,结果都是一致的。

  • 重复提交指用户或系统在短时间内多次提交同一个请求或数据。重复提交通常发生在用户界面上,可能导致数据重复处理或资源浪费。

四、接口幂等性解决方案

4.1 使用核心业务字段唯一性约束

在很多业务场景中,业务表都会设置某个字段的唯一性约束,从而确定数据的唯一性,通常来说可以添加字段的唯一索引。比如订单表的订单号,用户表用户编码等。如果相同的请求再次发送过来,由于字段的唯一性约束,将会触异常而被捕获。如下是一段伪代码:

S       tring orderId = "puk-3309-A";Order order = new Order();order.setOrderId(orderId);try {orderDao.save(order);}catch (Exception e){//唯一性约束异常return "订单已创建";}

完整流程如下:

4.1.1 唯一约束方案优缺点

优点:

  • 使用简单,代码集成难度较低;

  • 可靠性好;

缺点:

  • 基于数据库自身的特性,不够优雅;

  • 在应对较大的并发时,具有一定的局限性;

4.2 使用乐观锁解决幂等性问题

乐观锁的使用场景非常多,是一种很好的用于解决并发冲突,幂等性问题的方案,在mysql中,乐观锁可以避免对行数据加锁从而提升系统的并发性能。通常依赖于数据版本号,或时间戳等字段进行控制,适用于需要对数据变更进行版本管理的场景。

如下有一张表,表中有一个version的字段

CREATE TABLE `seek_order` (`id` int(11) NOT NULL,`amount` int(12) DEFAULT NULL,`version` int(12) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

利用乐观锁的方式怎么解决接口的幂等性问题呢?请参考如下的操作流程。

具体来说,完整步骤如下:

  • 查询数据

    • select id,amount,version from seek_order where id=1;

  • 更新数据

    • update seek_order set amount = amount+10 ,version=version+1 where id=1 and version=1;

  • 判断更新的行数

    • 大于0,说明本次更新成功,如果等于0,说明本次没有对数据进行变更;

  • 本次操作数据的同时,还修改了数据的版本号,如果此时并发请求过来,再次执行相同的sql时候,update 并不会真正更新数据,从而update的执行影响行数为0,因为上一个update完成之后,数据的version已经变成2了,所以version=1肯定无法满足条件了;

  • 为了保证接口幂等性,接口可以直接返回客户端本次处理成功,因为version已经修改了,所以签名的请求一定成功过一次,后面都是重复请求;

与之类似的还有状态机字段,比如处理订单的时候,通常订单表中会有一个订单处理的状态,比如0代表创建,1代表待支付,2为已支付...,相同的update请求过来时需要带上status,如果本次update的影响行数为0,说明之前已经有更新成功了,如果影响行数为1,说明本次修改成功。

4.3 分布式锁解决幂等性问题

上面前两种解决方案中,其实都是利用了数据库的分布式锁机制,但是在实际开发中,一般并不太推荐使用,一是并发性能有局限,同时在捕获的异常中进行处理起来不够优雅,所以如果基于锁的特性来解决,可以采用分布式锁的方案来解决解决幂等性问题。

分布式锁有很多选择,像主流的基于redis的分布式锁解决方案,基于zookeeper的分布式锁解决方案等

如果以redis为例进行说明,在解决接口幂等性问题时,以生成订单的场景说明,可以参考如下流程

具体来说,操作步骤如下:

  • 生成唯一订单code作为唯一业务字段;

  • 使用redis的分布式锁,利用code作为key,同时需要设置key的超时时间;

  • 判断是否能设置成功,如果能设置成功,说明是第一次请求,则进入核心逻辑处理;

  • 如果设置失败,说明是重复请求,直接返回成功即可;

注意,分布式锁一定需要设置一个合理的超时时间,设置过短,无法合理的防止重复请求,设置太长,则会浪费redis的存储空间;

4.4 预置令牌解决幂等性问题

预置令牌,即token方案,简单来说,实现步骤如下:

  • 客户端发起业务操作前,先请求服务端颁发token,服务端生成一个token返回给客户端;

  • 服务端存储token到redis中,并设置过期时间;

  • 客户端发起操作请求,比如创建订单请求,携带token;

  • 服务端接收请求,并查询redis中是否存在token;

    • 如果不存在,则执行业务操作;

    • 否则,可认为是重复请求直接返回;

    • 业务逻辑操作完成后需要删除token;

4.4 本地消息事件表

在微服务场景中,经常利用MQ对微服务进行解耦,在使用MQ过程中,一个容易出现的问题就是,消息的重复发送,或消息的重复消费,在消息消费端,如果没有对消息做幂等性处理的话,可能会引发数据不一致问题。针对这种幂等性问题的场景,可以考虑采用本地消息事件表来解决。

具体操作流程如下:

  • 生成者发送消息到MQ,消息具备唯一的标识这里记为messageId;

  • 消费者第一次接收到消息并消费,将业务处理结果入库,同时记录业务唯一标识与messageId,表示处理过;

  • 如果因为某种原因,发生重复消费,先拿着业务ID或messageId去映射表中查询;

    • 如果已经存在,说明已经处理过,是重复请求;

五、代码实现

接下来演示基于token的接口幂等性方案在代码中的实现过程。

5.1 工程搭建

5.1.1 创建一张表

CREATE TABLE `seek_order` (`id` int(11) NOT NULL,`amount` int(12) DEFAULT NULL,`version` int(12) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

5.1.2 创建maven工程

工程目录如下

5.1.3 导入maven依赖

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.5</version><relativePath/> <!-- lookup parent from repository --></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</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.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency></dependencies>

5.1.4 添加配置文件

server:port: 8088spring:application:name: client-servicedatasource:url: jdbc:mysql://IP:3306/数据库名driverClassName: com.mysql.jdbc.Driverusername: rootpassword: rootredis:host: 127.0.0.1port: 6379mybatis:mapper-locations: classpath:mapper/*.xml#目的是为了省略resultType里的代码量type-aliases-package: com.congge.entityconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

5.2 代码实现过程

5.2.1 获取token接口

一般来说,token需要与当前登录人信息进行关联,这里简单起见,使用客户端请求IP为唯一标识,同时,在设置redis的key时候带上过期时间。

public String getToken(HttpServletRequest request) {String ipAddr = IpUtil.getIpAddr(request);String token = UUID.randomUUID().toString();redisTemplate.opsForValue().set(ipAddr,token, 1,TimeUnit.MINUTES);return token;
}

5.2.2 执行业务逻辑

按照上述的流程,客户端需要先获取token,然后在真正执行业务逻辑时携带token;

    @Transactionalpublic String createOrder(HttpServletRequest request) {String token = request.getHeader("token");String ipAddr = IpUtil.getIpAddr(request);String redisToken = redisTemplate.opsForValue().get(ipAddr);if(StringUtils.isEmpty(redisToken)){throw new RuntimeException("重复请求");}if(!redisToken.equals(token)){throw new RuntimeException("无效token");}SeekOrder seekOrder = new SeekOrder();int maxId = seekOrderDao.getMaxId();seekOrder.setId(maxId+1);seekOrder.setAmount(11);seekOrder.setVersion(1);seekOrderDao.saveOrder(seekOrder);//执行成功删除tokenredisTemplate.delete(ipAddr);return "订单创建成功";}

5.3 功能测试

5.3.1 客户端获取token

调用获取token接口,http://localhost:8088/token

5.3.2 创建订单

在获取到token之后,接下来执行创建订单接口,需要把上一步的token带入到请求header中

再次执行创建订单接口,由于约定了请求必须携带token,第一次创建完成之后,删除了token,所以再次创建订单时候抛异常,也可以直接返回创建成功。

5.4 优化改进

可以看到,在上面创建订单的处理逻辑中,对请求是否重复的判断是比较冗余的,或者说放到创建订单的主流程中不是很优雅,但这种处理又是必须要的,于是可以考虑在某个地方统一处理,这里我们定义一个过滤器,在过滤器中进行处理。

5.4.1 自定义拦截器

package com.congge.filter;import com.congge.service.IpUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@WebFilter(urlPatterns = "/*",filterName = "orderCheckFilter")
public class OrderCheckFilter implements Filter {@Autowiredprivate RedisTemplate<String,String> redisTemplate;@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)servletRequest;HttpServletResponse response = (HttpServletResponse)servletResponse;System.out.println(request.getRequestURI());if(!"/create".equals(request.getRequestURI())){filterChain.doFilter(servletRequest,servletResponse);return;}//只拦截创建订单的请求String token = request.getHeader("token");String ipAddr = IpUtil.getIpAddr(request);String redisToken = redisTemplate.opsForValue().get(ipAddr);if(StringUtils.isEmpty(redisToken)){throw new RuntimeException("重复请求");}if(!redisToken.equals(token)){throw new RuntimeException("无效token");}return;}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("orderCheckFilter init");}@Overridepublic void destroy() {System.out.println("orderCheckFilter destroy");}}

5.4.2 创建订单逻辑改造

将原本token处理的那一段逻辑移除

    @Transactionalpublic String createOrder(HttpServletRequest request) {String ipAddr = IpUtil.getIpAddr(request);SeekOrder seekOrder = new SeekOrder();int maxId = seekOrderDao.getMaxId();seekOrder.setId(maxId+1);seekOrder.setAmount(11);seekOrder.setVersion(1);seekOrderDao.saveOrder(seekOrder);//执行成功删除tokenredisTemplate.delete(ipAddr);return "订单创建成功";}

5.4.3 启动类添加注解

启动类别忘了添加下面的注解

@SpringBootApplication
@MapperScan(basePackages = {"com.congge.mapper"})
@ServletComponentScan("com.congge.filter")
public class SeekOrderApp {public static void main(String[] args) {SpringApplication.run(SeekOrderApp.class,args);}
}

5.4.4 功能测试验证

按照上面的步骤再次测试,当第二次相同的创建订单请求过来时,将会抛出异常

六、写在文末

本文详细介绍了在微服务开发场景中幂等性问题的解决方案,并结合一个实际场景给出了代码案例,幂等性问题的处理在实际开发中是一个不可忽视的问题,尤其是对于数据的一致性要求比较高的场景,有兴趣的同学可以基于本文提到的其他方案继续深入探究,本篇到此结束感谢观看。

相关文章:

【微服务】接口幂等性常用解决方案

一、前言 在微服务开发中&#xff0c;接口幂等性问题是一个常见却容易被忽视的问题&#xff0c;同时对于微服务架构设计来讲&#xff0c;好的幂等性设计方案可以让程序更好的应对一些高并发场景下的数据一致性问题。 二、幂等性介绍 2.1 什么是幂等性 通常我们说的幂等性&…...

RocketMQ学习笔记:零拷贝

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、零拷贝技术1.1、什么是零拷贝1.2、mmap()1.3、Java中的零拷贝 1、零拷贝技术 1.1、什么是零拷贝 使用传统的IO&#xff0c;从硬盘读取数据然后发送到网络需要经过四个步骤。 通过DMA复…...

3.26日总结

1.Fliptile Sample Input 4 4 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 Sample Output 0 0 0 0 1 0 0 1 1 0 0 1 0 0 0 0 题意&#xff1a;在题目输入的矩阵&#xff0c;在这个矩阵的基础上&#xff0c;通过最少基础反转&#xff0c;可以将矩阵元素全部变为0&#xff0c;如果不能达…...

应用日志集成到ElasticSearch

1、阿里云sls平台集成日志 阿里sls集成日志步骤 2、filebeat 收集到指定es 安装docker容器 Docker安装 拉取镜像&#xff1a; docker pull elastic/filebeat:7.5.1启动&#xff1a; docker run -d --namefilebeat elastic/filebeat:7.5.1拷贝容器中的数据文件到宿主机&a…...

MySQL多表联查函数

1 多表联查 1.1 表之间的关系 表和表的关系有: 一对一 老公 --> 老婆 , 人 ---> 身份证/户口本 一对多 皇帝 --> 妻妾 , 人 ---> 房/车 多对多 订单 --> 商品 1.2 合并结果集 合并结果集,是将多表查询的结果纵向合并 语法: select field1,field2 from t1 un…...

JAVAEE—实现多线程版本的定时器

文章目录 什么是定时器定时器的概念定时器的简单应用和介绍代码示例 定时器的代码解析定时器在执行任务的时候是创建了一个线程去执行吗&#xff1f;为什么叫做扫描线程呢&#xff1f;执行完任务之后代码就暂停了不自动结束吗&#xff1f; 手撕定时器demo相对时间与绝对时间Myt…...

KY228 找位置(用Java实现)

描述 对给定的一个字符串&#xff0c;找出有重复的字符&#xff0c;并给出其位置&#xff0c;如&#xff1a;abcaaAB12ab12 输出&#xff1a;a&#xff0c;1&#xff1b;a&#xff0c;4&#xff1b;a&#xff0c;5&#xff1b;a&#xff0c;10&#xff0c;b&#xff0c;2&…...

物联网边缘网关有哪些优势?-天拓四方

随着物联网技术的快速发展&#xff0c;越来越多的设备接入网络&#xff0c;数据交互日益频繁&#xff0c;对数据处理和传输的要求也越来越高。在这样的背景下&#xff0c;物联网边缘网关应运而生&#xff0c;以其低延迟、减少带宽消耗、提高数据质量和安全性等优势&#xff0c;…...

【C++】6-2 交换函数2 分数 10

6-2 交换函数2 分数 10 全屏浏览 切换布局 作者 刘利 单位 惠州学院 根据题目需求&#xff0c;编写一个交换函数Swap。 裁判测试程序样例&#xff1a; #include <iostream> using namespace std; class pen{private:string brand;string color;double price;publi…...

kafka 01

01....

Linux离线安装Docker-Oracle_11g

拉取oracle11g镜像 docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g创建11g容器 docker run -d -p 1521:1521 --name oracle11g registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g查看容器是否创建成功 docker ps -a导出oracle容器&#xff0c;查看…...

web前端3.19

一、属性选择器与伪类选择器 属性选择器&#xff1a;匹配那些具有特定属性或属性值的元素 <style>/* */input[type"password"] {background-color: aqua;}/* 具有某个属性的指定标签 */div[title] {background-color: pink;}/* 属性的值中包含某个值 */inpu…...

Rust 函数体内能定义数据类型或者做其他什么事情吗?

一、可以在函数体内定义数据类型吗&#xff1f; 在 Rust 中&#xff0c;你不能直接在函数体内定义新的数据类型&#xff08;如结构体或枚举&#xff09;。数据类型必须在模块或块的作用域内定义&#xff0c;这通常是在函数外部。然而&#xff0c;你可以在函数体内定义新的类型…...

flask_restful结合蓝图使用

在蓝图中&#xff0c;如果使用 Flask_RESTful &#xff0c; 创建 Api 对象的时候&#xff0c;传入蓝图对象即可&#xff0c;不再是传入 app 对象 /user/__init__.py from flask.blueprints import Blueprintuser_bp Blueprint(user,__name__)from user import views /user…...

干货分享之反射笔记

入门级笔记-反射 一、利用反射破泛型集合二、Student类三、获取构造器的演示和使用1.getConstructors只能获取当前运行时类的被public修饰的构造器2.getDeclaredConstructors:获取运行时类的全部修饰符的构造器3.获取指定的构造器3.1得到空构造器3.2得到两个参数的有参构造器&a…...

使用小皮【phpstudy】运行Vue+MySql项目

现在的情况是我扒到了一个开源的项目&#xff0c;现在想要实现一下前端对应的功能&#xff0c;后端是完备的&#xff0c;但是需要调用数据库将数据跑起来&#xff0c;这里可以使用到MySql数据库&#xff0c;这里我还发现了一个比较好用的软件小皮【phpStudy】 官网 一 安装软件…...

局部静态变量实现单例模式,线程安全(推荐使用)c++11

class Singleton{ public:~Singleton();static Singleton& getInstance(){static Singleton instance;return instance; } private:Singleton(); };原因是C 11标准中新增了一个特性叫Magic Static&#xff1a;如果变量在初始化时&#xff0c;并发线程同时进入到static声明语…...

Machine Learning机器学习之决策树算法 Decision Tree(附Python代码)

目录 前言&#xff1a; 一、决策树思想 二、经典决策树算法 三、算法应用案列 基于Python 和 Scikit-learn 库实现决策树算法的简单示例代码&#xff0c;用于解决分类问题&#xff1a; ​四、总结 算法 决策树算法应用&#xff1a; 决策树算法优缺点&#xff1a; 博主介绍&…...

Mybatis-Plus——09,代码自动生成器

代码自动生成器 一、先创建一个表二、创建一个类&#xff0c;配置代码生成器三、运行方法四、运行主方法&#xff0c;报错了。 一、先创建一个表 二、创建一个类&#xff0c;配置代码生成器 package com.gang;import com.baomidou.mybatisplus.annotation.DbType; import com.…...

Temu api接口 获取商品详情 数据采集

iDataRiver平台 https://www.idatariver.com/zh-cn/ 提供开箱即用的Temu电商数据采集API&#xff0c;供用户按需调用。 接口使用详情请参考Temu接口文档 接口列表 1. 获取商品详情 参数类型是否必填默认值示例值描述apikeystring是idr_***从控制台里复制apikeycountrystrin…...

安捷伦Agilent N1912A功率计

181/2461/8938产品概述&#xff1a; Keysight(原Agilent) N1912A P系列双通道功率计可提供峰值、峰均比、平均功率、上升时间、下降时间、最大功率值、最小功率值以及宽带信号的统计数据。 Keysight(原Agilent) N1912A P系列双通道功率计, 可提供峰值、峰均比、平均功率、上升…...

ES 进阶知识

索引Index 一个索引就是一个拥有几分相似特征的文档的集合。比如说&#xff0c;你可以有一个客户数据的索引&#xff0c;另一个产品目录的索引&#xff0c;还有一个订单数据的索引。一个索引由一个名字来标识&#xff08;必须全部是小写字母&#xff09;&#xff0c;并且当我们…...

ChatGPT 对 ELT的理解

本文主要内容来自 ChatGPT 4.0 到底什么是 ETL&#xff1f;在数据库内部&#xff0c;把数据从 ODS 层加工成 DWD&#xff0c;再加工成 DWS&#xff0c;这个过程和 ETL 的关系是什么&#xff1f;带着这些问题&#xff0c;我问了一下 ChatGPT&#xff0c;总结如下。 数据在两个数…...

qt事件机制学习笔记

实现闹钟功能 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), speecher(new QTextToSpeech(this)) //给语音播报者实例化空间 {ui->setupUi(this); }Widget::~Widget() {delete …...

网红电商主播培养体系招聘管理制度孵化方案

【干货资料持续更新&#xff0c;以防走丢】 网红电商主播培养体系招聘管理制度孵化方案 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 共120页可编辑&#xff08;完整资料包含以下内容&#xff09; 目录 主播团队组建方案 让好主播主动留下 1. 好主播选拔标准…...

Android获取经纬度的最佳实现方式

Android中获取定位信息的方式有很多种&#xff0c;系统自带的LocationManager&#xff0c;以及第三方厂商提供的一些定位sdk&#xff0c;都能帮助我们获取当前经纬度&#xff0c;但第三方厂商一般都需要申请相关的key&#xff0c;且调用量高时&#xff0c;还会产生资费问题。这…...

芒果YOLOv8改进137:主干篇CSPNeXt,小目标检测专用,COCO数据集验证,协调参数量和计算量的均衡,即插即用 | 打造高性能检测

该专栏完整目录链接: 芒果YOLOv8深度改进教程 芒果专栏 本篇基于 CSPNeXt 的改进结构,改进源码教程 | 详情如下🥇 本博客 CSPNeXt 改进 适用于 YOLOv8 按步骤操作运行改进后的代码即可 即插即用 结构,博客包括改进所需的 核心结构代码 文件 重点:🔥🔥🔥YOLOv8 …...

【测试开发学习历程】认识Python + 安装Python

目录 1 认识 Python 1.1 Python 的起源 1.2 Python的组成 1.2.1 解释器 1.1.2 Python 的设计目标 1.1.3 Python 的设计哲学 1.2 为什么选择 Python 测试人员选择Python的理由 1.3 Python 特点 面向对象的思维方式 1.4 Python 的优缺点 1.4.1 优点 1.4.2 缺点 3. 安…...

webpack proxy工作原理?为什么能解决跨域?

一、是什么 webpack proxy&#xff0c;即webpack提供的代理服务 基本行为就是接收客户端发送的请求后转发给其他服务器 其目的是为了便于开发者在开发模式下解决跨域问题&#xff08;浏览器安全策略限制&#xff09; 想要实现代理首先需要一个中间服务器&#xff0c;webpac…...

ArkTS编写的HarmonyOS原生聊天UI框架

简介 ChatUI&#xff0c;是一个ArkTS编写的HarmonyOS原生聊天UI框架&#xff0c;提供了开箱即用的聊天对话组件。 下载安装 ohpm install changwei/chatuiOpenHarmony ohpm 环境配置等更多内容&#xff0c;请参考如何安装 OpenHarmony ohpm 包 接口和属性列表 接口列表 接…...