簡介
關於這個框架的背景,在前面我已經交代過了。不清楚的可以檢視這個連結 極簡實用的Asp.NetCore模組化框架決定免費開源了
在最近一段時間內,對這個框架新增了以下功能:
1、新增了CMS模組,目前整體都比較簡單,適合個人部落格使用。
2、新增了AOP快取,使用AspectCore,快取可做到Memarycache和redis一件切換。
3、新增AOP事務,服務層和控制器都可以打上特性標籤使用。
4、對多租戶使用Filter,不管是新增還是更新、查詢即可自動賦值。
5、新增七牛雲圖片上傳功能。
6、對於單表的增刪改查,在控制器內做了封裝,有新的業務按約定建立對應的CRUD實體,一套API自動完成。
7、後臺管理新增站群管理。
說了那麼多,讓我們上點程式碼和截圖來瞧一瞧吧。
AOP快取
public class CacheInterceptorAttribute : AbstractInterceptorAttribute { private static readonly ConcurrentDictionary<Type, MethodInfo> TypeofTaskResultMethod = new ConcurrentDictionary<Type, MethodInfo>(); readonly int _expireSecond; readonly string _cacheKey; #region 攔截處理 /// <summary> /// 過期時間,單位:分 /// </summary> /// <param name="expireMin"></param> public CacheInterceptorAttribute(string cacheKey = null, int expireMin = -1) { _expireSecond = expireMin * 60; _cacheKey = cacheKey; } public async override Task Invoke(AspectContext context, AspectDelegate next) { try { string key = string.Empty; //自定義的快取key不存在,再獲取類名+方法名或類名+方法名+引數名的組合式key if (!string.IsNullOrEmpty(_cacheKey)) { key = _cacheKey; } else { key = GetKey(context.ServiceMethod, context.Parameters); } var returnType = GetReturnType(context); var cache = context.ServiceProvider.GetService<ICacheHelper>(); if (!cache.Exists(key)) { return; } var strResult = cache.Get<string>(key); var result = JsonConvert.DeserializeObject(strResult, returnType); if (result != null) { context.ReturnValue = ResultFactory(result, returnType, context.IsAsync()); } else { result = await RunAndGetReturn(context, next); if (_expireSecond > 0) { cache.Set(key, result, TimeSpan.FromMinutes(_expireSecond)); } else { cache.Set(key, result); } } } catch (Exception e) { Console.WriteLine(e.Message); } } private static string GetKey(MethodInfo method, object[] parameters) { return GetKey(method.DeclaringType.Name, method.Name, parameters); } private static string GetKey(string className, string methodName, object[] parameters) { var paramConcat = parameters.Length == 0 ? string.Empty : ":" + JsonConvert.SerializeObject(parameters); return $"{className}:{methodName}{paramConcat}"; } /// <summary> /// 獲取被攔截方法返回值型別 /// </summary> /// <param name="context"></param> /// <returns></returns> private Type GetReturnType(AspectContext context) { return context.IsAsync() ? context.ServiceMethod.ReturnType.GetGenericArguments().First() : context.ServiceMethod.ReturnType; } /// <summary> /// 執行被攔截方法 /// </summary> /// <param name="context"></param> /// <param name="next"></param> /// <returns></returns> private async Task<object> RunAndGetReturn(AspectContext context, AspectDelegate next) { await context.Invoke(next); return context.IsAsync() ? await context.UnwrapAsyncReturnValue() : context.ReturnValue; } /// <summary> /// 處理攔截器返回結果 /// </summary> /// <param name="result"></param> /// <param name="returnType"></param> /// <param name="isAsync"></param> /// <returns></returns> private object ResultFactory(object result, Type returnType, bool isAsync) { return !isAsync ? result : TypeofTaskResultMethod .GetOrAdd(returnType, t => typeof(Task) .GetMethods() .First(p => p.Name == "FromResult" && p.ContainsGenericParameters) .MakeGenericMethod(returnType)) .Invoke(null, new object[] { result }); } #endregion
多租戶
public class MultiTenantAttribute : ActionFilterAttribute, IActionFilter { /// <summary> /// 全域性註冊過濾器 ,自動為新增 更新方法賦值。也可自行手動打上特性標籤 /// </summary> /// <param name="context"></param> //private string[] methods = new string[] { "add", "modify" }; public override void OnActionExecuting(ActionExecutingContext context) { var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; var actionName = actionDescriptor.ActionName.ToLower(); ICacheHelper cache = context.HttpContext.RequestServices.GetRequiredService(typeof(ICacheHelper)) as ICacheHelper; var siteId = cache.Get<Site>(KeyHelper.Cms.CurrentSite)?.Id; //如果是增加和修改方法 根據站群id //if (methods.Any(o => actionName.Contains(o))) //{ foreach (var parameter in actionDescriptor.Parameters) { var parameterName = parameter.Name;//獲取Action方法中引數的名字 var parameterType = parameter.ParameterType;//獲取Action方法中引數的型別 //if (!typeof(int).IsAssignableFrom(parameterType))//如果不是ID型別 //{ // continue; //} //自動新增租戶id if (typeof(IGlobalSite).IsAssignableFrom(parameterType)) { var model = context.ActionArguments[parameterName] as IGlobalSite; if (siteId != null) { model.SiteId = siteId.Value; } } } //} } } }
控制器單表CRUD API
/// <summary> /// 適用於多租戶模組使用 /// </summary> /// <typeparam name="TEntity">實體</typeparam> /// <typeparam name="TDetailQuery">詳情查詢引數實體</typeparam> /// <typeparam name="TDeleteInput">刪除實體</typeparam> /// <typeparam name="TListQuery">列表分頁查詢引數實體</typeparam> /// <typeparam name="TCreateInput">建立實體</typeparam> /// <typeparam name="TUpdateInput">更新實體</typeparam> [Route("api/[controller]/[action]")] [ApiController] [Authorize] [MultiTenant] public abstract class ApiTenantBaseController<TEntity, TDetailQuery, TDeleteInput, TListQuery, TCreateInput, TUpdateInput> : ControllerBase where TEntity : BaseSiteEntity, new() where TDetailQuery : DetailSiteQuery where TDeleteInput : DeletesSiteInput where TListQuery : ListSiteQuery where TCreateInput : class where TUpdateInput : class { private readonly IBaseServer<TEntity> _service; private readonly IMapper _mapper; public ApiTenantBaseController(IBaseServer<TEntity> service, IMapper mapper) { _service = service; _mapper = mapper; } /// <summary> /// 批量真實刪除 /// </summary> /// <param name="deleteInput"></param> /// <returns></returns> [HttpDelete] public virtual async Task<ApiResult> Deletes([FromBody] TDeleteInput deleteInput) { var res = await _service.DeleteAsync(deleteInput.Ids); if (res <= 0) { throw new FriendlyException("刪除失敗了!"); } return new ApiResult(); } /// <summary> /// 單個真實刪除 /// </summary> /// <param name="deleteInput"></param> /// <returns></returns> [HttpDelete] public virtual async Task<ApiResult> Delete([FromBody] TDeleteInput deleteInput) { foreach (var item in deleteInput.Ids) { var res = await _service.DeleteAsync(d => d.Id == item && d.SiteId == deleteInput.SiteId); if (res <= 0) { throw new FriendlyException("刪除失敗了!"); } } return new ApiResult(); } /// <summary> /// 軟刪除 /// </summary> /// <param name="deleteInput"></param> /// <returns></returns> [HttpDelete] public virtual async Task<ApiResult> SoftDelete([FromBody] TDeleteInput deleteInput) { foreach (var item in deleteInput.Ids) { var res = await _service.UpdateAsync(d => new TEntity() { Status = false }, d => d.Id == item && d.SiteId == deleteInput.SiteId&&d.Status==true); if (res <= 0) { throw new FriendlyException("刪除失敗了!"); } } return new ApiResult(); } /// <summary> /// 列表分頁 /// </summary> /// <param name="listQuery">引數實體</param> /// <returns></returns> [HttpGet] public virtual async Task<ApiResult> GetListPages([FromQuery] TListQuery listQuery) { var res = await _service.GetPagesAsync(listQuery.Page, listQuery.Limit, d => d.SiteId == listQuery.SiteId&&d.Status==true, d => d.Id, false); return new ApiResult(data: new { count = res.TotalItems, items = res.Items }); } /// <summary> /// 詳情 /// </summary> /// <param name="detailQuery">引數實體</param> /// <returns></returns> [HttpGet] public virtual async Task<ApiResult> Detail([FromQuery] TDetailQuery detailQuery) { var res = await _service.GetModelAsync(d => d.Id == detailQuery.Id && d.SiteId == detailQuery.SiteId&&d.Status==true); return new ApiResult(data: res); } /// <summary> /// 新增 /// </summary> /// <param name="createInput">新增實體</param> /// <returns></returns> [HttpPost] public virtual async Task<ApiResult> Add([FromBody] TCreateInput createInput) { var entity = _mapper.Map<TEntity>(createInput); var res = await _service.AddAsync(entity); if (res <= 0) { throw new FriendlyException("新增失敗了!"); } return new ApiResult(data: res); } /// <summary> /// 修改-預設忽略更新CreateTime欄位 /// </summary> /// <param name="updateInput">修改實體</param> /// <returns></returns> [HttpPut] public virtual async Task<ApiResult> Modify([FromBody] TUpdateInput updateInput) { var entity = _mapper.Map<TEntity>(updateInput); var res = await _service.UpdateAsync(entity, d => new { d.CreateTime }); if (res <= 0) { throw new FriendlyException("修改失敗了!"); } return new ApiResult(data: res); } }
效果圖
總結
好了,又要到說再見的時候了,框架我只要有時間就會一直更新下去,不合理的地方歡迎瀏覽程式碼指導批評,我希望這個框架從簡單的一點一滴做起,慢慢地把它做大做強。算是程式設計師階段最後一次做框架了,什麼時候不更新了,有可能就轉行了。大家也可以不使用這個框架,只要裡面地思路能幫助到一部分人,我認為這就足夠了。
原始碼地址
碼雲:https://gitee.com/shenniu_code_group/shen-nius.-modularity
github:https://github.com/realyrare/ShenNiusFramework