MINIO是提供.NET SDK的,但是这么老的版本没找到,于是使用http的方式直接调用,方便简单。

我这里需求不复杂,只需要上传下载删除即可,如果后续有需求再补充方法。

核心代码MinioHttpOperatorDemo如下:

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Collections.Specialized; // For NameValueCollection, though not directly used in this version, good to keep for potential expansion.
using System.Security.Cryptography; // For HMACSHA256, SHA256Managed
using System.Globalization; // For CultureInfo.InvariantCulture
using System.Collections.Generic; // For SortedList
using System.Linq; // For string.Join and other LINQ operations
using System.Xml.Linq; namespace MinioHttpOperatorDemo
{
/// <summary>
/// MinIO HTTP 操作类,适用于 .NET Framework 4.0 环境,不依赖 MinIO SDK。
/// 使用 HttpWebRequest 和 HttpWebResponse 进行文件上传、下载和删除操作。
/// 已加入 AWS Signature Version 4 认证的简化实现,并进行了进一步的完善。
/// </summary>
public class MinioHttpOperator
{
private readonly string _minioEndpoint; // MinIO 服务器的端点地址,例如: http://localhost:9000
private readonly string _accessKey; // MinIO Access Key
private readonly string _secretKey; // MinIO Secret Key
private readonly string _region; // S3 兼容 API 需要的区域,MinIO 通常用 "us-east-1"
private readonly string _service; // S3 兼容 API 需要的服务名称,通常是 "s3" /// <summary>
/// 构造函数,初始化 MinIO 操作器。
/// </summary>
/// <param name="minioEndpoint">MinIO 服务器的 URL,例如 "http://localhost:9000"</param>
/// <param name="accessKey">Access Key,用于认证。</param>
/// <param name="secretKey">Secret Key,用于认证。</param>
/// <param name="region">S3 兼容 API 需要的区域,默认为 "us-east-1"。</param>
/// <param name="service">S3 兼容 API 需要的服务名称,默认为 "s3"。</param>
public MinioHttpOperator(string minioEndpoint, string accessKey, string secretKey, string region = "cn-north-1", string service = "s3")
{
// 移除末尾的斜杠,确保 URL 格式正确
_minioEndpoint = minioEndpoint.TrimEnd('/');
_accessKey = accessKey;
_secretKey = secretKey;
_region = region;
_service = service; if (string.IsNullOrEmpty(_accessKey) || string.IsNullOrEmpty(_secretKey))
{
// 抛出异常而不是警告,因为没有凭据就无法认证
throw new ArgumentNullException("AccessKey 和 SecretKey 不能为空,因为需要进行认证。");
}
} /// <summary>
/// 上传文件到 MinIO。
/// </summary>
/// <param name="bucketName">目标桶的名称。</param>
/// <param name="objectName">在桶中保存的对象名称(包含路径,例如 "myfolder/myfile.txt")。</param>
/// <param name="filePath">本地待上传文件的完整路径。</param>
/// <param name="contentType">文件的 MIME 类型,例如 "application/octet-stream"、"image/jpeg"、"text/plain"。</param>
/// <returns>如果上传成功返回 true,否则返回 false。</returns>
public bool UploadFile(string bucketName, string objectName, string filePath, string contentType = "application/octet-stream")
{
try
{
if (!File.Exists(filePath))
{
Console.WriteLine($"错误:文件未找到,路径:{filePath}");
return false;
} string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "PUT";
request.ContentType = contentType; // 计算文件内容的 SHA256 哈希值
string contentHash;
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
using (SHA256 sha256 = new SHA256Managed())
{
byte[] hashBytes = sha256.ComputeHash(fileStream);
contentHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
fileStream.Position = 0; // 重置流位置以便后续读取
request.ContentLength = fileStream.Length; // 设置请求内容长度 // 签名请求
SignRequest(request, bucketName, objectName, contentHash); // 获取请求流并写入文件内容
using (Stream requestStream = request.GetRequestStream())
{
byte[] buffer = new byte[4096]; // 4KB 缓冲区
int bytesRead;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0)
{
requestStream.Write(buffer, 0, bytesRead);
}
}
} using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode == HttpStatusCode.OK)
{
Console.WriteLine($"成功上传文件 {objectName} 到桶 {bucketName}。");
return true;
}
else
{
Console.WriteLine($"上传文件 {objectName} 失败。状态码:{response.StatusCode}");
return false;
}
}
}
catch (WebException webEx)
{
HandleWebException(webEx, "上传");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"上传时发生未知错误:{ex.Message}");
return false;
}
} /// <summary>
/// 从 MinIO 下载文件。
/// </summary>
/// <param name="bucketName">源桶的名称。</param>
/// <param name="objectName">要下载的对象名称。</param>
/// <param name="savePath">本地保存文件的完整路径。</param>
/// <returns>如果下载成功返回 true,否则返回 false。</returns>
public bool DownloadFile(string bucketName, string objectName, string savePath)
{
try
{
string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET"; // 对于 GET 请求,payload hash 是固定的空字符串的 SHA256 哈希
string contentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; // SHA256("") SignRequest(request, bucketName, objectName, contentHash); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode == HttpStatusCode.OK)
{
using (Stream responseStream = response.GetResponseStream())
using (FileStream fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
{
fileStream.Write(buffer, 0, bytesRead);
}
}
Console.WriteLine($"成功下载文件 {objectName} 到 {savePath}。");
return true;
}
else
{
Console.WriteLine($"下载文件 {objectName} 失败。状态码:{response.StatusCode}");
return false;
}
}
}
catch (WebException webEx)
{
HandleWebException(webEx, "下载");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"下载时发生未知错误:{ex.Message}");
return false;
}
} /// <summary>
/// 从 MinIO 删除文件。
/// </summary>
/// <param name="bucketName">文件所在桶的名称。</param>
/// <param name="objectName">要删除的对象名称。</param>
/// <returns>如果删除成功返回 true,否则返回 false。</returns>
public bool DeleteFile(string bucketName, string objectName)
{
try
{
string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "DELETE"; // 对于 DELETE 请求,payload hash 是固定的空字符串的 SHA256 哈希
string contentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; // SHA256("") SignRequest(request, bucketName, objectName, contentHash); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
// 成功的 DELETE 请求通常返回 204 No Content
if (response.StatusCode == HttpStatusCode.NoContent)
{
Console.WriteLine($"成功删除文件 {objectName} 从桶 {bucketName}。");
return true;
}
else
{
Console.WriteLine($"删除文件 {objectName} 失败。状态码:{response.StatusCode}");
return false;
}
}
}
catch (WebException webEx)
{
HandleWebException(webEx, "删除");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"删除时发生未知错误:{ex.Message}");
return false;
}
} /// <summary>
/// 处理 Web 请求异常并输出详细信息。
/// </summary>
/// <param name="webEx">WebException 实例。</param>
/// <param name="operation">发生异常的操作名称(例如:"上传"、"下载")。</param>
private void HandleWebException(WebException webEx, string operation)
{
if (webEx.Response != null)
{
using (StreamReader reader = new StreamReader(webEx.Response.GetResponseStream()))
{
string responseText = reader.ReadToEnd();
Console.WriteLine($"{operation}时发生 Web 异常:{webEx.Message}。状态码:{(int)((HttpWebResponse)webEx.Response).StatusCode}。响应内容:{responseText}");
}
}
else
{
Console.WriteLine($"{operation}时发生 Web 异常:{webEx.Message}");
}
} /// <summary>
/// 为 HttpWebRequest 签名 AWS Signature Version 4 认证头部。
/// 这是 AWS Signature Version 4 规范的简化实现,旨在与 MinIO 兼容。
/// </summary>
/// <param name="request">要签名的 HttpWebRequest 实例。</param>
/// <param name="bucketName">桶名称。</param>
/// <param name="objectName">对象名称。</param>
/// <param name="contentHash">请求体的 SHA256 哈希值。</param>
private void SignRequest(HttpWebRequest request, string bucketName, string objectName, string contentHash)
{
// --- 步骤 1: 创建 Canonical Request --- // 1.1 HTTP 方法
string httpRequestMethod = request.Method; // 1.2 Canonical URI
// 对象名必须进行 URI 编码,但斜杠 (/) 作为路径分隔符不能被编码。
// Uri.EscapeDataString 会编码 '/' 为 '%2F',需要将其替换回来。
// 确保 objectName 不以斜杠开头,因为 canonicalUri 会添加一个。
string cleanedObjectName = objectName.StartsWith("/") ? objectName.Substring(1) : objectName;
string encodedObjectName = Uri.EscapeDataString(cleanedObjectName).Replace("%2F", "/");
string canonicalUri = $"/{bucketName}/{encodedObjectName}"; // 1.3 Canonical Query String (本示例不处理查询参数,因此为空)
string canonicalQueryString = ""; // 1.4 Canonical Headers
// 头部名称必须小写,并按字典序排序。
// 头部值必须去除前导/尾随空格,多个空格替换为单个空格。
// Host 头部必须包含端口(如果是非默认端口)。
var headersToSign = new SortedList<string, string>(); // Host 头部
string hostHeaderValue = request.RequestUri.Host;
if (!request.RequestUri.IsDefaultPort)
{
hostHeaderValue += ":" + request.RequestUri.Port;
}
headersToSign.Add("host", hostHeaderValue); // x-amz-content-sha256 头部
headersToSign.Add("x-amz-content-sha256", contentHash); // x-amz-date 头部
DateTime requestDateTime = DateTime.UtcNow;
string amzDate = requestDateTime.ToString("yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture);
headersToSign.Add("x-amz-date", amzDate); // Content-Type 头部 (仅用于 PUT/POST 请求)
if (request.Method == "PUT" || request.Method == "POST")
{
string actualContentType = request.ContentType;
if (string.IsNullOrEmpty(actualContentType))
{
actualContentType = "application/octet-stream"; // 签名时使用的默认 Content-Type
}
headersToSign.Add("content-type", actualContentType);
} // 构建 canonicalHeaders 字符串
StringBuilder canonicalHeadersBuilder = new StringBuilder();
foreach (var header in headersToSign)
{
canonicalHeadersBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}:{1}\n", header.Key, header.Value.Trim()); // trim header values
}
string canonicalHeaders = canonicalHeadersBuilder.ToString(); // 1.5 Signed Headers
// 包含在规范化头部中所有头部名称的列表,小写,按字典序排序,用分号分隔。
string signedHeaders = string.Join(";", headersToSign.Keys.ToArray()); // 1.6 Payload Hash (已在方法参数中提供) // 1.7 组合 Canonical Request
string canonicalRequest = string.Format(CultureInfo.InvariantCulture,
"{0}\n{1}\n{2}\n{3}\n{4}\n{5}",
httpRequestMethod,
canonicalUri,
canonicalQueryString,
canonicalHeaders, // 注意这里已经包含了末尾的换行符
signedHeaders,
contentHash); // --- 步骤 2: 创建 String to Sign --- // Algorithm
string algorithm = "AWS4-HMAC-SHA256"; // Credential Scope
string dateStamp = requestDateTime.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
string credentialScope = string.Format(CultureInfo.InvariantCulture,
"{0}/{1}/{2}/aws4_request",
dateStamp,
_region,
_service); // Hash of Canonical Request
string hashedCanonicalRequest = ToHex(Hash(Encoding.UTF8.GetBytes(canonicalRequest))); // 组合 String to Sign
string stringToSign = string.Format(CultureInfo.InvariantCulture,
"{0}\n{1}\n{2}\n{3}",
algorithm,
amzDate,
credentialScope,
hashedCanonicalRequest); // --- 步骤 3: 计算签名 --- // Signing Key 派生
byte[] kSecret = Encoding.UTF8.GetBytes("AWS4" + _secretKey);
byte[] kDate = HmacSha256(kSecret, dateStamp);
byte[] kRegion = HmacSha256(kDate, _region);
byte[] kService = HmacSha256(kRegion, _service);
byte[] kSigning = HmacSha256(kService, "aws4_request"); // 计算最终签名
byte[] signatureBytes = HmacSha256(kSigning, stringToSign);
string signature = ToHex(signatureBytes); // --- 步骤 4: 添加 Authorization 头部 ---
string authorizationHeader = string.Format(CultureInfo.InvariantCulture,
"{0} Credential={1}/{2}, SignedHeaders={3}, Signature={4}",
algorithm,
_accessKey,
credentialScope,
signedHeaders,
signature); request.Headers["Authorization"] = authorizationHeader; // 设置 x-amz-date 头部(如果尚未设置)
request.Headers["x-amz-date"] = amzDate;
// 设置 x-amz-content-sha256 头部(如果尚未设置)
request.Headers["x-amz-content-sha256"] = contentHash;
} /// <summary>
/// 计算字节数组的 SHA256 哈希值。
/// </summary>
private static byte[] Hash(byte[] bytes)
{
using (SHA256 sha256 = new SHA256Managed())
{
return sha256.ComputeHash(bytes);
}
} /// <summary>
/// 计算 HMAC-SHA256 哈希值。
/// </summary>
private static byte[] HmacSha256(byte[] key, string data)
{
using (HMACSHA256 hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
}
} /// <summary>
/// 将字节数组转换为十六进制字符串。
/// </summary>
private static string ToHex(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
}
}
}

测试代码如下:

using System;
using System.IO;
using System.Threading; namespace MinioHttpOperatorDemo
{
class Program
{
static void Main(string[] args)
{ // 替换为您的 MinIO 实例的地址
string minioEndpoint = "http://127.xxx.xxx.xxx:9000"; // 如果您的 MinIO 实例需要认证,请在这里提供您的 AccessKey 和 SecretKey。
// 例如:
string accessKey = "accessKey ";
string secretKey = "secretKey ";
MinioHttpOperator minioOperator = new MinioHttpOperator(minioEndpoint, accessKey, secretKey); // 如果 MinIO 允许匿名访问,则无需提供 AccessKey 和 SecretKey
//MinioHttpOperator minioOperator = new MinioHttpOperator(minioEndpoint); // 确保此桶在 MinIO 中存在或 MinIO 服务器允许自动创建桶。
string bucketName = "bucketName ";
string testFolder = "test"; // --- 准备多个测试文件 ---
string localFilePath1 = Path.Combine(Path.GetTempPath(), "testfile1.txt");
string localFilePath2 = Path.Combine(Path.GetTempPath(), "testfile2.jpg"); // 模拟图片文件
string minioObjectName1 = $"{testFolder}/document1.txt";
string minioObjectName2 = $"{testFolder}/image.jpg"; string content1 = "This is the content for document one.";
byte[] content2 = new byte[1024]; // 模拟一个1KB的二进制数据作为图片内容
new Random().NextBytes(content2); // 填充随机字节 try
{
File.WriteAllText(localFilePath1, content1);
File.WriteAllBytes(localFilePath2, content2);
Console.WriteLine($"已在本地创建测试文件:{localFilePath1} 和 {localFilePath2}");
}
catch (Exception ex)
{
Console.WriteLine($"创建测试文件失败:{ex.Message}");
Console.WriteLine("请检查文件路径和权限。程序将退出。");
Console.ReadKey();
return;
} Console.WriteLine("\n--- 开始 MinIO 批量操作 ---"); // --- 上传第一个文件 ---
Console.WriteLine($"\n尝试上传文件:{localFilePath1} 到 {minioEndpoint}/{bucketName}/{minioObjectName1}...");
if (minioOperator.UploadFile(bucketName, minioObjectName1, localFilePath1, "text/plain"))
{
Console.WriteLine("文件上传成功。");
}
else
{
Console.WriteLine("文件上传失败。");
}
Thread.Sleep(1000); // 稍作等待 // --- 上传第二个文件 ---
Console.WriteLine($"\n尝试上传文件:{localFilePath2} 到 {minioEndpoint}/{bucketName}/{minioObjectName2}...");
if (minioOperator.UploadFile(bucketName, minioObjectName2, localFilePath2, "image/jpeg"))
{
Console.WriteLine("文件上传成功。");
}
else
{
Console.WriteLine("文件上传失败。");
}
Thread.Sleep(1000); // 稍作等待 Console.WriteLine("\n----------------------------------------"); // --- 下载第一个文件 ---
string downloadSavePath1 = Path.Combine(Path.GetTempPath(), "downloaded_document1.txt");
Console.WriteLine($"\n尝试从 {minioEndpoint}/{bucketName}/{minioObjectName1} 下载文件到 {downloadSavePath1}...");
if (minioOperator.DownloadFile(bucketName, minioObjectName1, downloadSavePath1))
{
Console.WriteLine("文件下载成功。");
try
{
Console.WriteLine($"下载文件的内容:{File.ReadAllText(downloadSavePath1)}");
}
catch (Exception ex)
{
Console.WriteLine($"读取下载文件内容失败:{ex.Message}");
}
}
else
{
Console.WriteLine("文件下载失败。");
}
Thread.Sleep(1000); // 稍作等待 // --- 尝试下载一个不存在的文件 ---
string nonExistentObject = $"{testFolder}/nonexistent.pdf";
string downloadNonExistentPath = Path.Combine(Path.GetTempPath(), "nonexistent.pdf");
Console.WriteLine($"\n尝试下载不存在的文件:{nonExistentObject}...");
if (!minioOperator.DownloadFile(bucketName, nonExistentObject, downloadNonExistentPath))
{
Console.WriteLine("下载不存在的文件失败(预期结果)。");
}
Thread.Sleep(1000); // 稍作等待 Console.WriteLine("\n----------------------------------------"); // --- 删除第一个文件 ---
Console.WriteLine($"\n尝试从 {minioEndpoint}/{bucketName} 删除文件 {minioObjectName1}...");
if (minioOperator.DeleteFile(bucketName, minioObjectName1))
{
Console.WriteLine("文件删除成功。");
}
else
{
Console.WriteLine("文件删除失败。");
}
Thread.Sleep(1000); // 稍作等待 // --- 删除第二个文件 ---
Console.WriteLine($"\n尝试从 {minioEndpoint}/{bucketName} 删除文件 {minioObjectName2}...");
if (minioOperator.DeleteFile(bucketName, minioObjectName2))
{
Console.WriteLine("文件删除成功。");
}
else
{
Console.WriteLine("文件删除失败。");
} Console.WriteLine("\n--- MinIO 批量操作结束 ---"); // 清理本地创建的测试文件
try
{
if (File.Exists(localFilePath1))
{
File.Delete(localFilePath1);
Console.WriteLine($"已清理本地测试文件:{localFilePath1}");
}
if (File.Exists(localFilePath2))
{
File.Delete(localFilePath2);
Console.WriteLine($"已清理本地测试文件:{localFilePath2}");
}
if (File.Exists(downloadSavePath1))
{
File.Delete(downloadSavePath1);
Console.WriteLine($"已清理本地下载文件:{downloadSavePath1}");
}
if (File.Exists(downloadNonExistentPath))
{
File.Delete(downloadNonExistentPath);
Console.WriteLine($"已清理本地下载文件:{downloadNonExistentPath}");
}
}
catch (Exception ex)
{
Console.WriteLine($"清理本地文件失败:{ex.Message}");
} Console.WriteLine("\n按任意键退出程序。");
Console.ReadKey();
}
}
}

.NET4通过HTTP操作MINIO的更多相关文章

  1. 四、C#简单操作MinIO

    MinIO的官方网站非常详细,以下只是本人学习过程的整理 一.MinIO的基本概念 二.Windows安装与简单使用MinIO 三.Linux部署MinIO分布式集群 四.C#简单操作MinIO He ...

  2. 使用terraform-provider-s3 操作minio

    尽管默认官方提供了s3 的操作,但是对于开源minio 无法支持,更多的是aws 的s3,社区提供了一个通用 s3 操作的provider(基于minio 的sdk) 环境准备 docker-comp ...

  3. 第十二章 管理类型(In .net4.5) 之 操作字符串

    1. 概述 本章包括 字符串基本操作 以及 查找.遍历.格式化字符串. 2. 主要内容 2.1 在.net平台中使用字符串 C#中,string是用来保存文本信息的.是一个被当做值类型使用的引用类型. ...

  4. vs2010 net4.0 c# 操作 sqlite

    1.百科介绍 SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中.它是D.RichardHipp建立的公有领域项目.它的设计目标是嵌入式的,而且目前已经 ...

  5. 利用 MinIO 轻松搭建静态资源服务

    目录 1 引言 2 MinIO 简介 3 MinIO 运行与静态资源使用 3.1 MinIO 获取 3.2 MinIO 启动与运行 3.2.1 前台简单启动 3.2.2 后台指定参数运行 3.2.3 ...

  6. 三、Linux部署MinIO分布式集群

    MinIO的官方网站非常详细,以下只是本人学习过程的整理 一.MinIO的基本概念 二.Windows安装与简单使用MinIO 三.Linux部署MinIO分布式集群 四.C#简单操作MinIO 一. ...

  7. 二、Windows安装与简单使用MinIO

    MinIO的官方网站非常详细,以下只是本人学习过程的整理 一.MinIO的基本概念 二.Windows安装与简单使用MinIO 三.Linux部署MinIO分布式集群 四.C#简单操作MinIO 一. ...

  8. 一、MinIO的基本概念

    MinIO的官方网站非常详细,以下只是本人学习过程的整理 一.MinIO的基本概念 二.Windows安装与简单使用MinIO 三.Linux部署MinIO分布式集群 四.C#简单操作MinIO 一. ...

  9. SpringBoot 搭建基于 MinIO 的高性能存储服务

    1.什么是MinIO MinIO是根据GNU Affero通用公共许可证v3.0发布的高性能对象存储.它与Amazon S3云存储服务兼容.使用MinIO构建用于机器学习,分析和应用程序数据工作负载的 ...

  10. 云原生分布式文件存储 MinIO 教程

    文章转载自:https://mp.weixin.qq.com/s/_52kZ5jil1Cec98P5oozoA MinIO 提供开源.高性能.兼容 s3 的对象存储,为每个公共云.每个 Kuberne ...

随机推荐

  1. kubernetes service 原理精讲

    --- # 介绍 Kubernetes Service 用于流量的负载均衡和反向代理,其通过 kube-proxy 组件实现.从服务的角度来看,kube-controller-manager 实现了服 ...

  2. MCP与华为云CSE珠联璧合,打造AI时代微服务生态引擎

    本文分享自华为云社区<MCP与华为云CSE珠联璧合,打造AI时代微服务生态引擎>,作者:华为云社区精选 从 AI 技术的"火热概念"到"实际业务的落地&quo ...

  3. 网络编程:epoll

    原理 select 的几个缺点: 1)每次调用select,都需要把fd集合从用户空间拷贝到内核空间,这个开销在fd很多时会很大 2)每次调用select都需要在内核遍历传递进来的所有fd,这个开销在 ...

  4. 数据库迁移的艺术:FastAPI生产环境中的灰度发布与回滚策略

    title: 数据库迁移的艺术:FastAPI生产环境中的灰度发布与回滚策略 date: 2025/05/17 21:06:56 updated: 2025/05/17 21:06:56 author ...

  5. [2025.3.9~2025.3.15 鲜花/省选后记/rain] ほら逃げられないわ

    [2025.3.9~2025.3.15 鲜花/省选后记] ほら逃げられないわ 堪称大型sm游戏的whk学习... 省选完后要补whk,一开始说的是网课,后面返校那天班主任说是线下课,那咋受得了的啊,加 ...

  6. [abc302f] Merge Set

    F - Merge Set 显然要建图 首先,我们有一个粗略的想法,对于同一集合\(S_i\)内的元素,\(S_{i,j}\)与\(S_{i,j+1}\)间连一条无向的标号为\(i\)的边 那么题目显 ...

  7. .Net 6 的类库中添加 IWebHostEnvironment 接口类时找不到

    通过添加 Microsoft.AspNetCore.Hosting.Abstractions.dll 包是没有效果的 需要添加的包叫做:MiniProfiler.AspNetCore.Mvc

  8. java--Hibernate查询、连接池、二级缓存

    hibernate查询 1) Get/load主键查询 2) 对象导航查询 3) HQL查询, Hibernate Query language hibernate 提供的面向对象的查询语言. 4) ...

  9. java实现聊天,服务端与客户端代码(UDP)-狂神改

    首先是文件结构: 最后run的是下面两个 代码用的狂神的,不过他写的有点小bug,比如传信息会出现一堆空格(recieve data那里长度不应该用data.lenth()而应该用packet.get ...

  10. 前端开发系列059-网络篇之网络基础知识和HTTP协议

    一.网络编程基本概念 1.1 客户端和服务器的基本概念 客户端(Client)能从服务器获取资源为客户提供服务的程序(设备). 服务器(Server)为客户端提供服务.提供数据.提供资源的机器. 说明 ...