简介

关于这个框架的背景,在前面我已经交代过了。不清楚的可以查看这个链接 极简实用的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. Microsoft Lifecycle Policy

    Microsoft Lifecycle Policy The Microsoft Lifecycle Policy gives you consistent and predictable guide ...

  2. Chrome new features preview

    Chrome new features preview CSS Overview https://css-tricks.com/new-in-chrome-css-overview/ capture ...

  3. how to convert SVG shapes to polygon

    how to convert SVG shapes to polygon 如何将 svg 的 rect 转换成 polygon rect.circle.ellipse.line.polyline.po ...

  4. js 位掩码

    原文 定义掩码 const mask0 = parseInt("00000001", 2); const mask1 = parseInt("00000010" ...

  5. js bese64转化为blob使用FormData上传

    原文 工作示例 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...

  6. C++算法代码——求数列[coci2014/2015 contest #1]

    题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1815 题目描述 Mirko在数学课上以一种有趣的方式操作数列,首先,他写下一个数列A ...

  7. 【Notes_4】现代图形学入门——光栅化、离散化三角形、深度测试与抗锯齿

    光栅化 Viewport Transform(视口变换) 将经过MVP变换后得到的单位空间模型变换到屏幕上,屏幕左边是左下角为原点. 所以视口变换的矩阵 \[M_{viewport}=\begin{p ...

  8. MySQL学习笔记(六)

    好耶,七天课程的最后一天!我当然还没精通了,,,之后可能是多练习题目然后再学学其他的东西吧.mysql新的知识点也会在后面补充的. 一.杂七杂八补充 1. 当多个函数共用同样的参数时,可以转变成类进行 ...

  9. deepin 340 USB转console线驱动安装及使用

    刚换DEEPIN系统, 有个路由器要做调整,的确是没windows友好,查了网上资料,归总一下. zhaodong@zhaodong-PC:sudo find / -name serial 进入 zh ...

  10. CMD(命令提示符)的基本操作(文件)

    打开CMD窗口,接下来将介绍如何使用CMD来创建.删除.修改.查看文件 1.1 使用CMD创建空文件(为了更好的演示,本文皆以D盘为当前路径),命令如下: copy nul xxx.xx(文件名) 命 ...