今天给大家分享一下采用asp.net core 快速构建小型创业公司后台管理系统,该项目是我给一个朋友做的,将要用到公司项目,今天分享出来权限管理模块喜欢的朋友可以试用用一下。
项目不是一个什么新项目,也没有用到什么牛逼的东西,但里面融入了我用asp.net core构建管理系统的思考,可以说是思想的结合体。
该项目的权限模块我已经放到了Liinu上,项目预览地址:http://xingchenbeta.52expo.top/Welcome 账户:admin 密码:123456
接下来再说一下该项目基本点:
- 项目采用Asp.Net Core2.1+EF Core构建后台服务
- 项目前端使用BootStrap+Angular1.x构建管理系统前端
- 数据库采用MySql 5.7.24
- 部署环境在CentOS 7.4上
- 项目中用到Redis
- 用到Quartz
接下来在上一张项目图片:
项目基本完成功能点有:
- 角色管理(curd)
- 角色授权
- 后台用户管理(crud)
- 代码动态生成权限
废话就说这么多吧,接下来我就从头开始介绍这个项目。
一.项目基本组成介绍
项目成员就这么多,大家看着名字可能很眼熟,其实我只接用了DDD的名字而已。
除去单元测试,项目分为五部分:API,WebSite,Domain,Infrastuctrue,Applicaiton
Api 主要用来给我们的移动端兄弟们提供api支持
Website里面呢就是我们的后台管理系统
Domain里面我放的是数据库实体模型,识图模型,枚举类,服务接口约束,以及必要的核心东西
Infrastructure里面我放着部分Domain接口的实现,数据库上下文,三方,工具类,一些拓展方法,等基础构造
Application里面放着Domain接口的实现,这里我主要放自己需要写的服务,也可以称之为业务逻辑
二.抽丝剥茧,看看具体怎么实现
1,设计好数据库(我们还是采用db frist的思想,因为我觉得code frist开发的有点慢,并不是说code frist不好!),在Domain项目引入支持mysql的nuget包
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.2" />
生成实体类
## dbfrist 数据库迁移命令
Scaffold-DbContext "Server=host;Database=RestApi;User=root;Password=xx.;" "Pomelo.EntityFrameworkCore.MySql" -Force -OutputDir Entitys
如图
把context从实体中拉出来,放到infrastructure里,在Infrastructure里创建context文件夹
2. 在Domain 创建Repositorys文件夹
IDbRepository封装装ef数据仓储接口
/// <summary> /// 封装装ef数据仓储接口 /// </summary> public interface IDbRepository<TContext> where TContext:dbcontext { /// <summary> /// get <see cref="!:TSource" /> from raw sql query /// the TSource must in database or <see cref="T:Microsoft.EntityFrameworkCore.DbContext" /> /// </summary> /// <typeparam name="TSource"></typeparam> /// <param name="sql"></param> /// <param name="parameters"></param> /// <returns></returns> IQueryable<TSource> FromSql<TSource>(string sql, params object[] parameters) where TSource : class; /// <summary>get single or default TSource</summary> /// <typeparam name="TSource">entity</typeparam> /// <param name="predicate"></param> /// <returns></returns> TSource Single<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class; /// <summary>get first or default TSource</summary> /// <typeparam name="TSource"></typeparam> /// <param name="predicate"></param> /// <returns></returns> TSource First<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class; /// <summary>select entity by conditions</summary> /// <typeparam name="TSource"></typeparam> /// <param name="predicate"></param> /// <returns></returns> IQueryable<TSource> Where<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class; /// <summary>counting the entity's count under this condition</summary> /// <typeparam name="TSource"></typeparam> /// <param name="predicate"></param> /// <returns></returns> int Count<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class; /// <summary>return the query</summary> /// <typeparam name="TSource"></typeparam> /// <returns></returns> IQueryable<TSource> Query<TSource>() where TSource : class; /// <summary>check the condition</summary> /// <typeparam name="TSource"></typeparam> /// <param name="predicate"></param> /// <returns></returns> bool Exists<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class; /// <summary>paging the query</summary> /// <typeparam name="T"></typeparam> /// <param name="query"></param> /// <param name="pageIndex">page index</param> /// <param name="pageSize">page size </param> /// <param name="count">total row record count</param> /// <returns></returns> IQueryable<T> Pages<T>(IQueryable<T> query, int pageIndex, int pageSize, out int count) where T : class; /// <summary>paging the query</summary> /// <typeparam name="T"></typeparam> /// <param name="pageIndex">page index</param> /// <param name="pageSize">page size </param> /// <param name="count">total row record count</param> /// <returns></returns> IQueryable<T> Pages<T>(int pageIndex, int pageSize, out int count) where T : class; /// <summary> /// 分页 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="query"></param> /// <param name="pageIndex"></param> /// <param name="pageSize"></param> /// <param name="count">总条数</param> /// <param name="pageCount">页数</param> /// <returns></returns> IQueryable<T> Pages<T>(IQueryable<T> query, int pageIndex, int pageSize, out int count, out int pageCount) where T : class; /// <summary>save all the changes add, update, delete</summary> void Save(); /// <summary>add entity to context or database</summary> /// <param name="entity"></param> /// <param name="save">save the add and all changes before this to database</param> void Add(object entity, bool save = false); /// <summary>update entity to context or database</summary> /// <param name="entity"></param> /// <param name="save">save the update and all changes before this to database</param> void Update(object entity, bool save = false); /// <summary>update entitys to context or database</summary> /// <param name="list"></param> /// <param name="save">save the updates and all changes before this to database</param> void Update(IEnumerable<object> list, bool save = false); /// <summary>delete entity from context or database</summary> /// <param name="entity"></param> /// <param name="save">save the delete and all changes before this to database</param> void Delete(object entity, bool save = false); /// <summary>delete entitys from context or database</summary> /// <param name="list"></param> /// <param name="save">save the deletes and all changes before this to database</param> void Delete(IEnumerable<object> list, bool save = false); }
在Infrastructure里创建Repositorys,DbRepository类实现了IDbRepository,封装泛型仓储 依赖接口 DbContext约束
/// <summary> /// 封装泛型仓储 依赖接口 DbContext约束 /// </summary> /// <typeparam name="TContext"></typeparam> public class DbRepository<TContext> : IDbRepository where TContext : DbContext { private TContext _context; protected virtual TContext DataContext { get { if ((object)this._context == null) this._context = ServiceCollectionExtension.New<TContext>(); return this._context; } } /// <summary> /// get <see cref="!:TSource" /> from raw sql query /// the TSource must in database or <see cref="T:Microsoft.EntityFrameworkCore.DbContext" /> /// </summary> /// <typeparam name="TSource"></typeparam> /// <param name="sql"></param> /// <param name="parameters"></param> /// <returns></returns> public IQueryable<TSource> FromSql<TSource>(string sql, params object[] parameters) where TSource : class { return this.DataContext.Set<TSource>().FromSql<TSource>((RawSqlString)sql, parameters); } /// <summary>get single or default TSource</summary> /// <typeparam name="TSource">entity</typeparam> /// <param name="predicate"></param> /// <returns></returns> public TSource Single<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class { if (predicate == null) return this.DataContext.Set<TSource>().SingleOrDefault<TSource>(); return this.DataContext.Set<TSource>().SingleOrDefault<TSource>(predicate); } /// <summary>get first or default TSource</summary> /// <typeparam name="TSource"></typeparam> /// <param name="predicate"></param> /// <returns></returns> public TSource First<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class { if (predicate == null) return this.DataContext.Set<TSource>().FirstOrDefault<TSource>(); return this.DataContext.Set<TSource>().FirstOrDefault<TSource>(predicate); } /// <summary>select entity by conditions</summary> /// <typeparam name="TSource"></typeparam> /// <param name="predicate"></param> /// <returns></returns> public IQueryable<TSource> Where<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class { if (predicate == null) return this.DataContext.Set<TSource>().AsQueryable<TSource>(); return this.DataContext.Set<TSource>().Where<TSource>(predicate); } /// <summary>counting the entity's count under this condition</summary> /// <typeparam name="TSource"></typeparam> /// <param name="predicate"></param> /// <returns></returns> public int Count<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class { if (predicate == null) return this.DataContext.Set<TSource>().Count<TSource>(); return this.DataContext.Set<TSource>().Count<TSource>(predicate); } /// <summary>return the query</summary> /// <typeparam name="TSource"></typeparam> /// <returns></returns> public IQueryable<TSource> Query<TSource>() where TSource : class { return (IQueryable<TSource>)this.DataContext.Set<TSource>(); } /// <summary>check the condition</summary> /// <typeparam name="TSource"></typeparam> /// <param name="predicate"></param> /// <returns></returns> public bool Exists<TSource>(Expression<Func<TSource, bool>> predicate = null) where TSource : class { if (predicate == null) return this.DataContext.Set<TSource>().Any<TSource>(); return this.DataContext.Set<TSource>().Any<TSource>(predicate); } /// <summary>paging the query</summary> /// <typeparam name="T"></typeparam> /// <param name="query"></param> /// <param name="pageIndex">page index</param> /// <param name="pageSize">page size </param> /// <param name="count">total row record count</param> /// <returns></returns> public IQueryable<T> Pages<T>(IQueryable<T> query, int pageIndex, int pageSize, out int count) where T : class { if (pageIndex < 1) pageIndex = 1; if (pageSize < 1) pageSize = 10; count = query.Count<T>(); query = query.Skip<T>((pageIndex - 1) * pageSize).Take<T>(pageSize); return query; } /// <summary>paging the query</summary> /// <typeparam name="T"></typeparam> /// <param name="pageIndex">page index</param> /// <param name="pageSize">page size </param> /// <param name="count">total row record count</param> /// <returns></returns> public IQueryable<T> Pages<T>(int pageIndex, int pageSize, out int count) where T : class { if (pageIndex < 1) pageIndex = 1; if (pageSize < 1) pageSize = 10; var source = this.DataContext.Set<T>().AsQueryable<T>(); count = source.Count<T>(); return source.Skip<T>((pageIndex - 1) * pageSize).Take<T>(pageSize); } /// <summary> /// 分页 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="query"></param> /// <param name="pageIndex"></param> /// <param name="pageSize"></param> /// <param name="count"></param> /// <param name="pageCount"></param> /// <returns></returns> public IQueryable<T> Pages<T>(IQueryable<T> query, int pageIndex, int pageSize, out int count, out int pageCount) where T : class { if (pageIndex < 1) { pageIndex = 1; } if (pageSize < 1) { pageSize = 10; } if (pageSize > 100) { pageSize = 100; } count = query.Count(); pageCount = count / pageSize; if ((decimal)pageCount < (decimal)count / (decimal)pageSize) { pageCount++; } query = query.Skip((pageIndex - 1) * pageSize).Take(pageSize); return query; } /// <summary>save all the changes add, update, delete</summary> public void Save() { this.DataContext.SaveChanges(); } /// <summary>add entity to context or database</summary> /// <param name="entity"></param> /// <param name="save">save the add and all changes before this to database</param> public void Add(object entity, bool save = false) { this.DataContext.Add(entity); if (!save) return; this.Save(); } /// <summary>update entity to context or database</summary> /// <param name="entity"></param> /// <param name="save">save the update and all changes before this to database</param> public void Update(object entity, bool save = false) { this.DataContext.Update(entity); if (!save) return; this.Save(); } /// <summary>update entitys to context or database</summary> /// <param name="list"></param> /// <param name="save">save the updates and all changes before this to database</param> public void Update(IEnumerable<object> list, bool save = false) { this.DataContext.UpdateRange(list); if (!save) return; this.Save(); } /// <summary>delete entity from context or database</summary> /// <param name="entity"></param> /// <param name="save">save the delete and all changes before this to database</param> public void Delete(object entity, bool save = false) { this.DataContext.Remove(entity); if (!save) return; this.Save(); } /// <summary>delete entitys from context or database</summary> /// <param name="list"></param> /// <param name="save">save the deletes and all changes before this to database</param> public void Delete(IEnumerable<object> list, bool save = false) { this.DataContext.RemoveRange(list); if (!save) return; this.Save(); } }
需要注意的地方我说一下
public class DbRepository<TContext> : IDbRepository<TContext> where TContext : DbContext { private TContext _context; protected virtual TContext DataContext { get { if ((object)this._context == null) this._context = ServiceCollectionExtension.New<TContext>(); return this._context; } } }
ServiceCollectionExtension.New<TContext>()是我写的一个服务拓展,可以创建出一个TContext类型的实体,当然我们可以把这个DbRepository放到Application
这样Repository这块东西就只是Domain和Application通过接口之间的约束了
3.简要说一下ServiceCollectionExtension里的东西
这个拓展我写到了Infrastructure里,主要用来做服务注册用的,他非常重要非常重要非常重要,代码我这里分享一下。
public static class ServiceCollectionExtension { private static IHttpContextAccessor _httpContextAccessor; private static IServiceProvider _serviceProvider; /// <summary> /// cerf weige /// </summary> private static IDataProtector Protector => ServiceProvider.GetDataProtector("AspNetCore", Array.Empty<string>()); /// <summary> /// 注册常用服务 /// </summary> /// <param name="service"></param> public static IServiceCollection AddRegisterContainer(this IServiceCollection services) { //注入httpContextAccessor services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); //注入配置文件获取服务 services.AddSingleton<IConfigGeter, ConfigGeter>(); return services; } // /// <summary> /// 创建自定义AddMvc /// </summary> /// <param name="services"></param> /// <param name="mvcOptions"></param> /// <returns></returns> public static IMvcBuilder AddMvcCustomer(this IServiceCollection services, Action<MvcApplicationOptions> mvcOptions = null) { ServiceCollectionDescriptorExtensions.Replace(services, ServiceDescriptor.Singleton<IFilterProvider, MvcFilterProvider>()); var result= services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddJsonOptions(_mvcJsonOptions => { _mvcJsonOptions.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; _mvcJsonOptions.SerializerSettings.DateFormatString = "yyyy-MM-d HH:mm"; _mvcJsonOptions.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local; _mvcJsonOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); _mvcJsonOptions.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); services.AddAuthentication(options => options.AddScheme<MvcCookieAuthenticationHandler>(TbConstant.WEBSITE_AUTHENTICATION_SCHEME, TbConstant.WEBSITE_AUTHENTICATION_SCHEME)); services.BuildServiceProvider().RegisterServiceProvider(); return result; } /// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="services"></param> /// <returns></returns> public static IServiceCollection AddAuthorizedFilter<T>(this IServiceCollection services) where T : class, IAuthorizationFilter { services.AddTransient<IAuthorizationFilter, T>(); return services; } /// <summary> /// 创建服务提供者 /// </summary> /// <param name="serviceProvider"></param> /// <returns></returns> public static IServiceProvider RegisterServiceProvider(this IServiceProvider serviceProvider) { _serviceProvider = serviceProvider ?? throw new MvcException("IServiceProvider serviceProvider canot be null"); _httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>(); return serviceProvider; } /// <summary> /// /// </summary> public static IServiceProvider ServiceProvider { get { if (_serviceProvider == null) { return _httpContextAccessor.HttpContext.RequestServices; } return _serviceProvider;//_httpContextAccessor.HttpContext.RequestServices; } } public static HttpContext HttpContext => _httpContextAccessor?.HttpContext; public static object New(Type type) { return ActivatorUtilities.CreateInstance(ServiceProvider, type, Array.Empty<object>()); } public static T New<T>() { return ActivatorUtilities.CreateInstance<T>(ServiceProvider, Array.Empty<object>()); } public static T Get<T>() { T val; try { val = ActivatorUtilities.GetServiceOrCreateInstance<T>(ServiceProvider); } catch (Exception ex) { try { val = ServiceProvider.GetService<T>(); } catch (Exception ex2) { try { val = default(T); } catch (Exception ex3) { throw new MvcException($"ex0={ex.Message}; ex1={ex2.Message}; ex2={ex3.Message};"); } } } if (val != null) { return val; } return default(T); } public static object Get(Type type) { try { return ActivatorUtilities.GetServiceOrCreateInstance(ServiceProvider, type); } catch { object service = ServiceProvider.GetService(type); if (service == null) { return null; } return service; } } /// <summary> /// base 64/256解密 /// </summary> /// <param name="plaintext">密文</param> /// <returns></returns> public static string Decrypt(string plaintext) { IDataProtector protector = Protector; if (protector is Base256DataProtector) { return (protector as Base256DataProtector).Unprotect(plaintext, true); } return protector.Unprotect(plaintext); } /// <summary> /// base64/256加密 /// </summary> /// <param name="plaintext">明文</param> /// <returns></returns> public static string Encrypt(string plaintext) { IDataProtector protector = Protector; if (protector is Base256DataProtector) { return (protector as Base256DataProtector).Protect(plaintext, true); } return protector.Protect(plaintext); } #endregion public static IServiceCollection AddTestIdentityServer(this IServiceCollection services) { services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Auth.Config.GetApiResources()) .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>() .AddInMemoryClients(Auth.Config.GetClients()) .AddTestUsers(Auth.Config.GetUsers()); return services; } public static IServiceCollection AddTestAuthentication(this IServiceCollection services) { services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = ConfigLocator.Instance["ApplicationUrl"]; options.RequireHttpsMetadata = false; options.ApiName = "banbrickcustomer"; }); return services; } }
4.写自己具体的服务
AccountService实现了接口IAccountService
5.在StartUp里注入服务
6.在控制器里使用
这样就大体上从上到下,梳理了一下这个项目
三.下期分享
这个项目下期准备继续讲一下里面的
- BaseController里分装的奥妙
- 配置文件读取强类型Model
- Redis如何更好灵活的配置