SpringBoot中接口幂等性实现方案-自定义注解+Redis+拦截器实现防止订单重复提交

场景

SpringBoot+Redis+自定义注解实现接口防刷(限制不同接口单位时间内最大请求次数):

SpringBoot+Redis+自定义注解实现接口防刷(限制不同接口单位时间内最大请求次数)_redis防刷_霸道流氓气质的博客-CSDN博客

以下接口幂等性的实现方式与上面博客类似,可参考。

接口幂等性

什么是幂等性?

幂等是一个数学与计算机学概念,在数学中某一元运算为幂等时,其作用在任一元素两次后

会和其作用一次的结果相同。在计算机中编程中,一个幂等操作的特点是其任意多次执行所

产生的影响均与一次执行的影响相同。幂等函数或幂等方法是指可以使用相同参数重复执行,

并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

什么是接口幂等性?

在HTTP/1.1中,对幂等性进行了定义。它描述了一次和多次请求某一个资源对于资源本身应该具有

同样的结果(网络超时等问题除外),即第一次请求的时候对资源产生了副作用,但是以后的多次请求

都不会再对资源产生副作用。这里的副作用是不会对结果产生破坏或者产生不可预料的结果。

也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

为什么需要实现幂等性?

在接口调用时一般情况下都能正常返回信息不会重复提交,不过在遇见以下情况时可以就会出现问题,

如:

前端重复提交表单:

在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,

致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。

用户恶意进行刷单:

例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到

用户重复提交的投票信息,这样会使投票结果与事实严重不符。

接口超时重复提交:

很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络

波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。

消息进行重复消费:

当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。

使用幂等性最大的优势在于使接口保证任何幂等性操作,免去因重试等造成系统产生的未知的问题。

实现接口幂等性的方案有很多,下面记录一种自定义注解加拦截器和redis的实现方式模拟防止订单重复提交。

注:

博客:
霸道流氓气质_C#,架构之路,SpringBoot-CSDN博客

实现

1、新建SpringBoot项目并添加必要的依赖

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--MySQL驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--MyBatis整合SpringBoot框架的起步依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.1</version></dependency><!-- redis 缓存操作 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 阿里JSON解析器 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.75</version></dependency></dependencies>

2、修改配置文件yml添加相关配置

# 开发环境配置
server:# 服务器的HTTP端口,默认为8080port: 996servlet:# 应用的访问路径context-path: /tomcat:# tomcat的URI编码uri-encoding: UTF-8# tomcat最大线程数,默认为200max-threads: 800# Tomcat启动初始化的线程数,默认值25min-spare-threads: 30# 数据源
spring:application:name: badao-tcp-demodatasource:url:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverdbcp2:min-idle: 5                                # 数据库连接池的最小维持连接数initial-size: 5                            # 初始化连接数max-total: 5                               # 最大连接数max-wait-millis: 150                       # 等待连接获取的最大超时时间# redis 配置redis:# 地址#本地测试用host: 127.0.0.1port: 6379password: 123456# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 8# 连接池的最大数据库连接数max-active: 8# #连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms# mybatis配置
mybatis:mapper-locations: classpath:mapper/*.xml    # mapper映射文件位置type-aliases-package: com.badao.demo.entity    # 实体类所在的位置configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   #用于控制台打印sql语句


3、新建Redis的两个配置类

RedisConfig:

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;/*** redis配置**/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })@Primarypublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);serializer.setObjectMapper(mapper);template.setValueSerializer(serializer);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.afterPropertiesSet();return template;}
}

FastJson2JsonRedisSerializer:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.util.Assert;import java.nio.charset.Charset;/*** Redis使用FastJson序列化**/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{@SuppressWarnings("unused")private ObjectMapper objectMapper = new ObjectMapper();public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");private Class<T> clazz;static{ParserConfig.getGlobalInstance().setAutoTypeSupport(true);}public FastJson2JsonRedisSerializer(Class<T> clazz){super();this.clazz = clazz;}@Overridepublic byte[] serialize(T t) throws SerializationException{if (t == null){return new byte[0];}return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);}@Overridepublic T deserialize(byte[] bytes) throws SerializationException{if (bytes == null || bytes.length <= 0){return null;}String str = new String(bytes, DEFAULT_CHARSET);return JSON.parseObject(str, clazz);}public void setObjectMapper(ObjectMapper objectMapper){Assert.notNull(objectMapper, "'objectMapper' must not be null");this.objectMapper = objectMapper;}protected JavaType getJavaType(Class<?> clazz){return TypeFactory.defaultInstance().constructType(clazz);}
}

4、新建Redis工具类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;/*** spring redis 工具类***/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{@Autowiredpublic RedisTemplate redisTemplate;@Autowiredpublic StringRedisTemplate stringRedisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @return 缓存的对象*/public <T> ValueOperations<String, T> setCacheObject(String key, T value){ValueOperations<String, T> operation = redisTemplate.opsForValue();operation.set(key, value);return operation;}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度* @return 缓存的对象*/public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit){ValueOperations<String, T> operation = redisTemplate.opsForValue();operation.set(key, value, timeout, timeUnit);return operation;}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public void deleteObject(String key){redisTemplate.delete(key);}/*** 删除集合对象** @param collection*/public void deleteObject(Collection collection){redisTemplate.delete(collection);}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList){ListOperations listOperation = redisTemplate.opsForList();if (null != dataList){int size = dataList.size();for (int i = 0; i < size; i++){listOperation.leftPush(key, dataList.get(i));}}return listOperation;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(String key){List<T> dataList = new ArrayList<T>();ListOperations<String, T> listOperation = redisTemplate.opsForList();Long size = listOperation.size(key);for (int i = 0; i < size; i++){dataList.add(listOperation.index(key, i));}return dataList;}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet){BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(String key){Set<T> dataSet = new HashSet<T>();BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key);dataSet = operation.members();return dataSet;}/*** 缓存Map** @param key* @param dataMap* @return*/public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap){HashOperations hashOperations = redisTemplate.opsForHash();if (null != dataMap){for (Map.Entry<String, T> entry : dataMap.entrySet()){hashOperations.put(key, entry.getKey(), entry.getValue());}}return hashOperations;}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(String key){Map<String, T> map = redisTemplate.opsForHash().entries(key);return map;}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(String pattern){return redisTemplate.keys(pattern);}/*** 如果redis中不存在则存储进redis* @return*/public Boolean setIfAbsent(Object key,Object value,long timeout){return redisTemplate.opsForValue().setIfAbsent(key, value,timeout, TimeUnit.SECONDS);}
}

5、新建自定义注解类Idempotent

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {/*** 用于拼接幂等性判断的key的入参字段* @return*/String[] fields();/*** 用于接口幂等性校验的Redis中Key的过期时间,单位秒* @return*/long timeout() default 10l;
}

6、创建自定义拦截器IdempotentInterceptor

import com.badao.demo.annotation.Idempotent;
import com.badao.demo.utils.RedisCache;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;@Component
public class IdempotentInterceptor implements HandlerInterceptor {@Resourceprivate RedisCache redisCache;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Idempotent idempotent = ((HandlerMethod)handler).getMethodAnnotation(Idempotent.class);if(idempotent == null){return true;}String idempotentKey = this.idempotentKey(idempotent,request);Boolean success = redisCache.setIfAbsent(idempotentKey,1, idempotent.timeout());if(Boolean.FALSE.equals(success)){render(response,"请勿重复请求");return false;}return true;}private String idempotentKey(Idempotent idempotent,HttpServletRequest request){String[] fields = idempotent.fields();StringBuilder idempotentKey = new StringBuilder();for (String field : fields) {String parameter = request.getParameter(field);idempotentKey.append(parameter);}return idempotentKey.toString();}/*** 接口渲染* @param response* @throws Exception*/private void render(HttpServletResponse response,String message)throws Exception {response.setContentType("application/json;charset=UTF-8");OutputStream out = response.getOutputStream();out.write(message.getBytes("UTF-8"));out.flush();out.close();}
}

7、配置拦截器IdempotentInterceptor注册到SpringMVC的拦截器链中

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import javax.annotation.Resource;/*** 配置拦截器IdempotentInterceptor注册到SpringMVC的拦截器链中*/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {@Resourceprivate IdempotentInterceptor idempotentInterceptor;@Overrideprotected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(idempotentInterceptor);}
}

8、创建测试接口并添加自定义注解

import com.badao.demo.annotation.Idempotent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TestController {@Idempotent(fields = {"orderId"},timeout = 10)@GetMapping("/test")public String test(@RequestParam("orderId") String orderId, String remark){return "success";}
}

这里指定orderId为订单唯一ID参数,并设置在10秒内如果此字段相同的请求会被拦截

9、创建测试方法

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;@SpringBootTest
class IdempotenceTest {@Autowiredprivate WebApplicationContext webApplicationContext;@Testvoid test1() throws Exception {//初始化MockMvcMockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();//循环调用5次进行测试for (int i = 1; i <= 5; i++) {System.out.println("第"+i+"次调用接口");//调用接口String result = mockMvc.perform(MockMvcRequestBuilders.get("/test").accept(MediaType.TEXT_HTML).param("orderId","001").param("remark","badao")).andReturn().getResponse().getContentAsString();System.out.println(result);}}
}

测试结果

使用postman等接口测试工具复测验证

 

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

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

相关文章

python pygbag教程 —— 在网页上运行pygame程序(全网中文教程首发)

pygame是一款流行的游戏制作模块&#xff0c;经过特殊的方式编译后&#xff0c;可以在浏览器web网页上运行。web上的打包主要使用第三方模块pygbag。 pygame教程&#xff1a;Python pygame(GUI编程)模块最完整教程&#xff08;1&#xff09;_pygame模块详解_Python-ZZY的博客-…

【配置环境】Windows下 VS Code 远程连接虚拟机Ubuntu

一&#xff0c;环境 Windows 11 家庭中文版VMware Workstation 16 Pro &#xff08;版本&#xff1a;16.1.2 build-17966106&#xff09;ubuntu-22.04.2-desktop-amd64 二&#xff0c;关键步骤 Windows下安装OpenSSHVS Code安装Remote - SSH插件 三&#xff0c;详细步骤 在Ubun…

React 前端应用中快速实践 OpenTelemetry 云原生可观测性(SigNoz/K8S)

OpenTelemetry 可用于跟踪 React 应用程序的性能问题和错误。您可以跟踪从前端 web 应用程序到下游服务的用户请求。OpenTelemetry 是云原生计算基金会(CNCF)下的一个开源项目&#xff0c;旨在标准化遥测数据的生成和收集。已成为下一代可观测平台的事实标准。 React(也称为 Re…

Kotlin 内联函数语法之let、apply、also、run、with的用法与详解

一、介绍 kotlin的语法千奇百怪&#xff0c;今天我们将介绍项目中频率使用比较高的几个内联函数。 二、什么叫内联函数&#xff1f; 内联函数 的语义很简单&#xff1a;把函数体复制粘贴到函数调用处 。使用起来也毫无困难&#xff0c;用 inline关键字修饰函数即可。 语法&a…

详解zookeeper安装使用

目录 1.概述 1.1.功能 1.2.特点 1.3.数据结构 2.安装 2.1.Windows 2.2.Linux 3.基础操作 3.1.增 3.2.删 3.3.改 3.4.查 3.5.监听 4.JAVA操作Zookeeper 4.1.依赖 4.2.客户端 4.3.增 4.4.删 4.5.查 4.6.改 1.概述 1.1.功能 zookeeper&#xff0c;Apache旗下…

pdf转换word软件哪个好?式?这款软件帮你轻松实现转换

在工作中&#xff0c;我们常常遇到这样的情况&#xff1a;我们的文件可能是PDF格式的&#xff0c;但对方要求我们以Word形式发送&#xff0c;因为Word相对于PDF占用更小的内存&#xff0c;打开更方便&#xff0c;发送时间更短。这时我们需要将PDF转换为Word格式&#xff0c;然而…

【跨代码仓库合并方案】

1、背景&#xff1a; 1、wiser绑定的uiidA的定制修改内容和ELKO绑定的uiidB基本是一样的&#xff0c;需要手动粘贴同步&#xff0c;增加测试保障风险&#xff0c;还会浪费开发资源投入&#xff1b; 2、施耐德wiser和elko面板两套面板基本一致&#xff0c;但是经过new art升级后…

机器学习深度学习——感知机

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——softmax回归的简洁实现 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们…

市面上的ipad国产触控笔怎么样?精选的性价比电容笔

要知道&#xff0c;真正的苹果品牌的那款原装电容笔&#xff0c;光是一支电容笔就价格近千元。实际上&#xff0c;平替电容笔对没有太多预算的用户是个不错的选择。一支苹果品牌的电容笔&#xff0c;价格是平替品牌的四倍&#xff0c;但电容笔的书写效果&#xff0c;却丝毫不逊…

科技云报道:是时候全员FinOps了吗?

科技云报道原创。 在论坛上&#xff0c;国外某企业的真实案例引发了热议。一开始该企业只顾技术创新&#xff0c;积极上云&#xff0c;不顾成本。 直到有一天&#xff0c;高层介入喊停&#xff1a;“这个云不能再上了&#xff0c;成本已经远大于收益了”。该企业因为成本失控…

java-day01

一&#xff1a;基础常识 软件&#xff1a;按照特定顺序的计算机数据与指令的集合。可分为系统软件&#xff08;如操作系统&#xff09;和应用软件&#xff08;如QQ&#xff09; 人机交互方式&#xff1a;图形化界面&#xff08;GUI&#xff09;与命令行&#xff08;CLI&#…

性能优化 - 前端性能监控和性能指标计算方式

性能优化 - 前端性能监控和性能指标计算方式 前言一. 性能指标介绍1.1 单一指标介绍1.2 指标计算① Redirect(重定向耗时)② AppCache(应用程序缓存的DNS解析)③ DNS(DNS解析耗时)④ TCP(TCP连接耗时)⑤ TTFB(请求响应耗时)⑥ Trans(内容传输耗时)⑦ DOM(DOM解析耗时) 1.3 FP(f…

代码随想录算法训练营第二天| 977

977. 有序数组的平方y 思路&#xff0c;原数组是有序的&#xff0c;但是因为负数平方后可能变无序了&#xff0c;因此利用双指针遍历原数组&#xff0c;比较 nums[left]*nums[left]和nums[right]*nums[right]谁更大&#xff0c;然后对新数组赋值 class Solution {public int…

MFC第二十四天 使用GDI对象画笔和画刷来开发控件(分页控件选择态的算法分析、使用CToolTipCtrl开发动静态提示)

文章目录 GDI对象画笔和画刷来开发控件梯形边框的按钮控件CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp 矩形边框的三态按钮控件 CToolTipCtrl开发动静态提示CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp: 实现文件 矩形边框的三态按钮控件 CToolTipCtrl开发动…

欢乐暑假,华为儿童手表5系列为孩子位置安全保驾护航!

暑假带娃&#xff0c;就像爸妈的练兵场。幸好有 5 系列&#xff0c;离线定位、位置提醒、行为记录等安全守护功能面面俱到、样样精通&#xff0c;陪伴孩子度过悠长假期&#xff0c;也让爸妈长辈更安心更省力&#xff5e; 暑期到了&#xff0c;小朋友们都想出去玩&#xff0c;但…

修改密码和再次确认密码的js和element-ui的使用

<template><div><!-- plan的插槽 --><plan title"修改密码"><!-- 插槽的名字 --><span slot"header">修改密码</span><el-form:model"ruleForm2"status-icon:rules"rules2"ref"rul…

【数据结构】实验七:字符串

实验七 字符串实验报告 一、实验目的与要求 1&#xff09;巩固对串的理解&#xff1b; 2&#xff09;掌握串的基本操作实现&#xff1b; 3&#xff09;掌握 BF 和 KMP 算法思想。 二、实验内容 1. 给定一个字符串ababcabcdabcde和一个子串abcd,查找字串是否在主串中出现。…

【数据结构】实验一:绪论

实验一 绪论 一、实验目的与要求 1&#xff09;熟悉C/C语言&#xff08;或其他编程语言&#xff09;的集成开发环境&#xff1b; 2&#xff09;通过本实验加深对算法时间复杂度的理解&#xff1b; 3&#xff09;结合具体的问题分析算法时间复杂度。 二、实验内容 设计程…

深入学习 redis - Stream、Geospatial、HyperLogLog、Bitmap、Bitfields 类型扩展

目录 前言 Stream geospatial HyperLogLog Bitmaps Bitfields 前言 redis 中最关键的五个数据类型 String、List、Hash、Set、Zset 应用最广泛&#xff0c;同时 redis 也推出了额外的 5 个数据类型&#xff0c;他们分别是针对特殊场景才进行的应用的. Ps&#xff1a;这几种…

odoo16-domain

odoo16-domain 参考:https://blog.csdn.net/u013250491/article/details/86699928 domain的使用注意以下几点: 是在py文件中使用还是在xml中使用,py文件是在后端使用可以利用orm, 而xml是在前端渲染,使用的是js,没有办法使用orm如果在xml中使用,domain的格式建议为[[]], 二维…