VS2017 签名时出错: 未能对 bin\Debug\app.publish\*.exe 签名。SignTool Error: No certificates were found_cxu123321的博客-CSDN博客

mikel阅读(775)

来源: (1条消息) VS2017 签名时出错: 未能对 bin\Debug\app.publish\*.exe 签名。SignTool Error: No certificates were found_cxu123321的博客-CSDN博客

在使用Visual Studio 2017时遇到项目生成失败的问题,出现错误:签名时出错: 未能对 bin\Debug\app.publish[项目名称].exe 签名。SignTool Error: No certificates were found that met all the given criteria.

 

SignTool Error: No certificates were found that met all the given criteria.

目录

一、简单粗暴的解决办法

这里先说下最简单的方法,只要取消掉项目的ClickOnce清单签名即可,此方法不可滥用,有一定的局限性和要注意的地方,后面会说明原因。具体操作方法如下:

1、 在VS右侧的解决方案资源管理器里找到生成失败的项目。

2、 右键打开项目的属性。

打开项目属性

3、 在属性设置界面中找到签名选项卡。

4、 取消“为ClickOnce清单签名”勾选。

取消签名多选框

完成操作后重新生成

5、 关闭属性设置界面后然后重新生成下项目就可以了。

这个方法虽然简单,但是要根据自己的情况来,不能瞎操作。之所以这样说是因为SignTool Error的问题和签名证书有关,如果项目是属于公司的,或者说这个项目的部署有用到对应的证书,这种情况只能想办法重新安装证书,当然具体怎么操作还是得跟着自己的情况来,最好咨询下项目负责人。

但如果项目本身就没有要用到签名证书的业务,那情况就和我类似,糊里糊涂对项目(Windows应用程序的项目)进了发布操作,随后就莫名其妙的遇到无法生成老报错的情况。我回想了下自己当时操作,大概就是在发布向导界面瞎按了一通完成了发布,随后不小心把对应的证书文件删除了或者做了什么不可描述的操作,结果就出现项目生成时因为签名失败而出错。

PS:其实错误信息也提示很清楚了:”No certificates were found…”,大意就是:签名错误的原因是没有找到符合给定规范的证书,所以要么证书丢了要么证书有问题咯。我的项目是一个控制台应用程序(只是用来测试几段代码的),VS2017用的是社区版本的。

二、总结与归纳

首先要明白VS2017中Windows应用程序的发布/部署默认使用的是ClickOnce技术进行部署,按照官方文档.aspx)的说明:

若要使用 ClickOnce 部署发布应用程序,必须用“公钥/私钥对”为应用程序的部署清单和应用程序清单签名。

所以问题关键点就是围绕着项目部署所使用的签名证书来的,如果仔细留意会发现项目第一次发布后,会自动生成一个.pfx文件(证书文件)。解决方法要么取消相关签名操作,要么修复有问题的证书(可以检查下证书是不是被删除了或者过期之类的)。

几个额外备注:

  1. Windows窗体或控制台应用程序都是属于Windows应用程序。
  2. 关于ClickOnce部署技术,本文没有详细说明,建议另外查阅资料。

版权声明:本文由十有三创作,采用知识共享许可协议:署名-相同方式共享 4.0 国际(CC BY-SA 4.0)。欢迎转载本文,转载请务必署名-保留作者名称及出处:https://shiyousan.com/post/636422963761134191

FreeSql (一)入门 - FreeSql - 博客园

mikel阅读(1858)

来源: FreeSql (一)入门 – FreeSql – 博客园

FreeSQL是功能强大的 .NET ORM,支持 .NetFramework 4.0+、.NetCore 2.1+、Xamarin 等支持 NetStandard 所有运行平台。

支持 MySQL/SQLServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/神通/人大金仓/翰高/MsAccess 数据库。

QQ群:4336577(已满)、8578575(在线)、52508226(在线)

模型

FreeSql 使用模型执行数据访问,模型由实体类表示数据库表或视图,用于查询和保存数据。

可从现有数据库生成实体模型,FreeSql 提供 IDbFirst 接口实现生成实体模型

或者手动创建模型,基于模型创建或修改数据库,提供 ICodeFirst 同步结构的 API(甚至可以做到开发阶段自动同步)。

using FreeSql.DataAnnotations;
using System;

public class Blog
{
    [Column(IsIdentity = true, IsPrimary = true)]
    public int BlogId { get; set; }
    public string Url { get; set; }
    public int Rating { get; set; }
}

声明

dotnet add packages FreeSql.Provider.Sqlite

static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=db1.db")
    .UseAutoSyncStructure(true) //自动同步实体结构到数据库
    .Build(); //请务必定义成 Singleton 单例模式

注意: IFreeSql 在项目中应以单例声明,而不是在每次使用的时候创建。

迁移

程序运行中FreeSql会检查AutoSyncStructure参数,以此条件判断是否对比实体与数据库结构之间的变化,达到自动迁移的目的。

查询

var blogs = fsql.Select<Blog>()
    .Where(b => b.Rating > 3)
    .OrderBy(b => b.Url)
    .Skip(100)
    .Limit(10) //第100行-110行的记录
    .ToList();

插入

var blog = new Blog { Url = "http://sample.com" };
blog.BlogId = (int)fsql.Insert<Blog>()
    .AppendData(blog)
    .ExecuteIdentity();

更新

fsql.Update<Blog>()
    .Set(b => b.Url, "http://sample2222.com")
    .Where(b => b.Url == "http://sample.com")
    .ExecuteAffrows();

删除

fsql.Delete<Blog>()
    .Where(b => b.Url == "http://sample.com")
    .ExecuteAffrows();

FreeSqlBuilder

方法 返回值 说明
UseConnectionString this 设置连接串
UseSlave this 设置从数据库,支持多个
UseConnectionFactory this 设置自定义数据库连接对象(放弃内置对象连接池技术)
UseAutoSyncStructure this 【开发环境必备】自动同步实体结构到数据库,程序运行中检查实体创建或修改表结构
UseNoneCommandParameter this 不使用命令参数化执行,针对 Insert/Update,也可临时使用 IInsert/IUpdate.NoneParameter()
UseGenerateCommandParameterWithLambda this 生成命令参数化执行,针对 lambda 表达式解析
UseLazyLoading this 开启延时加载功能
UseMonitorCommand this 监视全局 SQL 执行前后
UseNameConvert this 自动转换实体、属性名称 Entity Property -> Db Filed
UseExitAutoDisposePool this 监听 AppDomain.CurrentDomain.ProcessExit/Console.CancelKeyPress 事件自动释放连接池 (默认true)
Build<T> IFreeSql<T> 创建一个 IFreeSql 对象,注意:单例设计,不要重复创建

ConnectionStrings

DataType.MySql

Data Source=127.0.0.1;Port=3306;User ID=root;Password=root; Initial Catalog=cccddd;Charset=utf8; SslMode=none;Min pool size=1

DataType.PostgreSQL

Host=192.168.164.10;Port=5432;Username=postgres;Password=123456; Database=tedb;Pooling=true;Minimum Pool Size=1

DataType.SQLServer

Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Min Pool Size=1

DataType.Oracle

user id=user1;password=123456; data source=//127.0.0.1:1521/XE;Pooling=true;Min Pool Size=1

DataType.Sqlite

Data Source=|DataDirectory|\document.db; Attachs=xxxtb.db; Pooling=true;Min Pool Size=1

DataType.Firebird

database=localhost:D:\fbdata\EXAMPLES.fdb;user=sysdba;password=123456

DataType.MsAccess

Provider=Microsoft.Jet.OleDb.4.0;Data Source=d:/accdb/2003.mdb

DataType.Dameng(达梦)

server=127.0.0.1;port=5236;user id=2user;password=123456789;database=2user;poolsize=5

DataType.ShenTong(神通)

HOST=192.168.164.10;PORT=2003;DATABASE=OSRDB;USERNAME=SYSDBA;PASSWORD=szoscar55;MAXPOOLSIZE=2

DataType.KingbaseES(人大金仓)

Server=127.0.0.1;Port=54321;UID=USER2;PWD=123456789;database=TEST;MAXPOOLSIZE=2

DataType.OdbcMySql

Driver={MySQL ODBC 8.0 Unicode Driver}; Server=127.0.0.1;Persist Security Info=False; Trusted_Connection=Yes;UID=root;PWD=root; DATABASE=cccddd_odbc;Charset=utf8; SslMode=none;Min Pool Size=1

DataType.OdbcSqlServer

Driver={SQL Server};Server=.;Persist Security Info=False; Trusted_Connection=Yes;Integrated Security=True; DATABASE=freesqlTest_odbc; Pooling=true;Min Pool Size=1

DataType.OdbcOracle

Driver={Oracle in XE};Server=//127.0.0.1:1521/XE; Persist Security Info=False; Trusted_Connection=Yes;UID=odbc1;PWD=123456; Min Pool Size=1

DataType.OdbcPostgreSQL

Driver={PostgreSQL Unicode(x64)};Server=192.168.164.10; Port=5432;UID=postgres;PWD=123456; Database=tedb_odbc;Pooling=true;Min Pool Size=1

DataType.OdbcDameng (达梦)

Driver={DM8 ODBC DRIVER};Server=127.0.0.1:5236; Persist Security Info=False; Trusted_Connection=Yes; UID=USER1;PWD=123456789

DataType.OdbcKingbaseES (人大金仓)

Driver={KingbaseES 8.2 ODBC Driver ANSI};Server=127.0.0.1;Port=54321;UID=USER2;PWD=123456789;database=TEST

DataType.Odbc

Driver={SQL Server};Server=.;Persist Security Info=False; Trusted_Connection=Yes;Integrated Security=True; DATABASE=freesqlTest_odbc; Pooling=true;Min pool size=1

Packages

Package Name Version 说明
FreeSql.Repository NETStandard2.0、net45、net40 通用仓储 + UnitOfWork 实现
FreeSql.DbContext NETStandard2.0、net45、net40 EFCore 的使用风格实现
FreeSql.Provider.MySql NETStandard2.0、net45、net40 基于 MySql.Data(Oracle官方)
FreeSql.Provider.MySqlConnector NETStandard2.0、net45 基于 MySqlConnector
FreeSql.Provider.PostgreSQL NETStandard2.0、net45 基于 PostgreSQL 9.5+
FreeSql.Provider.SqlServer NETStandard2.0、net45、net40 基于 SqlServer 2005+
FreeSql.Provider.SqlServerForSystem NETStandard2.0、net45、net40 基于 System.Data.SqlClient + SqlServer 2005+
FreeSql.Provider.Sqlite NETStandard2.0、net45、net40
FreeSql.Provider.Oracle NETStandard2.0、net45、net40
FreeSql.Provider.Firebird NETStandard2.0、net452
FreeSql.Provider.MsAccess NETStandard2.0、net45、net40
FreeSql.Provider.Dameng NETStandard2.0、net45、net40 基于 达梦数据库
FreeSql.Provider.ShenTong NETStandard2.0、net45、net40 基于 神州通用数据库
FreeSql.Provider.KingbaseES NETStandard2.0、net461 基于 人大金仓数据库
FreeSql.Provider.Odbc NETStandard2.0、net45、net40 基于 ODBC
FreeSql.Extensions.LazyLoading NETStandard2.0、net45、net40 延时属性扩展包
FreeSql.Extensions.JsonMap NETStandard2.0、net45、net40 Json 序列化扩展包
FreeSql.Extensions.Linq NETStandard2.0、net45、net40 LinqToSql IQueryable 扩展包
FreeSql.Extensions.BaseEntity NETStandard2.0
FreeSql.Generator NETCoreapp3.1 从数据库生成实体类

系列文章导航

Jetbrains系列产品激活文件-小刀娱乐网 - 专注活动,软件,教程分享!总之就是网络那些事。

mikel阅读(1559)

来源: Jetbrains系列产品激活文件-小刀娱乐网 – 专注活动,软件,教程分享!总之就是网络那些事。

软件介绍

JetBrains公司旗下的IntelliJ IDEAJava是广泛使用的编程语言开发撰写时所用的集成开发环境。JetBrains全家桶,包括IntelliJ IDEA , Pycharm , Webstorm , PhpStorm , Rider Clion , RubyMine, AppCode, Goland, DataGrip , Kotlin等15款产品。Jetbrains系列产品2020.2.4最新激活文件,可以永久激活最新版本。

IDEA,专为Java编程语言开发撰写时常用的集成开发环境。

PhpStorm,专为Web和PHP开发者打造的专业集成开发环境。

RubyMine,针对Ruby和Rails的集成开发环境,为使用Ruby语言进行Web开发的开发人员们提供最为必需的工具和舒适便捷的开发环境。

PyCharm,针对Python语言打造的轻量级IDE,通过Django框架和Google App Engine来支持web开发。

AppCode,专为iOS/OS X开发人员打造的智能IDE,协助他们以更高的生产效率使用Objective-C、Swift、C或C++进行苹果设备的应用开发。

CLion,适用于C/C++开发人员的跨平台IDE,提供极佳的编码协助,为您节省大量时间。

WebStorm,适用于专业JavaScript和前端Web开发人员的集成开发环境。

DataGrip,适用于数据库与SQL开发人员的超级利器。

软件截图

1.png

2.png

关于激活

by Neo Peng

zhile.io/2018/08/25/jetbrains-license-server-crack.html

Jetbrains全系列产品2020.2.4及以下版本(理论上适用目前所有新老版本)

最新注册服务器(License Server)的解锁,可用它来激活你的Jetbrains IDE。

支持Activation Code注册码激活(自定义License name)可用于离线环境。

新的agent license server:https://fls.jetbrains-agent.com(HTTP也可用)

Jebrains付费插件Activation code,暂不在列表的试试 license server。

以下IDE版本实测可成功激活:

IntelliJ IDEA 2020.2.4及以下

AppCode 2019.3.7及以下

CLion 2019.3.5及以下

DataGrip 2020.2.4及以下

GoLand 2020.2.4及以下

PhpStorm 2020.2.4及以下

PyCharm 2020.2.4及以下

Rider 2019.3.4及以下

RubyMine 2019.3.4及以下

WebStorm 2020.2.4及以下

2020.11.26 例行更新激活文件配置助手安装参数

本激活文件适用于2020、2019、2018 全系列版

使用说明

Jetbrains系列产品 2020.2.3 激活文件 v3.2.2 2020.10.27 (支持2020.2.3及以下版本)

1.使用说明:之前lib目录有jar文件先删除,启动软件选试用(Evaluate for free)- Evaluate

2.将解锁激活文件 jetbrains-agent-latest.zip 拖到IDE欢迎界面,重启软件,会弹出配置助手

3.将最新安装参数->粘贴->配置助手对话框->点击为产品激活,重启即激活旗舰版至2089年!

JetBrains产品官方简体中文语言包页面(适用 JetBrains所有产品汉化)

要个毛的使用说明,启动软件,选择试用(Evaluate for free)- Evaluate

JetBrains_zh-CNLangPack_EAP.jar汉化插件包拖到IDE欢迎界面重启完事。

https://plugins.jetbrains.com/plugin/13710

教育邮箱或学生证免费1年正版授权官方申请地址

https://sales.jetbrains.com/hc/zh-cn/articles/207154369

创业公司5折正版授权官方购买地址

https://www.jetbrains.com/shop/eform/startup

下载地址:

链接: https://pan.baidu.com/s/1UD8W2C3cLy07MyFcRjR7RQ 提取码: q6ms

ASP.NET MVC同时支持web与webapi模式_Laymat's blog-CSDN博客

mikel阅读(888)

来源: ASP.NET MVC同时支持web与webapi模式_Laymat’s blog-CSDN博客

我们在创建 web mvc项目时是不支持web api的接口方式访问的,所以我们需要添加额外的组件来支持实现双模式。

首先我们需要准备三个web api依赖的组件(目前在.net 4/4.5版本下面测试正常,2.0暂未进行测试,需要自行测试)

1、Microsoft.AspNet.WebApi.Client.5.2.2

2、Microsoft.AspNet.WebApi.Core.5.2.2

3、Microsoft.AspNet.WebApi.WebHost.5.2.2

web api依赖组件下载地址 https://pan.baidu.com/s/1slJHdVJ

下载依赖组件后解压至packages目录或其他目录即可,解压完毕后打开编辑器 > 添加引用 > 找到下载的三个依赖dll并引用(其中Microsoft.AspNet.WebApi.Client.5.2.2为System.Net.Http的扩展包,故引用只有两个):

(如果原来已经引用了System.Web.Http则需要删除原来的引用后重新引用)

添加完引用后,我们需要在App_Start目录添加一个WebApiConfig的配置文件,用于初始化api访问路由,代码如下:

  1.     public static class WebApiConfig
  2.     {
  3.         public static void Register(HttpConfiguration config)
  4.         {
  5.             // Web API 路由
  6.             config.MapHttpAttributeRoutes();
  7.             config.Routes.MapHttpRoute(
  8.                 name: “DefaultApi”,
  9.                 routeTemplate: “api/{controller}/{id}”,
  10.                 defaults: new { id = RouteParameter.Optional }
  11.             );
  12.         }
  13.     }

添加完该配置文件后,我们接下来就需要在Global.asax全局文件中注册该配置文件:

  1.         protected void Application_Start()
  2.         {
  3.             AreaRegistration.RegisterAllAreas();
  4.             GlobalConfiguration.Configure(WebApiConfig.Register);
  5.             FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
  6.             RouteConfig.RegisterRoutes(RouteTable.Routes);
  7.         }

(注意:GlobalConfiguration.Configure(WebApiConfig.Register);注册信息必须位于普通RouteConfig之前,不然无法生效。)

完成以上配置操作后,我们就对webapi的基本运行环境配置完毕了,接下来我们添加一个web api controller:

  1.     [RoutePrefix(“api/Notify”)]
  2.     public class NotifyController : ApiController
  3.     {
  4.         [Route(“Alipay”)]
  5.         public string Alipay()
  6.         {
  7.             return “success”;
  8.         }
  9.     }

[RoutePrefix(“api/Notify”)] 配置api访问路径  示例:http://domain/api/Notify

[Route(“Alipay”)] 配置api访问接口  示例:http://domain/api/Notify/Alipay

如果需要对某个方法设置访问模式,则可以添加上[HttpPost]或[HttpGet]位于[Route]上方。

Datav超炫酷的可视化,大屏数据展示组件库-dataV组件库,“react-vue -组件库”_@baby张~的博客-CSDN博客

mikel阅读(1659)

来源: Datav超炫酷的可视化,大屏数据展示组件库-dataV组件库,“react-vue -组件库”_@baby张~的博客-CSDN博客

简单demo ,文尾有github demo地址

对可视化有所了解的应该都知道,某云平台的一款datav大屏可视化的工具,作者前年买了个个人版的,直接在页面上拖住就可以了确实很强。最近平台发来邮件,说我的个人版datav到期了,本来想续租,发现之前的个人版下架了,只剩下企业版、专业版、至尊版,最便宜的企业版一年4800,看着这价钱我慌了,够我吃一年小龙虾的了,不香嘛。果断放弃续费。。。

于是乎,就发现了这款github刚出来几个月的组件库。datav

如他所言:开源长期维护, 目前支持vue 、react、npm

所以组件都有vue和react版可以切换,很贴心。

svg绘制的特效,也就避免大屏幕缩放失真的问题

官方效果图


看着还是有点东西的,那么我们来实际操作一波看看。

自己动手撸

创建一个react或者vue项目

npm install @jiaminghi/data-view
或 yarn add @jiaminghi/data-view
  • 1
  • 2

也支持cdn

<!--调试版-->
  <script src="https://unpkg.com/@jiaminghi/data-view/dist/datav.map.vue.js"></script>
<!--压缩版 生产版本-->
  <script src="https://unpkg.com/@jiaminghi/data-view/dist/datav.min.vue.js"></script> 
  • 1
  • 2
  • 3
  • 4

安装完引入我们需要用到的组件,dom里直接使用组件

注意:它有react和vue版本,使用时别忘了切换

import { BorderBox1 ,BorderBox8 ,BorderBox13,Decoration1 ,ScrollBoard,ScrollRankingBoard } from '@jiaminghi/data-view-react'

 <BorderBox8>
  <div className="xpanel">
    <div className="fill-h" id="mainMap3"></div>
  </div>
 </BorderBox8>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

还可以配合其他的图表使用,我这里还用了echarts

最后实现的就是主图展示的

github Demo地址:https://github.com/babybrotherzb/my-datav

实际应用,升级版数据大屏项目

核心:Datav+G2+L7+Canvas+手写翻牌器
在这里插入图片描述
面朝科技-欢迎你的加入:https://www.mianchao.com/

还不懂Docker?一个故事安排的明明白白! - 轩辕之风 - 博客园

mikel阅读(637)

来源: 还不懂Docker?一个故事安排的明明白白! – 轩辕之风 – 博客园

程序员受苦久矣

多年前的一个夜晚,风雨大作,一个名叫Docker的年轻人来到Linux帝国拜见帝国的长老。

“Linux长老,天下程序员苦于应用部署久矣,我要改变这一现状,希望长老你能帮帮我”

长老回答:“哦,小小年纪,口气不小,先请入座,你有何所求,愿闻其详”

Docker坐下后开始侃侃而谈:“当今天下,应用开发、测试、部署,各种库的依赖纷繁复杂,再加上版本之间的差异,经常出现在开发环境运行正常,而到测试环境和线上环境就出问题的现象,程序员们饱受此苦,是时候改变这一状况了。”

Docker回头看了一眼长老接着说到:“我想做一个虚拟的容器,让应用程序们运行其中,将它们需要的依赖环境整体打包,以便在不同机器上移植后,仍然能提供一致的运行环境,彻底将程序员们解放出来!”

Linux长老听闻,微微点头:“年轻人想法不错,不过听你的描述,好像虚拟机就能解决这个问题。将应用和所依赖的环境部署到虚拟机中,然后做个快照,直接部署虚拟机不就可以了吗?”

Docker连连摇头说到:“长老有所不知,虚拟机这家伙笨重如牛,体积又大,动不动就是以G为单位的大小,因为它里面要运行一个完整的操作系统,所以跑起来格外费劲,慢就不说了,还非常占资源,一台机器上跑不了几台虚拟机就把性能拖垮了!而我想要做一个轻量级的虚拟容器只提供一个运行环境,不用运行一个操作系统,所有容器中的系统内核还是和外面的宿主机共用的,这样就可以批量复制很多个容器,轻便又快捷

Linux长老站了起来,来回踱步了几圈,思考片刻之后,忽然拍桌子大声说到:“真是个好想法,这个项目我投了!”

Docker眼里见光,喜上眉梢,“这事还真离不开长老的帮助,要实现我说的目标,对进程的管理隔离都至关重要,还望长老助我一臂之力!”

“你稍等”,Linux长老转身回到内屋。没多久就出来了,手里拿了些什么东西。

“年轻人,回去之后,尽管放手大干,我赐你三个锦囊,若遇难题,可依次拆开,必有大用”

Docker开心的收下了三个锦囊,拜别Linux长老后,冒雨而归。

锦囊1:chroot & pivot_root

受到长老的鼓励,Docker充满了干劲,很快就准备启动他的项目。

作为一个容器,首要任务就是限制容器中进程的活动范围——能访问的文件系统目录。决不能让容器中的进程去肆意访问真实的系统目录,得将他们的活动范围划定到一个指定的区域,不得越雷池半步!

到底该如何限制这些进程的活动区域呢?Docker遇到了第一个难题。

苦思良久未果,Docker终于忍不住拆开了Linux长老送给自己的第一个锦囊,只见上面写了两个函数的名字:chroot & pivot_root

Docker从未使用过这两个函数,于是在Linux帝国四处打听它们的作用。后来得知,通过这两个函数,可以修改进程和系统的根目录到一个新的位置。Docker大喜,长老真是诚不欺我!

有了这两个函数,Docker开始想办法怎么来“伪造”一个文件系统来欺骗容器中的进程。

为了不露出破绽,Docker很聪明,用操作系统镜像文件挂载到容器进程的根目录下,变成容器的rootfs,和真实系统目录一模一样,足可以以假乱真:

$ ls /
bin dev etc home lib lib64 mnt opt proc root run sbin sys tmp usr var

锦囊2:namespace

文件系统的问题总算解决了,但是Docker不敢懈怠,因为在他心里,还有一个大问题一直困扰着他,那就是如何把真实系统所在的世界隐藏起来,别让容器中的进程看到。

比如进程列表、网络设备、用户列表这些,是决不能让容器中的进程知道的,得让他们看到的世界是一个干净如新的系统。

Docker心里清楚,自己虽然叫容器,但这只是表面现象,容器内的进程其实和自己一样,都是运行在宿主操作系统上面的一个个进程,想要遮住这些进程的眼睛,瞒天过海,实在不是什么容易的事情。

Docker想过用HOOK的方式,欺骗进程,但实施起来工作太过复杂,兼容性差,稳定性也得不到保障,思来想去也没想到什么好的主意。

正在一筹莫展之际,Docker又想起了Linux长老送给自己的锦囊,他赶紧拿了出来,打开了第二个锦囊,只见上面写着:namespace

Docker还是不解其中之意,于是又在Linux帝国到处打听什么是namespace。

经过一阵琢磨,Docker总算是明白了,原来这个namespace是帝国提供的一种机制,通过它可以划定一个个的命名空间,然后把进程划分到这些命名空间中。

 

 

 

而每个命名空间都是独立存在的,命名空间里面的进程都无法看到空间之外的进程、用户、网络等等信息。

这不正是Docker想要的吗?真是踏破铁鞋无觅处,得来全不费功夫!

Docker赶紧加班加点,用上了这个namespace,将进程的“视野”锁定在容器规定的范围内,如此一来,容器内的进程彷佛被施上了障眼法,再也看不到外面的世界。

锦囊3:CGroup

文件系统和进程隔离的问题都解决了,Docker心里的石头总算是放下了。心里着急着想测试自己的容器,可又好奇这最后一个锦囊写的是什么,于是打开了第三个锦囊,只见上面写着:CGroup

这又是什么东西?Docker仍然看不懂,不过这一次管不了那么许多了,先运行起来再说。

试着运行了一段时间,一切都在Docker的计划之中,容器中的进程都能正常的运行,都被他构建的虚拟文件系统和隔离出来的系统环境给欺骗了,Docker高兴坏了!

很快,Docker就开始在Linux帝国推广自己的容器技术,结果大受欢迎,收获了无数粉丝,连nginxredis等一众大佬都纷纷入驻。

然而,鲜花与掌声的背后,Docker却不知道自己即将大难临头。

这天,Linux帝国内存管理部的人扣下了Docker准备“处决”掉他,Docker一脸诧异的问到,“到底发生了什么事,为什么要对我下手?”

管理人员厉声说到:“帝国管理的内存快被一个叫Redis的家伙用光了,现在要挑选一些进程来杀掉,不好意思,你中奖了”

 

 

 

Redis?这家伙不是我容器里的进程吗?Docker心中一惊!

“两位大人,我认识帝国的长老,麻烦通融通融,找别人去吧,Redis那家伙,我有办法收拾他”

没想到他还认识帝国长老,管理人员犹豫了一下,就放了Docker到别处去了。

惊魂未定的Docker,思来想去,如果不对容器中的进程加以管束,那简直太危险了!除了内存,还有CPU、硬盘、网络等等资源,如果某个容器进程霸占着CPU不放手,又或者某个容器进程疯狂写硬盘,那迟早得连累到自己身上。看来必须得对这些进程进行管控,防止他们干出出格的事来。

这时候,他想起了Linux长老的第三个锦囊:CGroup!说不定能解这燃眉之急。

经过一番研究,Docker如获至宝,原来这CGroup和namespace类似,也是Linux帝国的一套机制,通过它可以划定一个个的分组,然后限制每个分组能够使用的资源,比如内存的上限值、CPU的使用率、硬盘空间总量等等。系统内核会自动检查和限制这些分组中的进程资源使用量。

 

 

Linux长老这三个锦囊简直太贴心了,一个比一个有用,Docker内心充满了感激。

随后,Docker加上了CGroup技术,加强了对容器中的进程管控,这才松了一口气。

在Linux长老三个锦囊妙计的加持下,Docker可谓风光一时,成为了Linux帝国的大名人。

然而,能力越大,责任越大,让Docker没想到的是,新的挑战还在后面。

阿里大鱼--短信发送API_hit、run-CSDN博客_阿里大鱼短信

mikel阅读(783)

来源: 阿里大鱼–短信发送API_hit、run-CSDN博客_阿里大鱼短信

参考链接一
参考链接二
项目中运用了阿里大鱼来实现短信的发送.主要步骤如下
1)申请短信签名
在控制台完成模板与签名的申请
2)申请短信模板
在控制台完成模板与签名的申请
3) 第一部分,获取IAcsClient对象,该对象用来发送请求。

//定义常量
final String product = "Dysmsapi";//短信API产品名称(短信产品名固定,无需修改)
final String domain = "dysmsapi.aliyuncs.com";//短信API产品域名(接口地址固定,无需修改)
//替换成你的AK秘钥
final String accessKeyId = "yourAccessKeyId";//你的accessKeyId,参考本文档步骤2
final String accessKeySecret = "yourAccessKeySecret";//你的accessKeySecret,参考本文档步骤2
//设置超时时间-可自行调整
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化ascClient,暂时不支持多region(请勿修改)
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId,
accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);

4)根据短信模板,创建Request请求对象

//组装请求对象
 SendSmsRequest request = new SendSmsRequest();
 //使用post提交
 request.setMethod(MethodType.POST);
 //必填:待发送手机号
 request.setPhoneNumbers("1500000000");
 //必填:短信签名-可在短信控制台中找到
 request.setSignName("云通信");
 //必填:短信模板-可在短信控制台中找到,发送国际/港澳台消息时,请使用国际/港澳台短信模版
 request.setTemplateCode("SMS_1000000");
 //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
 if(content != null){
       request.setTemplateParam(content.toString());
   }
 //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
 request.setOutId("yourOutId");

5)发送短信,获取响应对象

//请求失败这里会抛ClientException异常
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
 if("OK".equals(sendSmsResponse.getCode())){
                return ResultData.ok();
            }else if("isv.MOBILE_COUNT_OVER_LIMIT".equals(sendSmsResponse.getCode()) || 		"isv.BUSINESS_LIMIT_CONTROL".equals(sendSmsResponse.getCode())){ //限流
                return ResultData.fail("短信发送频繁,请稍后再试");
            }

深入浅出之Smarty模板引擎工作机制(二) - 曾是土木人 - 博客园

mikel阅读(1033)

来源: 深入浅出之Smarty模板引擎工作机制(二) – 曾是土木人 – 博客园

源代码下载地址:深入浅出之Smarty模板引擎工作机制
接下来根据以下的Smarty模板引擎原理流程图开发一个自己的模板引擎用于学习,以便加深理解。

 

Smarty模板引擎的原理,其实是这么一个过程:
把模板文件编译成php文件,然后每次都去读取下模板的修改时间,没有修改就不编译。然后include这个“编译”后的PHP文件。
所谓编译也就是模板用正则替换成含PHP代码的过程。
实际上并不会每次请求都编译,所以性能尚可。

模板文件和php程序文件经过模板引擎的编译后合成为一个文件,即编译后的文件。

接下来,我们根据该原理流程写一个简单的模板引擎。。。。。。

  先贴上核心代码:

  Smarty.class.php文件

复制代码
<?php
    class Smarty{
        public $template_dir;//模板目录
        public $compile_dir;//编译目录
        public $arr=array();//定义一个数组,用以存放assign中的第二个参数传过来的值
        public function __construct($template_dir="../templates",$compile_dir="../templates_c"){
                $this->template_dir=$template_dir;//模板目录
                $this->compile_dir=$compile_dir;  //编译目录
            }
        public function assign($content,$replacment=null){
                if($content!=""){                 //如果指定模板变量,才将要赋的值存储到数组中
                        $this->arr[$content]=$replacment;
                    }
            }
        public function display($page){
                $tplFile=$this->template_dir."/".$page;//读取模板文件,注意:如果模板目录下还有子目录,记得要写完整,比如,$smarty->display('Default/index.tpl')
                if(!file_exists($tplFile)){
                        return;
                }
                $comFile=$this->compile_dir."/"."com_".$page.".php";
                $tplContent=$this->con_replace(file_get_contents($tplFile));//将smarty标签替换为php的标签
                file_put_contents($comFile,$tplContent);
                include $comFile;
        }
        public function con_replace($content){
                $pattern=array(
                    '/<{\s*\$([a-zA-Z_][a-zA-Z_0-9]*)\s*}>/i'
                );
                   $replacement=array(
                       '<?php echo $this->arr["${1}"] ?>'
                );
                    return preg_replace($pattern,$replacement,$content);
                }
        }
?>
复制代码

Smarty.class.php代码解释:

  • $template_dir  指定模板文件的目录
  • $compile_dir   指定编译后的模板文件的目录
  • 构造函数

public function __construct($template_dir=”../templates”,$compile_dir=”../templates_c”)

{

$this->template_dir=$template_dir;

$this->compile_dir=$compile_dir;

}

默认情况下,Smarty模板引擎将把templates目录用于存放模板文件,templates_c用于存放编译后的文件

 

  • assign($content,$replacment=null)函数的工作机制是将每次要传递给模板中的变量的值通过语句:$this->arr[$content]=$replacment;保存到数组中。

     那为何要$replacement的值保存到数组中呢?

其实内部操作是这么一个流程:将$replacement值保存到数组—>读取模板文件(index.dwt,由display函数完成)—>将数组中的值匹配给模板文件中的变量(由con_replace()函数完成)—>将替换后的模板文件写入到编译文件中(com_index.dwt.php)—>输出编译后的PHP文件

  • dispaly($page)函数接收一个参数,即要输出的模板文件(index.dwt)
    • 首先,将模板文件的路径赋给$tplFile($tplFile=$this->template_dir.”/”.$page)
    • 判断模板文件是否存在,如果不存在,就没必要加载了,直接return
    • 指定一个编译文件,以便存放替换变量后的模板文件
    • 通过函数file_get_contents()读取模板文件,并通过函数conf_replace()替换掉模板中的smarty标签
    • 将替换变量后的模板文件通过file_put_contents()写入到编译文件中
    • 将编译后的文件include进来,即可输出编译后的文件
  • 函数con_replace($content)用于替换模板文件(index.dwt)中的变量,即将php中的变量值赋给模板中的变量
    • 通过一个可以匹配<{$title}>形式的正则表达式匹配模板文件中的内容,并将匹配到的值替换为<?php echo $title?>的形式
    • 匹配到内容,并将替换后的内容返回
复制代码
/*Smarty.ini.php文件:用于完成初始化smarty的工作*/
<?php
    include "./libs/Smarty.class.php";
    $tpl=new Smarty();
    $tpl->template_dir="./Tpl";    
    $tpl->compile_dir="./Compile";
?>
复制代码

 

复制代码
<!--模板文件-->
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><{$title}></title>
</head>
<body>
<p>内容:<{$content}></p>
<p>作者:<{$auth}></p>
<p>网址:<{$website}></p>
</body>
</html>
复制代码

 

复制代码
/*index.php文件*/
<?php
    include "./Smarty.ini.php";
    $title="深入浅出之Smarty模板引擎工作机制";    
    $content="Smarty模板引擎工作机制流程图";
    $auth="MarcoFly";
    $website="www.MarcoFly.com";
    $tpl->assign("title",$title);
    $tpl->assign("content",$content);    
    $tpl->assign("auth",$auth);
    $tpl->assign("website",$website);
    $tpl->display("index.dwt");
?>
复制代码

该index.php就是PHP程序员编写的,可以从数据库中获取各种想要的数据,并保存到变量中,然后简单的调用assign()函数将数据保存到数组中,并通过display()函数将编译文件输出

注:此编译文件是php文件,通过服务器端执行,将结果输出的客户端的浏览器上

分析到这里,我们回过头来分析下在深入浅出之Smarty模板引擎工作机制(一)中给出的关于编译后的文件代码:

复制代码
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><?php echo $this->arr["title"] ?></title>
</head>
<body>
<p>内容:<?php echo $this->arr["content"] ?></p>
<p>作者:<?php echo $this->arr["auth"] ?></p>
<p>网址:<?php echo $this->arr["website"] ?></p>
</body>
</html>
复制代码

由于我们已经通过assign()函数,将要赋给模板标签中变量的值保存到了数组中了,即此时编译后的模板文件,可以直接输出该数组中的值了。

举个例子:

$title="深入浅出之Smarty模板引擎工作机制";
$tpl->assign("title",$title);
当执行了以上两句代码后,在数组$arr中就存放着下标为:title,值为:深入浅出之Smarty模板引擎工作机制的关联数组了。
此时,就可以直接通过$this->arr['title']直接输出该数组的值。
至于对如何从<{$title}>  ---> <?php echo $this->arr['title']?> 的转换,不懂的读者可以再仔细看下con_replace()函数

有了以上几个文件之后,我们在浏览器中访问index.php文件将得到以下结果:

 

到此,我们“开发”了一个自己的模板引擎,并且测试成功,当然,这只是供交流学习之用。如果你觉得这篇文章对你了解smarty模板引擎的工作机制有所帮助的话,请帮忙顶一顶哈O(∩_∩)O~

文章出自:WEB开发_小飞

转载请注明出处:http://www.cnblogs.com/hongfei/archive/2011/12/10/Smarty-two.html

深入浅出之Smarty模板引擎工作机制(一) - 曾是土木人 - 博客园

mikel阅读(1013)

来源: 深入浅出之Smarty模板引擎工作机制(一) – 曾是土木人 – 博客园

深入浅出Smarty模板引擎工作机制,我们将对比使用smarty模板引擎和没使用smarty模板引擎的两种开发方式的区别,并动手开发一个自己的模板引擎,以便加深对smarty模板引擎工作机制的理解。

在没有使用Smarty模板引擎的情况下,我们都是将PHP程序和网页模板合在一起编辑的,好比下面的源代码:

复制代码
<?php
$title="深处浅出之Smarty模板引擎工作机制";
$content="Smarty模板引擎原理流程图";
$auth="MarcoFly";
$website="www.MarcoFly.com";
?>
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><?php echo $title?></title>
</head>
<body>
<p>内容:<?php echo $content?></p>
<p>作者:<?php echo $auth?></p>
<p>网址:<?php echo $website?></p>
</body>
</html>
复制代码

输出到浏览器的结果截图:

查看HTML源代码:

复制代码
<!DOCTYPE HTML>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>深处浅出之Smarty模板引擎工作机制</title>

</head>

<body>

<p>内容:Smarty模板引擎原理流程图</p>

<p>作者:MarcoFly</p>

<p>网址:www.MarcoFly.com</p>

</body>

</html>
复制代码

程序比较小的情况下这种开发方式尚且不方便,一旦要开发一个大的WEB项目,就必须得使用到模板引擎。

使用模板引擎的情况下:
我们的开发方式将有所改变,美工人员只管做模板,后台开发人员专心写自己的程序。
一个web项目就可以分为模板文件PHP程序
比如:
美工人员就可以这样编辑网页模板文件:
index.dwt源代码

复制代码
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><{$title}></title>
</head>
<body>
<p>内容:<{$content}></p>
<p>作者:<{$auth}></p>
<p>网址:<{$website}></p>
</body>
</html>
复制代码
而后台WEB开发人员可以专注于PHP代码的书写:
index.php
复制代码
<?php
    include "./Smarty.ini.php";
    $title="深处浅出之Smarty模板引擎工作机制";
    $content="Smarty模板引擎工作机制流程图";
    $auth="MarcoFly";
    $website="www.MarcoFly.com";
    $tpl->assign("title",$title);
    $tpl->assign("content",$content);    
    $tpl->assign("auth",$auth);
    $tpl->assign("website",$website);
    $tpl->display("index.dwt");
?>
复制代码

从以上两段简单的演示代码可以看出,前台模板文件没有涉及到任何关于PHP的代码,只有几个看似陌生的标签<{$title}><{$content}>,而后台的php程序代码也没有涉及到前台的HMTL代码
      参考下图对比这两种开发方式的区别



通过对比,我们得出结论:在使用模板引擎后,原先需要使用PHP编写的地方,现在只需要用模板引擎提供标签的形式来代替了。
注:Smarty模板引擎默认的标签形式是{$xxx},如,{$title},{$content}
当然我们可以初始化为自己想要的标签形式,如我将其初始化为:<{$xxx}>的形式),如,<{$title}>、<{$content}>

不知各位看官有木有觉得奇怪,<{$title}>、<{$content}>根本就不是PHP的语法形式,那最终又是如何被输出到客户的浏览器中的,是否另有玄机?带着这个疑问,我们继续深究......
  其实,这一切的一切都是由Smarty模板引擎这双神秘的手在“暗中操作”着,经过Smarty模板引擎的“暗中操作”之后,起初的模板文件(index.dwt)经过Smarty“成功手术”之后,被改造为能在服务器端执行的PHP代码文件。
想看看模板文件(index.dwt)和后台的PHP程序(index.php)经过“手术”(即编译)之后的庐山真面目吗?
在此贴上经过模板引擎编译之后的编译文件的源代码:
复制代码
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><?php echo $this->arr["title"] ?></title>
</head>
<body>
<p>内容:<?php echo $this->arr["content"] ?></p>
<p>作者:<?php echo $this->arr["auth"] ?></p>
<p>网址:<?php echo $this->arr["website"] ?></p>
</body>
</html>
复制代码
看到这里,各位看官是否恍然大悟,原来Smarty模板引擎的工作就是:将前台美工人员编写的模板文件(index.dwt)和后台开发人员编写的PHP程序(index.php)整合在一起,经过编译这一步骤之后,原先的模板标签被替换成了php代码。
为了方便大家理解,我简单的做了一张代码流程图:

如果你觉得很神秘,想更深入了解Smarty模板引擎是如何完成这一步骤的,可以看看深入浅出之Smarty模板引擎工作机制(二)

文章出自:WEB开发_小飞

转载请注明出处:http://www.cnblogs.com/hongfei/archive/2011/12/10/Smarty-one.html

知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案二 - 初久的私房菜 - 博客园

mikel阅读(746)

来源: 知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案二 – 初久的私房菜 – 博客园

教程

01 | 模块化方案一

02 | 模块化方案二

其他教程预览

分库分表项目实战教程

Git地址: https://github.com/MrChuJiu/EasyLogger

01 | 前言

02 | 简单的分库分表设计

03 | 控制反转搭配简单业务

04 | 强化设计方案

05 | 完善业务自动创建数据库

06 | 最终篇-通过AOP自动连接数据库-完成日志业务

简介

开讲第二篇,本篇代码并非Copy的ABP,只是参考ABP的功能,进行的实现方案,让代码更加通俗易懂。代码的讲解思路和上一篇一样,但是不引用上篇的写法。

开始

第一步 基本操作

还是老样子,我们新建一个模块化接口类
新建接口 IAppModule (ps:项目中起的类名和方法名尽量对标ABP)

   /// <summary>
    /// 应用模块接口定义
    /// </summary>
    public interface IAppModule
    {
        /// <summary>
        /// 配置服务前
        /// </summary>
        /// <param name="context"></param>
        void OnPreConfigureServices();
        /// <summary>
        /// 配置服务
        /// </summary>
        /// <param name="context">配置上下文</param>
        void OnConfigureServices();
        /// <summary>
        /// 配置服务后
        /// </summary>
        /// <param name="context"></param>
        void OnPostConfigureServices();
        /// <summary>
        /// 应用启动前
        /// </summary>
        /// <param name="context"></param>
        void OnPreApplicationInitialization();
        /// <summary>
        /// 应用启动
        /// </summary>
        /// <param name="context"></param>
        void OnApplicationInitialization();
        /// <summary>
        /// 应用启动后
        /// </summary>
        /// <param name="context"></param>
        void OnPostApplicationInitialization();
        /// <summary>
        /// 应用停止
        /// </summary>
        /// <param name="context"></param>
        void OnApplicationShutdown();
    }

新建类 AppModule 继承 IAppModule

   public abstract class AppModule : IAppModule
    {
        public virtual void OnPreConfigureServices()
        {

        }

        public virtual void OnConfigureServices()
        {

        }

        public virtual void OnPostConfigureServices()
        {

        }

        public virtual void OnPreApplicationInitialization()
        {

        }

        public virtual void OnApplicationInitialization()
        {

        }

        public virtual void OnPostApplicationInitialization()
        {

        }
        public virtual void OnApplicationShutdown()
        {

        }
    }

第二步 预准备

这一步来完成ABP的DependsOnAttribute,通过特性进行引入模块,
这里参数 params Type[] 因为一个模块会依赖多个模块
新建类 DependsOnAttribute 继承 Attribute

/// <summary>
    /// 模块依赖的模块
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class DependsOnAttribute : Attribute
    {
        /// <summary>
        /// 依赖的模块类型
        /// </summary>
        public Type[] DependModuleTypes { get; private set; }

        public DependsOnAttribute(params Type[] dependModuleTypes)
        {
            DependModuleTypes = dependModuleTypes ?? new Type[0];
        }
    }

既然一个模块会包含多个模块的引用,那么就应该有一个存储的方式
新建类 ModuleDescriptor 该类来存储 自身和引用的其他模块

    /// <summary>
    /// 模块描述
    /// </summary>
    public class ModuleDescriptor
    {
        private object _instance;

        /// <summary>
        /// 模块类型
        /// </summary>
        public Type ModuleType { get; private set; }

        /// <summary>
        /// 依赖项
        /// </summary>
        public ModuleDescriptor[] Dependencies { get; private set; }

        /// <summary>
        /// 实例
        /// </summary>
        public object Instance
        {
            get
            {
                if (this._instance == null)
                {
                    this._instance = Activator.CreateInstance(this.ModuleType);
                }
                return this._instance;
            }
        }

        public ModuleDescriptor(Type moduleType, params ModuleDescriptor[] dependencies)
        {
            this.ModuleType = moduleType;
            // 如果模块依赖 为空给一个空数组
            this.Dependencies = dependencies ?? new ModuleDescriptor[0];
        }
    }

第三步 模块管理器

来到核心步骤,这里我们写模块管理器,白话就是存储模块和模块操作方法的一个类(同上一篇的StartupModulesOptions)
第一步肯定是模块的启动
我们新建 IModuleManager接口

 public interface IModuleManager : IDisposable
    {
        /// <summary>
        /// 启动模块
        /// </summary>
        /// <typeparam name="TModule"></typeparam>
        void StartModule<TModule>(IServiceCollection services)
            where TModule : IAppModule;
    }

紧跟新建类 ModuleManager 继承 IModuleManager, StartModule 先放在一边
这里的思路是:模块是从一个入口的根模块开始的慢慢的形成一个树状的引用关系,我们首先需要拿到所有的模块引用,并把他们从树叶为起点排列起来,依次注入。
(理解为A=>B=>C 那么注入的顺序应该是 C=>B=>A)

1.先来实现根绝入口递归获取所有的引用关系 我已经在方法中将每一步的注释都写上了

/// <summary>
        /// 获取模块依赖树
        /// </summary>
        /// <param name="moduleType"></param>
        /// <returns></returns>
        protected virtual List<ModuleDescriptor> VisitModule(Type moduleType) {

            var moduleDescriptors = new List<ModuleDescriptor>();
            // 是否必须被重写|是否是接口|是否为泛型类型|是否是一个类或委托
            if (moduleType.IsAbstract || moduleType.IsInterface || moduleType.IsGenericType || !moduleType.IsClass) {
                return moduleDescriptors;
            }

            // 过滤没有实现IRModule接口的类
            var baseInterfaceType = moduleType.GetInterface(_moduleInterfaceTypeFullName, false);
            if (baseInterfaceType == null)
            {
                return moduleDescriptors;
            }

            // 得到当前模块依赖了那些模块
            var dependModulesAttribute = moduleType.GetCustomAttribute<DependsOnAttribute>();
            // 依赖属性为空
            if (dependModulesAttribute == null)
            {
                moduleDescriptors.Add(new ModuleDescriptor(moduleType));
            }
            else {
                // 依赖属性不为空,递归获取依赖
                var dependModuleDescriptors = new List<ModuleDescriptor>();
                foreach (var dependModuleType in dependModulesAttribute.DependModuleTypes)
                {
                    dependModuleDescriptors.AddRange(
                        VisitModule(dependModuleType)
                    );
                }
                // 创建模块描述信息,内容为模块类型和依赖类型
                moduleDescriptors.Add(new ModuleDescriptor(moduleType, dependModuleDescriptors.ToArray()));
            }

            return moduleDescriptors;
        }
补: _moduleInterfaceTypeFullName 定义
        /// <summary>
        /// 模块接口类型全名称
        /// </summary>
        public static string _moduleInterfaceTypeFullName = typeof(IAppModule).FullName;
2.拿到依赖关系通过拓扑排序进行顺序处理 (ps:拓扑排序地址 https://www.cnblogs.com/myzony/p/9201768.html)

新建类 Topological 这块没啥特别要讲的根据链接去看下就好了

    /// <summary>
    /// 拓扑排序工具类
    /// </summary>
    public static class Topological
    {
        public static List<T> Sort<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies) {

            var sorted = new List<T>();
            var visited = new Dictionary<T, bool>();

            foreach (var item in source)
            {
                Visit(item, getDependencies, sorted, visited);
            }

            return sorted;
        }

        static void Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted, Dictionary<T, bool> visited)
        {
            bool inProcess;
            var alreadyVisited = visited.TryGetValue(item, out inProcess);

            // 如果已经访问该顶点,则直接返回
            if (alreadyVisited)
            {
                // 如果处理的为当前节点,则说明存在循环引用
                if (inProcess)
                {
                    throw new ArgumentException("模块出现循环依赖.");
                }
            }
            else
            {
                // 正在处理当前顶点
                visited[item] = true;

                // 获得所有依赖项
                var dependencies = getDependencies(item);
                // 如果依赖项集合不为空,遍历访问其依赖节点
                if (dependencies != null)
                {
                    foreach (var dependency in dependencies)
                    {
                        // 递归遍历访问
                        Visit(dependency, getDependencies, sorted, visited);
                    }
                }

                // 处理完成置为 false
                visited[item] = false;
                sorted.Add(item);
            }
        }

    }

回到 ModuleManager 新建方法 ModuleSort

 /// <summary>
        /// 模块排序
        /// </summary>
        /// <typeparam name="TModule"></typeparam>
        /// <returns></returns>
        public virtual List<ModuleDescriptor> ModuleSort<TModule>() where TModule : IAppModule
        {
            // 得到模块树依赖
            var moduleDescriptors = VisitModule(typeof(TModule));
            // 因为现在得到的数据是从树根开始到树叶 - 实际的注入顺序应该是从树叶开始 所以这里需要对模块进行排序
            return Topological.Sort(moduleDescriptors, o => o.Dependencies);
        }
补:ModuleSort本来是个私有方法 后为了让模块使用者可以实现重写,请在 IModuleManager 加入
        /// <summary>
        /// 模块排序
        /// </summary>
        /// <typeparam name="TModule">启动模块类型</typeparam>
        /// <returns>排序结果</returns>
        List<ModuleDescriptor> ModuleSort<TModule>()
            where TModule : IAppModule;

3.模块已经可以通过方法拿到了就来实现 StartModule 方法 筛选去重 依次进行注入, 并最终保存到全局对象中

        /// <summary>
        /// 模块明细和实例
        /// </summary>
        public virtual IReadOnlyList<ModuleDescriptor> ModuleDescriptors { get; protected set; }

        /// <summary>
        /// 入口 StartModule 
        /// 我们通过传递泛型进来的 TModule 为起点
        /// 查找他的依赖树
        /// </summary>
        /// <typeparam name="TModule"></typeparam>
        /// <param name="services"></param>
        public void StartModule<TModule>(IServiceCollection services) where TModule : IAppModule
        {

            var moduleDescriptors = new List<ModuleDescriptor>();

            var moduleDescriptorList = this.ModuleSort<TModule>();
            // 去除重复的引用 进行注入
            foreach (var item in moduleDescriptorList)
            {
                if (moduleDescriptors.Any(o => o.ModuleType.FullName == item.ModuleType.FullName))
                {
                    continue;
                }
                moduleDescriptors.Add(item);
                services.AddSingleton(item.ModuleType, item.Instance);
            }
            ModuleDescriptors = moduleDescriptors.AsReadOnly();
        }
4.ModuleDescriptors既然存储着我们的所有模块,那么我们怎么执行模块的方法呢

入口通过调用下面的方法进行模块的方法调用

        /// <summary>
        /// 进行模块的  ConfigurationService 方法调用
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public IServiceCollection ConfigurationService(IServiceCollection services, IConfiguration configuration) {

            foreach (var module in ModuleDescriptors)
            {
                (module.Instance as IAppModule)?.OnPreConfigureServices();
            }

            foreach (var module in ModuleDescriptors)
            {
                (module.Instance as IAppModule)?.OnConfigureServices();
            }

            foreach (var module in ModuleDescriptors)
            {
                (module.Instance as IAppModule)?.OnPostConfigureServices();
            }

            return services;
        }
        /// <summary>
        /// 进行模块的  Configure 方法调用
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        public IServiceProvider ApplicationInitialization(IServiceProvider serviceProvider)
        {
            foreach (var module in ModuleDescriptors)
            {
                (module.Instance as IAppModule)?.OnPreApplicationInitialization();
            }

            foreach (var module in ModuleDescriptors)
            {
                (module.Instance as IAppModule)?.OnApplicationInitialization();
            }

            foreach (var module in ModuleDescriptors)
            {
                (module.Instance as IAppModule)?.OnPostApplicationInitialization();
            }

            return serviceProvider;
        }
        /// <summary>
        /// 模块销毁
        /// </summary>
        public void ApplicationShutdown()
        {
            // todo我觉得这里有点问题问 易大师
            //var modules = ModuleDescriptors.Reverse().ToList();

            foreach (var module in ModuleDescriptors)
            {
                (module.Instance as IAppModule)?.OnApplicationShutdown();
            }
        }

当然还漏了一个模块销毁,该方法在主模块被销毁的时候调用(ps: 我个人思路应该是从树叶开始进行,但是ABP对模块顺序进行了反转从根开始进行销毁,所以这里同上)

        /// <summary>
        /// 主模块销毁的时候 销毁子模块
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool state)
        {
            this.ApplicationShutdown();

        }

第四步 Extensions

模块管理器写完了,那么这个方法如何调用呢来写我们的 Extensions
新建 RivenModuleServiceCollectionExtensions 类,让其完成ConfigurationService方法的模块调用

 /// <summary>
    /// 模块服务扩展
    /// </summary>
    public static class RivenModuleServiceCollectionExtensions
    {
        /// <summary>
        /// 添加Riven模块服务
        /// </summary>
        /// <typeparam name="TModule"></typeparam>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static IServiceCollection AddRivenModule<TModule>(this IServiceCollection services, IConfiguration configuration)
            where TModule : IAppModule
        {
            var moduleManager = new ModuleManager();
            // 将模块都查询排序好
            moduleManager.StartModule<TModule>(services);
            // 调用模块 和 子模块的ConfigurationService方法
            moduleManager.ConfigurationService(services, configuration);
            // 注入全局的  IModuleManager
            services.TryAddSingleton<IModuleManager>(moduleManager);
            return services;
        }
    }

新建 RivenModuleIApplicationBuilderExtensions 类 ,让其完成Configuration方法的模块调用

 public static class RivenModuleIApplicationBuilderExtensions
    {
        /// <summary>
        /// 使用RivenModule
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        public static IServiceProvider UseRivenModule(this IServiceProvider serviceProvider)
        {
            var moduleManager = serviceProvider.GetService<IModuleManager>();

            return moduleManager.ApplicationInitialization(serviceProvider);
        }
    }

第五步 测试

新建一个测试项目,引入写好的模块化类库,在 ConfigureServices 中调用

services.AddRivenModule<MyAppStartupModule>(Configuration);

Configure 中调用

 app.ApplicationServices.UseRivenModule();

模块销毁演示(ps:这个是演示效果、实际是在项目停止的时候进行。)

 app.Map("/ApplicationShutdown", _ =>
            {
                _.Run((context) =>
                {
                    var moduleManager = app.ApplicationServices.GetService<IModuleManager>();
                    moduleManager.ApplicationShutdown();
                    return Task.FromResult(0);
                });
            });

补:

新建 MyAppStartupModule、TestModuleA、TestModuleB 继承AppModule。
MyAppStartupModule作为入口模块 引用 A => B 然后在模块方法中打印 Console.WriteLine 看效果

补充 给模块传递参数

新建 ApplicationInitializationContext 类

public class ApplicationInitializationContext
    {
        public IServiceProvider ServiceProvider { get; }

        public IConfiguration Configuration { get; }

        public ApplicationInitializationContext([NotNull] IServiceProvider serviceProvider, [NotNull] IConfiguration configuration)
        {
            ServiceProvider = serviceProvider;
            Configuration = configuration;
        }
    }

新建 ApplicationShutdownContext 类

 public class ApplicationShutdownContext
    {
        public IServiceProvider ServiceProvider { get; }

        public ApplicationShutdownContext([NotNull] IServiceProvider serviceProvider)
        {
            ServiceProvider = serviceProvider;
        }
    }

新建 ServiceConfigurationContext 类

 public class ServiceConfigurationContext
    {
        public IServiceCollection Services { get; protected set; }

        public IConfiguration Configuration { get; protected set; }


        public ServiceConfigurationContext(IServiceCollection services, IConfiguration configuration)
        {
            Services = services;
            Configuration = configuration;
        }

    }

修改 IAppModule 接口, 模块和实现都自己手动都同步一下

    /// <summary>
    /// 应用模块接口定义
    /// </summary>
    public interface IAppModule
    {
        /// <summary>
        /// 配置服务前
        /// </summary>
        /// <param name="context"></param>
        void OnPreConfigureServices(ServiceConfigurationContext context);

        /// <summary>
        /// 配置服务
        /// </summary>
        /// <param name="context">配置上下文</param>
        void OnConfigureServices(ServiceConfigurationContext context);

        /// <summary>
        /// 配置服务后
        /// </summary>
        /// <param name="context"></param>
        void OnPostConfigureServices(ServiceConfigurationContext context);

        /// <summary>
        /// 应用启动前
        /// </summary>
        /// <param name="context"></param>
        void OnPreApplicationInitialization(ApplicationInitializationContext context);

        /// <summary>
        /// 应用启动
        /// </summary>
        /// <param name="context"></param>
        void OnApplicationInitialization(ApplicationInitializationContext context);

        /// <summary>
        /// 应用启动后
        /// </summary>
        /// <param name="context"></param>
        void OnPostApplicationInitialization(ApplicationInitializationContext context);

        /// <summary>
        /// 应用停止
        /// </summary>
        /// <param name="context"></param>
        void OnApplicationShutdown(ApplicationShutdownContext context);
    }

修改 ModuleManager的 ConfigurationService、ApplicationInitialization、ApplicationShutdown 方法给调用传递对应参数
这部分代码我就不贴了,会的大佬都能自己写,想看的去我的github直接下载源码看吧,麻烦老板们给点个星星!!!

项目地址

知识全聚集,逐个击破: https://github.com/MrChuJiu/Easy.Core.Flow

鸣谢

玩双截棍的熊猫

源地址:https://github.com/rivenfx/Modular