
软件开发从业8年,从一台服务器打天下到几千台服务器共同服务,一路采坑,一路成长
前言 随着系统越来越大,开发人员、站点、服务器越来越多,微服务化推进,......等等原因,实现自动化的devops越来越有必要。 当然,真实的原因是,在团队组建之初就预见到了这些问题,所以从一开始就决定这一块要自动化。 带来的实质好处也是显而易见的,人力成本的节省、规范化的流程、可追溯的发布历史、解脱双手(重复性劳动)、避免人为操作产生的错误等等。 感概一下 1.目前市面上的成套的产品要么贵的要死,要么不支持本地部署,要么就还是一个demo级别的东西,最重要的还是每个公司或者产品都有自己的一些特殊环境或者业务再里面。不一定都适合。反正是比较难找到好用的,而且是成套的产品来。期待一个devops界的SAP,而且还要便宜! 2.几个老大哥产品还是做得很牛逼!比如jira,confluence,jenkins,sonar。官方文档非常完善,网上教程多。接口完备。不像某些产品,看上去高大上,一用起来就是各种坑 3.懂开发的运维觉逼是牛逼的程序员!(^_^) 4.人真的非常重要,不然流程什么的,呵呵,都是个屁...... 大概的样子 当然,目前这套工具还有很多不完善的地方,随着不停的使用,或者变化的需求来进一步变化。 gitlab 开源的git仓库,主要有几个用途 1.源代码管理 分支管理规则可以参考gitflow,或者规定一个合适自己的就好,微服务化后,一个站点或者说一个项目参与的开发人员只有有限的几个人。用了简单的方法,master作为发布用分支,每次迭代开发使用新分支,上线前合并到master;线上简单的fix则直接在master分支上提交。 2.配置文件管理 放在gitlab上,主要是为了方便管理,以及追溯修改历史。当然,我们有自己的配置中心,能走配置中心的都尽量走配置中心,只有必须是文件配置管理的才放在gitlab上。 3.发布脚本管理 jenkins需要使用到的发布脚本。根据环境、源代码语言、部署方式等有所不同 jira jira敏捷开发管理工具,管理需求、研发迭代等。在加上他们家公司的wiki做知识库管理基本稳了。 jira用下来发现还是相当强大!各种自定义可配置。页面、字段、流程等等全可配置。有http open api可以直接调用修改信息、触发流程等 使用的发布流程也比较简单。开发创建发布任务,然后提交给测试,测试在jira上操作发布到测试环境,准线上环境,线上环境进行测试等。准线上环境测试完后要发布到线上需要让具有leader权限的人进行一次审核,一方面是让leader知道有什么东西上线了,另一方面也是安全控制的一些原因(比如说节假日前夕最好是不在做更新等,要做更新就得报备,不然出问题节假日就得嗝屁)。 截图是比较历史的版本了,最近在jira里面找了一个进度条插件,然后把构建发布的实时进度直接反馈到jira的页面上。这样就不用再打开发布系统查看发布进度了,进一步提高使用体验。 发布流程工作流,根据自身的情况设计的 发布系统 这一块,是我们自己开发的一个简单系统。主要作用是衔接各个开源工具的使用。作为一个粘合剂系统使之分散的各个子工具能链接为一个整体。 虽然,jira里面有jenkins插件,jenkins里面有jira的插件,但是组件对各个系统都有版本要求,然后组件使用上也蛮不方便的,最后也有一些需求要解决起来相当麻烦,所以才有了自己的发布系统。 功能还是比较简单,一个前端小伙弄弄页面什么的1个礼拜就完事了,关键还是把当下的各种新技术都秀了一波,虽然页面挺丑的。几个系统的接口调用自己研究写一写也就几天就完事了。 主要完成的几个功能 1.发布配置管理 站点或者系统的开发语言,部署的目标系统,要部署那些主机,是不是docker容器方式,docker要部署几个示例,部署方式并发、串行发布,要走那一个nginx,绑定的域名,绑定的端口等等信息。 在新的系统或者站点发布的时候由运维和研发协调填写,后期则由运维来维护,比如扩缩容等 2.接收jira的发布任务操作通知,并通知到某一个Jenkins去执行,sonar进行静态代码检查等 3.接收jenkins构建部署反馈过来的进度 4.展示构建部署进度 5.一部分CMDB系统的功能,主机管理(ip,名称,用户名,密码)之类的。方便。 至于为什么不用世面上已有的CMDB系统,也实属无赖,要么要钱,要么好麻烦、要么没接口。索行自己简单做一个。能满足功能即可。 因为涉及到主机的账号密码之类的,所以密码都是公钥加密存储在系统上。 而密码的使用方有2个,一个是jenkins在部署的时候新机器在创建SSH免密登录的时候要用一次,还有就是远程管理工具要用,所以对密码的使用单独写了个小组件用私钥解密获得密码,然后把发布系统和小组件单独管理 jenkins jenkins绝对可以说是这套工具里面的大佬了,可以说一切都是围着他在转。 接收发布系统发过来的构建请求,拉取代码,编译,拉取配置文件,打包成部署包,上传ftp,发布到私有docker仓库,部署等等。 还要区分系统环境,开发语言(windows、linux、nodejs、.net core)单独处理等。 1.参数化构建过程。比如要构建的分支名称之类的 2.源代码配置。git源代码地址,gitlab固定的代码只读账号,通过SSH进行代码的拉取。 3.调用构建脚本。jenkins内的执行命令大约如下面所示 #!/bin/bash -l cd /opt/deployscript # 进入构建脚本目录 git pull #拉取最新的构建脚本 #调用构建脚本 #workspace,build_number,jobname,project_name,git_commit,git_branch,env,jira_id,userid sh /opt/deployscript/${JOB_NAME}/${env}/deploy.sh "${WORKSPACE}" "${BUILD_NUMBER}" "${JOB_NAME}" "${PROJECT_NAME}" "${GIT_COMMIT}" "${selected_branch}" "${env}" "${JIRA_ISSUE_ID}" "${BUILD_USER_ID}" jenkins的构建脚本 重中之重了,所有的驱动都在这个脚本里面了。分环境、分开发语言单独编写的构建或部署脚本。 为什么每一个站点都有一个脚本的原因则是总有那么一些站点是那么的特殊和优秀,当然觉得多数系统都可以走一个公共的构建脚本。 脚本有不少要调用其他系统接口的,我则直接用.net core 写了一个控制台应用,专门负责这个事情,毕竟写shell不是专业的。 具体的构建脚本就不贴上来了。 脚本执行步骤(net core 测试环境脚本):在每一个部署完成或者出错的时候都把进度反馈到发布系统上。 1.源代码在jenkins配置里面已经帮忙拉取好了。所以脚本不用拉代码了。 2.编译。比如dotnet publish -c Release -r linux-x64 -o “输出路径” 3.编译输出内容打包 4.上传到ftp。 5.拉取配置文件。 6.将输入内容和配置文件,等打成压缩包 6.拉取部署配置。要部署到那些机器,部署要并发还是要串行等 7.检查机器是否已经完成SSH免密配置了,没有配置则拉取密码配置好。 8.并行或者串行进行发布操作 9.SSH到目标机器,上传压缩包,部署脚本 10.执行部署脚本(解压,停掉原来的服务,启动新的服务,检查是否启动成功等) sonar静态代码检查 在发布系统中接收到jira的发布请求后,拉取站点的配置,如果是需要进行sonar检查则把请求发送给sonar的jenkins。 目前我们配置的是发布到产线的时候才做sonar的静态代码检查,然后再sonar系统里面配置了。 后面看需要,是否要对sonar的结果进行邮件。打算这样做。每周出一份代码质量报告,统计一周内已上线的项目和上一周相比错误,漏洞,坏味道,覆盖了等数据的变化。弄个定时任务,sonar 2个接口获取一下数据,存储对比结果,发个邮件就完事了。 简单总结一下 文章随便写写,很多东西交代的不清楚,还有很多东西压根就没有说。比如说堡垒机集成,日志、host监控集成等等等。我不会说实在是我太懒了,打字好累啊! 总之,欢迎交流!!虽然实现的不完整,但是还是适合目前自身的需求的。合适的才是最好的嘛 感谢开源界大佬的贡献,虽然我还没钱捐款。让社区有那么多那么多好用的产品。 感谢前人已经种好的大树,很凉快! 整套工具搭建完成,如果真的算时间估计也就不到一个月,当然真实情况是零零散散的,东戳戳,西戳戳。好在做这个事情之前有一个简单的规划,没有走弯路,虽然再找国产产品的路上耗费了一些时间 从开始使用开始,3个月不到就发了不下2000次,这还是在刚起步阶段。可想而知,确实是生产力工具
文章目录 前情概要 前面一大坨一大坨的代码把route、controller、action、attribute都搞完事儿了,最后剩下一部分功能就是串起来的调用。 那接下就说个说第二个中间件,也是最后一个中间件RequestHandler RequestHandler 中间件的注册 app.use一下就完事啦。在RouteHandler把路由处理好之后,接着就是RequestHandler真正的来调用我们的处理函数啦,也就是我们的action。 import { RequestHandler, RouteHandler } from 'gd-express-basic' //第二个中间件,拦截所有请求对路由做自动映射 RouteHandler(_app, controllers); //第三个中间件,处理请求 _app.use(RequestHandler); RequestHandler 请求处理中间件代码 从当前请求拿到对应的action描述对象,如果没有就继续往后面的中间件走,比如走到404。 new一个新的controller对象,并把req,res对象传入。 完成参数的自动解析 调用action,得到返回结果 判断返回结果是否view类型,如果是view类型则调用render来渲染页面,如果不是则返回该对象 判断需要返回的对象是否是jsoncallback调用方式,是的话就适配一下 7.完事儿 /** * 请求处理中间件 * * @export * @param {core.Request} req * @param {core.Response} res * @param {(core.NextFunction | undefined)} next */ export function RequestHandler(req: core.Request, res: core.Response, next: core.NextFunction | undefined) { //1. 从当前请求拿到对应的action描述对象,如果没有就继续往后面的中间件走,比如走到404。 var desc: ActionDescriptor = res.locals.actionDescriptor if (!desc) { return next && next(); } var cname = desc.ControllerName; new Promise((reslove, reject) => { var cType = desc.ControllerType;//*controller class对象 //2. new一个新的controller对象,并把req,res对象传入。 var c = new cType(req, res);//new 一个controller 对象出来 //3. 完成参数的自动解析 var agrs = bindActionParameter(desc.ControllerType, desc.ControllerTypeName, desc.ActionType, desc.ActionName, req) //4. 调用action,得到返回结果 var actionResult = desc.ActionType.apply(c, agrs) return reslove(actionResult) }).then(actionResult => { if (actionResult instanceof ViewResult) { //5. 判断返回结果是否view类型,如果是view类型则调用render来渲染页面,如果不是则返回该对象 Promise.resolve(actionResult.data).then(ViewActionResultData => { var findViewNamePath = actionResult.name[0] === '/' ? actionResult.name.substr(1) : (cname + '/' + actionResult.name) res.render(findViewNamePath, ViewActionResultData, (err, html) => { if (err) { next && next(err); } else { res.send(html); res.end(); } }); }).catch(function (viewDataError) { next && next(viewDataError); }); } else if (typeof actionResult !== 'undefined') { //process object send response json //6. 判断需要返回的对象是否是jsoncallback调用方式,是的话就适配一下 let resultData = req.query['callback'] ? req.query['callback'] + '(' + JSON.stringify(actionResult) + ')' : actionResult; res.send(resultData); res.end() } else { //process not response or origin response.render or response.send. process.nextTick((_res: any) => { if (!_res.finished) { _res.end(); } }, res) } }).catch(processRequestError => { next && next(processRequestError); }) } 经过RouteHandler、RequestHandler两个方法的串联调用,就把我们整个零散的功能就完整统一的进行了一次调用。从controller的发现、注册,action的发现、注册,action参数配置,route解析、匹配,action调用,处理结果适配输出。 在编码调试过程中,发现目前dotnet core mvc的中间件的某些思想和实现方式和express的中间件基本一致。果然,思想都是相同的,哈哈哈。
文章目录 前情概要 路由、action的扫描、发现、注册搞定之后,后来我发现在我们的action里面获取参数往往都是通过request对象来一个一个获取。同样的一行代码我们不厌其烦的重复写了无数次。遂想着那我们能不能像后端程序一样做得更自动化一些呢? 所以,接下来我们再来完成一个比较重要的功能,那就是参数的自动绑定。 参数的自动绑定实现思路 依靠ts的装饰器特性,我们能做在方法上,在类上,在方法的参数上,在类的属性成员上通通可以加上装饰器来存放一些额外的数据。那理论上我们在编码阶段就可以通过一定的手段把这个标记加载我们需要处理的方法、类、参数等上面,等到运行时的时候可以根据这些额外的参数来帮我们做一些重复性的工作。 在需要使用到的方法参数、类、属性上增加我们的特定标识,标记当前参数需要自动解析,并记录一些诸如类型拉、名称啦等的一些额外属性。 在action的调用阶段,根据规则先把参数解析好。在传递进去。 完事儿,这就是我们的参数自动绑定功能。 参数的自动绑定实现---装饰器实现 部分代码,只贴了fromquery,其他几个formbody,fromheader之类的基本一样,都是调用makeActionParameterDescriptor方法 /** * 指示当前参数从request对象的query中解析 * * @export * @param {(target?: any) => Function} type * @returns {Function} */ export function fromQuery(type: (target?: any) => Function): Function; /** * 指示当前参数从request对象的query中解析 * * @export * @returns {Function} */ export function fromQuery(): Function { var thatArg = arguments; return function (target: Object, propertyKey: string, parameterIndex: number) { makeActionParameterDescriptor('query', thatArg, target, propertyKey, parameterIndex); } } function makeActionParameterDescriptor(parameterFromType: parameterFromType, thatArg: IArguments, target: Object, propertyKey: string, parameterIndex: number) { //非声明在属性和参数上 if (!propertyKey) return; var paramType = undefined; var val = new ActionParamDescriptor(); val.parameterName = propertyKey; val.target = target; val.parameterIndex = parameterIndex; val.parameterFromType = parameterFromType; val.parameterTypeType = 'simple' if (typeof parameterIndex === 'undefined') { //声明在类的属性上 val.localtionType = 'classProperty' } else { //声明在action的参数上 val.localtionType = 'methodParameter' val.actionMethodName = propertyKey; val.parameterName = getArgs((target as any)[propertyKey])[parameterIndex]; } //复杂类型 if (thatArg.length > 0) { val.parameterTypeType = 'complex' val.parameterType = thatArg[0](target); } SetActionParamDescriptor(val); } function getArgs(func: Object) { //匹配函数括号里的参数 var method = func.toString(); method = method.length > 500 ? method.substring(0, 500) : method; method = method.replace("\r|\n|\\s", "") var args = method.match(/.*?\(.*?\)/i); if (args == null) throw Error('can not match method parameters'); method = args[0]; method = method.replace(/.*?\(|\)/, "").replace(')', ''); //分解参数成数组 var arr = method.split(",").map(function (arg) { //去空格和内联注释 return arg.replace(/\/\*.*\*\//, "").trim(); }).filter(function (args) { //确保没有undefineds return args; }); return arr } ActionParamDescriptor 对象结构 export declare type parameterFromType = 'query' | 'body' | 'form' | 'header' | 'cookie' | 'auto' export class ActionParamDescriptor { /** * action参数的action名称 * * @type {string} * @memberof ActionParamDescriptor */ actionMethodName: string /** * 参数名称 * * @type {string} * @memberof ActionParamDescriptor */ parameterName: string /** * 参数所在类 * * @type {Object} * @memberof ActionParamDescriptor */ target: Object /** * 参数类型的类别 * * @type {('complex' | 'simple')} * @memberof ActionParamDescriptor */ parameterTypeType: 'complex' | 'simple' /** * 参数对象的类型(class)对象 * * @type {Function} * @memberof ActionParamDescriptor */ parameterType: Function /** * 参数所在参数类别的顺序 * * @type {(number | undefined)} * @memberof ActionParamDescriptor */ parameterIndex: number | undefined /** * 当前参数属性属于什么类型 * * @type {('classProperty'|'methodParameter')} * @memberof ActionParamDescriptor */ localtionType: 'classProperty' | 'methodParameter' /** * 标记参数应该从什么地方解析 * * @type {parameterFromType} * @memberof ActionParamDescriptor */ parameterFromType: parameterFromType } 参数的自动绑定实现---基本使用方法 可以在action上标记某一个参数从什么地方(query、form、body、cookie、header)进行解析, 也可以标记某个参数是一个复杂的查询参数,可以指定这个参数的类型。 当然复杂的查询class的每一个属性都可以指定解析来源,当然也必须使用装饰器来修饰一下,不然我们就没法知道有这个属性需要进行解析啦。 import { BaseController, post, fromQuery, fromBody, fromCookie, fromHeader, property } from "../src/index" export class demoActionBodyParams { id: string; name: string; pageSize: number; body: { req_bb: string } } export class demoActionQueryParams { @property() id: string; @property() name: string; @property() pageSize: number; @fromCookie() cookieName: string; @fromHeader() headerName: string; @fromBody() body: any; } export class demoController extends BaseController { @post() demoAction(@fromQuery(type => demoActionQueryParams) query: demoActionQueryParams, @fromQuery() p2: string, @fromBody() req_body: demoActionBodyParams) { return { query, p2, req_body } } } 参数的自动绑定实现---参数的说明元数据保存 reflect-metadata 目前来说也还是ts的一个实验性特性。可以用来辅助我们保存一些额外的数据。或者也可以理解成它是一个系统级别的静态字典。 那我们把对参数的一些特别设置都通过reflect-metadata保存下来,其实这里我们自己使用一个对象来保存也是可以的。 const request_params_auto_bind_MetadataKey = Symbol("request_params_auto_bind_MetadataKey"); export function SetActionParamDescriptor(val: ActionParamDescriptor) { (val as any).targetName = val.target.constructor.name if (val.parameterType) (val as any).parameterTypeName = val.parameterType.name console.log('SetActionParamDescriptor', JSON.stringify(val)); var arr: ActionParamDescriptor[] = []; if (val.localtionType === 'methodParameter') { arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, val.target, val.actionMethodName) || []; arr.push(val); Reflect.defineMetadata(request_params_auto_bind_MetadataKey, arr, val.target, val.actionMethodName); } else { arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, val.target) || []; arr.push(val); Reflect.defineMetadata(request_params_auto_bind_MetadataKey, arr, val.target); } } 参数的自动绑定实现---参数的自动解析和对象生成 嗯,大概是一些杂乱无章的代码(^_^)。 主要思路: 获得当前action的参数描述对象 根据参数描述对象中的配置来解析参数 就这么简单,完事儿 //开始参数的自动解析操作 var agrs = bindActionParameter(desc.ControllerType, desc.ControllerTypeName, desc.ActionType, desc.ActionName, req) function bindActionParameter(controllerType: Function, controllerName: string, actionType: Object, actionName: string, req: core.Request) { //获得当前action的所有参数描述对象 var arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, controllerType.prototype, actionName) || [] as ActionParamDescriptor[]; var args = [arr.length]; for (let index = 0; index < arr.length; index++) { args[arr[index].parameterIndex as number] = getParameterValue(req, arr[index], arr[index])//循环挨个进行解析 } return args; } function bindClassParameter(req: core.Request, target: any, methodParmeterdesc: ActionParamDescriptor): any { var arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, target.prototype) as ActionParamDescriptor[]; var obj = new target(); for (let index = 0; index < arr.length; index++) { var desc = arr[index]; obj[desc.parameterName] = getParameterValue(req, desc, methodParmeterdesc); } return obj; } function getParameterValue(req: core.Request, desc: ActionParamDescriptor, methodParmeterdesc: ActionParamDescriptor): any { //判断当前action的参数是基本类型参数,还是复杂类型参数。如果是复杂类型就走class绑定逻辑。 if (desc.parameterTypeType === 'simple' || (desc.localtionType === 'methodParameter' && desc.parameterFromType === 'body')) { return getparameterInRequest(desc.parameterFromType, desc.parameterName, req, methodParmeterdesc); } else if (desc.parameterTypeType === 'complex') { return bindClassParameter(req, desc.parameterType, methodParmeterdesc) } else throw Error('not support parameter type ' + desc.parameterTypeType) } //根据参数的不同配置进行不同解析。 function getparameterInRequest(fromType: parameterFromType, parameterName: string, req: core.Request, methodParmeterdesc: ActionParamDescriptor): any { switch (fromType) { case 'query': return getCompatibleParam(req.query, parameterName) case 'body': return req.body case 'header': return getCompatibleParam(req.headers, parameterName) case 'cookie': return getCompatibleParam(req.cookies, parameterName) case 'form': return getCompatibleParam(req.body, parameterName) case 'auto': return getparameterInRequest(methodParmeterdesc.parameterFromType, parameterName, req, methodParmeterdesc); } return undefined; } //忽略参数的大小写问题。 function getCompatibleParam(obj: any, propertyName: string) { var lower = propertyName.toLowerCase(); for (const key in obj) { if (obj.hasOwnProperty(key) && key.toLowerCase() == lower) { return obj[key]; } } } 需要说明的是,在这里有一个问题没有解决。当参数指定类型为body的时候,我们没有对参数进行更多的解析。也就意味着我申明的对象只有2个属性,提交的body有3个属性,最终在action里面的这个参数能拿到3个属性。一直犹豫是否要做这里是否要做filter。 从后端的角度来说是毫无疑问的,不可能我一个class只声明了2个属性,而到运行时的时候能取出来3个属性。这是不可能的。 但从前端的角度来讲,这也许是一个比较好的特性。某些时候更省事情。比较接口部分参数透传的时候之类的。 参数的自动解析大致就到这里了,嗯,这部分代码可能有点小逻辑。又加上没有注释有点难理解。不过我觉得这样挺好的,哈哈哈
文章目录 前情概要 上文中的RouteHandler中有一个重要方法GetActionDescriptor没有贴代码和说,接下来我们就说一说这个方法。 使用controllerName、actionName、httpmethod获得唯一匹配的处理函数描述对象 直接上代码,看代码注释即可 //action注册缓存对象 let _dic_override = new Map<string, Map<string, ActionDescriptor>>(); //最终路由到action映射关系的缓存对象 let _dic_buid_routes: Map<string, Map<string, ActionDescriptor>>; export function GetActionDescriptor(controllerName: string, actionName: string, method?: string): ActionDescriptor | undefined { //没有build过,则build一下。把路由到action的映射关系解析好 if (!_dic_buid_routes) build(); //获得controller描述对象 var c = _dic_buid_routes.get(controllerName) if (!c) return; //从controller描述对象中获得对应action,先根据请求类型_action名称来获取,获取不到的情况下则直接用action名称来获取。 var a = c.get(actionName + (method ? '_' + method.toLowerCase() : '')); if (!a) a = c.get(actionName) return a; } //对controller和action名称默认做小写处理。匹配的时候方便一点。url不区分大小写嘛。 //{"controllerName":{"post_addUser":{描述对象},"getuserinfo":{描述对象}}}。类似如此结构。 function build() { _dic_buid_routes = new Map<string, Map<string, ActionDescriptor>>(); _dic_override.forEach((v, k, m) => { if (v.size <= 0) return; var cname = ''; var aMap = new Map<string, ActionDescriptor>(); v.forEach((av, ak, am) => { cname = av.ControllerName; aMap.set(av.Id.toLowerCase(), av); }) _dic_buid_routes.set(cname.toLowerCase(), aMap) }) } GetActionDescriptor方法中先检查是否build过,没有则build完成之后,根据对应规则查找相应的描述对象并返回,交给后续的中间件使用。 为什么需要ActionDescriptor对象。 请求处理函数描述对象保存了当前处理函数的一些基本信息,比如controllername的名称,所在类的名称,所在类的原型。函数的名称,请求中函数的别,函数的原型,请求方法的约束等基本信息 export class ActionDescriptor { public ControllerType: any; public ControllerTypeName: string; public ControllerName: string; public ActionType: any; public ActionTypeName: string; public ActionName: string; public HttpMethod: string; public Id: string; public isAuth?: boolean; } 为什么要使用httpmethod_actionname 的格式规定id字段。 使用id字段可以方便直接hash查找缓存的处理函数方法 使用httpmethod的主要原因是我们需要支持针对单个处理函数指定它只接收某一种httpmethod方式的请求。 好比route.post('/path',(req,res,next)=>{});就只能处理post请求。
文章目录 前情概要 前边的文章把一些基本的前置任务都完成了。接下就是比较重要的处理函数action是如何自动发现和注册的拉,也就是入口函数RouteHandler(也是我们的第一个express中间件)里面的一些细节。 扫描action并添加到缓存 说一说我们的思路,其实和静态语言中的反射概念有点类似。 循环传进来的所有controller声明。详见 控制器的声明和定义篇---controller注册到RouteHandler 循环所有声明的controllers,并将每一个controller里面的action添加到action缓存中。 关键方法也就是Object.getOwnPropertyNames和Object.getOwnPropertyDescriptor2个方法了。目的则是对象上的所有成员,对应到比如说.net,java之类的就是反射拉。 export function RouteHandler(app: core.Express, controllers: any) { find(controllers) //app.use('/', (req, res, next) => 。。。。。。 } function find(controllers: any) { //controllers本质上是一个对象,类似:{host:{},home:{},site:{}}。那我们这里的key就是controller的名字,value就是controller实列了。 var _reg_controller_names = Object.getOwnPropertyNames(controllers)//对象上所有成员,就是我们所有的controller名称集合。 for (var index = 0; index < _reg_controller_names.length; index++) { var _reg_controller_name = _reg_controller_names[index];//controller的名称,比如:home var _reg_controller_Desc = Object.getOwnPropertyDescriptor(controllers, _reg_controller_name) as PropertyDescriptor//controller的描述对象 if (_reg_controller_name === '__esModule') continue; var cType = _reg_controller_Desc.value;//controller的类型,比如:Homecontroller var cName = cType.name;//controller的class名称。比如"HomeController"; var aNames = Object.getOwnPropertyNames(cType.prototype)//controller所有成员,也就是我们的action for (var index2 = 0; index2 < aNames.length; index2++) { var aName = aNames[index2]; if (aName === 'constructor') continue; var aType = (Object.getOwnPropertyDescriptor(cType.prototype, aName) as PropertyDescriptor).value//具体的每一个action函数 SetActionDescriptor(cName, aName, undefined, undefined, _reg_controller_name, cType, aType)//加入缓存 //第三个参数[httpMethod] 请求方法类型。默认给undefined,后续再通过扫描action上面的特性标签增加进来 //第四个参数 [actionName] 路由action名字。默认给undefined,后续再通过扫描action上面的特性标签增加进来 } } } SetActionDescriptor的实现 /** * * * @export * @param {string} controllerTypeName 控制器类型名字 * @param {string} actionTypeName 方法类型名字 * @param {string} [httpMethod] 请求方法类型 * @param {string} [actionName] 路由action名字 * @param {string} [controllerName] 路由控制器名字 * @param {*} [controllerType] 控制器对象 * @param {*} [actionType] action 对象 * @returns {ActionDescriptor} */ export function SetActionDescriptor(controllerTypeName: string, actionTypeName: string, httpMethod?: string, actionName?: string, controllerName?: string, controllerType?: any, actionType?: any, isAuth?: boolean): ActionDescriptor { var _actions = _dic_override.get(controllerTypeName) if (!_actions) { _actions = new Map<string, ActionDescriptor>(); _dic_override.set(controllerTypeName, _actions) } var _action = _actions.get(actionTypeName); if (!_action) { _action = new ActionDescriptor(); _actions.set(actionTypeName, _action) } _action.ControllerTypeName = controllerTypeName; _action.ActionTypeName = actionTypeName; if (!_action.ActionName) _action.ActionName = actionTypeName if (httpMethod) _action.HttpMethod = httpMethod.toUpperCase(); if (controllerType) _action.ControllerType = controllerType; if (controllerName) _action.ControllerName = controllerName; if (actionName) _action.ActionName = actionName; if (actionType) _action.ActionType = actionType if (isAuth === true || isAuth === false) _action.isAuth = isAuth; _action.Id = _action.ActionName + (_action.HttpMethod ? ('_' + _action.HttpMethod) : '') return _action } SetActionDescriptor方法参数有值得情况下则更新,没有值则跳过。因为针对同一个action可能会被调用多次。对一个action的描述信息也是分部分分多次set进来的。一部分是通过对象的原型,还有一部分则是ts的装饰器(后端语言的attribute)。 需要注意的是每个action有个id字段。id字段使用http method和action name 来拼接。
文章目录 前情概要 在使用express框架开发的时候,每加一个请求,都在增加一条route请求规则,类似于下面的代码,很烦有木有! app.use('/myroute path', (req, res, next) => { //dosomething }) 我们难道不能再智能一点点么,学习后端mvc框架一样,比如加个标记,或者默认规则直接自动映射嘛。约定胜于配置嘛! 我们的实现思路 拦截所有请求 根据我们的规则进行路由的匹配 调用匹配到的处理函数 拦截所有请求 这个太好办了,app.use('/') 搞定。参考下面的代码 import * as express from 'express' import * as controllers from './controller' import { RequestHandler, RouteHandler } from 'gd-express-basic' const _app = express(); //第一个express 中间件,处理一下跨域请求中的options请求。 _app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length,Authorization,Accept,X-Requested-With'); res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS'); if (req.method == 'OPTIONS') { res.send(new ResponseBase(200)); } next && next(); }); //第二个中间件,拦截所有请求对路由做自动映射 RouteHandler(_app, controllers); //第三个中间件,处理请求 _app.use(RequestHandler); //第N个中间件,处理一下error呀,404呀等其他情况。 根据规则进行路由的匹配 接下来看一看RouteHandler方法。主要干几个事情 缓存所有action,方便后续的调用。【 请求处理函数的特性注册篇【详细说明】 拦截所有请求,并根据规则解析到对应的action上面去。【app.use('/', (req, res, next)】 根据解析出来的controller 、action名称以及当前请求的method找到对应的action并记录到当前请求对象上,方便接下来的请求处理。 目前我们的规则很简单。url分2层,第一层为controller名称,第二层为action名称。即:/{controller}/{action}; /** * 路由选择处理中间件 * * @export * @param {core.Express} app * @param {*} controllers */ export function RouteHandler(app: core.Express, controllers: any) { //程序启动的时候,找到当前所有的controllers,并根据规则缓存好我们所有的处理函数(action),方便接下来的匹配 //请求处理函数发现篇【controller+action】具体讲到 find(controllers) //拦截所有请求,对请求 app.use('/', (req, res, next) => { //拿到route并解析出来controller和action的名称。 var pathArr = getRouteTokens(req.path) var controller = (pathArr[0] && pathArr[0].toLowerCase()) || 'home'; var action = (pathArr[1] && pathArr[1].toLowerCase()) || 'index' //根据参数找到能处理这个请求的action var desc = GetActionDescriptor(controller, action, req.method) if (!desc) { desc = GetActionDescriptor(controller, '_default', req.method) } if (desc && (!desc.HttpMethod || (desc.HttpMethod && desc.HttpMethod === req.method))) { res.locals.authInfo = { isAuth: desc.isAuth }; //如果请求能匹配到可以处理的action,则赋值 res.locals.actionDescriptor = desc; }else{//否则跳过。当然在这里也可以直接返回404,结束本次请求。 } next && next() }) } function getRouteTokens(path: string) { var pathArr = path.split('/'); var arr: string[] = []; pathArr.forEach(element => { if (element) arr.push(element) }); return arr } 代码那是相当的简单。其实只干了一件事情,据我们的url规则找到与之匹配的在项目启动的时候扫描缓存的请求处理函数 考虑到前端不太会有area的概念,所以暂时没有支持,如果要支持其实也很简单,增加一个area注册,然后再做路由匹配的时候多判断一次area就完事儿了。 估计也不太会有自定义route 的要求,比如dotnet mvc 里面的【[Route("/path")]】特性。所以也暂未做支持。如果确实有也可以通过app.use实现。
为什么要做这个 在使用nodejs开发过程中,总是发现需要做很多重复性的体力劳动,且因为自身是服务端程序员出身,感觉有一些服务端好的东西其实可以在nodejs上得到应用并能提高一些开发工作效率。 本系列文章将介绍对express框架的一些扩展,来达到部分后台框架一样的特性功能。如自动路由,路由映射,参数映射等等功能; 代码基本上都是用typescript写的,因为他有比较好的语法检查,以及最重要的智能提示!!!实在是烦透了方法名、类名要么自己手动敲,要么各种copy。累到嗝屁! 目录 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--开篇 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--自动路由篇【route】 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--路由限制及选择篇【route】 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--控制器的声明定义和发现篇(【controller+action】 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--控制器和处理函数的注册篇【controller+action】 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--请求处理函数的特性注册篇【controller+action+attribute】 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--参数自动映射篇 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--处理结果适配篇【requesthandler】 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--orm篇【像EF一样:如丝般滑】 【nodejs】 让nodejs像后端mvc框架(asp.net mvc)一样处理请求--总结(代码整理一下在来...) 基本业务流程图 已完成的功能 可以根据默认url路由规则自动调用请求处理函数 请求参数可以使用声明实体类+装饰器的方式完成参数的自动绑定 处理结果可以根据需求自动适配(nodejs服务端渲染、ajax调用返回json、jsoncallback调用) PS: 因为看到博问 关于nodejs作为后端功能性的疑问后,觉得我的这几百行代码还是可以分享一下的。所有有了本系列。 成文比较仓促,贴出的代码也是不太完整的,文章的逻辑也比较凌乱...。当然,最重要的原因还是懒!!!,代码整理后同步到github上就一目了然啦。 我相信只要有一点底子的还是基本能看到。后续代码完善一下,比如改改名称,大小写,谢谢注释,优化优化结构后放到github上。如果能帮到你,也甚是荣幸。刚好也借这次机会,把代码review一次,规范一下。
文章目录 前情概要 前面文章把路由已经介绍的差不多了,包括url映射,路由选择等。接下来讲一讲controller的一些基本规则 BaseController的所有代码都在这里拉。相当简单。 主要逻辑:我们的组件接到请求后,根据url规则找到对应的controller和要处理的请求的action后,直接new一个controller出来,把req,res等对象传递给controller对象。并对action进行invoke(call、applay)。 basecontroller的全部代码 import { UserInfo } from './UserInfo'; import { ViewResult } from './ViewResult'; import * as core from "express-serve-static-core"; export class BaseController { constructor(request: core.Request, response: core.Response) { this.request = request; this.response = response; var _req: any = this.request; this.UserInfo = _req.UserInfo; } /** * 当前请求的request对象 * * @type {core.Request} * @memberof BaseController */ public request: core.Request; /** * 当前请求的response对象 * * @type {core.Response} * @memberof BaseController */ public response: core.Response; /** * 当前登录的用户 * * @type {UserInfo} * @memberof BaseController */ public UserInfo: UserInfo; /** * 返回view由视图引擎在服务端进行渲染 * * @param {string} viewName 当前视图的名称 * @param {*} [viewData] 需要传递给视图的数据 * @returns {ViewResult} * @memberof BaseController */ public view(viewName: string, viewData?: any): ViewResult { return new ViewResult(viewName, viewData) } } 所以basecontroller的结构非常简单。只有 构造函数:注入req,res对象 几个属性:req,res,userinfo 几个方法:view 标记当前方法返回的是一个视图,需要在服务端进行渲染,非view返回值都视为直接返回给调用者。这边模仿asp.net mvc 提供比如json,content,file等类似方法也是可以的。目前就我们自己的需求来讲,也就2种,要么在服务端进行渲染,要么就是ajax请求。所以这里没有其他方法了。 具体项目中controller的声明和使用代码 import { Host, Site, OSType, HostEnv, Dictionary } from './../Entity'; import { BaseController, get, post, auth, actionName, ViewResult } from "gd-express-basic"; import { getMongoRepositoryAsync, FindManyOptions } from '../gd-mongo'; export class HostController extends BaseController { public async list() { var repo = await getMongoRepositoryAsync(Host); var b = await repo.FindAsync(); return b } public async info() { var repo = await getMongoRepositoryAsync(Host); var b = await repo.FindAsync({ Ip: this.request.query.ip, HostEnv: this.request.query.env }); return b } @get() public index() { return this.view("hostIndex"); //return this.view("hostIndex", {服务端页面渲染需要用到的数据对象}); } @get() public hostAdd() { return this.view("hostAdd", {}); } } controller注册到RouteHandler 在controller文件夹下弄个index文件,把controller全部声明并导出。 注册到RouteHandler import { RequestHandler, RouteHandler } from 'gd-express-basic' const _app = express(); RouteHandler(_app, controllers); controller 和 action 的定义就到这里完事了,没啥需要特别说的。 PS: 其实这里也是可以做自动发现的。比如:RouteHandler(_app, controllers); --> RouteHandler(_app, {controllers:"./controller/*.js"}); 也是一个不错的方式。个人觉得controller还是不会太频繁的增加。所以暂时也就没做自动发现了(^_^)
文章目录 前情概要 上篇文章把action的注册讲完了,但是我们的处理函数没有指定可接受的httpmethod,也没有别名上面的。下面我们使用typescript的特性之一装饰器来实现一把这个特性。 在控制器和处理函数的注册篇中有说到的第三,第四个参数就在这里排上用场拉。 SetActionDescriptor(cName, aName, undefined, undefined, _reg_controller_name, cType, aType)//加入缓存 第三个参数[httpMethod] 请求方法类型。默认给undefined,后续再通过扫描action上面的特性标签增加进来 第四个参数 [actionName] 路由action名字。默认给undefined,后续再通过扫描action上面的特性标签增加进来 get,post,actionname的装饰器实现方式 代码非常简单,通过SetActionDescriptor函数对当前的action的某些属性进行重写。 typescript的装饰器目前来说还是一个实验性的功能,依照微软的尿性,应该也没变动了,就算有也是增加新功能新特性。 然后装饰器这玩意和后端语言的比如dotnet的特性(attribute)、java的标注等比较相似。可以给方法增加一些额外的数据等。具体,可查看typescript 装饰器参考文档 import { SetActionDescriptor } from './RouteFactory'; import { ActionParamDescriptor, SetActionParamDescriptor, parameterFromType } from './RouteHandler'; /** * 标记当前方法只接受post请求 * * @export * @returns */ export function post() { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { SetActionDescriptor(target.constructor.name, propertyKey, 'post') } } /** * 标记当前方法只接受get请求 * * @export * @returns */ export function get() { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { SetActionDescriptor(target.constructor.name, propertyKey, 'get') } } /** * 重写当前方法的名字,请求使用重写后的名字进行调用 * * @export * @param {string} actionName * @returns */ export function actionName(actionName: string) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { SetActionDescriptor(target.constructor.name, propertyKey, undefined, actionName) } } 装饰器使用列子 觉不觉得眼熟?是不是和C#、java里面的特性、标注差不多。 //HostController.ts import { BaseController, get, post, auth, actionName, ViewResult } from "gd-express-basic"; export class HostController extends BaseController { @get() public index() { return this.view("hostIndex", {}); } @auth() @post() @actionName("saveHost") public hostAdd() { return this.view("hostAdd", {}); } } 装饰器的基本原理 HostController.ts 为typescript源文件代码。 HostController.js为使用tsc编译为es6后的代码。 //HostController.js "use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { //decorators 就是我们声明的装饰器返回的处理闭包函数啦 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); //d(target, key, r) ,调用函数,实际上就是return function (target: any, propertyKey: string, descriptor: PropertyDescriptor)调用这里返回的这个function。 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); const gd_express_basic_1 = require("gd-express-basic"); class HostController extends gd_express_basic_1.BaseController { index() { return this.view("hostIndex", {}); } hostAdd() { return this.view("hostAdd", {}); } } // 1.执行__decorate函数 __decorate([ gd_express_basic_1.get(),//调用我们声明的装饰器,返回要处理函数(闭包) __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], HostController.prototype, "index", null); __decorate([ gd_express_basic_1.auth(), gd_express_basic_1.post(), gd_express_basic_1.actionName("saveHost"), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], HostController.prototype, "hostAdd", null); exports.HostController = HostController; //# sourceMappingURL=HostController.js.map 简单来说就是在源文件加载的时候执行一次__decorate函数,__decorate函数内可以简单理解为调用我们的声明的装饰器函数返回的闭包函数。 到此,我们的controller和action的发现和配置基本上算完成了。
文章目录 前情概要 在使用nodejs开发过程中,刚好碰到需要做一个小工具,需要用到数据库存储功能。而我又比较懒,一个小功能不想搞一个nodejs项目,又搞一个后端项目。不如直接在nodejs里面把对数据库的操作也做掉。 结果百度一圈下来发现nodejs这边还都是比较原始的、类似后端的通过coneection连数据库,接着open,在写sql语句干嘛干嘛的。经过后端这么多年的脚手架工具熏陶,实在懒得写这些没营养的简单增删改查sql语句了。typeorm github地址typeorm github地址 遂通过baidu、google找到了typeorm这个orm框架。果然不错,作者自己也说大量参考了如entityframework、hibernate、dapper等等众多orm框架。吸收了各家之所长。 更多介绍和各种示例可以参考它的demo项目,基本每个数据库都有一个demo,然后对特性也基本都介绍到的。 比如mongodb如何映射复杂对象,关系型数据怎么弄级联删除之类的功能 使用总结 mysql、sqlite、mongodb3个数据库下都使用过,使用感觉虽然没有后端的orm那么强大,但是在nodejs领域内,orm我觉得它已经可以说是no.1啦。当然不排除我孤陋寡闻漏了更NB的其他框架。 绝大多数的后端orm该有的功能它都有,没有可能是没找到正确的使用方式。为此我还发过几条issue给开发者。基本上自己最后google找到解决方或者组件作者给与了回复。 基本功能介绍可以直接去GitHub看,基本上orm应该要有的功能它都有了。 typeorm 项目介绍 此项目github上的第一句介绍: ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms. remark: TypeORM is highly influenced by other ORMs, such as Hibernate, Doctrine and Entity Framework. Some TypeORM features: supports both DataMapper and ActiveRecord (your choice) entities and columns database-specific column types entity manager repositories and custom repositories clean object relational model associations (relations) eager and lazy relations uni-directional, bi-directional and self-referenced relations supports multiple inheritance patterns cascades indices transactions migrations and automatic migrations generation connection pooling replication using multiple database connections working with multiple databases types cross-database and cross-schema queries elegant-syntax, flexible and powerful QueryBuilder left and inner joins proper pagination for queries using joins query caching streaming raw results logging listeners and subscribers (hooks) supports closure table pattern schema declaration in models or separate configuration files connection configuration in json / xml / yml / env formats supports MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / sql.js supports MongoDB NoSQL database works in NodeJS / Browser / Ionic / Cordova / React Native / NativeScript / Expo / Electron platforms TypeScript and JavaScript support produced code is performant, flexible, clean and maintainable follows all possible best practices CLI And more... 个人的一些用法-mongodb 都是一些非常简单的封装,直接贴代码啦。 typeorm mongodb 初始化配置 比如数据库链接字符串,实体类,还有一些其他配置等等 InitMongoDb({ url: _appConfig.mongodb.url, entities: ['bin/Entity/*.js'], synchronize: true, logging: false }); export function InitMongoDb(dbName: string, mongoOptions: MongoConnectionOptions): void; export function InitMongoDb(mongoOptions: MongoConnectionOptions): void; export function InitMongoDb(): void { var options: MongoConnectionOptions = arguments[0]; var dbName: any; if (typeof arguments[0] === 'string') { dbName = arguments[0]; options = arguments[1]; } var dbName = dbName || 'default'; ManangerMongoConnection.ConnectOptions[dbName] = { hasGetConnection: false, options: options }; } typeorm mongodb repository管理器 export async function getMongoRepositoryAsync<Entity>(entityClass: ObjectType<Entity>): Promise<GDMongoRepository<Entity>>; export async function getMongoRepositoryAsync<Entity>(entityClass: ObjectType<Entity>, dbName: string): Promise<GDMongoRepository<Entity>> export async function getMongoRepositoryAsync<Entity>(): Promise<GDMongoRepository<Entity>> { var entityClass = arguments[0] as ObjectType<Entity>; var dbName = (arguments.length > 1 ? arguments[1] : undefined) || 'default'; var conn = await new ManangerMongoConnection().getConnection(dbName); var repo = conn.getMongoRepository(entityClass); var gdRepo = new GDMongoRepository(repo) return gdRepo; } class ManangerMongoConnection { static ConnectOptions: any = {}; async getConnection(dbName: string): Promise<Connection> { var conf = ManangerMongoConnection.ConnectOptions[dbName]; if (!conf) throw Error(`找不到(${dbName})的数据库配置`); if (conf.hasCreated) return conf.connection; var options = conf.options as MongoConnectionOptions; var conn = await createConnection({ type: 'mongodb', url: options.url, synchronize: options.synchronize, logging: options.logging, entities: options.entities }); conf.connection = conn; conf.hasCreated = true; return conn; } } typeorm mongodb repository 简单封装 import { ObjectType, FindManyOptions, MongoRepository, ObjectLiteral, Connection, createConnection, Entity, ObjectIdColumn, Column, ObjectID, getManager } from "typeorm"; import { ObjectId } from 'mongodb' export class GDMongoRepository<TEntity extends ObjectLiteral> { private _repo: MongoRepository<TEntity>; constructor(repo: MongoRepository<TEntity>) { this._repo = repo; } SaveAsync(docment: TEntity): Promise<TEntity> { if (!docment.createdTime) docment.createdTime = new Date(); return this._repo.save(docment); } FindByIdAsync(id: number | string) { return this._repo.findOneById(new ObjectId(id)); } FindByIdsAsync(ids: number[] | string[]) { var _id: ObjectId[] = []; for (let index = 0; index < ids.length; index++) { const element = ids[index]; _id.push(new ObjectId(element)); } return this._repo.findByIds(_id); } FindAsync(optionsOrConditions?: FindManyOptions<TEntity> | Partial<TEntity> | ObjectLiteral): Promise<TEntity[]> { return this._repo.find(optionsOrConditions) } CountAsync(optionsOrConditions?: FindManyOptions<TEntity> | Partial<TEntity> | ObjectLiteral): Promise<number> { var query: any = Object.assign({}, optionsOrConditions); if (query.take) delete query.take; if (query.skip) delete query.skip; if (query.order) delete query.order; query = query.where || query; return this._repo.count(query) } FindAndCount(optionsOrConditions?: FindManyOptions<TEntity> | Partial<TEntity>): Promise<[TEntity[], number]> { return this._repo.findAndCount(optionsOrConditions) } AggregateAsync(pipeline: ObjectLiteral[]): Promise<TEntity[]> { return this._repo.aggregate(pipeline).toArray() } async RemoveByIdAsync(id: number | string): Promise<number> { var r = await this._repo.deleteOne({ _id: new ObjectId(id) }); if (r.deletedCount) return r.deletedCount return 0; } async RemoveAsync(conditions: ObjectLiteral): Promise<number> { var r = await this._repo.deleteMany(conditions); if (r.deletedCount) return r.deletedCount return 0; } async UpdateByIdAsync(id: number | string, update: ObjectLiteral | Partial<TEntity>): Promise<number> { if (update.$set || update.$unset || update.$rename) { } else { update = { $set: update } } update.$set.lastModifyTime = new Date(); var r = await this._repo.updateOne({ _id: new ObjectId(id) }, update); return r.modifiedCount; } async UpdateAsync(query: FindManyOptions<TEntity> | Partial<TEntity> | ObjectLiteral, update: ObjectLiteral | Partial<TEntity>): Promise<number> { if (update.$set || update.$unset || update.$rename) { } else { update = { $set: update } } update.$set.lastModifyTime = new Date(); var r = await this._repo.updateMany(query, update); return r.modifiedCount; } } 一些简单的使用例子 public async list() { var repo = await getMongoRepositoryAsync(Host); var b = await repo.FindAsync(); return b } public async info() { var repo = await getMongoRepositoryAsync(Host); var b = await repo.FindAsync({ Ip: this.request.query.ip, HostEnv: this.request.query.env }); return b } 给开源项目点赞!给国际友人点赞!
写个demo来玩一玩linux平台下使用lldb加载sos来调试netcore应用。 当然,在真实的产线环境中需要分析的数据和难度远远高于demo所示,所以demo的作用也仅仅只能起到介绍工具的作用。 通常正常情况下,分析个几天才能得出一个结论的的结果都还是比较令人开心的!,很多时候分析来分析去也搞不出个所以然,也是很正常的(当然,也是自己学艺不精(^_^)) 在linux平台下的sos调试远没有在windows下面用windbg来得舒服,该有的命令很多都没有。 微软爸爸还要加油努力啊!如果能做到linux下的dmp能在windows下面用windbg之类的工具那就爽翻了,哈哈,当然不可能,臆想一下下拉。 lldb工具的安装,linux下netcore如何生成dump文件,查看下文centos7使用lldb调试netcore应用转储dump文件 图片有点多,文章有点长,来一个大纲先 准备DEMO程序的代码 生成待调试分析的dump文件 目前linux下sos支持的命令 模拟分析内存泄漏 内存泄漏调试分析结论 内存泄漏分析疑问一 内存泄漏分析疑问二 死循环调试分析 内存泄漏调试分析结论 准备DEMO程序的代码 废话不多说,先上demo程序代码。代码超级简单,模拟内存泄漏就简单的往一个静态list里面每次插入1M的byte[];死循环则就是一个while(true); PS:话说markdown插入代码能不能有收起,展开功能呢。那就爽歪歪拉 @dudu namespace linxu_dump_lldb.Controllers { class env { public static bool cpu_flag; public static bool setcpu_flag(bool flag) => cpu_flag = flag; public static bool getcpu_flag() => cpu_flag; public static List<byte[]> memory = new List<byte[]>(); } [Route("api/[controller]/[action]")] [ApiController] public class ValuesController : ControllerBase { public string index() =>(GC.GetTotalMemory(false) / 1024.0 / 1024).ToString("0.00M"); [HttpGet] public void begin_cpu() { env.setcpu_flag(true); Task.Run(() => {while (env.getcpu_flag()){}}); } [HttpGet] public void begin_memory() { var size_1m = 1 * 1024 * 1024; for (int i = 0; i < 100; i++) env.memory.Add(new byte[size_1m]); } [HttpGet] public void end_cpu() => env.setcpu_flag(false); [HttpGet] public void end_memory() { env.memory.Clear(); GC.Collect(); }}} 生成待调试分析的dump文件 生成模拟内存泄漏的dump 请求接口begin_memory来个几次后,然后通过createdump工具生成dump包,执行了4-5次begin_memory,也就是加了大约400-500M的byte[]放到静态变量中 生成死循环的dump包 请求接口begin_cpu开始异步任务进入死循环,然后通过createdump工具生成dump包 目前linux下sos支持的命令 当前dotnet版本2.1.1。如下图所示支持,sos支持的命令,缺少几个比较有用的命令:ProcInfo ,ObjSize ,SyncBlk,其他缺少的赶脚也用不太上。最最重要的是gdb,lldb的调试命令不熟悉,或者说找不到windbg所对应命令还是蛮难受的,需要进一步认真学习才行... 模拟分析内存泄漏 命令走一个,进入lldb。 /usr/local/llvm-3.9.0/bin/lldb dotnet -c /opt/dump_file/memory_dump -o "plugin load /usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.1/libsosplugin.so" dumpheap -stat 分析先走一波。对堆上面的对象进行统计 大于2kb的对象看一看 图上反馈byte[]数组对象占的内存最大,而且是远超其他类型的,因此可以判定应该是byte[]在代码的某个地方没有释放。进去跟进去即可。 真实情况项目情况很可能是占用内存最大,对象最多的string对象。分析起来真的有时候看运气,凭经验!...(^_^) dumpheap -mt addr(byte[]数组的MT地址) 过滤看看类型是byte[]的都有那些对象。 看上去特征特别明显,全是大小为1048600的bte[]对象。接下来随便找一个看看具体对象的数据是什么 dumpobj addr(对象地址);查看对象的基本结构 内存数据看上去全是 00 00 00。可以说是一个默认的byte[]对象。可以在进入查看一下 sos DumpArray -start 0 -length 10 00007fd5febff9d8(对象地址) 查看数据对象,上一张图上我们能看到数组的lenght有1048576个,所以加上-start,-length参数,只查看最前面10个对象。不然刷屏得刷死咯。 在接着使用 sos DumpVC(查看值类型命令) 00007fd611151460(数组元素类型的mt地址) 00007fd5febff9e9(数组元素对象的地址) a 如下图所示,每个数组元素的类型都是byte,他们的value都是0; 接下来,我们在看看这些个对象的gcroot对象是谁,也就是说这些个对象到底由谁持有 gcroot addr(对象地址) 在挨个看一看,能发现我们的这个list对象lenth有400个,_version=501;这是因为我clear过一次,所以。clear+1,add([100])个数组,所以400+100+1=501; 如果这是时候有一个objsize命令可以使用,我们就能计算出来这个list是一个400M的丑陋大对象。可惜linux下面木有。 那就只能用查看数据的方法看看这个数组的具体详情拉。 sos DumpArray -details(可以把每个对象的基本结构都打印出来),能看到他的每一个元素都有1M(size:1048600(0x100018) bytes)大小 内存泄漏调试分析结论 上图种gcroot有3个结果。 第一个,用DumpArray查看后发现,应该是一个系统的静态对象,里面存储都是context之类的东西。 第二个,就是我们的问题list对象。即List<byte[]> 第三个,是第二个list对象的items。 所以问题就出在我们这个静态的 list对象上了,那从代码上搜索一下就比较容易发现我们的List<byte[]>在哪里了。 疑问一 上图种是书籍Pro .Net Performance: Optimize Your C# Applications第98页的一个列子,可惜没有搞懂他的这个地址怎么出来的,能直接拉出来堆栈信息... 疑问二 按理来说1M应该等于1048576,那为什么这里显示是1048600呢,多余的24byte是啥玩意呢? dumpobj查看byte[]对象信息 dumpmt查看byte[]类型的mt信息 x addr(对象地址,x命令是lldb的命令,用户查看地址处的内存数据。可以使用 -c 24指定需要查看多少位数据) x addr 前16位数据小红框标记,最后8位小红框标记。中间的则是1M的01。01:byte数据,代码直接赋值。 for (int i = 0; i < 100; i++) { var x = new byte[size_1m]; for (int j = 0; j < x.Length; j++) x[j] = 1; env.memory.Add(x); } 但是这24位数据内存结构为何这么组织,以及具体的含义就不是特别清楚了,有待考证!!! 学艺不精!,准备回家看看C#本质论有没有说到这部分内容...或者哪位大哥可以说清楚一下,不胜感激!!! google搜索的时候发现 Pro .Net Performance: Optimize Your C# Applications,这本书很屌啊!!!,绝壁值得一看,就是英文不行,求中文版啊!!!,好想吐槽一下国内的垃圾编辑或作者,好的书一本都不翻译,垃圾玩意全翻译过来。 http://codingsight.com/precise-computation-of-clr-object-size/ https://stackoverflow.com/questions/38056513/why-does-windbg-show-system-int32-variables-as-24-bytes 死循环调试分析 clrthreads -live 先看看还在运行的线程有那些。然后通过thread select 线程编号(lldb命令)。来切换到当前线程。线程编号不是列表种的id字段,而是最前面一行的id。lldb 可以通过thread list命令来列举所有线程。 剩下的工作就是体力活动拉,一个一个看,一个一个分析。 比如,我们切换到线程3看一看他当前的堆栈信息 clrstack命令可以查看当前线程在托管代码种的堆栈信息。 dumstack则可以看到非托管代码种的堆栈信息 thread backtrace lldb查看堆栈信息的命令。 线程3,能看到当前栈在非托管代码中(libcoreclr.so!TwoWayPipe::WaitForConnection),看方法名字也能猜到干嘛的,不太像我们的目标。 另外,linux下面 ps -T -p 32728 命令可以查看到进行下线程的基本情况 top -H -p 32728 更happy。 所以在排查高cpu问题的时候能提供许多便利性,反而比内存问题要来得方便很多。(图中的pid等数据不是一致性的。因为在写blog的时候图片是多次截取的。) 所以在dump包的时候可以记录下来高cpu的线程id,然后通过thread select 找到对应的线程编号。在然后直接切换过去看一看就完事拉。 所以 thread select 30 clrstack看一看,嗯!当前线程在 linxu_dump_lldb.Controllers.ValuesController+<>c.b__1_0() [C:\Users\czd89\source\repos\ConsoleApp4\linxu_dump_lldb\Controllers\ValuesController.cs @ 31]。 看一看当前栈上面都有一些上面参数 CLRStack [-a] [-l] [-p];-p:看参数,-l:看局部变量,-a:=-l+-p; 当然,我们的代码是异步的,也没有捕获任何action里面的变量,所以这里的这个参数,以及参数里面的属性啥都没有。 从dll反编译代码也能和我们lldb看到的东西一一对以上。 内存泄漏调试分析结论 到这里,问题就很明显能看出来了,当然主要还是我们的DEMO是最简单的。还是开篇说过的那句话:通常正常情况下,分析个几天才能得出一个结论的的结果都还是比较令人开心的!,很多时候分析来分析去也搞不出个所以然,也是很正常的(当然,也是自己学艺不精(^_^),当自勉!) 还能看一看具体方法的汇编代码等信息。 参考资料: https://docs.microsoft.com/en-us/dotnet/framework/tools/sos-dll-sos-debugging-extension https://github.com/dotnet/coreclr/blob/master/Documentation/building/debugging-instructions.md https://lldb.llvm.org/tutorial.html https://stackoverflow.com/questions/38056513/why-does-windbg-show-system-int32-variables-as-24-bytes http://codingsight.com/precise-computation-of-clr-object-size/ https://zhuanlan.zhihu.com/p/20838172 https://blog.csdn.net/inuyashaw/article/details/55095545
安装cmake之前,记得升级gcc,请参考centos7 升级GCC版本到7.3.0 #shell 太简单,懒得解释 wget https://cmake.org/files/v3.11/cmake-3.11.4.tar.gz tar xzvf cmake-3.11.4.tar.gz cd cmake-3.11.4 ./bootstrap gmake gmake install 参考资料:https://cmake.org/download/
废话不多说,直接上shell,还是比较简单的。就是编译时间有点长... 都是以小时计的......,我刀片机上面一台虚拟机反正是等了3个小时 #必备组件安装 yum install -y gcc gcc-c++ bzip2 #root用户执行,到用户目录。其实cd哪里都阔以。 cd ~/ #下载gcc源代码 wget https://ftp.gnu.org/gnu/gcc/gcc-7.3.0/gcc-7.3.0.tar.gz #解压 tar -zxvf gcc-7.3.0.tar.gz #到源代码目录 cd gcc-7.3.0 #下载一些必须的东西 ./contrib/download\_prerequisites #如果下载不下来,或者下载缓慢可以考虑查看命令行拿到下载地址自己down下拉后,放到源代码目录。具体地址:ftp://gcc.gnu.org/pub/gcc/infrastructure/,需要下载的几个源代码包如下,可以查看./contrib/download\_prerequisites文件。 gmp='gmp-6.1.0.tar.bz2' mpfr='mpfr-3.1.4.tar.bz2' mpc='mpc-1.0.3.tar.gz' isl='isl-0.16.1.tar.bz2' #接着创建一个目录,用于gcc build mkdir gcc-build-7.3.0 #cd到build目录,准备开始编译了。 cd gcc-build-7.3.0 #编译的config,disable-multilib 64位编译标记。具体可查看官方文档 ../configure -enable-checking=release -enable-languages=c,c++ -disable-multilib #接着就是漫长的编译等待了 make #不知是否可以使用make -j8之类的开启多核编译是否会快一点,我反正是等了好几个小时 #next make install #重新建立软连接 find / -name "libstdc++.so*" #找到自己的文件路径 #把libstdc++.socopy到/usr/lib64目录,类似下面的命令 cp /root/gcc-7.3.0/gcc-build-7.3.0/x86\_64-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.24 /usr/lib64 cd /usr/lib64 rm -rf libstdc++.so.6 #删除原来的 ln -s libstdc++.so.6.0.24 libstdc++.so.6 #重新建立软连 gcc -v #看看输出,是不是如下图变成7.3.0拉。 参考资料:gcc源代码下载地址
centos7下安装lldb,dotnet netcore 进程生成转储文件,并使用lldb进行分析 随着netcore应用在linux上部署的应用越来越多,碰到cpu 100%,内存暴涨的情况也一直偶有发生,在windows平台下进程管理器右键转储,下载到本地使用windbg或者直接vs分析都比较方便。而在linux平台下因为一直接触的不深,所以对这一块也一直没有比较好的了解。所以接下来的文章将对在centos7下安装lldb,生成转储以及调试分析进行一些简单说明。 还有就是一般产线的机器也不太会有可以直接调试的机会,所以真出现问题也只能在产线机器dump进程,然后下载到本地来慢慢分析。 环境说明: os:centos7 dotnet :2.1.1。查看官方文档2.0.0只能使用lldb 3.6;2.1以上必须是3.9.0;所以特别要注意版本问题,一个是createdump 2.0的有bug会失败。二个是dotnet版本和lldb版本要匹配 被调试分析的应用也是用2.1跑起来的。 测试目标程序 yum install dotnet-sdk-2.1 dotnet new mvc vi /mvc.csproj #netcoreapp2.0 to netcoreapp2.1 #PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" to Version="2.1.1" dotnet restore dotnet build dotnet ./bin/Debug/netcoreapp2.1/mvc.dll centos7 升级GCC,安装cmake centos7 升级GCC版本到7.3.0centos7 安装cmake centos7下安装lldb调试工具 最开始直接使用给力网友的脚本进行安装(脚本地址查看文章结尾参考资料),后发现3.9.1不能调试分析netcore应用,必须要3.9.0,所以在给力网友的脚本上略作修改后使用。修改后脚本地址https://github.com/czd890/shell/blob/master/llvm_clang_lldb/3.9.0/llvm_clang_install.sh。主要修改几个地方:把lldb,libunwind移动到build_llvm_toolchain中,一次性安装。check_and_download方法中检查本地是否已下载源码包的检查略作修改,只判断指定版本,编译的时候修改为make -j8(我本地机器8核)。 脚本大概思路就是下载如下所表示的组件所有源码,除llvm外的其他组件源代码解压到llvm/tools目录下,这样子源代码就全部准备好 BUILD_TARGET_COMPOMENTS="llvm clang compiler_rt libcxx libcxxabi clang_tools_extra lldb lld libunwind"; 接下来就是编译的过程了。 #安装一些必要的依赖组件 yum install libedit-devel libxml2-devel ncurses-devel python-devel swig #执行根据给力网友的脚本修改后的脚本 当然如果脚本下载速度慢,也是可以自己下载后上传的目录的。具体下载地址查看文章尾部参考资料 llvm,clang,lldb源代码下载地址(3.9.0) 准备源代码差不多就如下图。然后 sh llvm_clang_install.sh开始执行脚本; 默认安装目录在 PREFIX_DIR=/usr/local/llvm-$LLVM_VERSION;。也就是是 /usr/local/llvm-3.9.0;可以在脚本的最开始对此进行修改。 开始执行,又是一段漫长的等待时间,8核并发编译,耗费了估计得有1-2个小时。 刀片机的CPU都跑满了!!! 出去吃完饭后回来,就看到完成拉。具体的path路径可以选择加不加都可以,加的话,直接/etc/profile export PATH=$PATH:llvm-path/bin即可 lldb安装完成,我们的工作就完成一大半拉。 dotnet netcore应用如何生成内存转储文件 /usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.1/createdump 9364 具体命令解释 createdump [options] pid -f, --name - dump path and file name. The pid can be placed in the name with %d. The default is "/tmp/coredump.%d" -n, --normal - create minidump (default). -h, --withheap - create minidump with heap. -t, --triage - create triage minidump. -u, --full - create full core dump. -d, --diag - enable diagnostic messages. 使用lldb调试分析netcore应用内存转储文件 #官方文档上是这样写的。 /usr/local/llvm-3.9.0/bin/lldb -O "settings set target.exec-search-paths /usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.1" \ -o "plugin load /usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.1/libsosplugin.so" \ --core /opt/dump\_file/mvcdumpmindump /usr/share/dotnet/dotnet #网友调试参考博客上是这样写的。 /usr/local/llvm-3.9.0/bin/lldb dotnet \ -c /opt/dump\_file/mvcdumpmindump \ -o "plugin load /usr/share/dotnet/shared/Microsoft.NETCore.App/2.1.1/libsosplugin.so" 2种写法都是可行的。然后具体的调试分析指令什么的都在coreclr调试说明指导文档有说明。 参考资料: coreclr调试说明指导文档https://github.com/dotnet/coreclr/blob/master/Documentation/building/debugging-instructions.md coreclr生成dmp说明指导文档https://github.com/dotnet/coreclr/blob/master/Documentation/botr/xplat-minidump-generation.md llvm,clang,lldb源代码下载地址(3.9.0)http://releases.llvm.org/download.html#3.9.0 lldb源码安装指导文档http://lldb.llvm.org/build.html#BuildingLldbOnLinux llvm源码安装指导文档http://releases.llvm.org/3.9.0/docs/GettingStarted.html 网友centos7安装llvm,clang,lldb等给力脚本https://github.com/owent-utils/bash-shell/blob/master/LLVM%26Clang%20Installer/3.9/installer.sh 网友调试参考博客文章使用SOS调试工具检查应用程序状态
要解决的问题 开发管理工具触发站点构建事件,事件处理中需要调用Jenkins接口开始构建动作。 我的应用场景: 使用jira作为管理工具,在jira中创建自定义的工作流来规定测试,上线,发布等流程,并通过自动化工具完成这一系列的操作。 jira issue数据格式地址:https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-getIssue jenkins接口参考地址:https://wiki.jenkins.io/display/JENKINS/Remote+access+API jira 的webhook webhook server端代码(nodejs+typescript) 主要的代码如下所示,所以看代码和注释都不需要多解释。 export class JenkinsController extends BaseController { public async jira_notify_jenkins_build() { const jenkinsAuthHeader = `Basic ${base64(jenkins_username + ':' + jenkins_userpwd)}`; var jenkinsBaseUrl = 'http://ip:8080/';//jenkins的地址 var comment_name = req.body.issue.fields.components[0].name,//站点名称 jira_issue_id = req.body.issue.key,//jira上面的任务id git_branch = req.body.issue.fields.customfield_10107//jira上面的自定义字段,存的是git 分支名称 , env = req.body.issue.fields.status.name;//当前要构建的的环境名称 //一些检查 if (!req.body.changelog || !req.body.changelog.items || !req.body.changelog.items[0] || req.body.changelog.items[0].field !== 'status') { log.Info(`${comment_name} changelog not status ${JSON.stringify(req.body && req.body.changelog)}`) res.end(); return; } if (env !== 'SIT' && env !== 'UAT' && env !== 'STG' && env !== 'PROD') { log.Info(`${comment_name} env not support.${env}`) res.end(); return; } //获得jenkins的crumb值,没有这玩意接口就不能调用 var data = await ApiClient.Get<any>(jenkinsBaseUrl, 'crumbIssuer/api/json', undefined, { headers: { 'Authorization': jenkinsAuthHeader } }); //组装接口调用要用到的参数 var headers: any = { 'Authorization': jenkinsAuthHeader, 'Content-Type': 'application/x-www-form-urlencoded' }; headers[data.crumbRequestField] = data.crumb; //parameter:jenkins job 构建时要传递的参数 var postData = { parameter: [ { "name": "selected_branch", "value": git_branch }, { "name": "env", "value": env }, { "name": "JIRA_ISSUE_ID", "value": jira_issue_id } ] }; log.Info(`${comment_name} notify jenkins building. ${JSON.stringify(postData)}`) //调用jenkins接口,开始构建 await ApiClient.Post(jenkinsBaseUrl, `job/${comment_name}/build`, { json: JSON.stringify(postData) }, { headers: headers }); } } 虽然jenkins和jira都有互相调用和触发的插件,但是很难完美的满足自身的业务, 比如要根据不同的env(构建环境)调用不同的jenkins来触发不同的构建脚本,毕竟测试环境,产线环境的脚本不太一样。 所以还不如自己做一个小站点来中转来得快,devops运维懂代码开发,是多么强悍呀! 顺带还能做个页面看看进度什么的。
问题概况 linux机器在/etc/profile配置完成环境变量后,SSH到目标机器执行命令,但是获取不到已配置的环境变量值。 例如场景: 在/etc/profile配置了http代理 export all_proxy=http://$PROXY_HOST:8118 export ftp_proxy=http://$PROXY_HOST:8118 export http_proxy=http://$PROXY_HOST:8118 export https_proxy=http://$PROXY_HOST:8118 然后 SSH 目标机器,并通过pm2 start app.js 启动应用程序,但是应用程序的请求走不到http代理程序。 解决办法 在 /etc/bashrc 文件中,把配置的那一坨也仍进去。就OK了。 问题原因 SSH 登录默认为非shell登录方式,而非shell登录方式执行的是bashrc脚本初始化环境变量。 而shell登录方式则是执行的是profile脚本初始化环境变量。 即 参考资料很好的说明了这个问题,就不搬砖了。 参考:Why does an SSH remote command get fewer environment variables then when run manually?
要解决的问题 jenkins自动构建完成后,希望能通过sonar静态代码检查生成一份报告,给与开发人员对当前代码的做一个质量评估和修改意见 1.安装并配置sonar服务器 懒得说,跟着官方文档走就行,这边主要的开发语言是.net core 和 typescript,所以在sonar server中的应用市场搜索对应语言安装就完事 安装参考地址:https://docs.sonarqube.org/display/SONAR/Setup+and+Upgrade 2.jenkins机器下载sonar扫描器 .net core 扫描器:https://docs.sonarqube.org/display/SCAN/Scanning+on+Linux+or+macOS+with+Scanner+4.0.x typescript 扫描器:https://docs.sonarqube.org/display/PLUG/SonarTS 路径地址替换自己的 .net core :/opt/sonar-scanner-netcore/sonar-scanner-3.1.0.1141/conf 默认扫描器:/opt/sonar-scanner/conf 该路径下有配置文件:sonar-scanner.properties 修改该配置文件中的sonar.host.url=http://192.168.1.133:9000 为自己的sonar server服务器地址。 3.创建jenkins构建任务 选择创建流水线任务,也就是pipeline。因为我们有一个自动化流程管理工具,所以job的触发构建动作是在自动化工具中实现的。这里只是怎么调用sonar-scanner。 自动化流程工具传递参数(需要扫描的站点名称,类型),进入jenkins的sonar扫描任务, 脚本做这么几个事情: 1.根据传入的站点名称,获取当前站点名称在jenkins的配置,然后从配置文件中获取源代码地址, 2.拉取源代码 3.sonar-scanner。 jenkins 内部对象api文档地址:http://javadoc.jenkins-ci.org/allclasses-noframe.html jenkins pipeline参考地址:https://jenkins.io/doc/book/pipeline/syntax/ pipeline script脚本如下: //@NonCPS 标记当前方法的返回值不需要序列话,因为 def job,这里的job对象不能被序列化。 @NonCPS def getUrl(){ def job=jenkins.model.Jenkins.getInstanceOrNull().getItem("${site_name}"); if(job==null){ throw new hudson.AbortException("not found jenkins job ${site_name}") } def jobScmUrl=job.getScm().getUserRemoteConfigs().get(0).getUrl(); //获得站点的git源代码地址 return jobScmUrl; } node { //typescript扫描器需要运行tsc命令,但是我们的项目是全局安装的typescript,所以这里要指定NODE_PATH environment { NODE_PATH = '/usr/local/node/lib/node_modules'; } stage('checkout') { deleteDir();//删除当前构建的workspace def scmUrl=getUrl(); // git 拉取代码到workspace,指定分支为master,并指定git使用的SSH证书id(3e6da11b-9f1d-42e2-8cb0-e8616ec0709e) def scmOut=checkout([ $class: 'GitSCM', branches: [[name: 'master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[ credentialsId: '3e6da11b-9f1d-42e2-8cb0-e8616ec0709e', url: scmUrl]]]); } if("${language}" == "netcore"){ //如果是.net core 类型的站点 stage('sonar-begin') { sh script: "dotnet /opt/sonar-scanner-netcore/SonarScanner.MSBuild.dll begin /k:\"${site_name}\" /d:sonar.exclusions=/**/*.js" } stage('sonar-build') { sh script: 'dotnet build'; } stage('sonar-end') { sh script: 'dotnet /opt/sonar-scanner-netcore/SonarScanner.MSBuild.dll end'; } } else if("${language}" == "nodejs"){ stage('sonar-scanner') { sh script: 'echo "------------$NODE_PATH"' sh script: "sh /opt/sonar-scanner/bin/sonar-scanner -Dsonar.projectKey=${site_name} -Dsonar.sources=." } } else{ echo 'not support language ${language}'; throw new hudson.AbortException("not support language ${language}") } } 4.成果展示
linux上安装不可描述的软件。有些时候github什么的总是访问慢呀,尤其是npm安装有些资源down不下来,很是恼火! 第一步安装ss客户端 #参考:https://github.com/shadowsocks/shadowsocks-libev yum -y install epel-release yum -y install python-pip pip install shadowsocks sudo yum install gettext gcc autoconf libtool automake make asciidoc xmlto c-ares-devel libev-devel # Installation of Libsodium export LIBSODIUM_VER=1.0.13 wget https://download.libsodium.org/libsodium/releases/libsodium-$LIBSODIUM_VER.tar.gz tar xvf libsodium-$LIBSODIUM_VER.tar.gz pushd libsodium-$LIBSODIUM_VER ./configure --prefix=/usr && make sudo make install popd sudo ldconfig 第二步编辑json文件 vi /etc/shadowsocks.json #例如: { "server":"*", "server_port":*, "local_address": "0.0.0.0", "local_port":1080, "password":"*", "timeout":300, "method":"chacha20", "fast_open": false, "workers": 1 } 第三步sslocal设置系统服务并开机自动启动 vi /etc/systemd/system/sslocal.service [Unit] Description=Shadowsocks [Service] TimeoutStartSec=0 ExecStart=/usr/bin/sslocal -c /etc/shadowsocks.json [Install] WantedBy=multi-user.target systemctl enable sslocal systemctl start sslocal 大公告成,如果在配合一个privoxy工具,嗯!很是爽歪歪
有些时候我们需要通过不同的代理访问不同资源,比如某些ip或域名走本地网络,某些ip或域名走不可描述的代理等。当然这只是举个栗子! 我要解决的问题是:我的内网机器没有internet访问权限,但是我的应用程序有部分请求是要访问intranet网络,而部分请求要访问internet网络。所以我必须得有一个软件或工具来做这个区分或者说是请求的转发。那么privoxy就闪亮登场了。 网络环境是这样子的 1.安装privoxy 这个就不说了 2.修改配置 主配置文件 /etc/privoxy/config 增加一条配置: actionsfile usr.proxy 例如: actionsfile match-all.action # Actions that are applied to all sites and maybe overruled later on. actionsfile default.action # Main actions file actionsfile user.action # User customizations actionsfile usr.proxy 3.编辑usr.proxy文件 在/etc/privoxy/目录下创建usr.proxy文件 {{alias}} #直连方式,也就是说让请求走本地网络 direct = +forward-override{forward .} #请求转发到代理机器,请求可以走到internet网络 proxy = +forward-override{forward 10.0.5.10:8118} #请求转发到代理机器,请求可以走到不可描述的地方去 #1080端口代表的是什么不可描述的东西,就不多说了。 #值得注意的是1080走的socks5代理,所以是forward-socks5 10.0.5.10:1080 .(后面的这个点可不能丢哦) over_wall_proxy = +forward-override{forward-socks5 10.0.5.10:1080 .} default = proxy #==========默认代理========== {default} / #==========直接连接========== {direct} 10.0.5.10 127.0.0.1 localhost .api.com {proxy} #==========不可描述的代理========== {over_wall_proxy} .google. .github.com .githubusercontent. .github.cnpmjs.org
#!/usr/bin/expect set timeout 10 set username [lindex $argv 0] set password [lindex $argv 1] set hostname [lindex $argv 2] #set username ftpuser #set password ftpuser #spawn 模拟终端交互 #ssh-copy-id将本机当前用户的ssh登录公钥copy到目标机器上 # "*(yes/no)*" 返回内容包含yes/no,表示添加host到已知host #password 表示要输入目标机器的密码 #"*please*","*Permission denied*" 错误,没法成功添加ssh公钥到目标机器 #"*All keys were skipped*","*you wanted were added*" 已添加过,则直接跳过。 spawn ssh-copy-id $username@$hostname expect { "*(yes/no)*" { send "yes\r"; exp_continue; } "password:" { send "$password\r"; exp_continue; } "*please*" { exit 5 } "*All keys were skipped*" { exit } "*you wanted were added*" {exit } "*Permission denied*" { exit 6 } } expect eof #1.保存上面的代码为sh文件,并设置文件具有执行权限 #2.如下使用 ./auto_ssh.sh "username" "passpord" "ip"
要解决的问题? 需要解决的问题:https://q.cnblogs.com/q/105319/ 简单来说就是本地机器通过一台公网机器SSH到公网机器后面的私网机器。 网络环境如下图:本地机器可访问代理机器,代理机器可访问内网机器,本地机器和内网机器不互通 操作步骤: 1.实现本地机器到代理机器的SSH连接。 ssh ftpuser@proxyip 2.关键步骤:修改本地机器的ssh_config(/etc/ssh/ssh_config): Host proxy_5_10 HostName 代理机器ip Port 22 User ftpuser #内网机器ip或ip的CIDR表达式。10.0.*:表示所有以10.0开头的ip全部走下面的代理方式。 Host 10.0.* Port 22 User ftpuser ProxyCommand ssh ftpuser@proxy_5_10 -W %h:%p 3.实现: 这个时候在本地机器上就可以直接已 ssh user@10.0.0.1 这种方式直接SSH到内网机器了。 更多参考请google搜索ProxyCommand,ssh正向代理,ssh反向代理
jenkins配置slave进行构建时,发现slave构建的控制台输入中文乱码,查看master,slave的jenkins系统信息 file.encoding和sun.jnu.encoding都没有问题,只有从master->node->查看系统信息发现encoding=ANSI_X3.4-1968。 搜索baidu各种方法都不起作用。 参考下面的连接发现需要这样玩 export LANG= 然后检查代码发现 /var/lib/jenkins/ 下面都没有.bashrc文件,so,从其他用户copy一个过来,重启完成。 参考:http://jenkins-ci.361315.n4.nabble.com/Hudson-slave-and-file-encoding-td3064290.html#a3074852
。net framework 下面可以用下面的代码获取到本地网络ip地址。netcore下面这个代码也依然可以用 System.Net.Dns.GetHostName() System.Net.Dns.GetHostEntry(hostName) But,偶然的一次线上日志查看,发现获取到的IP全都是127.0.0.1。虽然本地windows测试上面的代码好使,本地测试环境centos7也好使。就是线上部分机器有问题, 遂写了个demo程序放产线上跑了一下,发现GetHostEntry里面只有一张网卡的信息,即本地回环的那个(lo); 后,经过伟大的google教训:找到https://github.com/dotnet/corefx/issues/8458,遂恍然大悟,应该这样写: System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces() .Select(p => p.GetIPProperties()) .SelectMany(p => p.UnicastAddresses) .Where(p => p.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !System.Net.IPAddress.IsLoopback(p.Address)) .FirstOrDefault()?.Address.ToString(); 参考:https://github.com/dotnet/corefx/issues/8458
yum install autoconf automake libtool yum install freetype-devel fontconfig libXft-devel yum install libjpeg-turbo-devel libpng-devel giflib-devel libtiff-devel libexif-devel yum install glib2-devel cairo-devel git clone https://github.com/mono/libgdiplus cd libgdiplus ./autogen.sh make make install cd /usr/lib64/ ln -s /usr/local/lib/libgdiplus.so gdiplus.dll 错误一 type init 错误。提示找不到libgdiplus组件 Make solution 1: ln -s /usr/local/lib/libgdiplus.so /usr/lib64/libgdiplus.so ln -s /usr/local/lib/libgdiplus.so /usr/libgdiplus.so Make solution 2: vi /etc/ld.so.conf ##将 /usr/local/lib 加入 ldconfig #配置生效。 错误二 生成出来的图片没有任何文字 DrawString not dislpay in image 复制 windowns fronts to /usr/share/fonts/chinese/TrueType/
当前系统环境:centos7 x64. dotnet 2.0. 不管是 ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true; 还是: HttpClient httpClient = new HttpClient(new HttpClientHandler() { ServerCertificateCustomValidationCallback = (a, b, c, d) => true }); 都会发生错误: 错误信息大致如下: (The handler does not support custom handling of certificates with this combination of libcurl (7.29.0) and its SSL backend ("NSS/3.28.4").) ---> System.PlatformNotSupportedException: The handler does not support custom handling of certificates with this combination of libcurl (7.29.0) and its SSL backend ("NSS/3.28.4"). at System.Net.Http.CurlHandler.SslProvider.SetSslOptionsForUnsupportedBackend(EasyRequest easy, ClientCertificateProvider certProvider) at System.Net.Http.CurlHandler.SslProvider.SetSslOptions(EasyRequest easy, ClientCertificateOption clientCertOption) 解决方案: # yum update(可选) # yum install openssl-devel gcc #安装openssl和gcc # 安装指定版本的curl # wget https://curl.haxx.se/download/curl-7.55.1.tar.gz # tar -zxf curl-7.55.1.tar.gz # cd curl-7.55.1 # ./configure --prefix=/usr/local/curl/ --without-nss --with-ssl=/usr/local/ssl/ # make # make install #备份原来的curl mv /usr/bin/curl /usr/bin/curl.bak #将安装的curl 创建软连 ln -s /usr/local/curl/bin/curl /usr/bin/curl # curl --version #差不多输出下面的内容 #curl 7.55.1 (x86_64-pc-linux-gnu) libcurl/7.55.1 OpenSSL/1.0.2k zlib/1.2.7 #增加lib搜索目录 # vi /etc/ld.so.conf #增加 # /usr/local/curl/lib # cat /etc/ld.so.conf 差不多下面这样子 # include ld.so.conf.d/*.conf /usr/local/curl/lib # 重新load配置 # ldconfig 参考文章:https://www.latoooo.com/xia_zhe_teng/368.htmhttps://segmentfault.com/a/1190000012282935https://www.cnblogs.com/Anker/p/3209876.htmlhttps://github.com/dotnet/corefx/issues/9728#issuecomment-286251370
文章以efcore 2.0.0-preview2.测试验证通过。其他版本不保证使用,但是思路不会差太远。源代码,报道越短,事情越严重!文章越短,内容越精悍! 目标: 1.实现entity的自动发现和mapper设置. 2.默认字符串长度,而不是nvarchar(max). 3.decimal设置精度 实现目标1:继承RelationalModelCustomizer,重写Customize方法。 当然,我们也可以重写dbcontext的OnModelCreating方法,but,我们怎么能这么low呢。必须要用点高级玩意是吧,当然这也是更底层的扩展方式。项目里面有多个dbcontext的话,在这里集中扩展管理比较方便。 在然后,这个RelationalModelCustomizer继承自ModelCustomizer。在联想到efcore未来的版本会支持redis,nosql什么的。到时候估计还回有一个osqlModelCustomizer之类的吧,期待中...... 实现目标2、3:继承自CoreConventionSetBuilder类,重写CreateConventionSet方法。 重写CreateConventionSet方法,能拿到关键的ConventionSet对象。这个对象囊括了诸多的约定配置等等。比如maxlengthattribute属性标记,stringlength属性标记,timestamp属性标记,表id的自动发现规则等等等。。。 那,我们增加2个小小的约定:字符串默认长度(StringDefaultLengthConvention),和decimal精度设置attribute(DecimalPrecisionAttributeConvention)及fluntapi方式. 文章的最后附efcore 所有的可替换扩展service。 //servie,DI注入替换. services.AddSingleton<IModelCustomizer, MyRelationalModelCustomizer>(); services.AddSingleton<ICoreConventionSetBuilder, MyCoreConventionSetBuilder>(); //实现entity的自动发现和mapper设置 public class MyRelationalModelCustomizer : RelationalModelCustomizer { public MyRelationalModelCustomizer(ModelCustomizerDependencies dependencies) : base(dependencies){} public override void Customize(ModelBuilder modelBuilder, DbContext dbContext) { base.Customize(modelBuilder, dbContext); var sp = (IInfrastructure<IServiceProvider>)dbContext; var dbOptions = sp.Instance.GetServices<DbContextOptions>(); foreach (var item in dbOptions) { if (item.ContextType == dbContext.GetType()) ConfigureDbContextEntityService.Configure(modelBuilder, item, dbContext); } } } public class MyCoreConventionSetBuilder : CoreConventionSetBuilder { public MyCoreConventionSetBuilder(CoreConventionSetBuilderDependencies dependencies) : base(dependencies){} public override ConventionSet CreateConventionSet() { var conventionSet = base.CreateConventionSet(); //默认字符串长度,而不是nvarchar(max). //为什么要insert(0,obj),则是因为这个默认规则要最优先处理,如果后续有规则的话就直接覆盖了。 //propertyBuilder.HasMaxLength(32, ConfigurationSource.Convention); //理论上我指定了规则的级别为.Convention,应该和顺序就没有关系了。but,还没有完成测试,所以我也不知道 conventionSet.PropertyAddedConventions.Insert(0, new StringDefaultLengthConvention()); //decimal设置精度 conventionSet.PropertyAddedConventions.Add(new DecimalPrecisionAttributeConvention()); return conventionSet; } } 下面是StringDefaultLengthConvention和DecimalPrecisionAttributeConvention的实现代码 //字符串默认长度 public class StringDefaultLengthConvention : IPropertyAddedConvention { public InternalPropertyBuilder Apply(InternalPropertyBuilder propertyBuilder) { if (propertyBuilder.Metadata.ClrType == typeof(string)) propertyBuilder.HasMaxLength(32, ConfigurationSource.Convention); return propertyBuilder; } } //attribute方式设置decimal精度 public class DecimalPrecisionAttributeConvention : PropertyAttributeConvention<DecimalPrecisionAttribute> { public override InternalPropertyBuilder Apply(InternalPropertyBuilder propertyBuilder, DecimalPrecisionAttribute attribute, MemberInfo clrMember) { if (propertyBuilder.Metadata.ClrType == typeof(decimal)) propertyBuilder.HasPrecision(attribute.Precision, attribute.Scale); return propertyBuilder; } /// <summary> /// decimal类型设置精度 /// </summary> /// <param name="propertyBuilder"></param> /// <param name="precision">精度</param> /// <param name="scale">小数位数</param> public static PropertyBuilder<TProperty> HasPrecision<TProperty>(this PropertyBuilder<TProperty> propertyBuilder, int precision = 18, int scale = 4) { //fluntapi方式设置精度 ((IInfrastructure<InternalPropertyBuilder>)propertyBuilder).Instance.HasPrecision(precision, scale); return propertyBuilder; } public static InternalPropertyBuilder HasPrecision(this InternalPropertyBuilder propertyBuilder, int precision, int scale) { propertyBuilder.Relational(ConfigurationSource.Explicit).HasColumnType($"decimal({precision},{scale})"); return propertyBuilder; } 以上就是实现的代码,就这么几行。嗯,还是挺简单的. -------------- 以下,则是efcore的源代码。展示了如此之多的可扩展。随着DI的运用和微软的开放,嗯。用烂啦,用炸拉(^_^) //各种规则和约定 public virtual ConventionSet AddConventions(ConventionSet conventionSet) { ValueGeneratorConvention valueGeneratorConvention = new RelationalValueGeneratorConvention(); ReplaceConvention(conventionSet.BaseEntityTypeChangedConventions, valueGeneratorConvention); ReplaceConvention(conventionSet.PrimaryKeyChangedConventions, valueGeneratorConvention); ReplaceConvention(conventionSet.ForeignKeyAddedConventions, valueGeneratorConvention); ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, valueGeneratorConvention); var relationalColumnAttributeConvention = new RelationalColumnAttributeConvention(); conventionSet.PropertyAddedConventions.Add(relationalColumnAttributeConvention); var sharedTableConvention = new SharedTableConvention(); conventionSet.EntityTypeAddedConventions.Add(new RelationalTableAttributeConvention()); conventionSet.EntityTypeAddedConventions.Add(sharedTableConvention); conventionSet.BaseEntityTypeChangedConventions.Add(new DiscriminatorConvention()); conventionSet.BaseEntityTypeChangedConventions.Add( new TableNameFromDbSetConvention(Dependencies.Context?.Context, Dependencies.SetFinder)); conventionSet.EntityTypeAnnotationChangedConventions.Add(sharedTableConvention); conventionSet.PropertyFieldChangedConventions.Add(relationalColumnAttributeConvention); conventionSet.PropertyAnnotationChangedConventions.Add((RelationalValueGeneratorConvention)valueGeneratorConvention); conventionSet.ForeignKeyUniquenessChangedConventions.Add(sharedTableConvention); conventionSet.ForeignKeyOwnershipChangedConventions.Add(sharedTableConvention); conventionSet.ModelBuiltConventions.Add(new RelationalTypeMappingConvention(Dependencies.TypeMapper)); conventionSet.ModelBuiltConventions.Add(sharedTableConvention); conventionSet.ModelAnnotationChangedConventions.Add(new RelationalDbFunctionConvention()); return conventionSet; } //还是各种规则和约定 public virtual ConventionSet CreateConventionSet() { var conventionSet = new ConventionSet(); var propertyDiscoveryConvention = new PropertyDiscoveryConvention(Dependencies.TypeMapper); var keyDiscoveryConvention = new KeyDiscoveryConvention(); var inversePropertyAttributeConvention = new InversePropertyAttributeConvention(Dependencies.TypeMapper); var relationshipDiscoveryConvention = new RelationshipDiscoveryConvention(Dependencies.TypeMapper); conventionSet.EntityTypeAddedConventions.Add(new NotMappedEntityTypeAttributeConvention()); conventionSet.EntityTypeAddedConventions.Add(new NotMappedMemberAttributeConvention()); conventionSet.EntityTypeAddedConventions.Add(new BaseTypeDiscoveryConvention()); conventionSet.EntityTypeAddedConventions.Add(propertyDiscoveryConvention); conventionSet.EntityTypeAddedConventions.Add(keyDiscoveryConvention); conventionSet.EntityTypeAddedConventions.Add(inversePropertyAttributeConvention); conventionSet.EntityTypeAddedConventions.Add(relationshipDiscoveryConvention); conventionSet.EntityTypeAddedConventions.Add(new DerivedTypeDiscoveryConvention()); conventionSet.EntityTypeIgnoredConventions.Add(inversePropertyAttributeConvention); var foreignKeyIndexConvention = new ForeignKeyIndexConvention(); var valueGeneratorConvention = new ValueGeneratorConvention(); conventionSet.BaseEntityTypeChangedConventions.Add(propertyDiscoveryConvention); conventionSet.BaseEntityTypeChangedConventions.Add(keyDiscoveryConvention); conventionSet.BaseEntityTypeChangedConventions.Add(inversePropertyAttributeConvention); conventionSet.BaseEntityTypeChangedConventions.Add(relationshipDiscoveryConvention); conventionSet.BaseEntityTypeChangedConventions.Add(foreignKeyIndexConvention); conventionSet.BaseEntityTypeChangedConventions.Add(valueGeneratorConvention ); // An ambiguity might have been resolved conventionSet.EntityTypeMemberIgnoredConventions.Add(inversePropertyAttributeConvention); conventionSet.EntityTypeMemberIgnoredConventions.Add(relationshipDiscoveryConvention); var keyAttributeConvention = new KeyAttributeConvention(); var foreignKeyPropertyDiscoveryConvention = new ForeignKeyPropertyDiscoveryConvention(); var backingFieldConvention = new BackingFieldConvention(); var concurrencyCheckAttributeConvention = new ConcurrencyCheckAttributeConvention(); var databaseGeneratedAttributeConvention = new DatabaseGeneratedAttributeConvention(); var requiredPropertyAttributeConvention = new RequiredPropertyAttributeConvention(); var maxLengthAttributeConvention = new MaxLengthAttributeConvention(); var stringLengthAttributeConvention = new StringLengthAttributeConvention(); var timestampAttributeConvention = new TimestampAttributeConvention(); conventionSet.PropertyAddedConventions.Add(backingFieldConvention); conventionSet.PropertyAddedConventions.Add(concurrencyCheckAttributeConvention); conventionSet.PropertyAddedConventions.Add(databaseGeneratedAttributeConvention); conventionSet.PropertyAddedConventions.Add(requiredPropertyAttributeConvention); conventionSet.PropertyAddedConventions.Add(maxLengthAttributeConvention); conventionSet.PropertyAddedConventions.Add(stringLengthAttributeConvention); conventionSet.PropertyAddedConventions.Add(timestampAttributeConvention); conventionSet.PropertyAddedConventions.Add(keyDiscoveryConvention); conventionSet.PropertyAddedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.PropertyAddedConventions.Add(keyAttributeConvention); conventionSet.PrimaryKeyChangedConventions.Add(valueGeneratorConvention); conventionSet.KeyAddedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.KeyAddedConventions.Add(foreignKeyIndexConvention); conventionSet.KeyRemovedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.KeyRemovedConventions.Add(foreignKeyIndexConvention); conventionSet.KeyRemovedConventions.Add(keyDiscoveryConvention); var cascadeDeleteConvention = new CascadeDeleteConvention(); conventionSet.ForeignKeyAddedConventions.Add(new ForeignKeyAttributeConvention(Dependencies.TypeMapper)); conventionSet.ForeignKeyAddedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.ForeignKeyAddedConventions.Add(keyDiscoveryConvention); conventionSet.ForeignKeyAddedConventions.Add(valueGeneratorConvention ); conventionSet.ForeignKeyAddedConventions.Add(cascadeDeleteConvention); conventionSet.ForeignKeyAddedConventions.Add(foreignKeyIndexConvention); conventionSet.ForeignKeyRemovedConventions.Add(keyDiscoveryConvention); conventionSet.ForeignKeyRemovedConventions.Add(valueGeneratorConvention ); conventionSet.ForeignKeyRemovedConventions.Add(foreignKeyIndexConvention); conventionSet.ForeignKeyUniquenessChangedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.ForeignKeyUniquenessChangedConventions.Add(foreignKeyIndexConvention); conventionSet.ForeignKeyOwnershipChangedConventions.Add(new NavigationEagerLoadingConvention()); conventionSet.ModelBuiltConventions.Add(new ModelCleanupConvention()); conventionSet.ModelBuiltConventions.Add(keyAttributeConvention); conventionSet.ModelBuiltConventions.Add(new IgnoredMembersValidationConvention()); conventionSet.ModelBuiltConventions.Add(new PropertyMappingValidationConvention(Dependencies.TypeMapper)); conventionSet.ModelBuiltConventions.Add(new RelationshipValidationConvention()); conventionSet.ModelBuiltConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.NavigationAddedConventions.Add(backingFieldConvention); conventionSet.NavigationAddedConventions.Add(new RequiredNavigationAttributeConvention()); conventionSet.NavigationAddedConventions.Add(inversePropertyAttributeConvention); conventionSet.NavigationAddedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.NavigationAddedConventions.Add(relationshipDiscoveryConvention); conventionSet.NavigationRemovedConventions.Add(relationshipDiscoveryConvention); conventionSet.IndexAddedConventions.Add(foreignKeyIndexConvention); conventionSet.IndexRemovedConventions.Add(foreignKeyIndexConvention); conventionSet.IndexUniquenessChangedConventions.Add(foreignKeyIndexConvention); conventionSet.PropertyNullabilityChangedConventions.Add(cascadeDeleteConvention); conventionSet.PrincipalEndChangedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.PropertyFieldChangedConventions.Add(keyDiscoveryConvention); conventionSet.PropertyFieldChangedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.PropertyFieldChangedConventions.Add(keyAttributeConvention); conventionSet.PropertyFieldChangedConventions.Add(concurrencyCheckAttributeConvention); conventionSet.PropertyFieldChangedConventions.Add(databaseGeneratedAttributeConvention); conventionSet.PropertyFieldChangedConventions.Add(requiredPropertyAttributeConvention); conventionSet.PropertyFieldChangedConventions.Add(maxLengthAttributeConvention); conventionSet.PropertyFieldChangedConventions.Add(stringLengthAttributeConvention); conventionSet.PropertyFieldChangedConventions.Add(timestampAttributeConvention); return conventionSet; } 我就是所有的可替换service啦。不要眼花有点多!,找着合适的用起来! serviceCollection.AsQueryable() .Where(p => p.ServiceType.ToString().StartsWith("Microsoft.EntityFrameworkCore")) .Each(sd => { Console.WriteLine($"{sd.Lifetime.ToString().PadRight(15, ' ')}{sd.ServiceType.FullName}"); }); //output //Singleton Microsoft.EntityFrameworkCore.Storage.IDatabaseProvider //Singleton Microsoft.EntityFrameworkCore.ValueGeneration.IValueGeneratorCache //Singleton Microsoft.EntityFrameworkCore.Storage.IRelationalTypeMapper //Singleton Microsoft.EntityFrameworkCore.Storage.ISqlGenerationHelper //Singleton Microsoft.EntityFrameworkCore.Migrations.IMigrationsAnnotationProvider //Singleton Microsoft.EntityFrameworkCore.Storage.IRelationalValueBufferFactoryFactory //Singleton Microsoft.EntityFrameworkCore.Infrastructure.IModelValidator //Scoped Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.IConventionSetBuilder //Singleton Microsoft.EntityFrameworkCore.Update.IUpdateSqlGenerator //Scoped Microsoft.EntityFrameworkCore.Update.IModificationCommandBatchFactory //Scoped Microsoft.EntityFrameworkCore.ValueGeneration.IValueGeneratorSelector //Scoped Microsoft.EntityFrameworkCore.Storage.IRelationalConnection //Singleton Microsoft.EntityFrameworkCore.Migrations.IMigrationsSqlGenerator //Scoped Microsoft.EntityFrameworkCore.Storage.IRelationalDatabaseCreator //Scoped Microsoft.EntityFrameworkCore.Migrations.IHistoryRepository //Scoped Microsoft.EntityFrameworkCore.Query.ICompiledQueryCacheKeyGenerator //Scoped Microsoft.EntityFrameworkCore.Storage.IExecutionStrategyFactory //Scoped Microsoft.EntityFrameworkCore.Query.IQueryCompilationContextFactory //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.IMemberTranslator //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.ICompositeMethodCallTranslator //Singleton Microsoft.EntityFrameworkCore.Query.Sql.IQuerySqlGeneratorFactory //Singleton Microsoft.EntityFrameworkCore.Infrastructure.ISingletonOptions //Singleton Microsoft.EntityFrameworkCore.ValueGeneration.Internal.ISqlServerValueGeneratorCache //Singleton Microsoft.EntityFrameworkCore.Infrastructure.Internal.ISqlServerOptions //Scoped Microsoft.EntityFrameworkCore.Update.Internal.ISqlServerUpdateSqlGenerator //Scoped Microsoft.EntityFrameworkCore.ValueGeneration.Internal.ISqlServerSequenceValueGeneratorFactory //Scoped Microsoft.EntityFrameworkCore.Storage.Internal.ISqlServerConnection //Singleton Microsoft.EntityFrameworkCore.Storage.IParameterNameGeneratorFactory //Singleton Microsoft.EntityFrameworkCore.Migrations.IMigrationsIdGenerator //Singleton Microsoft.EntityFrameworkCore.Update.Internal.IKeyValueIndexFactorySource //Singleton Microsoft.EntityFrameworkCore.Infrastructure.IModelSource //Singleton Microsoft.EntityFrameworkCore.Infrastructure.IModelCustomizer //Scoped Microsoft.EntityFrameworkCore.Migrations.IMigrator //Singleton Microsoft.EntityFrameworkCore.Migrations.IMigrationCommandExecutor //Scoped Microsoft.EntityFrameworkCore.Migrations.IMigrationsAssembly //Scoped Microsoft.EntityFrameworkCore.Storage.IDatabase //Scoped Microsoft.EntityFrameworkCore.Update.IBatchExecutor //Singleton Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory //Singleton Microsoft.EntityFrameworkCore.Storage.IRawSqlCommandBuilder //Scoped Microsoft.EntityFrameworkCore.Update.ICommandBatchPreparer //Singleton Microsoft.EntityFrameworkCore.Migrations.IMigrationsModelDiffer //Singleton Microsoft.EntityFrameworkCore.Storage.ITypeMapper //Scoped Microsoft.EntityFrameworkCore.Storage.IDatabaseCreator //Scoped Microsoft.EntityFrameworkCore.Storage.IDbContextTransactionManager //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IMaterializerFactory //Singleton Microsoft.EntityFrameworkCore.Query.Internal.IShaperCommandContextFactory //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IConditionalRemovingExpressionVisitorFactory //Scoped Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ICompositePredicateExpressionVisitorFactory //Singleton Microsoft.EntityFrameworkCore.Query.Expressions.ISelectExpressionFactory //Scoped Microsoft.EntityFrameworkCore.Query.Internal.IExpressionPrinter //Scoped Microsoft.EntityFrameworkCore.Query.IRelationalResultOperatorHandler //Scoped Microsoft.EntityFrameworkCore.Query.IQueryContextFactory //Scoped Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.IEntityQueryableExpressionVisitorFactory //Scoped Microsoft.EntityFrameworkCore.Query.IEntityQueryModelVisitorFactory //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.IProjectionExpressionVisitorFactory //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.IExpressionFragmentTranslator //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ISqlTranslatingExpressionVisitorFactory //Scoped Microsoft.EntityFrameworkCore.Storage.Internal.INamedConnectionStringResolver //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.RelationalCompositeMemberTranslatorDependencies //Singleton Microsoft.EntityFrameworkCore.Storage.RelationalSqlGenerationHelperDependencies //Singleton Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapperDependencies //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.RelationalCompositeExpressionFragmentTranslatorDependencies //Singleton Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidatorDependencies //Singleton Microsoft.EntityFrameworkCore.Update.UpdateSqlGeneratorDependencies //Singleton Microsoft.EntityFrameworkCore.Query.Sql.QuerySqlGeneratorDependencies //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.RelationalCompositeMethodCallTranslatorDependencies //Singleton Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGeneratorDependencies //Singleton Microsoft.EntityFrameworkCore.Migrations.MigrationsAnnotationProviderDependencies //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitorDependencies //Singleton Microsoft.EntityFrameworkCore.Storage.ParameterNameGeneratorDependencies //Singleton Microsoft.EntityFrameworkCore.Query.Expressions.SelectExpressionDependencies //Singleton Microsoft.EntityFrameworkCore.Storage.RelationalValueBufferFactoryDependencies //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.RelationalProjectionExpressionVisitorDependencies //Scoped Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.RelationalConventionSetBuilderDependencies //Scoped Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreatorDependencies //Scoped Microsoft.EntityFrameworkCore.Migrations.HistoryRepositoryDependencies //Scoped Microsoft.EntityFrameworkCore.Query.RelationalCompiledQueryCacheKeyGeneratorDependencies //Scoped Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitorDependencies //Scoped Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.RelationalEntityQueryableExpressionVisitorDependencies //Scoped Microsoft.EntityFrameworkCore.Storage.RelationalConnectionDependencies //Scoped Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseDependencies //Scoped Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextDependencies //Singleton Microsoft.EntityFrameworkCore.Internal.IDbSetFinder //Singleton Microsoft.EntityFrameworkCore.Internal.IDbSetInitializer //Singleton Microsoft.EntityFrameworkCore.Internal.IDbSetSource //Singleton Microsoft.EntityFrameworkCore.Internal.IEntityFinderSource //Singleton Microsoft.EntityFrameworkCore.Metadata.Internal.IEntityMaterializerSource //Singleton Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ICoreConventionSetBuilder //Singleton Microsoft.EntityFrameworkCore.Infrastructure.IModelCacheKeyFactory //Singleton Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IInternalEntityEntryFactory //Singleton Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IInternalEntityEntrySubscriber //Singleton Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IEntityEntryGraphIterator //Singleton Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IEntityGraphAttacher //Singleton Microsoft.EntityFrameworkCore.Query.Internal.INodeTypeProviderFactory //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IKeyPropagator //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.INavigationFixer //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ILocalViewListener //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager //Scoped Microsoft.EntityFrameworkCore.Internal.IConcurrencyDetector //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IInternalEntityEntryNotifier //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IValueGenerationManager //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IChangeTrackerFactory //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IChangeDetector //Scoped Microsoft.EntityFrameworkCore.Internal.IDbContextServices //Scoped Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies //Singleton Microsoft.EntityFrameworkCore.Query.Internal.ICompiledQueryCache //Scoped Microsoft.EntityFrameworkCore.Query.Internal.IAsyncQueryProvider //Scoped Microsoft.EntityFrameworkCore.Query.Internal.IQueryCompiler //Singleton Microsoft.EntityFrameworkCore.Query.Internal.IQueryAnnotationExtractor //Scoped Microsoft.EntityFrameworkCore.Query.Internal.IQueryOptimizer //Singleton Microsoft.EntityFrameworkCore.Query.Internal.IEntityTrackingInfoFactory //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ITaskBlockingExpressionVisitor //Scoped Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IEntityResultFindingExpressionVisitorFactory //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IMemberAccessBindingExpressionVisitorFactory //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.INavigationRewritingExpressionVisitorFactory //Singleton Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IQuerySourceTracingExpressionVisitorFactory //Scoped Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IRequiresMaterializationExpressionVisitorFactory //Scoped Microsoft.EntityFrameworkCore.Query.IResultOperatorHandler //Singleton Microsoft.EntityFrameworkCore.Internal.ISingletonOptionsInitialzer //Singleton Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger`1 //Singleton Microsoft.EntityFrameworkCore.Diagnostics.ILoggingOptions //Singleton Microsoft.EntityFrameworkCore.Infrastructure.ISingletonOptions //Scoped Microsoft.EntityFrameworkCore.Metadata.IModel //Scoped Microsoft.EntityFrameworkCore.Internal.ICurrentDbContext //Scoped Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IEntityStateListener //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.INavigationListener //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IKeyListener //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IQueryTrackingListener //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IPropertyListener //Scoped Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IEntityStateListener //Scoped Microsoft.EntityFrameworkCore.Infrastructure.IResettableService //Scoped Microsoft.EntityFrameworkCore.Infrastructure.IResettableService //Singleton Microsoft.EntityFrameworkCore.Storage.DatabaseProviderDependencies //Singleton Microsoft.EntityFrameworkCore.Query.ResultOperatorHandlerDependencies //Singleton Microsoft.EntityFrameworkCore.Infrastructure.ModelSourceDependencies //Singleton Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorCacheDependencies //Singleton Microsoft.EntityFrameworkCore.Infrastructure.ModelValidatorDependencies //Singleton Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.CoreConventionSetBuilderDependencies //Scoped Microsoft.EntityFrameworkCore.Storage.ExecutionStrategyDependencies //Scoped Microsoft.EntityFrameworkCore.Query.CompiledQueryCacheKeyGeneratorDependencies //Scoped Microsoft.EntityFrameworkCore.Query.QueryContextDependencies //Scoped Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorSelectorDependencies //Scoped Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitorDependencies //Scoped Microsoft.EntityFrameworkCore.Storage.DatabaseDependencies //Scoped Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizerDependencies //Scoped Microsoft.EntityFrameworkCore.Infrastructure.ModelCacheKeyFactoryDependencies //Scoped Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextDependencies //Singleton Microsoft.EntityFrameworkCore.DbContextOptions<Aquirrel.EntityFramework.Test.TestDbContext> //Singleton Microsoft.EntityFrameworkCore.DbContextOptions //Singleton Microsoft.EntityFrameworkCore.Infrastructure.IModelCustomizer //Singleton Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ICoreConventionSetBuilder
昨儿个研究docker ,搭建私有仓库。想着用nginx代理一下仓库地址。方式使用80端口,于是愉快的下载,编辑,安装nginx。创建nginx.service作为系统启动服务。 结果......,多折腾了2个小时。。。。因为systemctl start nginx 的时候总是timeout。实际上nginx已经起来了。 顿时一阵baidu,bing,google全上阵,最终发现问题是 nginx.service 中的 PIDFile=/run/nginx.pid 和 nginx.conf中的 pid /run/nginx.pid 2处的文件路径不一致...硬是折磨了我2个小时,哎。。。。。,还是不熟悉。。坑
首先交代环境。本地2台主机,一台windows主机,一台等待安装centos的主机。2台主机在同一个局域网。通过路由器自动获取ip上网。 网上大多数pxe安装方式都采用自己搭建dns服务器的方式来进行,but,我们的dns服务器就是一个小破路由器,自然是做不来这个事情。 所以经过一番google,决定使用dnsmasq来搞定我们的dns服务器。临时凑合用用(主要是,突然发现家里没有一个大一点的U盘,也是醉了),网络环境:路由器网关 192.168.2.1,win ip:192.168.2.2,pxe server ip:192.168.2.3 1.win主机上,hyper-v虚拟一个centos,暂称为 pxe server hyper-v 安装centos 2.安装配置dnsmasq。 #安装dnsmasq yum install dnsmasq #配置dnsmasq mv /etc/dnsmasq.conf /etc/dnsmasq.conf.backup vi /etc/dnsmasq.conf #编辑如下类容 #网卡名字,通过ip addr获取 interface=eno16777736,lo domain=centos7.lan # DHCP range-leases dhcp-range= eno16777736,192.168.1.100,192.168.1.253,255.255.255.0,1h # PXE dhcp-boot=pxelinux.0,pxeserver,192.168.1.20 # Gateway dhcp-option=3,192.168.1.1 # DNS dhcp-option=6,92.168.1.1, 8.8.8.8 server=8.8.4.4 # Broadcast Address dhcp-option=28,10.0.0.255 # NTP Server dhcp-option=42,0.0.0.0 pxe-prompt="Press F8 for menu.", 60 pxe-service=x86PC, "Install CentOS 7 from network server 192.168.1.20", pxelinux enable-tftp tftp-root=/var/lib/tftpboot 参数解释: interface – 服务器需要监听并提供服务的网络接口。 bind-interfaces – 取消注释来绑定到该网络接口 domain – 替换为你的域名。 dhcp-range – 替换为你的网络掩码定义的网段。 dhcp-boot – 替换该IP地址为你的网络接口IP地址。 dhcp-option=3,192.168.1.1 – 替换该IP地址为你的网段的网关。 dhcp-option=6,92.168.1.1 – 替换该IP地址为你的DNS服务器IP——可以定义多个IP地址。 server=8.8.4.4 – 这里放置DNS转发服务器IP地址。 dhcp-option=28,10.0.0.255 – 替换该IP地址为网络广播地址——可选项。 dhcp-option=42,0.0.0.0 – 这里放置网络时钟服务器——可选项(0.0.0.0地址表示参考自身)。 pxe-prompt – 保持默认——按F8进入菜单,60秒等待时间。 pxe=service – 使用x86PC作为32为/64位架构,并在字符串引述中输入菜单描述提示。其它类型值可以是:PC98,IAEFI,Alpha,Arcx86,IntelLeanClient,IA32EFI,BCEFI,XscaleEFI和X86-64EFI。 enable-tftp – 启用内建TFTP服务器。 tftp-root – 使用/var/lib/tftpboot——所有网络启动文件所在位置。 3.安装syslinux和tftp-server并完成配置 yum install syslinux yum install tftp-server #copy 安装引导文件 cp -r /usr/share/syslinux/* /var/lib/tftpboot mkdir /var/lib/tftpboot/pxelinux.cfg #创建安装引导配置文件并编辑为下面的内容 touch /var/lib/tftpboot/pxelinux.cfg/default #我们使用syslinux引导安装,通过ftp传输安装包,所以,label 2,3,4可以删除。 default menu.c32 prompt 0 timeout 300 ONTIMEOUT local menu title ########## PXE Boot Menu ########## label 1 menu label ^1) Install CentOS 7 x64 with Local Repo kernel centos7/vmlinuz #我们自己搭建的ftp地址:ftp://192.168.1.20/pub append initrd=centos7/initrd.img method=ftp://192.168.1.20/pub devfs=nomount label 2 menu label ^2) Install CentOS 7 x64 with <http://mirror.centos.org> Repo kernel centos7/vmlinuz append initrd=centos7/initrd.img method=http://mirror.centos.org/centos/7/os/x86\_64/ devfs=nomount ip=dhcp label 3 menu label ^3) Install CentOS 7 x64 with Local Repo using VNC kernel centos7/vmlinuz append initrd=centos7/initrd.img method=ftp://192.168.1.20/pub devfs=nomount inst.vnc inst.vncpassword=password label 4 menu label ^4) Boot from local drive 4.准备centos安装包 #首先使用sftp之类的工具把我们的centos安装镜像上传到我们的pxe server #接着,挂在这个镜像 5.mount -o loop /path/to/centos-dvd.iso /mnt #创建tftp安装文件下载目录,并copy镜像内的文件到目录 mkdir /var/lib/tftpboot/centos7 cp /mnt/images/pxeboot/vmlinuz /var/lib/tftpboot/centos7 cp /mnt/images/pxeboot/initrd.img /var/lib/tftpboot/centos7 #安装ftp。copy镜像文件到ftp目录。此处目录要和syslinux配置的引导配置文件中的地址要一致 yum install vsftpd cp -r /mnt/* /var/ftp/pub/ #修改文件权限 chmod -R 755 /var/ftp/pub 5.准备工作已完成,接下来就是配置需要安装centos的主机了。 检查主板是否开启并支持了pxe安装方式。大约就是进blos配置一下无耻的盗一下图: 并且设置pxe为启动顺序第一位,然后保存配置重启主机。 这个时候,不出意外的话,主机上就会显示我们在pxe server里配置syslinux引导菜单了。 选择 label 1 开始安装。 附上pxe server的日志
摘要: 阿里云是最近新出的一个镜像源。得益于阿里云的高速发展,这么大的需求,肯定会推出自己的镜像源。 阿里云Linux安装镜像源地址:http://mirrors.aliyun.com/ CentOS系统更换软件安装源 第一步:备份你的原镜像文件,以免出错后可以恢复。 阿里云是最近新出的一个镜像源。得益于阿里云的高速发展,这么大的需求,肯定会推出自己的镜像源。 阿里云Linux安装镜像源地址:http://mirrors.aliyun.com/ CentOS系统更换软件安装源 第一步:备份你的原镜像文件,以免出错后可以恢复。 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 第二步:下载新的CentOS-Base.repo 到/etc/yum.repos.d/ #CentOS 5 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo #CentOS 6 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo #CentOS 7 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo 更改CentOS-Media.repo使其为不生效: enabled=0 第三步:运行yum makecache生成缓存 yum clean all yum makecache 转自:https://yq.aliyun.com/articles/33286
紧接上一篇镜像发布到官方之后,我们来搭建我们自己的私有仓库,比较,如果真的要在生产环境使用的话,这是必须的。 首先,我们来准备一下搭建私有仓库所需要的信息。 #先吧私有仓库down下来,这需要一点时间,刚好这中间的时间,我们可以准备一下其他的东西 docker pull registry 紧接着,registry需要https运行环境,所以来生成我们自己的证书(简单说明一下,目前的registry版本是2,之前的1是支持非ssl的,docker在0.9以下。) 先交代一下环境:物理机是win10,使用hyper-v 虚拟一个cenots(ip:192.168.50.2)作为我们的docker host。使用内部网络,物理机共享本地网络方式连接上网。私有仓库使用域名local.registry.docker.com,端口:3075。 #创建证书文件夹 mkdir certs #创建registry登录用户配置文件文件夹 mkdir auth #生成我们的ssl证书 openssl req -newkey rsa:4096 -nodes -sha256 -keyout /certs/local.registry.docker.com.key -x509 -days 365 -out /certs/local.registry.docker.com.crt #创建一个我们的private registry用户,admin admin 就是账号和密码了。 docker run --entrypoint htpasswd registry:2 -Bbn admin admin > /auth/htpasswd 网络环境还不错的情况下,这个时候pull registry应该也已经完成了。那...... 然后,把我们的 私有仓库跑起来先 docker run -dit -p 3075:5000 --restart=always --name hub \ -v /auth:/auth \ -e "REGISTRY_AUTH=htpasswd" \ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ -v /certs:/certs \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/local.registry.docker.com.crt \ -e REGISTRY_HTTP_TLS_KEY=/certs/local.registry.docker.com.key \ registry:2 解释一下参数: -d:表示容器后台运行 -p:端口映射 --restart=always:可以理解为开机启动。开机:就是启动docker客户端拉。 --name registry:给容器取一个名字,方便识别和记忆 -v:挂在本地文件到容器中。命令格式:hostdir:cdir[:rw|ro] 主机目录:容器目录[:读写权限] -v pwd/auth:/auth:挂在本地的密码文件夹 -v pwd/certs:/certs:挂在本地的ssl证书文件夹 -e:设置环境变量参数 -e REGISTRY_AUTH:验证方式 -e REGISTRY_AUTH_HTPASSWD_REALM:验证域名 -e REGISTRY_AUTH_HTPASSWD_PATH:密码文件路径 -e REGISTRY_HTTP_TLS_CERTIFICATE:ssl证书文件路径 -e REGISTRY_HTTP_TLS_KEY:ssl证书文件路径 最后的registry则是镜像的名字了。具体参数什么的,可以参考registry官方文档地址 centos docker客户端配置私有仓库信任 #在每个安装docker客户端的机器上执行。将前面搭建私有仓库创建的ssl证书copy到/etc/docker/certs.d/[仓库地址],如果不走这一步,就会收到下下下图的这种错误 x509....... mkdir -p /etc/docker/certs.d/local.registry.docker.com:3075 cp /certs/local.registry.docker.com.crt /etc/docker/certs.d/local.registry.docker.com\:3075/ windows配置私有仓库 在然后,登录到私有仓库  在在然后,吧我们刚才的hello world项目push到我们的本地仓库 docker tag imageid imagename:给镜像打个tag,然后push这个tag到本地仓库。 在在在然后,把我们的私有仓库的hello world跑起来 在在在在然后,在文章的最后,我们在装一个私有仓库web ui浏览工具(hyper/docker-registry-web): 本来想安装一个web ui管理工具:konradkleine/docker-registry-frontend,但是......,奈何......启动提示 no mpm loaded 错误。详见问题描述及解决办法https://github.com/kwk/docker-registry-frontend/issues/88。不想折腾了,索性安装另外一个web ui 浏览工具 话说web ui还是有好几个的,排在最前面的3个ui镜像,第一个安装有错误,要特殊处理,那我们就安装第二个好了...... docker run -dit -p 8899:8080 --restart=always --name registry-web-manager --link registry -e REGISTRY\_BASIC\_AUTH="YWRtaW46YWRtaW4=" -e REGISTRY\_TRUST\_ANY\_SSL=true -e REGISTRY\_URL=https://local.registry.docker.com:3075/v2/ -e REGISTRY\_NAME=https://local.registry.docker.com:3075 --add-host local.registry.docker.com:192.168.50.2 hyper/docker-registry-web 安装脚本参数解释 --link registry:容器之间建立联系,个人猜测,起始不需要,因为没用到....... -e REGISTRY_BASIC_AUTH:连接到私有仓库的账号密码base64结果(base64(username:password))。所以为什么仓库为什么要选择htpasswd验证方式了。 -e REGISTRY_TRUST_ANY_SSL:忽略ssl错误,因为我们用的是自签名的ssl证书 -e REGISTRY_URL:仓库访问地址 -e REGISTRY_NAME:仓库名 --add-host local.registry.docker.com:192.168.50.2:增加一条本地host。指示对我们local.registry.docker.com的访问解析到我们的docker host机器上,这样,在我们的web ui容器中访问我们的私有仓库地址时,才能正确解析。 这几天的学习和折腾就暂时到这里了,后续在研究一下docker-compose容器编排和自动构建部署。
学习一个技术的第一步,总是要先打印或显示一个hello world的。当然,学习docker也不例外。上一篇文章已经简单的介绍了环境的安装和配置。接下来就要打印我们的hello world了。 首先我们来跑一跑官方的hello world程序 #运行官方的hello-world镜像,顺带可以检查一下安装配置是否有问题。 docker run hello-world 出来这个就基本差不多了。 centos: windows: 在接下来跑一个我们自己的。net core 版本的hello world vs2017 preview装起来先,然后创建一个控制台项目.项目右键 add ,选择docker support。添加项目对docker的支持 完事之后大约就这样子 为了发布方便,我们吧项目改一下名字,czd890 是我在docker 注册的账号。所以我所有发布的项目都发布到自己的命名空间下。 然后,我们在main方法里面写上我们的hello world static void Main(string[] args) { Console.WriteLine("Hello World!"); Console.WriteLine("in docker -- private registry"); } 在然后,选择release模式,rebuild我们的项目 就出来 我们来跑一跑我们自己的 hello world。 --rm参数表示run完了之后自动参数容器。这样子,我们docker ps -a 显示所有容器的时候,就不会看到一个超长列表了...... 在在然后,我们吧我们的镜像发布到官方 首先,我们需要登录我们的账号: 在接着,发布:  最后,在我们的centos中来跑一下我们刚才发布的czd890/hello-world镜像
centos安装方式,采用阿里云的镜像和安装脚本 或者到https://store.docker.com/search?type=edition&offering=community下载相应系统的安装包安装 #安装docker客户端 curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh - #配置加速镜像 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["<https://我的阿里云专业加速地址.mirror.aliyuncs.com>"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker windows安装 https://store.docker.com/editions/community/docker-ce-desktop-windows 安装包下载,双击安装就完事了。 windows设置镜像地址
node_modules/html-webpack-plugin/index.js 搜索 postProcessHtml 修改代码增加如下: if (assetTags && assetTags.body && assetTags.body.length) { for (var index = 0; index < assetTags.body.length; index++) { var element = assetTags.body[index]; if (element && element.attributes && element.attributes.src === '/app.js') element.attributes.src = '/app.js?now=' + Date.now() } } 这么做是为什么呢? 手机端webview调试,发现手机端对资源做了缓存。导致每次都要app清理缓存才能加载新的js。所以服务端来做这个事情。只要每次重启服务端就好拉。
node es6 变相实现支持ts的剩余参数实现方式 //.ts method assign(to: any, options?: AssignOptions, ...forms: any[]){} //tsc 编译为es6的js assign(to, options, ...forms) { } 如上的代码在node run起来之后报错。语法解析错误。不支持...forms 实现方式:使用函数的重载方式 assign(to: any, options?: AssignOptions, ...forms: any[]); assign() { //your code } //tsc 编译后的es6代码 assign() { //your code }
node下载地址https://nodejs.org/en/download/ # wget https://nodejs.org/dist/v6.10.2/node-v6.10.2.tar.gz 1.安装gcc # yum install gcc-c++ 2.安装libssl-dev # yum install openssl-dev 3.确保安装了python,大部分安装失败都是由于python版本过低导致。安装之前,升级python版本。 #nodejs 0.8.5需要,请安装python前,先安装此模块。 yum install-ybzip2* tar zxvf node-v0.9.0.tar.gz # ./configure --prefix=/usr/local/node #这里安装在了/usr/local/node目录下 # make # make install l# n -s /usr/local/bin/node /usr/bin/node 3).配置NODE_HOME # vi /etc/profile #在export PATH USER 。。。一行的上面添加如下内容,并将NODE_HOME/bin设置到系统path中 #set for nodejs export NODE_HOME=/usr/local/node export PATH=$NODE_HOME/bin:$PATH #保存退出后执行如下命令,使刚才的配置生效 source /etc/profile #执行node -h命令验证设置成功 node -h node,npm 到次安装完成
1.安装jdk。jenkins 是一个java web程序。所以必然需要jdk。 yum install java 或者 yum install java-1.8.0-openjdk 2.下载jenkins安装包 # wget -P /opt https://pkg.jenkins.io/redhat-stable/jenkins-2.7.4-1.1.noarch.rpm 下载安装包到/opt目录 安装包下载页面:https://jenkins.io/download/ centos安装包页面:https://pkg.jenkins.io/redhat-stable/ 3.安装jenkins # cd /opt # rpm -ivh jenkins-2.7.4-1.1.noarch.rpm 4.启动初始化jenkins 4.1.# service jenkins start //启动jenkins服务 4.2.默认jenkins管理页面是ip:8080. 4.3.根据提示到指定目录 # vi initialAdminPassword.拿到一串密码 4.4.安装插件,选择推荐安装。 4.5.填写admin账号密码 jenkins到目前就算安装完成了。 1.新建项目 2.项目的配置 dotnet restore,publish中间遇到的几个坑 1.dotnet 找不到命令 dotnet command not found 解决办法:系统管理配置环境变量 # echo $PATH //查看系统的path变量 2.构建提示 bower command not found # npm install bower -g //安装bower组件 pwd ls echo $PATH whoami which dotnet dotnet --info dotnet --version echo '============================begin restore=======================================' dotnet restore echo '============================cd web app=======================================' cd ./src/NetCoreWebApp echo '============================begin build=======================================' #dotnet build -c:Release --no-incremental rm -rf $WORKSPACE/jenkins_publish mkdir $WORKSPACE/jenkins_publish dotnet publish -r centos.7-x64 -c:Release -o $WORKSPACE/jenkins_publish ### 此处只是简单的测试jenkins构建。所以构建后的产物只是简单的发布到本地机器。仅做demo演示 echo '============================产物发布到服务器=======================================' rm -rf /salesystem/NetCoreWebApp mkdir /salesystem/NetCoreWebApp cp -r $WORKSPACE/jenkins_publish/* /salesystem/NetCoreWebApp/ 成果 构建使用参数指定分支 完整的构建输出日志。构建脚本来自上面的shell 构建之后 copy到运行目录,run起来
因为各种原因,需要查看asp.net core mvc的源代码来理解运行机制等等,虽说源代码查看已经能很好的理解了。但是能够直接调试还是最直观的。所有就有了本次尝试。 因调试设置源代码调试太辍笔,所以不用这个方法,转而使用编译源代码的方式,当然也能在源代码里面加点log能更好的理解和调试 源代码准备及调试程序准备 1.从https://github.com/aspnet上clone下来mvc及相关项目的源代码,准备稍后的编译。 2.新建一个asp.net core mvc 项目,写上一些基本代码。这个就随意了了,本次尝试使用的是我自己的一个项目代码,就不贴图了。 源代码的编译 当前所编译的3个工程:mvc,routing,security。全家福。 编译之前,一定要根据调试项目所引用的package版本来。我项目引用的mvc版本是1.1.2,routing和security版本是1.1.1 所有git把分支checkout到对应版本上进行编译 security项目的编译 编译之后的packages包 本地nuget服务器准备 本地nuget服务器准备就比较简单了,网上一搜一大包,新建一个web 空工程,nuget引用nuget.server 包。然后发布到iis就完事了。 本地nuget包发布 如图上问题所示,该删除的删除就好了 调试项目引用本地nuget服务的包 成果展示 当前的断点在Microsoft.AspNetCore.Authorization.DefaultAuthorizationService.DefaultAuthorizationService 调用堆栈上能很明显的看出来 mvc,routing等也可以源码调试了。
传送门:http://visjs.org/ demo代码 <!doctype html> <html> <head> <title>vis.js newwork Demo</title> <script src="http://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script src="../vis.js"></script> <link href="../vis.css" rel="stylesheet" type="text/css" /> <style type="text/css"> #mynetwork { width: 100%; height: 600px; border: 1px solid lightgray; } #hisLog { width: 100%; height: 200px; border: 1px solid red; } </style> </head> <body> <div id="mynetwork"></div> <button id='addTo' value="Begin AddTo">Begin AddTo</button> <button id='stop_addTo' value="Stop AddTo">Stop AddTo</button> <button id='add_edge'>Begin Add Edge</button> <button id='stop_edge'>Stop Add Edge</button> <div id="hisLog"></div> <script src="./demo.js"></script> </body> </html> var nodes = new vis.DataSet(); var edges = new vis.DataSet(); var container = document.getElementById('mynetwork'); var data = { nodes: nodes, edges: edges }; var options = {}; var network = new vis.Network(container, data, options); function addNode(id, label, title) { nodes.add({ id: id, label: id }) this.addHisLog('node:' + id + ' add to container.'); } function addEdge(fromId, toId, type) { var edge = { from: fromId, to: toId, } if (type === 1) { edge['label'] = 'releation' edge.arrows = 'to' edge.color = 'red' edge.length = 400 } else { edge['label'] = 'arrows:circle' edge.arrows = { to: { type: 'circle' } } edge.length = 200 } edges.add(edge); this.addHisLog('edge:' + fromId + ' ---> ' + toId + ' .type:' + type + ' add to container.'); } function randomGetNodeId() { var names = Object.getOwnPropertyNames(nodes._data); var len = names.length; var index = Math.floor(Math.random() * len); return names[index]; } function randomAddNode() { var type = 0 if (Math.random() > 0.7) type = 1 var id = Date.now(); var fId = this.randomGetNodeId() this.addNode(id, id, null) this.addEdge(fId, id, type) } function randomAddEdge() { var fId = this.randomGetNodeId() var tId = this.randomGetNodeId() if (fId == tId) return; var type = 0 if (Math.random() > 0.7) type = 1 this.addEdge(fId, tId, type) } function addHisLog(message) { $('#hisLog').prepend('<div>' + message + '</div>') $('#hisLog div').remove('div:gt(8)') } network.on("click", function(params) { // randomAddNode() // if (params.nodes.length == 0) // return; // var names = Object.getOwnPropertyNames(nodes._data); // var len = names.length; // var index = Math.floor(Math.random() * len); // var _edgeId = names[index] // var id = Date.now(); // nodes.add({ // id: id, // label: id // }) // var edge = { // from: params.nodes[0], // to: id, // } // if (Math.random() > 0.5) { // edge['label'] = 'releation' // edge.arrows = 'to' // edge.color = 'red' // } else { // edge['label'] = '父子' // edge.arrows = { // to: { // type: 'circle' // } // } // } // edges.add(edge); }); $('#addTo').click(function() { _setIntervalId = setInterval(randomAddNode, 400) }) $('#stop_addTo').click(function() { clearInterval(_setIntervalId) }) $('#add_edge').click(function() { _setIntervalId2 = setInterval(randomAddEdge, 400) }) $('#stop_edge').click(function() { clearInterval(_setIntervalId2) })
从这里进入官网. 能找到这个NB的编辑器是因为公司项目需要一个可视化的cms编辑器,类似微信公众号编辑文章。可以插入各种卡片,模块,问题,图片等等。然后插入的内容还需要能删除,拖拽等等。所以采用vue开发,兼容vue并兼容拖拽的文本编辑器并不多,所以在github上一番搜索找到了quill这款文本编辑器神器。 先从官方例子里面扒一个图瞅瞅: PS:和大多数文本编辑器长得都差不多,如果功能都一样,那也不用介绍了。 他NB,强大的地方就是所有能看到的,不能看到的功能统统都是一个一个独立的模块。全部都是可以替换的。不得不对这段文字进行重点标记。当然其他编辑器的一些几本功能也统统都有且不仅如此。比如文本的样式,多媒体文件的上传,响应键盘事件,操作历史,公式支持等等。点击查看详情. 各种自定义的使用说明 比如上图中的菜单栏可以自定义,对已有的菜单栏定义:继续从官方例子里面扒图: 当然,如果插件自带的功能没有,比如你要做一个动画在菜单栏上加一个图标、选项或者什么的。可以对整个菜单栏进行定义和重写 下面从项目中的扩展点找2个说明一下这个NB的编辑器,当然他的更多可扩展功能也没有用上,所以只有看到的官方文档,才能理解他的可扩展性和灵活性。 修改字体大小选择,使用自定义的列表和单位(rem) 自带的字体大小编辑有2个如下。但是显然不太能支持我们的用法。一开始吧size扩展成了px。但是后来经过测试发现手机端使用的是rem,so。最后改成使用rem。 [{ 'size': ['small', false, 'large', 'huge'] }] [{ 'header': [1, 2, 3, 4, 5, 6, false] }], //扩展后的字体选择 [{ // 'size': ['10px', '12px', '14px', '16px', '18px', '20px'] //1/75 *2 //1px =0.026rem //1rem=36px 'size': ['0.26rem', '0.31rem', '0.37rem', '0.41rem', '0.47rem', '0.52rem'] }] 为了在菜单栏中显示对应的字体大小。加入css。差不多长这样,有多少个选项,就加多少个。 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="10px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="10px"]::before { content: '10px'; font-size: 10px; } .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before { content: '20px'; font-size: 20px; } //默认的样式 .ql-snow .ql-picker.ql-size .ql-picker-label::before, .ql-snow .ql-picker.ql-size .ql-picker-item::before { content: '14px'; font-size: 14px; } //rem:需要说明一下,在编辑的时候还是显示px单位,但最终生成的源代码使用rem,因为编辑是在pc上,并且运营人员也只熟悉px这个单位,对rem没有概念。 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="0.26rem"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="0.26rem"]::before { content: '10px'; font-size: 10px; } 在然后在初始化quill的地方加上下面的js代码 import Quill from 'quill' var Size = Quill.import('attributors/style/size'); // Size.whitelist = ['10px', '12px', '14px', '16px', '18px', '20px']; Size.whitelist = ['0.26rem', '0.31rem', '0.37rem', '0.41rem', '0.47rem', '0.52rem']; Quill.register(Size, true); 如此之后,对我们字体大小的选择就算扩展完毕了,让我们检验一下成果: 当然为了在pc上rem字体能生效,还必须得加上一行。 html { font-size: 36px; } 扩展居中,靠右使用样式,而不是class方式 值得说明的是,样式的设置等,几本都有多套策略可以选择。举个栗子,官方源代码。 这是官方的字体方向设置的源代码。我们可以看到他就有3种方式设置:通过attribute(algin:'right'),通过class(class='ql-align-right'),通过style(style='text-align:right');是不是很灵活,很强大,任君选择有木有 import Parchment from 'parchment'; let config = { scope: Parchment.Scope.BLOCK, whitelist: ['right', 'center', 'justify'] }; let AlignAttribute = new Parchment.Attributor.Attribute('align', 'align', config); let AlignClass = new Parchment.Attributor.Class('align', 'ql-align', config); let AlignStyle = new Parchment.Attributor.Style('align', 'text-align', config); export { AlignAttribute, AlignClass, AlignStyle }; 那如何指定使用其他的一种呢?像下面的代码一样,如果使用style。则使用 Quill.import('attributors/style/align');替换默认的,如果使用class:则使用 Quill.import('attributors/class/align'); var Align = Quill.import('attributors/style/align'); Align.whitelist = ['right', 'center', 'justify']; Quill.register(Align, true); 检验一下成果: 然后在来一个高级一点的。设置字体为粗体 quill默认使用的是strong或者b标签方式。其实这也是没有问题的,但是架不住公司的"高级"前端对手机端的所有html标签都reset了。所有的hx标签,em,strong等语义标签全部reset了。所以没办法只能使用style的方式来实现。 import Inline from '../blots/inline'; class Bold extends Inline { static create() { return super.create(); } static formats() { return true; } optimize() { super.optimize(); if (this.domNode.tagName !== this.statics.tagName[0]) { this.replaceWith(this.statics.blotName); } } } Bold.blotName = 'bold'; Bold.tagName = ['STRONG', 'B']; export default Bold; 使用style来实现文字的加粗 import Quill from 'quill' let Parchment = Quill.import('parchment') class BoldStyleAttributor extends Parchment.Attributor.Style { value(domNode) { let value = super.value(domNode); return value; } add(node, value) { $(node).css('font-weight', 'bold'); return true; } remove(node) { $(node).css('font-weight', 'normal'); } } let BoldStyle = new BoldStyleAttributor('bold', 'font-weight', { scope: Parchment.Scope.INLINE, whitelist: [true, false] }); export default BoldStyle;` 初始化quill的地方加上下面的代码 ./NodeEditText/TextBold”或者“./NodeEditText/TextBold.js”就是上面几行代码的js文件路径。 import MyBold from './NodeEditText/TextBold' Quill.register("formats/bold", MyBold, true); 检验一下成果: 诸如文字的字体啦,斜体啦,都类似写法。就不一一展开了。官方文档虽然是英文的,但是耐着性子看,还是能比较方便看懂的, 写在最后: 能够快速的自定义这个组件的前提是需要懂他的设计思想,我也只是粗浅的了解使用了一下这个组件,就不做什么总结了 回答一下 @48詹泽娟 的问题,集成到vue,大约是这样子. <template> <div id="quillWrapper"> <div ref="quillContainer" :id="$data.elmId" class="quill-container"></div> <button v-if="useSaveButton" class="save-button" @click="saveContent"> {{ buttonText ? buttonText : 'Save Content' }} </button> <div v-if="showPreview" ref="livePreview" class="ql-editor"></div> </div> </template> <script> import Quill from 'quill' import Parchment from 'parchment'; import MyBold from './NodeEditText/TextBold' import MyItalic from './NodeEditText/TextItalic' var defaultToolbar = [ ['bold', 'italic'], [{ 'color': [] }], [{ // 'size': ['10px', '12px', '14px', '16px', '18px', '20px'] //1/75 *2 //1px =0.026rem //1rem=36px 'size': ['0.26rem', '0.31rem', '0.37rem', '0.41rem', '0.47rem', '0.52rem'] }], [{ 'align': [] }], ['clean'], ]; export default { name: 'VueEditor', props: { editorContent: String, placeholder: String, buttonText: String, useSaveButton: { type: Boolean, default () { return true } }, showPreview: { type: Boolean, default () { return false } } }, data: function() { return { quill: null, editor: null, toolbar: defaultToolbar, elmId: 'quill-container' + (new Date()).getTime() } }, mounted: function() { const vm = this var Size = Quill.import('attributors/style/size'); // Size.whitelist = ['10px', '12px', '14px', '16px', '18px', '20px']; Size.whitelist = ['0.26rem', '0.31rem', '0.37rem', '0.41rem', '0.47rem', '0.52rem']; Quill.register(Size, true); var Align = Quill.import('attributors/style/align'); Align.whitelist = ['right', 'center', 'justify']; Quill.register(Align, true); // Quill.register(MyBold, true); Quill.register("formats/bold", MyBold, true); Quill.register("formats/italic", MyItalic, true); vm.quill = new Quill(vm.$refs.quillContainer, { modules: { toolbar: { 'container': this.toolbar, } }, placeholder: this.placeholder ? this.placeholder : '', theme: 'snow' }); vm.editor = $(this.$el).find('.ql-editor')[0]; vm.editor.innerHTML = this.editorContent; if (vm.$refs.livePreview !== undefined || false) { vm.quill.on('text-change', function() { vm.$refs.livePreview.innerHTML = vm.editor.innerHTML vm.$emit('editor-updated', vm.editor.innerHTML) }); } else { vm.quill.on('text-change', function() { vm.$emit('editor-updated', vm.editor.innerHTML) }); } var replaceReg = /<(\S*?) [^>]*>.*?<\/\1>|<.*?\/>/gm; $(vm.editor).on('paste', function(e) { var text = null; if (window.clipboardData && clipboardData.setData) { // IE text = window.clipboardData.getData('text'); } else { text = (e.originalEvent || e).clipboardData.getData('text/plain') || prompt('在这里输入文本'); } console.log(text); if (document.body.createTextRange) { if (document.selection) { textRange = document.selection.createRange(); } else if (window.getSelection) { sel = window.getSelection(); var range = sel.getRangeAt(0); // 创建临时元素,使得TextRange可以移动到正确的位置 var tempEl = document.createElement("span"); tempEl.innerHTML = "&#FEFF;"; range.deleteContents(); range.insertNode(tempEl); textRange = document.body.createTextRange(); textRange.moveToElementText(tempEl); tempEl.parentNode.removeChild(tempEl); } textRange.text = text; textRange.collapse(false); textRange.select(); } else { // Chrome之类浏览器 document.execCommand("insertText", false, text); } e.preventDefault(); console.log('paste:' + text); }); try { document.execCommand("AutoUrlDetect", false, false); } catch (e) {} }, watch: { editorContent: function() { if (this.editor.innerHTML != this.editorContent) { console.log('set inner html'); this.editor.innerHTML = this.editorContent; } } }, methods: { saveContent: function(value) { this.$emit('save-content', this.editor.innerHTML) } } } </script>
vs2017 rc 离线安装包制作 1.下载在线安装包:https://aka.ms/vs/15/release/vs_Enterprise.exe 2.制作离线安装包: vs_Enterprise.exe --layout D:\downloads\vs2017rc --lang zh-CN 3.等待下载完成 4.进入下载目录的/Certificates 文件夹 5.安装每一个证书 6.运行下载目录下的vs_Enterprise.exe ps: 更新离线安装包。将步骤2重复即可 参考地址
.net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活controller并调用action完成mvc的处理流程。下面我们看看服务器是如何调用route的。 core mvc startup基本代码。重点在AddMvc和UseMvc public class Startup { public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } AddMvc:把各种service加入IOC容器。比如格式化提供程序,action定位器,controllerFactory,controller激活器等等,一应服务全部在这里加入。 UseMvc:最重要的一行代码:builder.UseMiddleware(router); 看到这行代码就清楚的知道route 这个handler 在这里加入到请求委托链拉 public static IMvcBuilder AddMvc(this IServiceCollection services) { var builder = services.AddMvcCore(); builder.AddJsonFormatters(); builder.AddCors(); return new MvcBuilder(builder.Services, builder.PartManager); } public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services, Action<MvcOptions> setupAction) { var builder = services.AddMvcCore(); services.Configure(setupAction); return builder; } internal static void AddMvcCoreServices(IServiceCollection services) { services.TryAddSingleton<IActionSelector, ActionSelector>(); services.TryAddSingleton<ActionConstraintCache>(); services.TryAddSingleton<IActionSelectorDecisionTreeProvider, ActionSelectorDecisionTreeProvider>(); // This has a cache, so it needs to be a singleton services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>(); // Will be cached by the DefaultControllerFactory services.TryAddTransient<IControllerActivator, DefaultControllerActivator>(); services.TryAddEnumerable(ServiceDescriptor.Transient<IControllerPropertyActivator, DefaultControllerPropertyActivator>()); // Route Handlers services.TryAddSingleton<MvcRouteHandler>(); // Only one per app services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app } public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes) { var routes = new RouteBuilder(app) { DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(), }; configureRoutes(routes); routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); return app.UseRouter(routes.Build()); } public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router) { return builder.UseMiddleware<RouterMiddleware>(router); } 如此,mvc的入口route handler就加入了我们的请求委托链中。后续服务器接收到的请求就能交由route匹配,查找action,激活action处理。 router middleware的激活调用 middleware 请求调用委托链的激活调用请看这篇文章 //middleware加入_components请求处理委托链 public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _components.Add(middleware); return this; } public static class UseMiddlewareExtensions { private const string InvokeMethodName = "Invoke"; private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static); //注册middleware public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args) { var applicationServices = app.ApplicationServices; //将middleware 加入请求处理委托链 return app.Use(next => { //解析方法和参数。查找类的Invoke方法作为入口方法。所以middleware只要是个class就行。只要有一个功公共的Invoke方法即可。 var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)).ToArray(); var methodinfo = invokeMethods[0]; var parameters = methodinfo.GetParameters(); var ctorArgs = new object[args.Length + 1]; ctorArgs[0] = next; Array.Copy(args, 0, ctorArgs, 1, args.Length); //创建middleware的实例。并且通过构造函数注入相关的service var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs); //如果方法只有一个参数,默认它就是httpcontext。 if (parameters.Length == 1) { return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance); } //多余一个参数的则构建一个func。并从ioc容器解析参数注入 var factory = Compile<object>(methodinfo, parameters); return context => { var serviceProvider = context.RequestServices ?? applicationServices; return factory(instance, context, serviceProvider); }; }); } //代码中的创建实例注入service,创建有多个参数的invoke方法注入service具体代码就不贴上来了,占地方。 //构造函数就是匹配最适合的构造函数,然后从IServiceProvider get实例,注入。 //多个参数的invoke就更简单了。直接从IServiceProvider get实例注入。 上述源代码git地址,aspnet/HttpAbstractions项目 route handler middleware代码 public class RouterMiddleware { private readonly ILogger _logger; private readonly RequestDelegate _next; private readonly IRouter _router; //创建middleware的实例。并且通过构造函数注入相关的service public RouterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IRouter router) { _next = next; _router = router; _logger = loggerFactory.CreateLogger<RouterMiddleware>(); } //被调用的方法。从这里开始进入mvc route。 public async Task Invoke(HttpContext httpContext) { //此处的 IRouter router对象。是我们在Startup中routes.MapRoute...配置的route集合对象:RouteCollection。当然也还有比如attributeroute等等好几种route。 var context = new RouteContext(httpContext); context.RouteData.Routers.Add(_router); await _router.RouteAsync(context); if (context.Handler == null) { //没有匹配到route的情况 _logger.RequestDidNotMatchRoutes(); await _next.Invoke(httpContext); } else { httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature() { RouteData = context.RouteData, }; //匹配到路由处理 await context.Handler(context.HttpContext); } } } //Microsoft.AspNetCore.Routing.RouteCollection public async virtual Task RouteAsync(RouteContext context) { // Perf: We want to avoid allocating a new RouteData for each route we need to process. // We can do this by snapshotting the state at the beginning and then restoring it // for each router we execute. var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null); for (var i = 0; i < Count; i++) { var route = this[i]; context.RouteData.Routers.Add(route); try { //循环所有routes规则,逐一匹配,匹配到一个自然就break。 await route.RouteAsync(context); if (context.Handler != null) break; } finally { if (context.Handler == null) snapshot.Restore(); } } } UseMvc中有一行非常重要的代码。给RouteBuilder的DefaultHandler赋值一个handler。记住这行代码,我们继续往下看。 public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes) { var routes = new RouteBuilder(app) { DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(), }; } //我们在Startup中routes.MapRoute的所有调用最终调用方法都是这个。new Route( routeBuilder.DefaultHandler,....) //全部都指定了_target为routeBuilder.DefaultHandler public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, string name, string template, object defaults, object constraints, object dataTokens) { if (routeBuilder.DefaultHandler == null) throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder))); var inlineConstraintResolver = routeBuilder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>(); routeBuilder.Routes.Add(new Route( routeBuilder.DefaultHandler, name, template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), inlineConstraintResolver)); return routeBuilder; } 到这里,我们的逻辑有点绕了,让我们理理清楚: 1.请求进到RouterMiddleware.Invoke()方法 2.调用RouteCollection.RouteAsync()方法,RouteCollection.RouteAsync方法中循环注册的每一个route对象。 并调用route对象的RouteAsync()方法(route对象的RouteAsync方法在它的父类中Microsoft.AspNetCore.Routing.RouteBase)。 这里说的route对象即时Startup中routes.MapRoute生成的route对象(Microsoft.AspNetCore.Routing.Route)。Route继承RouteBase,RouteBase实现IRouter接口 3.RouteBase.RouteAsync()判断当前请求是否符合当前route规则,如果匹配的话,则调用抽象方法OnRouteMatched 4.RouteBase的抽象方法OnRouteMatched,又回到Route对象的OnRouteMatched方法中。调用_target.RouteAsync();_target对象即上面代码中的routeBuilder.DefaultHandler。 5.来到Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler.RouteAsync()方法中。最重要的一行代码: context.Handler =.... 6.调用堆栈最终返回到1中(RouterMiddleware.Invoke())。判断context.Handler == null。为null没找到route匹配的action。不为null则await context.Handler(context.HttpContext) 7.context.Handler即为5中赋值的func。即下面的代码,定位action,调用action。 //Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler.RouteAsync public Task RouteAsync(RouteContext context) { var candidates = _actionSelector.SelectCandidates(context); if (candidates == null || candidates.Count == 0) { _logger.NoActionsMatched(); return TaskCache.CompletedTask; } var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates); if (actionDescriptor == null) { _logger.NoActionsMatched(); return TaskCache.CompletedTask; } context.Handler = (c) => { var routeData = c.GetRouteData(); var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); if (_actionContextAccessor != null) _actionContextAccessor.ActionContext = actionContext; var invoker = _actionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(actionDescriptor.DisplayName)); return invoker.InvokeAsync(); }; return TaskCache.CompletedTask; } 至此,route的处理流程大约交代清楚了。包括route的注册,route的激活,route的选择等。
上篇讲到.net core web app是如何启动并接受请求的,下面接着探索kestrel server是如何完成此任务的。 1.kestrel server的入口KestrelServer.Start(Microsoft.AspNetCore.Hosting.Server.IHttpApplication) FrameFactory创建的frame实例最终会交给libuv的loop回调接收请求。但是在这过程中还是有很多的初始化工作需要做的。后面我们就管中窥豹来看一看。 public void Start<TContext>(IHttpApplication<TContext> application) { var engine = new KestrelEngine(new ServiceContext { FrameFactory = context => { return new Frame<TContext>(application, context); }, AppLifetime = _applicationLifetime, Log = trace, ThreadPool = new LoggingThreadPool(trace), DateHeaderValueManager = dateHeaderValueManager, ServerOptions = Options }); //启动引擎。完成libuv的配置和启动 engine.Start(threadCount); //针对绑定的多个地址创建server来接收请求。也就是针对ip:port来启动tcp监听 foreach (var address in _serverAddresses.Addresses.ToArray()) { engine.CreateServer(ipv4Address); } } 2.启动kestrel engine。engine.Start(threadCount); 启动绑定的端口*最大处理线程的thread。并初始化libuv组件。 每一个线程初始化libuv,注册loop回调等,并启动libuv。 public void Start(int count) { for (var index = 0; index < count; index++) { Threads.Add(new KestrelThread(this)); } foreach (var thread in Threads) { thread.StartAsync().Wait(); } } private void ThreadStart(object parameter) { lock (_startSync) { var tcs = (TaskCompletionSource<int>) parameter; try { //初始化loop _loop.Init(_engine.Libuv); //注册loop回调 //EnqueueCloseHandle:持有的资源释放后的回调方法,回调往queue内增加一个item,事件循环该queue完成资源的最终释放 _post.Init(_loop, OnPost, EnqueueCloseHandle); //注册心跳定时器 _heartbeatTimer.Init(_loop, EnqueueCloseHandle); //启动心跳定时器 _heartbeatTimer.Start(OnHeartbeat, timeout: HeartbeatMilliseconds, repeat: HeartbeatMilliseconds); _initCompleted = true; tcs.SetResult(0); } catch (Exception ex) { tcs.SetException(ex); return; } } try { //当前线程执行到Run()这里会挂起 _loop.Run(); //应用程序stop,shutdown之类的情况,libuv唤醒当前线程,完成资源清理 if (_stopImmediate) { // thread-abort form of exit, resources will be leaked //线程中止形式的退出,资源会被泄露。 return; } // run the loop one more time to delete the open handles //再次运行循环以删除打开的句柄 _post.Reference(); _post.Dispose(); _heartbeatTimer.Dispose(); // Ensure the Dispose operations complete in the event loop. //确保事件循环中的Dispose操作完成。 _loop.Run(); _loop.Dispose(); } catch (Exception ex) { _closeError = ExceptionDispatchInfo.Capture(ex); // Request shutdown so we can rethrow this exception // in Stop which should be observable. //请求关闭,以便我们可以重新抛出此异常在停止应该是可观察的。 _appLifetime.StopApplication(); } finally { _threadTcs.SetResult(null); } } 3.libuv启动完成之后,接着就是处理订阅注册tcp了。 回到1的kestrel的start中。接着执行engine.CreateServer(ipv4Address);,这里和.net 里面的tcplistener不太一样。.net里面就是listener bind,start,accept就好了。而libuv涉及到一个多路io复用的概念,这也是为什么使用他能高并发的原因。 public IDisposable CreateServer(ServerAddress address) { var usingPipes = address.IsUnixPipe; var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var single = Threads.Count == 1; var first = true; foreach (var thread in Threads) { if(single){}//single就不考虑,这种情况真是环境是不会这样玩的 else if (first) { //根据当前平台创建tcp listener var listener = usingPipes ? (ListenerPrimary)new PipeListenerPrimary(ServiceContext) : new TcpListenerPrimary(ServiceContext); listener.StartAsync(pipeName, address, thread).Wait(); } else { //如果是多次对同一个ip:port做监听 var listener = usingPipes ? (ListenerSecondary)new PipeListenerSecondary(ServiceContext) : new TcpListenerSecondary(ServiceContext); listener.StartAsync(pipeName, address, thread).Wait(); } first = false; } } tcplistener启动细节,这里就只看TcpListenerPrimary了。 首先说明一下TcpListenerPrimary这个类的继承关系:TcpListenerPrimary -->ListenerPrimary -->Listener。这样才有助于后续代码的理解。 后续代码到处都能看到thread.post/postaysnc的代码。这玩意的意思是把传入的action放到libuv loop中,并激活异步完成回调。libuv另一个重要的概念各种回调。 1.接着上面的代码,我们进入TcpListenerPrimary.StartAsync()方法。方法在ListenerPrimary中。 public async Task StartAsync(string pipeName, ServerAddress address, KestrelThread thread) { _pipeName = pipeName; await StartAsync(address, thread).ConfigureAwait(false); await Thread.PostAsync(state => ((ListenerPrimary)state).PostCallback(), this).ConfigureAwait(false); } 2.接着上面的代码进入StartAsync(address, thread)。他是父类Listener的方法。 public Task StartAsync(ServerAddress address, KestrelThread thread) { ServerAddress = address; Thread = thread; var tcs = new TaskCompletionSource<int>(this); Thread.Post(state => { var tcs2 = (TaskCompletionSource<int>)state; var listener = ((Listener)tcs2.Task.AsyncState); //创建socket listener.ListenSocket = listener.CreateListenSocket(); ////socket监听,libu注册监听并设置回调函数,最大队列。 ListenSocket.Listen(Constants.ListenBacklog, ConnectionCallback, this); tcs2.SetResult(0); }, tcs); return tcs.Task; } protected override UvStreamHandle CreateListenSocket() { //初始化socket并bind到address var socket = new UvTcpHandle(Log); socket.Init(Thread.Loop, Thread.QueueCloseHandle); //是否使用Nagle's algorithm算法。 socket.NoDelay(ServerOptions.NoDelay); socket.Bind(ServerAddress); // If requested port was "0", replace with assigned dynamic port. ServerAddress.Port = socket.GetSockIPEndPoint().Port; return socket; } 在接着上面的代码ListenSocket.Listen成功之后,libuv回调ConnectionCallback函数。 进入ConnectionCallback函数,完成重要的listen Accept. step1:listen成功libuv回调ConnectionCallback方法。 step2:初始化接收请求socket,并将之关联到监听socket step3:适配接收请求socket,如果是第一次适配的话则创建connection step4:创建connection并启动 step5:new connection 关联 Frame对象。 step6:启动frame step7:由Connection类调用一次以开始RequestProcessingAsync循环。 step8:循环接收请求,接收请求到之后交给上层程序处理 private static void ConnectionCallback(UvStreamHandle stream, int status, Exception error, object state) { var listener = (Listener)state; listener.OnConnection(stream, status);//step 1 } protected override void OnConnection(UvStreamHandle listenSocket, int status)//step 2 { var acceptSocket = new UvTcpHandle(Log); acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); acceptSocket.NoDelay(ServerOptions.NoDelay); listenSocket.Accept(acceptSocket); DispatchConnection(acceptSocket); } protected override void DispatchConnection(UvStreamHandle socket)// step 3 { var index = _dispatchIndex++ % (_dispatchPipes.Count + 1); if (index == _dispatchPipes.Count) { base.DispatchConnection(socket); } else { DetachFromIOCP(socket); var dispatchPipe = _dispatchPipes[index]; var write = new UvWriteReq(Log); write.Init(Thread.Loop); write.Write2(dispatchPipe, _dummyMessage, socket, (write2, status, error, state) => { write2.Dispose(); ((UvStreamHandle)state).Dispose(); }, socket); } } protected virtual void DispatchConnection(UvStreamHandle socket)//step 4 { var connection = new Connection(this, socket); connection.Start(); } private Func<ConnectionContext, Frame> FrameFactory => ListenerContext.ServiceContext.FrameFactory; public Connection(ListenerContext context, UvStreamHandle socket) : base(context)//step 5 { SocketInput = new SocketInput(Thread.Memory, ThreadPool, _bufferSizeControl); SocketOutput = new SocketOutput(Thread, _socket, this, ConnectionId, Log, ThreadPool); //重点代码在这里,FrameFactory是一个委托,是KestrelServer.Start中注册的action _frame = FrameFactory(this); } public void Start()//step 6 { Log.ConnectionStart(ConnectionId); // Start socket prior to applying the ConnectionFilter _socket.ReadStart(_allocCallback, _readCallback, this); _frame.Start(); } /// <summary> /// Called once by Connection class to begin the RequestProcessingAsync loop. /// </summary> public void Start()//step 7 { Reset(); _requestProcessingTask = Task.Factory.StartNew( (o) => ((Frame)o).RequestProcessingAsync(), this, default(CancellationToken), TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap(); } /// <summary> /// 主循环消耗套接字输入,将其解析为协议帧,并调用应用程序委托,只要套接字打算保持打开。 /// 从此循环得到的任务将保留在服务器需要时使用的字段中以排除和关闭所有当前活动的连接。 /// </summary> public override async Task RequestProcessingAsync() { while (!_requestProcessingStopping) { InitializeHeaders(); var context = _application.CreateContext(this); await _application.ProcessRequestAsync(context).ConfigureAwait(false); } }
最近.net core 1.1也发布了,蹒跚学步的小孩又长高了一些,园子里大家也都非常积极的在学习,闲来无事,扒拔源码,涨涨见识。 先来见识一下web站点是如何启动的,如何接受请求,.net core web app最简单的例子,大约长这样 public static void Main(string[] args) { //dotnet NetCoreWebApp.dll --server.urls="http://localhost:5000/;http://localhost:5001/" var config = new ConfigurationBuilder().AddCommandLine(args).Build(); new WebHostBuilder() .UseConfiguration(config) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) //.UseIISIntegration() .UseStartup<Startup>() //.Configure(confApp => //{ // confApp.Run(context => // { // return context.Response.WriteAsync("hello"); // }); //}) .Build() .Run(); } WebHostBuilder看名字也知道是为了构建WebHost而存在的。在构建WebHost的路上他都做了这些:如加载配置,注册服务,配置功能等。 1.1 加载配置 builder内部维护了一个IConfiguration _config,可以简单的理解为key-value集合对象。可以通过UseSetting增加,也可以通过UseConfiguration增加 WebHostBuilder对UseStartup()的解析实现 我们从官方代码例子中能看到Startup类只是一个普通的类,builder是如何调用到这个类的方法的呢? Build方法关于这一块的代码大概如下: private IServiceCollection BuildHostingServices() { var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName); if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { services.AddSingleton(typeof(IStartup), startupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName); return new ConventionBasedStartup(methods); }); } } 能看出来其实Startup可以是一个实现了IStartup接口的类。为什么官方还需要搞一个普通类的方式呢?其实这里还有一个小技巧: 针对Configure和ConfigureServices方法我们还可以做的更多,那就是根据不同的environmentName调用不同的方法。 Configure方法可以是Configure+EnvironmentName,ConfigureServices则是Configure+EnvironmentName+Services。这样的话还能做到区分环境进去不同的配置。 下面代码展示了builder是如何选择这2个方法的 private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); return new ConfigureBuilder(configureMethod); } private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) { var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false) ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); return servicesMethod == null ? null : new ConfigureServicesBuilder(servicesMethod); } 1.2 Build() 根据之前use的各类配置,服务,参数等构建WebHost public IWebHost Build() { // Warn about deprecated environment variables if (Environment.GetEnvironmentVariable("Hosting:Environment") != null) { Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'"); } if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null) { Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'"); } if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null) { Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'"); } var hostingServices = BuildHostingServices(); var hostingContainer = hostingServices.BuildServiceProvider(); var host = new WebHost(hostingServices, hostingContainer, _options, _config); host.Initialize(); return host; } 2.1 构建WebHost 调用Initialize完成,host的初始化工作。Initialize 调用一次BuildApplication(); public void Initialize() { if (_application == null) { _application = BuildApplication(); } } private RequestDelegate BuildApplication() { //获取ServiceCollection中的IStartup,完成我们Startup.ConfigureService方法的调用,将我们代码注册的service加入到系统 EnsureApplicationServices(); //解析可以为urls或server.urls的value为绑定的address。以;分割的多个地址 //初始化UseKestrel(),UseIISIntegration()等指定的 实现了IServer接口的server EnsureServer(); var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>(); var builder = builderFactory.CreateBuilder(Server.Features); builder.ApplicationServices = _applicationServices; var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>(); Action<IApplicationBuilder> configure = _startup.Configure; foreach (var filter in startupFilters.Reverse()) { configure = filter.Configure(configure); } configure(builder); return builder.Build(); } 2.2 ApplicationBuilderFactory.Build(); 根据Server.Features build ApplicationBuilderFactory对象。 完成ApplicationBuilderFactory的build过程。 大致就是注册各类中间件_components(middleware),也就是说的这个 https://docs.asp.net/en/latest/fundamentals/middleware.html 借用官方的图说明一下什么是middleware。 public RequestDelegate Build() { RequestDelegate app = context => { context.Response.StatusCode = 404; return TaskCache.CompletedTask; }; foreach (var component in _components.Reverse()) { app = component(app); } return app; } 2.3 builder完成之后,接着执行Run方法启动web服务 启动host。host.Run();最终调用到WebHost.Start(),并调用当前app指定的Server对象启动web服务 public virtual void Start() { Initialize(); _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>(); var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticSource>(); var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>(); _logger.Starting(); Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory)); _applicationLifetime.NotifyStarted(); _logger.Started(); } 2.4 KestrelHttpServer的Start方法,启动对监听的监听接收请求 简化代码大约这样子 public void Start<TContext>(IHttpApplication<TContext> application) { var engine = new KestrelEngine(new ServiceContext { //接收到请求之后,回调FrameFactory方法,开始处理请求 FrameFactory = context => { return new Frame<TContext>(application, context); }, //启动完成,停止等通知事件 AppLifetime = _applicationLifetime, Log = trace, ThreadPool = new LoggingThreadPool(trace), DateHeaderValueManager = dateHeaderValueManager, ServerOptions = Options }); //启动工作线程 engine.Start(threadCount); foreach (var address in _serverAddresses.Addresses.ToArray()) { //判断ipv4,ipv6,localhosts得到监听的地址,并启动对该端口的监听,等待请求进来 engine.CreateServer(address) } } //engine.Start(threadCount); public void Start(int count) { for (var index = 0; index < count; index++) { Threads.Add(new KestrelThread(this)); } foreach (var thread in Threads) { thread.StartAsync().Wait(); } } engine.CreateServer(address) 先不说了,是tcpListener的一堆代码。看了代码感觉这里又是深不可测,先放着,有空了在撸这一部分。需要理解tcpListener为何如此设计,需要精读这部分代码 2.5 接收请求后的处理 listerner接到请求之后 实例化Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Connection,并调用该对象的Start() 接着由Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.Start() 异步启动task开始处理请求。 KestrelHttpServer处理请求:Frame.RequestProcessingAsync(); public override async Task RequestProcessingAsync() { var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this); _keepAlive = messageBody.RequestKeepAlive; _upgrade = messageBody.RequestUpgrade; InitializeStreams(messageBody); var context = _application.CreateContext(this); await _application.ProcessRequestAsync(context).ConfigureAwait(false); //经过一系列的检查,各种判断,请求终于由KestrelHttpServer交给了统一的Host VerifyResponseContentLength(); } 这里的application 就是Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));这里实例化的HostingApplication 也就是Microsoft.AspNetCore.Hosting.Internal下面的public class HostingApplication : IHttpApplication<HostingApplication.Context> 2.6 httpcontext的创建 _application.CreateContext(this); public Context CreateContext(IFeatureCollection contextFeatures) { var httpContext = _httpContextFactory.Create(contextFeatures); var diagnoticsEnabled = _diagnosticSource.IsEnabled("Microsoft.AspNetCore.Hosting.BeginRequest"); var startTimestamp = (diagnoticsEnabled || _logger.IsEnabled(LogLevel.Information)) ? Stopwatch.GetTimestamp() : 0; var scope = _logger.RequestScope(httpContext); _logger.RequestStarting(httpContext); if (diagnoticsEnabled) { _diagnosticSource.Write("Microsoft.AspNetCore.Hosting.BeginRequest", new { httpContext = httpContext, timestamp = startTimestamp }); } return new Context { HttpContext = httpContext, Scope = scope, StartTimestamp = startTimestamp, }; } 2.7 Host处理请求 ```C# public Task ProcessRequestAsync(Context context) { return _application(context.HttpContext); } ~~~ 这里的_application就是Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));中的_application,也就是BuildApplication()构建出来的RequestDelegate。开启mvc处理流程 3 mvc接受请求,开始处理流程 mvc大致调用顺序:Startup.Configure方法中 //1 app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); //2 public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> onfigureRoutes) { return app.UseRouter(routes.Build()); } //3 public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router) { if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatUnableToFindServices( nameof(IServiceCollection), nameof(RoutingServiceCollectionExtensions.AddRouting), "ConfigureServices(...)")); } //注册一个Middleware接收请求,开始处理.如2.2所展示的代码,RouterMiddleware将加入到_components,由2.7完成调用 return builder.UseMiddleware<RouterMiddleware>(router); } 至此,mvc框架才真正开始处理我们的web请求。host的配置,启动,监听,接受请求,转交给上层服务的大概脉络逻辑就说完了。
尝试使用markdown来写一篇blog,啦啦啦 源代码传送门:github 在特殊情况下我们使用jquery.validate.js对用户输入的内容做验证的时候,表单并不是一定包含在form之中,有可能是一个div弹层,有可能是嵌套在form里面的一个div,这个时候官方的validate就不能很好的支持了。对此,在官方的源代码基础上做小小的改动,就能兼容原有form方式,也能使用于非form方式,何乐而不为呢。 分析官方代码之后,能得知它都是通过element.form找到当前input的form,在从form标签获取validate对象。那只要我们修改为我们指定的容器标签即可。 修改代码如下: 1.增加一个获取容器的方法 getContainer: function (element) { var container = $(element).closest('.validateContainer')[0]; container = container || element.form; return container; } 2.替换所有使用element.form为$.validator.getContainer(element) demo: <div id="x" class="validateContainer"> <input data-rule-required="true" data-rule-number="true" data-rule-digits="true" acc="x" Acc2="xx" AcAc="3" acAc="4" name="xx" /> <input type="submit" value="submit" /> </div> <script src="jquery-3.1.0.js"></script> <script src="jquery.validate.js"></script> <script> $("#x").validate(); </script> 当然,兼容性等尚未进行测试,经供参考 ps:话说markdown还是蛮好用的
vs2015 update3 新建的xamarin.forms项目中的android项目编译错误。提示缺少android_m2repository_r22.zip,96659D653BDE0FAEDB818170891F2BB0.zip等类似错误。 Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.Design\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.Design\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v4\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v4' available in SDK installer. Java library file C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v4\23.0.1.3\embedded\classes.jar doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v4\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v4' available in SDK installer. Java library file C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v4\23.0.1.3\embedded\libs/internal_impl-23.0.1.jar doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v4\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v4' available in SDK installer. Android resource directory C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v4\23.0.1.3\embedded\./ doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.AppCompat\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v7.AppCompat' available in SDK installer. Java library file C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.AppCompat\23.0.1.3\embedded\classes.jar doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.AppCompat\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v7.AppCompat' available in SDK installer. Android resource directory C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.AppCompat\23.0.1.3\embedded\./ doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.CardView\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v7.CardView' available in SDK installer. Java library file C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.CardView\23.0.1.3\embedded\classes.jar doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.CardView\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v7.CardView' available in SDK installer. Android resource directory C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.CardView\23.0.1.3\embedded\./ doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.MediaRouter\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v7.MediaRouter' available in SDK installer. Java library file C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.MediaRouter\23.0.1.3\embedded\classes.jar doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.MediaRouter\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v7.MediaRouter' available in SDK installer. Java library file C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.MediaRouter\23.0.1.3\embedded\libs/internal_impl-23.0.1.jar doesn't exist. XamarinFormsExample.Droid Error Download failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r22.zip and put it to the C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.MediaRouter\23.0.1.3 directory. XamarinFormsExample.Droid Error Reason: One or more errors occurred. XamarinFormsExample.Droid Error Please install package: 'Xamarin.Android.Support.v7.MediaRouter' available in SDK installer. Android resource directory C:\Users\warrenbr\AppData\Local\Xamarin\Android.Support.v7.MediaRouter\23.0.1.3\embedded\./ doesn't exist. XamarinFormsExample.Droid View Code 解决办法 删除文件夹 C:\Users\your user\AppData\Local\Xamarin 新建文件夹C:\Users\your user\AppData\Local\Xamarin\zips 将下载的zip文件copy到zips文件夹,下载地址和文件夹名称关系可参考 http://www.cnblogs.com/qinjin/p/m2repository.html 清理解决方案,然后重新生成即可 参考 解决Xamarin Android墙的问题 http://stackoverflow.com/questions/36616524/xamarin-support-package-error http://forums.xamarin.com/discussion/61533/project-wont-build-xamarin-android-support
.net core 自带一个基础的logger框架Microsoft.Extensions.Logging。 微软默认实现了Microsoft.Extensions.Logging.Console.dll。控制台的日志输出和Microsoft.Extensions.Logging.Debug.dll调试输出。 下面我们写一个我们自己的本地文件输出模块demo,简单理解一下自带的这个logger系统。 logger框架主要几个类:LoggerFactory,Logger,LoggerProvider。 看名字就很好理解,都不需要解释。 实现我们自己的file logger只需要实现logger,loggerProvider即可。 第一步:入口。 loggerFactory.AddFile(this.Configuration.GetSection("FileLogging")); 为LoggerFactory扩张一个方法,提供增加日志写文件方式的入口。相关的配置来自appsettings.json 1 public static class FileLoggerExtensions 2 { 3 //add 日志文件创建规则,分割规则,格式化规则,过滤规则 to appsettings.json 4 public static ILoggerFactory AddFile(this ILoggerFactory factory, IConfiguration configuration) 5 { 6 return AddFile(factory, new FileLoggerSettings(configuration)); 7 } 8 public static ILoggerFactory AddFile(this ILoggerFactory factory, FileLoggerSettings fileLoggerSettings) 9 { 10 factory.AddProvider(new FileLoggerProvider(fileLoggerSettings)); 11 return factory; 12 } 13 } View Code 第二步:实现我们的logger提供程序,实现ILoggerProvider接口 public class FileLoggerProvider : ILoggerProvider, Idisposable 关键方法CreateLogger,创建真正写日志的logger。对当前的logger可以做适当的缓存,配置logger 1 public class FileLoggerProvider : ILoggerProvider, IDisposable 2 { 3 FileLoggerSettings _configuration; 4 readonly ConcurrentDictionary<string, InitLoggerModel> _loggerKeys = new ConcurrentDictionary<string, InitLoggerModel>(); 5 readonly ConcurrentDictionary<string, FileLogger> _loggers = new ConcurrentDictionary<string, FileLogger>(); 6 7 public FileLoggerProvider(FileLoggerSettings configuration) 8 { 9 _configuration = configuration; 10 _configuration.ChangeToken.RegisterChangeCallback(p => 11 { 12 //appsettings.json changed. reload settings. 13 _configuration.Reload(); 14 15 //update loggers settings form new settings 16 foreach (var item in this._loggers.Values) 17 { 18 InitLoggerModel model = new InitLoggerModel(); 19 InitLoggerSettings(item.Name, model); 20 InitLogger(model, item); 21 } 22 23 }, null); 24 } 25 public ILogger CreateLogger(string categoryName) 26 { 27 var loggerKey = this._loggerKeys.GetOrAdd(categoryName, p => 28 { 29 InitLoggerModel model = new InitLoggerModel(); 30 InitLoggerSettings(categoryName, model); 31 return model; 32 }); 33 var key = loggerKey.FileDiretoryPath + loggerKey.FileNameTemplate; 34 return this._loggers.GetOrAdd(key, p => 35 { 36 var logger = new FileLogger(categoryName); 37 InitLogger(loggerKey, logger); 38 return logger; 39 }); 40 } 41 42 private static void InitLogger(InitLoggerModel model, FileLogger logger) 43 { 44 logger.FileNameTemplate = model.FileNameTemplate; 45 logger.FileDiretoryPath = model.FileDiretoryPath; 46 logger.MinLevel = model.MinLevel; 47 } 48 49 class InitLoggerModel 50 { 51 public LogLevel MinLevel { get; set; } 52 public string FileDiretoryPath { get; set; } 53 public string FileNameTemplate { get; set; } 54 55 public override int GetHashCode() 56 { 57 return this.MinLevel.GetHashCode() + this.FileDiretoryPath.GetHashCode() + this.FileNameTemplate.GetHashCode(); 58 } 59 public override bool Equals(object obj) 60 { 61 var b = obj as InitLoggerModel; 62 if (b == null) 63 return false; 64 return this.MinLevel == b.MinLevel && this.FileDiretoryPath == b.FileDiretoryPath && this.FileNameTemplate == b.FileNameTemplate; 65 } 66 67 } 68 private void InitLoggerSettings(string categoryName, InitLoggerModel model) 69 { 70 model.MinLevel = LogLevel.Debug; 71 var keys = this.GetKeys(categoryName); 72 foreach (var item in keys) 73 { 74 var switchV = _configuration.GetSwitch(item); 75 if (switchV.Item1) 76 { 77 model.MinLevel = switchV.Item2; 78 break; 79 } 80 } 81 model.FileDiretoryPath = this._configuration.DefaultPath; 82 foreach (var item in keys) 83 { 84 var switchV = _configuration.GetDiretoryPath(item); 85 if (switchV.Item1) 86 { 87 model.FileDiretoryPath = switchV.Item2; 88 break; 89 } 90 } 91 model.FileNameTemplate = this._configuration.DefaultFileName; 92 foreach (var item in keys) 93 { 94 var switchV = _configuration.GetFileName(item); 95 if (switchV.Item1) 96 { 97 model.FileNameTemplate = switchV.Item2; 98 break; 99 } 100 } 101 } 102 103 IEnumerable<string> GetKeys(string categoryName) 104 { 105 while (!String.IsNullOrEmpty(categoryName)) 106 { 107 // a.b.c 108 //--result 109 // a.b.c,a.b,a,Default 110 yield return categoryName; 111 var last = categoryName.LastIndexOf('.'); 112 if (last <= 0) 113 { 114 yield return "Default"; 115 yield break; 116 } 117 System.Diagnostics.Debug.WriteLine(categoryName + "--" + last); 118 categoryName = categoryName.Substring(0, last); 119 } 120 yield break; 121 122 } 123 public void Dispose() 124 { 125 } 126 } View Code 第三步:实现我们的logger,实现ILogger接口。真正将log写入file public class FileLogger : Ilogger 1 public class FileLogger : ILogger 2 { 3 static protected string delimiter = new string(new char[] { (char)1 }); 4 public FileLogger(string categoryName) 5 { 6 this.Name = categoryName; 7 } 8 class Disposable : IDisposable 9 { 10 public void Dispose() 11 { 12 } 13 } 14 Disposable _DisposableInstance = new Disposable(); 15 public IDisposable BeginScope<TState>(TState state) 16 { 17 return _DisposableInstance; 18 } 19 public bool IsEnabled(LogLevel logLevel) 20 { 21 return this.MinLevel <= logLevel; 22 } 23 public void Reload() 24 { 25 _Expires = true; 26 } 27 28 public string Name { get; private set; } 29 30 public LogLevel MinLevel { get; set; } 31 public string FileDiretoryPath { get; set; } 32 public string FileNameTemplate { get; set; } 33 public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) 34 { 35 if (!this.IsEnabled(logLevel)) 36 return; 37 var msg = formatter(state, exception); 38 this.Write(logLevel, eventId, msg, exception); 39 } 40 void Write(LogLevel logLevel, EventId eventId, string message, Exception ex) 41 { 42 EnsureInitFile(); 43 44 //TODO 提高效率 队列写!!! 45 var log = String.Concat(DateTime.Now.ToString("HH:mm:ss"), '[', logLevel.ToString(), ']', '[', 46 Thread.CurrentThread.ManagedThreadId.ToString(), ',', eventId.Id.ToString(), ',', eventId.Name, ']', 47 delimiter, message, delimiter, ex?.ToString()); 48 lock (this) 49 { 50 this._sw.WriteLine(log); 51 } 52 } 53 54 bool _Expires = true; 55 string _FileName; 56 protected StreamWriter _sw; 57 void EnsureInitFile() 58 { 59 if (CheckNeedCreateNewFile()) 60 { 61 lock (this) 62 { 63 if (CheckNeedCreateNewFile()) 64 { 65 InitFile(); 66 _Expires = false; 67 } 68 } 69 } 70 } 71 bool CheckNeedCreateNewFile() 72 { 73 if (_Expires) 74 { 75 return true; 76 } 77 //TODO 使用 RollingType判断是否需要创建文件。提高效率!!! 78 if (_FileName != DateTime.Now.ToString(this.FileNameTemplate)) 79 { 80 return true; 81 } 82 return false; 83 } 84 void InitFile() 85 { 86 if (!Directory.Exists(this.FileDiretoryPath)) 87 { 88 Directory.CreateDirectory(this.FileDiretoryPath); 89 } 90 var path = ""; 91 int i = 0; 92 do 93 { 94 _FileName = DateTime.Now.ToString(this.FileNameTemplate); 95 path = Path.Combine(this.FileDiretoryPath, _FileName + "_" + i + ".log"); 96 i++; 97 } while (System.IO.File.Exists(path)); 98 var oldsw = _sw; 99 _sw = new StreamWriter(new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read), Encoding.UTF8); 100 _sw.AutoFlush = true; 101 if (oldsw != null) 102 { 103 try 104 { 105 _sw.Flush(); 106 _sw.Dispose(); 107 } 108 catch 109 { 110 } 111 } 112 } 113 } View Code 代码:https://github.com/czd890/NetCoreWebApp
实验demo现在需要发布到生产环境,发现在发布的时候要考虑到不一致的几个地方。 1.各类配置文件线下,线上不一致。 2.绑定的url不一致,可能是域名不一致,也可能是schema不一致(http,https) 配置文件的不一致问题,可以使用环境配置来解决。系统默认定义了3个:Development, Staging, Production appsettings.json 开发:appsettings.Development.json 线上:appsettings.Production.json 绑定url的问题在rc1还好解决。 project.json add "commands": { "web": "Microsoft.AspNetCore.Server.Kestrel --server.urls http://unix:/var/aspnet/HelloMVC/kestrel.sock", }, 但是到rc2之后,这个也取消了。google了一圈,找到有2种解决方案。 其一:http://benfoster.io/blog/how-to-configure-kestrel-urls-in-aspnet-core-rc2 使用配置文件的方式,因为没有环境参数所以不好兼容到线上线下环境。暂时不考虑 其二:https://www.billboga.com/posts/setting-host-uri-in-aspnet-core-rc2 使用启动参数来设置。暂时考虑使用这个,考虑到假如线上分布式部署,包括系统的安装,脚本等都是统一管理。所以还是可行的 发布到linux线上环境: 运行环境。nginx反向代理由kestrel运行.net core程序 第一步:搭建网站,本地能正常运行:http://www.cnblogs.com/calvinK/p/5604577.html 修改Main入口,使我们的程序绑定的url来自启动参数 发布到本地,然后上传到linux服务器。进入服务器,cd 到上传的目录, dotnet NetCoreWebApp.dll --server.urls="http://localhost:6000;http://localhost:6001"//已";"分割.可以绑定多个 url。 启动完成,使用 curl http://localhost:6000 测试是否能正常访问。 第二步:配置nginx反向代理。nginx安装:http://www.cnblogs.com/calvinK/p/5604036.html 修改配置nginx.conf location / { #设置主机头和客户端真实地址,以便服务器获取客户端真实IP proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #需要代理的地址。upstream 配置负责均衡 proxy_pass http://localhost:6000; } 修改完成 使用nginx -t -c nginx.conf 验证配置是否修改有误问题,测试木有问题就可以restart后,通过nginx代理访问到我们绑定在http://localhost:6000的.net core demo程序啦。 当然,我们的服务器存在重启或者down掉等问题,不可能出了问题我们手动重启等 1.nginx 开机自动启动。新建服务 # vim /usr/lib/systemd/system/nginx.service [Unit] Description=nginx - high performance web server Documentation=http://nginx.org/en/docs/ After=network.target remote-fs.target nss-lookup.target [Service] Type=forking PIDFile=/nginxInstall/sbin/logs/nginx.pid ExecStartPre=/nginxInstall/sbin/nginx -t -c /nginxInstall/conf/nginx.conf ExecStart=/nginxInstall/sbin/nginx -c /nginxInstall/conf/nginx.conf ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s QUIT $MAINPID PrivateTmp=true [Install] WantedBy=multi-user.target 编辑完成 使用命令 systemctl enable nginx.service 配置nginx服务为随开机启动。 > 需要注意的地方是:PIDFile=/nginxInstall/sbin/logs/nginx.pid 和 nginx.conf 中的 pid /nginxInstall/sbin/logs/nginx.pid 一定要一致 2.配置web app的监控程序。使用官方推荐的 supervisor 2.1.第一步安装 setuptools # wget https://bootstrap.pypa.io/ez_setup.py -O - | python 如果提示木有python,那就安装一个ypthon就好。视网络情况而定,可能是漫长的下载等待。 当然也可以下载安装:https://pypi.python.org/pypi/setuptools 下载.gz压缩包,上传到centos, 解压:[root@localhost opt]# tar -xzvf setuptools-23.1.0.tar.gz 编译:[root@localhost setuptools-23.1.0]# sudo python setup.py build 安装:[root@localhost setuptools-23.1.0]# sudo python setup.py install 可以看到安装到/usr/bin下面啦。当然我们也可以使用configure 指定安装目录。 如果按照在自己的指定目录,可以使用 # sudo ln -s [your install path] /bin/easy_install 创建一个软连接,方便我们使用。 2.2.安装supervisor # easy_install supervisor 配置supervisor: 参考:http://www.cnblogs.com/hamu/p/5587220.html 在/etc/supervisor目录下新建配置文件supervisord.conf,当然在这些目录下也是可以的 default paths (/usr/etc/supervisord.conf, /usr/supervisord.conf, supervisord.conf, etc/supervisord.conf, /etc/supervisord.conf, /etc/supervisor/supervisord.conf); #vim /etc/supervisor/supervisord.conf supervisord配置文件写入: [supervisord] logfile = /tmp/supervisord.log logfile_maxbytes = 50MB logfile_backups=10 loglevel = info pidfile = /tmp/supervisord.pid nodaemon = false minfds = 1024 minprocs = 200 umask = 022 user = root identifier = supervisor directory = /tmp nocleanup = true childlogdir = /tmp strip_ansi = false 配置supervisor作为服务运行 #vim /usr/lib/systemd/system/supervisord.service [Unit] Description=Supervisor daemon [Service] Type=forking ExecStart=/usr/bin/supervisord ExecStop=/usr/bin/supervisorctl $OPTIONS shutdown ExecReload=/usr/bin/supervisorctl $OPTIONS reload KillMode=process Restart=on-failure RestartSec=42s [Install] WantedBy=multi-user.target 增加监控 编辑我们上面创建的supervisord.conf,加入 [program:NetCoreWebAppFirst] #启动命令 command=dotnet NetCoreWebApp.dll --server.urls=http://localhost:6000 #启动前要cd到的位置 directory=/saleSystem/NetCoreWebApp/ #是否自动启动 autorestart=true #是否重定向程序的错误输出到标准输出 redirect_stderr=true #延时启动时间 startsecs=10 #启动超时时间 stopwaitsecs=30 #日志 stdout_logfile=/var/log/netcorewebapp/error.log stdout_logfile_maxbytes=50MB #是否随supervisor一起启动 autostart=true #管理页面 [inet_http_server] port=9100 username=mon password=mon 配置完成后,stop,在start服务 访问管理页面就能看到 当然不忘记让我们增加的服务自动开机启动 systemctl enable nginx.servicve systemctl enable supervisord.service 如此这样,我们做到了nginx和supervisord都随机启动并保证服务因意外中断也能自动重启。
.net core中可以说是用了全新的IOC模板,定义在Microsoft.Extensions.DependencyInjection下。提供了一套标准的接口。并提供了默认实现。并且大范围使用着,处处都体现着IOC的设计思想。 Startup的ConfigureServices方法中,集中对服务进行配置。可以看到默认的.net core mvc默认项目已经为我们注入了不少服务。看注释也就懂了,也没什么特别好解释的(^_^)。 最后2行代码是自定义配置的注入。需要Microsoft.Extensions.Options.ConfigurationExtensions package的支持 AddOptions 针对Ioptions<POCOModel>提供管理,缓存,配置变动自动支持服务,即配置文件有改动会自动反应在应用程序中,且应用程序不需要自动重启。不像以前的web.config.有任何改动都会自动重启。 Configure<T>(IConfiguration).表示配置的内容来自配置文件。 IndexSetting是新建的一个model,即配置项。 配置文件中的IndexSetting 目前.net core ioc有3种获取service方式。 1.ctor注入。 2.action 参数注入 3.容器获取方式 当然,应对一般情况,这种方式也许够用了。but,现实显然不是这样的。通常我们需要更强大的ioc lib。目前官方推荐的更强大的ioc lib。也是业界大名鼎鼎的autofac。autofac的介绍园子里面一大把,并且写的很详细,很好。就不多介绍了。 官方文档:http://docs.autofac.org/en/latest/integration/aspnetcore.html asp.net和asp.net core时代的区别主要有几点。 1.Use InstancePerLifetimeScope instead of InstancePerRequest. 简单来说就是生命周期的统一。 2.No more DependencyResolver. 没有以前的DependencyResolver入口了 3.No manual controller registration. 不再需要注册自己的controllerfactory了,也包括owin中的middleware。 需要使用它,当然需要添加它 针对Startup的ConfigureServices方法做一些改造 使用任何第三方ioc,都需要返回IServiceProvider。替换系统的默认IServiceProvider。否则不能生效。 然后创建autofac的build,一切照旧,我们就可以愉快的使用autofac啦。
.net core 对配置系统做出了大幅度更新,不在局限于之前的*.xml配置方式。现在支持json,xml,ini,in memory,环境变量等等。毫无疑问的是,现在的json配置文件是.net core世界中的一等公民。 每个类型的配置文件都有一个schema说明。有schema的好处是能有智能提示。 可以在任何配置项的Key上面按F12导航到该配置的schema查看结构,或说明 比如global.json的schema 例外的是appsettings.json。他是没有schema的。因为他是各个组件的配置。所以也没法有schema。 还有各类配置的schema,比如bundleconfig. js,css压缩的配置schema。 新建项目有的各类配置 global.json 看名字也大概能猜到,意指整个solution的配置。 launchSettings.json 运行的宿主配置。设置启动url绑定的域名+端口,定义环境变量等 他的可视化配置页面 项目 右键属性 调试 project.json 项目的配置文件,类似之前的*.csrpoj文件。 dependencies:项目的依赖引用关系 tools:工具类,比如ef生成脚本,T4脚本等。 frameworks:框架版本 buildOptions:编译配置。 runtimeOptions:运行时配置 publishOptions:发布配置 scripts:发布脚本,编译脚本等 appsettings.json 各类应用配置,第三方组件配置,自定义配置等
前面搭建好啦linux运行环境,下面搭建windows下的开发环境。并完成调试 参考地址:https://www.microsoft.com/net/core#windows。 按照步骤来就好。安装.net core sdk 安装vs插件,安装vscode等。 vscode下载地址https://code.visualstudio.com/ 。C# 插件查看地址https://marketplace.visualstudio.com/VSCode vscode 命令执行 ext install csharp就可以。需要网络环境不错。不然你懂的。 安装完成后,cmd运行dotnet new 命令,创建一个新的项目。当然需要cd到一个新建的目录 完成之后,使用vscode打开文件夹的方式打开文件夹。根据https://docs.asp.net/en/latest/getting-started.html 的指导完成web项目的初始化。 project.json 添加"Microsoft.AspNetCore.Server.Kestrel":"1.0.0-rc2-final" 执行命令 dotnet restore。 vscode ctrl+p 弹出命令行输入:>dotnet…选择 增加 startup文件,修改program按照指导来即可。 windows F5启动调试 点击选择环境.net core。vscode自动生成配置文件 launch.json 修改"name": ".NET Core Launch (web)中的program value。 ${workspaceRoot}/bin/Debug/<target-framework>/<project-name.dll> 替换 target-framework project-name.dll 最终配置如下: F5开始调试,vscode提示没有配置任何任务运行。根据提示点击配置即可,选择.net core 不出意外,F5调试就木有问题啦。 当然,如果你需要输出中文并且不乱码,还需要增加一行code:context.Response.ContentType="text/html;charset=utf-8"; 最终strapup.csd代码 public class Startup { public void Configure(IApplicationBuilder app) { app.Run(context => { context.Response.ContentType="text/html;charset=utf-8"; return context.Response.WriteAsync("我是使用vscode开发的程序,现在运行在cnetos7上面。哈哈"); }); } } 发布到centos并运行 vs code 切换到命令行工具 输入dotnet publish 发布web项目 将发布的目录使用winscp等类似ftp工具上传到centos服务器 在然后就是检验成果的时候到啦