.net连接MySQL的方法 - KeenLeung - 博客园

mikel阅读(1146)

来源: .net连接MySQL的方法 – KeenLeung – 博客园

目录

摘自:http://www.cnblogs.com/huayangmeng/archive/2011/04/06/2006866.html

最近要用C#做一个东西,连接之前项目的数据库(用MySQL建的)。.net连接MySQL实现起来其实并不是很复杂的事,网上找找,方法挺多的,下面将我实现的方法简单记录一下:

1、首先上MySQL网站下驱动(以前学Java的时候,要用connecter来做):http://www.mysql.com/products/connector/

因为是用.net来连接MySQL,使用我们下载ADO.NET Driver for MySQL(这句废话,忽略。。。)。

2、安装下下来的安装包(这步就不说了,你懂的~)

3、我们在Visual Studio里创建一个Web Application,然后引入X:\XXXX\MySQL\MySQL Connector Net 6.3.6\Assemblies下的v2.0或v4.0里的DLL文件。

如图:

在MySQLHelper里面加入:

using MySql.Data.MySqlClient;

在Web.config里面加入MySQL数据库的连接字符串:

<connectionStrings>
<add name=”MySqlStr” connectionString=”server = localhost; user id = XXXX; password = XXXX; database = XXXX”/>
</connectionStrings>

这样,就完成了.net连接MySQL数据库的基础操作。

我在MySQL下建了一个名为tb_test的数据表,然后简单写了一个test方法(取出表中全部数据)。看效果:

看,已经可以了,成功连接MySQL。^_^

 

Win7-64bit系统下安装mysql的ODBC驱动 - Agoly - 博客园

mikel阅读(1499)

来源: Win7-64bit系统下安装mysql的ODBC驱动 – Agoly – 博客园

安装过mySQL数据库后,有些软件在调用mySQL数据库时不会直接调用,需要安装mySQL数据库的ODBC驱动,再来调用。这里就介绍下,如何在win7系统下安装mysql的ODBC驱动。

Win7系统分为32位和64位,相对应的mysqlODBC驱动也可以找到相应的32位和64位的。

下载地址:

http://dev.mysql.com/downloads/connector/odbc/

 

 

win7 32位系统上安装MySQ的ODBC驱动方法非常简单,下载相应MySQL版本的32位ODBC驱动就行,然后双击安装程序,按照提示步骤按照即可。但是在win7 64位系统上有些不太一样。有时候我们按照相应的mysql版本下载ODBC-64位的,可以正常安装,有时候却不行。

在正常安装过mysql的ODBC驱动后,还要对ODBC进行配置。正常情况下配置odbc的方法为:

打开控制面板找到管理工具,如果在控制面板下看不到管理工具,可以选择窗口右上角的查看方式,点击倒三角下拉菜单,选择里面的“小图标”,就可以看到如下图样式的显示方式,在里面找到“管理工具”。

打开“管理工具”窗口后,双击选择“数据源(ODBC)”选项。可以弹出ODBC配置窗口。

 

在弹出的ODBC数据源管理器中的“用户DSN”下选择第一个Dbase Files 这个选项,

点击“添加”会弹出创建新数据源窗口,在里面找到我们刚才安装的mysql ODBC 5.3这个驱动程序,如下图所示,点击确定弹出配置窗口。

在弹出的配置窗口中需要填写如下说所示的参数:

Data Soure Name 数据源名称可以定义,必填;

Description 说明选填;

TCP/IP Server要连接的mysql数据库所在电脑的IP地址或者服务器名称,如果是本机话就填入localhost,后面是Port是数据库所用端口。

这个是单选框跟下面的Named Pipe是同级的两者二选一,一般选择TCP/IP这个参数;下面的user是指数据库登陆名称,password登陆密码,database是要连接的数据库名称。

填好以上参数后点击“test”按钮进行连接测试。

连接成功的话会弹出如下图所示的Connection successful(连接成功)的提示。

 

 

异常问题:

以上是在win7系统上安装对应位数的mysql的ODBC驱动的步骤,也就是在win7 32位上安装32位的ODBC驱动,在win7 64位上安装64位的ODBC驱动。

除此之后,还有一种处理方法,当64位系统上安装不上64位的ODBC驱动,或者安装上无法使用时,可以在64位系统上安win7装32位的ODBC驱动,安装方法是一样的。就是在安装好32位mysql ODBC驱动后,在数据源(ODBC)中添加配置驱动时,发现里面并没有mysql ODBCD对应版本的驱动程序,这时需要安装下面的目录找到odbcad32.exe程序:

C:\Windows\SysWOW64\odbcad32.exe

双击运行改程序,接下来就可以按照其他的配置方法配置了。

之所以会介绍这种方法,除了操作系统兼容性外,还有一点就是有些程序需要用ODBC32位的才行,在开篇提到的来电弹屏电话管理系统:来电通来电弹屏软件就是如此,之前说过它可以直接跟管理软件的数据库对接,在使用mysql数据库的管理软件时,就需要安装mysql ODBC 32位的ODBC驱动

 

最后通过lr连接数据:

 

Delphi在调试的时候查看变量的值

mikel阅读(1578)

除非你的程序只有几行,否则几乎不可能一次写成,因此调试就非常必要。然而许多初学者不知道如何进行调试,写完程序就运行,发现结果不对再看代码,这样觉得非常吃力。这里,简单介绍一下Delphi所提供的调试功能。

1. 语法检查(Syntax Check)


Delphi提供了语法检查的功能,这个功能和正常编译很相似,同样也会给出各类错误和警告信息,但是不会生成代码。

Delphi的编译信息分为4类:Fault(故障),Error(错误),Warning(警告)和Hint(提示)。Fault是指导致编译程序不能继续工作的错误,例如内存溢出等;Error是指发现用户程序不符合某些规定而导致不能按照用户程序的要求进行处理;Warning是指用户程序使用了某些不符合规定的形式,但是并不影响编译程序生成目标文件;Hint是指编译程序对用户程序的某些形式提出了怀疑。

前两类信息是必须要解决的,否则你不能运行你的程序,但是往往会有很多人忽略后两种信息。然而,这些信息却是非常重要的。

2. 启动、暂停、中止集成调试程序


最基本、最重要的调试手段包括:单步跟踪、断点、变量观察、堆栈检查等。所有这些功能在Delphi的集成调试程序中都能提供。

当你按下F9(Compile and Run,编译并运行)一个程序时,就已经启动了Delphi的集成调试程序,而按下Ctrl+Break(Program Pause,程序暂停)时则会暂停被调试程序返回到集成调试程序中去,再次按下F9会从暂停地地方继续执行,而Alt+F2(Program Reset,程序复位)则会完全中止被调试程序的执行,返回集成调试程序中去。

3 单步跟踪(Step)


所谓单步跟踪是指一行一行地执行程序,每执行一行语句后就停下来等待指示,这样你就能够仔细了解程序的执行顺序,以及当时的各种状况。

注意:虽然Object Pascal允许在一行内书写任意多的语句,但是所有的单步跟踪都以“行”为单位,因此为了便于调试,主张在一行内只写一条语句,否则会给你带来很大的麻烦。

单步跟踪可以分为Step Over(跳过)、Trace Into(跟踪进入)和Trace to Next Source Line(跟踪到下一条源代码行)。

Step Over和Trace Into都是执行一行语句,差别在于遇到过程和函数时Trace Into将会进入过程和函数,而Step Over不会,而只会把过程和函数作为一条语句执行。

当使用Ctrl+Break暂停程序时,程序不一定停在你的源代码位置上,而可能是在操作系统或者其它模块中,此时集成调试程序会出现一个CPU窗口(CPU Window),用汇编指令的形式显示当前的内容,可以用Trace to Next Source Line继续执行程序,直到程序执行到第一条有源代码的地方。

4 断点(Breakpoint)


断点是调试中非常重要的一个手段。由于在执行到某些代码前需要执行许多其它代码,不可能用单步跟踪一条一条执行过来,这时只要在需要暂停的地方设置一个断点,然后让程序运行,当执行到这个断点位置时不需要用户干预就会暂停并返回集成调试程序。

Delphi提供了丰富的断点功能,包括:源代码断点、指令断点、数据断点等。

源代码断点(Source Breakpoint)是指在你的源程序中设置断点,指令断点(Address Breakpoint)是指在某机器指令处设置断点,数据断点(Data Breakpoint)是指当写入某变量时暂停用户程序。

所有的断点都可以设置更详细的属性,包括:条件、通过次数、组、高级操作等。

条件(Condition)是指触发断点的条件,例如你可以写:a=10,表示当a等于10时在这个断点位置暂停;

通过次数(Pass Count)是指即使符合条件,也需要执行这些次数才N暂停,例如在某断点设置通过次数为5,则表示当第5次通过这个断点时才暂停程序,当然,如果有条件存在的话还要符合相当次数的条件;

组(Group)是指一组断点,你可以用一个名字来标记许多断点,这样你可以用禁止或允许组(Disable Group/Enable Group)来同时打开或禁止多个断点。

高级操作是指和每个断点相关的一些行为(Action),具体如下:

中断(Break):中断程序,这是默认操作。
忽略后续异常(Ignore subsequent exceptions):通过这个断点后忽略所有异常(exceptions);
处理后续异常(Handle subsequent exceptions):通过这个断点后处理异常,这和前一个操作是对应的;
记录信息(Log message),通过这个断点时记录一条事件日志信息,你可以在事件日志(Event Log)中查看这条信息;
表达式求值(Eval expression):对指定的一个表达式进行求值,并且可以通过记录结果(Log result)把这个结果记录在日志中;
禁止/允许组(Enable group/Disable group):通过这个断点以后禁止或者允许其它的组,由此可以控制其它断点的状态。
在Delphi中除了上述的显式断点以外,还提供了隐式断点:运行到光标(Run to cursor)和运行到返回(Run until return)。

运行到光标是让程序到当前光标所在程序行,相当于你在当前光标位置设置了一个断点。这是一次性断点,并且如果在到达这里前遇到了其它断点,会停止在那个断点的地方,同时取消了这个临时断点。

运行到返回是用于过程和函数中,运行到过程和函数退出的位置,使得可以迅速返回上层调用程序。

5. 变量查看(Watch)/检查(Inspect)
在程序暂停的时候你可以用Watch查看某个变量,按Ctrl+F7(Add Watch,添加查看)可以在查看列表(Watch List)中增加一个变量。在Watch中你可以查看变量或者表达式,指定数据的格式,甚至可以指示Delphi调用某些函数,显示函数的返回值。

有一种快速查看模式,称为Local Variables(局部变量),按Ctrl+Alt+L能够显示这个窗体,里面是当前过程或函数的局部变量。

Delphi还支持一种临时的求值模式(Evaluate/Modify),按Ctrl+F4显示求值框,你可以在这里输入一个变量或者表达式,计算其数值,对于变量还可以在运行时改变它的值,这样如果你已经发现数据有错,你可以修改它,让程序继续运行下去,就像这个数值就是程序得出的一样。

检查(Inspect),是一种可以进一步查看变量信息的手段。把光标放在某个变量前,按Alt+F5显示检查窗。在这里可以看到有关这个变量的详细信息,包括:类型、值等,这对于类类型、记录类型尤其有用。和Evaluate/Modify一样,你也可以改变这些值。

6. 调用堆栈(Call Stack)
对于某些递归调用和复杂的嵌套调用来说,使用Call Stack功能能够方便的检查函数的调用情况。

按Ctrl+Alt+S可以显示这个窗体,在最上面的是当前过程或函数,在最下面的往往是你的主程序。例如:

TForm1.Button1Click(???);
Project1
这表示Project1调用了方法TForm1.Button1Click,由于其参数是一个对象(Sender:TObject),不能求值,所以用???表示。双击Project1可以看出在什么地方调用了TForm1.Button1Click(如果调用点没有源代码,则显示有源代码的第一行)。

7. 高级调试功能

上面所说的是常规的调试功能,Delphi还提供了很多高级调试功能。

线程状态(Thread Status):显示当前程序中有多少线程在运行,各线程的状态是什么?参数是什么?
模块(Modules):显示当前进程使用了多少模块,其名称和地址是多少?这对于调试DLL时很有用。
CPU/FPU:在汇编语言层次显示代码,这能够更加精确地观察程序是如何运行的,各寄存器是怎么变化的。
进程附着(Attach Process):为了调试某些特殊程序(例如Windows 2000下的服务【Service】),允许先运行用户程序,再运行调试程序。
远程调试(Remote Debug):允许在一台计算机上运行用户程序,在另外一台计算机上运行Delphi,通过网络进行调试,这对于调试大型程序很有用,也能调试那些对系统有特殊要求的程序。

Delphi变量查看(Watch)/检查(Inspect)

在程序暂停的时候你可以用Watch查看某个变量,按 Ctrl+F7(Add Watch,添加查看)可以在查看列表(Watch List)中增加一个变量。在Watch中你可以查看变量或者表达式,指定数据的格式,甚至可以指示Delphi调用某些函数,显示函数的返回值。

有一种快速查看模式,称为Local Variables(局部变量),按 Ctrl+Alt+L 能够显示这个窗体,里面是当前过程或函数的局部变量。

Delphi还支持一种临时的求值模式(Evaluate/Modify),按 Ctrl+F4 显示求值框,你可以在这里输入一个变量或者表达式,计 算其数值,对于变量还可以在运行时改变它的值,这样如果你已经发现数据有错,你可以修改它,让程序继续运行下去,就像这个数值就是程序得出的一样。

检查(Inspect),是一种可以进一步查看变量信息的手段。把光标放在某个变量前,按Alt+F5显示检查窗。在这里可以看到有关这个变量的详细信息,包括:类型、值等,这对于类类型、记录类型尤其有用。和Evaluate/Modify一样,你也可以改变这些值。

MySql与VS2010 C#连接方法之ODBC - CSDN博客

mikel阅读(1958)

MySQL与VS2010 C#连接方法之ODBC

来源: MySql与VS2010 C#连接方法之ODBC – CSDN博客

  • 测试电脑是64位的,32位电脑没有测试过,仅供参考。

  • 1.还未安装MySQL:先安装mysql-x.x.x,64位或32位都可以,安装过程略
  • 2.已经安装了mysql的:下载安装mysql-connector-odbc-x.x.x win32(官网有),必须要是32位的,与MySql是64还是32 无关,而且obdc- x.x.x的版本第一个数字要和mysql的第一个数字对上。
  • 3.OBDC数据源还未配置成功的:在cmd中输入C:\windows\sysWOW64\odbcad32.exe,创建数据源,root和password同mysql,接着test。
    (解释:C:\windows\sysWOW64\odbcad32.exe显示的是32位的数据源,通过控制面板设置的数据源是64位的,驱动程序的位数和数据库程序的位数无关)
    这里写图片描述

    这里写图片描述

  • 4.数据源连接successful之后,在vs的Server Explorer界面中点击Connecte to Database按钮
    这里写图片描述
  • 5.选Microsoft ODBC Data Source,填写信息,Test Connection,连接succeeded后,也就是MySql和Visual Studio连接成功。如果出现“在指定的 DSN 中,驱动程序和应用程序之间的体系结构不匹配”的问题,请返回第三步。确定做好第三步还出现该问题的,请另行谷歌或百度。
    这里写图片描述
  • 6.编写代码,可以复制connection string——例:Dsn=MySqlOCBC;uid=root
    这里写图片描述
  • 7.ODBC连接语句(仅供参考)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Odbc;
namespace DAL
{
    public class SqlHelper
    {
        public int test()
        {
            string conStr = @"Dsn=MySqlOCBC;uid=root";
            OdbcConnection conn = new OdbcConnection(conStr);
            conn.Open();
            string sql = "insert into category(name) values('asdf')";
            OdbcCommand cmd = new OdbcCommand(sql, conn);
            int res = cmd.ExecuteNonQuery();
            conn.Close();
            return res;
        }
    }
}

VS2010连接Mysql数据库图解详细教程 - heikeyuit - 博客园

mikel阅读(1031)

来源: VS2010连接Mysql数据库图解详细教程 – heikeyuit – 博客园

今天学习.net多态和接口,想到了vs2010可以连接多个数据库,所以就想连接下以前学习php用的数据库,但是怎么连接啊,遇到了很多问题,最后自己查阅了很多资料,总结一下,供大家参考。

首先需要安装.net连接mysql的驱动,去http://dev.mysql.com/downloads/connector/net/下载并安装。
1、然后你就可以在vs的server explorer添加连接了。


2、连接上数据库后一定要添加引用啊


好了,可以用了,下面引用我的查询代码

class Program

{

MySQLConnection mconn = null; //声明连接对象

mconn = new MySQLConnection(“server=localhost;user id=用户名;Password=密码;database=数据库名;persist security info=False”);//创建对象

MySQLCommand mcmd = new MySqlCommand();//创建MySqlCommand对象

mcmd.Connection = mconn;

mcmd.CommandText = “SELECT * FROM test;”;//查询命令
try
{

mconn.Open();//数据库打开连接

MySqlDataReader mReader = mcmd.ExecuteReader();//创建MySqlDataReader对象

while (mReader.Read())//进行数据读取
{
Console.WriteLine(mReader.GetString(mReader.GetOrdinal(“Name”)));//输出查询数据中的某个字段
}
}
catch (Exception er)
{
Console.WriteLine(er.Message);
}
finally
{
mconn.Close();//关闭数据库连接
}

}

WinForm皮肤 支持.NET4.0 IrisSkin4多彩皮肤演示和下载 - yzeng - 博客园

mikel阅读(1463)

来源: WinForm皮肤 支持.NET4.0 IrisSkin4多彩皮肤演示和下载 – yzeng – 博客园

IrisSkin4是一款.NET平台非常优秀的Winform皮肤,链接库文件仅544kb,使用方法也非常简单

IrisSkin4(IrisSkin4.dll + 73套皮肤)【下载地址】

使用方法:

//仅在首个窗体中添加此代码即可实现所有窗体皮肤
new Sunisoft.IrisSkin.SkinEngine().SkinFile ="skins/MacOS.ssk";

效果图如下:

【Dome源码及IrisSkin4打包下载】

中小研发团队架构实践之开篇 - arch-system - 博客园

mikel阅读(983)

来源: 中小研发团队架构实践之开篇 – arch-system – 博客园

      中小型研发团队很多,而社区在中小型研发团队架构实践方面的探讨却很少。中小型研发团队特别是50至200人的研发团队,在早期的业务探索阶段,更多关注业务逻辑,快速迭代以验证商业模式,很少去关注技术架构。这时如果继续按照原有的架构及研发模式,会出现大量的问题,再也无法玩下去了。能不能有一套可直接落地、基于开源、成本低,可快速搭建的中间件及架构升级方案呢?我是一个有十多年经验的IT老兵,曾主导了两家公司的技术架构升级改造,现抛砖引玉,与大家一起探讨这方面的问题。整个系列有18篇文章,可分为三个部分,包括框架篇、架构篇和公共应用篇。框架篇即中间件或工具的使用,如缓存、消息队列、集中式日志、度量、微服务框架等,工欲善其事,必先利其器。架构篇主要是设计思想的提升,有企业总体架构、单个项目架构设计、统一应用分层等。公共应用篇是业务与技术的结合,有单点登录和企业支付网关,以下是具体篇章的介绍:

一、框架篇——工欲善其事,必先利其器

       如果说运维是地基,那么框架就是承重墙。农村建住房是一块砖一块砖地往上垒,而城市建大House则是先打地基,再建承重墙,最后才是垒砖,所以中间件的搭建和引进是建设高可用、高性能、易扩展可伸缩的大中型系统的前提。框架篇中的每篇主要由四部分组成:它是什么、工作原理、使用场景和可直接调试的Demo。其中Demo及中间件是历经两家公司四年时间的考验,涉及几百个应用,100多个库1万多张表,日订单从几万张到十几万,年GMV从几十亿到几百亿。所有中间件及工具都是基于开源,早期我们也有部分自主研发如集中式日志和度量框架。后期在第二家公司时为了快速地搭建,降低成本,易于维护和扩展,全部改为开源。这样不仅利于个人的学习成长、知识重用和职业生涯,也利于团队的组建和人才的引进。

1、集中式缓存Redis

      缓存是计算机的难题之一,分布式缓存亦是如此。Redis看起来非常简单,但它影响着系统的效率、性能、数据一致性。用好它不容易,具体包括:缓存时长(复杂多维度的计算)、缓存失效处理(主动更新)、缓存键(Hash和方便人工干预)、缓存内容及数据结构的选择、缓存雪崩的处理、缓存穿透的处理等。Redis除了缓存的功能,还有其它功能如Lua计算能力、Limit与Session时间窗口、分布式锁等。我们使用ServiceStack.Redis做客户端,使用方法详见Demo。

2、消息队列RabbitMQ

      消息队列好比葛洲坝,有大量数据的堆积能力,然后再可靠地进行异步输出。它是EDA事件驱动架构的核心,也是CQRS同步数据的关键。为什么选择RabbitMQ而没有选择Kafka,因为业务系统有对消息的高可靠性要求,以及对复杂功能如消息确认Ack的要求。

3、集中式日志ELK

       日志主要分为系统日志和应用日志两类。试想一下,你该如何在一个具有几百台服务器的集群中定位到问题?如何追踪每天产生的几G甚至几T的数据?集中式日志就是此类问题的解决方案。早期我们使用自主研发的Log4Net+MongoDB来收集和检索日志信息,但随着数据量的增加,查询速度却变得越来越慢。后期改为开源的ELK,虽然易用性有所下降,但它支持海量数据以及与编程语言无关的特征。下图是ELK的架构图。

4、任务调度Job

       任务调度Job如同数据库作业或Windows计划任务,是分布式系统中异步和批处理的关键。我们的Job分为WinJob和HttpJob:WinJob是操作系统级别的定时任务,使用开源的框架Quartz.NET实现;而HttpJob则是自主研发实现,采用URL方式可定时调用微服务。HttpJob借助集群巧妙地解决了WinJob的单点和发布问题,并集中管理所有的调度规则,调度规则有简单规则和Cron表达式。HttpJob它简单易用,但间隔时间不能低于1分钟,毕竟通过URL方式来调度并不高效。下图是HttpJob的管理后台。

5、应用监控Metrics

       “没有度量就没有提升”,度量是改进优化的基础,是做好一个系统的前置条件。Zabbix一般用于系统级别的监控,Metrics则用于业务应用级别的监控。业务应用是个黑盒子,通过数据埋点来收集应用的实时状态,然后展示在大屏或看板上。它是报警系统和数字化管理的基础,还可以结合集中式日志来快速定位和查找问题。我们的业务监控系统使用Metrics.NET+InfluxDB+Grafana。

6、微服务框架MSA

       微服务是细粒度业务行为的重用,需要与业务能力及业务阶段相匹配。微服务框架是实现微服务及分布式架构的关键组件,我们的微服务框架是基于开源ServiceStack来实现。它简单易用、性能好,文档自动生成、方便调试测试,调试工具Swagger UI、自动化接口测试工具SoapUI。微服务的接口开放采用我们自主研发的微服务网关,通过治理后台简单的配置即可。网关以NIO、IOCP的方式实现高并发,主要功能有鉴权、超时、限流、熔断、监控等,下图是Swagger UI调试工具。

7、搜索利器Solr

       分库分表后的关联查询,大段文本的模糊查询,这些要如何实现呢?显然传统的数据库没有很好的解决办法,这时可以借助专业的检索工具。全文检索工具Solr不仅简单易用性能好,而且支持海量数据高并发,只需实现系统两边数据的准实时或定时同步即可。下图是Solr的工作原理。

8、更多工具

  • 分布式协调器ZooKeeper:ZK工作原理、配置中心、Master选举、Demo,一篇足以;
  • ORM框架:Dapper.NET语法简单、运行速度快,与数据库无关,SQL自主编写可控,是一款适合于互联网系统的数据库访问工具;
  • 对象映射工具EmitMapper和AutoMapper:EmitMapper性能较高,AutoMapper易用性较好;
  • IoC框架:控制反转IoC轻量级框架Autofac;
  • DLL包管理:公司内部DLL包管理工具NuGet,可解决DLL集中存储、更新、引用、依赖问题;
  • 发布工具Jenkins:一键编译、发布、自动化测试、一键回滚,高效便捷故障低。

二、架构篇——思想提升

       会使用以上框架并不一定能成为优秀的架构师,但一位优秀架构师一定会使用框架。架构师除了会使用工具外,还需要设计思想的提升和性能调优技能。此篇以真实项目为背景,思想方法追求简单有效,主要内容包括企业总体架构、单个项目架构设计、统一应用分层、调试工具WinDbg。

1、企业总体架构

       当我们有了几百个上千个应用后,不仅仅需要单个项目的架构设计,还需要企业总体架构做顶层思考和指导。大公司与小商贩的商业思维是一样的,但大公司比较难看到商业全貌和本质。而小公司又缺乏客户流量和中间件的应用场景,中型公司则兼而有之,所以企业总体架构也相对好落地。企业总体架构需要在技术、业务、管理之间游刃有余地切换,它包括业务架构、应用架构、数据架构和技术架构。附档是一份脱敏感信息后的真实案例,有参考TOGAF标准。但内容以解决公司系统的架构问题为导向、以时间为主线,包括企业商务模型、架构现状、架构规划和架构实施。

2、单个项目架构设计

       单个项目的架构设计如同施工图纸,能直接指导工程代码的实施。上一环是功能需求,下一环是代码实施,这是架构设计的价值所在。从功能需求到用例,到用例活动图,到领域图、架构分层,到核心代码,它们之间环环相扣。做不好领域图可能源自没有做好用例活动图,因为用例活动图是领域图的上一环。关注职责、边界、应用关系、存储、部署是架构设计的核心,下图是具体案例参考。

3、统一应用分层

给应用分层这件事情很简单,但是让一家公司的几百个应用采用统一的分层结构,这可不是件简单的事情。它要做到可大可小、简单易用、支持多种场景,我们使用IPO方式:I表示Input、O表示Output、P表示Process,一进一出一处理。应用系统的本质就是机器,是处理设备,也是一进一出一处理,IPO方式相对于DDD而言更为简单实用。

4、调试工具WinDbg

       生产环境偶尔会出现一些异常问题,而WinDbg或GDB就是解决此类问题的利器。调试工具WinDbg如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具,Dump文件类似于飞机的黑匣子,记录着生产环境程序运行的状态。本文主要介绍了调试工具WinDbg和抓包工具ProcDump的使用,并分享一个真实的案例。N年前不知谁写的代码,导致每一两个月偶尔出现CPU飙高的现象。我们先使用ProcDump在生产环境中抓取异常进程的Dump文件,然后在不了解代码的情况下通过WinDbg命令进行分析,最终定位到有问题的那行代码。

三、公共应用篇——业务与技术的结合

       先工具再框架,然后架构设计,最后深入公共应用。这不仅是架构升级改造的正确路径,也是微服务架构实施的正确路径。公共应用因为与业务系统结合紧密,但又具有一定的独立性,所以一般自主开发,不使用开源也不方便开源。公共应用主要包括单点登录、企业支付网关、CTI通讯网关(短信邮件微信),此次分享单点登录和企业支付网关。

1、单点登录

       应用拆分后总要合在一起,拆分是应用实施层面的拆分,合成是用户层面的合成,而合成必须解决认证和导航问题。单点登录SSO即只需要登录一次,便可到处访问,它是建立在用户系统、权限系统、认证系统和企业门户的基础上。我们的凭证数据Token使用JWT标准,以解决不同语言、不同客户端、跨WebAPI的安全问题。

2、企业支付网关

       企业支付网关集中和封装了公司的各大支付,例如支付宝、财付通、微信、预付款等。它统一了业务系统调用各支付接口的方式,简化了业务系统与支付系统的交互。它将各种支付接口统一为支付、代扣、分润、退款、退分润、补差、转账、冻结、解冻、预付款等,调用时只需选择支付类型即可。企业支付网关将各大支付系统进行集中的设计、研发、部署、监控、维护,提供统一的加解密、序列化、日志记录,安全隔离。
 
       在接下来的一段时间里,我会陆续推出此系列文章。因个人原因,发布顺序会根据本人的准备情况而作调整,敬请谅解。根据我们以往的经验,分享者主讲一个小时左右,业务研发就可以快速地进入项目实战。对于后面新加入的团队成员,也可通过WIKI自主快速学习。这是我们之前对自己的要求,尽量降低工具对人员的要求,简单实用、降低成本。文章中部分Demo采用C#语言,但到了框架或架构层面,与语言本身没有太多直接的关系。如RabbitMQ、Job、Redis和集中式日志ELK,它们服务端的部署是一样的,只是客户端语言版本稍有不同。所有Demo都可直接运行,服务地址及管理后台也可直接访问。因为部署在公有云,牵涉到成本费用的问题,我计划持续到明年3月底。以上这些小小的基础工作,希望能够帮到中小型研发团队,解决他们项目中遇到的实际问题。愿与你一起成长,你的分享和点赞是我此次付出的动力,谢谢!

所有Demo下载:

dedecms支持动态传参 - CSDN博客

mikel阅读(1005)

 

来源: dedecms支持动态传参 – CSDN博客

事情:

文章中添加联动信息。根据不同的分类显示不同的列表就成了大问题。网上找了找没有找到自己满意的。索性自己搞二次开发…..

解决:该模型使用了独立模型。

1. 修改/plus/list.php ,添加新参支持,classid为浏览器传入的参数。


//---add 添加classid支持
if(empty($classid)){
include(DEDEINC."/arc.sglistview.class.php");
$lv = new SgListView($tid,$cArr);
}else{
include(DEDEINC."/arc.nsglistview.class.php");
$lv = new NsgListView($classid,$tid,$cArr);
}

2.  将arc.sglistview.class.php 复制一份改成名为 arc.nsglistview.class.php

  •  将Sglistview改成NsglistView
  •  修改构造参数添加$classid
  • php4调用也添加$classid支持
  • 添加全局变量支持     var $classid;
  • 放开添加表支持


//--add---放开使用添加表
// if($naddQuery!='')
// {
$this->sAddTable = true;
$this->addSQL .= $naddQuery."AND arc.xuexiaozuhe = '{$this->classid}'";
// }

更改后的文件内容为:


$this->TypeID = $typeid;
$this->dSQL = $dsql;
$this->CrossID = '';
$this->IsReplace = false;
$this->IsError = false;
$this->dtp = new DedeTagParse();
$this->dtp->SetRefObj($this);
$this->sAddTable = false;
$this->dtp->SetNameSpace("dede","{","}");
$this->dtp2 = new DedeTagParse();
$this->dtp2->SetNameSpace("field","[","]");
$this->TypeLink = new TypeLink($typeid);
$this->searchArr = $searchArr;
if(!is_array($this->TypeLink->TypeInfos))
{
$this->IsError = true;
}
if(!$this->IsError)
{
$this->ChannelUnit = new ChannelUnit($this->TypeLink->TypeInfos['channeltype']);
$this->Fields = $this->TypeLink->TypeInfos;
$this->Fields['id'] = $typeid;
$this->Fields['position'] = $this->TypeLink->GetPositionLink(true);
$this->Fields['title'] = preg_replace("/[<>]/", " / ", $this->TypeLink->GetPositionLink(false));

//获得附加表和列表字段信息
$this->AddTable = $this->ChannelUnit->ChannelInfos[‘addtable’];
$listfield = trim($this->ChannelUnit->ChannelInfos[‘listfields’]);

$this->ListFields = explode(‘,’, $listfield);

//设置一些全局参数的值
foreach($GLOBALS[‘PubFields’] as $k=>$v) $this->Fields[$k] = $v;
$this->Fields[‘rsslink’] = $GLOBALS[‘cfg_cmsurl’].”/data/rss/”.$this->TypeID.”.xml”;

//设置环境变量
SetSysEnv($this->TypeID,$this->Fields[‘typename’],0,”,’list’);
$this->Fields[‘typeid’] = $this->TypeID;

//获得交叉栏目ID
if($this->TypeLink->TypeInfos[‘cross’]>0 && $this->TypeLink->TypeInfos[‘ispart’]==0)
{
$selquery = ”;
if($this->TypeLink->TypeInfos[‘cross’]==1)
{
$selquery = “SELECT id,topid FROM `#@__arctype` WHERE typename LIKE ‘{$this->Fields[‘typename’]}’ AND id<>'{$this->TypeID}’ AND topid<>'{$this->TypeID}’ “;
}
else
{
$this->Fields[‘crossid’] = preg_replace(“/[^0-9,]/”, ”, trim($this->Fields[‘crossid’]));
if($this->Fields[‘crossid’]!=”)
{
$selquery = “SELECT id,topid FROM `#@__arctype` WHERE id IN({$this->Fields[‘crossid’]}) AND id<>{$this->TypeID} AND topid<>{$this->TypeID} “;
}
}
if($selquery!=”)
{
$this->dsql->SetQuery($selquery);
$this->dsql->Execute();
while($arr = $this->dsql->GetArray())
{
$this->CrossID .= ($this->CrossID==” ? $arr[‘id’] : ‘,’.$arr[‘id’]);
}
}
}

}//!error

}

//php4构造函数
function NsgListView($classid,$typeid,$searchArr=array()){
$this->__construct($classid,$typeid,$searchArr);
}
//关闭相关资源
function Close()
{

}

/**
* 统计列表里的记录
*
* @access public
* @return void
*/
function CountRecord()
{
global $cfg_list_son;

//统计数据库记录
$this->TotalResult = -1;
if(isset($GLOBALS[‘TotalResult’])) $this->TotalResult = $GLOBALS[‘TotalResult’];
if(isset($GLOBALS[‘PageNo’])) $this->PageNo = $GLOBALS[‘PageNo’];
else $this->PageNo = 1;
$this->addSql = ” arc.arcrank > -1 “;

//栏目id条件
if(!empty($this->TypeID))
{
if($cfg_list_son==’N’)
{
if($this->CrossID==”) $this->addSql .= ” AND (arc.typeid='”.$this->TypeID.”‘) “;
else $this->addSql .= ” AND (arc.typeid IN({$this->CrossID},{$this->TypeID})) “;
}
else
{
if($this->CrossID==”) $this->addSql .= ” AND (arc.typeid IN (“.GetSonIds($this->TypeID,$this->Fields[‘channeltype’]).”) ) “;
else $this->addSql .= ” AND (arc.typeid IN (“.GetSonIds($this->TypeID,$this->Fields[‘channeltype’]).”,{$this->CrossID}) ) “;
}
}

$naddQuery = ”;
//地区与信息类型条件
if(count($this->searchArr) > 0)
{
if(!empty($this->searchArr[‘nativeplace’]))
{
if($this->searchArr[‘nativeplace’] % 500 ==0 )
{
$naddQuery .= ” AND arc.nativeplace >= ‘{$this->searchArr[‘nativeplace’]}’ AND arc.nativeplace < ‘”.($this->searchArr[‘nativeplace’]+500).”‘”;
}
else
{
$naddQuery .= “AND arc.nativeplace = ‘{$this->searchArr[‘nativeplace’]}'”;
}
}
if(!empty($this->searchArr[‘infotype’]))
{
if($this->searchArr[‘infotype’] % 500 ==0 )
{
$naddQuery .= ” AND arc.infotype >= ‘{$this->searchArr[‘infotype’]}’ AND arc.infotype < ‘”.($this->searchArr[‘infotype’]+500).”‘”;
}
else
{
$naddQuery .= “AND arc.infotype = ‘{$this->searchArr[‘infotype’]}'”;
}
}
if(!empty($this->searchArr[‘keyword’]))
{
$naddQuery .= “AND arc.title like ‘%{$this->searchArr[‘keyword’]}%’ “;
}
}

//–add—放开使用添加表
// if($naddQuery!=”)
// {
$this->sAddTable = true;
$this->addSql .= $naddQuery.”AND arc.xuexiaozuhe = ‘{$this->classid}'”;
// }

if($this->TotalResult==-1)
{
if($this->sAddTable)
{
$cquery = “SELECT COUNT(*) AS dd FROM `{$this->AddTable}` arc WHERE “.$this->addSql;
}
else
{
$cquery = “SELECT COUNT(*) AS dd FROM `#@__arctiny` arc WHERE “.$this->addSql;
}
$row = $this->dsql->GetOne($cquery);
if(is_array($row))
{
$this->TotalResult = $row[‘dd’];
}
else
{
$this->TotalResult = 0;
}
}
//初始化列表模板,并统计页面总数
$tempfile = $GLOBALS[‘cfg_basedir’].$GLOBALS[‘cfg_templets_dir’].”/”.$this->TypeLink->TypeInfos[‘templist’];
$tempfile = str_replace(“{tid}”,$this->TypeID,$tempfile);
$tempfile = str_replace(“{cid}”,$this->ChannelUnit->ChannelInfos[‘nid’],$tempfile);
if(!file_exists($tempfile))
{
$tempfile = $GLOBALS[‘cfg_basedir’].$GLOBALS[‘cfg_templets_dir’].”/”.$GLOBALS[‘cfg_df_style’].”/list_default_sg.htm”;
}
if(!file_exists($tempfile)||!is_file($tempfile))
{
echo “模板文件不存在,无法解析文档!”;
exit();
}
$this->dtp->LoadTemplate($tempfile);
$ctag = $this->dtp->GetTag(“page”);
if(!is_object($ctag))
{
$ctag = $this->dtp->GetTag(“list”);
}
if(!is_object($ctag))
{
$this->PageSize = 20;
}
else
{
if($ctag->GetAtt(‘pagesize’)!=”)
{
$this->PageSize = $ctag->GetAtt(‘pagesize’);
}
else
{
$this->PageSize = 20;
}
}
$this->TotalPage = ceil($this->TotalResult/$this->PageSize);
}

/**
* 列表创建HTML
*
* @access public
* @param string $startpage 开始页面
* @param string $makepagesize 生成尺寸
* @return string
*/
function MakeHtml($startpage=1,$makepagesize=0)
{
if(empty($startpage))
{
$startpage = 1;
}

//创建封面模板文件
if($this->TypeLink->TypeInfos[‘isdefault’]==-1)
{
echo ‘这个类目是动态类目!’;
return ”;
}

//单独页面
else if($this->TypeLink->TypeInfos[‘ispart’]>0)
{
$reurl = $this->MakePartTemplets();
return $reurl;
}

$this->CountRecord();
//初步给固定值的标记赋值
$this->ParseTempletsFirst();
$totalpage = ceil($this->TotalResult/$this->PageSize);
if($totalpage==0)
{
$totalpage = 1;
}
CreateDir(MfTypedir($this->Fields[‘typedir’]));
$murl = ”;
if($makepagesize > 0)
{
$endpage = $startpage+$makepagesize;
}
else
{
$endpage = ($totalpage+1);
}
if( $endpage >= $totalpage+1 )
{
$endpage = $totalpage+1;
}
if($endpage==1)
{
$endpage = 2;
}
for($this->PageNo=$startpage; $this->PageNo < $endpage; $this->PageNo++)
{
$this->ParseDMFields($this->PageNo,1);
$makeFile = $this->GetMakeFileRule($this->Fields[‘id’],’list’,$this->Fields[‘typedir’],”,$this->Fields[‘namerule2’]);
$makeFile = str_replace(“{page}”,$this->PageNo,$makeFile);
$murl = $makeFile;
if(!preg_match(“/^\//”,$makeFile))
{
$makeFile = “/”.$makeFile;
}
$makeFile = $this->GetTruePath().$makeFile;
$makeFile = preg_replace(“/\/{1,}/”, “/”, $makeFile);
$murl = $this->GetTrueUrl($murl);
$this->dtp->SaveTo($makeFile);
}
if($startpage==1)
{
//如果列表启用封面文件,复制这个文件第一页
if($this->TypeLink->TypeInfos[‘isdefault’]==1
&& $this->TypeLink->TypeInfos[‘ispart’]==0)
{
$onlyrule = $this->GetMakeFileRule($this->Fields[‘id’],”list”,$this->Fields[‘typedir’],”,$this->Fields[‘namerule2’]);
$onlyrule = str_replace(“{page}”,”1″,$onlyrule);
$list_1 = $this->GetTruePath().$onlyrule;
$murl = MfTypedir($this->Fields[‘typedir’]).’/’.$this->Fields[‘defaultname’];
$indexname = $this->GetTruePath().$murl;
copy($list_1,$indexname);
}
}
return $murl;
}

/**
* 显示列表
*
* @access public
* @return void
*/
function Display()
{
if($this->TypeLink->TypeInfos[‘ispart’]>0 && count($this->searchArr)==0 )
{
$this->DisplayPartTemplets();
return ;
}
$this->CountRecord();
/*
if((empty($this->PageNo) || $this->PageNo==1) && $this->TypeLink->TypeInfos[‘ispart’]==1)
{
$tmpdir = $GLOBALS[‘cfg_basedir’].$GLOBALS[‘cfg_templets_dir’];
$tempfile = str_replace(“{tid}”,$this->TypeID,$this->Fields[‘tempindex’]);
$tempfile = str_replace(“{cid}”,$this->ChannelUnit->ChannelInfos[‘nid’],$tempfile);
$tempfile = $tmpdir.”/”.$tempfile;
if(!file_exists($tempfile))
{
$tempfile = $tmpdir.”/”.$GLOBALS[‘cfg_df_style’].”/index_default_sg.htm”;
}
$this->dtp->LoadTemplate($tempfile);
}
*/
$this->ParseTempletsFirst();
$this->ParseDMFields($this->PageNo,0);
$this->dtp->Display();
}

/**
* 创建单独模板页面
*
* @access public
* @return string
*/
function MakePartTemplets()
{
$this->PartView = new PartView($this->TypeID,false);
$this->PartView->SetTypeLink($this->TypeLink);
$nmfa = 0;
$tmpdir = $GLOBALS[‘cfg_basedir’].$GLOBALS[‘cfg_templets_dir’];
if($this->Fields[‘ispart’]==1)
{
$tempfile = str_replace(“{tid}”,$this->TypeID,$this->Fields[‘tempindex’]);
$tempfile = str_replace(“{cid}”,$this->ChannelUnit->ChannelInfos[‘nid’],$tempfile);
$tempfile = $tmpdir.”/”.$tempfile;
if(!file_exists($tempfile))
{
$tempfile = $tmpdir.”/”.$GLOBALS[‘cfg_df_style’].”/index_default_sg.htm”;
}
$this->PartView->SetTemplet($tempfile);
}
else if($this->Fields[‘ispart’]==2)
{
//跳转网址
return $this->Fields[‘typedir’];
}
CreateDir(MfTypedir($this->Fields[‘typedir’]));
$makeUrl = $this->GetMakeFileRule($this->Fields[‘id’],”index”,MfTypedir($this->Fields[‘typedir’]),$this->Fields[‘defaultname’],$this->Fields[‘namerule2’]);
$makeUrl = preg_replace(“/\/{1,}/”, “/”, $makeUrl);
$makeFile = $this->GetTruePath().$makeUrl;
if($nmfa==0)
{
$this->PartView->SaveToHtml($makeFile);
}
else
{
if(!file_exists($makeFile))
{
$this->PartView->SaveToHtml($makeFile);
}
}
return $this->GetTrueUrl($makeUrl);
}

/**
* 显示单独模板页面
*
* @access public
* @return void
*/
function DisplayPartTemplets()
{
$this->PartView = new PartView($this->TypeID,false);
$this->PartView->SetTypeLink($this->TypeLink);
$nmfa = 0;
$tmpdir = $GLOBALS[‘cfg_basedir’].$GLOBALS[‘cfg_templets_dir’];
if($this->Fields[‘ispart’]==1)
{
//封面模板
$tempfile = str_replace(“{tid}”,$this->TypeID,$this->Fields[‘tempindex’]);
$tempfile = str_replace(“{cid}”,$this->ChannelUnit->ChannelInfos[‘nid’],$tempfile);
$tempfile = $tmpdir.”/”.$tempfile;
if(!file_exists($tempfile))
{
$tempfile = $tmpdir.”/”.$GLOBALS[‘cfg_df_style’].”/index_default_sg.htm”;
}
$this->PartView->SetTemplet($tempfile);
}
else if($this->Fields[‘ispart’]==2)
{
//跳转网址
$gotourl = $this->Fields[‘typedir’];
header(“Location:$gotourl”);
exit();
}
CreateDir(MfTypedir($this->Fields[‘typedir’]));
$makeUrl = $this->GetMakeFileRule($this->Fields[‘id’],”index”,MfTypedir($this->Fields[‘typedir’]),$this->Fields[‘defaultname’],$this->Fields[‘namerule2’]);
$makeFile = $this->GetTruePath().$makeUrl;
if($nmfa==0)
{
$this->PartView->Display();
}
else
{
if(!file_exists($makeFile))
{
$this->PartView->Display();
}
else
{
include($makeFile);
}
}
}

/**
* 获得站点的真实根路径
*
* @access public
* @return string
*/
function GetTruePath()
{
$truepath = $GLOBALS[“cfg_basedir”];
return $truepath;
}

/**
* 获得真实连接路径
*
* @access public
* @param string $nurl 连接地址
* @return string
*/
function GetTrueUrl($nurl)
{
if(preg_match(“/^http:\/\//”, $nurl)) return $nurl;
if($this->Fields[‘moresite’]==1)
{
if($this->Fields[‘sitepath’]!=”)
{
$nurl = preg_replace(“/^”.$this->Fields[‘sitepath’].”/”, ”, $nurl);
}
$nurl = $this->Fields[‘siteurl’].$nurl;
}
return $nurl;
}

/**
* 解析模板,对固定的标记进行初始给值
*
* @access private
* @return void
*/
function ParseTempletsFirst()
{
if(isset($this->TypeLink->TypeInfos[‘reid’]))
{
$GLOBALS[‘envs’][‘reid’] = $this->TypeLink->TypeInfos[‘reid’];
}
$GLOBALS[‘envs’][‘channelid’] = $this->TypeLink->TypeInfos[‘channeltype’];
$GLOBALS[‘envs’][‘typeid’] = $this->TypeID;
$GLOBALS[‘envs’][‘cross’] = 1;
MakeOneTag($this->dtp,$this);
}

/**
* 解析模板,对内容里的变动进行赋值
*
* @access public
* @param int $PageNo 页码
* @param int $ismake 是否编译
* @return void
*/
function ParseDMFields($PageNo, $ismake=1)
{
//替换第二页后的内容
if(($PageNo>1 || strlen($this->Fields[‘content’])<10 ) && !$this->IsReplace)
{
$this->dtp->SourceString = str_replace(‘[cmsreplace]’,’display:none’,$this->dtp->SourceString);
$this->IsReplace = true;
}
foreach($this->dtp->CTags as $tagid=>$ctag)
{
if($ctag->GetName()==”list”)
{
$limitstart = ($this->PageNo-1) * $this->PageSize;
$row = $this->PageSize;
if(trim($ctag->GetInnerText())==””)
{
$InnerText = GetSysTemplets(“list_fulllist.htm”);
}
else
{
$InnerText = trim($ctag->GetInnerText());
}
$this->dtp->Assign($tagid,
$this->GetArcList(
$limitstart,
$row,
$ctag->GetAtt(“col”),
$ctag->GetAtt(“titlelen”),
$ctag->GetAtt(“listtype”),
$ctag->GetAtt(“orderby”),
$InnerText,
$ctag->GetAtt(“tablewidth”),
$ismake,
$ctag->GetAtt(“orderway”)
)
);
}
else if($ctag->GetName()==”pagelist”)
{
$list_len = trim($ctag->GetAtt(“listsize”));
$ctag->GetAtt(“listitem”)==”” ? $listitem=”index,pre,pageno,next,end,option” : $listitem=$ctag->GetAtt(“listitem”);
if($list_len==””)
{
$list_len = 3;
}
if($ismake==0)
{
$this->dtp->Assign($tagid,$this->GetPageListDM($list_len,$listitem));
}
else
{
$this->dtp->Assign($tagid,$this->GetPageListST($list_len,$listitem));
}
}
else if($PageNo!=1 && $ctag->GetName()==’field’ && $ctag->GetAtt(‘display’)!=”)
{
$this->dtp->Assign($tagid,”);
}
}
}

/**
* 获得要创建的文件名称规则
*
* @access public
* @param string $typeid 栏目ID
* @param string $wname
* @param string $typedir 栏目目录
* @param string $defaultname 默认名称
* @param string $namerule2 名称规则
* @return string
*/
function GetMakeFileRule($typeid,$wname,$typedir,$defaultname,$namerule2)
{
$typedir = MfTypedir($typedir);
if($wname==’index’)
{
return $typedir.’/’.$defaultname;
}
else
{
$namerule2 = str_replace(‘{tid}’,$typeid,$namerule2);
$namerule2 = str_replace(‘{typedir}’,$typedir,$namerule2);
return $namerule2;
}
}

/**
* 获得一个单列的文档列表
*
* @access public
* @param int $limitstart 限制开始
* @param int $row 行数
* @param int $col 列数
* @param int $titlelen 标题长度
* @param int $infolen 描述长度
* @param int $imgwidth 图片宽度
* @param int $imgheight 图片高度
* @param string $listtype 列表类型
* @param string $orderby 排列顺序
* @param string $innertext 底层模板
* @param string $tablewidth 表格宽度
* @param string $ismake 是否编译
* @param string $orderWay 排序方式
* @return string
*/
function GetArcList($limitstart=0,$row=10,$col=1,$titlelen=30,$listtype=”all”,$orderby=”default”,$innertext=””,$tablewidth=”100″,$ismake=1,$orderWay=’desc’)
{
global $cfg_list_son;
$typeid=$this->TypeID;

if($row==”) $row = 10;

if($limitstart==”) $limitstart = 0;

if($titlelen==”) $titlelen = 100;

if($listtype==”) $listtype = “all”;

if($orderby==”) $orderby=’id’;
else $orderby=strtolower($orderby);

if($orderWay==”) $orderWay = ‘desc’;

$tablewidth = str_replace(“%”, “”, $tablewidth);
if($tablewidth==”) $tablewidth=100;
if($col==”) $col=1;
$colWidth = ceil(100 / $col);
$tablewidth = $tablewidth.”%”;
$colWidth = $colWidth.”%”;

$innertext = trim($innertext);
if($innertext==”) $innertext = GetSysTemplets(‘list_sglist.htm’);

//排序方式
$ordersql = ”;
if($orderby==’senddate’||$orderby==’id’)
{
$ordersql=” ORDER BY arc.aid $orderWay”;
}
else if($orderby==’hot’||$orderby==’click’)
{
$ordersql = ” ORDER BY arc.click $orderWay”;
}
else
{
$ordersql=” ORDER BY arc.aid $orderWay”;
}

$addField = ‘arc.’.join(‘,arc.’,$this->ListFields);

//如果不用默认的sortrank或id排序,使用联合查询(数据量大时非常缓慢)
if(preg_match(‘/hot|click/’, $orderby) || $this->sAddTable)
{
$query = “SELECT tp.typedir,tp.typename,tp.isdefault,tp.defaultname,tp.namerule,tp.namerule2,
tp.ispart,tp.moresite,tp.siteurl,tp.sitepath,arc.aid,arc.aid AS id,arc.typeid,
$addField
FROM `{$this->AddTable}` arc
LEFT JOIN `#@__arctype` tp ON arc.typeid=tp.id
WHERE {$this->addSql} $ordersql LIMIT $limitstart,$row”;
}
//普通情况先从arctiny表查出ID,然后按ID查询(速度非常快)
else
{
$t1 = ExecTime();
$ids = array();
$nordersql = str_replace(‘.aid’,’.id’,$ordersql);
$query = “SELECT id From `#@__arctiny` arc WHERE {$this->addSql} $nordersql LIMIT $limitstart,$row “;

$this->dsql->SetQuery($query);
$this->dsql->Execute();
while($arr=$this->dsql->GetArray())
{
$ids[] = $arr[‘id’];
}
$idstr = join(‘,’,$ids);
if($idstr==”)
{
return ”;
}
else
{
$query = “SELECT tp.typedir,tp.typename,tp.isdefault,tp.defaultname,tp.namerule,tp.namerule2,
tp.ispart,tp.moresite,tp.siteurl,tp.sitepath,arc.aid,arc.aid AS id,arc.typeid,
$addField
FROM `{$this->AddTable}` arc LEFT JOIN `#@__arctype` tp ON arc.typeid=tp.id
WHERE arc.aid IN($idstr) AND arc.arcrank >-1 $ordersql “;
}
$t2 = ExecTime();
//echo $t2-$t1;
}

$this->dsql->SetQuery($query);
$this->dsql->Execute(‘al’);
$t2 = ExecTime();

//echo $t2-$t1;
$artlist = ”;
$this->dtp2->LoadSource($innertext);
$GLOBALS[‘autoindex’] = 0;
for($i=0;$i<$row;$i++) { if($col>1)
{
$artlist .= ”

\r\n”;
}
for($j=0;$j<$col;$j++) { if($row = $this->dsql->GetArray(“al”))
{
$GLOBALS[‘autoindex’]++;
$ids[$row[‘aid’]] = $row[‘id’]= $row[‘aid’];//处理一些特殊字段
$row[‘ismake’] = 1;
$row[‘money’] = 0;
$row[‘arcrank’] = 0;
$row[‘filename’] = ”;
$row[‘filename’] = $row[‘arcurl’] = GetFileUrl($row[‘id’],$row[‘typeid’],$row[‘senddate’],$row[‘title’],$row[‘ismake’],
$row[‘arcrank’],$row[‘namerule’],$row[‘typedir’],$row[‘money’],$row[‘filename’],$row[‘moresite’],$row[‘siteurl’],$row[‘sitepath’]);

$row[‘typeurl’] = GetTypeUrl($row[‘typeid’],MfTypedir($row[‘typedir’]),$row[‘isdefault’],$row[‘defaultname’],
$row[‘ispart’],$row[‘namerule2’],$row[‘moresite’],$row[‘siteurl’],$row[‘sitepath’]);
if($row[‘litpic’] == ‘-‘ || $row[‘litpic’] == ”)
{
$row[‘litpic’] = $GLOBALS[‘cfg_cmspath’].’/images/defaultpic.gif’;
}
if(!preg_match(“/^http:\/\//”, $row[‘litpic’]) && $GLOBALS[‘cfg_multi_site’] == ‘Y’)
{
$row[‘litpic’] = $GLOBALS[‘cfg_mainsite’].$row[‘litpic’];
}
$row[‘picname’] = $row[‘litpic’];

$row[‘pubdate’] = $row[‘senddate’];

$row[‘stime’] = GetDateMK($row[‘pubdate’]);

$row[‘typelink’] = ““.$row[‘typename’].”“;

$row[‘fulltitle’] = $row[‘title’];

$row[‘title’] = cn_substr($row[‘title’],$titlelen);

if(preg_match(‘/b/’, $row[‘flag’]))
{
$row[‘title’] = ““.$row[‘title’].”“;
}

$row[‘textlink’] = ““.$row[‘title’].”“;

$row[‘plusurl’] = $row[‘phpurl’] = $GLOBALS[‘cfg_phpurl’];

$row[‘memberurl’] = $GLOBALS[‘cfg_memberurl’];

$row[‘templeturl’] = $GLOBALS[‘cfg_templeturl’];

//编译附加表里的数据
foreach($row as $k=>$v) $row[strtolower($k)] = $v;

foreach($this->ChannelUnit->ChannelFields as $k=>$arr)
{
if(isset($row[$k]))
{
$row[$k] = $this->ChannelUnit->MakeField($k,$row[$k]);
}
}

if(is_array($this->dtp2->CTags))
{
foreach($this->dtp2->CTags as $k=>$ctag)
{
if($ctag->GetName()==’array’)
{
//传递整个数组,在runphp模式中有特殊作用
$this->dtp2->Assign($k,$row);
}
else
{
if(isset($row[$ctag->GetName()]))
{
$this->dtp2->Assign($k,$row[$ctag->GetName()]);
}
else
{
$this->dtp2->Assign($k,”);
}
}
}
}
$artlist .= $this->dtp2->GetResult();
}//if hasRow

}//Loop Col

if($col>1)
{
$i += $col – 1;
$artlist .= ”

\r\n”;
}
}//Loop Line

$t3 = ExecTime();

//echo ($t3-$t2);
$this->dsql->FreeResult(‘al’);
return $artlist;
}

/**
* 获取静态的分页列表
*
* @access public
* @param int $list_len 列表宽度
* @param string $listitem 列表样式
* @return string
*/
function GetPageListST($list_len,$listitem=”index,end,pre,next,pageno”)
{
$prepage=””;
$nextpage=””;
$prepagenum = $this->PageNo-1;
$nextpagenum = $this->PageNo+1;
if($list_len==”” || preg_match(“/[^0-9]/”, $list_len))
{
$list_len=3;
}
$totalpage = ceil($this->TotalResult / $this->PageSize);
if($totalpage <= 1 && $this->TotalResult > 0)
{
return “1“.$this->TotalResult.”条记录“;
}
if($this->TotalResult == 0)
{
return “0“.$this->TotalResult.”条记录“;
}
$purl = $this->GetCurUrl();
$maininfo = “{$totalpage}“.$this->TotalResult.”“;
$tnamerule = $this->GetMakeFileRule($this->Fields[‘id’], “list”, $this->Fields[‘typedir’], $this->Fields[‘defaultname’], $this->Fields[‘namerule2’]);
$tnamerule = preg_replace(“/^(.*)\//”, ”, $tnamerule);

//获得上一页和主页的链接
if($this->PageNo != 1)
{
$prepage.=”

\r\n”;
$indexpage=”

\r\n”;
}
else
{
$indexpage=”

  • 首页

\r\n”;
}

//下一页,未页的链接
if($this->PageNo != $totalpage && $totalpage>1)
{
$nextpage.=”

\r\n”;
$endpage=”

\r\n”;
}
else
{
$endpage=”

  • 末页

“;
}

//option链接
$optionlist = “”;
/*
$optionlen = strlen($totalpage);
$optionlen = $optionlen*10+18;
$optionlist = ”











  • “;
    *///获得数字链接
    $listdd = “”;
    $total_list = $list_len * 2 + 1;
    if($this->PageNo >= $total_list)
    {
    $j = $this->PageNo – $list_len;
    $total_list = $this->PageNo + $list_len;
    if($total_list > $totalpage)
    {
    $total_list = $totalpage;
    }
    }
    else
    {
    $j=1;
    if($total_list > $totalpage)
    {
    $total_list = $totalpage;
    }
    }
    for($j; $j <= $total_list; $j++) { if($j == $this->PageNo)
    {
    $listdd.= “
  • $j
  • \r\n”;
    }
    else
    {
    $listdd.=”
  • “.$j.”
  • \r\n”;
    }
    }
    $plist = “”;
    if(preg_match(‘/info/i’, $listitem))
    {
    $plist .= $maininfo.’ ‘;
    }
    if(preg_match(‘/index/i’, $listitem))
    {
    $plist .= $indexpage.’ ‘;
    }
    if(preg_match(‘/pre/i’, $listitem))
    {
    $plist .= $prepage.’ ‘;
    }
    if(preg_match(‘/pageno/i’, $listitem))
    {
    $plist .= $listdd.’ ‘;
    }
    if(preg_match(‘/next/i’, $listitem))
    {
    $plist .= $nextpage.’ ‘;
    }
    if(preg_match(‘/end/i’, $listitem))
    {
    $plist .= $endpage.’ ‘;
    }
    if(preg_match(‘/option/i’, $listitem))
    {
    $plist .= $optionlist;
    }
    return $plist;
    }

    /**
    * 获取动态的分页列表
    *
    * @access public
    * @param int $list_len 列表宽度
    * @param string $listitem 列表样式
    * @return string
    */
    function GetPageListDM($list_len,$listitem=”index,end,pre,next,pageno”)
    {
    global $nativeplace,$infotype,$keyword;
    if(empty($nativeplace)) $nativeplace = 0;
    if(empty($infotype)) $infotype = 0;
    if(empty($keyword)) $keyword = ”;
    $prepage = $nextpage = ”;
    $prepagenum = $this->PageNo – 1;
    $nextpagenum = $this->PageNo + 1;
    if($list_len==”” || preg_match(“/[^0-9]/”, $list_len))
    {
    $list_len=3;
    }
    $totalpage = ceil($this->TotalResult / $this->PageSize);
    if($totalpage<=1 && $this->TotalResult>0)
    {
    return “共1页/”.$this->TotalResult.”条记录“;
    }
    if($this->TotalResult == 0)
    {
    return “共0页/”.$this->TotalResult.”条记录“;
    }
    $purl = $this->GetCurUrl();
    $geturl = “tid=”.$this->TypeID.”&TotalResult=”.$this->TotalResult.”&nativeplace=$nativeplace&infotype=$infotype&keyword=”.urlencode($keyword).”&”;
    $hidenform = “\r\n”;
    $hidenform = “\r\n”;
    $hidenform = “\r\n”;
    $hidenform = “\r\n”;
    $hidenform .= “\r\n”;
    $purl .= “?”.$geturl;

    //获得上一页和下一页的链接
    if($this->PageNo != 1)
    {
    $prepage.=”

  • 上一页
  • \r\n”;
    $indexpage=”
  • 首页
  • \r\n”;
    }
    else
    {
    $indexpage=”
  • 首页
  • \r\n”;
    }
    if($this->PageNo!=$totalpage && $totalpage>1)
    {
    $nextpage.=”
  • 下一页
  • \r\n”;
    $endpage=”
  • 末页
  • \r\n”;
    }
    else
    {
    $endpage=”
  • 末页
  • “;
    }

    //获得数字链接
    $listdd=””;
    $total_list = $list_len * 2 + 1;
    if($this->PageNo >= $total_list)
    {
    $j = $this->PageNo – $list_len;
    $total_list = $this->PageNo + $list_len;
    if($total_list > $totalpage)
    {
    $total_list = $totalpage;
    }
    }
    else
    {
    $j=1;
    if($total_list > $totalpage)
    {
    $total_list = $totalpage;
    }
    }
    for($j; $j <= $total_list; $j++) { if($j == $this->PageNo)
    {
    $listdd.= “

  • $j
  • \r\n”;
    }
    else
    {
    $listdd.=”
  • “.$j.”
  • \r\n”;
    }
    }

    $plist = $indexpage.$prepage.$listdd.$nextpage.$endpage;
    return $plist;
    }

    /**
    * 获得当前的页面文件的url
    *
    * @access private
    * @return string
    */
    function GetCurUrl()
    {
    if(!empty($_SERVER[“REQUEST_URI”]))
    {
    $nowurl = $_SERVER[“REQUEST_URI”];
    $nowurls = explode(“?”,$nowurl);
    $nowurl = $nowurls[0];
    }
    else
    {
    $nowurl = $_SERVER[“PHP_SELF”];
    }
    return $nowurl;
    }
    }//End Class

  • 3. 页面使用:{dede:list}
    4. 实现浏览器传参,则分类必须为动态浏览

DEDECMS模板原理、模板标签学习 - .Little Hann - 推酷

mikel阅读(1411)

来源: DEDECMS模板原理、模板标签学习 – .Little Hann – 推酷

本文,小瀚想和大家一起来学习一下DEDECMS中目前所使用的模板技术的原理:

什么是编译式模板、解释式模板,它们的区别是什么?

模板标签有哪些种类,它们的区别是什么,都应用在哪些场景?

学习模板的机制原理对我们修复目前CMS中常出现的模板类代码执行的漏洞能起到怎样的帮助?

带着这些问题,我们进入今天的代码研究,just hacking for fun!!

文章主要分为以下几个部分

1. 模板基本知识介绍
2. 怎么使用模板机制、模板标签的使用方法
3. DEDE模板原理学习
  1) 编译式模板
  2) 解释式模板
  3) 视图类模板
4. 针对模板解析底层代码的Hook Patch对CMS漏洞修复的解决方案

http://www.phpchina.com/archives/view-42534-1.html

http://tools.dedecms.com/uploads/docs/dede_tpl/index.htm

1. 模板基本知识介绍

cms模板是以cms为程序架构,就是在对应CMS系统的基础上制作的各类CMS内容管理系统的样式,页面模板等。业内对于CMS模板的定义亦是通过对于CMS系统的标签调用语言,实现CMS系统的前端展示风格,就像与一个人的外衣。

简单来说,模板技术就是将业务逻辑代码和前台的UI逻辑进行了有效分离,使CMS的UI呈现和代码能够最大程序的解耦和,和MVC中的View层和Control层的思想很类似

系统的模板目录在系统根目录下的templets内,下面是模板目录的文件目录结构。

/templets·········································································
├─default······································································ 默认模板目录
│  ├─images································································ 模板图片目录
│  │  ├─mood····························································
│  │  └─photo····························································
│  ├─js······································································ 模板JS脚本目录
│  └─style··································································· 模板CSS样式目录
├─lurd········································································· LURD系统模板
├─plus········································································· 插件模板目录
├─system······································································ 系统底层模板目录
└─wap········································································· WAP模块模板目录

DedeCMS 从 V5 开始采用了解析式引擎与编译式引擎并存的模式,由于在生成 HTML 时,解析式引擎拥有巨大的优势,但对于动态浏览的互动性质的页面,编译式引擎更实用高效,织梦 CMS 采用双引擎并存的模式,事实上还有另一种模板的使用方法,即视图类,不过它是对解释式模板的代码复用而成的,我们接下来会注意学习它们

2.  怎么使用模板机制、模板标签的使用方法

在了解了模板的基本知识之后,我们接下来学习一下在DEDECMS中的模板机制、以及模板标签的使用方法

总体来说,目前DEDECMS有以下三种模板机制

1. 编译式模板
  1) 核心文件:
  include/dedetemplate.class.php
  /include/tpllib
  2) 标签使用方法
    2.1) 配置变量
    {dede:config name='' value=''/}
    配置变量可以在载入模板后通过 $tpl->GetConfig($name) 获得,仅作为配置,不在模板中显示。
    2.2) 短标记
    {dede:global.name/}   外部变量      等同于 
    {dede:var.name/}      var数组       等同于 'name']; ?>
    {dede:field.name/}    field数组     等同于 'name']; ?>
    {dede:cfg.name/}      系统配置变量  等同于 
    考虑到大多数情况下都会在函数或类中调用模板,因此 $_vars、$fields 数组必须声明为 global 数组,否则模板引擎无法获得它的值从而导致产生错误。
    2.3) 自由调用块标记
    {tag:blockname bind='GetArcList' bindtype='class'} 循环代码 {/tag:blockname}
    必要属性:
    bind       数据源来源函数
    bindtype   函数类型,默认是 class 可选为 sub
    rstype     返回结果类型,默认是 array ,可选项为 string
    自定义函数格式必须为 function(array $atts,object $refObj, array $fields);
    在没有指定 bind 绑定的函数的情况下,默认指向 MakePublicTag($atts,$tpl->refObj,$fields) 统一管理。
    2.4) 固定块标记
      2.4.1) datalist
      从绑定类成员函数GetArcList中获取数组并输出
      {dede:datalist} 循环代码 {/dede:datalist}
      遍历一个二给维数组,数据源是固定的,只适用用类调用。
      等同于
      {tag:blockname bind='GetArcList' bindtype='class' rstype='arrayu'} 循环代码 {/tag:blockname}
      2.4.2) label
      从绑定函数中获取字符串值并输出
      等同于 {tag:blockname bind='func' bindtype='sub' rstype='string'/}
      2.4.3) pagelist
      从绑定类成员函数GetPageList中获取字符串值并输出
      等同于 {tag:blockname bind='GetPageList' bindtype='class' rstype='string'/}
      2.4.4) include
      {dede:include file=''/}
      {dede:include filename=''/}
      2.4.5) php
      {dede:php php 代码 /}
      或
      {dede:php} php代码 {/dede:php}
      2.4.6) If
      仅支持 ifelseelse 直接用{else}表示,但不支持{else if}这样的语法 ,一般建议模板中不要使用太复杂的条件语法,如果确实有需要,可以直接使用 php 语法。
      {dede:if 条件} a-block  {else} b-block {/dede:if}
      条件中允许使用 var.name 、global.name 、field.name、cfg.name 表示相应的变量。
      如:
      {dede:if field.id>10 }....{/dede:if}
      2.4.7) 遍历一个 array 数组
      {dede:array.name}
        {dede:key/} = {dede:value/}
      {/dede:array}
      各种语法的具体编译后的代码,可查看dedetemplate.class.php的function CompilerOneTag(&$cTag)

2. 解释式模板
  1) 核心文件:
  include/dedetag.class.php
  /include/taglib
  2) 标签使用方法
    2.1) 内置系统标记
      2.1.1) global 标记,表示获取一个外部变量,除了数据库密码之外,能调用系统的任何配置参数,形式为:
      {dede:global name='变量名称'}{/dede:global}
      或
      {dede:global name='变量名称'/}
      其中变量名称不能加$符号,如变量$cfg_cmspath,应该写成{dede:global name='cfg_cmspath'/}。
      2.1.2) foreach 用来输出一个数组,形式为:
       {dede:foreach array='数组名称'}[field:key/] [field:value/]{/dede:foreach}
      2.1.3) include 引入一个文件,形式为:
       {dede:include file='文件名称' ismake='是否为dede板块模板(yes/no)'/}
       对文件的搜索路径为顺序为:绝对路径、include文件夹,CMS安装目录,CMS主模板目录
    2.2) 自定义函数使用(之后在学习视图类的时候,会发现视图类的就是复用了解释式模板标签的这个自定义函数的标签用法)
    {dede:标记名称 属性='' function='youfunction("参数一","参数二","@me")'/}
    其中 @me 用于表示当前标记的值,其它参数由你的函数决定是否存在,例如:
    {dede:field name='pubdate' function='strftime("%Y-%m-%d %H:%M:%S","@me")'/}
    2.3) 织梦标记允许有限的编程扩展
    格式为:
    {dede:tagname runphp='yes'}
           $aaa = @me;
           @me = "123456";
    {/dede:tagname}
    @me 表示这个标记本身的值,因此标记内编程是不能使用echo之类的语句的,只能把所有返回值传递给@me。
    此外由于程序代码占用了底层模板InnerText的内容,因此需编程的标记只能使用默认的InnerText。

3. 视图类模板
  1) 核心文件
  ....
  arc.partview.class.php
  ...
  channelunit.class.php
  channelunit.func.php
  channelunit.helper.php
  /include/taglib
  2) 标签使用方法
    2.1) 复用解释式模板标签的自定义函数标签,即钩子技术
    {dede:php}...{/dede:php} 

3. DEDE模板原理学习

要使用模板机制,我们就必须有一个代码层,负责提供数据,还得有一个UI层,负责调用模板标签进行UI显示,而模板标签的底层解析DEDECMS的核心库已经提供了,我们只要在我们的代码层进行引入就可以了,牢记这一点对我们理解模板标签的使用、以及模板解析的原理很有帮助

3.1 编译式模板

先来写个程序(以后root代表根目录)
root/code.php

php
    //利用dedecms写php时,基本都要引入common.inc.php
    require_once (dirname(__FILE__) . '/include/common.inc.php');
    //利用编译式模板所需的文件
    require_once (DEDEINC.'/dedetemplate.class.php');
    
    //生成编译模板引擎类对象
    $tpl = new DedeTemplate(dirname(__file__));
    //装载网页模板
    $tpl->LoadTemplate('code.tpl.htm');
    
    //把php值传到html
    $title = 'Hello World';
    $tpl->SetVar('title',$title);
    $tpl->Display();
    //把编译好的模板缓存做成code.html,就可以直接调用
    $tpl->SaveTo(dirname(__FILE__).'/code.html');
?>

root/code.tpl.htm

"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

 
       
  
       "Content-Type" content="text/html; charset=utf-8" />

{dede:var.title/}

    {dede:php echo "Little"; /} 
    {dede:php}
        echo "Hann";
    {/dede:php}
 

这两个文件编写完成后,访问code.php

同时,在当前目录下也生成了静态的html文件

code.html

这也是所谓的”编译式模板”的意思,联想我们在写C程序的时候,编译器会根据你的C代码编译出exe静态文件,dede的编译式引擎这里也采取了类似的思路。

我们前面说过,编译式模板和标签解释的文件都放在/include/ tpllib 下,所以如果我们需要编写、实现我们自己的自定义标签,就需要按照DEDE的代码架构,在这个文件夹下添加新的标签处理代码逻辑

在include/tpllib中找一个文件来仿制。如plus_ask(我们编写的自定义标签的解析逻辑需要满足DEDE的代码架构,这点在编写插件的时候也是同样的思路,因为我们是在别人的基础上进行二次开发)
root/include/tpllib/plus_hello

php
if(!defined('DEDEINC')) exit('Request Error!');
/**
 * 动态模板hello标签
 *
 * @version        $Id: plus_ask.php 1 13:58 2010年7月5日Z tianya $
 * @package        DedeCMS.Tpllib
 * @copyright      Copyright (c) 2007 - 2010, DesDev, Inc.
 * @license        http://help.dedecms.com/usersguide/license.html
 * @link           http://www.dedecms.com
 */
 
function plus_hello(&$atts,&$refObj,&$fields)
{
    global $dsql,$_vars;
    
    //给出标签的属性默认参数值列表,以’,’分隔,即使不设置默认参数也要给出属性名
    $attlist = "name=";

    FillAtts($atts,$attlist);
    FillFields($atts,$fields,$refObj);
    extract($atts, EXTR_OVERWRITE);
    
    //返回处理结果,以替换标签
    return 'hello!'.$name;
}
?>

还是同样的思路,编写模板文件,去调用这个自定义标签

root/code.tpl.htm

"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

 
       
  
       "Content-Type" content="text/html; charset=utf-8" />

{dede:hello name=LittleHann rstype=string/}

 

这两个文件都编写完毕之后,访问code.php

访问静态html文件

了解了编译式模板的使用方法,接下来我们要一起深入DEDECMS的源代码,来看看DEDE在底层是怎么去实现这些方便的模板机制的,使用的版本为

DedeCMS-V5.7-GBK-SP1.tar

这里允许我再复制一遍code.php的代码,我们对照着它的代码来一行一行的解释

php
    //利用dedecms写php时,基本都要引入common.inc.php
    require_once (dirname(__FILE__) . '/include/common.inc.php');
    //利用编译式模板所需的文件
    require_once (DEDEINC.'/dedetemplate.class.php');
    
    //生成编译模板引擎类对象
    $tpl = new DedeTemplate(dirname(__file__));
    //装载网页模板
    $tpl->LoadTemplate('code.tpl.htm');
    
    //把php值传到html
    $title = 'Hello World';
    $tpl->SetVar('title',$title);
    $tpl->Display();
    //把编译好的模板缓存做成code.html,就可以直接调用
    $tpl->SaveTo(dirname(__FILE__).'/code.html');
?>

//生成编译模板引擎类对象
$tpl = new DedeTemplate(dirname(__file__));

function __construct($templatedir='',$refDir='')
{ 
    //缓存目录
    if($templatedir=='')
    {
        $this->templateDir = DEDEROOT.'/templates';
    }
    else
    {
        //接收用户指定的模板目录
        $this->templateDir = $templatedir;
    }

    //模板include目录
    if($refDir=='')
    { 
        if(isset($GLOBALS['cfg_df_style']))
        {
        //根据用户在后台风格设置所选择风格设置模板
        $this->refDir = $this->templateDir.'/'.$GLOBALS['cfg_df_style'].'/'; 
        }
        else
        {
        $this->refDir = $this->templateDir;
        }
    }
    //设置模板编译缓存文件目录
    $this->cacheDir = DEDEROOT.$GLOBALS['cfg_tplcache_dir']; 
}

//装载网页模板
$tpl->LoadTemplate(‘code.tpl.htm’);

function LoadTemplate($tmpfile)
{
    if(!file_exists($tmpfile))
    {
        echo " Template Not Found! ";
        exit();
    }
    //对用户传入的路径参数进行规范化
    $tmpfile = preg_replace("/[\\/]{1,}/", "/", $tmpfile); 
    $tmpfiles = explode('/',$tmpfile);
    $tmpfileOnlyName = preg_replace("/(.*)\//", "", $tmpfile);
    $this->templateFile = $tmpfile;
    $this->refDir = '';
    for($i=0; $i < count($tmpfiles)-1; $i++)
    {
        $this->refDir .= $tmpfiles[$i].'/';
    } 
    //设置缓存目录
    if(!is_dir($this->cacheDir))
    {
        $this->cacheDir = $this->refDir;
    }
    if($this->cacheDir!='')
    {
        $this->cacheDir = $this->cacheDir.'/';
    }
    if(isset($GLOBALS['_DEBUG_CACHE']))
    {
        $this->cacheDir = $this->refDir;
    }
    //生成对应的高速缓存的文件名
    $this->cacheFile = $this->cacheDir.preg_replace("/\.(wml|html|htm|php)$/", "_".$this->GetEncodeStr($tmpfile).'.inc', $tmpfileOnlyName);
    $this->configFile = $this->cacheDir.preg_replace("/\.(wml|html|htm|php)$/", "_".$this->GetEncodeStr($tmpfile).'_config.inc', $tmpfileOnlyName);

    /*
        1. 不开启缓存
        2. 当缓存文件不存在
        3. 及模板未更新(即未被改动过)的文件的时候才载入模板并进行解析 
    */
    if($this->isCache==FALSE || !file_exists($this->cacheFile) || filemtime($this->templateFile) > filemtime($this->cacheFile))
    {
        $t1 = ExecTime(); //debug
        $fp = fopen($this->templateFile,'r');
        $this->sourceString = fread($fp,filesize($this->templateFile));
        fclose($fp);
        //对模板源文件进行解析,接下来重点分析
        $this->ParseTemplate();
        //模板解析时间
        //echo ExecTime() - $t1;
    }
    else
    {
        //如果存在config文件,则载入此文件,该文件用于保存 $this->tpCfgs的内容,以供扩展用途
        //模板中用{tag:config name='' value=''/}来设定该值
        if(file_exists($this->configFile))
        {
        //当前高速缓存文件有效命中(即在有效期之内),则引入之
        include($this->configFile);
        }
    }
}

//对模板源文件进行解析
$this->ParseTemplate();

function ParseTemplate()
{ 
    if($this->makeLoop > 5)
    {
        return ;
    }
    //当前模板文件中的模板标签个数
    $this->count = -1;
    //保存解析出的模板标签数组
    $this->cTags = array();
    $this->isParse = TRUE;
    $sPos = 0;
    $ePos = 0;
    //模板标签的开始定界符
    $tagStartWord =  $this->tagStartWord; 
    //模板标签的结束定界符
    $fullTagEndWord =  $this->fullTagEndWord; 
    $sTagEndWord = $this->sTagEndWord; 
    $tagEndWord = $this->tagEndWord; 
    $startWordLen = strlen($tagStartWord);
    //保存模板原始文件的字符串
    $sourceLen = strlen($this->sourceString); 
    //检测当前模板文件是否是有效模板文件
    if( $sourceLen <= ($startWordLen + 3) )
    {
        return;
    }
    //实例化标签属性解析对象
    $cAtt = new TagAttributeParse();
    $cAtt->CharToLow = TRUE;

    //遍历模板字符串,请取标记及其属性信息
    $t = 0;
    $preTag = '';
    $tswLen = strlen($tagStartWord);
    for($i=0; $i<$sourceLen; $i++)
    {
        $ttagName = '';

        //如果不进行此判断,将无法识别相连的两个标记
        if($i-1>=0)
        {
        $ss = $i-1;
        }
        else
        {
        $ss = 0;
        }
        $tagPos = strpos($this->sourceString,$tagStartWord,$ss);

        //判断后面是否还有模板标记 
        if($tagPos==0 && ($sourceLen-$i < $tswLen || substr($this->sourceString,$i,$tswLen) != $tagStartWord ))
        {
        $tagPos = -1;
        break;
        }

        //获取TAG基本信息
        for($j = $tagPos+$startWordLen; $j < $tagPos+$startWordLen+$this->tagMaxLen; $j++)
        {
        if(preg_match("/[ >\/\r\n\t\}\.]/", $this->sourceString[$j]))
        {
            break;
        }
        else
        {
            $ttagName .= $this->sourceString[$j];
        }
        } 
        if($ttagName!='')
        {
        $i = $tagPos + $startWordLen;
        $endPos = -1;

        //判断  '/}' '{tag:下一标记开始' '{/tag:标记结束' 谁最靠近
        $fullTagEndWordThis = $fullTagEndWord.$ttagName.$tagEndWord;
        $e1 = strpos($this->sourceString, $sTagEndWord, $i);
        $e2 = strpos($this->sourceString, $tagStartWord, $i);
        $e3 = strpos($this->sourceString, $fullTagEndWordThis, $i);
        $e1 = trim($e1); $e2 = trim($e2); $e3 = trim($e3);
        $e1 = ($e1=='' ? '-1' : $e1);
        $e2 = ($e2=='' ? '-1' : $e2);
        $e3 = ($e3=='' ? '-1' : $e3);
        if($e3==-1)
        {
            //不存在'{/tag:标记'
            $endPos = $e1;
            $elen = $endPos + strlen($sTagEndWord);
        }
        else if($e1==-1)
        {
            //不存在 '/}'
            $endPos = $e3;
            $elen = $endPos + strlen($fullTagEndWordThis);
        }

        //同时存在 '/}' 和 '{/tag:标记'
        else
        {
            //如果 '/}' 比 '{tag:'、'{/tag:标记' 都要靠近,则认为结束标志是 '/}',否则结束标志为 '{/tag:标记'
            if($e1 < $e2 &&  $e1 < $e3 )
            {
            $endPos = $e1;
            $elen = $endPos + strlen($sTagEndWord);
            }
            else
            {
            $endPos = $e3;
            $elen = $endPos + strlen($fullTagEndWordThis);
            }
        }

        //如果找不到结束标记,则认为这个标记存在错误
        if($endPos==-1)
        {
            echo "Tpl Character postion $tagPos, '$ttagName' Error!
\r\n";
            break;
        }
        $i = $elen;

        //分析所找到的标记位置等信息
        $attStr = '';
        $innerText = '';
        $startInner = 0;
        for($j = $tagPos+$startWordLen; $j < $endPos; $j++)
        {
            if($startInner==0)
            {
            if($this->sourceString[$j]==$tagEndWord)
            {
                $startInner=1; continue;
             }
            else
            {
                $attStr .= $this->sourceString[$j];
            }
            }
            else
            {
            $innerText .= $this->sourceString[$j];
            }
        } 
        $ttagName = strtolower($ttagName); 

        /*
            1. if标记,把整个属性串视为属性
            2. 注意到preg_replace的$format参数最后有一个"i",代表执行正则替换的同时,进行代码执行,也就是以PHP的方式对IF语句进行执行
        */
        if(preg_match("/^if[0-9]{0,}$/", $ttagName))
        {
            $cAtt->cAttributes = new TagAttribute();
            $cAtt->cAttributes->count = 2;
            $cAtt->cAttributes->items['tagname'] = $ttagName;
            $cAtt->cAttributes->items['condition'] = preg_replace("/^if[0-9]{0,}[\r\n\t ]/", "", $attStr);
            $innerText = preg_replace("/\{else\}/i", '<'."?php\r\n}\r\nelse{\r\n".'?'.'>', $innerText); 
        }
        /*
            1. php标记
            2. 注意到preg_replace的$format参数最后有一个"i",代表执行正则替换的同时,并"不"进行代码执行,只是简单地将标签内的内容翻译为等价的PHP语法
        */
        else if($ttagName=='php')
        {
            $cAtt->cAttributes = new TagAttribute();
            $cAtt->cAttributes->count = 2;
            $cAtt->cAttributes->items['tagname'] = $ttagName;
            $cAtt->cAttributes->items['code'] = '<'."?php\r\n".trim(preg_replace("/^php[0-9]{0,}[\r\n\t ]/",
                              "",$attStr))."\r\n?".'>';
        }
        else
        {
            //普通标记,解释属性
            $cAtt->SetSource($attStr);
        }
        $this->count++;
        $cTag = new Tag();
        $cTag->tagName = $ttagName;
        $cTag->startPos = $tagPos;
        $cTag->endPos = $i;
        $cTag->cAtt = $cAtt->cAttributes;
        $cTag->isCompiler = FALSE;
        $cTag->tagID = $this->count;
        $cTag->innerText = $innerText;
        $this->cTags[$this->count] = $cTag;
        }
        else
        {
        $i = $tagPos+$startWordLen;
        break;
        }
    }//结束遍历模板字符串
    if( $this->count > -1 && $this->isCompiler )
    {
        //调用/include/tplib/下的对应标签解析文件对指定标签进行解析
        $this->CompilerAll();
    }
}

回到code.php的代码分析上来,我们已经知道引擎会把php标签内的内容翻译为等价的<?php .. ?>代码

//把php值传到html
$title = ‘Hello World’;
$tpl->SetVar(‘title’,$title);

function SetVar($k, $v)
{
    /*
        1. 所谓的从代码层向UI层传值,本质上就是利用超全局变量进行变量共享
        2. 模板标签的本质就是等价的值替换
    */
    $GLOBALS['_vars'][$k] = $v;
}

回到code.php

//显示编译后的模板文件

$tpl->Display();

function Display()
{ 
    global $gtmpfile;
    //进行一次全局数组的变量注册
    extract($GLOBALS, EXTR_SKIP);
    //将编译后的模板文件写进告诉缓存文件中,以备下一次访问的时候加速访问速度
    $this->WriteCache(); 
    /*
        1. 编译好的文件include引入进来
        2. 这一步是代码能够执行的关键,因为我们知道,编译式模板引擎在上一步翻译标签的时候只是单纯地将php标签内的内容翻译为等价的"",并不提供执行
        3. include进来后,代码就得到了执行
    */
    include $this->cacheFile;
}

回到code.php

//把编译好的模板缓存做成code.html,就可以直接调用
$tpl->SaveTo(dirname(__FILE__).’/code.html’);

function SaveTo($savefile)
{
    extract($GLOBALS, EXTR_SKIP);
    //这就是为什么我们在访问了一次编译式模板.php代码后,可以继而访问已经生成了静态html文件
    $this->WriteCache();
    ob_start();
    //再次引入一次
    include $this->cacheFile;
    $okstr = ob_get_contents();
    ob_end_clean();
    $fp = @fopen($savefile,"w") or die(" Tag Engine Create File FALSE! ");
    fwrite($fp,$okstr);
    fclose($fp);
}

3.2 解释式模板

首先需要解释一下这个名词,为什么要称之为解释式模板引擎呢?我们都知道C语言属于编译式的语言,需要将源代码一次全部编译成exe文件才可以统一执行,而PHP属于解释式语言,zend引擎在解释的时候是逐条读取PHP源代码,然后逐条执行。

而回想我们之前学习编译式模板引擎的时候,编译式引擎会先将所有的php执行标签全部先翻译为等价的php可执行语法,然后在最后一个统一的include进行代码执行,这不就是编译式的思想吗?

而我们接下来要学习的解释式模板引擎,是逐个检测php执行标签,在解析的同时就直接进行eval执行,这恰好体现了解释式语言的思想,这就是编译式、解释式名词的由来

我们先来学习一下解释式标签的使用方法

编写/root/code.php,还是一样,记住模板的两个关键要素,代码层、UI层

php
    require_once (dirname(__file__).'/include/common.inc.php');
    //利用解析式模板所需的文件
    require_once (dirname(__file__).'/include/dedetag.class.php');

    $dtp=new DedeTagParse(); 
    $dtp->LoadTemplate(dirname(__file__).'\code.tpl.htm ');

    foreach ($dtp->CTags as $id=>$tag)
    {
        if($tag->GetName()=='my')
            //把id为$id的tag翻译成这是my标签
            $dtp->Assign($id,'this is my tag
');    
        else if($tag->GetName()=='test')
            $dtp->Assign($id,'this is test tag
');
    }

    $dtp->Display(); 
?>

编写code.tpl.htm文件

"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

 
       
  
       "Content-Type" content="text/html; charset=utf-8" />
 
 
         {dede:my att1=1 att2='2'}
            [field:my/]
        {/dede:my}
        {dede:test att1=1 att2='2'}
            [field:test/]
        {/dede:test}
    {dede:tagname runphp='yes'}
               echo "LittleHann" . "
";
    {/dede:tagname} 
 

这两个文件都编写好之后,访问code.php

解释式模板引擎并不会产生静态html文件,即时解释,即时生效,并不保存

了解了解释式模板标签的使用方法后,我们接下来学习一下解释式模板引擎的代码原理

请允许我再次将code.php的代码复制出来,我们逐条的分析它的代码

php
    require_once (dirname(__file__).'/include/common.inc.php');
    //利用解析式模板所需的文件
    require_once (dirname(__file__).'/include/dedetag.class.php');

    //实例化一个DedeTagParse对象
    $dtp=new DedeTagParse(); 
    //加载模板
    $dtp->LoadTemplate(dirname(__file__).'\code.tpl.htm ');

    foreach ($dtp->CTags as $id=>$tag)
    {
        if($tag->GetName()=='my')
            //把id为$id的tag翻译成这是my标签
            $dtp->Assign($id,'this is my tag
');    
        else if($tag->GetName()=='test')
            $dtp->Assign($id,'this is test
');
    }

    $dtp->Display(); 
?>

//实例化一个DedeTagParse对象
$dtp=new DedeTagParse();

function __construct()
{
    //设置是否保存高速缓存文件
    if(!isset($GLOBALS['cfg_tplcache']))
    {
        $GLOBALS['cfg_tplcache'] = 'N';
    }
    if($GLOBALS['cfg_tplcache']=='Y')
    {
        $this->IsCache = TRUE;
    }
    else
    {
        $this->IsCache = FALSE;
    }
    //设置默认命名空间为dede
    $this->NameSpace = 'dede';
    //设置模板标签开始定界符
    $this->TagStartWord = '{';
    //设置模板标签结束定界符
    $this->TagEndWord = '}';
    //模板标签最大长度
    $this->TagMaxLen = 64;
    $this->CharToLow = TRUE;
    //保存模板源文件
    $this->SourceString = '';
    //保存解析后的标签对象数组
    $this->CTags = Array();
    $this->Count = -1;
    $this->TempMkTime = 0;
    $this->CacheFile = '';
}

//加载模板
$dtp->LoadTemplate(dirname(__file__).’\code.tpl.htm ‘);

function LoadTemplate($filename)
{ 
    //设置默认模板文件路径
    $this->SetDefault();
    //检测模板文件是否存在
    if(!file_exists($filename))
    {
        $this->SourceString = " $filename Not Found! ";
        $this->ParseTemplet();
    }
    else
    {
        $fp = @fopen($filename, "r");
        while($line = fgets($fp,1024))
        {
          $this->SourceString .= $line;
        }
        fclose($fp);
        //如果高速缓存命中,则直接返回,加快访问速度
        if($this->LoadCache($filename))
        {
          return '';
        }
        else
        {
          //对模板源文件进行标签解析
          $this->ParseTemplet();
        }
    }
}

//对模板源文件进行标签解析
$this->ParseTemplet();

function ParseTemplet()
{
    //模板标签开始定界符
    $TagStartWord = $this->TagStartWord;
    //模板标签结束定界符
    $TagEndWord = $this->TagEndWord;
    $sPos = 0; 
    $ePos = 0;
    //命名空间的拼接
    $FullTagStartWord =  $TagStartWord.$this->NameSpace.":";
    $sTagEndWord =  $TagStartWord."/".$this->NameSpace.":";
    $eTagEndWord = "/".$TagEndWord;
    $tsLen = strlen($FullTagStartWord);
    $sourceLen=strlen($this->SourceString);

    //检测原始模板文件是否符合规范
    if( $sourceLen <= ($tsLen + 3) )
    {
        return;
    }
    //实例化一个标签属性解析对象
    $cAtt = new DedeAttributeParse();
    $cAtt->charToLow = $this->CharToLow;

    //遍历模板字符串,请取标记及其属性信息
    for($i=0; $i < $sourceLen; $i++)
    {
        $tTagName = '';

        //如果不进行此判断,将无法识别相连的两个标记
        if($i-1 >= 0)
        {
        $ss = $i-1;
        }
        else
        {
        $ss = 0;
        }
        $sPos = strpos($this->SourceString,$FullTagStartWord,$ss);
        $isTag = $sPos;
        if($i==0)
        {
        $headerTag = substr($this->SourceString,0,strlen($FullTagStartWord));
        if($headerTag==$FullTagStartWord)
        {
            $isTag=TRUE; $sPos=0;
        }
        }
        if($isTag===FALSE)
        {
        break;
        } 
        //开始遍历模板源文件
        for($j=($sPos+$tsLen); $j<($sPos+$tsLen+$this->TagMaxLen); $j++)
        {
        if($j>($sourceLen-1))
        {
            break;
        }
        else if( preg_match("/[\/ \t\r\n]/", $this->SourceString[$j]) || $this->SourceString[$j] == $this->TagEndWord )
        {
            break;
        }
        else
        {
            $tTagName .= $this->SourceString[$j];
        }
        }
        //对标签的开始和结束、嵌套标签进行定位
        if($tTagName != '')
        {  
        $i = $sPos + $tsLen;
        $endPos = -1;
        $fullTagEndWordThis = $sTagEndWord.$tTagName.$TagEndWord; 
        $e1 = strpos($this->SourceString,$eTagEndWord, $i);
        $e2 = strpos($this->SourceString,$FullTagStartWord, $i);
        $e3 = strpos($this->SourceString,$fullTagEndWordThis,$i);
        
        //$eTagEndWord = /} $FullTagStartWord = {tag: $fullTagEndWordThis = {/tag:xxx]
        
        $e1 = trim($e1); $e2 = trim($e2); $e3 = trim($e3);
        $e1 = ($e1=='' ? '-1' : $e1);
        $e2 = ($e2=='' ? '-1' : $e2);
        $e3 = ($e3=='' ? '-1' : $e3);
        //not found '{/tag:'
        if($e3==-1) 
        {
            $endPos = $e1;
            $elen = $endPos + strlen($eTagEndWord);
        }
        //not found '/}'
        else if($e1==-1) 
        {
            $endPos = $e3;
            $elen = $endPos + strlen($fullTagEndWordThis);
        }
        //found '/}' and found '{/dede:'
        else
        {
            //if '/}' more near '{dede:'、'{/dede:' , end tag is '/}', else is '{/dede:'
            if($e1 < $e2 &&  $e1 < $e3 )
            {
            $endPos = $e1;
            $elen = $endPos + strlen($eTagEndWord);
            }
            else
            {
            $endPos = $e3;
            $elen = $endPos + strlen($fullTagEndWordThis);
            }
        }

        //not found end tag , error
        if($endPos==-1)
        {
            echo "Tag Character postion $sPos, '$tTagName' Error!
\r\n";
            break;
        }
        $i = $elen;
        $ePos = $endPos;

        //分析所找到的标记位置等信息
        $attStr = '';
        $innerText = '';
        $startInner = 0;
        for($j=($sPos+$tsLen);$j < $ePos;$j++)
        {
            if($startInner==0 && ($this->SourceString[$j]==$TagEndWord && $this->SourceString[$j-1]!="\\") )
            {
            $startInner=1;
            continue;
            }
            if($startInner==0)
            {
            $attStr .= $this->SourceString[$j];
            }
            else
            {
            $innerText .= $this->SourceString[$j];
            }
        }
        //echo "\r\n";

        /*
            朋友们看到这里可以稍微停一下,我们将dedetag.class.php和dedetemplate.class.php进行一下横向对比
            1. 编译式模板引擎在loadTemplate的时候就会将所有的标签都翻译为等价的PHP代码,相当于一个编译的过程,等待之后的include进行引入执行
            2. 解释式模板引擎在laodTemplate的时候只是进行单纯的标签解析、提取出有效内容,并不做实际的翻译。而具体的解释和执行是在后面的Display中进行的,即边解释,边执行
            3. 在学习这两种模板机制的时候多多和传统编程中的概念进行对比,能够帮助我们更加深入地理解概念
        */
        $cAtt->SetSource($attStr); 
        if($cAtt->cAttributes->GetTagName()!='')
        {
            $this->Count++;
            $CDTag = new DedeTag();
            $CDTag->TagName = $cAtt->cAttributes->GetTagName();
            $CDTag->StartPos = $sPos;
            $CDTag->EndPos = $i;
            $CDTag->CAttribute = $cAtt->cAttributes;
            $CDTag->IsReplace = FALSE;
            $CDTag->TagID = $this->Count;
            $CDTag->InnerText = $innerText;
            $this->CTags[$this->Count] = $CDTag;
        } 
        }
        else
        {
        $i = $sPos+$tsLen;
        break;
        }
    }
    //结束遍历模板字符串
    if($this->IsCache)
    { 
        //保存标签解释完毕后的模板文件到高速缓存中(注意,因为这是解释式引擎,所以此时保存的cache中并不是PHP代码,而是附带标签的模板文件)
        $this->SaveCache(); 
    }
}

回到code.php上来

$dtp->Display();

function Display()
{
    echo $this->GetResult();
}

echo $this->GetResult();

function GetResult()
{
    $ResultString = '';
    if($this->Count==-1)
    {
        return $this->SourceString;
    }

    //进行标签的解释、并执行。这里就相当于解释器的作用了
    $this->AssignSysTag(); 
    $nextTagEnd = 0;
    $strok = "";
    for($i=0;$i<=$this->Count;$i++)
    {
        $ResultString .= substr($this->SourceString,$nextTagEnd,$this->CTags[$i]->StartPos-$nextTagEnd);
        $ResultString .= $this->CTags[$i]->GetValue();
        $nextTagEnd = $this->CTags[$i]->EndPos;
    }
    $slen = strlen($this->SourceString);
    if($slen>$nextTagEnd)
    {
        $ResultString .= substr($this->SourceString,$nextTagEnd,$slen-$nextTagEnd);
    } 
    //返回解释执行后的返回结果
    return $ResultString;
}

//进行标签的解释、并执行。这里就相当于解释器的作用了
$this->AssignSysTag();

function AssignSysTag()
{
    global $_sys_globals;
    for($i=0;$i<=$this->Count;$i++)
    {
        $CTag = $this->CTags[$i];
        $str = '';

        //获取一个外部变量
        if( $CTag->TagName == 'global' )
        {
        $str = $this->GetGlobals($CTag->GetAtt('name'));
        if( $this->CTags[$i]->GetAtt('function')!='' )
        {
            //$str = $this->EvalFunc( $this->CTags[$i]->TagValue, $this->CTags[$i]->GetAtt('function'),$this->CTags[$i] );
            $str = $this->EvalFunc( $str, $this->CTags[$i]->GetAtt('function'),$this->CTags[$i] );
        }
        $this->CTags[$i]->IsReplace = TRUE;
        $this->CTags[$i]->TagValue = $str;
        }

        //引入静态文件
        else if( $CTag->TagName == 'include' )
        {
          $filename = ($CTag->GetAtt('file')=='' ? $CTag->GetAtt('filename') : $CTag->GetAtt('file') );
          $str = $this->IncludeFile($filename,$CTag->GetAtt('ismake'));
          $this->CTags[$i]->IsReplace = TRUE;
          $this->CTags[$i]->TagValue = $str;
        }

        //循环一个普通数组
        else if( $CTag->TagName == 'foreach' )
        {
        $arr = $this->CTags[$i]->GetAtt('array');
        if(isset($GLOBALS[$arr]))
        {
            foreach($GLOBALS[$arr] as $k=>$v)
            {
            $istr = '';
            $istr .= preg_replace("/\[field:key([\r\n\t\f ]+)\/\]/is",$k,$this->CTags[$i]->InnerText);
            $str .= preg_replace("/\[field:value([\r\n\t\f ]+)\/\]/is",$v,$istr);
            }
        }
        $this->CTags[$i]->IsReplace = TRUE;
        $this->CTags[$i]->TagValue = $str;
        }

        //设置/获取变量值
        else if( $CTag->TagName == 'var' )
        {
        $vname = $this->CTags[$i]->GetAtt('name');
        if($vname=='')
        {
            $str = '';
        }
        else if($this->CTags[$i]->GetAtt('value')!='')
        {
            $_vars[$vname] = $this->CTags[$i]->GetAtt('value');
        }
        else
        {
            $str = (isset($_vars[$vname]) ? $_vars[$vname] : '');
        }
          $this->CTags[$i]->IsReplace = TRUE;
          $this->CTags[$i]->TagValue = $str;
        }

        /*
        运行PHP接口
        当检测到有runphp这种标签属性的时候,则对这个标签进行PHP解析
        */
        if( $CTag->GetAtt('runphp') == 'yes' )
        {
          $this->RunPHP($CTag, $i);
        }
        if(is_array($this->CTags[$i]->TagValue))
        {
          $this->CTags[$i]->TagValue = 'array';
        }
    }
}

$this->RunPHP($CTag, $i);

function RunPHP(&$refObj, $i)
    {
        $DedeMeValue = $phpcode = '';
        if($refObj->GetAtt('source')=='value')
        {
            $phpcode = $this->CTags[$i]->TagValue; 
        }
        else
        {
            $DedeMeValue = $this->CTags[$i]->TagValue;
            //获取标签内的内容
            $phpcode = $refObj->GetInnerText(); 
        }
        //将@me替换成$DedeMeValue标签值
        $phpcode = preg_replace("/'@me'|\"@me\"|@me/i", '$DedeMeValue', $phpcode);
        /*
            这句是关键,对php执行标签内的内容直接调用eval进行执行\
            体会一下这是不是边解释、边执行的效果
        */
        @eval($phpcode); //or die("");

        //保存执行的结果
        $this->CTags[$i]->TagValue = $DedeMeValue;
        $this->CTags[$i]->IsReplace = TRUE;
    }

3.3 视图类模板

接下来要学习的第三种模板称之为视图类模板,严格来说,它不能算是一种新的模板机制,因为它复用了很多解释式模板的代码逻辑

先来学习一下视图类模板的使用方法

在根目录下编写code.php

php
    require_once (dirname(__file__).'/include/common.inc.php');
    //利用解析式模板所需的文件
    require_once(DEDEINC.'/arc.partview.class.php');

    //实例化一个PartView对象
    $pv = new PartView();
    $tagbody = file_get_contents("code.tpl.htm");
    //加载模板
    $pv->SetTemplet($tagbody, 'string');
   echo $pv->GetResult(); 
?>

然后编写模板文件 code.tpl.htm

"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

 
       
  
       "Content-Type" content="text/html; charset=utf-8" />

{dede:php} echo LittleHann; {/dede:php}

这两个文件都准备好之后,访问code.php

接下来,我们来分析一下这个视图类模板的解析原理

//实例化一个PartView对象
$pv = new PartView();

function __construct($typeid=0,$needtypelink=TRUE)
{
    global $_sys_globals,$ftp;
    $this->TypeID = $typeid;
    $this->dsql = $GLOBALS['dsql'];
    /*
        实例化一个解释式模板引擎对象
        这句要重点注意,我们之后会看到视图类模板对象复用了解释式模板引擎的部分代码逻辑
    */
    $this->dtp = new DedeTagParse();
    //设置模板标签的命名空间
    $this->dtp->SetNameSpace("dede","{","}");
    $this->dtp->SetRefObj($this);
    $this->ftp = &$ftp;
    $this->remoteDir = '';

    if($needtypelink)
    {
        $this->TypeLink = new TypeLink($typeid);
        if(is_array($this->TypeLink->TypeInfos))
        {
        foreach($this->TypeLink->TypeInfos as $k=>$v)
        {
            if(preg_match("/[^0-9]/", $k))
            {
            $this->Fields[$k] = $v;
            }
        }
        }
        $_sys_globals['curfile'] = 'partview';
        $_sys_globals['typename'] = $this->Fields['typename'];

        //设置环境变量
        SetSysEnv($this->TypeID,$this->Fields['typename'],0,'','partview');
    }
    SetSysEnv($this->TypeID,'',0,'','partview');
    $this->Fields['typeid'] = $this->TypeID;

    //设置一些全局参数的值
    foreach($GLOBALS['PubFields'] as $k=>$v)
    {
        $this->Fields[$k] = $v;
    }
}

回到code.php上来

//加载模板
$pv->SetTemplet($tagbody, ‘string’);

function SetTemplet($temp,$stype="file")
{
    if($stype=="string")
    {   
        //复用解释式模板引擎的LoadSource方法,去加载、匹配标签
        $this->dtp->LoadSource($temp);
    }
    else
    {
        $this->dtp->LoadTemplet($temp);
    }
    if($this->TypeID > 0)
    {
        $this->Fields['position'] = $this->TypeLink->GetPositionLink(TRUE);
        $this->Fields['title'] = $this->TypeLink->GetPositionLink(false);
    }  
    //调用视图类模板引擎自己的标签解释方法ParseTemplet
    $this->ParseTemplet();
}

//复用解释式模板引擎的LoadSource方法,去加载、匹配标签
$this->dtp->LoadSource($temp);

我们知道,对于解释式模板引擎来说,LoadSource只是在在加载模板,并对模板文件中的标签进行提取并保存,而具体的标签解析、执行要在Display中进行。

所以,视图类模板引擎复用了解释式模板引擎的这个LoadSource逻辑

接下来,视图类模板引擎调用了自己的 ParseTemplet 方法,进行具体的标签解析、执行

//调用视图类模板引擎自己的标签解释方法ParseTemplet
$this->ParseTemplet();

function ParseTemplet()
{
    $GLOBALS['envs']['typeid'] = $this->TypeID;
    if($this->TypeID>0)
    {
        $GLOBALS['envs']['topid'] = GetTopid($this->TypeID);
    }
    else 
    {
        $GLOBALS['envs']['topid'] = 0;
    }
    if(isset($this->TypeLink->TypeInfos['reid']))
    {
        $GLOBALS['envs']['reid'] = $this->TypeLink->TypeInfos['reid'];
    }
    if(isset($this->TypeLink->TypeInfos['channeltype']))
    {
      $GLOBALS['envs']['channelid'] = $this->TypeLink->TypeInfos['channeltype'];
    }
    /*
        这个函数放在 channelunit.func.php 文件中 
        视图类模板引擎使用了钩子技术来对标签进行动态地解析
        这个函数是钩子的入口
    */
    MakeOneTag($this->dtp,$this); 
}

MakeOneTag($this->dtp,$this);

在arc.partview.class.php的开头,include了channelunit.class.php,而channelunit.class.php又引入了channelunit.func.php,在channelunit.func.php中加载了一个辅助类

helper('channelunit');

这个辅助类的加载函数helper的实现在common.func.php中

$_helpers = array();
function helper($helpers)
{
    //如果是数组,则进行递归操作
    if (is_array($helpers))
    {
        foreach($helpers as $dede)
        {
            helper($dede);
        }
        return;
    }

    if (isset($_helpers[$helpers]))
    {
        continue;
    }
    if (file_exists(DEDEINC.'/helpers/'.$helpers.'.helper.php'))
    { 
        include_once(DEDEINC.'/helpers/'.$helpers.'.helper.php');
        $_helpers[$helpers] = TRUE;
    }
    // 无法载入小助手
    if ( ! isset($_helpers[$helpers]))
    {
        exit('Unable to load the requested file: helpers/'.$helpers.'.helper.php');                
    }
}

这样,通过调用helper(‘channelunit’)成功加载了channelunit.helper.php文件, MakeOneTag 的实现就在这个文件中

function MakeOneTag(&$dtp, &$refObj, $parfield='Y')
{
    global $cfg_disable_tags;
    //检测用户是否设置了禁用{dede:php}模板标签
    $cfg_disable_tags = isset($cfg_disable_tags)? $cfg_disable_tags : 'php';
    $disable_tags = explode(',', $cfg_disable_tags);
    $alltags = array();
    $dtp->setRefObj($refObj);
    //读取自由调用tag列表
    $dh = dir(DEDEINC.'/taglib');
    while($filename = $dh->read())
    {
        if(preg_match("/\.lib\./", $filename))
        {
            $alltags[] = str_replace('.lib.php','',$filename);
        }
    }
    $dh->Close();

    //遍历tag元素
    if(!is_array($dtp->CTags))
    {
        return '';
    }
    foreach($dtp->CTags as $tagid=>$ctag)
    {
        $tagname = $ctag->GetName();
        if($tagname=='field' && $parfield=='Y')
        {
            $vname = $ctag->GetAtt('name');
            if( $vname=='array' && isset($refObj->Fields) )
            {
                $dtp->Assign($tagid,$refObj->Fields);
            }
            else if(isset($refObj->Fields[$vname]))
            {
                $dtp->Assign($tagid,$refObj->Fields[$vname]);
            }
            else if($ctag->GetAtt('noteid') != '')
            {
                if( isset($refObj->Fields[$vname.'_'.$ctag->GetAtt('noteid')]) )
                {
                    $dtp->Assign($tagid, $refObj->Fields[$vname.'_'.$ctag->GetAtt('noteid')]);
                }
            }
            continue;
        }

        //由于考虑兼容性,原来文章调用使用的标记别名统一保留,这些标记实际调用的解析文件为inc_arclist.php
        if(preg_match("/^(artlist|likeart|hotart|imglist|imginfolist|coolart|specart|autolist)$/", $tagname))
        {
            $tagname='arclist';
        }
        if($tagname=='friendlink')
        {
            $tagname='flink';
        }
        if(in_array($tagname,$alltags))
        {
            if(in_array($tagname, $disable_tags))
            {
                echo 'DedeCMS Error:Tag disabled:"'.$tagname.'"           target="_blank">more...!';
                return FALSE;
            }
            if (DEBUG_LEVEL==TRUE) {
                $ttt1 = ExecTime();
            }
            /*
                从这里开始就是关于钩子技术的实现了
                1. 根据标签动态地决定要加载什么标签解析文件。我们知道,和解释式标签有关的解释代码都在/include/taglib/中
                2. 根据标签动态的拼接要调用的函数名,即PHP的动态函数执行,这是一种典型的钩子技术
            */
            $filename = DEDEINC.'/taglib/'.$tagname.'.lib.php';
            include_once($filename);
            $funcname = 'lib_'.$tagname;
            //调用动态函数进行执行,并将返回结果传给UI层
            $dtp->Assign($tagid,$funcname($ctag,$refObj));
            if (DEBUG_LEVEL==TRUE) {
                $queryTime = ExecTime() - $ttt1;
                echo '标签:'.$tagname.'载入花费时间:'.$queryTime."
\r\n";
            }
        }
    }
}

例如,我们的模板文件的内容是{dede:php}echo 2;{/dede:php}

则在钩子函数MakeOneTag这里就会动态的去include引入php_lib.php的这个文件,并调用php_lib方法对这个标签进行解析,具体怎么解析的逻辑都在php_lib.php这个文件中

function lib_php(&$ctag, &$refObj)
{
    global $dsql;
    global $db;
    $phpcode = trim($ctag->GetInnerText());
    if ($phpcode == '')
        return '';
    ob_start();
    //再次进行一次本地啊变量注册
    extract($GLOBALS, EXTR_SKIP);
    //这句是关键,直接对标签内部的内容调用eval进行执行
    @eval($phpcode);
    //只不过和解释式模板引擎不同的是,这里并不是直接返回执行结果,而是将执行结果缓存起来,返回给调用方
    $revalue = ob_get_contents();
    ob_clean();
    return $revalue;
}

回到code.php上来

//模板标签的执行结果已经保存起来了,需要我们自己去显示出来
echo $pv->GetResult();

总结一下和三种模板标签和代码执行有关的的PHP代码执行标签的用法

1. 编译式标签:
{dede:php   php代码 /}{dede:php}  php代码 {/dede:php}
2. 解释式标签
{dede:tagname runphp='yes'}
  php代码
{/dede:tagname}
3. 视图类标签
{dede:php} php代码 {/dede:php}

黑客要利用模板类的漏洞进行代码执行,所会使用的模板标签就是这三种

4. 针对模板解析底层代码的Hook Patch对CMS漏洞修复的解决方案

所有模板类相关的漏洞都有一个共同的特点,就是代码执行,而在模板引擎中,进行代码执行的底层文件是比较集中的,我们可以针对某几个特定的文件进行Hook Patch,检测流经其中的数据是否包含敏感关键字,而从从底层来防御这种模板类漏洞,当然从原则上,CMS的其他漏洞也是可以采取相同的思路,这里面我们要做的就是对这些漏洞进行分类,从而找出漏洞的共同点

我希望在本文的研究中做出一些试探性的尝试,同时也希望引发大家的共同思考,对目前CMS漏洞的修复除了单纯地针对某个文件去做针对性的修复,然后每次ODAY爆发,再急忙到官网删去下补丁(或者站长自己就是程序员,自己做手工patch),这样带来的问题就是补丁的修复具有滞后性,如果能从根源上去思考漏洞的成因,在代码层的底部做一个总览性、归类性的防御,是不是能更好地解决目前CMS漏洞的发生呢?

从目前的情况来看,我的思考结果是,可以在两种模板引擎的解析函数中进行Hook,就可以达到目的了,因为视图类模板复用了解释式模板引擎的模板解析代码,所以也包含在这两个

dedetag.class.php -> ParseTemplet
dedetemplate.class.php -> ParseTemplate

我们可以在其中的关键代码位置Hook上这个函数

function find_tag_payload($tagbody)
{ 
    $express = "/<\?(php){0,1}(.*)/i";
    if (preg_match($express, $tagbody)) 
    {
    die("Request Error!");  
    }  
}

我们来做一些尝试

1. dedetag.class.php -> ParseTemplet

在匹配、提取模板标签的位置做Hook

..
$cAtt->SetSource($attStr); 
if($cAtt->cAttributes->GetTagName()!='')
{
    $this->Count++;
    $CDTag = new DedeTag();
    $CDTag->TagName = $cAtt->cAttributes->GetTagName();
    $CDTag->StartPos = $sPos;
    $CDTag->EndPos = $i;
    $CDTag->CAttribute = $cAtt->cAttributes;
    $CDTag->IsReplace = FALSE;
    $CDTag->TagID = $this->Count;

    $this->find_tag_payload($innerText);
    
    $CDTag->InnerText = $innerText;
    $this->CTags[$this->Count] = $CDTag;
}
....


function find_tag_payload($tagbody)
{ 
    $express = "/<\?(php){0,1}(.*)/i";
    if (preg_match($express, $tagbody)) 
    {
        die("Request Error!");  
    }  
}

2. dedetemplate.class.php -> ParseTemplate

在if、php标签的地方都做Hook

...
if(preg_match("/^if[0-9]{0,}$/", $ttagName))
{
    $cAtt->cAttributes = new TagAttribute();
    $cAtt->cAttributes->count = 2;
    $cAtt->cAttributes->items['tagname'] = $ttagName; 
    $cAtt->cAttributes->items['condition'] = preg_replace("/^if[0-9]{0,}[\r\n\t ]/", "", $attStr);

    $this->find_tag_payload($innerText);
    
    $innerText = preg_replace("/\{else\}/i", '<'."?php\r\n}\r\nelse{\r\n".'?'.'>', $innerText); 
}
/*
    1. php标记
    2. 注意到preg_replace的$format参数最后有一个"i",代表执行正则替换的同时,并"不"进行代码执行,只是简单地将标签内的内容翻译为等价的PHP语法
*/
else if($ttagName=='php')
{
    $cAtt->cAttributes = new TagAttribute();
    $cAtt->cAttributes->count = 2;
    $cAtt->cAttributes->items['tagname'] = $ttagName;

    $this->find_tag_payload($attStr);

    $cAtt->cAttributes->items['code'] = '<'."?php\r\n".trim(preg_replace("/^php[0-9]{0,}[\r\n\t ]/",
                      "",$attStr))."\r\n?".'>';
}
...

function find_tag_payload($tagbody)
{ 
    $express = "/<\?(php){0,1}(.*)/i";
    if (preg_match($express, $tagbody)) 
    {
        die("Request Error!");  
    }  
}

这样做好Hook Patch之后,我们使用dede的一个很有名的模板类执行漏洞进行测试

http://ha.cker.in/1006.seo

http://www.i0day.com/1403.html

这是一个利用注入漏洞向数据库打入模板执行rootkit,然后再触发模板执行,从而进行写磁盘GETSHELL

访问

http://localhost/dede5.7/plus/mytag_js.php?aid=1

攻击被成功地防御住了。我觉得这有点类似于堡垒主机的思维方式,将复杂多变的上层漏洞风险集中到相对较少数量的底层代码逻辑上,在所有代码流都必须流经的关键点做攻击检测,从而从根本上防御一些已知、甚至未知的CMS漏洞攻击。

目前这种方法还处在完善中,也希望搞CMS、WEB漏洞攻防的朋友能分享一些更好的思路,代码的安全问题的路还很长。

今天的文章就到这里了,下一步调研一下DEDECMS的其他类型的漏洞,希望能将这些漏洞进行归类,找出一些通用性的修复方案

iOS系统框架概述 - 简书

mikel阅读(1633)

来源: iOS系统框架概述 – 简书

iOS操作系统是苹果为公司移动设备提供的操作系统,为方便应用开发者采用了先进的设计模式。采用了经典的MVC设计模式和框架模式。本篇文章我们主要关注iOS系统提供的框架,对MVC设计模式不做过多的描述。

我们通常称呼iOS的框架为Cocoa Touch框架,Cocoa Touch是一个框架的集合,里面包含了众多的子框架。每一个子框架都是一个目录,包含了共享资源库,用于访问该资源库中储存的代码的头文件,以及图像、声音文件等其他资源,共享资源库定义应用程序可以调用的函数和方法。框架中的类相互依赖构成一个整体,提供完善的某一方面的服务或解决方案,多个框架一起实现整个应用程序的结构。由于应用程序的结构是通用的,开发者通过框架提供的函数和方法,做细致个性化的处理,从而满足不同应用的不同需求。开发一个应用程序就是将需求细致化的代码插入到框架提供的设计中来组合成一个整体完成最终的应用设计。

框架的结构是泛型结构,为应用程序提供一个模板。就像开发商开发好的毛坯房,你需要用应用程序代码来布置不同的家具地板门窗,这才让不同的房屋有不同的格调,不同的幸福。当然,有时候,做装修的时候你需要简单的改装你的房屋,但承重墙是不能改造的。就像我们之前说的,多个框架一起实现了整个应用程序的结构,我们必须接受它所定义好的应用程序结构,设计的时候让我们的应用适合该结构。

Android开发中,采用模板模式来实现应用程序的一些特性行为,Android提供了Activity,Service,Content providers,Broadcast receivers四大组件默认功能,应用通过继承这些组件根据需要覆盖组件的一些方法来完成应用程序开发。在iOS中则采用代理和协议模式来实现应用的特性行为。例如Cocoa Touch框架集合中的UIKit框架的UIApplication对象,它负责整个应用程序生命周期的事件分发。是应用最核心的一个对象,Android的设计中就需要对其子类化,覆盖父类的方法,iOS中则交给UIApplication的代理AppDeleagte来处理应用程序的各种状态改变相关事件(AppDelegate需要实现UIApplicationDelegate协议) 。在iOS的框架中,大量的使用代理和协议。

iOS提供的许多可使用的框架,构成了iOS操作系统的层次结构,从下到上依次是:Core OS、Core Ssevices、MediaLayer、Cocoa Touch共四层。下图为iOS8.3系统的框架架构图。

iOS8.3系统框架架构图

Core OS Layer,系统核心层包含大多数低级别接近硬件的功能,它所包含的框架常常被其它框架所使用。Accelerate框架包含数字信号,线性代数,图像处理的接口。针对所有的iOS设备硬件之间的差异做优化,保证写一次代码在所有iOS设备上高效运行。CoreBluetooth框架利用蓝牙和外设交互,包括扫描连接蓝牙设备,保存连接状态,断开连接,获取外设的数据或者给外设传输数据等等。Security框架提供管理证书,公钥和私钥信任策略,keychain,hash认证数字签名等等与安全相关的解决方案。

Core Services Layer,系统服务层提供给应用所需要的基础的系统服务。如Accounts账户框架,广告框架,数据存储框架,网络连接框架,地理位置框架,运动框架等等。这些服务中的最核心的是CoreFoundation和Foundation框架,定义了所有应用使用的数据类型。CoreFoundation是基于C的一组接口,Foundation是对CoreFoundation的OC封装。

Media Layer,媒体层提供应用中视听方面的技术,如图形图像相关的CoreGraphics,CoreImage,GLKit,OpenGL ES,CoreText,ImageIO等等。声音技术相关的CoreAudio,OpenAL,AVFoundation,视频相关的CoreMedia,Media Player框架,音视频传输的AirPlay框架等等。

Cocoa Touch Layer,触摸层提供应用基础的关键技术支持和应用的外观。如NotificationCenter的本地通知和远程推送服务,iAd广告框架,GameKit游戏工具框架,消息UI框架,图片UI框架,地图框架,连接手表框架,自动适配等等

在上面所有的框架中,最重要也最经常使用的就是UIKit和Foundation框架。Foundation框架提供许多基本的对象类和数据类型,使其成为应用程序开发的基础,为所有应用程序提供最基本的系统服务,和界面无关。 UIKit框架提供的类是基础的UI类库,用于创建基于触摸的用户界面,所有 iOS 应用程序都是基于 UIKit,它提供应用程序的基础架构,用于构建用户界面,绘图、处理和用户交互事件,响应手势等等。UIKit通过控制器对象管理屏幕上显示的内容,界面的跳转,来组织应用程序。没有UIKit框架就没有iOS应用程序。

之后的文章里,我们会介绍Foundation框架最常用的一些基本类,如NSString,NSArrary,NSDictionary,NSAttributedString,NSBundle等等。也会具体介绍UIKit框架的一些最基本最常用的控件。


作者:pican
链接:https://www.jianshu.com/p/0f2ab4b09e8b
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。