【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读

news2024/9/23 1:41:41

项目地址

项目地址 https://toscode.gitee.com/nepxion/Aquarius

项目介绍

Nepxion Aquarius是一款基于Redis + Zookeeper的分布式应用组件集合,包含分布式锁,缓存,ID生成器,限速限流器。它采用Nepxion Matrix AOP框架进行切面架构,提供注解调用方式,也提供API调用方式

分布式缓存

Maven依赖

引入依赖

分布式缓存
<dependency>
    <groupId>com.nepxion</groupId>
    <artifactId>aquarius-cache-starter</artifactId>
    <version>${aquarius.version}</version>
</dependency>

源码解析

aquarius-cache-starter项目下spring.factories配置,Nepxion自己实现了自动配置。

com.nepxion.aquarius.cache.annotation.EnableCache=\
com.nepxion.aquarius.cache.configuration.CacheConfiguration

EnableCache注入了CacheImportSelector

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(CacheImportSelector.class)
public @interface EnableCache {

}

CacheImportSelector,当isEnabled方法返回true,会注入spring.factories键是EnableCache对应的类,即CacheConfiguration

@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class CacheImportSelector extends AbstractImportSelector<EnableCache> {
    @Override
    protected boolean isEnabled() {
        return new RelaxedPropertyResolver(getEnvironment()).getProperty(CacheConstant.CACHE_ENABLED, Boolean.class, Boolean.TRUE);
    }
}

CacheConfiguration注入了CacheAopConfigurationRedisCacheConfiguration

@Configuration
@Import({ AquariusConfiguration.class, CacheAopConfiguration.class, RedisCacheConfiguration.class })
public class CacheConfiguration {

}

RedisCacheConfiguration会根据条件生成CacheDelegateRedisHandler

@Configuration
@Import({ RedisConfiguration.class })
public class RedisCacheConfiguration {
    @Bean
    @Conditional(RedisCacheCondition.class)
    public CacheDelegate redisCacheDelegate() {
        return new RedisCacheDelegateImpl();
    }

    @Bean
    @Conditional(RedisCacheCondition.class)
    @ConditionalOnMissingBean
    public RedisHandler redisHandler() {
        return new RedisHandlerImpl();
    }
}

RedisCacheCondition

public class RedisCacheCondition extends AquariusCondition {
    public RedisCacheCondition() {
        super(CacheConstant.CACHE_TYPE, CacheConstant.CACHE_TYPE_REDIS);
    }
}

AquariusCondition会根据设置的键值对匹配环境变量,匹配条件为真。

public class AquariusCondition implements Condition {
    private String key;
    private String value;

    public AquariusCondition(String key, String value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String beanName = context.getEnvironment().getProperty(key);

        return StringUtils.equals(beanName, value);
    }
}

CacheAopConfiguration会注入CacheAutoScanProxyCacheInterceptor

@Configuration
public class CacheAopConfiguration {
    @Value("${" + CacheConstant.CACHE_SCAN_PACKAGES + ":}")
    private String scanPackages;

    @Bean
    public CacheAutoScanProxy cacheAutoScanProxy() {
        return new CacheAutoScanProxy(scanPackages);
    }

    @Bean
    public CacheInterceptor cacheInterceptor() {
        return new CacheInterceptor();
    }
}

CacheAutoScanProxy根据方法匹配,拦截 Cacheable.class, CachePut.class, CacheEvict.class注解标注的方法,用cacheInterceptor来获取拦截器。

public class CacheAutoScanProxy extends DefaultAutoScanProxy {
    private static final long serialVersionUID = 5099476398968133135L;

    private String[] commonInterceptorNames;

    @SuppressWarnings("rawtypes")
    private Class[] methodAnnotations;

    public CacheAutoScanProxy(String scanPackages) {
        super(scanPackages, ProxyMode.BY_METHOD_ANNOTATION_ONLY, ScanMode.FOR_METHOD_ANNOTATION_ONLY);
    }

    @Override
    protected String[] getCommonInterceptorNames() {
        if (commonInterceptorNames == null) {
            commonInterceptorNames = new String[] { "cacheInterceptor" };
        }

        return commonInterceptorNames;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Class<? extends Annotation>[] getMethodAnnotations() {
        if (methodAnnotations == null) {
            methodAnnotations = new Class[] { Cacheable.class, CachePut.class, CacheEvict.class };
        }

        return methodAnnotations;
    }
}

CacheInterceptor获取对应的缓存注解,执行对应的方法。如果局部变量没配置,取全局变量值。

public class CacheInterceptor extends AbstractInterceptor {
    private static final Logger LOG = LoggerFactory.getLogger(CacheInterceptor.class);

    @Autowired
    private CacheDelegate cacheDelegate;

    @Value("${" + AquariusConstant.PREFIX + "}")
    private String prefix;

    @Value("${" + AquariusConstant.FREQUENT_LOG_PRINT + ":false}")
    private Boolean frequentLogPrint;

    @Value("${" + CacheConstant.CACHE_EXPIRE + ":-1}")
    private long expiration;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Cacheable cacheableAnnotation = getCacheableAnnotation(invocation);
        if (cacheableAnnotation != null) {
            String name = cacheableAnnotation.name();
            String[] keys = cacheableAnnotation.key();
            long expire = cacheableAnnotation.expire();
            // 如果局部变量没配置,取全局变量值
            if (expire == -1234567890L) {
                expire = expiration;
            }

            return invokeCacheable(invocation, name, keys, expire);
        }

        CachePut cachePutAnnotation = getCachePutAnnotation(invocation);
        if (cachePutAnnotation != null) {
            String name = cachePutAnnotation.name();
            String[] keys = cachePutAnnotation.key();
            long expire = cachePutAnnotation.expire();
            // 如果局部变量没配置,取全局变量值
            if (expire == -1234567890L) {
                expire = expiration;
            }

            return invokeCachePut(invocation, name, keys, expire);
        }

        CacheEvict cacheEvictAnnotation = getCacheEvictAnnotation(invocation);
        if (cacheEvictAnnotation != null) {
            String name = cacheEvictAnnotation.name();
            String[] keys = cacheEvictAnnotation.key();
            boolean allEntries = cacheEvictAnnotation.allEntries();
            boolean beforeInvocation = cacheEvictAnnotation.beforeInvocation();

            return invokeCacheEvict(invocation, name, keys, allEntries, beforeInvocation);
        }

        return invocation.proceed();
    }
}

分布式锁

Maven依赖

引入依赖

分布式锁
<dependency>
    <groupId>com.nepxion</groupId>
    <artifactId>aquarius-lock-starter</artifactId>
    <version>${aquarius.version}</version>
</dependency>

aquarius-lock-starter包下面spring.factories引入LockConfiguration

com.nepxion.aquarius.lock.annotation.EnableLock=\
com.nepxion.aquarius.lock.configuration.LockConfiguration

LockConfiguration会注入LockAopConfigurationRedisLockConfigurationLocalLockConfiguration

@Configuration
@Import({ AquariusConfiguration.class, LockAopConfiguration.class, RedisLockConfiguration.class, ZookeeperLockConfiguration.class, LocalLockConfiguration.class })
public class LockConfiguration {

}

LocalLockConfiguration根据条件注入了LockDelegateLockExecutor

@Configuration
public class LocalLockConfiguration {
    @Bean
    @Conditional(LocalLockCondition.class)
    public LockDelegate localLockDelegate() {
        return new LocalLockDelegateImpl();
    }

    @Bean
    @Conditional(LocalLockCondition.class)
    public LockExecutor<Lock> localLockExecutor() {
        return new LocalLockExecutorImpl();
    }
}

本地锁的实现是由ReentrantLock实现,Redis锁是由Redisson实现的。

分布式限流

分布式限速限流
<dependency>
    <groupId>com.nepxion</groupId>
    <artifactId>aquarius-limit-starter</artifactId>
    <version>${aquarius.version}</version>
</dependency>

源码解析

aquarius-limit-starter包下面spring.factories引入LimitConfiguration

@Configuration
@Import({ AquariusConfiguration.class, LimitAopConfiguration.class, RedisLimitConfiguration.class, LocalLimitConfiguration.class })
public class LimitConfiguration {

}

LimitAopConfiguration定义了拦截@Limit注解,使用LimitInterceptor拦截

@Configuration
public class LimitAopConfiguration {
    @Value("${" + LimitConstant.LIMIT_SCAN_PACKAGES + ":}")
    private String scanPackages;

    @Bean
    public LimitAutoScanProxy limitAutoScanProxy() {
        return new LimitAutoScanProxy(scanPackages);
    }

    @Bean
    public LimitInterceptor limitInterceptor() {
        return new LimitInterceptor();
    }
}

LimitInterceptor获取注解信息,使用LimitDelegate进行处理。

public class LimitInterceptor extends AbstractInterceptor {
    private static final Logger LOG = LoggerFactory.getLogger(LimitInterceptor.class);

    @Autowired
    private LimitDelegate limitDelegate;

    @Value("${" + AquariusConstant.PREFIX + "}")
    private String prefix;

    @Value("${" + AquariusConstant.FREQUENT_LOG_PRINT + ":false}")
    private Boolean frequentLogPrint;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Limit limitAnnotation = getLimitAnnotation(invocation);
        if (limitAnnotation != null) {
            String name = limitAnnotation.name();
            String key = limitAnnotation.key();
            int limitPeriod = limitAnnotation.limitPeriod();
            int limitCount = limitAnnotation.limitCount();

            return invoke(invocation, limitAnnotation, name, key, limitPeriod, limitCount);
        }

        return invocation.proceed();
    }
    
        private Object invoke(MethodInvocation invocation, Annotation annotation, String name, String key, int limitPeriod, int limitCount) throws Throwable {
        if (StringUtils.isEmpty(name)) {
            throw new AquariusException("Annotation [Limit]'s name is null or empty");
        }

        if (StringUtils.isEmpty(key)) {
            throw new AquariusException("Annotation [Limit]'s key is null or empty");
        }

        String spelKey = null;
        try {
            spelKey = getSpelKey(invocation, key);
        } catch (Exception e) {
            spelKey = key;
        }
        String compositeKey = KeyUtil.getCompositeKey(prefix, name, spelKey);
        String proxyType = getProxyType(invocation);
        String proxiedClassName = getProxiedClassName(invocation);
        String methodName = getMethodName(invocation);

        if (frequentLogPrint) {
            LOG.info("Intercepted for annotation - Limit [key={}, limitPeriod={}, limitCount={}, proxyType={}, proxiedClass={}, method={}]", compositeKey, limitPeriod, limitCount, proxyType, proxiedClassName, methodName);
        }

        return limitDelegate.invoke(invocation, compositeKey, limitPeriod, limitCount);
    }
}

LocalLimitConfiguration注入了LimitDelegateLimitExecutorLimitDelegate就是调用LimitExecutor#tryAccess

@Configuration
public class LocalLimitConfiguration {
    @Bean
    @Conditional(LocalLimitCondition.class)
    public LimitDelegate localLimitDelegate() {
        return new LocalLimitDelegateImpl();
    }

    @Bean
    @Conditional(LocalLimitCondition.class)
    public LimitExecutor localLimitExecutor() {
        return new GuavaLocalLimitExecutorImpl();

        // return new JdkLimitExecutorImpl();
    }
}

GuavaLocalLimitExecutorImpl#tryAccess(java.lang.String, int, int),根据key获取RateLimiterEntity,从而获取到封装的RateLimiter,进行缓存。封装主要是为了修改rate的时候,不需要重新生成RateLimiter

    public boolean tryAccess(String compositeKey, int limitPeriod, int limitCount) throws Exception {
        if (StringUtils.isEmpty(compositeKey)) {
            throw new AquariusException("Composite key is null or empty");
        }
        
        if (limitPeriod != 1) {
            throw new AquariusException("Limit period must be 1 second for Guava rate limiter");
        }

        RateLimiterEntity rateLimiterEntity = getRateLimiterEntity(compositeKey, limitCount);
        RateLimiter rateLimiter = rateLimiterEntity.getRateLimiter();

        return rateLimiter.tryAcquire();
    }

    private RateLimiterEntity getRateLimiterEntity(String compositeKey, double rate) {
        RateLimiterEntity rateLimiterEntity = rateLimiterEntityMap.get(compositeKey);
        if (rateLimiterEntity == null) {
            RateLimiter newRateLimiter = RateLimiter.create(rate);

            RateLimiterEntity newRateLimiterEntity = new RateLimiterEntity();
            newRateLimiterEntity.setRateLimiter(newRateLimiter);
            newRateLimiterEntity.setRate(rate);

            rateLimiterEntity = rateLimiterEntityMap.putIfAbsent(compositeKey, newRateLimiterEntity);
            if (rateLimiterEntity == null) {
                rateLimiterEntity = newRateLimiterEntity;
            }
        } else {
            if (rateLimiterEntity.getRate() != rate) {
                rateLimiterEntity.getRateLimiter().setRate(rate);
                rateLimiterEntity.setRate(rate);
            }
        }

        return rateLimiterEntity;
    }

RedisLimitExecutorImpl#tryAccess(java.lang.String, int, int),Redis实现主要是通过执行lua脚本。

    @Override
    public boolean tryAccess(String compositeKey, int limitPeriod, int limitCount) throws Exception {
        if (StringUtils.isEmpty(compositeKey)) {
            throw new AquariusException("Composite key is null or empty");
        }

        List<String> keys = new ArrayList<String>();
        keys.add(compositeKey);

        RedisTemplate<String, Object> redisTemplate = redisHandler.getRedisTemplate();
        Number count = redisTemplate.execute(redisScript, keys, limitCount, limitPeriod);

        if (count == null) {
            return false;
        }

        if (frequentLogPrint) {
            LOG.info("Access try count is {} for key={}", count, compositeKey);
        }

        return count.intValue() <= limitCount;
    }

分布式全局唯一ID

<dependency>
    <groupId>com.nepxion</groupId>
    <artifactId>aquarius-id-generator-starter</artifactId>
    <version>${aquarius.version}</version>
</dependency>

aquarius-id-generator-starter项目中的spring.factories会根据注解注入LocalIdGeneratorConfigEnableRedisIdGenerator或者EnableZookeeperIdGenerator

com.nepxion.aquarius.idgenerator.annotation.EnableLocalIdGenerator=\
com.nepxion.aquarius.idgenerator.configuration.LocalIdGeneratorConfig

com.nepxion.aquarius.idgenerator.annotation.EnableRedisIdGenerator=\
com.nepxion.aquarius.idgenerator.configuration.RedisIdGeneratorConfig

com.nepxion.aquarius.idgenerator.annotation.EnableZookeeperIdGenerator=\
com.nepxion.aquarius.idgenerator.configuration.ZookeeperIdGeneratorConfig

LocalIdGeneratorConfig注入LocalIdGeneratorConfiguration

@Configuration
@Import({ AquariusConfiguration.class, LocalIdGeneratorConfiguration.class })
public class LocalIdGeneratorConfig {

}

LocalIdGeneratorConfiguration注入LocalIdGenerator

@Configuration
public class LocalIdGeneratorConfiguration {
    @Bean
    public LocalIdGenerator localIdGenerator() {
        return new LocalIdGeneratorImpl();
    }
}

LocalIdGeneratorImpl#nextUniqueId(long, long, long),获取ID生成器,执行nextId方法

    public String nextUniqueId(long startTimestamp, long dataCenterId, long machineId) throws Exception {
        String nextUniqueId = getIdGenerator(startTimestamp, dataCenterId, machineId).nextId();

        if (frequentLogPrint) {
            LOG.info("Next unique id is {} for startTimestamp={}, dataCenterId={}, machineId={}", nextUniqueId, startTimestamp, dataCenterId, machineId);
        }

        return nextUniqueId;
    }

    private SnowflakeIdGenerator getIdGenerator(long startTimestamp, long dataCenterId, long machineId) {
        String key = dataCenterId + "-" + machineId;

        SnowflakeIdGenerator idGenerator = idGeneratorMap.get(key);
        if (idGenerator == null) {
            SnowflakeIdGenerator newIdGnerator = new SnowflakeIdGenerator(startTimestamp, dataCenterId, machineId);
            idGenerator = idGeneratorMap.putIfAbsent(key, newIdGnerator);
            if (idGenerator == null) {
                idGenerator = newIdGnerator;
            }
        }

        return idGenerator;
    }
【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读 【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读 【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读

在这里插入图片描述
【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读
【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读
【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读

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

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

相关文章

天工开物 #6 Git 分支管理与版本发布

Git 版本管理系统由 Linux 的作者 Linus Torvalds 于 2005 年创造&#xff0c;至今不到二十年。 起初&#xff0c;Git 用于 Linux Kernel 的协同开发&#xff0c;用于替代不再提供免费许可的 BitKeeper 软件。随后&#xff0c;这一提供轻量级分支的分布式版本管理系统得到了开源…

产品经理被气的脸都绿了!

见字如面&#xff0c;我是军哥&#xff01; 本来今天不想发文了&#xff0c;想躺平一下&#xff0c;毕竟今天周五了嘛。 可是今天早上一位买了我《技术人核心能力》的程序员学员发来私信&#xff0c;说他给产品经理上了一课&#xff0c;声称产品经理当时脸都绿了&#xff0c;并…

浅浅的理解MVI

MVI 的概念 官网解释&#xff1a; https://developer.android.google.cn/topic/architecture?hlzh-cn MVI在架构分层上和MVP没有本质区别&#xff0c;但区别主要体现在架构风格和编程思想上。 MVI 是 Model-View-Intent 的缩写&#xff0c;它也是一种响应式 流式处理思想的…

【Linux高级 I/O(2)】如何使用阻塞 I/O 与非阻塞 I/O?——select()函数

上次我们虽然使用非阻塞式 I/O 解决了阻塞式 I/O 情况下并发读取文件所出现的问题&#xff0c;但依然不够完美&#xff0c;使得程序的 CPU 占用率特别高。解决这个问题&#xff0c;就要用到本文将要介绍的 I/O 多路复用方法。 何为 I/O 多路复用 I/O 多路复用&#xff08;IO m…

数据结构-时间复杂度和空间复杂度

时间复杂度和空间复杂度 算法效率时间复杂度空间复杂度表示方法&#xff08;大O的线性表示&#xff09;举例说明时间复杂度举例说明空间复杂度举例说明冒泡排序的时间和空间复杂度递归情况下的时间和空间复杂度两个例子 算法效率 算法&#xff08;Algorithn&#xff09;是指用来…

Spring MVC的核心类和注解

DispatcherServlet DispatcherServlet作用 DispatcherServlet是Spring MVC的核心类&#xff0c;也是Spring MVC的流程控制中心&#xff0c;也称为Spring MVC的前端控制器&#xff0c;它可以拦截客户端的请求。拦截客户端请求之后&#xff0c;DispatcherServlet会根据具体规则…

chatgpt赋能Python-python3接口自动化

Python3接口自动化&#xff1a;提升测试效率的利器 Python是一种高级编程语言&#xff0c;广泛应用于Web开发、数据科学、机器学习等领域。近年来&#xff0c;Python在接口自动化测试领域也变得越来越受欢迎。 Python的易读性、可扩展性以及模块化的特性&#xff0c;使得它成为…

Elasticsearch环境搭建(Windows)

一、介绍 布式、RESTful 风格的搜索和分析。 Elasticsearch 是位于 Elastic Stack 核心的分布式搜索和分析引擎。Logstash 和 Beats 有助于收集、聚合和丰富您的数据并将其存储在 Elasticsearch 中。Kibana 使您能够以交互方式探索、可视化和分享对数据的见解&#xff0c;并管…

手撕code(1)—— 排序算法

文章目录 前言1 冒泡排序2 选择排序3 插入排序4 快速排序5 归并排序6 堆排序7 希尔排序 前言 算法动画 时间复杂度分析 从小到大排序 1 冒泡排序 被动的将最大值送到最右边 1、比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。 2、对每一对相邻元素作同…

Vite+Vue+iClient for Leaflet引入并实现MapV/Eharts第三方可视化库示例

作者&#xff1a;gaogy 文章目录 背景一、使用Vite构建Vue3JavaScript项目二、搭建iClient for Leaflet开发环境三、第三方可视化库Echarts的使用四、第三方可视化库MapV的使用五、其他地图库 背景 最近很多小伙伴咨询关于基于Vue3框架搭建iClent开发环境并使用Echarts与MapV等…

OPEN AI角色插件通道开放接入支持各种细分领域对话场景模型一键接入AI 智能

相信还是有很多伙伴不了解OPEN AI平台 &#xff0c;这里在细说一下 大家知道ChatGPT, 或者百度文心一言 阿里通意千问 包括各种其他的AI 聊天或者画图&#xff0c;等应用层出不穷。 但是我们要自己实现自己 语言大模型&#xff0c;或者说是人工智能应用能不能。 有实力当然可…

C++小项目之文本编辑器mynote(1.0.0版本)

2023年5月19日&#xff0c;周五晚上&#xff1a; 今天晚上突然想写一个运行在命令行上的文本编辑器&#xff0c;因为平时写文本时老是要创建新的文本文件&#xff0c;觉得太麻烦了。捣鼓了一个晚上&#xff0c;才选出一个我觉得比较满意的。我把这个程序添加到了系统环境变量中…

C语言指针学习笔记

1-二维数组指针 int a[3][4]a代表二维数组首元素的地址&#xff0c;此首元素不是一个简单的整形元素&#xff0c;而是由4个整形元素组成的一维数组&#xff0c;因此a代表的是首行&#xff08;序号为0的行&#xff09;的起始地址。a1代表序号为1的行的起始地址。a指向a[0], …

一个月50场面试,跑的慢就抢在别人前面!

300万字&#xff01;全网最全大数据学习面试社区等你来&#xff01; 今天的主人公也是一个应届生新人拿到满意offer的案例。 下面是一些聊天记录和面经&#xff0c;这名同学做的非常好的一个点&#xff0c;他把个人项目中的所用到的技术栈和项目具体的业务流程图以及用到的技术…

2年再见面

我和张哥是在两年前吃过饭&#xff0c;那时候我是在大学城上班。 两年前&#xff0c;张哥在微信上跟我说话&#xff0c;说要来深圳找我&#xff0c;问我什么时间方便&#xff0c;请我吃个便饭。两年前&#xff0c;公众号还比较火热。有挺多人找我做事情&#xff0c;找我做事情之…

桥梁安全监测,智能化桥梁结构健康监测方案

桥梁是现代城市交通网络中不可或缺的组成部分&#xff0c;但由于长期受到自然环境和人为因素的影响&#xff0c;桥梁的安全问题一直备受关注。传统的桥梁检测方式主要是靠人力进行巡查&#xff0c;这种方式效率低下、成本高&#xff0c;而且难以全面掌握桥梁结构的真实情况。随…

回顾 | Let's Learn .NET-通过 Semantic Kernel .NET SDK 管理你的 OpenAI 项目

点击蓝字 关注我们 编辑&#xff1a;Alan Wang 排版&#xff1a;Rani Sun Lets Learn .NET 系列 “Lets Learn .NET” 是面向全球的 .NET 初学者学习系列&#xff0c;旨在通过不同语言&#xff0c;帮助不同地区的开发者掌握最新的 .NET 开发知识与技能。 在 ChatGPT 与 OpenAI…

从零玩转设计模式之简单工厂设计模式-jiandangonchangmoshi

title: 从零玩转设计模式之简单工厂设计模式 date: 2022-12-08 11:31:19.472 updated: 2022-12-11 23:03:34.805 url: https://www.yby6.com/archives/jiandangonchangmoshi categories: - 设计模式 tags: - 设计模式 简单工厂模式是一种创建型设计模式&#xff0c;用于创建单…

Docker安装MinIO教程

本章教程&#xff0c;主要介绍一下&#xff0c;如何在Linux用Docker安装MinIO。 MinIO是一个高性能、分布式对象存储系统&#xff0c;支持S3 API&#xff0c;适用于云原生环境。MinIO可以在标准硬件上运行&#xff0c;并且具有低延迟、高吞吐量、高可用性和可扩展性等优势。Min…

C语言两百行代码实现简易扫雷

文章目录 前言一.代码实现二.设计思路main()函数搭建框架reset ( )函数dis_play( )函数setmine( )函数player_move( )函数 前言 扫雷应该是我们接触到的第一个电脑游戏&#xff0c;用c语言实现扫雷对初学者来说是一个不错的锻炼 编写扫雷只需要用到数组、函数和生成随机数的知…