【复杂系统迁移 .NET Core平台系列】之静态文件

源宝导读:微软跨平台技术框架—.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的扩展需求了,这里我们采用了两个适配器模式来讲解决这个问题,首先我们看看整体的类图:

说明:

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

    public IFileInfo GetFileInfo(string subpath){var handlers = IocManager.ServiceProvider.GetServices<IFileHandler>();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中就可以少些一些代码。

    // BaseHandler
    protected 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";
    }//JsLangFileHandler
    protected 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<T> GetServices<T>(this IServiceProvider provider) 方法,所以这里简单的采用遍历判断的方法,如果只有获取单个实现的方法的话,这里可以调整为责任链模式,有兴趣的可以尝试一下。

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

作者简介

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

也许您还想看

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311382.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Abp vNext 源码分析] - 19. 多租户

一、简介ABP vNext 原生支持多租户体系&#xff0c;可以让开发人员快速地基于框架开发 SaaS 系统。ABP vNext 实现多租户的思路也非常简单&#xff0c;通过一个 TenantId 来分割各个租户的数据&#xff0c;并且在查询的时候使用统一的全局过滤器(类似于软删除)来筛选数据。关于…

HTMLCSS 超简单的前端设计入门-2!

文章目录图片元素img元素和a元素连用和map元素和figure元素多媒体元素videoaudio列表元素有序列表无序列表定义列表容器元素div元素语义化容器元素元素包含关系为网页添加样式CSS术语解释选择器声明块CSS代码书写位置常见样式声明选择器简单选择器选择器的组合选择器的并列层叠…

八数码问题I-bfs和map标记

解题思路&#xff1a; bfs&#xff0c;将棋盘转化成一个整数表示其状态&#xff0c;比如我们到到达的状态是815736402&#xff0c;而样例给的输入状态是264137058&#xff0c;因为这些整数过大&#xff0c;标记数组开不下&#xff0c;所以可以用map来代替数组&#xff0c;写得时…

一个迷你ASP.NET Core框架的实现(下)

【框架内幕】| 作者 / Edison Zhou这是恰童鞋骚年的第196篇原创文章上一篇我们了解了AspNetCore.Mini这个项目的背景及项目结构和流程&#xff0c;这一篇我们继续解析几个核心对象。本文整理自A大&#xff08;蒋金楠&#xff09;的主题分享&#xff0c;点击本文底部“阅读原文”…

CoreJava 笔记总结-第七章 异常,断言和日志

文章目录第七章 异常,断言和日志处理错误异常的分类声明检查型异常如何抛出异常创建异常类捕获异常捕获单个异常捕获多个异常再次抛出异常和异常链finally子句try-with-Resource语句分析堆栈轨迹元素使用异常的技巧使用断言使用断言完成参数检查日志基本日志高级日志调试技巧第…

根据后序和中序求二叉树的先序

代码如下&#xff1a; #include <iostream> using namespace std; const int N 1010; int in_order[N], post_order[N], lch[N], rch[N];int build(int inL, int inR, int postL, int postR) {if (inL > inR)return 0;int root post_order[postR];int k inL;while…

CoreJava 笔记总结-第九章 集合

第九章 集合 文章目录第九章 集合Java集合框架集合接口与实现分离Collection接口迭代器泛型实用方法集合框架中的接口具体集合链表数组列表散列集树集优先队列映射映射的基本操作更新映射条目映射视图弱散列试图链接散列集与映射枚举集与映射标识散列映射视图与包装器小集合子范…

30多岁程序员老W,无奈选择转行!问题出在哪?

作者&#xff1a;邹溪源&#xff0c;长沙资深互联网从业者&#xff0c;架构师社区特邀嘉宾&#xff01;一有一天&#xff0c;一位同事跟我说&#xff1a;老w已经改行做美缝去了&#xff0c;你怎么看&#xff1f;我想了想&#xff0c;说&#xff1a;他大概终于做出了眼下最符合他…

一维前缀和

代码如下&#xff1a; #include <iostream> using namespace std; const int N 1010; int a[N], s[N];int main() {int n;cin >> n;for (int i 1; i < n; i) {cin >> a[i];//原数组s[i] s[i - 1] a[i];}for (int i 1; i < n; i)cout << s[…

HttpClientFactory的套路,你知多少?

背景ASP.NET Core 在 2.1 之后推出了具有弹性 HTTP 请求能力的 HttpClient 工厂类 HttpClientFactory。替换的初衷还是简单摆一下&#xff1a;① using(var client new HttpClient()) 调用的 Dispose() 方法并不会立即释放底层 Socket 连接&#xff0c;新建 Socket 需要时间&a…

CoreJava 笔记总结-第十二章 并发-1

第十二章 并发 线程 package chapter12_concurrent.threads;public class ThreadsTest {public static final int DELAY 10;public static final int STEPS 100;public static final double MAX_AMOUNT 1000;public static void main(String[] args) {var bank new Bank(…

走迷宫-双向bfs解法

双向bfs适用于知道起点和终点的状态下使用&#xff0c;从起点和终点两个方向开始进行搜索&#xff0c;可以非常大的提高单个bfs的搜索效率同样&#xff0c;实现也是通过队列的方式&#xff0c;可以设置两个队列&#xff0c;一个队列保存从起点开始搜索的状态&#xff0c;另一个…

.NET Core开发实战(第16课:选项数据热更新:让服务感知配置的变化)--学习笔记...

16 | 选项数据热更新&#xff1a;让服务感知配置的变化选项框架还有两个关键类型&#xff1a;1、IOptionsMonitor2、IOptionsSnapshot场景&#xff1a;1、范围作用域类型使用 IOptinsSnapshot2、单例服务使用 IOptionsMonitor通过代码更新选项&#xff1a;IPostConfigureOption…

CoreJava 笔记总结-第十二章 并发-2

文章目录第十二章 并发synchronized关键字同步块监视器概念volatile字段final变量原子性死锁线程安全的集合阻塞队列映射条目的原子更新对并发散列映射的批操作并行数组算法同步包装器任务和线程池Callable, FutureExecutor执行器fork-in框架异步计算可完成Future组合可完成Fut…

八数码问题II-bfs和map标记

问题描述&#xff1a; 在33的棋盘上&#xff0c;摆有八个棋子&#xff0c;每个棋子上标有1至8的某一数字。棋盘中留有一个空格&#xff0c;空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是&#xff1a; 给出一种初始布局&#xff08;初始状态&#xff09;和目标布…

华为任职资格_看了华为的任职资格体系,你就明白员工为啥这么拼?

提到华为的18万奋斗者&#xff0c;职场上没人不竖起大拇指。而优秀人才的背后&#xff0c;就像任正非曾说过的那样&#xff0c;华为的成功&#xff0c;很大意义上讲就是人力资源的成功。华为的人力资源管理最有代表性的&#xff0c;除了狼性文化和薪酬绩效外&#xff0c;就是任…

如何使用有序GUID提升数据库读写性能

源宝导读&#xff1a;数据库设计时&#xff0c;经常会使用GUID作为表的主键&#xff0c;但由于GUID的随机性会导致数据库在读写数据时效率严重下降&#xff0c;影响应用程序整体性能。本文将深入探讨如何通过使用有序GUID提升数据读写的性能。一、背景常见的数据库设计是使用连…

八数码问题II-双向bfs和map标记

问题描述&#xff1a; 在33的棋盘上&#xff0c;摆有八个棋子&#xff0c;每个棋子上标有1至8的某一数字。棋盘中留有一个空格&#xff0c;空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是&#xff1a; 给出一种初始布局&#xff08;初始状态&#xff09;和目标布…

MarkDown语法, 快捷键,Dos命令

文章目录MarkDown 语法计算机软件快捷键Dos命令MarkDown 语法 字体: 斜体, 粗体, 删除图片: ![title](link)超链接: [words](link)引用>标题列表表格 名字|性别|生日 31辅导班VS看复旦南开都是vs1ds代码块 分割线*** 计算机软件 系统软件应用软件DOS, Windows, Unix,…

《商业洞察力30讲》学习笔记(上)

【洞察力】| 作者 / Edison Zhou这是恰童鞋骚年的第197篇原创文章学习洞察力&#xff0c;也是新时代IT人员的一门进阶必修课...1学习背景2019年下半年至今&#xff0c;在领导的推荐下学习了刘润老师的《商业洞察力30讲》&#xff0c;刷新了我对于事物的认知&#xff0c;也为我提…