Abp vNext 二进制大对象系统(BLOB)

一、简介

ABP vNext 在 v 2.9.x 版本当中添加了 BLOB 系统,主要用于存储大型二进制文件。ABP 抽象了一套通用的 BLOB 体系,开发人员在存储或读取二进制文件时,可以忽略具体实现,直接使用IBlobContainer 或 IBlobContainer<T> 进行操作。官方的 BLOB Provider 实现有AzureAWSFileSystem(文件系统存储)Database(数据库存储)阿里云 OSS,你也可以自己继承 BlobProviderBase 来实现其他的 Provider。

BLOB 常用于各类二进制文件存储和管理,基本就是对云服务的 OSS 进行了抽象,在使用当中也会有 Bucket 和 Object Key 的概念,在 BLOB 里面对应的就是 ContainerName 和 BlobName。

关于 BLOB 的官方使用指南,可以参考 https://docs.abp.io/en/abp/latest/Blob-Storing,本文的阅读前提是建立在你已经阅读过该指南,并有一定的使用经验。

二、源码分析

2.1 模块分析

看一个 ABP 的库项目,首先从他的 Module 入手,对应的 BLOB 核心库的 Module 就是 AbpBlobStoringModule 类,在其内部,只进行了两个操作,注入了 IBlobContainer 与 IBlobContainer<> 的实现。

public override void ConfigureServices(ServiceConfigurationContext context)
{context.Services.AddTransient(typeof(IBlobContainer<>),typeof(BlobContainer<>));context.Services.AddTransient(typeof(IBlobContainer),serviceProvider => serviceProvider.GetRequiredService<IBlobContainer<DefaultContainer>>());
}

从上述代码可以看出来,IBlobContainer 的默认实现还是基于 BlobContainer<T> 的。那么为啥会有个泛型的 Container,从简介中可以看到 OSS 里面对应的 Bucket 其实就是一个 IBlobContainer。假如你会针对某云的多个 Bucket 进行操作,那么就需要类型化的 BlobContainer 了。

在这里可以看到,IBlobContainer 的实现是一个工厂方法,这一点在后面会进行解释。

2.2 BLOB 容器

2.2.1 容器的定义

每个容器就是一个 OSS 的 Bucket,开发人员在对 BLOB 进行操作时,会注入 IBlobContainer/IBlobContainer<T>,通过接口提供的 5 种方法进行操作,这五个方法分别是 保存对象删除对象判断对象是否存在获取对象获取对象(不存在返回 NULL)

public interface IBlobContainer
{// 保存对象Task SaveAsync(string name,Stream stream,bool overrideExisting = false,CancellationToken cancellationToken = default);// 删除对象Task<bool> DeleteAsync(string name,CancellationToken cancellationToken = default);// 判断对象是否存在Task<bool> ExistsAsync(string name,CancellationToken cancellationToken = default);// 获取对象Task<Stream> GetAsync(string name,CancellationToken cancellationToken = default);// 获取对象(不存在返回 NULL)Task<Stream> GetOrNullAsync(string name,CancellationToken cancellationToken = default);//TODO: Create shortcut extension methods: GetAsArraryAsync, GetAsStringAsync(encoding) (and null versions)
}

泛型的 BLOB 容器也是集成自该接口,内部没有任何特殊的方法。

public interface IBlobContainer<TContainer> : IBlobContainerwhere TContainer: class
{}

2.2.2 容器的实现

容器的两种实现都存放在 BlobContainer.cs 文件当中,标注容器实现内部都会有一个 ContainerName,用于标识不同的容器,并且和其他的组件作为 关联键 进行绑定。每个容器都会关联 BlobContainerConfigurationIBlobProvider 两个组件,它们分别提供了容器的配置信息和容器的具体实现 Provider,在容器构造的时候根据 ContainerName 分别进行初始化。

public class BlobContainer : IBlobContainer
{protected string ContainerName { get; }protected BlobContainerConfiguration Configuration { get; }protected IBlobProvider Provider { get; }protected ICurrentTenant CurrentTenant { get; }protected ICancellationTokenProvider CancellationTokenProvider { get; }protected IServiceProvider ServiceProvider { get; }// ... 其他代码。
}

可以看到这里还注入了 ICurrentTenant,注入该对象的主要作用是用来处理多租户的情况,如果当前容器启用了多租户,那么会手动 Change()。下面以 SaveAsync() 方法为例。

public virtual async Task SaveAsync(string name,Stream stream,bool overrideExisting = false,CancellationToken cancellationToken = default)
{// 变更当前租户信息,当启用了多租户时,会使用当前租户进行变更。using (CurrentTenant.Change(GetTenantIdOrNull())){// 根据 ContainerName 取得对应的标准化容器名称和对象名称。var (normalizedContainerName, normalizedBlobName) = NormalizeNaming(ContainerName, name);// 使用 ContainerName 匹配的 Provider 存储对象数据。await Provider.SaveAsync(new BlobProviderSaveArgs(normalizedContainerName,Configuration,normalizedBlobName,stream,overrideExisting,CancellationTokenProvider.FallbackToProvider(cancellationToken)));}
}

这里有两个地方需要单独分析,第一个是 NormalizeNaming() 的作用,第二个是 BlobProviderSaveArgs 对象。

2.2.3.1 名称标准化对象

IBlobNamingNormalizer(BLOB 名称标准化对象),主要用于将一个字符串进行标准化处理,防止 Provider 无法处理这种名称。各大 OSS 都对容器的名称或对象的名称有命名要求,比如必须全部小写,不能有哪些特殊符号等等。

protected virtual (string, string) NormalizeNaming(string containerName,  string blobName)
{// 从当前的配置信息中获取对应的标准化器,如果不存在任何标准化工具对象,则直接返回原始名称。if (!Configuration.NamingNormalizers.Any()){return (containerName, blobName);}using (var scope = ServiceProvider.CreateScope()){// 获取所有的标准化器,并依次进行名称的标准化处理。foreach (var normalizerType in Configuration.NamingNormalizers){var normalizer = scope.ServiceProvider.GetRequiredService(normalizerType).As<IBlobNamingNormalizer>();containerName = normalizer.NormalizeContainerName(containerName);blobName = normalizer.NormalizeBlobName(blobName);}return (containerName, blobName);}
}
2.2.3.2 BLOB 上下文

在 BLOB 里面,ABP 分别为每个操作都定义了一个 ***Args 对象,它就是一个上下文对象,用于在整个调用周期中传递参数。

2.2.3.3 BLOB 配置信息

每个 BLOB 容器都会有一个 BlobContainerConfiguration 用于存储配置信息,它主要有以下几个重要的属性。

public class BlobContainerConfiguration
{// 当前 BLOB 容器对应的 Provider 类型。public Type ProviderType { get; set; }// 当前 BLOB 容器是否启用了多租户。public bool IsMultiTenant { get; set; } = true;// 当前 BLOB 容器的名称标准化对象。public ITypeList<IBlobNamingNormalizer> NamingNormalizers { get; }// 当前 BLOB 容器的属性。[NotNull] private readonly Dictionary<string, object> _properties;// 当尝试获取某些配置属性,但是不存在时,会从这个 Configuration 拿取数据。[CanBeNull] private readonly BlobContainerConfiguration _fallbackConfiguration;public BlobContainerConfiguration(BlobContainerConfiguration fallbackConfiguration = null){NamingNormalizers = new TypeList<IBlobNamingNormalizer>();_fallbackConfiguration = fallbackConfiguration;_properties = new Dictionary<string, object>();}[CanBeNull]public T GetConfigurationOrDefault<T>(string name, T defaultValue = default){return (T) GetConfigurationOrNull(name, defaultValue);}[CanBeNull]public object GetConfigurationOrNull(string name, object defaultValue = null){return _properties.GetOrDefault(name) ??_fallbackConfiguration?.GetConfigurationOrNull(name, defaultValue) ??defaultValue;}// ... 其他代码。
}

在后续各种 Provider 里面定义的配置项,本质上就是对 _properties 字典进行操作。

2.2.3 容器的构造与初始化

BLOB 容器并不是通过 IoC 容器直接解析构造的,而是通过 IBlobContainerFactory 工厂进行创建,与容器相关的配置对象和 BLOB Provider 也是在这个时候进行构造赋值。

public class BlobContainerFactory : IBlobContainerFactory, ITransientDependency
{protected IBlobProviderSelector ProviderSelector { get; }protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }protected ICurrentTenant CurrentTenant { get; }protected ICancellationTokenProvider CancellationTokenProvider { get; }protected IServiceProvider ServiceProvider { get; }public BlobContainerFactory(IBlobContainerConfigurationProvider configurationProvider,ICurrentTenant currentTenant,ICancellationTokenProvider cancellationTokenProvider,IBlobProviderSelector providerSelector,IServiceProvider serviceProvider){ConfigurationProvider = configurationProvider;CurrentTenant = currentTenant;CancellationTokenProvider = cancellationTokenProvider;ProviderSelector = providerSelector;ServiceProvider = serviceProvider;}public virtual IBlobContainer Create(string name){// 根据容器的名称,获取对应的配置。var configuration = ConfigurationProvider.Get(name);// 构造一个新的容器对象。return new BlobContainer(name,configuration,// 一样的是根据容器名称,获得匹配的 Provider 类型。ProviderSelector.Get(name),CurrentTenant,CancellationTokenProvider,ServiceProvider);}
}

那么这个工厂方法是在什么时候调用的呢?跳转到工厂方法的实现,发现会被一个静态扩展方法所调用,重要的是这个方法是一个泛型方法,这样就与开头的类型化 BLOB 容器相对应了。

public static class BlobContainerFactoryExtensions
{public static IBlobContainer Create<TContainer>(this IBlobContainerFactory blobContainerFactory){// 通过 GetContainerName 方法获取容器的名字。return blobContainerFactory.Create(BlobContainerNameAttribute.GetContainerName<TContainer>());}
}

GetContainerName() 方法也很简单,如果容器类型没有指定 BlobContainerNameAttribute特性,那么就会默认使用类型的 FullName 作为名称。

public static string GetContainerName(Type type)
{var nameAttribute = type.GetCustomAttribute<BlobContainerNameAttribute>();if (nameAttribute == null){return type.FullName;}return nameAttribute.GetName(type);
}

最后的最后,看一下这个类型化的 BLOB 容器。

public class BlobContainer<TContainer> : IBlobContainer<TContainer>where TContainer : class
{private readonly IBlobContainer _container;public BlobContainer(IBlobContainerFactory blobContainerFactory){_container = blobContainerFactory.Create<TContainer>();}// ... 其他代码。
}

对应的是模块初始化的工厂方法:

context.Services.AddTransient(typeof(IBlobContainer),serviceProvider => serviceProvider.GetRequiredService<IBlobContainer<DefaultContainer>>()

这里的 DefaultContainer 就指定了该特性,所以本质上一个 IBlobContainer 就是一个类型化的容器,它的泛型参数是 DefaultContainer

[BlobContainerName(Name)]
public class DefaultContainer
{public const string Name = "default";
}
2.2.3.1 BLOB 的配置提供者

BLOB 容器工厂使用 IBlobContainerConfigurationProvider 来匹配对应容器的配置信息,实现比较简单,直接注入了 AbpBlobStoringOptions 并尝试从它的 BlobContainerConfigurations 中获取配置对象。

public class DefaultBlobContainerConfigurationProvider : IBlobContainerConfigurationProvider, ITransientDependency
{protected AbpBlobStoringOptions Options { get; }public DefaultBlobContainerConfigurationProvider(IOptions<AbpBlobStoringOptions> options){Options = options.Value;}public virtual BlobContainerConfiguration Get(string name){return Options.Containers.GetConfiguration(name);}
}

这里的 BlobContainerConfigurations 对象,核心就是一个键值对,键就是 BLOB 容器的名称,值就是容器对应的配置对象。

public class BlobContainerConfigurations
{private BlobContainerConfiguration Default => GetConfiguration<DefaultContainer>();private readonly Dictionary<string, BlobContainerConfiguration> _containers;public BlobContainerConfigurations(){_containers = new Dictionary<string, BlobContainerConfiguration>{// 添加默认的 BLOB 容器。[BlobContainerNameAttribute.GetContainerName<DefaultContainer>()] = new BlobContainerConfiguration()};}// ... 其他代码public BlobContainerConfigurations Configure([NotNull] string name,[NotNull] Action<BlobContainerConfiguration> configureAction){Check.NotNullOrWhiteSpace(name, nameof(name));Check.NotNull(configureAction, nameof(configureAction));configureAction(_containers.GetOrAdd(name,() => new BlobContainerConfiguration(Default)));return this;}public BlobContainerConfigurations ConfigureAll(Action<string, BlobContainerConfiguration> configureAction){foreach (var container in _containers){configureAction(container.Key, container.Value);}return this;}// ... 其他代码
}

在使用过程中,我们在模块里面调用的 Configure() 方法,就会在字典添加一个新的 Item,并为其赋值。而 ConfigureAll() 就是遍历这个字典,为每个 BLOB 容器调用委托,以便进行配置。

2.2.3.2 BLOB 的 Provider 选择器

在构造 BLOB 容器的时候,BLOB 容器工厂通过 IBlobProviderSelector 来选择对应的 BLOB Provider,具体选择哪一个是根据 BlobContainerConfiguration 里面的 ProviderType 决定的。

public virtual IBlobProvider Get([NotNull] string containerName)
{Check.NotNull(containerName, nameof(containerName));// 获得当前 BLOB 容器对应的配置信息。var configuration = ConfigurationProvider.Get(containerName);if (!BlobProviders.Any()){throw new AbpException("No BLOB Storage provider was registered! At least one provider must be registered to be able to use the Blog Storing System.");}foreach (var provider in BlobProviders){// 通过配置信息匹配对应的 Provider。if (ProxyHelper.GetUnProxiedType(provider).IsAssignableTo(configuration.ProviderType)){return provider;}}throw new AbpException($"Could not find the BLOB Storage provider with the type ({configuration.ProviderType.AssemblyQualifiedName}) configured for the container {containerName} and no default provider was set.");
}

上面的 BlobProviders 其实就是直接从 IoC 解析的 IEnumerable<IBlobProvider> 对象,我还找了半天是哪个地方进行赋值的。当 ABP 框架自动之后,会自动将已经实现的 BLOB Provider 注入到 IoC 容器中,如果某个容器在使用时指定了对应的配置参数,则会匹配对应的 BLOB Provider。

2.3 Provider 的实现

2.3.1 File System

文件系统作为 BLOB 的最简化实现,本质就是通过文件夹进行租户隔离动作,所有操作都会将数据持久化到硬盘上。核心代码就一个文件 FileSystemBlobProvider,在这个文件内部定义了具体的执行逻辑,我们这里大概看一下 SaveAsyn() 的实现。

public override async Task SaveAsync(BlobProviderSaveArgs args)
{var filePath = FilePathCalculator.Calculate(args);if (!args.OverrideExisting && await ExistsAsync(filePath)){throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");}DirectoryHelper.CreateIfNotExists(Path.GetDirectoryName(filePath));var fileMode = args.OverrideExisting? FileMode.Create: FileMode.CreateNew;await Policy.Handle<IOException>().WaitAndRetryAsync(2, retryCount => TimeSpan.FromSeconds(retryCount)).ExecuteAsync(async () =>{using (var fileStream = File.Open(filePath, fileMode, FileAccess.Write)){await args.BlobStream.CopyToAsync(fileStream,args.CancellationToken);await fileStream.FlushAsync();}});
}

很简单,通过 FilePathCalculator计算出来文件的具体路径,然后结合配置参数来判断文件是否存在,以及是否进入后续操作。通过 Polly 提供的重试机制来创建文件。

2.3.2 DataBase

数据库 Provider 是利用数据库的 BLOB 类型,将这些大型对象存储到数据库当中,不太建议这样操作。这里不再进行详细介绍,基本大同小异。

2.3.3 各类 OSS (腾讯云为例)

OSS 作为云厂商的标配,基本概念和操作都与 ABP 的 BLOB 相匹配,集成起来也还是比较简单,就是将各个 OSS 的 SDK 塞进来就行。这里注意点的是,每个 BLOB Provider 都会编写一个基于 BlobContainerConfiguration 类型的静态方法,取名都叫做 UseXXX(),并在里面对具体的配置进行赋值。

public static class TencentCloudBlobContainerConfigurationExtensions
{public static TencentCloudBlobProviderConfiguration GetTencentCloudConfiguration(this BlobContainerConfiguration containerConfiguration){return new TencentCloudBlobProviderConfiguration(containerConfiguration);}public static BlobContainerConfiguration UseTencentCloud(this BlobContainerConfiguration containerConfiguration,Action<TencentCloudBlobProviderConfiguration> tencentCloudConfigureAction){containerConfiguration.ProviderType = typeof(TencentCloudBlobProvider);containerConfiguration.NamingNormalizers.TryAdd<TencentCloudBlobNamingNormalizer>();tencentCloudConfigureAction(new TencentCloudBlobProviderConfiguration(containerConfiguration));return containerConfiguration;}
}

可能会对这个 TencentCloudBlobProviderConfiguration 有一些好奇,其实就是个套娃,因为直接传入了 BlobContainerConfiguration 对象,里面的各种属性本质上就是对配置项的那个 Dictionary<string,object> 进行操作。

public class TencentCloudBlobProviderConfiguration
{public string AppId{get => _containerConfiguration.GetConfigurationOrDefault<string>(TencentCloudBlobProviderConfigurationNames.AppId);set => _containerConfiguration.SetConfiguration(TencentCloudBlobProviderConfigurationNames.AppId, value);}public string SecretId{get => _containerConfiguration.GetConfigurationOrDefault<string>(TencentCloudBlobProviderConfigurationNames.SecretId);set => _containerConfiguration.SetConfiguration(TencentCloudBlobProviderConfigurationNames.SecretId, value);}// ... 其他代码public TencentCloudBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration){_containerConfiguration = containerConfiguration;}
}

腾讯云的 BLOB Provider 仓库:https://github.com/EasyAbp/Abp.BlobStoring.TencentCloud

2.4 回顾

  1. 开发人员可以在模块的 ConfigureService() 阶段为所有容器或者特定容器指定参数。

  2. ABP vNext 框架会注入所有的 BLOB Provider,并注入默认的 IBlobContainer<DefaultContainer> 容器和其他的类型化容器实现。

  3. 当需要使用 BLOB 时,开发人员注入了 IBlobContainer 或 IBlobContainer<T>

  4. BLOB 容器的工厂会根据容器的名称匹配对应的 BLOB Provider 和配置对象。

  5. BLOB Provider 根据 **Args 参数内部附带的配置对象,读取对应的配置信息进行自定义的操作。

三、总结

小型项目直接集成 FileSystem 即可,中大型项目可以使用各种 OSS Provider,BLOB 系统可以简化开发人员对于大量二进制文件的管理操作。最近工作相当杂乱繁忙,下半年希望有时间继续学习更新吧。

相关文章:

  • 我和ABP vNext 的故事

  • 基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目

  • [Abp vNext 源码分析] - 19. 多租户

  • abp vnext2.0核心组件之DDD组件之实体结构源码解析

  • 基于Abp VNext框架设计 - Masstransit分布式消息

  • ABP vNext中使用开源日志面板 LogDashboard

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

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

相关文章

[Nginx]location 指令说明

location 指令说明 该指令用于匹配 URL。 语法如下&#xff1a; 1、 &#xff1a;用于不含正则表达式的 uri 前&#xff0c;要求请求字符串与 uri 严格匹配&#xff0c;如果匹配 成功&#xff0c;就停止继续向下搜索并立即处理该请求。 2、~&#xff1a;用于表示 uri 包含正则…

基于GitBook框架搭建技术文档平台

源宝导读&#xff1a;为了向用户更好的传递ERP开放平台的价值与技术知识&#xff0c;我们基于GitBook框架搭建了一个文档中心站点&#xff0c;本文将介绍此站点的设计与实现过程。一、项目架构图因为文档会涉及到很多的产品线&#xff0c;所以目前主要是通过拉取各个产品线的文…

[Nginx]nginx 配置实例-负载均衡

nginx 配置实例-负载均衡 1、实现效果 &#xff08;1&#xff09;浏览器地址栏输入地址 http://192.168.111.134/edu/a.html&#xff0c;负载均衡效果&#xff0c;平均分担到 8080和 8081 端口中 2、准备工作 &#xff08;1&#xff09;准备两台 tomcat 服务器&#xff0c;…

css3边框交替动画_用css3实现惊艳面试官的背景即背景动画(高级附源码)

我们传统的前端更多的是用javascript实现各种复杂动画&#xff0c;自从有了Css3 transition和animation以来,前端开发在动画这一块有了更高的自由度和格局,对动画的开发也越来越容易。这篇文章就让我们汇总一下使用Css3实现的各种特效。这篇文章参考《css揭秘》这本书&#xff…

用Blazor技术封装G2Plot实现Charts组件

Blazor是一个使用 .NET 生成交互式客户端 Web UI 的框架。目前社区刚起步&#xff0c;相关的组件并不多&#xff0c;有幸有一群爱好者正在努力建设社区&#xff0c;我作为社区一员也来贡献一些内容。这里我就分享分享我封装G2Plot后的Blazor组件ant-design-charts-blazor。ant-…

[Nginx]nginx配置实例_反向代理

nginx 配置实例-反向代理1 1、实现效果 &#xff08;1&#xff09;打开浏览器&#xff0c;在浏览器地址栏输入地址 www.123.com&#xff0c;跳转到 liunx 系统 tomcat 主页面中 2、准备工作 &#xff08;1&#xff09;在 liunx 系统安装 tomcat&#xff0c;使用默认端口 80…

lts安装 rust ubuntu_一起学Rust编程「1」:开发环境

引言Rust是近几年获得广泛关注和认可的一门系统级编程语言。它严苛的静态类型检查和独特的所有权系统&#xff0c;使得编译器能够尽可能的帮开发者在编译时就排除一些符合常见模式的bug。这也让很多人认为rust是一门更加“安全”的语言。专注数据安全技术的红小豆同学也非常看好…

使用 iPerf 测试 Azure VM 之间的网速

点击上方关注“汪宇杰博客” ^_^导语以往提到测网速&#xff0c;大家可能想到的都是用著名的 speedtest 等工具测试互联网连接速度。但实际上仅仅测试互联网连接速度并不可靠&#xff0c;在部分应用场景里网速还受到服务器之间的连接速度影响&#xff0c;因此清楚你的网络性能瓶…

[Nginx]nginx 配置实例-动静分离

nginx 配置实例-动静分离 1、什么是动静分离 Nginx 动静分离简单来说就是把动态跟静态请求分开&#xff0c;不能理解成只是单纯的把动态页面和静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开&#xff0c;可以理解成使用 Nginx 处理静态页面&#xff0c;Tomcat 处…

收购最大K8s服务商,重回独立的SUSE又要和Red Hat拼混合云

7月8日&#xff0c;SUSE 宣布收购 Kubernetes 管理平台公司 Rancher Labs&#xff0c;交易预计在2020年10月底之前完成。有外媒称&#xff0c;收购价预估在6亿至7亿美元之间。 宣布要收购之后&#xff0c;SUSE 的介绍前缀中又多了个关键词——Kubernetes&#xff0c;变成企业级…

post获取重定向的链接 python_【转载】python面试基础知识(四) 网络部分

最近&#xff0c;小编在整理python面试基础知识&#xff0c;看了很多博客、文章和咨询了一些大厂公司大牛。了解到&#xff0c;在python面试的时候&#xff0c;不仅要求你有项目经验&#xff0c;还要考试代码呢&#xff01;今天&#xff0c;小编和大家分享一下python面试基础知…

[MyBatisPlus]MyBatisPlus简介特性

简介 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 愿景 我们的愿景是成为 MyBatis 最好的搭档&#xff0c;就像魂斗罗中的 1P、2P&#xff0c;基友搭配&…

交换机千兆和百兆对网速影响_家里明明装了百兆宽带!为啥网速还这么慢?原因竟然在这!...

现在的人&#xff0c;已经渐渐离不开手机和电脑&#xff0c;而说到手机和电脑&#xff0c;那就绕不过网络。随着光纤入户&#xff0c;网速有了大大的提升&#xff0c;百兆宽带也走进了寻常百姓家。可是不知道你有没有发现一个问题&#xff0c;为什么你明明安装的是百兆的宽带&a…

修复被破坏的 vs 工程设置

缘起 前几天打开工作项目进行编译&#xff0c;没想到居然报错&#xff0c;明明前一天编译还正常的。简单排查后&#xff0c;临时修复了问题。但是今天新建工程时居然还有相同的问题&#xff0c;是可忍熟不可忍&#xff1f;本文记录了排查过程&#xff0c;希望对各位小伙伴儿有帮…

7-4 二叉树的遍历!(简单) (25 分)

7-4 二叉树的遍历&#xff01;&#xff08;简单&#xff09; (25 分) 二叉树作为FDS课程最核心的数据结构之一&#xff0c;要求每个人都掌握&#xff01; 这是一道简单的二叉树问题&#xff01; 我们将给出一颗二叉树&#xff0c;请你输出它的三种遍历&#xff0c;分别是先序…

[MyBatisPlus]入门案例

入门案例 创建测试数据库和表 CREATE DATABASE mybatis_plus /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use mybatis_plus;CREATE TABLE user ( id bigint(20) NOT NULL COMMENT 主键ID,name varchar(30) DEFAULT NULL COMMENT 姓名, age int(11) DEFAULT NULL COMMENT 年…

vs 2019 aspx灰色_蛇纹当道,豹纹在侧:穿成动物园是2019时尚大势?

↑点击上方三联生活周刊加星标&#xff01;忘记动物纹让你联想到的隐喻吧&#xff0c;它应该用时髦来吸引你。蛇纹当道&#xff0c;豹纹在侧和有嬉皮印记的植物花纹不同&#xff0c;动物纹让人觉得老派而华丽&#xff0c;所以前者有像《佩斯利公园》这样的歌来将它比喻成没有世…

TensorFlow.NET 在工业部署中的应用

前言深度学习训练的模型 如何快速地在工业应用中进行部署&#xff0c;这一直是工业领域深度学习技术应用的痛点。我们来看下TIOBE 2020年7月 的 TOP 10 编程语言排行榜&#xff1a;从上图中可以看到&#xff0c;Python 占据了 第 3 名&#xff0c;C# 在 第 5 名。在深度学习的科…

全年营业额怎么计算_门店盈亏平衡计算及案例分析 | 商品管理

以某门店为例&#xff0c;面积为150平方米。年租金16万元、人员工资费用15万元、水电费3万元&#xff0c;税费1.2万元、装修费2.9万元、交通费1.6万元、投入成本的利息及其他费用3.3万元。(进货折扣)是50%&#xff0c;春夏季销售额占年总销售额的40%&#xff0c;一件春夏季的衣…

人工智能?.NetCore一样胜任!

提起AI&#xff0c;大家都会先想到Python&#xff0c;确实Python作为一门好几十年的老语言&#xff0c;上一波的AI大流行使它焕发了青春。大家用Phtyon来做AI&#xff0c;最主要的原因无非就是编码量更少&#xff0c;很多数学和AI相关的Api都是现成的。但是随着ML.net的问世&am…