极简实用的Asp.NetCore模块化框架新增CMS模块
简介
关于这个框架的背景,在前面我已经交代过了。不清楚的可以查看这个链接 极简实用的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
极简实用的Asp.NetCore模块化框架新增CMS模块的更多相关文章
- 极简实用的Asp.NetCore模块化框架决定免费开源了
背景 在开发这个框架之前,前前后后看过好几款模块化的框架,最后在一段时间内对ABP VNext痛下狠心,研究一段时间后,不得不说 ABP VNext的代码层面很规范,也都是一些最佳实践,开发出一个模块 ...
- 极简实用的Asp.NetCore框架再新增商城模块
概述 关于这个框架的背景,在前面我已经交代过了.不清楚的可以查看这个链接 1.极简实用的Asp.NetCore模块化框架决定免费开源了 2.极简实用的Asp.NetCore模块化框架新增CMS模块 算 ...
- Resty 一款极简的restful轻量级的web框架
https://github.com/Dreampie/Resty Resty 一款极简的restful轻量级的web框架 开发文档 如果你还不是很了解restful,或者认为restful只是一种规 ...
- RELabel : 一个极简的正则表达式匹配和展示框架
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- 极简SpringBoot指南-Chapter01-如何用Spring框架声明Bean
仓库地址 w4ngzhen/springboot-simple-guide: This is a project that guides SpringBoot users to get started ...
- 通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]
<200行代码,7个对象--让你了解ASP.NET Core框架的本质>让很多读者对ASP.NET Core管道有了真实的了解.在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分 ...
- CentOS安装使用.netcore极简教程(免费提供学习服务器)
本文目标是指引从未使用过Linux的.Neter,如何在CentOS7上安装.Net Core环境,以及部署.Net Core应用. 仅针对CentOS,其它Linux系统类似,命令环节稍加调整: 需 ...
- ASP.NETCORE MVC模块化
ASP.NETCORE MVC模块化编程 前言 记得上一篇博客中跟大家分享的是基于ASP.NETMVC5,实际也就是基于NETFRAMEWORK平台实现的这么一个轻量级插件式框架.那么今天我主要分享的 ...
- php 极简框架ES发布(代码总和不到 400 行)
ES 框架简介 ES 是一款 极简,灵活, 高性能,扩建性强 的php 框架. 未开源之前在商业公司 经历数年,数个高并发网站 实践使用! 框架结构 整个框架核心四个文件,所有文件加起来放在一起总行数 ...
随机推荐
- React Learning Paths
React Learning Paths React Expert React in Action The assessment may cover: Components Events and Bi ...
- website url spam
website url spam xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许注册用户才可以访问!
- ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await
ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await co ...
- MacBook Pro 关闭触控板
MacBook Pro 关闭触控板 https://support.apple.com/zh-cn/HT204895 https://support.apple.com/zh-cn/HT203171 ...
- js trigger click event & dispatchEvent & svg element
js trigger click event & dispatchEvent & svg element but svg element not support trigger cli ...
- EventBus / Event Bus
EventBus / Event Bus EventEmitter / Event Emitter https://greenrobot.org/eventbus/documentation/ htt ...
- 1月22日第二轮空投来袭,SPC算力福利币究竟能带来什么?
行情数据显示,比特币于14日23时30分再次突破40000美元,市值回升至7400亿美元.根据行情频道数据,比特币于14日2时展露上行态势,价格于34000美元附近起跳,至12时站上37000美元.此 ...
- django学习-25.admin管理后台里:把表名称和表字段名称的展示值都由英文显示改为由中文显示
目录结构 1.前言 2.完整的操作步骤 2.1.第一步:修改模型类Article 2.2.第二步:重启服务 2.3.第三步:退出登录并再次成功登陆admin管理后台 2.4.第四步:查看最新的表名称展 ...
- Wireshark安装使用及报文分析
先看链接!!! Wireshark使用教程:https://jingyan.baidu.com/article/93f9803fe902f7e0e56f5553.html Wireshark过滤规则筛 ...
- 单例模式有效解决过多的if-else
策略模式 引例:假如我们要分享一个篇文章.有微信分享.微博分享.QQ分享等......我们是先判断类型是哪个,然后再调用各自得API去做分享操作 一般来说,大多数人都会根据类型判断是哪个渠道吧,如下代 ...