簡介
關于這個架構的背景,在前面我已經交代過了。不清楚的可以檢視這個連結 極簡實用的Asp.NetCore子產品化架構決定免費開源了
在最近一段時間内,對這個架構新增了以下功能:
1、新增了CMS子產品,目前整體都比較簡單,适合個人部落格使用。
2、新增了AOP緩存,使用AspectCore,緩存可做到Memarycache和redis一件切換。
3、新增AOP事務,服務層和控制器都可以打上特性标簽使用。
4、對多租戶使用Filter,不管是添加還是更新、查詢即可自動指派。
5、新增七牛雲圖檔上傳功能。
6、對于單表的增删改查,在控制器内做了封裝,有新的業務按約定建立對應的CRUD實體,一套API自動完成。
7、背景管理新增站群管理。
說了那麼多,讓我們上點代碼和截圖來瞧一瞧吧。
AOP緩存
{
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
多租戶
{
/// <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>
/// <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.-modularitygithub:
https://github.com/realyrare/ShenNiusFramework