新增 业务中间件

修复 直观的异常
This commit is contained in:
小肥羊 2025-07-14 18:23:45 +08:00
parent 494251c76a
commit 1e4794a879
26 changed files with 1000 additions and 99 deletions

View File

@ -0,0 +1,52 @@
using Learn.Archives.API.Controllers.Dto;
using Learn.Archives.API.Expand;
using Learn.Archives.Core.Common;
using Learn.Archives.Core.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using System.Security.Claims;
namespace Learn.Archives.API.Controllers
{
public class AdminController : BackController<Admin>
{
readonly Repository<Admin> baseService;
public AdminController(Repository<Admin> baseService) : base(baseService)
{
this.baseService = baseService;
}
/// <summary>
/// 后台管理员登录
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost, AllowAnonymous]
[HttpLogEnable]
public async Task<string> Login([FromBody] AdminLoginReq model)
{
if (string.IsNullOrWhiteSpace(model.Account))
Oh.Error("登录失败,用户名不能为空");
if (string.IsNullOrWhiteSpace(model.Password))
Oh.Error("登录失败,密码不能为空");
var admin = await baseService.GetFirstAsync(x => x.Account == model.Account);
if (admin == null)
Oh.Error("登录失败,用户不存在!");
if (!admin!.Enable)
Oh.Error("登录失败,用户已锁定!");
if (admin.Password != model.Password)
Oh.Error("登录失败,密码错误");
// 获取租户信息
//获取
return JwtHelper.GetToken(AppCommon.Config.AuthKey,
[
new Claim(ClaimEnum.Role,admin.RoleId.ToString()),
new Claim(ClaimEnum.Id, admin.Id.ToString()),
new Claim(ClaimEnum.Name, admin.Name),
]);
}
}
}

View File

@ -0,0 +1,156 @@
using Learn.Archives.API.Expand;
using Learn.Archives.Core.Common;
using Learn.Archives.Core.Common.Expand;
using Learn.Archives.Core.Model.Dto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SqlSugar;
using UserCenter.Model.Interface;
namespace Learn.Archives.API.Controllers
{
[ApiController]
public class BaseController : ControllerBase
{
}
/// <summary>
/// 管理员接口
/// </summary>
[Authorize(AuthenticationSchemes = Authentication.Admin)]
[Route("api/[controller]/[action]")]
public abstract class BackBaseController : BaseController
{
}
/// <summary>
/// 管理员的公共接口
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BackController<T> : BackBaseController where T : class, IDFilter, new()
{
readonly Repository<T> _baseRepository;
/// <summary>
///
/// </summary>
/// <param name="baseService"></param>
public BackController(Repository<T> baseService) : base()
{
_baseRepository = baseService;
}
/// <summary>
/// 详情
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet, Route("{id}")]
public virtual async Task<dynamic> Info(long id)
{
return await _baseRepository.GetByIdAsync(id);
}
/// <summary>
/// 新增/修改
/// <para>id=0 时新增</para>
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost, Route("edit")]
[HttpLogEnable]
public virtual async Task<bool> Edit([FromBody] T model)
{
if (model.Id == 0)
return await _baseRepository.InsertAsync(model);
else
return await _baseRepository.UpdateAsync(model);
}
/// <summary>
/// 删除
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpPost, Route("delete")]
[HttpLogEnable]
public virtual async Task<bool> Del([FromBody] params long[] ids)
{
var db = _baseRepository;
int sum = 0;
if (typeof(T).IsAssignableFrom(typeof(IDeletedFilter)))
{
if(ids.Length==1)
return await db.DeleteByIdAsync(ids.First());
else
return await db.DeleteByIdsAsync(ids.Select(s=>(dynamic)s).ToArray());
}
try
{
db.Context.Ado.BeginTran();
foreach (var item in ids)
sum += await db.AsUpdateable().SetColumns("DeleteState", true)
.Where("Id=" + item).ExecuteCommandAsync();
db.Context.Ado.CommitTran();
}
catch
{
db.Context.Ado.RollbackTran();
throw;
}
return true;
}
/// <summary>
/// 分页/全查 的基础查询函数
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
///
[NonAction]
public virtual ISugarQueryable<T> BaseQuery(QueryDto model)
{
List<IConditionalModel> where = new List<IConditionalModel>();
foreach (var item in model.Conditions)
where.Add(item);
var d = _baseRepository.AsQueryable()
.Where(where);
if ((typeof(T)).GetProperty("DeleteState") != null)
d = d.Where("DeleteState=0");
if (!string.IsNullOrEmpty(model.OrderBy))
return d.OrderByPropertyName(model.OrderBy, model.OrderByType);
return d;
}
/// <summary>
/// 分页查询
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost, Route("pagelist")]
public virtual async Task<dynamic> GetPageList([FromBody] QueryRequestBase model)
{
var sqlquery = BaseQuery(model);
RefAsync<int> total = 0;
var data = await sqlquery.ToPageListAsync(model.PageIndex + 1, model.PageSize, total);
return new PageResult<T>() { Data = data, Total = total };
}
/// <summary>
/// 查询下拉列表
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost, Route("querycombo")]
public virtual async Task<List<ComboModel>> QueryCombo([FromBody] QueryCombo model)
{
if (string.IsNullOrEmpty(model.ValueName) || string.IsNullOrEmpty(model.TextName))
Oh.ModelError("ValueName TextName 是必填项");
List<IConditionalModel> where = [.. model.Conditions];
var sqlquery = _baseRepository.AsQueryable().Where(where).Where("DeleteState=0");
if (!string.IsNullOrEmpty(model.OrderBy))
sqlquery = sqlquery.OrderByPropertyName(model.OrderBy, model.OrderByType);
var res = await sqlquery.Select<ComboModel>($"{model.TextName} as Text , {model.ValueName} as Value").ToListAsync();
return res;
}
}
}

View File

@ -0,0 +1,8 @@
namespace Learn.Archives.API.Controllers.Dto
{
public class AdminLoginReq
{
public string Account { get; set; }
public string Password { get; set; }
}
}

View File

@ -0,0 +1,58 @@
using Learn.Archives.Core.Common;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Learn.Archives.Core.Model.Dto;
using Aliyun.OSS;
namespace Learn.Archives.API.Expand
{
public static class AuthorizeExpand
{
public static IServiceCollection AddPermissionAuthentication(this IServiceCollection services)
{
services.AddAuthentication()
.AddJwtBearer(Authentication.Admin, options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = false,//保存token,后台验证token是否生效(重要)
RequireExpirationTime = true, // 设置请求需要携带accesstoken的过期时间
ValidateIssuer = false,//必须验证签发人
ValidateAudience = false,//验证受众
ValidateLifetime = true,//是否验证Token有效期
ValidateIssuerSigningKey = true,//是否验证签名,不验证 会被篡改数据,不安全
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppCommon.Config.AuthKey.Secret)),//解密的密钥
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
context.Response.Clear();
context.Response.ContentType = "application/json";
context.Response.StatusCode = 403;
var data = new BaseReturn() { Code = 403, Message = context.Exception.Message + context.Exception?.StackTrace };
context.Response.WriteAsync(data.ToJson());
return Task.CompletedTask;
},
OnChallenge = context =>
{
context.HandleResponse();
context.Response.Clear();
context.Response.ContentType = "application/json";
context.Response.StatusCode = 403;
var data = new BaseReturn() { Code = 403, Message = context.Error + context.AuthenticateFailure?.Message };
context.Response.WriteAsync(data.ToJson());
return Task.CompletedTask;
}
};
});
return services;
}
}
}

View File

@ -0,0 +1,255 @@
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using SqlSugar;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text.Json;
using System.Collections.Generic;
using System.Data;
using Microsoft.Extensions.Hosting;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Learn.Archives.Core.Common;
using Learn.Archives.Core.Model.Dto;
using Learn.Archives.Core.Model;
namespace Learn.Archives.API.Expand
{
/// <summary>
/// 使用该属性,接口对结果原样输出,不做包装
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public class ResultIgnore : Attribute { }
/// <summary>
/// http接口日志启用
/// </summary>
public class HttpLogEnable : Attribute { }
/// <summary>
/// Http请求过滤器
/// </summary>
public class HttpLogAttribute : ActionFilterAttribute, IAsyncExceptionFilter
{
readonly Repository<HttpLog> logService;
readonly LiveUserInfo userInfo;
readonly Stopwatch _stopwatch;//统计程序耗时
public HttpLogAttribute(Repository<HttpLog> logService, LiveUserInfo userInfo)
{
this.logService = logService;
_stopwatch = Stopwatch.StartNew();
this.userInfo = userInfo;
}
/// <summary>
/// 执行接口前400 处理
/// </summary>
/// <param name="context"></param>
/// <exception cref="CustomException"></exception>
public void Executing400(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errMsg = string.Join(',', context.ModelState.Values.SelectMany(s => s.Errors.Select(e => e.ErrorMessage)));
Oh.ModelError(errMsg);
}
}
private bool HasAttribute<T>(ActionExecutedContext context) where T : Attribute
{
if (context.ActionDescriptor is ControllerActionDescriptor descriptor)
{
// 检查方法上是否有 SkipApiResultAttribute
if (descriptor.MethodInfo.GetCustomAttributes(typeof(T), false).Any())
return true;
// 检查控制器上是否有 SkipApiResultAttribute
if (descriptor.ControllerTypeInfo.GetCustomAttributes(typeof(T), false).Any())
return true;
}
return false;
}
/// <summary>
/// 接口结果格式化
/// </summary>
/// <param name="context"></param>
public BaseReturn<object>? ApiResultFormatting(ActionExecutedContext context)
{
//特殊处理ResultIgnore不进行返回结果包装原样输出
if (HasAttribute<ResultIgnore>(context))
{
base.OnActionExecuted(context);
return null;
}
// 返回结果为JsonResult的请求进行Result包装
if (context.Exception != null)
throw context.Exception;
if (context.Result != null)
{
object? resData = null;
if (context.Result is ObjectResult objectResult)
resData = objectResult.Value;
else if (context.Result is ContentResult contentRes)
resData = contentRes.Content;
else if (context.Result is JsonResult resJ)
resData = resJ.Value;
else if (context.Result is FileResult)
return null;
var code = (context?.Result as IStatusCodeActionResult)?.StatusCode ?? 200;
var res = new BaseReturn<object>()
{
Code = code,
Data = resData,
Message = "SUCCESS"
};
context.Result = new JsonResult(res);
return res;
}
return null;
}
/// <summary>
/// 添加http日志信息
/// </summary>
/// <param name="context"></param>
/// <param name="result"></param>
/// <param name="e"></param>
/// <returns></returns>
public async Task AddHttpLogAsync(HttpContext context, BaseReturn<object>? result = null, Exception? e = null)
{
//特殊处理ResultIgnore不进行返回结果包装原样输出
var endpoint = context.GetEndpoint();
// 直接返回原始结果,不封装
if (endpoint?.Metadata.GetMetadata<HttpLogEnable>() == null) return;
string request = null;
var logId = Yitter.IdGenerator.YitIdHelper.NextId();
if (!context.Request.Method
.Equals("GET", StringComparison.InvariantCultureIgnoreCase))
{
context.Request.EnableBuffering();
//记录请求参数
if (context.Request.Body.CanSeek)
{
try
{
if (context.Request.HasFormContentType && context.Request?.Form?.Files?.Count() > 0)
{
// 设置保存目录例如项目根目录下的Uploads文件夹
string uploadsFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UploadLogs", logId.ToString());
// 创建目录(如果不存在)
if (!Directory.Exists(uploadsFolder))
Directory.CreateDirectory(uploadsFolder);
foreach (var file in context.Request.Form.Files)
{
// 生成安全文件名(防止路径遍历攻击)
string uniqueFileName = Guid.NewGuid().ToString().Substring(0, 5) + "_" + Path.GetFileName(file.FileName);
string filePath = Path.Combine(uploadsFolder, uniqueFileName);
// 保存文件
using var stream = new FileStream(filePath, FileMode.Create);
await file.CopyToAsync(stream);
}
request = $"请求体包含{context.Request.Form.Files.Count()}个文件 目录 {uploadsFolder}";
}
else
{
context.Request.Body.Position = 0;
using var sr = new StreamReader(context.Request.Body);
request = await sr.ReadToEndAsync();
}
}
catch (Exception ex)
{
request = "处理请求日志时发生了错误 \r\n" + ex.ToString();
}
}
}
//写入队列
await logService.InsertAsync(new HttpLog
{
Id = logId,
Url = context.Request.Path + context.Request.QueryString,
Method = context.Request.Method,
Request = request,
IP = context.Connection?.RemoteIpAddress?.ToString(),
ResponseCode = result?.Code ?? -1,
Response = result != null ? JsonSerializer.Serialize(result) : null,
Authorization = context.Request.Headers.ContainsKey("Authorization")
? context.Request.Headers["Authorization"].ToString()
: string.Empty,
Exception = e?.ToString(),
ExceptionMessage = e?.Message,
AdminId = userInfo.Id ,
TotalMilliseconds = (double)_stopwatch.Elapsed.TotalMilliseconds
});
}
/// <summary>
/// 在Controller的Action执行前执行
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
Executing400(context);
base.OnActionExecuting(context);
}
/// <summary>
/// 在Controller的Action执行后执行
/// </summary>
/// <param name="context"></param>
public override async void OnActionExecuted(ActionExecutedContext context)
{
try
{
BaseReturn<object>? res = ApiResultFormatting(context);
await AddHttpLogAsync(context.HttpContext, res);
}
catch (Exception ex)
{
}
//添加http请求日志
base.OnActionExecuted(context);
}
/// <summary>
/// 执行错误时
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task OnExceptionAsync(ExceptionContext context)
{
var code = -1;
var msg = context.Exception.Message;
if (context.Exception is OhException exception)
code = exception.Code;
var result = new BaseReturn()
{
Code = -1,
Message = context.Exception.Message
};
context.Result = new JsonResult(result);
await AddHttpLogAsync(context.HttpContext, null, context.Exception);
if (code == 401 || code == 403)
context.HttpContext.Response.StatusCode = code;
context.ExceptionHandled = true;
}
}
}

View File

@ -5,18 +5,16 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.18" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.2-pre01" /> <PackageReference Include="Mapster.DependencyInjection" Version="1.0.2-pre01" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Learn.Archives.Core\Learn.Archives.Core.csproj" /> <ProjectReference Include="..\Learn.Archives.Core\Learn.Archives.Core.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -1,6 +0,0 @@
@Learn.Archives.API_HostAddress = http://localhost:5199
GET {{Learn.Archives.API_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -4,6 +4,8 @@ using System.Text.Encodings.Web;
using System.Text.Unicode; using System.Text.Unicode;
using Learn.Archives.Core.Common.Expand; using Learn.Archives.Core.Common.Expand;
using Mapster; using Mapster;
using System.Text.Json;
using Learn.Archives.API.Expand;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -15,11 +17,16 @@ builder.Services.AddLogging(loggingBuilder =>
loggingBuilder.SetMinimumLevel(LogLevel.Warning); // ÉèÖÃ×îСÈÕÖ¾¼¶±ðΪ Warning loggingBuilder.SetMinimumLevel(LogLevel.Warning); // ÉèÖÃ×îСÈÕÖ¾¼¶±ðΪ Warning
}); });
builder.Services.AddControllers() builder.Services.AddControllers(options =>
.AddJsonOptions(options => {
{ // 全局模型赋值默认值 和 统一返回格式处理
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); options.Filters.Add<HttpLogAttribute>();
}); })
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);//中文转换时不使用Unicode
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// 默认小驼峰 null 大驼峰
});
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c => builder.Services.AddSwaggerGen(c =>
{ {
@ -34,19 +41,15 @@ builder.Services.AddSwaggerGen(c =>
}); });
builder.Configuration.AddAppConfig(args); builder.Configuration.AddAppConfig(args);
builder.Services.AddPermissionAuthentication();
builder.Services.AddSqlSugarExpand(); builder.Services.AddSqlSugarExpand();
builder.Services.AddRedisExpand(); builder.Services.AddRedisExpand();
builder.Services.AddCorsExpand(); builder.Services.AddCorsExpand();
builder.Services.AddMapster(); builder.Services.AddMapster();
builder.Services.AddCorsExpand(); builder.Services.AddLiveUserInfoExpand();
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpContextAccessor();
//异常过滤器
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(ExceptionFilter));
});
var app = builder.Build(); var app = builder.Build();
@ -63,7 +66,6 @@ app.UseSqlSugarExpand();
app.UseAuthorization(); app.UseAuthorization();
app.MapControllers(); app.MapControllers();
app.Run(); app.Run();

View File

@ -8,17 +8,6 @@
"Redis": { "Redis": {
"ConnectionString": "redis-external.23544.com:16379,password=poiuyt)(*&^%,defaultDatabase=3" "ConnectionString": "redis-external.23544.com:16379,password=poiuyt)(*&^%,defaultDatabase=3"
}, },
"FFmpeg": {
" TimeSlice": 600
},
"AliyunOSS": {
"AccessKeyId": "LTAI5tDC6p9h747B7FHbgwkH",
"AccessKeySecret": "vRKgmbp1LB05LaGOjh3ZrZxbHSLYLF",
"BucketDomain": "https://learn-videoanalysis.oss-cn-chengdu.aliyuncs.com",
"Region": "cn-chengdu",
"BucketName": "learn-videoanalysis",
"EndPoint": "oss-cn-chengdu.aliyuncs.com" //
},
"DB": { "DB": {
//"ConnectionString": "AllowLoadLocalInfile=true;Server=10.255.255.3;Port=3306;Database=learn.videoanalysis;User ID=marking;Password=qwe123!@#;CharSet=utf8mb4;pooling=true;SslMode=None", //"ConnectionString": "AllowLoadLocalInfile=true;Server=10.255.255.3;Port=3306;Database=learn.videoanalysis;User ID=marking;Password=qwe123!@#;CharSet=utf8mb4;pooling=true;SslMode=None",
"ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None", "ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None",

View File

@ -8,19 +8,20 @@
"AllowedHosts": "*", "AllowedHosts": "*",
"AppConfig": { "AppConfig": {
"ID": 1, // "ID": 1, //
"SimpLetex": {
"Host": "https://server.simpletex.cn/api/",
"AppSecret": "05ZbPfCFZgTmfd4uIqHHc9pHgYR2V8bk",
"AppId": "GH2OXwuxSZEH5W28H61bdSzD"
},
"Redis": { "Redis": {
"ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10" "ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10"
}, },
"DB": { "DB": {
"ConnectionString": "AllowLoadLocalInfile=true;Server=192.168.2.9;User ID=root;Password=qwe123!@#;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None", "ConnectionString": "AllowLoadLocalInfile=true;Server=192.168.2.9;User ID=root;Password=qwe123!@#;Port=3306;Database=learn.archives;CharSet=utf8mb4;pooling=true;SslMode=None",
"SqlType": "MySql", "SqlType": "MySql",
"UpdateTable": false "UpdateTable": false
}, },
"AuthKey": {
"Secret": "9FAB7AC7-F2DB-4C52-B81F-044055A34AF2",
"Issuer": "Learn.Archive", //
"Audience": "Admin",
"Expires": 120 // 120
},
"OtherDBArr": [ "OtherDBArr": [
//{ //{
// "ConfigId": 1001, //ResourceBank // "ConfigId": 1001, //ResourceBank

View File

@ -22,6 +22,10 @@ namespace Learn.Archives.Core.Common
/// </summary> /// </summary>
public AdminConfig Admin { get; set; } = new AdminConfig(); public AdminConfig Admin { get; set; } = new AdminConfig();
/// <summary> /// <summary>
/// 授权配置
/// </summary>
public AuthKeyConfig AuthKey { get; set; } = new AuthKeyConfig();
/// <summary>
/// 子系统 /// 子系统
/// </summary> /// </summary>
public SubsystemConfig Subsystem { get; set; } = new SubsystemConfig(); public SubsystemConfig Subsystem { get; set; } = new SubsystemConfig();
@ -68,6 +72,25 @@ namespace Learn.Archives.Core.Common
} }
public class AuthKeyConfig
{
/// <summary>
/// 密钥
/// </summary>
public string Secret { get; set; }
/// <summary>
/// 签发人
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 受众
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public int Expires { get; set; }
}
public class SimpLetexConfig public class SimpLetexConfig
{ {
/// <summary> /// <summary>

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Learn.Archives.Core.Common
{
public class Authentication
{
public const string Admin = "Admin";
}
}

View File

@ -12,8 +12,6 @@ namespace Learn.Archives.Core.Common
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly string _realm; private readonly string _realm;
public BasicAuthMiddleware(RequestDelegate next, string realm) public BasicAuthMiddleware(RequestDelegate next, string realm)
{ {
_next = next; _next = next;
@ -37,7 +35,6 @@ namespace Learn.Archives.Core.Common
return; return;
} }
} }
context.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{_realm}\""; context.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{_realm}\"";
context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return; return;

View File

@ -0,0 +1,13 @@
namespace Learn.Archives.Core.Common
{
public class ClaimEnum
{
public static string TenantId => "tenant";
public static string PositionId => "position";
public static string UserId => "user";
public static string Id => "id";
public static string Role => "role";
public static string Scope => "scope";
public static string Name => "name";
}
}

View File

@ -1,39 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace Learn.Archives.Core.Common
{
public class ExceptionFilter : IAsyncExceptionFilter
{
public ExceptionFilter()
{
}
public async Task OnExceptionAsync(ExceptionContext context)
{
// 创建一个包含错误信息的对象
var errorObject = new
{
ErrorMessage = context.Exception.Message,
context.Exception.StackTrace,
};
// 将错误对象序列化为JSON格式
var json = errorObject.ToJson();
// 设置响应内容类型为JSON
context.HttpContext.Response.ContentType = "application/json";
// 设置状态码
context.HttpContext.Response.StatusCode = 500;
// 将JSON数据写入响应体
await context.HttpContext.Response.WriteAsync(json);
}
}
}

View File

@ -0,0 +1,66 @@
using Microsoft.IdentityModel.Tokens;
using SqlSugar.Extensions;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
namespace Learn.Archives.Core.Common
{
public class JwtHelper
{
public static string GetToken(AuthKeyConfig jwt, params Claim[] claims)
{
return GetToken(jwt.Issuer, jwt.Audience, jwt.Secret, jwt.Expires, claims);
}
/// <summary>
/// 生成Token
/// </summary>
/// <param name="issuer">签发人</param>
/// <param name="audience">受众</param>
/// <param name="secreKey">密钥</param>
/// <param name="expires">过期时间(单位:小时)</param>
public static string GetToken(string issuer, string audience, string secreKey, int expires, params Claim[] claims)
{
var now = DateTime.Now;
//添加用户的信息,转成一组声明,还可以写入更多用户信息声明
var claimss = new List<Claim>
{
//JWT ID 唯一标识符
new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString() ),
//发布时间戳 issued timestamp
new Claim(JwtRegisteredClaimNames.Iat, now.ToString()),
};
if (claims != null && claims.Length > 0)
{
claimss.AddRange(claims);
}
//下面使用 Microsoft.IdentityModel.Tokens帮助库下的类来创建JwtToken
//安全秘钥
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secreKey));
var jwt = new JwtSecurityToken(
//jwt签发人
issuer: issuer,
//jwt受众
audience: audience,
//jwt一组声明
claims: claimss,
notBefore: now,
//jwt令牌过期时间
expires: now.AddHours(value: expires),
//签名凭证: 安全密钥、签名算法
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
);
//生成jwt令牌(json web token)
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
}
}

View File

@ -0,0 +1,48 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Learn.Archives.Core.Common
{
public static class LiveUserInfoExpand
{
/// <summary>
/// 添加 当前作用域登录用户信息
/// </summary>
/// <param name="services"></param>
public static void AddLiveUserInfoExpand(this IServiceCollection services)
{
services.AddScoped<LiveUserInfo>();
}
}
public class LiveUserInfo
{
private readonly IHttpContextAccessor _httpContextAccessor;
public LiveUserInfo(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// 管理员角色id
/// </summary>
public string? RoleId
{
get => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimEnum.Role)?.Value;
}
/// <summary>
/// 管理员id
/// </summary>
public long Id
{
get => long.Parse(_httpContextAccessor.HttpContext?.User.FindFirst(ClaimEnum.Id)?.Value ?? "0");
}
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Learn.Archives.Core.Common
{
/// <summary>
/// 异常抛出的拓展类
/// </summary>
public class Oh
{
/// <summary>
/// 抛出 异常
/// </summary>
/// <param name="message"></param>
/// <param name="code"></param>
/// <exception cref="OhException"></exception>
public static void Error(string message, int code = 500)
{
throw new OhException(message, code);
}
/// <summary>
/// 抛出 模型校验异常
/// </summary>
/// <param name="message"></param>
/// <param name="code"></param>
/// <exception cref="OhException"></exception>
public static void ModelError(string message, int code = 400)
{
throw new OhException(message, code);
}
}
public class OhException : Exception
{
/// <summary>
/// 错误码
/// </summary>
public virtual int Code { get; }
public OhException(string message, int code = -1) :base(message)
{
Code= code;
}
}
}

View File

@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.3.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Json" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
@ -26,10 +27,4 @@
<PackageReference Include="SqlSugarCore" Version="5.1.4.170" /> <PackageReference Include="SqlSugarCore" Version="5.1.4.170" />
<PackageReference Include="UserCenter.Model" Version="1.3.9" /> <PackageReference Include="UserCenter.Model" Version="1.3.9" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Model\Dto\" />
</ItemGroup>
</Project> </Project>

View File

@ -3,7 +3,9 @@ using SqlSugar;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Net; using System.Net;
using System.Text.Json; using System.Text.Json;
using UserCenter.Model;
using UserCenter.Model.Enum; using UserCenter.Model.Enum;
using UserCenter.Model.Interface;
namespace Learn.Archives.Core.Model namespace Learn.Archives.Core.Model
{ {
@ -11,13 +13,8 @@ namespace Learn.Archives.Core.Model
/// 管理员 /// 管理员
/// </summary> /// </summary>
[SugarTable("admin")] [SugarTable("admin")]
public class Admin : IDB public class Admin : EntityBaseId,IDB
{ {
/// <summary>
/// id
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
public long Id { get; set; }
/// <summary> /// <summary>
/// 账号 /// 账号
/// </summary> /// </summary>
@ -35,6 +32,10 @@ namespace Learn.Archives.Core.Model
/// </summary> /// </summary>
public bool Enable { get; set; } public bool Enable { get; set; }
/// <summary> /// <summary>
/// 角色id
/// </summary>
public long RoleId { get; set; }
/// <summary>
/// 创建时间 /// 创建时间
/// </summary> /// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now; public DateTime CreateTime { get; set; } = DateTime.Now;

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Learn.Archives.Core.Model.Dto
{
public class BaseReturn
{
/// <summary>
/// 消息码
/// </summary>
public int Code { get; set; }
/// <summary>
/// 消息
/// </summary>
public string? Message { get; set; }
}
public class BaseReturn<T>
{
public required T? Data { get; set; }
/// <summary>
/// 消息码
/// </summary>
public int Code { get; set; }
/// <summary>
/// 消息
/// </summary>
public string? Message { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Learn.Archives.Core.Model.Dto
{
/// <summary>
/// 公共返回实体
/// </summary>
public class ComboModel
{
public ComboModel(string t, object v)
{
Text = t;
Value = v;
}
public ComboModel()
{
}
public object Value { get; set; }
public string Text { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Learn.Archives.Core.Model.Dto
{
public class PageResult<T>
{
/// <summary>
/// 数据
/// </summary>
public List<T> Data { get; set; }
/// <summary>
/// 总条数
/// </summary>
public int Total { get; set; }
}
}

View File

@ -0,0 +1,75 @@
using SqlSugar;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Learn.Archives.Core.Model.Dto
{
/// <summary>
/// 查询实体
/// </summary>
public partial class QueryDto
{
/// <summary>
/// 查询条件
/// </summary>
public List<ConditionalModel> Conditions { get; set; } = new List<ConditionalModel>();
/// <summary>
/// 排序字段名 例【CreateTime】
/// </summary>
public string OrderBy { get; set; } = "CreateTime";
/// <summary>
/// 排序方式
/// </summary>
public OrderByType OrderByType { get; set; } = OrderByType.Desc;
}
/// <summary>
/// 查询请求基类
/// </summary>
public class QueryRequestBase : QueryDto
{
/// <summary>
/// 页面索引
/// </summary>
public int PageIndex { get; set; } = 0;
/// <summary>
/// 页面数量
/// </summary>
public int PageSize { get; set; } = 20;
}
/// <summary>
/// 查询下拉列表
/// </summary>
public class QueryCombo : QueryDto
{
/// <summary>
/// 值对应属性名称
/// </summary>
[Required(ErrorMessage = "{0}是必填项")]
public string ValueName { get; set; }
/// <summary>
/// 文本对应属性名称
/// </summary>
[Required(ErrorMessage = "{0}是必填项")]
public string TextName { get; set; }
}
/// <summary>
/// 查询导出
/// </summary>
public class QueryExport : QueryDto
{
/// <summary>
/// 字段转换 班级:[{1班级}]
/// </summary>
public Dictionary<string, Dictionary<string,string>> DataSource { get; set; }
/// <summary>
/// 导出字段 学校:School
/// </summary>
public Dictionary<string, string> Custom { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
}
}

View File

@ -0,0 +1,78 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;
using UserCenter.Model;
namespace Learn.Archives.Core.Model
{
///<summary>
/// 管理员角色表
///</summary>
[SugarTable("httplog")]
public partial class HttpLog : EntityBaseId
{
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
/// <summary>
/// 路由
/// </summary>
[SugarColumn(Length = 500)]
public string Url { get; set; }
/// <summary>
/// 请求方法类型
/// </summary>
[SugarColumn(Length = 10)]
public string Method { get; set; }
/// <summary>
/// 请求来自哪个ip
/// </summary>
[SugarColumn(IsNullable = true, Length = 30)]
public string IP { get; set; }
/// <summary>
/// 请求参数
/// </summary>
[SugarColumn(IsNullable = true, ColumnDataType = "longtext")]
public string? Request { get; set; }
/// <summary>
/// 请求返回参数
/// </summary>
[SugarColumn(IsNullable = true, ColumnDataType = "longtext")]
public string? Response { get; set; }
/// <summary>
/// 响应状态码
/// </summary>
public int ResponseCode { get; set; }
/// <summary>
/// 授权信息
/// </summary>
[SugarColumn(IsNullable = true, Length = 500)]
public string? Authorization { get; set; }
/// <summary>
/// 异常完整信息
/// </summary>
[SugarColumn(IsNullable = true, ColumnDataType = "text")]
public string Exception { get; set; }
/// <summary>
/// 异常信息
/// </summary>
[SugarColumn(IsNullable = true, ColumnDataType = "text")]
public string? ExceptionMessage { get; set; }
/// <summary>
/// 管理员ID
/// </summary>
public long AdminId { get; set; }
/// <summary>
/// 总耗时(秒)
/// </summary>
public double TotalMilliseconds { get; set; }
}
}

View File

@ -13,13 +13,8 @@ namespace Learn.Archives.Core.Model
/// <para>数据中心拓展用户</para> /// <para>数据中心拓展用户</para>
/// </summary> /// </summary>
[SugarTable("user")] [SugarTable("user")]
public class User : IDB public class User : EntityBaseId, IDB
{ {
/// <summary>
/// id
/// </summary>
[SugarColumn(IsPrimaryKey = true)]
public long Id { get; set; }
/// <summary> /// <summary>
/// 用户中心的id /// 用户中心的id
/// <see cref="UserCenter.Model.User.Id"/> /// <see cref="UserCenter.Model.User.Id"/>