最近在做一个后台配置系统,其实之前也接触过,所谓的配置系统就是指,将你的网站布局抽象成一个xml模板,里面包括你自定义的节点,然后将变化的部分作为配置项,通过服务将配置选项与模板组装成一个js(这个服务你可以分离出去,也就是分布式部署,这样的话系统需要引用其客户端进行访问,也可以耦合在一起,这个自己看情况选择),用户只需引用js(当然需要在页面上加上事前约定好的dom节点),然后显示。

  这种需求一般是一些网站前端经常变化,然后需要每次开发人员改动html后都需要发布站点,比较繁琐。

  今天主要介绍一下核心的拼接服务,xml模板是存放到数据库中的,因为每次都去读数据库中的xml很繁琐,我们是在保存xml模板的时候形成了cache缓存,每当模板更新保存的时候触发更新缓存(当然也设置了缓存过期时间)。

组装模板如下:

//load xml
XmlDocument doc = new XmlDocument();
doc.LoadXml(template);
//childItemTemplate
XmlNode childTpl = doc.SelectSingleNode("leftMenu").SelectSingleNode("XXName");
Razor.Compile(childTpl.InnerText, typeof(IDictionary<string, string>), string.Format("XXName{0}", OO));
//container
XmlNode containerTpl = doc.SelectSingleNode("leftMenu").SelectSingleNode("container");
Razor.Compile(containerTpl.InnerText, typeof(IDictionary<string, string>), string.Format("CCC_{0}", CCC));
//levelOneMenuTemplate
var levelOneChilds = doc.SelectSingleNode("leftMenu").SelectSingleNode("levelOneMenuTemplate").ChildNodes;
Parallel.For(0, levelOneChilds.Count, i =>
{
XmlElement xe;
var node = levelOneChilds[i];
if (node is XmlElement)
{
xe = (XmlElement)node;
Razor.Compile(xe.InnerText, typeof(IDictionary<string, string>), string.Format("levelOneMenuTemplate_{0}_{1}", xe.GetAttribute("index"), templateId));
}
});
//SetCache
Nav.Common.CacheHelper.SetCache(string.Format("IsWriteComplied_{0}", templateId), true, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(30));

其中,Razor.Compile()是生成Razor缓存的机制,有很多重载函数,但是本次用到的是

 public static void Compile(string razorTemplate, Type modelType, string cacheName);

最终生成的代码如下:思路也是比较简单,首先循环遍历一级菜单,在判断该菜单有无子节点,如果有就再循环遍历其菜单子节点,将子节点append在一级菜单下面,当然本系统有个最大显示数,就是不是所有的

节点都要显示的,只有需要显示的时候才显示(这个用js控制),所有在组装的时候,多加了一个标签是sbHiddenChild,这个就是隐藏的子节点string

 private string GenerateFinalMenu(MenuEntity entity)
{
StringBuilder sbDisplayChild = new StringBuilder();
StringBuilder sbHiddenChild = new StringBuilder();
StringBuilder sbLevelOne = new StringBuilder();
string rst = string.Empty;
//load xml
XmlDocument doc = new XmlDocument();
doc.LoadXml(entity.HtmlTemplate);
XmlNode root = doc.SelectSingleNode("leftMenu");
var levelOneMenuTemplate = root.SelectSingleNode("levelOneMenuTemplate");
var childItemTemplate = root.SelectSingleNode("childItemTemplate").InnerText;
Common.Helper.TemplateCompiler.CompileWriteTemplate(entity.HtmlTemplate, entity.HtmlTemplateId, false);
//组装子节点
foreach (var item in levelOneMenuTemplate.ChildNodes)
{
sbDisplayChild.Clear();
sbHiddenChild.Clear();
XmlElement xe;
if (item is XmlElement)
{
xe = (XmlElement)item;
var menuIndex = int.Parse(xe.GetAttribute("index"));
//可显示最大子节点
int maxChild = xe.HasAttribute("maxChild") ? int.Parse(xe.GetAttribute("maxChild")) : -;
if (xe.GetAttribute("hasChild") == "true")
{
var subMenuItems = entity.MenuItems[menuIndex].SubMenuItems;
for (int i = ; i < subMenuItems.Count; i++)
{
//child to display
if ((maxChild > && maxChild > i) || maxChild == -)
{
sbDisplayChild.Append(Razor.Run<IDictionary<string, string>>(string.Format("childItemTemplate_{0}", entity.HtmlTemplateId),
new Dictionary<string, string> {{ "Href", subMenuItems[i].ItemHref },
{ "Id", subMenuItems[i].ItemValue },
{ "Class", subMenuItems[i].ItemStyle },
{ "Name", subMenuItems[i].ItemName }}));
}
else//child to hide
{
sbHiddenChild.Append(Razor.Run<IDictionary<string, string>>(string.Format("childItemTemplate_{0}", entity.HtmlTemplateId),
new Dictionary<string, string> {{ "Href", subMenuItems[i].ItemHref },
{ "Id", subMenuItems[i].ItemValue },
{ "Class", subMenuItems[i].ItemStyle },
{ "Name", subMenuItems[i].ItemName }}));
}
} sbLevelOne.Append(Razor.Run<IDictionary<string, string>>(string.Format("levelOneMenuTemplate_{0}_{1}", menuIndex.ToString(), entity.HtmlTemplateId),
new Dictionary<string, string> {{ "DisplayItems", sbDisplayChild.ToString()},
{ "HiddenItems", sbHiddenChild.ToString()}}));
}
else
{
sbLevelOne.Append(xe.InnerText);
}
} };
rst = Razor.Run<IDictionary<string, string>>(string.Format("container_{0}", entity.HtmlTemplateId), new Dictionary<string, string> { { "MenuItems", sbLevelOne.ToString() } });
return rst;
}

我们在使用的时候,通过要压缩一下

dbEnity.MenuContent = Nav.Common.WebHelper.Compress(this.GenerateFinalMenu(entity));
public static string Compress(string strHTML)
{
Regex reg = new Regex(@"(?<=^|>)[^<>]+(?=<|$)");
Regex regSpa = new Regex(@"^\s+|\s+$");
return reg.Replace(strHTML, delegate(Match m) { return regSpa.Replace(m.Value, ""); });
}

压缩完成后,保存在数据库中。至此,配置服务可以说完成了一半了,接下来,就是生成一个js,并把js发布掉。

生成js其实很简单的,第一步:我们用的是web api 返回一个HttpResponseMessage类的对象,但是他的主要数据是result啊,那这个result从哪来的呢?

[HttpGet]
public HttpResponseMessage GetContent(string e = "")
{
INavMenuVersionService svc = IoC.Resolve<INavMenuVersionService>();
string result = svc.GetOnlineJsContent();
var ecode = System.Text.Encoding.UTF8;
if(!string.IsNullOrEmpty(e))
{
ecode = System.Text.Encoding.GetEncoding("gb2312");
}
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new StringContent(result, ecode, "text/plain");
return resp;
}

看看result是从哪来的吧,对的,就是上面我们出来拼接后的数据,我们存放到MenuContent中的

  public string GetOnlineJsContent()
{
try
{
string rst = string.Empty;
Dictionary<string, string> contents = new Dictionary<string, string>();
SearchParams sp = new SearchParams { States = (int)VersionState.Online };
//get all version list
var verList = this.GetNavMenuVersionList(sp); if (verList != null && verList.Data != null)
{
foreach (var v in verList.Data)
{
contents.Add(v.MenuType.ToString(), HttpUtility.HtmlEncode(Nav.Common.WebHelper.Compress(v.MenuContent).Trim()));
} //complie read template
//load template xml
rst = BuildTemplateAndMenuContent(verList.Data[], contents);
}
return rst + C_END_TAG;
}
catch
{
return string.Empty;
}
}

看上面的代码,我们发现其实处理的不是一个菜单啊,貌似VersionState.Online的都处理了啊,这个。。这个是我们故意的,拿到MenuContent之后并不是直接用来生成js,因为MenuContent只是一个html

格式啊,并不是一个js所以在上面中使用BuildTemplateAndMenuContent()方法进行Build一下,该方法主要是下面这一句:

string rst = Razor.Run<IDictionary<string, string>>("jsTemplate_" + versionData.MenuType,
new Dictionary<string, string> { { "MenuContent", Newtonsoft.Json.JsonConvert.SerializeObject(contents) } });
private string BuildTemplateAndMenuContent(INavMenuVersionExtEntity versionData,Dictionary<string, string> contents)
{
       //创建cache
var menuEntity = Newtonsoft.Json.JsonConvert.DeserializeObject<MenuEntity>(versionData.MenuSchema);
XmlDocument doc = new XmlDocument();
doc.LoadXml(menuEntity.HtmlTemplate);
Common.Helper.TemplateCompiler.CompileReadTemplate(doc, menuEntity.MenuType);
       //build,主要是加上菜单的类型
string rst = Razor.Run<IDictionary<string, string>>("jsTemplate_" + versionData.MenuType,
new Dictionary<string, string> { { "MenuContent", Newtonsoft.Json.JsonConvert.SerializeObject(contents) } });
return rst;
}
上面的方法中其他的代码其实都是为了创建缓存用的(在此不做介绍),其中,menuEntity是我们的配置保存的菜单所有项(包含了一级导航,二级子节点和模板内容),之所以加上菜单类型
主要是因为我们生成的js是所用Online菜单数据,这样方便用户多选择使用,只需要传入一个MenuType就能获取自己想要的数据(因为我们就只有一个js,不然一种菜单一种js也是可以实现的)
最后发布即可。。。。

关于xml作为模板的配置服务系统开发的更多相关文章

  1. 在微服务系统开发部署中使用Azure RBAC自定义角色

    Azure的官方文档介绍了如何创建用于Azure基于角色的访问控制的自定义角色(RBAC Role). 我们也可以根据同样的原理把RBAC细粒度资源管理运用于微服务产品的开发部署中.(https:// ...

  2. Spring Cloud 配置服务

    Spring Cloud 配置服务 1. 配置服务简介 产生背景: 传统开发中,我们通常是将系统的业务无关配置(数据库,缓存服务器)在properties中配置,在这个文件中不会经常改变,但随着系统规 ...

  3. 【Linux】Ubuntu配置服务自启动 sysv-rc-conf

    在Ubuntu下,配置服务系统开机自启动,使用的不是chkconfig,而是sysv-rc-conf. 且看如下: 安装: sudo apt-get install sysv-rc-conf 帮助信息 ...

  4. 分布式监控系统开发【day37】:服务端生成配置数据(四)

    一.目录结构 二.引子与代码 1.客户端获取服务列表接口 1.解决了什么问题 客户端要给我获取服务列表的的时候,他肯定要告诉他是谁?他怎么告诉我,客户端必须有一个id号 Saltsack你装一个客户端 ...

  5. Spring Boot + Spring Cloud 构建微服务系统(九):配置中心(Spring Cloud Config)

    技术背景 如今微服务架构盛行,在分布式系统中,项目日益庞大,子项目日益增多,每个项目都散落着各种配置文件,且随着服务的增加而不断增多.此时,往往某一个基础服务信息变更,都会导致一系列服务的更新和重启, ...

  6. Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)

    本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...

  7. Win 10 开发中Adaptive磁贴模板的XML文档结构,Win10 应用开发中自适应Toast通知的XML文档结构

    分享两篇Win 10应用开发的XML文档结构:Win 10 开发中Adaptive磁贴模板的XML文档结构,Win10 应用开发中自适应Toast通知的XML文档结构. Win 10 开发中Adapt ...

  8. WCF学习心得------(三)配置服务

    配置服务 配置服务概述 在设计和实现服务协定后,便可以进行服务的配置.在其中可以定义和自定义如何向客户段公开服务,包括指定可以找到服务的地址,服务用于发送和接受消息的传输和消息编码,以及服务需要的安全 ...

  9. WCF分布式开发步步为赢(4):WCF服务可靠性传输配置与编程开发

    今天继续WCF分布式开发步步为赢系列的第4节:WCF服务可靠性传输配置与编程开发.这个章节,我们要介绍什么是WCF服务的可靠性传输,随便介绍网络协议的概念,Web Service为什么不支持可靠性传出 ...

随机推荐

  1. WPF学习笔记-TextBox光标位置如何放到最后?

    TextBox光标位置如何放到最后? 使用SelectionStart : TextBox.SelectionStart = TextBox.Text.Length;

  2. oracle 的一点累积

    1.  oracle用户相关 sqlplus sys/oracle as sysdba    -- sys登录 create user xxx identified by password;   -- ...

  3. [转]加盐hash保存密码的正确方式

    0x00 背景 大多数的web开发者都会遇到设计用户账号系统的需求.账号系统最重要的一个方面就是如何保护用户的密码.一些大公司的用户数据库泄露事件也时有发生,所以我们必须采取一些措施来保护用户的密码, ...

  4. 协程coroutine

    协程(coroutine)顾名思义就是“协作的例程”(co-operative routines).跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程 ...

  5. 【转】Android 菜单(OptionMenu)大全 建立你自己的菜单--不错

    原文网址:http://www.cnblogs.com/salam/archive/2011/04/04/2005329.html 菜单是用户界面中最常见的元素之一,使用非常频繁,在Android中, ...

  6. 【转】Javabyte[]数组和十六进制String之间的转换Util------包含案例和代码

    原文网址:http://blog.csdn.net/caijunjun1006/article/details/11740223 Java中byte用二进制表示占用8位,而我们知道16进制的每个字符需 ...

  7. 【动态规划】HDU 5492 Find a path (2015 ACM/ICPC Asia Regional Hefei Online)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5492 题目大意: 一个N*M的矩阵,一个人从(1,1)走到(N,M),每次只能向下或向右走.求(N+ ...

  8. 【模拟】Codeforces 699A Launch of Collider

    题目链接: http://codeforces.com/problemset/problem/699/A 题目大意: 给N个点,向左或向右运动,速度均为1,问最早什么时候有两个点相撞.无解输出-1 题 ...

  9. UVA 11478 Halum(差分约束)

    题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=34651 [思路] 差分约束系统. 设结点u上的操作和为sum[u] ...

  10. 关于连不上dc服务器的机器强行退出域的方法

    有时加入域的计算机无法连接到之前的域了,只能强制退出域了,有两个前提条件:   1.断开网络,就是拔掉网线或者禁用网卡.   2.使用本地管理员登陆.   然后命令行执行如下命令即可:   netdo ...