前言
不管是官方自带模板还是其他开源搞的,总是一来一大堆,如果你也嫌弃这些过于臃肿,不如看看我这个方式
已开源,没啥技术含量,只是一个思路
开源地址:https://github.com/DotNetGoodMorning/SimpleWaiBao/tree/main
正文
1、前提
假设我要做一个简单的API
2、方式
想到清爽,那肯定是简单方便,脑袋第一个念头就是.NET 6 推出的miniapi了
3、官方路子
- 使用 ASP.NET Core 创建最小 API
- 最小 API 快速参考
两篇官方文档足以,按照文档step by step 就ok了,其他的需要就加
我的野路子
官方是官方,官方走的路子当然还是基于它最标准的搞法,我的路子则是基于国内实际情况 总体思路就是用控制台改api
模拟前提场景
搞一个普通企业官网的api,那么要求就是以下几点
- 需要数据库操作
- 需要授权鉴权
- 需要swagger文档
- 需要上传文件
根据这些要求,我需要引入最基本的就几个:
- Swashbuckle.AspNetCore (swagger相关)
- SqlSugarCore (sqlsugar Orm) (用啥都可以,例如还有freesql)
- Microsoft.AspNetCore.Authentication.JwtBearer (授权鉴权这里用简单的jwt)
- Mapster (dto和entity互转)
如果有其他需求,再自己加,一点也不冗余
注意:需要先右键控制台项目,将 <Project Sdk="Microsoft.NET.Sdk">
改为 <Project Sdk="Microsoft.NET.Sdk.Web">
其原因可以去官网翻找资料感悟一下
代码
dotNet 几忘了,反正很早就是通用主机了,如果你同时还要搞blazor server啥的,把这一坨封装一下即可,通用的
Program.cs
里面直接无脑写下以下代码
var builder = WebApplication.CreateBuilder(args); | |
builder.Services.AddMemoryCache(); | |
builder.Services.AddControllers(); | |
builder.Services.Configure<FormOptions>(options => | |
{ | |
// 设置上传大小限制256MB | |
options.MultipartBodyLengthLimit = 268435456; | |
}); | |
builder.Services.AddSingleton<SqlSugarMemoryCacheService>(); | |
// 添加身份验证和授权中间件 | |
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) | |
.AddJwtBearer(options => | |
{ | |
options.TokenValidationParameters = new TokenValidationParameters | |
{ | |
ValidateIssuer = true, | |
ValidateAudience = true, | |
ValidateLifetime = true, | |
ValidateIssuerSigningKey = true, | |
ValidIssuer = "ningissuer", | |
ValidAudience = "wr", | |
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("sdfsdfsrty45634kkhxxhtdgdfss345t678xx")) | |
}; | |
}); | |
builder.Services.AddAuthorization(options => | |
{ | |
options.AddPolicy("AdminOnly", policy => | |
{ | |
policy.RequireClaim("role", "admin"); | |
}); | |
}); | |
builder.Services.AddEndpointsApiExplorer(); | |
builder.Services.AddSwaggerGen(c => | |
{ | |
c.SwaggerDoc("v1", new OpenApiInfo { Title = "企业官网Api", Version = "v1" }); | |
// 添加身份验证 | |
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme | |
{ | |
Description = "JWT Authorization header using the Bearer scheme", | |
Name = "Authorization", | |
In = ParameterLocation.Header, | |
Type = SecuritySchemeType.ApiKey | |
}); | |
// 添加授权要求 | |
c.AddSecurityRequirement(new OpenApiSecurityRequirement | |
{ | |
{ | |
new OpenApiSecurityScheme | |
{ | |
Reference = new OpenApiReference | |
{ | |
Type = ReferenceType.SecurityScheme, | |
Id = "Bearer" | |
} | |
}, | |
new string[] {} | |
} | |
}); | |
// 设置 XML 注释文件的路径 | |
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; | |
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); | |
c.IncludeXmlComments(xmlPath); | |
}); | |
var app = builder.Build(); | |
app.UseSwagger(); | |
app.UseStaticFiles(); | |
// 启用身份验证和授权中间件 | |
app.UseAuthentication(); | |
app.UseRouting(); | |
app.UseAuthorization(); | |
app.UseEndpoints(endpoints => | |
{ | |
endpoints.MapControllers(); // 这里配置了使用控制器的路由 | |
}); | |
app.UseSwaggerUI(c => | |
{ | |
c.SwaggerEndpoint("/swagger/v1/swagger.json", "企业官网Api"); | |
c.RoutePrefix = string.Empty; // 将 Swagger UI 设置为应用程序的根路径 | |
}); | |
ServiceLocator.Instance = app.Services; | |
ServiceLocator.ApplicationBuilder = app; | |
var db = SqlSugarHelper.Db; | |
//数据库初始化 | |
app.MapGet("/seed", async () => | |
{ | |
db.CodeFirst.InitTables<SysUserEntity>(); | |
string name = "op"; | |
string pwd = "op"; | |
var loginResult = await db.Queryable<SysUserEntity>().Where(a => !a.IsBan && a.UsePwd == pwd && a.UserName == name).AnyAsync(); | |
if (!loginResult) | |
{ | |
await db.Insertable<SysUserEntity>(new SysUserEntity { IsBan = false, UsePwd = pwd, UserName = name }).ExecuteCommandAsync(); | |
} | |
db.CodeFirst.InitTables<FileSourceEntity>(); | |
db.CodeFirst.InitTables<ArticleEntity>(); | |
}); | |
app.MapGet("/health", () => "1024"); | |
app.Run(); |
接口就"勉为其难"的新建个api文件夹然后
/// <summary> | |
/// 系统用户 | |
/// </summary> | |
[ | ]|
[ | ]|
public class SysUserController : BaseApi | |
{ | |
public SysUserController() | |
{ | |
} | |
/// <summary> | |
/// 检测Token信息 | |
/// </summary> | |
/// <returns></returns> | |
[ | ]|
[ | ]|
public ApiResult CheckToken() | |
{ | |
var httpContext = HttpContext; | |
// 从请求头中获取 Authorization 标头的值 | |
var authorizationHeader = httpContext.Request.Headers["Authorization"].FirstOrDefault(); | |
if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.StartsWith("Bearer ")) | |
{ | |
// 提取令牌字符串(去除 "Bearer " 前缀) | |
var token = authorizationHeader.Substring(7); | |
var tokenHandler = new JwtSecurityTokenHandler(); | |
var jwtToken = tokenHandler.ReadJwtToken(token); | |
// 获取 ClaimTypes.Name 的值 | |
var username = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "name")?.Value; | |
// 在这里使用 username 进行其他操作 | |
return Success($"当前Token用户是:{username}"); | |
} | |
return Error("Toekn信息解析失败"); | |
} | |
/// <summary> | |
/// 登录 | |
/// </summary> | |
/// <param name="model"></param> | |
/// <returns></returns> | |
[ | ]|
[ | ]|
public async Task<ApiResult> Login(SysUserEntity model) | |
{ | |
string secretKey = "sdfsdfsrty45634kkhxxhtdgdfss345t678xx"; | |
var loginResult = await db.Queryable<SysUserEntity>().Where(a => !a.IsBan && a.UsePwd == model.UsePwd && a.UserName == model.UserName).AnyAsync(); | |
// 验证用户名和密码 | |
if (!loginResult) | |
{ | |
return Error("账号密码错误"); | |
} | |
// 生成 JWT 令牌 | |
var tokenHandler = new JwtSecurityTokenHandler(); | |
var key = Encoding.ASCII.GetBytes(secretKey); | |
var tokenDescriptor = new SecurityTokenDescriptor | |
{ | |
Subject = new ClaimsIdentity(new List<Claim> | |
{ | |
new Claim(ClaimTypes.Name, model.UserName), | |
new Claim(JwtRegisteredClaimNames.Jti, model.Id.ToString()), | |
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToString()), | |
new Claim(ClaimTypes.Expiration, DateTime.Now.AddHours(10).ToString()) | |
}), | |
Expires = DateTime.UtcNow.AddDays(7), | |
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature), | |
Issuer = "ningissuer", | |
Audience = "wr", | |
}; | |
var token = tokenHandler.CreateToken(tokenDescriptor); | |
var tokenString = tokenHandler.WriteToken(token); | |
// 返回 JWT 令牌 | |
return Success(new { token = "Bearer " + tokenString }); | |
} | |
} |
到这里,基本上已经结束了,剩下的无非加加业务,或者加一些更丰富的组件,什么autofac啦,nacos啦,yarp啦,seq啦
总结
对项目而言
其实这种方式已经足够适用绝大多数中小公司的普通项目需求了,如果你还要加些限流或者什么中间件的话,也是可以很直观的去加,而不是像某些框架封装一坨又一坨,你在哪加个什么东西要翻找半天,毁坏了原本dotNet自身的生态(指官方文档)
这样出来对的项目也很直观,物尽其才,只要后续开发定好一个规范管理,就不会像你公司那破框架一堆密密麻麻的东西都没使用过的情况出现
对新手而言
同时呢,这样构建一个项目框架,也方便新手学习,因为十分的直观,不会对莫名其妙出现的东西感觉到匪夷所思,根本不知道拿来做什么的,像这样需要什么加什么,就对所有加的东西包括nuget包,中间件,或者封装啥的都有个很清晰的认知
对转行到.NET 的人而言
dotnet官方本身已经是一个大封装了,不要把别的语言思维带到这里,做什么破功能都要自己写,写又写不好,写好了又没文档,人走了之后又坑公司又坑其他.net开发者
结语,给所有中小公司和个人的开发建议
马上2024了,.Net的生态已经算是十分丰富了,请不要再试图自行造轮子
举个例子假如你要 对接微信(企业微信,小程序,公众号)/字节用这个:https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat
别在那自己瞎琢磨封装,对个人而言你瞎封装有什么用对你也没什么好处费时费力,还封装不好,你能保证自己封装完了还会提供详细的文档?
一句很重要的话,我在一线开发从curd干到框架,我觉得很多人都没意识到的一点就是:企业的项目,技术方面所有都要为了实际业务而做出努力,而不是为了技术而技术。
就刚才这封装的例子,如果你是自己封装,随便有点变动你是不是要抛下业务需求不管去维护?
一切的代码和封装前提思想就是不要为了写代码而去写代码,唉,忍不吐槽一下,这其实是码农基本素养,但还是看的太多太心累
代码文件补充
SqlSugarHelper
public class SqlSugarHelper | |
{ | |
public static readonly SqlSugarScope Db = new SqlSugarScope(new ConnectionConfig() | |
{ | |
ConnectionString = "server=xxx;Database=xxx;Uid=root;Pwd=xxx;Port=6607;Allow User Variables=True;",//连接符字串 | |
DbType = DbType.MySql, | |
IsAutoCloseConnection = true, | |
}, db => | |
{ | |
ExternalServicesSetting(db); | |
db.Aop.OnLogExecuting = (sql, pars) => | |
{ | |
Console.WriteLine(sql); | |
}; | |
}); | |
/// <summary> | |
/// 拓展配置 | |
/// </summary> | |
/// <param name="db"></param> | |
/// <param name="config"></param> | |
private static void ExternalServicesSetting(SqlSugarClient db) | |
{ | |
var cache = ServiceLocator.Instance.GetService<SqlSugarMemoryCacheService>(); | |
db.CurrentConnectionConfig.ConfigureExternalServices = new ConfigureExternalServices | |
{ | |
DataInfoCacheService = cache, | |
}; | |
} | |
} |