分类 其它 下的文章

经过前面章节的学习,可以说大家已经算Redis开发入门了。已经可以去到项目上磨砺了。

但是今天我还想和大家分享一章:封装自己的Redis C#库,然后打包成Nuget包。

首先要说明的是:不是要自己开发一个Redis客户端库,而是基于上篇文章中介绍的6大库,做一个简单封装,真的很简单的那种,就是包个壳子。

再来说说为什么要做这个事情。

01
、原因

1.可测试

我希望代码是以服务的方式注入到程序中,而不是静态方法的方式去调用。使用依赖注入来提供服务使得程序可测试性增强,如果做单元测试,依赖注入的服务很容易通过mock来测试,而静态方法往往很难被模拟,测试起来很不灵活;

2.解耦

对于一个系统来说会使用到各种技术来达到某种能力,实现一个功能可能会有很多种方法,我们在意的是实现这个功能,而不是你用了什么方法。回到主题,我们需要的是使用Redis来实现业务功能,而具体用那个客户端库并不重要。

再举个例子比如今天我们选择了ServiceStack.Redis库接过遇到问题我们解决不了怎么办?难道业务不做了?不可能吧!而恰巧这个时候CSRedisCore库可以解决,你会怎么选?

这时候可能会想换的成本有多大?两种库方法名不统一,功能也不一样,如果系统中到处散落Redis方法调用,这可怎么换啊。

试想如果我们封装了一层,提供了一组接口,打包成Nuget包,这个时候大家用的就是这个Nuget包,而我们只需要把Nuget包里用ServiceStack.Redis实现的方法换成用CSRedisCore实现一下,大家直接更新一下Nuget包,可能一行代码都不用改就完成了替换。

因为我们依赖的是我们自己封装的接口,而不是具体Redis客户端库,因此可以解耦轻松替换。

3.扩展

Redis的原生功能可以理解为基础功能,表明Redis有这种能力。但是我们怎么使用,怎么更好的发挥它的价值,这就是我们自己能力的体现了。

大家相过想过没有,为什么有的库商业化做的特别好,特别简单容易上手。商业化好说明提供的服务好,也就是说明它能帮你做很多事情,它在原生功能上加了很多自己的创作。

项目做多了,我们自己也会遇到一些相似的功能,如果这个时候我们想把这些相似功能封装一下,要放哪里呢?怎么给别人用?如果我们遇到一个功能现有库都没有相应能力,需要我们基于原生自己开发实现又应该在哪做呢?

这些功能积累的多了总要有个地方放吧,而这时如果我们自己封装了一层,放哪的问题是不是一下子就解决了,说不定做着做着就成了一个产品了呢。

不知道到这里,大家有没有感觉思路一下子被打开了。可能我们没写几行代码,但是这个格局一下子就打开了,而这几行代码也可能产生意想不到的收获,可能是无限可能,也可能就是你实现自己Redis客户端库的开端。

当然这也是我自己对编码,对封装的个人拙见。说这么多也是希望可以给大家一些帮助。

02
、实现

下面闲话少说进入正题,如果来封装呢?

我们先梳理一下大致思路:

1.我们需要一个接口,里面包含:原库原生能力Client、其他我们自定义功能。

2.一个入口,别人要用,总要有个入口吧。

其实要求就这么简单。

下面我们就以封装CSRedisClient为例,首先定义IRedisService接口,里面包含Client字段以及两个演示的自定义方法

using CSRedis;
namespace Redis.RedisExtension
{
    public interface IRedisService
    {
        /// <summary>
        /// RedisClient
        /// </summary>
        /// <returns></returns>
        CSRedisClient Client { get; }
        #region 自定义方法
        /// <summary>
        /// 获取指定 key 的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        T Get<T>(string key);
        /// <summary>
        /// 获取指定 key 的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        Task<T> GetAsync<T>(string key);
        #endregion
    }
}

然后需要实现IRedisService,同时以CSRedisClient为构造函数入参,具体代码如下:

using CSRedis;
namespace Redis.RedisExtension
{
    public class RedisService : IRedisService
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="redisClient"></param>
        public RedisService(CSRedisClient redisClient)
        {
            Client = redisClient;
        }
        /// <summary>
        /// CSRedis
        /// </summary>
        /// <returns></returns>
        public CSRedisClient Client { get; }
        #region 自定义方法
        /// <summary>
        /// 获取指定 key 的值
        /// </summary>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            return Client.Get<T>(key);
        }
        /// <summary>
        /// 获取指定 key 的值
        /// </summary>
        /// <returns></returns>
        public Task<T> GetAsync<T>(string key)
        {
            return Client.GetAsync<T>(key);
        }
        #endregion
    }
}

到这里第一个问题就解决了,对于第二个问题我们,我们可以对IServiceCollection进行扩展添加启动扩展方法AddRedisClientSetup,具体代码如下:

using CSRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Redis.RedisExtension
{
    public static class RedisSetupExtensions
    {
        /// <summary>
        /// Redis客户端启动项
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddRedisClientSetup(this IServiceCollection services)
        {
            services.AddSingleton<CSRedisClient>(serviceProvider =>
            {
                var configuration = serviceProvider.GetRequiredService<IConfiguration>();
                var setting = configuration["RedisConnectionString"];
                return new CSRedisClient(setting);
            });
            services.AddSingleton<IRedisService, RedisService>();
            return services;
        }
        /// <summary>
        /// Redis客户端启动项
        /// </summary>
        /// <param name="services"></param>
        /// <param name="connectionString"></param>
        /// <returns></returns>
        public static IServiceCollection AddRedisClientSetup(this IServiceCollection services, string connectionString)
        {
            services.AddSingleton<CSRedisClient>(serviceProvider =>
            {
                return new CSRedisClient(connectionString);
            });
            services.AddSingleton<IRedisService, RedisService>();
            return services;
        }
    }
}

为什么要提供两个重载方法,因为如果用户基于我们的约定,在配置文件中以"RedisConnectionString"命名Redis连接字符串,用户直接调用AddRedisClientSetup()方法即可完成Redis启动,但是可能因为各种原因用户没法遵守约定,因此我们也要提供一个用户可以指定Redis连接字符串方法的入口。

下面我们就用基于约定的方式,在配置文件中加入以下配置:

{
  "RedisConnectionString": "127.0.0.1:6379"
}

然后使用Client.Set方法设置key1,再用自定义方法Get
方法读取,代码如下:

public static void Run()
{
    var configuration = new ConfigurationBuilder()
        .SetBasePath(AppContext.BaseDirectory)
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .Build();
    var services = new ServiceCollection();
    services.AddSingleton<IConfiguration>(configuration);
    services.AddRedisClientSetup();
    var redisService = services.BuildServiceProvider().GetService<IRedisService>();
    var setResult = redisService.Client.Set("key1", "value1");
    Console.WriteLine($"redisService.Client.Set(\"key1\",\"value1\")执行结果:{setResult}");
    var value = redisService.Get<string>("key1");
    Console.WriteLine($"redisService.Get<string>(\"key1\")执行结果:{value}");
    redisService.Client.Del("key1");
}

执行结果如下:

是不是很简单,然后我们只需要把上面三个文件IRedisService、RedisService、RedisSetupExtensions放到单独的类库中,然后打包发布成Nuget包,就可以给大家一起用啦,今天我这边就不发布Nuget包了,后面会有相关的计划,到时候再细聊。


:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。
https://gitee.com/hugogoos/Planner

前言

这个考题的大部分内容,我感觉都是我们会的,但所有的考题都穿上了马甲,穿上马甲我们就不好认了,而且如果是一个两个人穿马甲,还好推断,如果1000人穿马甲,你识别的概率就会急速下降。
有些题的内容则是即无法识别,也无法背,因为它也没有个前因后果,完全是出题人拍脑袋想的,所以,这种题我们是无法通过知识来判断的,因为用知识来判断,你会发现,四个选项全是正确的,这时我们可以采用逐字读题法,就是一个字一个字的读,然后把答案分别放里,看看哪个读起来更顺。

  • 例如这题:
    信息系统规划方法中,关键成功因素法通过对关键成功因素的识别,找出实现目标所需要的关键信息集合,从而确定系统开发的()。关键成功因素来源于组织的目标,通过组织的目标分解和关键成功因素识别、( )识别,一直到产生数据字典。
    A.系统边界 B. 功能指标 C.优先次序 D.性能指标
    逐字阅读,会发现C和D适合俩个空。

正题

嵌入式系统

就是安卓系统,安装系统是计算模式。

  • 混成系统
    嵌入式系统子类
    就是安卓系统加入了传感器的SDK,就是汽车上的安卓系统

控制模式

  • 计算模式控制
    就是cpu分配模式,合理利用资源,缺点一瘫痪全瘫痪。
  • 同步异步控制
    就是同步异步,缺点就是会有延迟,优点是一个延迟不影响别人。市面的大系统没有这模式的,这模式一般是我们开发的系统,比如交易,流水线等等。

tcp端口号作用

这个比较冷门,是对应用层进程寻址用的,我们写tcp代码时是要写端口号的,但那个是属于tcp协议部分,tcp协议用那个端口号建立连接,如果单问端口号的作用,那它就是应用层进程寻址用的。

DHCP

DHCP(Dynamic Host Configuration Protocol)就是自动给电脑分配IP的一个协议,电脑插上网线就会发这个协议,路由器收到后就会回一个消息,给电脑一个ip,然后我们使用ipconfig命令就能看到IP地址了。

概要设计和详细设计

  • 概要设计:讲软件需求转化为数据结构和软件系统结构。
  • 详细设计:过程设计,通过对结构细化,得到软件详细数据结构和算法。
    注意1:这里的数据结构不是我们认知中的数据结构,他是指流程图,我们在写概要设计时,即使再省略,也还是要写流程图的。
    注意2:详细设计的算法,就是我们写的伪代码,加减乘除,连表查询在这里都是算法。

软件结构化设计

就是网站开发,好听点就是叫网站应用开发,或者高级点ERP,MIS,CRM。
步骤是:系结构设计、接口设计、数据设计和过程设计。
这个其实是特指java+vue的前后台分离开发中的java后台接口项目的设计。具体对应真实操作如下:
系结构设计:框架开发。
接口设计:controller设计。
数据设计:数据库设计。
过程设计:代码开发。

软件逆向工程

软件逆向工程导出信息的四个抽象层次:
A.实现级:代码
B.结构级:函数
C.功能级:类
D.领域级:package包/命名空间

测试方法

强度测试:是在系统资源特别低的情况下考查软件系统极限运行情况。
负载测试:用于测试超负荷环境中程序是否能够承担。通过负载测试,确定在各种工作负载下系统的性能,目标是测试当负载逐渐增加时,系统各项性能指标的变化情况。
压力测试:通过确定系统的瓶颈或不能接收的性能点,来获得系统能够提供的最大服务级别的测试。负载测试和压力测试可以结合进行,统称为负载压力测试。
容量测试:并发测试也称为容量测试,主要用于测试系统可同时处理的在线最大用户数量。

  • 强度测试:测试性能,测系统卡不卡,什么时候卡。
  • 负载测试:测可靠性,测系统挂不挂,什么情况挂,理解为用人的数量测。
  • 压力测试:测可靠性,测系统挂不挂,什么情况挂,理解为用接口调用次数测。
  • 容量测试:测并发数。

设计方法

ABSD/ABSDM——基于体系结构的软件设计,这个应该是必考的,需要背。
其步骤如下:
【需求=>设计=>文档化=>复审=>实现=>演化】
这个设计模式还是挺好的,可以学习一下,我以前也写过关于他的文章。
ABSD/ABSDM

软件方法学是

软件方法学是以软件开发方法为研究对象的学科。
自定向下开发方法:是先对最高层次中的问题.进行定义、设计、编程和测试,而将其中未解决的问题作为一个子任务放到下一层次中去解决。
自底向上开发方法:是根据系统功能要求,从具体的器件、逻辑部件或者相似系统开始,通过对其进行相互连接、修改和扩大,构成所要求的系统。
形式化开发方法:是建立在严格数学基础上的软件开发方法。
非形式化方法(代表-头脑风暴):不把严格性作为其主要着眼点,通常以各种开发模型的形式得以体现。从适应范围来看,可分为整体性方法与局部性方法。适用于软件开发全过程的方法称为整体性方法;适用于开发过程某个具体阶段的软件方法称为局部性方法。

必死题

关于模块化设计,( )是错误的。
A.模块是指执行某一特定任务的数据结构和程序代码
B.模块的接口和功能定义属于其模块自身的内部特性
C.每个模块完成相对独立的特定子功能,与其他模块之间的关系最简单
D.模块设计的重要原则是高内聚、低耦合

首先要理解,这里的模块是[专指]java的项目下的子项目,就是IDEA里的new->module。
这道题答案是B,但4个答案都是正确的,而且是那种完全正确的那种。
解析:

模块的接口是模块与其他模块进行交互的部分,所以接口的定义不仅仅属于其模块自身的内部特性,与外部模块也具有相关性。B 选项的描述是错误的。

这是纯纯的文字游戏,按这种解析方式,我也可以把上面4个答案都解析成错的。
那也就是说,今年再出这题,换一个解析方式,答案就可以是A,C,D。
比如模块设计的重要原则是不仅仅是高内聚、低耦合,还有职责分离原则。所以D是错的,这也能解释的过去。
所以这样的题,就只能放弃了,必死题。

构件组装是指将库中的构件经适当修改后相互连接构成新的目标软件。()不属于构件组装技术。
A.基于功能的构件组装技术
B. 基于数据的构件组装技术
C.基于实现的构件组装技术
D.面向对象的构件组装技术

解析:

构件组装技术大致可分为基于功能的组装技术、基于数据的组装技术和面向对象的组装技术。
答案:C。

构件组装有这个概念,但基于xxx的构件组装技术,这是纯纯的他们凭空想象出来的概念,这种题一般都只考一次,因为明年他们会再想一个新的概念考你,背也没用,首先不会再考,其次,现实中没这定义。


注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!



若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

https://www.cnblogs.com/kiba/p/18409540

说明

该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。

该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。

说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。

有兴趣的朋友,请关注我吧(*^▽^*)。

使用前提

1、Visual Studio使用2022版本

搭建项目

OverallAuth2.0依然和OverallAuth1.0一样,采用前后端分离模式,所以搭建后端,我们选择  .net core web api(如下图)


选择项目模板后,我们点击【下一步】

选择.net 8.0(最新长期支持版本),随后创建项目

默认项目结构如下图

直接运行,查看默认界面

运行起来可以看到,系统默认的swagger界面非常简介,也少了很多信息比如:

1、系统说明、版本、作者等。

2、接口的描述、参数等信息。

3、接口的分类等。

优化Swagger

上面说道,系统默认的接口文档是非常简洁的,接下来我们在系统中,这样做,让swagger看起来更优美和专业。

在项目下新增一个文件件PlugInUnit,然后再该文件夹下新建一个类SwaggerPlugInUnit


建好SwaggerPlugInUnit后,在webapi同级建一个类库Utility,用于存放系统的辅助工具等,然后再该类库下建一个Enum文件夹,并新建ModeuleGroupEnum该枚举。

如图:

建好文件后,在ModeuleGroupEnum文件中写一个枚举SysMenu,并保存。

/// <summary>
///模块分组/// </summary>
public enumModeuleGroupEnum
{
SysMenu
= 1,
}

随后在SwaggerPlugInUnit中编写一个方法,具体代码如下

/// <summary>
///swagger插件/// </summary>
public static classSwaggerPlugInUnit
{
/// <summary> ///初始化Swagger/// </summary> /// <param name="services"></param> public static void InitSwagger(thisIServiceCollection services)
{
//添加swagger services.AddSwaggerGen(optinos =>{typeof(ModeuleGroupEnum).GetEnumNames().ToList().ForEach(version =>{
optinos.SwaggerDoc(version,
newOpenApiInfo()
{
Title
= "权限管理系统",
Version
= "V2.0",
Description
= "求关注,求一键三连",
Contact
= new OpenApiContact { Name = "微信公众号作者:不只是码农 b站作者:我不是码农呢", Url = new Uri("http://www.baidu.com") }
});

});
//反射获取接口及方法描述 var xmlFileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
optinos.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFileName),
true);

});
}
/// <summary> ///swagger加入路由和管道/// </summary> /// <param name="app"></param> public static void InitSwagger(thisWebApplication app)
{
app.UseSwagger();
app.UseSwaggerUI(options
=>{typeof(ModeuleGroupEnum).GetEnumNames().ToList().ForEach(versoin =>{
options.SwaggerEndpoint($
"/swagger/{versoin}/swagger.json", $"接口分类{versoin}");
});
});
}
}
然后再program中使用自定义swagger中间件

做好以上步骤,我们的swagger基本算是搭建好了,只需要在控制器上方,添加路由和分组。

 /// <summary>
 ///系统模块/// </summary>
[ApiController]
[Route(
"api/[controller]/[action]")]
[ApiExplorerSettings(GroupName
= nameof(ModeuleGroupEnum.SysMenu))]

做完以上这些,我们对swagger就算优化完成,只需要运行系统,就可以查看效果

注意:
必须生成接口的xml文件,不然会报错。

好了,以上就是搭建WebApi+优化Swagger的全部过程,你快来试试吧

如果对你有帮助,请关注我吧(*^▽^*)。

源代码地址:https://gitee.com/yangguangchenjie/overall-auth2.0-web-api

帮我Star,谢谢。

有兴趣的朋友,请关注我吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界

C++17 特性示例

1. 结构化绑定(Structured Binding)

结构化绑定允许你用一个对象的元素或成员同时实例化多个实体。
结构化绑定允许你在声明变量的同时解构一个复合类型的数据结构(如 结构体,
std::tuple

std::pair
, 或者
std::array
)。这样可以方便地获取多个值,而不需要显式地调用
std::tie()
或者
.get()
方法。

使用结构化绑定,能大大提升代码的可读性:

#include <iostream>
#include <string>
#include <unordered_map>

int main() {

  std::unordered_map<std::string,std::string> mymap;
  mymap.emplace("k1","v1");
  mymap.emplace("k2","v2");
  mymap.emplace("k2","v3");

  for (const auto& elem : mymap)
      std::cout << "old: " << elem.first << " : " << elem.second << std::endl;

  for (const auto& [key,value] : mymap)
      std::cout << " new: " << "key: " << key << ", value: " << value  << std::endl;

  return 0;
}

### 细说结构化绑定
为了理解结构化绑定,必须意识到这里面其实有一个隐藏的匿名对象。结构化绑定时引入的新变量名其实都指向这个匿名对象的成员/元素。
绑定到一个匿名实体
如下初始化的精确行为:
struct MyStruct {
  int i = 0;
  std::string s;
};
MyStruct ms;
auto [u, v] = ms;
等价于我们用 ms初始化了一个新的实体 e,并且让结构化绑定中的 u和 v变成 e的成员的别名,类似于如下定义:
auto e = ms;
aliasname u = e.i;
aliasname v = e.s;
这意味着 u和 v仅仅是 ms的一份本地拷贝的成员的别名。然而,我们没有为 e声明一个名称,因此我们不能直接访问这个匿名对象。注意 u和 v并不是 e.i和 e.s的引用(而是它们的别名)。decltype(u)的结果是成员 i的类型,declytpe(v)的结果是成员 s的类型。因此:
std::cout << u << ' ' << v << '\n';
会打印出 e.i和 e.s(分别是 ms.i和 ms.s的拷贝)。

e的生命周期和结构化绑定的生命周期相同,当结构化绑定离开作用域时 e也会被自动销毁。另外,除非使用了引用,否则修改用于初始化的变量并不会影响结构化绑定引入的变量(反过来也一样)  

### 示例代码
```cpp
#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <map>
#include <unordered_map>

struct MyStruct {
      int num {1};
      std::string str {"test"};
};

int main() {

  // 使用结构化绑定从 tuple 中提取值
  std::tuple<int, double, std::string> data = std::make_tuple(1, 3.14, "hello");
  auto [a, b, c] = data;
  std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;


  //value
  MyStruct my_struc;
  auto [num,str] = my_struc;
  std::cout << "num: " << num << ", str: " << str  << std::endl;
  my_struc.num = 2;
  str = "test2";
  std::cout << "num: " << num << ", str: " << str  << std::endl;


  //ref
  MyStruct my_struc2 {2,"test2"};
  const auto& [num2,str2] = my_struc2;
  std::cout << "num2: " << num2 << ", str2: " << str2  << std::endl;
  my_struc2.num = 4;
  std::cout << "num2: " << num2 << ", str2: " << str2  << std::endl;

  return 0;
}

### 总结
理论上讲,结构化绑定适用于任何有 public数据成员的结构体、C 风格数组和“类似元组 (tuple-like)的对
象”:
• 对于所有非静态数据成员都是 public的结构体和类,你可以把每一个成员绑定到一个新的变量名上。
• 对于原生数组,你可以把数组的每一个元素都绑定到新的变量名上。
• 对于任何类型,你可以使用 tuple-like API 来绑定新的名称,无论这套 API 是如何定义“元素”的。对于一个
类型 type这套 API 需要如下的组件:
– std::tuple_size<type>::value要返回元素的数量。
– std::tuple_element<idx, type>::type 要返回第 idx个元素的类型。
– 一个全局或成员函数 get<idx>()要返回第 idx个元素的值。
标准库类型 std::pair<>、std::tuple<>、std::array<> 就是提供了这些 API 的例子。如果结构体和类提供了 tuple-like API,那么将会使用这些 API 进行绑定,而不是直接绑定数据成员。

## 2. 拷贝消除(Copy Elision)

从技术上讲,C++17引入了一个新的规则:当以值传递或返回一个临时对象的时候必须省略对该临时对象的拷贝。
从效果上讲,我们实际上是传递了一个未实质化的对象 (unmaterialized object)。

自从第一次标准开始,C++就允许在某些情况下省略 (elision)拷贝操作,即使这么做可能会影响程序的运行结果(例如,拷贝构造函数里的一条打印语句可能不会再执行)。当用临时对象初始化一个新对象时就很容易出现这种情况,尤其是当一个函数以值传递或返回临时对象的时候。例如:
```cpp
#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <map>
#include <complex>


class MyClass
{
public:
  // 没有拷贝/移动构造函数的定义
  MyClass(const MyClass&) = delete;
  MyClass(MyClass&&) = delete;
};

MyClass bar() {
  return MyClass{};       // 返回临时对象
}

int main() {
  MyClass x = bar();  // 使用返回的临时对象初始化x

  return 0;
}
上面的代码用c++14会报错,c++17已经不报错了

然而,注意其他可选的省略拷贝的场景仍然是可选的,这些场景中仍然需要一个拷贝或者移动构造函数。例如:
MyClass foo()
{
  MyClass obj;
  ...
  return obj; // 仍 然 需 要 拷 贝/移 动 构 造 函 数 的 支 持
}
这里,foo()中有一个具名的变量 obj (当使用它时它是左值 (lvalue))。因此,具名返回值优化 (named returnvalue optimization)(NRVO) 会生效,然而该优化仍然需要拷贝/移动支持。当 obj是形参的时候也会出现这种情况:
MyClass bar(MyClass obj) // 传 递 临 时 变 量 时 会 省 略 拷 贝
{
  ...
  return obj; // 仍 然 需 要 拷 贝/移 动 支 持
}

当传递一个临时变量(也就是纯右值 (prvalue))作为实参时不再需要拷贝/移动,但如果返回这个参数的话仍然需要拷贝/ 移动支持因为返回的对象是具名的。 

### 作用
这个特性的一个显而易见的作用就是减少拷贝会带来更好的性能。尽管很多主流编译器之前就已经进行了这种优化,但现在这一行为有了标准的保证。尽管移动语义能显著的减少拷贝开销,但如果直接不拷贝还是能带来很大的性能提升(例如当对象有很多基本类型成员时移动语义还是要拷贝每个成员)。另外这个特性可以减少输出参数的使用,转而直接返回一个值(前提是这个值直接在返回语句里创建)。另一个作用是可以定义一个总是可以工作的工厂函数,因为现在它甚至可以返回不允许拷贝或移动的对象。
例如,考虑如下泛型工厂函数:
```cpp
#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <map>
#include <complex>
#include <utility>
#include <memory>
#include <atomic>


template <typename T, typename... Args>
T create(Args&&... args)
{
  return T{std::forward<Args>(args)...};
}


int main() {
  int i = create<int>(42);
  std::unique_ptr<int> up = create<std::unique_ptr<int>>(new int{42});
  std::atomic<int> ai = create<std::atomic<int>>(42);
  std::cout << "ai: " << ai << std::endl;
  return 0;
}



图形界面使用

bucket

bucket创建

图形界面创建bucket。

022

023

特性:

  • Versioning
    开启版本控制,开启版本控制则允许在同一键下保持同一对象的多个版本。

  • Object Locking
    对象锁定防止对象被删除,需要支持保留和合法持有,只能在创建桶时启用。

  • Quita
    配额限制bucket中的数据量。

  • Retention
    保留会施加规则,在一段时间内防止对象删除。为了设置桶保留策略,必须启用版本控制。

确认创建成功,查看bucket详情。

024

035

Anonymous:配置Access Poilcy为custom,可以自己定义那些前缀是只读,那些前缀是读写的等。

036

Events:事件,设置Bucket绑定那些事件通知。

037

Lifecycle:生命周期,配置bucket的生命周期。
类型选择Expiry代表是过期直接删除,选择Transition就是过期后转移到目标存储服务器,需要搭配Tiering使用。

Prefix:文件名前缀。
After:代表多少天后过期。

038

039

测试上传

测试上传文件。

033

034

Access keys

创建Access Keys

图形界面创建AK和SK。

025

强烈建议对锁创建的key进行记录:

xuq70VZxkfwPvDG5Mfga
t3FgyXTOhfyKowWFbMvFpTPJ9rJQh0RiTLEnJ2F5

026

027

也可将所创建的Key下载到本地。

028

配置权限

授予权限

Minio 的存储桶默认是不和任何 Acess Key 关联,同时 Minio 支持标准 S3 协议,可以在创建 bucket 和 ak 后给 Access Key 授予某个 Bucket 存储桶的访问权限,实现 Key 和 Bucket 的绑定。

Policy

MinIO使用基于策略的访问控制(PBAC)来定义经过身份验证的用户有权访问的授权操作和资源。
每个策略描述一个或多个操作和条件,这些操作和条件概括了一个用户或一组用户的权限。
MinIO PBAC是为了兼容AWS IAM策略语法、结构和行为而构建的。
每个用户只能访问内置角色显式授予的资源和操作。
默认情况下,MinIO拒绝访问任何其他资源或操作。

即通过策略来管理用户是否有权进行操作和可访问的资源等等。

  • Policy语句
    语句中的信息均含在一系列的元素内。
    Version:指定要使用的策略语言版本。建议您使用最新的 2012-10-17 版本。
    Statement:将该主要策略元素作为以下元素的容器。可以在一个策略中包含多个语句。
    Sid(可选):包括可选的语句 ID 以区分不同的语句。
    Effect:使用 Allow 或 Deny 指示策略是允许还是拒绝访问。
    Principal(仅在某些情况下需要):如果创建基于资源的策略,必须指示要允许或拒绝访问的账户、用户、角色或联合身份用户。如果要创建 IAM 权限策略以附加到用户或角色,则不能包含该元素。主体暗示为该用户或角色。
    Action:包括策略允许或拒绝的操作列表。
    Resource(仅在某些情况下需要):如果创建 IAM 权限策略,必须指定操作适用的资源列表。如果创建基于资源的策略,则该元素是可选的。如果不包含该元素,则该操作适用的资源是策略附加到的资源。
    Condition(可选):指定策略在哪些情况下授予权限。

更多IMA参考:
IAM 中的策略和权限

User

User:MinIO用户由唯一的access key (username) 和 对应的 secret key (password)组成。

客户端必须通过指定现有MinlO用户的有效access key (username)和相应的secret key (password)来验证其身份。
Groups提供了一种简化的方法,用于管理具有通用访问模式和工作负载的用户之间的共享权限。
User通过所属组继承对数据和资源的访问权限。

MinlO使用基于策略的访问控制(PBAC)来定义经过身份验证的用户有权访问的授权操作和资源。
每个策略描述一个或多个操作和条件,这些操作和条件概括了一个用户或一组用户的权限。
每个用户只能访问内置角色显式授予的资源和操作。

默认情况下,MinlO拒绝访问任何其他资源或操作。

创建User。

040

Groups

Groups可以有一个附加的IAM策略,该组中具有成员资格的所有用户都继承该策略。
Groups支持更简化的MinIO租户用户权限管理,即可以通过用户组来管理用户组下的用户权限,多对多的关系,一个组下面有可以选择多个组员,一个组员可以属于多个用户组。

创建一个组。
041

042
确认创建成功。
043

设置Groups的Policy。

044

勾选已创建的Policy。

045

确认创建完成。
046

关联策略,关联用户成员,从而实现更方便的批量权限管理。

Monitoring

Metrics

Metrics指标,MinIO 支持 Prometheus 用于指标和警报。
Prometheus 是一个开源系统和服务监控系统 支持基于收集的指标进行分析和警报。
Prometheus 生态系统包括多个 integrations ,允许广泛的处理和存储范围收集的指标。

MinIO 发布了 Prometheus 兼容的集群获取endpoint和节点级指标。
看 指标 为了 更多信息。
对于警报,请使用 Prometheus Alerting Rules 和 Alert Manager 根据收集的指标触发警报。

MinIO 使用 Prometheus 兼容数据发布收集的指标数据结构。
任何与 Prometheus 兼容的抓取软件都可以获取和处理 MinIO 指标以进行分析、可视化和警报。

如下图所示,可以看到基本信息相关的监控指标。

047

Trace

追踪,选择那些Trace的调用,然后开启Trace,下面就会记录所有的Trace,如时间,名称。状态,等等。
048

Events

时间通知,MinIO桶通知允许管理员就某些对象或桶事件向受支持的外部服务发送通知。MinIO支持桶级和对象级S3事件,类似于Amazon S3事件通知。

本质是Minio操作完成后通过事件的形式对外进行通知。

049

Minio支持多种事件的通知,如下图所示:

050

Tiering

对象生命周期管理:Tiering由MinIO对象生命周期管理使用,它允许创建基于时间或日期的对象自动转换或到期规则。对于对象迁移,MinIO会自动将对象迁移到已配置的远端存储层。

对于对象到期,MinIO 会自动删除该对象。
即对象生命周期可以用户自定义管理,但是对象过期了处理方式,Minio提供了两种选择,要么直接删除该对象,要么把该对象转换到远端的存储服务上去。

051

MinIO支持创建对象转移的生命周期管理规则,MinIO可以自动将对象移动到远程存储“Tiering”:
MinIO支持以下Tier类型:

052

Site Replication

复制站点:网站复制,此特性允许将使用相同外部IDentity Provider (IDP)的多个独立MinIO站点(或集群)配置为副本。。

即复制一个副本出来,到目标网站,快速搭建集群,以及迁移,无需再繁琐的配置等。

053

如下图所示,填写对应的站点信息,包括名字、端地址、Access Key,Secret Key。
需要填写本站的,目标站的,目标站可以多个。

054

客户端使用

mc客户端安装

MinIO Client mc命令行工具提供了ls、cat、cp、mirror和diff等命令,支持文件系统和Amazon s3兼容的云存储服务。

mc命令行工具是为与AWS S3 API兼容而构建的,并在MinIO和AWS S3上测试了预期的功能和行为。

安装mc:

[root@master01 minio]# curl https://dl.min.io/client/mc/release/linux-amd64/mc \
  --create-dirs \
  -o /usr/local/bin/mc

[root@master01 minio]# chmod +x /usr/local/bin/mc
[root@master01 minio]# mc --autocompletion              #mc自动补全

[root@master01 minio]# mc --help

连接minio:
使用mc alias set命令将Amazon s3兼容的服务添加到mc配置中,将alias替换为要关联到S3服务的名称。
mc命令通常需要alias作为参数来标识要对哪个S3服务执行,如果省略ACCESS_KEY和SECRET_KEY,执行命令时会提示在CLI中输入这些值。

[root@master01 minio]# mc alias set myminio https://api.linuxsb.com minio minio123
Added `myminio` successfully.
[root@master01 minio]# mc admin info myminio

055

bucket管理

  • 创建bucket
[root@master01 ~]# mc mb myminio/mybucket02                 #创建bucket

[root@master01 ~]# mc ls myminio                            #列出bucket
[2024-09-06 04:14:49 CST]     0B mybucket/
[2024-09-09 07:29:20 CST]     0B mybucket02/
  • 删除bucket
[root@k8s-master ~]# mc rb myminio/mybucket02               #删除没有object的bucket

[root@k8s-master ~]# mc rb myminio/mybucket02 --force       #强制删除bucket,即使含有文件

object管理

  • 上传下载
[root@master01 ~]# echo "This is my test file!" > test01.txt
[root@master01 ~]# mc cp test01.txt myminio/mybucket/test01.txt                 #上传测试文件

[root@master01 ~]# mkdir testdir                                                #创建测试目录

[root@master01 ~]# echo "This is my test02 file!" > testdir/test02.txt
[root@master01 ~]# mc cp testdir myminio/mybucket --recursive                   #上传测试目录,且递归上传

[root@master01 ~]# mc ls myminio/mybucket                                       #查看minio存储
[2024-09-10 09:32:04 CST]    22B STANDARD test01.txt
[2024-09-10 09:32:45 CST]     0B testdir/

[root@master01 ~]# mkdir download
[root@master01 ~]# mc cp myminio/mybucket/test01.txt download/                  #下载文件
[root@master01 ~]# mc cp myminio/mybucket/testdir download/ --recursive         #下载目录,且递归下载

[root@master01 ~]# ll download/                                                 #查看文件
total 4.0K
-rw-r--r-- 1 root root 22 Sep 10 09:33 test01.txt
drwxr-xr-x 2 root root 24 Sep 10 09:34 testdir

[root@master01 ~]# ll download/testdir/
total 4.0K
-rw-r--r-- 1 root root 24 Sep 10 09:34 test02.txt
  • 删除
[root@master01 ~]# mc rm myminio/mybucket/test01.txt                            #删除文件

[root@master01 ~]# mc rm myminio/mybucket/testdir --recursive --force           #删除目录,即使包含文件

[root@master01 ~]# mc ls myminio/mybucket                                       #查看minio存储

Policy管理

查看policy

列出 MinIO 上的所有预设策略。

[root@master01 ~]# mc admin policy list myminio
diagnostics
mypolicy
readonly
readwrite
writeonly
consoleAdmin

删除policy

[root@master01 ~]# mc admin policy remove myminio listbucketsonly

创建policy

创建一个新的policy。

[root@master01 ~]# vim listbucketsonly.json              #编写新的策略规则
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListAllMyBuckets"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}

[root@master01 ~]# mc admin policy create myminio listbucketsonly ./listbucketsonly.json

User管理

  • 创建用户
[root@master01 ~]# mc admin user add myminio newuser newuser123
Added user `newuser` successfully.

[root@master01 ~]# mc admin user add myminio newuser02 newuser123
Added user `newuser02` successfully.

[root@master01 ~]# mc admin user add myminio newuser03 newuser123
Added user `newuser03` successfully.
  • 禁用用户
[root@master01 ~]# mc admin user disable myminio newuser
Disabled user `newuser` successfully.
  • 启用用户
[root@master01 ~]# mc admin user enable myminio newuser
Enabled user `newuser` successfully.
  • 给用户关联策略
[root@master01 ~]# mc admin policy attach myminio listbucketsonly --user newuser
Attached Policies: [listbucketsonly]
To User: newuser

  • 创建用户组及关联用户
    创建用户组,并关联用户。
[root@master01 ~]# mc admin group add myminio somegroup newuser
Added members `newuser` to group `somegroup` successfully.

[root@master01 ~]# mc admin group add myminio somegroup newuser02
Added members `newuser02` to group `somegroup` successfully.
  • 给用户组关联策略
[root@master01 ~]# mc admin policy attach myminio listbucketsonly --group somegroup
Attached Policies: [listbucketsonly]
To Group: somegroup
  • 列出用户
    列出用户,或以json格式列出。
[root@master01 ~]# mc admin user list myminio
enabled    newuser               listbucketsonly     
enabled    newuser02                                 
enabled    newuser03                                 

[root@master01 ~]# mc admin user list myminio --json
{
 "status": "success",
 "accessKey": "newuser02",
 "userStatus": "enabled"
}
{
 "status": "success",
 "accessKey": "newuser03",
 "userStatus": "enabled"
}
{
 "status": "success",
 "accessKey": "newuser",
 "policyName": "listbucketsonly",
 "userStatus": "enabled",
 "memberOf": [
  {
   "name": "somegroup",
   "policies": [
    "listbucketsonly"
   ]
  }
 ]
}
  • 显示用户信息
    显示用户详细信息。
[root@master01 ~]# mc admin user info myminio newuser
AccessKey: newuser
Status: enabled
PolicyName: listbucketsonly
MemberOf: [somegroup]
  • 删除用户
[root@master01 ~]# mc admin user remove myminio newuser03
Removed user `newuser03` successfully.

Groups管理

  • 创建用户组并添加用户

创建用户组并添加用户

  • 从组中移除用户
[root@master01 ~]# mc admin group remove myminio somegroup newuser02
Removed members {newuser02} from group somegroup successfully.
  • 列出用户组
[root@master01 ~]# mc admin group list myminio
mygroups
somegroup
  • 禁用用户组
[root@master01 ~]# mc admin group disable myminio somegroup
Disabled group `somegroup` successfully.
  • 启用用户组
[root@master01 ~]# mc admin group enable myminio somegroup
Enabled group `somegroup` successfully.
  • 显示用户组信息
[root@master01 ~]# mc admin group info myminio somegroup
Group: somegroup
Status: enabled
Policy: listbucketsonly
Members: newuser
  • 删除用户组
    只有当用户组为空,即用户组里不存在用户时,才可删除。
[root@master01 ~]# mc admin group remove myminio somegroup
mc: <ERROR> Could not perform remove operation. The specified group is not empty - cannot remove it. (Specified group is not empty - cannot remove it).

[root@master01 ~]# mc admin group info myminio somegroup                #查看用户组信息
Group: somegroup
Status: enabled
Policy: listbucketsonly
Members: newuser

[root@master01 ~]# mc admin group remove myminio somegroup newuser      #删除仅存的用户
Removed members {newuser} from group somegroup successfully.

[root@master01 ~]# mc admin group remove myminio somegroup              #再次删除用户组
Removed group somegroup successfully.

config管理

[root@master01 ~]# mc admin config export myminio                       #获取集群配置

[root@master01 ~]# mc admin config export myminio > my-serverconfig     #获取集群配置并重定向至文件

集群管理

查看集群信息

mc工具

显示debug调试

[root@master01 ~]# mc admin info --debug myminio

json格式显示

指定显示格式。

[root@master01 ~]# mc admin info myminio --json

重启minio服务

service命令提供了一种重新启动和停止所有 MinIO 服务器的方法。

[root@master01 ~]# mc admin policy --help
[root@master01 ~]# mc admin service restart myminio
Service status: ▰▰▱ [DONE]
Summary:
    ┌───────────────┬─────────────────────────────┐
    │ Servers:      │ 4 online, 0 offline, 0 hung │
    │ Restart Time: │ 1.133381597s                │
    └───────────────┴─────────────────────────────┘

curl工具使用

curl工具参考:
curl客户端使用