简介

关于这个框架的背景,在前面我已经交代过了。不清楚的可以查看这个链接 极简实用的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模块的更多相关文章

  1. 极简实用的Asp.NetCore模块化框架决定免费开源了

    背景 在开发这个框架之前,前前后后看过好几款模块化的框架,最后在一段时间内对ABP VNext痛下狠心,研究一段时间后,不得不说 ABP VNext的代码层面很规范,也都是一些最佳实践,开发出一个模块 ...

  2. 极简实用的Asp.NetCore框架再新增商城模块

    概述 关于这个框架的背景,在前面我已经交代过了.不清楚的可以查看这个链接 1.极简实用的Asp.NetCore模块化框架决定免费开源了 2.极简实用的Asp.NetCore模块化框架新增CMS模块 算 ...

  3. Resty 一款极简的restful轻量级的web框架

    https://github.com/Dreampie/Resty Resty 一款极简的restful轻量级的web框架 开发文档 如果你还不是很了解restful,或者认为restful只是一种规 ...

  4. RELabel : 一个极简的正则表达式匹配和展示框架

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  5. 极简SpringBoot指南-Chapter01-如何用Spring框架声明Bean

    仓库地址 w4ngzhen/springboot-simple-guide: This is a project that guides SpringBoot users to get started ...

  6. 通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]

    <200行代码,7个对象--让你了解ASP.NET Core框架的本质>让很多读者对ASP.NET Core管道有了真实的了解.在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分 ...

  7. CentOS安装使用.netcore极简教程(免费提供学习服务器)

    本文目标是指引从未使用过Linux的.Neter,如何在CentOS7上安装.Net Core环境,以及部署.Net Core应用. 仅针对CentOS,其它Linux系统类似,命令环节稍加调整: 需 ...

  8. ASP.NETCORE MVC模块化

    ASP.NETCORE MVC模块化编程 前言 记得上一篇博客中跟大家分享的是基于ASP.NETMVC5,实际也就是基于NETFRAMEWORK平台实现的这么一个轻量级插件式框架.那么今天我主要分享的 ...

  9. php 极简框架ES发布(代码总和不到 400 行)

    ES 框架简介 ES 是一款 极简,灵活, 高性能,扩建性强 的php 框架. 未开源之前在商业公司 经历数年,数个高并发网站 实践使用! 框架结构 整个框架核心四个文件,所有文件加起来放在一起总行数 ...

随机推荐

  1. React Learning Paths

    React Learning Paths React Expert React in Action The assessment may cover: Components Events and Bi ...

  2. website url spam

    website url spam xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许注册用户才可以访问!

  3. ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await

    ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await co ...

  4. MacBook Pro 关闭触控板

    MacBook Pro 关闭触控板 https://support.apple.com/zh-cn/HT204895 https://support.apple.com/zh-cn/HT203171 ...

  5. js trigger click event & dispatchEvent & svg element

    js trigger click event & dispatchEvent & svg element but svg element not support trigger cli ...

  6. EventBus / Event Bus

    EventBus / Event Bus EventEmitter / Event Emitter https://greenrobot.org/eventbus/documentation/ htt ...

  7. 1月22日第二轮空投来袭,SPC算力福利币究竟能带来什么?

    行情数据显示,比特币于14日23时30分再次突破40000美元,市值回升至7400亿美元.根据行情频道数据,比特币于14日2时展露上行态势,价格于34000美元附近起跳,至12时站上37000美元.此 ...

  8. django学习-25.admin管理后台里:把表名称和表字段名称的展示值都由英文显示改为由中文显示

    目录结构 1.前言 2.完整的操作步骤 2.1.第一步:修改模型类Article 2.2.第二步:重启服务 2.3.第三步:退出登录并再次成功登陆admin管理后台 2.4.第四步:查看最新的表名称展 ...

  9. Wireshark安装使用及报文分析

    先看链接!!! Wireshark使用教程:https://jingyan.baidu.com/article/93f9803fe902f7e0e56f5553.html Wireshark过滤规则筛 ...

  10. 单例模式有效解决过多的if-else

    策略模式 引例:假如我们要分享一个篇文章.有微信分享.微博分享.QQ分享等......我们是先判断类型是哪个,然后再调用各自得API去做分享操作 一般来说,大多数人都会根据类型判断是哪个渠道吧,如下代 ...