热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

htmlunit有js文件不存在_【复杂系统迁移.NETCore平台系列】之静态文件

源宝导读:微软跨平台技术框架—.NETCore已经日趋成熟,已经具备了支撑大型系统稳定运行的条件。本文将介绍明源云ERP平台从.NETFramework

2af4e7aa0a34904555eef3415d9479db.png

fb1782ab468983c630138d65d23d11d6.png

源宝导读:微软跨平台技术框架—.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件。本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验。

一、背景

    随着ERP的产品线越来越多,业务关联也日益复杂,应用间依赖关系也变得错综复杂,单体架构的弱点日趋明显。19年初,由于平台底层支持了分应用部署模式,将ERP从应用子系统层面进行了切割分离,迈出了从单体架构向微服务架构转型的坚实一步。不久的将来,ERP会进一步将各业务拆分成众多的微服务,而微服务势必需要进行容器化部署和运行管理,这就要求ERP技术底层必须支持跨平台,所以将现有ERP系统从.NET Framework迁移到 .NET Core平台势在必行。

    上一篇我们讲述了Erp在改造.Net Core页面的处理,这一篇我们将讲述在静态文件改造过程中遇到的问题和解决思路。

二、静态文件扩展需求

    在ERP中,不仅仅要支持后端的扩展需求,还要支撑前端的扩展,ERP常见的扩展就是项目扩展标准产品的功能,其中包含JS和CSS的扩展

sea.js的扩展

    ERP采用sea.js的同步js加载机制,sea.js中是通过sea-config.js的配置来通过模块名映射到正确的js路径,而ERP中sea-config.js因为屏蔽掉手动配置,所以采取动态生成机制,通过如下几步完成前端sea-config.js的加载。

我们需要先了解下sea.js的编码规范,以下是js文件的一个示例:

//OrganizationAppService.js文件/*** AppService代理类* @author 本代码由代码生成器自动生成,请不要手工调整* http://localhost:4602/script/Mysoft.PubPlatform.Organization.AppServices.OrganizationAppService/proxy*/define("Mysoft.PubPlatform.Organization.AppServices.OrganizationAppService", function (require, exports, module) { var utility = require("utility"); //业务逻辑代码 })

有了确定好的格式以后,我们会在编译前端代码时候扫描OrganizationAppService.js的路径和 define中定义的模块名,生成如下json结构:

{"Mysoft.PubPlatform.Organization.AppServices.OrganizationAppService": { "type": "product", "product": { "path": "PubPlatform/Organization/AppService/OrganizationAppService.js", "description": "\r\n\r\nAppService代理类\r\n@author本代码由代码生成器自动生成,请不要手工调整\r\nhttp:localhost:4602scriptMysoft.PubPlatform.Organization.AppServices.OrganizationAppServiceproxy\r\n\r\n", "mtime": 1581492465639 } }}

    根据ERP的扩展要求,会有如下三个文件:    

  • 平台js文件 _frontend_build\platform-module.json。

  • 产品js文件 _frontend_build\product-module.json。

  • 二开js文件 _frontend\Customize{app}\dist\sea-config.json(app代表系统简称例如cbxt)。

    接下来就是在代码中生成如下的sea-config.js内容,因为上述json内容是一个结构化的内容,通过程序反序列化加载如上三个文件到内存中,再转换成如下标准的sea-config.js的标准内容:

var __lang = !!window.Mysoft ? window.Mysoft.Map6.UI.page.lang : "";seajs.config({ "vars": { "lang": "zh-cn" }, "alias": { "RptUtility": "Report/Common/RptUtility.js", "Mysoft.Report.Preview.RptDownload": "Report/Preview/RptDownload.js",}, "map": [ [/^.*$/, function (url) { return url + "?_t=31634695680000000&lang=" + __lang; }] ], "base": "/", "debug": true});

    这样就完成了整个js文件的sea.js的整个代码编写到配置加载的整个环路。最后在在Framework的ERP中是通过配置在webconfig的SeaConfigHandler进行加载的。

多语言js的扩展

    ERP的js支持多语言的扩展,而多语言都存储在后端,所以js文件的加载是配置在webconfig中配置的StaticFileHandler来扩展功能,在加载中读取物理文件内容时候,进行多语言替换之后返回到前端。

css文件扩展需求

    css文件加载为了产品进行二次开发扩展也是通过StaticFileHandler的配置来加载,首先加载产品的css文件,然后加载项目的css文件,将两个文件内容进行合并后返回到前端。

静态文件缓存的需求

    在上述的静态文件加载过程中,都增加了浏览器静态文件缓存的功能,通过max-age 和etag的http头进行标记,让浏览器知道要缓存静态文件,从而减少对后端服务器的请求数据。

    上一篇文章中我们提到的TagHelper中,静态文件也可以通过在生成的url中,拼接文件的的 SHA256的哈希值来直接使用浏览器自带的缓存功能。

二、.Net Core静态文件加载原理

    前面我们讲述了静态文件的扩展需求,这部分在Core的改造中是需要做兼容的,整个的业务逻辑是不变的,但是Core中没有HttpHanler的处理机制,所有的请求都是通过Middleware来进行处理,Core中对应静态文件的处理全部是放在StaticFileMiddleware 中,通过IApplicationBuilder.UseStaticFiles进行引用。

    在Core中提供的默认静态文件加载主要是如下几个类型:

  • FileExtensionContentTypeProvider:用来确定哪些请求是需要静态文件StaticFileMiddleware 进行处理的。

  • PhysicalFileInfo:继承自IFileInfo,用来读取物理文件内容

  • PhysicalFileProvider:继承自IFileProvider,用来映射物理路径到FileInfo和监听文件是否修改。

    Core静态文件的处理流程如下:

  1. 首先通过FileExtensionContentTypeProvider确定是否需要处理请求。

  2. 通过PhysicalFileProvider的GetFileInfo返回IFileInfo对象(实际类型是PhysicalFileInfo,如果文件不存在返回NotFoundFileInfo)。

  3. 通过IFileInfo的Exists判断文件是否存在,如果存在通过CreateReadStream方法返回文件内容。

  4. 第一次返回给客户端的ETag和Last-Modified的值,会在第二次请求返回给服务端,如果文件没有更改则这两个值在生成规则一样的情况下不会改变,这时候返回给客户端304。

三、.Net Core中静态文件改造

    在讲述了ERP的静态文件扩展需求和.Net Core中静态文件加载原理之后,接下来就是在.Net Core提供的静态文件功能基础上进行扩展来接入ERP的扩展需求了,这里我们采用了两个适配器模式来讲解决这个问题,首先我们看看整体的类图:

924e37a2bcb2af3ce2f02317ffe1a6e0.png

4566852ba9a469bcbdabf4d8d1ae48df.png

说明:

  • PhysicalFileProviderAdapter 和PhysicalFileProvider 是组合关系,通过重写GetFileInfo来返回IFileHandler对象,代码如下:

    public IFileInfo GetFileInfo(string subpath) {    var handlers = IocManager.ServiceProvider.GetServices();    var handler = handlers.OrderBy(item => item.Order)// 优先处理特殊的,默认的优先级最小        .ToList()        .First(item =>        {            item.InitFile(_provider, _provider.GetFileInfo(subpath));            return item.CanHandler();        });    return handler;}

  • 为了兼容默认静态文件处理行为,默认的DefaultHandler是可以处理所有的需要处理的请求,所以DefaultHandler的Order最大,SeaConfigJsFileHandler因为需要在JsLangeHandler之前处理所以Order最小。

  • BaseHandler 主要处理通用逻辑,并且可以做一些子类的公用代码封装,例如通过如下代码,确定那种文件类型是这个类需要处理的,这样在JSLangHandler和CssLangeHandler中就可以少些一些代码。

    // BaseHandlerprotected BaseFileHandler(IHttpContextAccessor accessor){ _accessor = accessor; ContentTypeProvider = new FileExtensionContentTypeProvider();} protected virtual string HandlerContentType { get; }public virtual bool CanHandler(){ //通过头和物理路径两种条件来获取ContentType,如果都获取不到则不处理 if (!ContentTypeProvider.TryGetContentType(_accessor.HttpContext.Request.Path, out var contentType) ||!ContentTypeProvider.TryGetContentType(FileInfo.PhysicalPath, out contentType)) return false; if (contentType != HandlerContentType) return false; return true;} //CssMergeFileHandler protected override string HandlerContentType => "text/css"; public override bool CanHandler(){ //忽略swagger的文件 return base.CanHandler() && FileInfo.Name != "swagger-ui.css";} //JsLangFileHandlerprotected override string HandlerContentType => "application/Javascript"; public override bool CanHandler(){ //忽略swagger的文件 return base.CanHandler() && FileInfo.Name != "sea-config.js" && !(FileInfo.Name == "swagger-ui-standalone-preset.js" || FileInfo.Name == "swagger-ui-bundle.js");}

  • JsLangFileHandler,CssMergeFileHandler和SeaConfigJsFileHandler都是处理后的内容写入MemoryStream然后通过CreateReadStream 返回给Core的静态文件处理。

  • 在BaseHandler中提供InitFile的模板方法,子类重写InnerInit来实现业务逻辑,AppendHeader用来处理浏览器缓存的逻辑。

//模板方法,所有的初始化逻辑都在此处public virtual void InitFile([NotNull] IFileProvider provider,[NotNull] IFileInfo fileInfo){ FileProvider=provider?? throw new ArgumentNullException(nameof(provider)); FileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo)); if (!CanHandler()) return; InnerInit(); AppendHeader();} //留给子类来出来各自的业务逻辑protected virtual void InnerInit(){}//给返回的静态文件添加缓存protected virtual void AppendHeader(){ string language = LanguageResourceManager.GetCurrentTopCulture(); _accessor.HttpContext.Response.Headers.Append("X-Language", language); string fileVer = _accessor.HttpContext.Request.GetQueryValue("_t"); if (string.IsNullOrEmpty(fileVer) ) { //没有版本管理的文件默认添加缓存时间为30*60秒 var staticFileExpiresMinutes = GetStaticFileExpires(); _accessor.HttpContext.Response.Headers.Append("Cache-Control", $"public,max-age={staticFileExpiresMinutes*60}"); _accessor.HttpContext.Response.Headers.Append("X-StaticFileHandler", $"{staticFileExpiresMinutes}minute"); } else { //如果有版本管理默认添加一年,因为版本会随着文件更改而更改 _accessor.HttpContext.Response.Headers.Append("Cache-Control", $"public,max-age={TimeSpan.FromDays(365).Seconds}");        _accessor.HttpContext.Response.Headers.Append("X-StaticFileHandler", $"1year");     }}

四、总结

    相比于Framework散落的静态文件处理,.NET Core的静态文件处理职责更加明确,点更加集中。基于适配器的扩展之后,将职责更加明确,每个FileHandler只有一个职责,并且在以后需要类似的静态文件功能时候增加一个FileHandler即可,更加易于扩展。

    由于依赖于.Net Core 中Ioc容器提供的获取实现列表的功能,IEnumerable GetServices(this IServiceProvider provider) 方法,所以这里简单的采用遍历判断的方法,如果只有获取单个实现的方法的话,这里可以调整为责任链模式,有兴趣的可以尝试一下。

------ END ------

作者简介

熊同学: 研发工程师,目前负责ERP运行平台的设计与开发工作。

也许您还想看

【复杂系统迁移 .NET Core平台系列】之迁移项目工程

【复杂系统迁移 .NET Core平台系列】之界面层

招商城科走进武汉研发中心,现场编码解锁平台内核技术

如何解决大批量数据保存的性能问题

【2019总结篇】谈谈数字化时代,ERP如何坐稳数字化底座

2f3473de55ba32439770f65f64f786e7.png




推荐阅读
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Firefox火狐浏览器关闭到http://detectportal.firefox.com的流量问题解决办法
    本文介绍了使用Firefox火狐浏览器时出现关闭到http://detectportal.firefox.com的流量问题,并提供了解决办法。问题的本质是因为火狐默认开启了Captive portal技术,当连接需要认证的WiFi时,火狐会跳出认证界面。通过修改about:config中的network.captive-portal-service.en的值为false,可以解决该问题。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 使用正则表达式爬取36Kr网站首页新闻的操作步骤和代码示例
    本文介绍了使用正则表达式来爬取36Kr网站首页所有新闻的操作步骤和代码示例。通过访问网站、查找关键词、编写代码等步骤,可以获取到网站首页的新闻数据。代码示例使用Python编写,并使用正则表达式来提取所需的数据。详细的操作步骤和代码示例可以参考本文内容。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
author-avatar
daadhkiw_267
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有