SharpIcoWeb开发记录篇
SharpIcoWeb开发记录篇
前言
大佬用.NET 9.0开发了SharpIco轻量级图标生成工具,是一款控制台应用程序,支持AOT发布,非常方便。
功能特点
- ️ 将PNG图像转换为多尺寸ICO图标
- 支持生成包含自定义尺寸的ICO图标(最高支持1024×1024)
- 检查ICO文件的内部结构和信息
- 准确识别并显示超大尺寸图标(如512×512、1024×1024)的实际尺寸
直达地址:https://github.com/star-plan/sharp-ico
现在互联网上应该有很多这类小工具或者网站,我也想部署把这个小工具部署成网站,当然不要和别人网站一模一样,所有后端我采用 .NET Core Minimal API
去开发。
大佬开发的这款小工具可以直接在集成 .NET Core
中,所以我只需要写一个上传文件的接口就行了。用了 .NET
这么久,写接口一直用的是 WebApi
,这次尝试下 Minimal Api
。
Minimal Api 简介
在使用 ASP.NET Core 生成快速 HTTP API 时,可以将最小 API 作为一种简化的方法。 可以使用最少的代码和配置生成完全正常运行的 REST 终结点。 跳过传统的基架,并通过流畅地声明 API 路由和操作来避免不必要的控制器。
主要特点
- 简洁的语法:使用更少的代码定义 API 端点
- 减少样板代码:不需要控制器类
- 内置依赖注入:简化服务配置
- 路由和请求处理一体化:直接在路由定义中处理请求
- 支持所有 ASP.NET Core 功能:中间件、认证、授权等
架构概述
MiniAPI
的核心架构包括以下几个关键组件:
- WebApplication:这是整个应用的入口点和宿主。它负责配置服务、中间件和路由。
- Endpoints:这些是API的终点,也就是处理特定HTTP请求的地方。
- Handlers:这些是实际处理请求并生成响应的函数。
- Middleware:这些组件在请求到达handler之前和之后处理请求。
关键术语
在进行开发前,先了解下什么是 Endpoints
、Handlers
、Middleware
。
Endpoints(端点):Endpoints是一个特定的URL路径,与一个HTTP方法相关联。
app.MapGet("/hello", () => "Hello, World!");
Handlers(处理器):Handlers是一个函数,它接收http请求并返回响应。在
MiniAPIs
中handler可以是一个简单的lambda表达式,也可以是一个单独定义的方法。例如:app.MapGet("/users/{id}", (int id) => $"User ID: {id}");
Middleware(中间件):与WebApi一样,MiniAPIs中也可以使用中间件,它们可以在请求到达handler之前执行操作,也可以在handler处理完请求后修改响应。例如,你可以使用中间件来处理身份验证、日志记录或异常处理。
开发
了解完一些 Minimal Api
基础知识后,就可以开始开发了。首先创建 MiniMal Api
模版的 .NET 9 Api
程序。
模版默认为创建一个示例接口,看到这个接口,看起来和 WebApi
写法差别不大,但是代码是直接写在 Program.cs
文件中,无需创建控制器。
接下来开发一个上传文件的接口,然后调用大佬开发的工具就ok了。
在 MiniAPI
中也可以使用依赖注入,那么这里创建一个处理文件的服务,然后再注入这个服务在接口中使用。
项目结构
可以看到我的项目可以直接引用 SharpIco
工具,这里右键 SharpIco
编辑csproj文件将项目类型改为类库就可以直接调用了。
<PropertyGroup>
<!-- 改为生成库而不是控制台应用 -->
<OutputType>Library</OutputType>
</PropertyGroup>
IFileService接口
public interface IFileService
{
// 检查文件是否有效
bool IsFileValid(IFormFile file);
// 保存上传的文件
Task<string> SaveUploadedFile(IFormFile file);
// 创建临时目录
string GetTempDirectory();
// 读取文件到内存流
Task<MemoryStream> ReadFileToMemoryAsync(string filePath);
// 删除临时文件
void DeleteFile(string tempFilePath);
}
FileService类
这里使用了一个变量 _tempFiles
去记录上传的临时文件,然后实现IDisposable接口自动清理临时文件,确保上传的图片不会留存到硬盘里面。
一开始我的设计思路就是在 wooroot
中去建立一个待转换和转换后的目录取处理图片,但转念一想,作为一款图片转ico的工具,还是不要留存图片好一点,所以才采用了临时目录这个办法。
public class FileService : IFileService, IDisposable
{
private readonly List<string> _tempFiles = new();
public string GetTempDirectory()
{
var tempDir = Path.Combine(Path.GetTempPath(), "SharpIcoTemp");
Directory.CreateDirectory(tempDir);
return tempDir;
}
public bool IsFileValid(IFormFile file)
{
var allowedExtensions = new[] { ".png", ".jpg", ".jpeg" };
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
return file.Length is > 0 and < 10 * 1024 * 1024 && // 10MB
allowedExtensions.Contains(extension);
}
// 保存上传的文件
public async Task<string> SaveUploadedFile(IFormFile file)
{
var tempDir = GetTempDirectory();
var tempFilePath = Path.Combine(tempDir, $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}");
await using var stream = new FileStream(tempFilePath, FileMode.Create);
await file.CopyToAsync(stream);
_tempFiles.Add(tempFilePath); // 记录待清理文件
return tempFilePath;
}
// 安全读取文件到内存流
public async Task<MemoryStream> ReadFileToMemoryAsync(string filePath)
{
var memoryStream = new MemoryStream();
await using (var fileStream = File.OpenRead(filePath))
{
await fileStream.CopyToAsync(memoryStream);
}
memoryStream.Position = 0;
return memoryStream;
}
public void DeleteFile(string tempFilePath)
{
if (File.Exists(tempFilePath))
File.Delete(tempFilePath);
}
// 实现IDisposable接口自动清理
public void Dispose()
{
foreach (var file in _tempFiles)
{
try
{
if (File.Exists(file))
File.Delete(file);
}
catch { /* 忽略删除异常 */ }
}
GC.SuppressFinalize(this);
}
}
Program类
服务写完后,接下来在配置类中注入服务并完成接口编写就大功告成了。
注入服务:
builder.Services.AddScoped<IFileService, FileService>();
接口编写:
app.MapPost("/api/uploadDownload", async ([FromForm] IFormFile file,[FromForm] string? sizes,IFileService fileService, ILogger<Program> logger) =>
{
try
{
// 验证输入
if (!fileService.IsFileValid(file))
{
return Results.BadRequest("请上传有效文件,文件大小不能超过10MB");
}
// 保存临时文件
var tempFilePath = await fileService.SaveUploadedFile(file);
// 生成输出路径
var outputPath = Path.Combine(fileService.GetTempDirectory(), $"{Guid.NewGuid()}.ico");
// 执行转换 处理大小参数
var sizesArray = sizes?.Split(',').Select(int.Parse).ToArray();
if (sizesArray is { Length: > 0 })
IcoGenerator.GenerateIcon(tempFilePath, outputPath, sizesArray);
else
IcoGenerator.GenerateIcon(tempFilePath, outputPath);
// 读取到内存
var memoryStream = await fileService.ReadFileToMemoryAsync(outputPath);
// 删除临时文件
fileService.DeleteFile(outputPath);
logger.LogInformation($"{DateTime.UtcNow} 文件 {file.FileName} 转换成功");
return Results.File(memoryStream, "image/x-icon");
}
catch (Exception ex)
{
logger.LogError(ex, $"{DateTime.UtcNow} 处理文件时发生错误");
return Results.Problem("处理文件时发生错误");
}
}).DisableAntiforgery();
接口测试
这里直接使用模版自带的http文件进行测试,这个还是第一次使用,还折腾了挺久。
SharpIcoWeb.http
文件
简单介绍下,第一个请求只上传了图片文件,未指定尺寸参数,后端接口会默认生成16,32,48,64,128,256,512,1024 尺寸的ico文件。
第二个请求就是添加了尺寸参数的请求。
@SharpIcoWeb_HostAddress = http://localhost:5235
### 上传文件并转换为ICO(不带尺寸参数)
POST {{SharpIcoWeb_HostAddress}}/api/uploadDownload
Content-Type: multipart/form-data; boundary=WebAppBoundary
--WebAppBoundary
Content-Disposition: form-data; name="file"; filename="1.png"
Content-Type: image/png
< ./1.png
--WebAppBoundary--
### 上传文件并转换为ICO(带尺寸参数)
POST {{SharpIcoWeb_HostAddress}}/api/uploadDownload
Content-Type: multipart/form-data; boundary=WebAppBoundary
--WebAppBoundary
Content-Disposition: form-data; name="file"; filename="1.png"
Content-Type: image/png
< ./1.png
--WebAppBoundary
Content-Disposition: form-data; name="sizes"
16,32,48,64,128
测试结果
关键代码
IcoGenerator.GenerateIcon(tempFilePath, outputPath, sizesArray);
这里是调用SharpIco工具提供的方法。
app.MapPost().DisableAntiforgery()
DisableAntiforgery作用是禁用 ASP.NET Core 的防伪造令牌验证。
相关链接
- SharpIco:https://github.com/star-plan/sharp-ico
- SharpIcoWeb:https://github.com/ZyPLJ/SharpIcoWeb
- 最小API官方文档:https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/minimal-apis/overview?view=aspnetcore-9.0
- 最小API学习指南:https://blog.csdn.net/xiaohucxy/article/details/140134927
SharpIcoWeb开发记录篇的更多相关文章
- Python全栈开发记录_第一篇(循环练习及杂碎的知识点)
Python全栈开发记录只为记录全栈开发学习过程中一些难和重要的知识点,还有问题及课后题目,以供自己和他人共同查看.(该篇代码行数大约:300行) 知识点1:优先级:not>and 短路原则:a ...
- .Net Core ORM选择之路,哪个才适合你 通用查询类封装之Mongodb篇 Snowflake(雪花算法)的JavaScript实现 【开发记录】如何在B/S项目中使用中国天气的实时天气功能 【开发记录】微信小游戏开发入门——俄罗斯方块
.Net Core ORM选择之路,哪个才适合你 因为老板的一句话公司项目需要迁移到.Net Core ,但是以前同事用的ORM不支持.Net Core 开发过程也遇到了各种坑,插入条数多了也特别 ...
- 【学习记录】第一章 数据库设计-《SQL Server数据库设计和开发基础篇视频课程》
一.课程笔记 1.1 软件开发周期 (1)需求分析阶段 分析客户的业务和数据处理需求. (2)概要设计阶段 设计数据库的E-R模型图,确认需求信息的正确和完整. /* E-R图:实体-关系图(Ent ...
- CozyRSS开发记录12-MVVM,绑定RSS源和数据
CozyRSS开发记录12-MVVM,绑定RSS源和数据 1.引入MvvmLight MVVM最近貌似在前端那块也挺火的.据说,WPF的程序如果不用MVVM,那跟MFC和winform的,也没啥区别. ...
- iOS开发数据库篇—SQLite简单介绍
iOS开发数据库篇—SQLite简单介绍 一.离线缓存 在项目开发中,通常都需要对数据进行离线缓存的处理,如新闻数据的离线缓存等. 说明:离线缓存一般都是把数据保存到项目的沙盒中.有以下几种方式 (1 ...
- iOS开发数据库篇—SQL
iOS开发数据库篇—SQL 一.SQL语句 如果要在程序运行过程中操作数据库中的数据,那得先学会使用SQL语句 1.什么是SQL SQL(structured query language):结构化查 ...
- iOS开发数据库篇—SQL代码应用示例
iOS开发数据库篇—SQL代码应用示例 一.使用代码的方式批量添加(导入)数据到数据库中 1.执行SQL语句在数据库中添加一条信息 插入一条数据的sql语句: 点击run执行语句之后,刷新数据 2.在 ...
- iOS开发数据库篇—SQLite的应用
iOS开发数据库篇—SQLite的应用 一.简单说明 在iOS中使用SQLite3,首先要添加库文件libsqlite3.dylib和导入主头文件. 导入头文件,可以使用库中的函数(是纯C语言的) 二 ...
- IOS开发数据库篇—SQLite模糊查询
IOS开发数据库篇—SQLite模糊查询 一.示例 说明:本文简单示例了SQLite的模糊查询 1.新建一个继承自NSObject的模型 该类中的代码: // // YYPerson.h // 03- ...
- iOS开发数据库篇—SQLite常用的函数
iOS开发数据库篇—SQLite常用的函数 一.简单说明 1.打开数据库 int sqlite3_open( const char *filename, // 数据库的文件路径 sqlite3 * ...
随机推荐
- langchain0.3教程:从0到1打造一个智能聊天机器人
在上一篇文章<大模型开发之langchain0.3(一):入门篇> 中已经介绍了langchain开发框架的搭建,最后使用langchain实现了HelloWorld的代码案例,本篇文章将 ...
- java的反射是要先实例化的!
java两种获得反射的方法 ,一种是Class.forName("A"); 另一种是 A a = new A(); a.getClass(); 第二种是自己实例化之后,我们在类的静 ...
- 让 LLM 来评判 | 技巧与提示
这是 让 LLM 来评判 系列文章的第六篇,敬请关注系列文章: 基础概念 选择 LLM 评估模型 设计你自己的评估 prompt 评估你的评估结果 奖励模型相关内容 技巧与提示 LLM 评估模型已知偏 ...
- eolinker响应预处理:传参解决方法(截取返回数据中的某一段数据,正则截取)
特别注意:需要使用全局变量或者预处理前务必阅读本链接https://www.cnblogs.com/becks/p/13713278.html 场景描述: 登录用例A,参加活动用例B,用户参加活动需要 ...
- 题解:P10983 [蓝桥杯 2023 国 Python A] 跑步计划
一眼看,什么 py,不是纯计算题吗? 需要知道的 2023 年是平年,有 365 天. 每个月分别有 31,28,31,30,31,30,31,31,30,31,30,31 天. 计算 一月,十月,十 ...
- Spring编程式事务控制
目录 Spring编程式事务控制 代码实现 测试 Spring编程式事务控制 实际中很少使用 代码实现 pom.xml <?xml version="1.0" encodin ...
- Spring中的依赖注入DI
目录 Spring中的依赖注入DI Spring中的依赖注入DI 依赖注入的简单理解就是给对象设置变量值. Spring配置文件 <?xml version="1.0" en ...
- SpringBoot3特性——错误信息Problemdetails
Spring Framework 6 实现了 HTTP API 规范 RFC 7807 的问题详细信息. 在本文中,我们将学习如何在 SpringBoot 3 REST API(使用 Spring F ...
- Polarctf -- Re(1)
Polarctf之简单逆向 1. shell 用exeinfope查看下程序结构, 发现存在upx壳 用upx工具脱壳, upx.exe -d shell.exe 再使用IDAPro打开 #flag{ ...
- 鸿蒙NEXT(五):鸿蒙版React Native架构浅析
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...