diff --git a/Learn.Archives.API/Controllers/AdminController.cs b/Learn.Archives.API/Controllers/AdminController.cs new file mode 100644 index 0000000..15dc819 --- /dev/null +++ b/Learn.Archives.API/Controllers/AdminController.cs @@ -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 + { + readonly Repository baseService; + public AdminController(Repository baseService) : base(baseService) + { + this.baseService = baseService; + } + /// + /// 后台管理员登录 + /// + /// + /// + [HttpPost, AllowAnonymous] + [HttpLogEnable] + public async Task 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), + ]); + } + } +} diff --git a/Learn.Archives.API/Controllers/BaseController.cs b/Learn.Archives.API/Controllers/BaseController.cs new file mode 100644 index 0000000..9f8e14d --- /dev/null +++ b/Learn.Archives.API/Controllers/BaseController.cs @@ -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 + { + } + + /// + /// 管理员接口 + /// + [Authorize(AuthenticationSchemes = Authentication.Admin)] + [Route("api/[controller]/[action]")] + public abstract class BackBaseController : BaseController + { + } + + /// + /// 管理员的公共接口 + /// + /// + public abstract class BackController : BackBaseController where T : class, IDFilter, new() + { + readonly Repository _baseRepository; + /// + /// + /// + /// + public BackController(Repository baseService) : base() + { + _baseRepository = baseService; + } + /// + /// 详情 + /// + /// + /// + [HttpGet, Route("{id}")] + public virtual async Task Info(long id) + { + return await _baseRepository.GetByIdAsync(id); + } + /// + /// 新增/修改 + /// id=0 时新增 + /// + /// + /// + [HttpPost, Route("edit")] + [HttpLogEnable] + public virtual async Task Edit([FromBody] T model) + { + if (model.Id == 0) + return await _baseRepository.InsertAsync(model); + else + return await _baseRepository.UpdateAsync(model); + } + /// + /// 删除 + /// + /// + /// + [HttpPost, Route("delete")] + [HttpLogEnable] + public virtual async Task 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; + } + + /// + /// 分页/全查 的基础查询函数 + /// + /// + /// + /// + [NonAction] + public virtual ISugarQueryable BaseQuery(QueryDto model) + { + List where = new List(); + 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; + } + /// + /// 分页查询 + /// + /// + /// + [HttpPost, Route("pagelist")] + public virtual async Task GetPageList([FromBody] QueryRequestBase model) + { + var sqlquery = BaseQuery(model); + RefAsync total = 0; + var data = await sqlquery.ToPageListAsync(model.PageIndex + 1, model.PageSize, total); + return new PageResult() { Data = data, Total = total }; + } + + /// + /// 查询下拉列表 + /// + /// + /// + [HttpPost, Route("querycombo")] + public virtual async Task> QueryCombo([FromBody] QueryCombo model) + { + if (string.IsNullOrEmpty(model.ValueName) || string.IsNullOrEmpty(model.TextName)) + Oh.ModelError("ValueName TextName 是必填项"); + List 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($"{model.TextName} as Text , {model.ValueName} as Value").ToListAsync(); + return res; + } + } + +} diff --git a/Learn.Archives.API/Controllers/Dto/AdminLoginRequestDto.cs b/Learn.Archives.API/Controllers/Dto/AdminLoginRequestDto.cs new file mode 100644 index 0000000..3ee9efa --- /dev/null +++ b/Learn.Archives.API/Controllers/Dto/AdminLoginRequestDto.cs @@ -0,0 +1,8 @@ +namespace Learn.Archives.API.Controllers.Dto +{ + public class AdminLoginReq + { + public string Account { get; set; } + public string Password { get; set; } + } +} diff --git a/Learn.Archives.API/Expand/AuthorizeExpand.cs b/Learn.Archives.API/Expand/AuthorizeExpand.cs new file mode 100644 index 0000000..78a5964 --- /dev/null +++ b/Learn.Archives.API/Expand/AuthorizeExpand.cs @@ -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; + } + + } +} diff --git a/Learn.Archives.API/Expand/HttpFilter.cs b/Learn.Archives.API/Expand/HttpFilter.cs new file mode 100644 index 0000000..0b97dc2 --- /dev/null +++ b/Learn.Archives.API/Expand/HttpFilter.cs @@ -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 +{ + + /// + /// 使用该属性,接口对结果原样输出,不做包装 + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)] + public class ResultIgnore : Attribute { } + + /// + /// http接口日志启用 + /// + public class HttpLogEnable : Attribute { } + + /// + /// Http请求过滤器 + /// + public class HttpLogAttribute : ActionFilterAttribute, IAsyncExceptionFilter + { + readonly Repository logService; + readonly LiveUserInfo userInfo; + readonly Stopwatch _stopwatch;//统计程序耗时 + + public HttpLogAttribute(Repository logService, LiveUserInfo userInfo) + { + this.logService = logService; + _stopwatch = Stopwatch.StartNew(); + this.userInfo = userInfo; + } + + + + /// + /// 执行接口前400 处理 + /// + /// + /// + 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(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; + } + /// + /// 接口结果格式化 + /// + /// + public BaseReturn? ApiResultFormatting(ActionExecutedContext context) + { + //特殊处理:ResultIgnore,不进行返回结果包装,原样输出 + if (HasAttribute(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() + { + Code = code, + Data = resData, + Message = "SUCCESS" + }; + context.Result = new JsonResult(res); + return res; + } + return null; + } + /// + /// 添加http日志信息 + /// + /// + /// + /// + /// + public async Task AddHttpLogAsync(HttpContext context, BaseReturn? result = null, Exception? e = null) + { + //特殊处理:ResultIgnore,不进行返回结果包装,原样输出 + var endpoint = context.GetEndpoint(); + // 直接返回原始结果,不封装 + if (endpoint?.Metadata.GetMetadata() == 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 + }); + } + + + + + + + + /// + /// 在Controller的Action执行前执行 + /// + /// + public override void OnActionExecuting(ActionExecutingContext context) + { + Executing400(context); + base.OnActionExecuting(context); + } + + /// + /// 在Controller的Action执行后执行 + /// + /// + public override async void OnActionExecuted(ActionExecutedContext context) + { + try + { + BaseReturn? res = ApiResultFormatting(context); + await AddHttpLogAsync(context.HttpContext, res); + } + catch (Exception ex) + { + } + //添加http请求日志 + + base.OnActionExecuted(context); + } + /// + /// 执行错误时 + /// + /// + /// + 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; + + } + } + +} diff --git a/Learn.Archives.API/Learn.Archives.API.csproj b/Learn.Archives.API/Learn.Archives.API.csproj index df5547a..063b96d 100644 --- a/Learn.Archives.API/Learn.Archives.API.csproj +++ b/Learn.Archives.API/Learn.Archives.API.csproj @@ -5,18 +5,16 @@ enable enable Linux + True + - - - - diff --git a/Learn.Archives.API/Learn.Archives.API.http b/Learn.Archives.API/Learn.Archives.API.http deleted file mode 100644 index a49bb40..0000000 --- a/Learn.Archives.API/Learn.Archives.API.http +++ /dev/null @@ -1,6 +0,0 @@ -@Learn.Archives.API_HostAddress = http://localhost:5199 - -GET {{Learn.Archives.API_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/Learn.Archives.API/Program.cs b/Learn.Archives.API/Program.cs index 252e568..dd955f5 100644 --- a/Learn.Archives.API/Program.cs +++ b/Learn.Archives.API/Program.cs @@ -4,6 +4,8 @@ using System.Text.Encodings.Web; using System.Text.Unicode; using Learn.Archives.Core.Common.Expand; using Mapster; +using System.Text.Json; +using Learn.Archives.API.Expand; var builder = WebApplication.CreateBuilder(args); @@ -15,11 +17,16 @@ builder.Services.AddLogging(loggingBuilder => loggingBuilder.SetMinimumLevel(LogLevel.Warning); // С־Ϊ Warning }); -builder.Services.AddControllers() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); - }); +builder.Services.AddControllers(options => +{ + // ȫģ͸ֵĬֵ ͳһظʽ + options.Filters.Add(); +}) +.AddJsonOptions(options => +{ + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);//תʱʹUnicode + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// ĬСշ null շ +}); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { @@ -34,19 +41,15 @@ builder.Services.AddSwaggerGen(c => }); builder.Configuration.AddAppConfig(args); +builder.Services.AddPermissionAuthentication(); builder.Services.AddSqlSugarExpand(); builder.Services.AddRedisExpand(); builder.Services.AddCorsExpand(); builder.Services.AddMapster(); -builder.Services.AddCorsExpand(); +builder.Services.AddLiveUserInfoExpand(); builder.Services.AddHttpClient(); builder.Services.AddHttpContextAccessor(); -//쳣 -builder.Services.AddControllersWithViews(options => -{ - options.Filters.Add(typeof(ExceptionFilter)); -}); var app = builder.Build(); @@ -63,7 +66,6 @@ app.UseSqlSugarExpand(); app.UseAuthorization(); - app.MapControllers(); app.Run(); diff --git a/Learn.Archives.API/appsettings.Production.json b/Learn.Archives.API/appsettings.Production.json index 5c5ba0a..677ce98 100644 --- a/Learn.Archives.API/appsettings.Production.json +++ b/Learn.Archives.API/appsettings.Production.json @@ -8,17 +8,6 @@ "Redis": { "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": { //"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", diff --git a/Learn.Archives.API/appsettings.json b/Learn.Archives.API/appsettings.json index 1c0d039..3f7b1b1 100644 --- a/Learn.Archives.API/appsettings.json +++ b/Learn.Archives.API/appsettings.json @@ -8,19 +8,20 @@ "AllowedHosts": "*", "AppConfig": { "ID": 1, //程序唯一值 - "SimpLetex": { - "Host": "https://server.simpletex.cn/api/", - "AppSecret": "05ZbPfCFZgTmfd4uIqHHc9pHgYR2V8bk", - "AppId": "GH2OXwuxSZEH5W28H61bdSzD" - }, "Redis": { "ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10" }, "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", "UpdateTable": false }, + "AuthKey": { + "Secret": "9FAB7AC7-F2DB-4C52-B81F-044055A34AF2", + "Issuer": "Learn.Archive", //签发人 + "Audience": "Admin", + "Expires": 120 // 过期时间120小时 + }, "OtherDBArr": [ //{ // "ConfigId": 1001, //ResourceBank diff --git a/Learn.Archives.Core/Common/AppConfig.cs b/Learn.Archives.Core/Common/AppConfig.cs index a5f3200..c3aada0 100644 --- a/Learn.Archives.Core/Common/AppConfig.cs +++ b/Learn.Archives.Core/Common/AppConfig.cs @@ -22,6 +22,10 @@ namespace Learn.Archives.Core.Common /// public AdminConfig Admin { get; set; } = new AdminConfig(); /// + /// 授权配置 + /// + public AuthKeyConfig AuthKey { get; set; } = new AuthKeyConfig(); + /// /// 子系统 /// public SubsystemConfig Subsystem { get; set; } = new SubsystemConfig(); @@ -68,6 +72,25 @@ namespace Learn.Archives.Core.Common } + public class AuthKeyConfig + { + /// + /// 密钥 + /// + public string Secret { get; set; } + /// + /// 签发人 + /// + public string Issuer { get; set; } + /// + /// 受众 + /// + public string Audience { get; set; } + /// + /// 过期时间 + /// + public int Expires { get; set; } + } public class SimpLetexConfig { /// diff --git a/Learn.Archives.Core/Common/AuthenticationSchemes.cs b/Learn.Archives.Core/Common/AuthenticationSchemes.cs new file mode 100644 index 0000000..b487679 --- /dev/null +++ b/Learn.Archives.Core/Common/AuthenticationSchemes.cs @@ -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"; + } +} diff --git a/Learn.Archives.Core/Common/BasicAuthMiddleware.cs b/Learn.Archives.Core/Common/BasicAuthMiddleware.cs index b175635..b4bd09d 100644 --- a/Learn.Archives.Core/Common/BasicAuthMiddleware.cs +++ b/Learn.Archives.Core/Common/BasicAuthMiddleware.cs @@ -12,8 +12,6 @@ namespace Learn.Archives.Core.Common private readonly RequestDelegate _next; private readonly string _realm; - - public BasicAuthMiddleware(RequestDelegate next, string realm) { _next = next; @@ -37,7 +35,6 @@ namespace Learn.Archives.Core.Common return; } } - context.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{_realm}\""; context.Response.StatusCode = StatusCodes.Status401Unauthorized; return; diff --git a/Learn.Archives.Core/Common/ClaimEnum.cs b/Learn.Archives.Core/Common/ClaimEnum.cs new file mode 100644 index 0000000..e456ae0 --- /dev/null +++ b/Learn.Archives.Core/Common/ClaimEnum.cs @@ -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"; + } +} diff --git a/Learn.Archives.Core/Common/ExceptionFilter.cs b/Learn.Archives.Core/Common/ExceptionFilter.cs deleted file mode 100644 index f6f4995..0000000 --- a/Learn.Archives.Core/Common/ExceptionFilter.cs +++ /dev/null @@ -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); - } - } -} diff --git a/Learn.Archives.Core/Common/JwtHelper.cs b/Learn.Archives.Core/Common/JwtHelper.cs new file mode 100644 index 0000000..9771fe4 --- /dev/null +++ b/Learn.Archives.Core/Common/JwtHelper.cs @@ -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); + } + + /// + /// 生成Token + /// + /// 签发人 + /// 受众 + /// 密钥 + /// 过期时间(单位:小时) + public static string GetToken(string issuer, string audience, string secreKey, int expires, params Claim[] claims) + { + var now = DateTime.Now; + //添加用户的信息,转成一组声明,还可以写入更多用户信息声明 + var claimss = new List + { + //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; + } + } +} diff --git a/Learn.Archives.Core/Common/LiveUserInfo.cs b/Learn.Archives.Core/Common/LiveUserInfo.cs new file mode 100644 index 0000000..2331887 --- /dev/null +++ b/Learn.Archives.Core/Common/LiveUserInfo.cs @@ -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 + { + + /// + /// 添加 当前作用域登录用户信息 + /// + /// + public static void AddLiveUserInfoExpand(this IServiceCollection services) + { + services.AddScoped(); + } + } + public class LiveUserInfo + { + + private readonly IHttpContextAccessor _httpContextAccessor; + public LiveUserInfo(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + /// + /// 管理员角色id + /// + public string? RoleId + { + get => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimEnum.Role)?.Value; + } + + /// + /// 管理员id + /// + public long Id + { + get => long.Parse(_httpContextAccessor.HttpContext?.User.FindFirst(ClaimEnum.Id)?.Value ?? "0"); + } + } +} diff --git a/Learn.Archives.Core/Common/OhException.cs b/Learn.Archives.Core/Common/OhException.cs new file mode 100644 index 0000000..72eca5c --- /dev/null +++ b/Learn.Archives.Core/Common/OhException.cs @@ -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 +{ + /// + /// 异常抛出的拓展类 + /// + public class Oh + { + /// + /// 抛出 异常 + /// + /// + /// + /// + public static void Error(string message, int code = 500) + { + throw new OhException(message, code); + } + /// + /// 抛出 模型校验异常 + /// + /// + /// + /// + public static void ModelError(string message, int code = 400) + { + throw new OhException(message, code); + } + } + public class OhException : Exception + { + /// + /// 错误码 + /// + public virtual int Code { get; } + public OhException(string message, int code = -1) :base(message) + { + Code= code; + } + + } +} diff --git a/Learn.Archives.Core/Learn.Archives.Core.csproj b/Learn.Archives.Core/Learn.Archives.Core.csproj index 41408e8..9c9496e 100644 --- a/Learn.Archives.Core/Learn.Archives.Core.csproj +++ b/Learn.Archives.Core/Learn.Archives.Core.csproj @@ -19,6 +19,7 @@ + @@ -26,10 +27,4 @@ - - - - - - diff --git a/Learn.Archives.Core/Model/Admin.cs b/Learn.Archives.Core/Model/Admin.cs index ccd4dd0..6895a62 100644 --- a/Learn.Archives.Core/Model/Admin.cs +++ b/Learn.Archives.Core/Model/Admin.cs @@ -3,7 +3,9 @@ using SqlSugar; using System.ComponentModel.DataAnnotations; using System.Net; using System.Text.Json; +using UserCenter.Model; using UserCenter.Model.Enum; +using UserCenter.Model.Interface; namespace Learn.Archives.Core.Model { @@ -11,13 +13,8 @@ namespace Learn.Archives.Core.Model /// 管理员 /// [SugarTable("admin")] - public class Admin : IDB + public class Admin : EntityBaseId,IDB { - /// - /// id - /// - [SugarColumn(IsPrimaryKey = true)] - public long Id { get; set; } /// /// 账号 /// @@ -35,6 +32,10 @@ namespace Learn.Archives.Core.Model /// public bool Enable { get; set; } /// + /// 角色id + /// + public long RoleId { get; set; } + /// /// 创建时间 /// public DateTime CreateTime { get; set; } = DateTime.Now; diff --git a/Learn.Archives.Core/Model/Dto/BaseReturn.cs b/Learn.Archives.Core/Model/Dto/BaseReturn.cs new file mode 100644 index 0000000..572ec9a --- /dev/null +++ b/Learn.Archives.Core/Model/Dto/BaseReturn.cs @@ -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 + { + + /// + /// 消息码 + /// + public int Code { get; set; } + /// + /// 消息 + /// + public string? Message { get; set; } + } + public class BaseReturn + { + public required T? Data { get; set; } + + /// + /// 消息码 + /// + public int Code { get; set; } + /// + /// 消息 + /// + public string? Message { get; set; } + } +} diff --git a/Learn.Archives.Core/Model/Dto/ComboModel.cs b/Learn.Archives.Core/Model/Dto/ComboModel.cs new file mode 100644 index 0000000..5f9089f --- /dev/null +++ b/Learn.Archives.Core/Model/Dto/ComboModel.cs @@ -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 +{ + /// + /// 公共返回实体 + /// + 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; } + } +} diff --git a/Learn.Archives.Core/Model/Dto/PageResult.cs b/Learn.Archives.Core/Model/Dto/PageResult.cs new file mode 100644 index 0000000..d26823e --- /dev/null +++ b/Learn.Archives.Core/Model/Dto/PageResult.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Learn.Archives.Core.Model.Dto +{ + public class PageResult + { + /// + /// 数据 + /// + public List Data { get; set; } + /// + /// 总条数 + /// + public int Total { get; set; } + } +} diff --git a/Learn.Archives.Core/Model/Dto/QueryRequestBase.cs b/Learn.Archives.Core/Model/Dto/QueryRequestBase.cs new file mode 100644 index 0000000..49989d2 --- /dev/null +++ b/Learn.Archives.Core/Model/Dto/QueryRequestBase.cs @@ -0,0 +1,75 @@ +using SqlSugar; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Learn.Archives.Core.Model.Dto +{ + /// + /// 查询实体 + /// + public partial class QueryDto + { + /// + /// 查询条件 + /// + public List Conditions { get; set; } = new List(); + /// + /// 排序字段名 例:【CreateTime】 + /// + public string OrderBy { get; set; } = "CreateTime"; + /// + /// 排序方式 + /// + public OrderByType OrderByType { get; set; } = OrderByType.Desc; + + } + /// + /// 查询请求基类 + /// + public class QueryRequestBase : QueryDto + { + /// + /// 页面索引 + /// + public int PageIndex { get; set; } = 0; + /// + /// 页面数量 + /// + public int PageSize { get; set; } = 20; + } + /// + /// 查询下拉列表 + /// + public class QueryCombo : QueryDto + { + /// + /// 值对应属性名称 + /// + [Required(ErrorMessage = "{0}是必填项")] + public string ValueName { get; set; } + /// + /// 文本对应属性名称 + /// + [Required(ErrorMessage = "{0}是必填项")] + public string TextName { get; set; } + } + + /// + /// 查询导出 + /// + public class QueryExport : QueryDto + { + /// + /// 字段转换 班级:[{1:班级}] + /// + public Dictionary> DataSource { get; set; } + /// + /// 导出字段 学校:School + /// + public Dictionary Custom { get; set; } + /// + /// 名称 + /// + public string Name { get; set; } + } +} diff --git a/Learn.Archives.Core/Model/HttpLog.cs b/Learn.Archives.Core/Model/HttpLog.cs new file mode 100644 index 0000000..8bf0bd4 --- /dev/null +++ b/Learn.Archives.Core/Model/HttpLog.cs @@ -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 +{ + /// + /// 管理员角色表 + /// + [SugarTable("httplog")] + public partial class HttpLog : EntityBaseId + { + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } = DateTime.Now; + + /// + /// 路由 + /// + [SugarColumn(Length = 500)] + public string Url { get; set; } + /// + /// 请求方法类型 + /// + [SugarColumn(Length = 10)] + public string Method { get; set; } + + /// + /// 请求来自哪个ip + /// + [SugarColumn(IsNullable = true, Length = 30)] + public string IP { get; set; } + + /// + /// 请求参数 + /// + [SugarColumn(IsNullable = true, ColumnDataType = "longtext")] + public string? Request { get; set; } + /// + /// 请求返回参数 + /// + [SugarColumn(IsNullable = true, ColumnDataType = "longtext")] + public string? Response { get; set; } + /// + /// 响应状态码 + /// + public int ResponseCode { get; set; } + /// + /// 授权信息 + /// + [SugarColumn(IsNullable = true, Length = 500)] + public string? Authorization { get; set; } + /// + /// 异常完整信息 + /// + [SugarColumn(IsNullable = true, ColumnDataType = "text")] + public string Exception { get; set; } + /// + /// 异常信息 + /// + [SugarColumn(IsNullable = true, ColumnDataType = "text")] + public string? ExceptionMessage { get; set; } + /// + /// 管理员ID + /// + public long AdminId { get; set; } + /// + /// 总耗时(秒) + /// + public double TotalMilliseconds { get; set; } + } +} diff --git a/Learn.Archives.Core/Model/User.cs b/Learn.Archives.Core/Model/User.cs index 715d3c1..2a4d067 100644 --- a/Learn.Archives.Core/Model/User.cs +++ b/Learn.Archives.Core/Model/User.cs @@ -13,13 +13,8 @@ namespace Learn.Archives.Core.Model /// 数据中心拓展用户 /// [SugarTable("user")] - public class User : IDB + public class User : EntityBaseId, IDB { - /// - /// id - /// - [SugarColumn(IsPrimaryKey = true)] - public long Id { get; set; } /// /// 用户中心的id ///