[外包]!采用asp.net core 快速构建小型创业公司后台管理系统(一)
今天给大家分享一下采用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如何更好灵活的配置
上一篇: 别让你妈妈把你拉出来了
下一篇: MyBatis中避免注入攻击