之前说起过FastReport.NET这款报表工具的使用,但当时主要是从程序的角度,示例了在B/S架构下的相关使用,但报表终归还是要划到设计的范畴里来,毕竟能够将报表的内容展示在客户的眼前,这才是报表的根本目的,而诸如打印、转换格式个人觉得应该算是锦上添花的功能吧。

而随着报表设计的复杂,问题自然也就增多了,没办法,硬着头皮上官网下点文档吧。

这里将遇到的一些小问题汇总如下,自己做个记录,需要的朋友也可以省点“阅读理解”的时间哈。。。

一、页面设置

情景:FastReport设计器页面默认设置为A4纸,但如果需要显示的字段过多,这时就出现了页面的大小无法满足完整显示所需内容的问题。

解决:出现这个问题后,我们可以在来到”文件”—”页面设置”选项中进行设置,在这里可以直接调整页面的纸张类型和尺寸,但是我使用的时候更多的会调整Portrait为Landscape,所谓Portrait按词霸的翻译就是“纸短的一边在顶端和底端的打印方式”,而Landscape则恰恰相反,意思是“纸长的一边在顶端和底端的打印方式”,可以简单的理解Portrait为纵向,Landscape为横向,通过这样的调整既可以保证用标准的A4纸打印,又满足了设计时能够扩充纸张大小,保证显示内容的目的。

 

二、过滤或排序显示数据

情景:为了将数据按顺序显示,便于查看。

解决:此时可以双击数据区,这时就会看到排序和过滤的选项,可以通过点击后面fx图标,使用设计器的方式实现,当然也可以手动添加代码:

三、添加和设置子报表

情景:显示复杂关系的数据。

解决:进入”报表”–“设置报表栏”,可以对报表进行各种设置,其中数据首只会打印一次,数据尾则会在所有数据之后被打印。

 

四、日期显示

情景:有些数据表里的日期字段是空的,这时FastReport会自动将其转化为0001/1/1的形式。

解决:此时如果想要让日期为空时不显示此默认的形式,可以再次进入”报表”–“选项”中,在”一般”选项卡中取消勾选”转换空值”即可。

五、数据表的嵌套

情景:复杂的数据库表之间有很多复杂的主从对应关系,这时需要在数据源中建立关系。

解决:选择动作–新建关系,然后就可以象在数据库里一样建立主外键关联了。

六、手动确定数据源

情景:有些时候SQL语句建立表的关系过于复杂,以至于很难建立良好的主从关系,比如我遇到的这样的SQL查询

SELECT i.SheetKeyId,i.SheetId,i.OperatorName,
i.InCheckDateTime,i.OutCheckDateTime,i.OutCheckPeople,
s1.BranchName AS InBranchName,s2.BranchName AS OutBranchName
FROM   dbo.T_StorageBranch s1 , dbo.T_StorageBranch s2 ,
dbo.T_LeechdomIOSheet i , dbo.T_LeechdomIOSheet i2
WHERE  i.InBranchKeyId = s1.BranchKeyId
AND    i2.OutBranchKeyId = s2.BranchKeyId
AND    i.SheetId = i2.SheetId

既有自表关联,又有和其他表的关联,那建立主从关系岂不要郁闷死。。。

解决:此时我的解决方法有两种,一个是基于FastReport是支持表和视图作为数据源的,此时可以事先在数据库中建立视图,从而使用视图作为数据源解决此表的关联问题,当然如没有项目的需求,大可采用第二种方法,即点击”添加SQL查询”,这是输入我们的SQL语句,便会自动生成一个查询结果,我们只需要在报表设计时用其作为数据源就ok了。

七、使用系统变量

情景:我们经常会在报表打印时要求显示当前的页数,当前的数据量统计以及当前的打印时间等。

解决:FastReport其实已经我们内置了所有这些功能,可以在系统变量中找到,只需要简单的拖入Date就可以显示出当前日期,拖入PageN就可以显示页码等。

八、使报表显示时呈现常见的奇偶行变色效果。

情景:数据量大时可以方便查看数据。

解决:这里同样有两种方法,一是在”报表”–“样式”中添加一个样式,叫做EvenRows,什么样式都不用设置,确定即可,然后单击数据区,在外观的EvenStyle属性里面选择此样式,最后改变Fill属性的填充颜色即可。第二种方法是通过程序的方式来实现,代码如下:

  private void Data1_BeforePrint(object sender, EventArgs e)

  {

      if (((Int32)Report.GetVariableValue(“Row#”)) % 2 == 0)

         Data1.FillColor = Color.Gainsboro;

  }

  这里的Row#为系统内置变量,代表行号,我们选出偶数行后,设置FillColor为自己想要的颜色即可,但不可为window颜色,这样就看不出效果了。

  九、使用程序操作报表

  情景:在窗体程序中可以使用编程的方式对报表的相关字段进行操作。

  解决:

    //获取数据源中列的值

    string productName = (string)Report.GetColumnValue(“Products.Name”);

//获取数据源

DataSourceBase ds = Report.GetDataSource(“Products”);

//获取系统变量值

DateTime date = (DateTime)Report.GetVariableValue(“Date”);

//获取总计数

float sales = Report.GetTotalValue(“TotalSales”);

//获取参数

int myParam = (int)Report.GetParameterValue(“MyParameter”);

//设置参数

report1.SetParameterValue(“EmployeeID”, 2);

十、备注

1、在FastReport中的变量都使用的是[DataSourceName.FieldName]的形式,凡是以这种形式表示的都需要从数据库中读取,而普通文本则需要[文本内容]这样的形式就可以搞定。

2、在FastReport中可以设定数据表的别名,这样可以在多表关联的时候使用别名与相关字段对应。

3、如果你还没有心动的话,我贴几张现成的基于Northwind数据库的demo示例看看吧。。。

 

 

估计就说这么多了,好用的东西大家一起分享哇。。。^_^

 

作者:Rocky翔
出处:http://www.cnblogs.com/RockyMyx/

有时会出现卸载软件后残留开始菜单与桌面图标问题(只是极其个别电脑,但这种现象存在),还有的就是在更改几次安装程序后原安装程序的残留信息,某些情况下会对新生成的安装文件运行产生干扰,因此需要在新安装程序运行时强制删除残留信息(不是卸载,已经卸载过)。现将解决这些小问题的思路整理如下:
1)开始菜单及桌面图标残留
可以用LaunchAppAndWait来运行cmd进行文件夹及文件的删除,也可以用IS自带的DeleteProgramFolder和DeleteFolderIcon进行显示删除,这里记住几个常量以对特殊文件夹路径进行读取
WINSYSDIR或SystemFolder读取%windir%\system32文件夹(如c:\windows\system32)
WINDIR或WindowsFolder读取%windir%文件夹(如c:\windows)
FOLDER_DESKTOP或DesktopFolder读取桌面文件夹
INSTALLDIR或TARGETDIR读取文件安装路径
FOLDER_PROGRAMS或ProgramMenuFolder读取程序菜单文件夹
@PRODUCT_NAME获取安装程序名称
@PRODUCT_GUID获取安装程序的ID
一般来说这几个常量就能够满足我们的操作了,如果还要了解更多,可以在<ISProductFolder>\Script\isrt\Include\SysVars.h和<ISProductFolder> Script\iswi\Include\SysVarsConv.h,记不住?IS的安装文件里面搜.h文件,找一个你知道的关键字,总不会记一个关键字也难吧?
因此DeleteFile ( FOLDER_DESKTOP^@PRODUCT_NAME+”.lnk” );和DeleteProgramFolder ( FOLDER_PROGRAMS^@PRODUCT_NAME );这两句话加在OnMaintUIAfter()里就可满足清除残留图标的要求了。
2)自定义快捷方式
IS自带有向导来让你指定快捷方式及其图标,但是这样一来必须把你指定的每一个图标作为一个资源强加载到安装程序中,在%windir%\Installer\[ProductCode]文件夹内生成N个EXE或ICO文件,二来也不能根据系统本身的情况作出适当调整,比如你有几个WORD文档,几个TXT文档和几个PDF文档,那么你用指定快捷方式向导的话你就得在上述文件夹内生成N个图标程序,而不管是否它们中几个文件类型是相同的,更别说某些用户系统类对该类文档的解析程序可能不同,它们的文件图标也可能根本跟你指定的不一样。对于这个问题,当然最灵活的方式就是用脚本添加快捷方式了。
完全从脚本添加快捷方式:
CreateProgramFolder ( @PRODUCT_NAME );
然后AddFolderIcon (szProgramFolder, szItemName, szCommandLine, szWorkingDir,
szIconPath, nIcon, szShortCutKey, nFlag);
就可以添加快捷方式到相应的位置了,使用时请注意用LongPathToQuote处理有空格的路径。这里有一点麻烦的就是查找已知文件的图标关联,起初我想的是找到相应的解析程序,然后从其应用程序里面提取图标。在IS里面调用一个批处理来获得该EXE路径,以查找PDF相应关联程序为例:
批处理脚本:
@echo off
setlocal enabledelayedexpansion
echo “%1”
for /f “tokens=2 delims==” %%i in (‘assoc .pdf’) do (
for /f “tokens=2 delims==” %%j in (‘ftype %%i’) do (
set “strPath=%%j”
set strPath=!strPath: “%%1″=!
echo !strPath!
echo !strPath!>”%1″
)
)

IS相应脚本:
szBATFile = SUPPORTDIR^”getPath.bat”;
szTXTFile = FOLDER_TEMP^”PDFRdr.txt”;
LongPathToQuote( szBATFile, TRUE );
LongPathToQuote( szTXTFile, TRUE );
LaunchAppAndWait ( szBATFile , szTXTFile , LAAW_OPTION_HIDDEN|LAAW_OPTION_WAIT );
if ( FindFile ( FOLDER_TEMP , “PDFRdr.txt” , svResult ) = 0 ) then
OpenFileMode ( FILE_MODE_NORMAL );
OpenFile ( nvFileHandle , FOLDER_TEMP , svResult );
GetLine ( nvFileHandle , svExePath );
CloseFile ( nvFileHandle );
endif;
if ( StrCompare ( svExePath , “” ) = 0 ) then
MessageBox ( “当前未查到有任何PDF阅读软件”, SEVERE );
else
MessageBox ( svExePath, INFORMATION );
endif;
将获得的应用程序图径代入AddFolderIcon,作为图标路径。结果发现诸如PDF文档的图标未必就在EXE里面,FoxitReader的图标在EXE文件里,可Adobe的Exe里面只有一个图标,PDF文档图标根本没有,没办法,还只有从注册表一途来查找了:
/*****************************
*取得系统PDF文件关联类型图标
*****************************/
function STRING GetPDFIcon()
NUMBER nStart, nvType, nvSize;
STRING szStart;
STRING svResult, svValue, svKey;
STRING svIconPath;
NUMBER nvIconIndex;
begin
RegDBSetDefaultRoot ( HKEY_CLASSES_ROOT );
svKey = “.pdf”;
if ( RegDBKeyExist ( svKey ) = 1 ) then
RegDBGetKeyValueEx ( svKey , “” , nvType , svValue , nvSize );
svKey = svValue;
if ( RegDBKeyExist ( svKey ) = 1 ) then
//子键存在,查找DefaultIcon,如果有,读取,如果没有,读取它的Version,再读注册表
if ( RegDBKeyExist ( svKey + “\\DefaultIcon” ) = 1 ) then
RegDBGetKeyValueEx ( svKey + “\\DefaultIcon” , “” , nvType , svValue, nvSize );
return svValue;
else
//未发现关联类型的DefaultIcon键,则查找其CurVer键,查其版本号
if ( RegDBKeyExist ( svKey + “\\CurVer” ) = 1 ) then
RegDBGetKeyValueEx( svKey + “\\CurVer”, “” , nvType, svValue, nvSize );
svKey = svValue;
if ( RegDBKeyExist ( svKey ) = 1 ) then
if ( RegDBKeyExist ( svKey + “\\DefaultIcon” ) = 1 ) then
RegDBGetKeyValueEx( svKey + “\\DefaultIcon” , “” , nvType, svValue, nvSize );
return svValue;
else
return “”;
endif; //endif check ICON value
else
return “”;
endif; //endif check CurVer value
else
return “”;
endif; //endif check CurVer Exist
endif; //endif check DefaultIcon Exist
endif; //end if check .pdf filetype exist
endif; //endif check .pdf key exist
end;

然后在OnFirstUIAfter()里面调用该函数取得文件关联路径:
//查找PDF文件关联,并取得关联类型图标
svDefaultIcon = GetPDFIcon();
if ( StrCompare ( svDefaultIcon , “” ) != 0 ) then
nStart = StrFind ( svDefaultIcon , “,” );
if ( nStart > 0 ) then
StrSub ( svPDFIconPath , svDefaultIcon , 0 , nStart );
StrSub ( svPDFIconIndex , svDefaultIcon , nStart+1 , 10 );
//MessageBox ( “图标路径为:” + svPDFIconPath + ” 索引号为:” + svPDFIconIndex , INFORMATION );
endif;
StrToNum ( nvPDFIconIndex , svPDFIconIndex );
else
svPDFIconPath = WINSYSDIR^”shell32.dll”;
nvPDFIconIndex = 0;
endif;
svResult = “”;
nvResult = FindAllFiles ( INSTALLDIR , “*.pdf” , svResult , RESET );
while ( !nvResult )
LongPathToQuote ( svResult , TRUE );
ParsePath ( svPDFFileName , svResult , FILENAME_ONLY );
AddFolderIcon ( FOLDER_PROGRAMS^@PRODUCT_NAME ,
svPDFFileName ,
svResult ,
“” ,
svPDFIconPath , nvPDFIconIndex ,
“” ,
REPLACE );
nvResult = FindAllFiles ( INSTALLDIR , “*.pdf” ,svResult , CONTINUE );
endwhile;

最后,添加卸载的快捷方式,就基本上大功告成了。
//添加卸载快捷方式
nStart = StrFind ( UNINSTALL_STRING , “.exe” );
if ( nStart >= 0 ) then
StrSub ( szUninstPath , UNINSTALL_STRING , 0 , nStart+4 );
LongPathToQuote ( szUninstPath , FALSE );
StrSub ( szUninstParam, UNINSTALL_STRING , nStart+4 , 200 );
LongPathToQuote ( szUninstParam, FALSE );
endif;
AddFolderIcon ( FOLDER_PROGRAMS^@PRODUCT_NAME ,
“卸载” + @PRODUCT_NAME ,
“\”” + szUninstPath + “\”” + szUninstParam ,
“” ,
INSTALLDIR^”Uninstall.ico” ,
0 ,
“” ,
REPLACE );

3)残留安装信息的删除
目前就发现一个IS的安装信息在[ProgramFilesFolder]\InstallShield Installation Information\[ProcuctCode]和[WindowsFolder]\Installer\[ProductCode]里面有相关的文件,于是安装之前先检测此两处文件夹是否存在,清除之,避免残留信息的干扰。
/*****************************
*旧版残留信息清除
*****************************/
function NUMBER DealOldEdition()
STRING szPath;
begin
//删除InstallSheild Installation Information信息
szPath = PROGRAMFILES^”InstallShield Installation Information”^PRODUCT_GUID;
if ( ExistsDir ( szPath ) = 0 ) then
if (LaunchAppAndWait( WINSYSDIR^”cmd.exe”, “/c rd /s/q \””+szPath+”\””, LAAW_OPTION_WAIT | LAAW_OPTION_HIDDEN) = 0) then
//MessageBox( “删除文件夹”+szPath+”成功”, INFORMATION );
else
return 0;
//MessageBox( “删除文件夹”+szPath+”失败”, INFORMATION );
endif;
endif;
//删除Installer信息
szPath = WINDIR^”Installer”^PRODUCT_GUID;
if ( ExistsDir ( szPath ) = 0 ) then
if (LaunchAppAndWait( WINSYSDIR^”cmd.exe”, “/c rd /s/q \””+szPath+”\””, LAAW_OPTION_WAIT | LAAW_OPTION_HIDDEN) = 0) then
//MessageBox( “删除文件夹”+szPath+”成功”, INFORMATION );
else
return 0;
//MessageBox( “删除文件夹”+szPath+”失败”, INFORMATION );
endif;
endif;
return 1;
end;

服务器端:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace AsyncServer
{
    public class UdpState
    {
        public UdpClient udpClient;
        public IPEndPoint ipEndPoint;
        public const int BufferSize = 1024;
        public byte[] buffer = new byte[BufferSize];
        public int counter = 0;
    }
    public class AsyncUdpSever
    {
        private IPEndPoint ipEndPoint = null;
        private IPEndPoint remoteEP = null;
        private UdpClient udpReceive = null;
        private UdpClient udpSend = null;
        private const int listenPort = 1100;
        private const int remotePort = 1101;
        UdpState udpReceiveState = null;
        UdpState udpSendState = null;
        private ManualResetEvent sendDone = new ManualResetEvent(false);
        private ManualResetEvent receiveDone = new ManualResetEvent(false);
        public AsyncUdpSever()
        {
            ipEndPoint = new IPEndPoint(IPAddress.Any, listenPort);
            remoteEP = new IPEndPoint(Dns.GetHostAddresses(Dns.GetHostName())[0], remotePort);
            udpReceive = new UdpClient(ipEndPoint);
            udpSend = new UdpClient();
            udpReceiveState = new UdpState();            
            udpReceiveState.udpClient = udpReceive;
            udpReceiveState.ipEndPoint = ipEndPoint;

            udpSendState = new UdpState();
            udpSendState.udpClient = udpSend;
            udpSendState.ipEndPoint = remoteEP;
        }
        public void ReceiveMsg()
        {
            Console.WriteLine("listening for messages");
            while(true)
            {
                lock (this)
                {   
                    IAsyncResult iar = udpReceive.BeginReceive(new AsyncCallback(ReceiveCallback), udpReceiveState);
                    receiveDone.WaitOne();
                    Thread.Sleep(100);
                }
            }
        }
        private void ReceiveCallback(IAsyncResult iar)
        {
            UdpState udpReceiveState = iar.AsyncState as UdpState;
            if (iar.IsCompleted)
            {
                Byte[] receiveBytes = udpReceiveState.udpClient.EndReceive(iar, ref udpReceiveState.ipEndPoint);
                string receiveString = Encoding.ASCII.GetString(receiveBytes);
                Console.WriteLine("Received: {0}", receiveString);
                //Thread.Sleep(100);
                receiveDone.Set();
                SendMsg();
            }
        }

        private void SendMsg()
        {
            udpSend.Connect(udpSendState.ipEndPoint);
            udpSendState.udpClient = udpSend;
            udpSendState.counter ++;

            string message = string.Format("第{0}个UDP请求处理完成!",udpSendState.counter);
            Byte[] sendBytes = Encoding.Unicode.GetBytes(message);
            udpSend.BeginSend(sendBytes, sendBytes.Length, new AsyncCallback(SendCallback), udpSendState);
            sendDone.WaitOne();
        }
        private void SendCallback(IAsyncResult iar)
        {
            UdpState udpState = iar.AsyncState as UdpState;
            Console.WriteLine("第{0}个请求处理完毕!", udpState.counter);
            Console.WriteLine("number of bytes sent: {0}", udpState.udpClient.EndSend(iar));
            sendDone.Set();
        }

        public static void Main()
        {
            AsyncUdpSever aus = new AsyncUdpSever();
            Thread t = new Thread(new ThreadStart(aus.ReceiveMsg));
            t.Start();
            Console.Read();
        }
    }
}

客户端:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace AsyncClient
{
    public class UdpState
    {
        public UdpClient udpClient = null;
        public IPEndPoint ipEndPoint = null;
        public const int BufferSize = 1024;
        public byte[] buffer = new byte[BufferSize];
        public int counter = 0;
    }
    public class AsyncUdpClient
    {
        public static bool messageSent = false;
        // Receive a message and write it to the console.
        private const int listenPort = 1101;
        private const int remotePort = 1100;
        private IPEndPoint localEP = null;
        private IPEndPoint remoteEP = null;
        private UdpClient udpReceive = null;
        private UdpClient udpSend = null;
        private UdpState udpSendState = null;
        private UdpState udpReceiveState = null;
        private int counter = 0;
        private ManualResetEvent sendDone = new ManualResetEvent(false);
        private ManualResetEvent receiveDone = new ManualResetEvent(false);
        private Socket receiveSocket;
        private Socket sendSocket;
        public AsyncUdpClient()
        {
            localEP = new IPEndPoint(IPAddress.Any, listenPort);
            remoteEP = new IPEndPoint(Dns.GetHostAddresses(Dns.GetHostName())[0],remotePort);
            udpReceive = new UdpClient(localEP);            
            udpSend = new UdpClient();

            udpSendState = new UdpState();
            udpSendState.ipEndPoint = remoteEP;
            udpSendState.udpClient = udpSend;

            udpReceiveState = new UdpState();
            udpReceiveState.ipEndPoint = remoteEP;
            udpReceiveState.udpClient = udpReceive;

            receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            receiveSocket.Bind(localEP);

            sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            sendSocket.Bind(remoteEP);
        }
        public void SendMsg()
        {   
            udpSend.Connect(remoteEP);

            //Thread t = new Thread(new ThreadStart(ReceiveMessages));
            //t.Start();
            Byte[] sendBytes;
            string message;            
            while (true)
            {   
                message = "Client" + (counter++).ToString();
                lock (this)
                {
                    sendBytes = Encoding.ASCII.GetBytes(message);
                    udpSendState.counter = counter;
                    udpSend.BeginSend(sendBytes, sendBytes.Length, new AsyncCallback(SendCallback), udpSendState);
                    sendDone.WaitOne();
                    Thread.Sleep(200);
                    ReceiveMessages();
                }
            }             
        }

        public void SendCallback(IAsyncResult iar)
        {
            UdpState udpState = iar.AsyncState as UdpState;
            if (iar.IsCompleted)
            {
                Console.WriteLine("第{0}个发送完毕!", udpState.counter);
                Console.WriteLine("number of bytes sent: {0}", udpState.udpClient.EndSend(iar));
                //if (udpState.counter == 10)
                //{
                //    udpState.udpClient.Close();
                //}
                sendDone.Set();
            }            
        }

        public void ReceiveMessages()
        {
            lock (this)
            {
                udpReceive.BeginReceive(new AsyncCallback(ReceiveCallback), udpReceiveState);
                receiveDone.WaitOne();
                Thread.Sleep(100);
            }   
        }
        public void ReceiveCallback(IAsyncResult iar)
        {
            UdpState udpState = iar.AsyncState as UdpState;
            if (iar.IsCompleted)
            {
                Byte[] receiveBytes = udpState.udpClient.EndReceive(iar, ref udpReceiveState.ipEndPoint);
                string receiveString = Encoding.Unicode.GetString(receiveBytes);
                Console.WriteLine("Received: {0}", receiveString);
                receiveDone.Set();
            }            
        }

        public static void Main()
        {
            AsyncUdpClient auc = new AsyncUdpClient();
            auc.SendMsg();
            Console.Read();
        }
    }
}

中国互联网络信息中心(CNNIC CA)在网上发布了新版的《中国互联网发展大事记》。在该大事记修订过程中,技术人员首次核实并确认了我国发出的第一封电子邮件的时间和原文内容。
CNNIC CA确认的结果显示,第一封从我国发出的电子邮件:”Across the Great Wall we can reach every corner in the world.(越过长城,走向世界)”,是北京市计算机应用技术研究所于1987年9月14日21时07分发往德国的。通过与德国卡尔斯鲁厄大学档案馆联系,CNNIC CA查到了这封邮件的打印件。

现在我分享一下能真正翻越长城,到达世界的玩意:

萤火虫

Firefly 萤火虫

请更新至最新版本 (android 0.0.9, 桌面 0.5.0)

下载

“地球众生都是以地球围绕太阳的公转周期作为一年的计量,但是我总是与时间有着不同的关系,而且并不仅仅因为我是火星人。(这只是开玩笑,你不必当真。)但即使是我也无法否认时间正向前发展。我的人生中发生了如此多的事情,而且现在似乎是分享我的‘家庭成员’最新成绩的良好时机。

SpaceX(15岁)生长的非常迅速。SpaceX不仅成功在今年夏天完成了国际空间站的第12次再补给任务,而且一份相当详细的殖民火星计划彰显着它的不断提升的野心。这项计划中包含了一个被公司称为BFR火箭的行星间运输系统,而且将在5年内建造完成。

特斯拉(14岁)在今年夏天承诺开始交付Model 3汽车之后,我的这个小汽车制造商为了达成目标一路狂奔。Model 3可以说是一项奇迹,拥有巨大的玻璃车顶、革命性的触屏界面以及你所期待的特斯拉加速能力。我可以肯定已经预定Model 3的40万消费者在得到他们的汽车之后会认同我的观点。早熟的特斯拉好像还不满足于此,今年还展示了一款电动卡车,而且有许多公司已经下了订单,此外还有一款能够给汽油车带来冲击的全新跑车。

OpenAI(2岁)和Neuralink(1岁)。我一直认为我们应当将我们的大脑与计算机融合到一起,而且我很高兴这两个年轻的家庭成员致力于实现我的这一梦想。OpenAI甚至还在借助异常苗条的相扑选手让人工智能更加聪明和敏捷。或许未来它能够使用为自动驾驶系统设计的AI芯片帮助它的哥哥特斯拉。

Boring公司(1岁)这个月度过的自己的第一个生日。根据它在SpaceX的洛杉矶总部下面和巴尔的摩下面挖掘的隧道,我不得不猜测是否有人会借助鼹鼠人欺骗我。Boring了解我对于公共交通系统的想法,而且让我放心的是这些隧道是为充满想象力的超回路列车和搭乘电动滑板行动的私家车准备的。如果有一天老年人能够乘坐地下运输系统去看望自己的孩子的话,我会感到很欣慰,我不必担心连环杀人恶魔或者是其他危险人物出现。

那么长话短说,给我的朋友们、粉丝们、伙伴们、竞争对手们还有投资者们送上我最真挚的祝福。愿你们的生活和我们的一样富有、令人振奋而且充满野心!”(过客)

以下为贺卡英文原文:

Earthlings measure years by how long it takes this planet to revolve around its sun, but I’ve always had a different relationship with time, and not just because I’m a Martian. (Kidding. Maybe. You know, don’t worry about it.) But even I can’t deny things are moving forward. And with so much going on in my life, this seems as good a time as any to share my family’s latest accomplishments.

(NB: For ease of use, I’ll be using Earth years.)

SpaceX (age 15)Man these companies grow up fast. SpaceX didn’t just successfully launch its 12th resupply missionto the International Space Station this summer, it upped its ambitions with a pretty detailed plan for colonizing Mars. (OK, as long as it comes home for Thanksgiving and Christmas!) The scheme involves an Interplanetary Transport System the company calls the BFR, or Big Fucking Rocket (you wonder where they get their sense of humor!), which it will definitely have built in just five years.

Tesla (age 14)After promising to start deliveries of its affordable Model 3 sedan this summer, my little automaker went all the way to production hell to make it happen. And boy is the car a wonder, with its huge glass roof, innovative touchscreen interface (so long, dashboard), and all the acceleration you know to expect from Tesla. I’m sure the 400,000 people who have pre-ordered one will agree whenever they get theirs! As if that wasn’t enough, my precocious teen showed off an electric semitruck—a bunch of companies have already put their orders in—and a new version of the Roadster, just to give a hardcore smackdown to gasoline cars. And it did it all even with the SEC poking aroundand handling a class action lawsuit accusing it of racist practices! This is the kid who does all the extracurriculars.

OpenAI and Neuralink (ages 2, 1)I’ve always thought we should merge our brains with computers, and I’m so glad two of my youngest are dedicated to making it happen. As if that weren’t enough, OpenAI is using surprisingly svelte sumo wrestlersto make artificial intelligence smarter and nimbler. Maybe it’ll even find the time to help big brother Tesla with that AI chip it’s making for Autopilot.

The Boring Company (age 1)Celebrated its first birthday this month! And based on the tunnels it has been digging under SpaceX’s headquarters in Los Angeles and under Baltimore, I have to wonder if someone cheated on me with the moleman! Now, Boring knows my views on public transit, and has reassured me these tunnels will be for fancy hyperloops and private cars on electric sleds, only. I’m glad to know that when it’s time for the old man to visit his kid underground, I won’t have to worry about serial killers or even other people.

So, my friends and fans, comrades and competitors, investors short and long, my best tidings. May your lives be as rich, electrifying, and ambitious as ours.

— Elon

FineCMS学习第二节:M_Controller学习:

今天在M_Controller内遇到如下代码,故对如何判断ajax请求,post请求及REQUEST_TIME于time()函数差别做了一下学习,再次记录给大家参考

define(‘IS_AJAX’, $this->input->is_ajax_request());
define(‘IS_POST’, $_SERVER[‘REQUEST_METHOD’] == ‘POST’ && count($_POST) ? TRUE : FALSE);
define(‘SYS_TIME’, $_SERVER[‘REQUEST_TIME’] ? $_SERVER[‘REQUEST_TIME’] : time());
is_ajax_request()方法是CI内判断是否ajax请求的一种方法,官方文档对于它的解释是:检查服务器头中是否含有 HTTP_X_REQUESTED_WITH ,如果有返回 TRUE ,否则返回 FALSE 。

当然,这样的判断是不够完善的,涉及到判断的原理如下:

当我们通过jquery发送ajax请求时,会在请求头部添加一个名为 X-Requested-With 的信息,信息默认内容为:XMLHttpRequest,这是CI框架对于ajax请求判断的依据,但是,当我们用原生js时如何进行判断呢?

xmlHttpRequest.setRequestHeader(“request_type”,”ajax”);
根据XMLHttpRequest对象,手动进行头部设置,如上设置完成后,我们可以再PHP逻辑代码中如下获取:

$_SERVER[‘HTTP_REQUEST_TYPE’]
很明显,我们在头部设置的request_type在后台获取时,加上HTTP_的前置字符串,即可获取到前台发送的Header信息,即通过调用$_SERVER[‘HTTP_REQUEST_TYPE’]得到前台传值:ajax,当然request_type也可以自行设置为别的字段。

在学习中,发现跨域请求时,这种方法是不能使用的,后续学习后再行补充!

===========================================分割线==================================

POST与GET请求判断:

搜索了一圈,简单对此有了解,决定现在次标注一下,学习完《图解HTTP》以后再进行详细解析

===========================================分割线==================================

$_SERVER[“REQUEST_TIME”]与time()同样是返回自从 Unix 纪元(格林威治时间 1970 年 1 月 1 日 00:00:00)到当前时间的秒数

其中$_SERVER[“REQUEST_TIME”]从PHP 5.1.0有效

其中$_SERVER[“REQUEST_TIME”]记录了请求的发送时间,而time()是记录运行至此句代码时的当前时间

步骤1:设置config.php(项目使用了独立分组)
‘APP_SUB_DOMAIN_DEPLOY’=>1, // 开启子域名配置
/*子域名配置
*格式如: ‘子域名’=>array(‘分组名/[模块名]’,’var1=a&var2=b’);
*/
‘APP_SUB_DOMAIN_RULES’=>array(
‘###.****.com’=>array(‘Admin/’), // admin域名指向Admin分组
‘###.****.com’=>array(‘Home/’), // test域名指向Home分组
),
步骤2:配置http-vhost.conf文件,文件位置在wamp安装目录下,我的是D:\wamp\bin\apache\Apache2.2.21\conf\extraz
在文件中修改添加如下内容:

ServerAdmin 分组名@###.****.com
DocumentRoot “D:/wamp/www/项目名称”
ServerName ###.****.com
ErrorLog “logs/dummy-host2.example.com-error.log”
CustomLog “logs/dummy-host2.example.com-access.log” common

多少个二级域名则添加修改多少个
步骤3:修改hosts文件文件位置在C:\Windows\System32\drivers\etc文件夹中
在最后添加
127.0.0.1 ###.****.com
步骤4:重启wamp,测试即可

0x00.影响版本 

Version 4.6.x (<4.6.3)
Version 4.4.x (<4.4.15.7)

 

0x01.漏洞描述

phpMyAdmin是一个web端通用MySQL管理工具,上述版本在central_columns.lib.php文件里的db参数存在sql注入漏洞,攻击者利用此漏洞可以获取数据库中敏感信息,甚至可以执行任意命令

0x02.测试环境 

Windows7(X64)+PHP5.5.12+Apache/2.4.9+phpMyAdmin4.6.2

0x03.漏洞详情 

从官方的补丁ef6c66dca1b0cb0a1a482477938cfc859d2baee3来看,其对libraries\central_columns.lib.php中某几个function中的$db参数进行了转义,由此我们可以初步判定注入点就出现在这几个函数的$db参数处 ;
我们全局搜索一下引入了central_columns.lib.php的文件
总共四个文件包含了central_columns.lib.php,其中三个文件为库函数文件,因此最直接与用户交互的文件还是db_central_columns.php
我们来到db_central_columns.php,搜索一下打补丁的几个函数:

PMA_deleteColumnsFromList出现在大约89行
PMA_getColumnsList出现在大约135行
PMA_getCentralColumnsCount出现在大约94行
PMA_getCentralColumnsListRaw出现在大约49行

我们应该尽量选择不需要满足过多if条件语句以及位置尽量靠前的不危险函数分析,跟进PMA_getCentralColumnsCount函数:

我们能在函数91行之前打印出任意字符串,而在if语句之后无法打印出字符串,我们看到函数93~95行出现了if语句判断,因此我们跟进if语句if(empty($cfgCentralColumns)),其中$cfgCentralColumns是从PMA_centralColumnsGetParams获取到的,继续跟进PMA_centralColumnsGetParams函数:
该函数同样在central_columns.lib.php文件里大约17行

我们在25行之后打印一下$cfgRelation变量

根据接下来的if语句,我们需要使得$cfgRelation[‘centralcolumnswork’]不为false,才能使程序完整执行下去,那么$cfgRelation[‘centralcolumnswork’]是个什么变量呢?
跟进函数PMA_getRelationsParam(在libraries\relation.lib.php大约61行)

继续跟进函数PMA_checkRelationsParam(在libraries\relation.lib.php大约451行)

我们可以看到,我们需要的变量在这个foreach语句中被定义成了false,我们继续向下跟进,从大约563行起,出现了如下语句:

官方文档http://docs.phpmyadmin.net/zh_CN/latest/config.html,我们可以知道PMA_checkRelationsParam函数的作用就是检查用户是否自定义了配置文件,只有用户自定义了配置文件,我们需要的$cfgRelation[‘centralcolumnswork’]才能为True

那么,如何自定义配置文件呢?

我们知道,phpMyAdmin在没有自定义配置文件时会默认加载libraries\config.default.php中的配置;要自定义配置文件,我们只需要在phpMyAdmin根目录将config.sample.inc.php文件copy为config.inc.php,并将41~44/47~68行取消注释,43以及44行改为MySQL用户(需要与攻击时phpMyAdmin的登录用户一致):

然后创建一个名为phpmyadmin的数据库,选中该数据库然后点击导航栏“操作”,根据页面错误提示即可自动创建phpMyAdmin自身的数据库

接下来回到central_columns.lib.php文件PMA_centralColumnsGetParams函数中,
我们在与之前同样的位置打印一下$cfgCentralColumns变量,我们发现页面已经能让你能够dump出数据了,说明程序已经能够向下执行,$cfgRelation[‘centralcolumnswork’]变量为True

 

接下来我们到存在漏洞的函数PMA_getCentralColumnsCount中打印查询语句以及结果集:
我们发现打印在页面上的sql语句存在很典型的注入类型,如下图所示:

最后需要明确的一个问题,central_columns是什么?
我们知道之前我们创建的数据库phpmyadmin存在一个数据表pma_central_columns,而任意一个数据表的任何字段都可以添加进入pma_central_columns,在创建新的表或者添加字段时就能从此表中直接选取插入,所以我们能看出central_columns是作为储存一些关键字段,以方便添加外键时使用。

0x04.漏洞修复 

1.使用Utils::sqlAddSlashes函数对libraries\central_columns.lib.php的$db参数转义,
或者升级phpMyAdmin Version4.6至4.6.3,Version4.4 升级至4.4.15.7以修复此漏洞

0x05.漏洞总结 

phpMyAdmin本身作为数据库管理工具,登录后的注入显得相当鸡肋,最近的无需登录漏洞也是出在2.8.3左右版本。而从出现漏洞的文件来看,同一个文件仅仅部分函数进行了转义防护,这也可以看出开发人员在修复漏洞时“指哪修哪”,而最近以色列安全研究人员@e3amn2l提交的近20个漏洞中也出现了相似的未转义引起的漏洞,更加印证了这一点。
因此我认为安全从业人员在接收到安全漏洞时,应该首先构思最优的安全修复指南,而不是直接交给开发人员进行修复,多一个流程也可尽量避免在同一个地方重复跌倒。

0x06.参考资料 

http://smita786.blogspot.com/2014/06/gsoc14-coding-week-3-using-central-list.html
2016年phpMyAdmin漏洞统计

在网上支付输入银行卡的时候,经常看到输入的数字会放大和提示。

下面是WPF版的一个例子。

 public class ZoomTextTooltip : FrameworkElement
    {
        public object ZoomText
        {
            get { return (object)GetValue(ZoomTextProperty); }
            set { SetValue(ZoomTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomTextProperty =
            DependencyProperty.Register("ZoomText", typeof(object), typeof(ZoomTextTooltip), new UIPropertyMetadata(null,
                (o, args) =>
                {
                    var textBox = args.NewValue as TextBox;
                    if (textBox == null)
                        return;
                    var zoomTextTooltip = o as ZoomTextTooltip;
                    if (zoomTextTooltip == null)
                        return;
                    var popup = new Popup();
                    var textBlock = new TextBlock
                    {
                        Text = textBox.Text,
                        FontWeight = FontWeights.Bold,
                        FontSize = zoomTextTooltip.CustomFontSize,
                        Background = zoomTextTooltip.CustomBackground,
                        Foreground = zoomTextTooltip.CustomForeground
                    };
                    var binding = new Binding();
                    binding.Source = textBox;
                    binding.Path = new PropertyPath("IsKeyboardFocused");
                    BindingOperations.SetBinding(popup, Popup.StaysOpenProperty, binding);

                    var inputText = zoomTextTooltip.AddBlockString(textBox.Text.Trim(), zoomTextTooltip.BlockCount);
                    textBlock.Text = inputText;
                    popup.Child = textBlock;

                    textBox.GotFocus += (sender, eventArgs) =>
                                               {
                                                   popup.PlacementTarget = textBox;
                                                   popup.Placement = PlacementMode.Top;
                                                   popup.IsOpen = true;
                                               };
                    textBox.TextChanged += (sender, eventArgs) =>
                                               {
                                                   var addBlockString = zoomTextTooltip.AddBlockString(textBox.Text.Trim(), zoomTextTooltip.BlockCount);
                                                   textBlock.Text = addBlockString;
                                               };

                    textBox.LostFocus += (sender, eventArgs) =>
                                             {
                                                 popup.IsOpen = false;
                                             };
                }
                ));
        //字符串截取
        private string AddBlockString(string input, int count)
        {
            if (count == 0)
                return input;
            var blockinput = string.Empty;
            var length = Math.Ceiling((double)input.Length / count);
            for (int i = 0; i < length; i++)
            {
                var firstStart = i * count;
                var endString = input.Length - firstStart;
                if (endString < count)
                {
                    blockinput += input.Substring(firstStart);
                }
                else
                {
                    blockinput += input.Substring(firstStart, count);
                    blockinput += " ";
                }

            }
            return blockinput;
        }
        public double CustomFontSize
        {
            get { return (double)GetValue(CustomFontSizeProperty); }
            set { SetValue(CustomFontSizeProperty, value); }
        }

        public static readonly DependencyProperty CustomFontSizeProperty =
            DependencyProperty.Register("CustomFontSize", typeof(double), typeof(ZoomTextTooltip), new UIPropertyMetadata(12.0));

        public Brush CustomBackground
        {
            get { return (Brush)GetValue(CustomBackgroundProperty); }
            set { SetValue(CustomBackgroundProperty, value); }
        }

        public static readonly DependencyProperty CustomBackgroundProperty =
            DependencyProperty.Register("CustomBackground", typeof(Brush), typeof(ZoomTextTooltip), new UIPropertyMetadata(Brushes.White));

        public Brush CustomForeground
        {
            get { return (Brush)GetValue(CustomForegroundProperty); }
            set { SetValue(CustomForegroundProperty, value); }
        }

        public static readonly DependencyProperty CustomForegroundProperty =
            DependencyProperty.Register("CustomForeground", typeof(Brush), typeof(ZoomTextTooltip), new UIPropertyMetadata(Brushes.Black));

        public int BlockCount
        {
            get { return (int)GetValue(BlockCountProperty); }
            set { SetValue(BlockCountProperty, value); }
        }

        public static readonly DependencyProperty BlockCountProperty =
            DependencyProperty.Register("BlockCount", typeof(int), typeof(ZoomTextTooltip), new UIPropertyMetadata(0));



    }
<TextBox  x:Name="tb" /> 
<ControlTest:ZoomTextTooltip CustomFontSize="20"  ZoomText="{Binding ElementName=tb}" BlockCount="4"/>

图示:

image

MYSQL查询重复记录的方法很多,下面就为您介绍几种最常用的MYSQL查询重复记录的方法,希望对您学习MYSQL查询重复记录方面能有所帮助。

1、查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断

  1. select * from people
  2. where peopleId in (select peopleId from people group by peopleId having count(peopleId) > 1)

2、删除表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断,只留有rowid最小的记录

  1. delete from people
  2. where peopleId in (select peopleId from people group by peopleId   having count(peopleId) > 1)
  3. and rowid not in (select min(rowid) from people group by peopleId having count(peopleId )>1)

3、查找表中多余的重复记录(多个字段)

  1. select * from vitae a
  2. where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)

4、删除表中多余的重复记录(多个字段),只留有rowid最小的记录

  1. delete from vitae a
  2. where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)
  3. and rowid not in (select min(rowid) from vitae group by peopleId,seq having count(*)>1)

5、查找表中多余的重复记录(多个字段),不包含rowid最小的记录

  1. select * from vitae a
  2. where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)
  3. and rowid not in (select min(rowid) from vitae group by peopleId,seq having count(*)>1)

开发WEB应用系统通常都会遇到报表打印问题。简单应用可利用IE的页面打印功能,利用HTML标签控制格式来实现。但复杂的业务型应用系统,报表不仅是组成应用的重要部分,还常常是相当复杂的。现在很多应用系统都要求提供自定义报表的功能——即客户可以自行设计、修改报表。

在C/S结构系统中,报表问题有很多成熟的解决方法。如DELPHI开发工具不仅自带有报表控件,还可以利用第三方控件来实现快速灵活的报表制作和打印,其中有名的控件是FR-Software & A.Tzyganenko 的FastReport。FastReport提供了能与DELPHI无缝集成的从设计到打印的完整控件包,提供的设计界面友好灵活,对于开发可让用户自定义报表的C/S应用来说,是一种很好的解决方式。

在B/S结构应用中,Crystal Report是一种大型报表系统常用和推荐的解决方案。但Crystal Report目前价格昂贵,而且该系统相当庞大。它的可定制性及精确控制打印效果方面尚不够完善。当然,在目前市场上,它仍是一种首选的WEB应用的报表解决方案。

如果能将C/S应用中成熟的报表解决方案搬到B/S应用中,相信对于大部分开发人员来说,都是非常欢迎的。本文将讲述一个在JAVA环境中利用FastReport实现B/S应用中用户可自定义的报表解决方案。因为笔者近段时间正用DELPHI、JAVA做一些项目,所以样例代码就以DELPHI、JAVA编写。

本解决方案样例的基本环境是:WINDOWS 2000 SERVER+SQL SERVER 2000+TOMCAT 4.0。开发工具:IntelliJ IDEA 3.0,DELPHI 5.0。客户端为IE 5.0浏览器。

方案共要求用DELPHI编写两个程序,一个是将被包含在网页中并在浏览器中运行的ACTIVEX(.ocx),一个是运行在服务器端的报表处理程序,中间通过JAVA程序连接——或任何其他WEB语言都可以,如ASP、PHP等。方案的基本原理图如下:

报表设计过程

 

 

报表打印过程

 

 

REPORT SERVER:可以做成一个普通的WINDOWS程序,也可以做成一个COM程序(Automation Object)。在报表设计过程中,用户端(ACTIVEX)向WEB SERVER发送报表设计请求,请求中包含要设计报表的名称;WEB SERVER 收到该请求后,调用REPORT SERVER请求设计的报表文件;REPORT SERVER收到请求后,先装载报表的数据环境,然后将报表设计文件(.frf)和该报表的数据环境文件压缩成一个包文件(.zip),将该包文件的完整路径名返回给WEB SERVER调用程序;WEB SERVER将包文件回送给用户端(ACTIVEX),用户端将接收到的包文件保存到本地硬盘上,并解压缩,从数据环境文件中恢复数据环境,通过FASTREPORT的相应控件打开报表文件给用户提供可视化设计。用户在ACTIVEX中设计报表时,虽然不能和数据库连接,但因数据环境已存在,所以用户仿如在通常的C/S应用结构下设计报表,能正常地看到报表的数据字典信息。在报表打印过程中,用户端(ACTIVEX)向WEB SERVER发送报表打印/预览请求,请求中包含要打印/预览的报表名称;WEB SERVER 收到该请求后,调用REPORT SERVER请求打印或预览的报表文件;REPORT SERVER收到请求后,先装载报表的数据环境,然后装载报表文件(.frf),接着在无界面状态下运行报表,最后将生成的已准备的报表文件(.frp)压缩成一个包文件(.zip),将该包文件的完整路径名返回给WEB SERVER调用程序;WEB SERVER将包文件回送给用户端(ACTIVEX),用户端将接收到的包文件保存到本地硬盘上,并解压缩,通过FASTREPORT的相应控件打开报表文件(.frp)给用户预览或打印或重新调整格式或输出为其他格式文件。用户在ACTIVEX中预览报表,仿如在通常的C/S应用结构下预览报表。

WEB SERVER:提供通常的WEB服务功能。

ACTIVEX:ActiveX是Microsoft提出的一组使用COM(Component Object Model,部件对象模型)使得软件部件可在网络环境中进行交互的技术集。它与具体的编程语言无关。作为针对Internet应用开发的技术,ActiveX被广泛应用于WEB服务器以及客户端的各个方面。本方案中的ACTIVEX控件主要做两方面的事情,一是利用FASTREPORT控件进行报表处理,包括报表设计(.frf文件)和报表打印(.frp文件)。一是与WEB SERVER进行通信,请求和接收包文件。本文样例的ACTIVEX采用DELPHI 5.0编写。

下面分述各部分的一例具体实现(因为仅为说明方案的实现,所以很多代码细节都进行了简省)。

一、             REPORT SERVER

REPORT SERVER既可以做成一个普通的WINDOWS程序,也可以做成一个COM程序(Automation Object)。本例中为简化见,采用普通的WINDOWS程序实现。

在DELPHI中NEW一个应用程序。在FORM中加入TfrReport、TfrDBDataSet、ADOConnection、TADOQuery等控件——为了使用FASTREPORT的控件,需要安装该控件包,可从站点http://www.fast-report.com 下载,国内很多软件站点都提供该控件包的下载。其中TfrDBDataSet、TADOQuery控件视应用需要可加入多个,另外为了压缩文件,还要加入一个压缩控件,本例使用VCLZip。在Form1中加入三个函数:preDesignReport(rpFileName:String),prePrintReport(rpFileName:String),zipReportFiles(rpFileName:String),分别用于准备报表设计文件、准备报表打印文件、压缩报表文件 。Form1.Create方法为:

procedure TForm1.FormCreate(Sender: TObject);

var

rpFileName,mode:String;

begin

if paramCount>1 then

begin

mode:=paramStr(1);

rpFileName:=paramStr(2);

if mode=’d’ then   //设计报表

if preDesignReport(rpFileName) then

zipReportFiles(rpFileName);

if mode=’r’ then   //打印报表

if prePrintReport(rpFileName) then

zipReportFiles(rpFileName);

end;

Application.Terminate;

end;

程序根据调用参数判断是准备报表设计文件还是准备报表打印文件,接着调用相应的过程来实现。最后的Application.Terminate 是让程序执行功能后即退出——因为这是服务端程序,是不能与用户交互的。

preDesignReport(rpFileName:String)方法:

function TForm1.preDesignReport(rpFileName:String):boolean;

var

……   //其他变量

dtfFileName:String;

begin

……

dtfFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),’.dtf’,

[rfReplaceAll, rfIgnoreCase]);

try

rpAdoquery.SQL.Add(‘…’);

rpAdoquery.open;//打开报表的数据环境

rpAdoquery.FieldList.SaveToFile(dtfFileName);

result:=true;

except

on Exception do

result:=false;

end;

end;

函数preDesignReport的作用是准备报表设计文件。报表中可以引用多个DataSet,本例假设报表只引用一个名为rpAdoquery的DataSet。rpFileName 为报表文件名(.frf),DtfFileName为保存数据环境的文件名(.dtf)。因为用户端不能连接数据库,所以将DataSet中的Fileds通过rpAdoquery.FieldList.SaveToFile(dtfFileName)保存到文件,和报表文件一起传送给用户端的ACTIVEX,ACTIVEX利用.dtf文件复现报表的数据环境。

prePrintReport(rpFileName:String)方法:

function TForm1.prePrintReport(rpFileName:String):boolean;

var

……

repFileName:String;

begin

……

repFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),’.frp’,

[rfReplaceAll, rfIgnoreCase]);

try

rpAdoquery.SQL.Add(‘…’);

rpAdoquery.open;//打开报表的数据环境

frReport1.ShowProgress:=False;

frReport1.Clear;

frReport1.LoadFromFile(rpFileName);

frDBDataSet1.DataSet :=rpAdoquery;

frReport1.Dataset :=frDBDataSet1;

frReport1.PrepareReport;

frReport1.SavePreparedReport(repFileName);

result:=true;

except

on Exception do

result:=false;

end;

end;

函数prePrintReport的作用是准备打印的报表文件,即先在服务器端装载报表并运行,将运行好的报表保存为文件,用于传送到用户端进行预览或打印。RepFileName是已准备好的报表文件名(.frp)。同样假设报表只引用一个名为rpAdoquery的DataSet。frReport1.ShowProgress:=False 使报表运行过程中不显示进度窗口(服务器端不能显示与用户交互的界面);接下来frReport1.Clear;…装载报表文件及设置相关数据属性;frReport1.PrepareReport 是在不显示预览窗口的情况下运行报表;frReport1.SavePreparedReport(repFileName) 将运行好的报表保存到文件,该文件传送给用户端的ACTIVEX,ACTIVEX可以直接预览或显示该报表。

zipReportFiles(rpFileName:String)方法:

function TForm1.zipReportFiles(rpFileName:String):boolean;

var

……

zipFileName,fileName:String;

zipCount:Integer;

begin

……

zipFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),’.zip’,

[rfReplaceAll, rfIgnoreCase]);

fileName:= ExtractFileName(rpFileName);

fileName:= ChangeFileExt(fileName,’.*’);

try

VCLZip1.ZipName:= zipFileName;

VCLZip1.RootDir:= ‘./’;

VCLZip1.FilesList.Add(fileName);

zipCount:= VCLZip1.Zip;

if zipCount = 0 then

result:=false

else

result:=true;

except

on Exception do

result:=false;

end;

end;

函数zipReportFiles的作用是把要传送给用户端的报表文件压缩为一个.zip文件,简化文件传送过程,而且压缩了数据量。ACTIVEX接收到.zip文件后,先解压出包中文件,再进行处理。

 

 

二、             WEB SERVER

方案中WEB SERVER的作用主要是根据ACTIVEX的请求调用REPORT SERVER,并将REPORT SERVER生成的.zip文件发送给ACTIVEX。样例通过一个report.jsp文件来处理:ACTIVEX通过get请求report.jsp文件,report.jsp文件调用REPORT SERVER处理后,将.zip文件发送给ACTIVEX。

Report.jsp文件:

<%@ page import=”…”%>

<%@page contentType=” APPLICATION/OCTET-STREAM” %>

<%

try

{

String reqFileName = request.getParameter(“rpFileName”);

String reqMode = request.getParameter(“mode”);//d为设计报表,r为打印报表

String rpFileName = xxxx.getRpFileName(reqFileName); //根据请求的报表名获得实际的报表文件名,如请求订单报表,而订单报表实际对应的报表文件为order.frf。

String      l_cmd=”reportserver.exe “+reqMode+” “+ reqFileName;

Process l_ps=java.lang.Runtime.getRuntime().exec(l_cmd,null);

byte[] l_b=new byte[100];

while(l_ps.getInputStream().read(l_b,0,100)!=-1){

;

}

 

//发送文件

String zipFileName = xxxx.getZipFileName(reqFileName); //获得压缩文件名

response.setHeader(“Content-Disposition”,”attachment; filename=/”” +

zipFileName + “/””);

java.io.FileInputStream fileInputStream =

new java.io.FileInputStream(zipFileName);

int i;

while ((i=fileInputStream.read()) != -1) {

out.write(i);

}

fileInputStream.close();

out.close();

}

catch(Exception e)

{

……

}

%>

String l_cmd=”reportserver.exe “+reqMode+” “+ reqFileName; 组成调用REPORT SERVER的命令串。while(l_ps.getInputStream().read(l_b,0,100)!=-1){ ; } 等待REPORT SERVER执行完成,否则,程序在启动REPORT SERVER后即执行下一行语句。发送文件的方式有多种,比如也可以由ACTIVEX通过ftp方式取得。

 

 

 

三、ACTIVEX

方案中的ACTIVEX控件主要做两方面的事情,一是报表利用FASTREPORT控件进行报表处理,包括报表设计(.frf文件)和报表打印(.frp文件)。一是与WEB SERVER进行通信,请求和接收包文件。

在DELPHI中NEW一个ActiveForm 应用,取名为reportAForm。在form中加入Combox、button、edit、label等与用户交互的控件;为了处理报表,加入FASTREPORT的多个frSpeedButton用于处理报表事件,如设计、预览、打印、翻页、保存等;加入frReport、frDBDataSet、frDesigner等用于在运行时设计报表;如果设计报表时要使用图形、复选框等内容,也要加入相应的控件;加入frPreview、frTextExport、frRTFExport等控件使可以预览报表并可以将报表输出为text、rtf等格式文件;加入ADOQuery(根据实际需要可加入多个)为报表设计提供数据环境,ADOQuery不OPEN,不与数据库连接;加入NMHTTP用于与WEB SERVER联系。加入四个函数:DesignReport(rpFileName:String),PrintReport(rpFileName:String),unzipReportFiles(rpFileName:String),getReportFile(rpFileName,mode:String)分别用于设计报表、打印报表、解压缩报表和向WEB SERVER发送请求以取得报表文件 。

getReportFile(rpFileName,mode:String)方法:

function TreportAForm.getReportFile(rpFileName,mode:String):boolean;

var

……

zipFileName:String;

begin

……

zipFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),’.zip’,

[rfReplaceAll, rfIgnoreCase]);

try

NMHTTP1.inputFileMode := TRUE;

NMHTTP1.body:=’./ ‘+ zipFileName;

NMHTTP1.Get(‘http://www…./../report.jsp?rpFileName=’+

rpFileName+’&mode=’+mode);

Result:=true;

except

on Exception do

Result:=false;

end;

end;

函数getReportFile的作用是向WEB SERVER发送报表请求(通过NMHTTP的Get方法),并将返回的压缩包文件保存到本地硬盘(zipFileName)。

unzipReportFiles(rpFileName:String)方法:

function TreportAForm.unzipReportFiles(rpFileName:String) :boolean;

var

……

zipFileName,fileName:String;

zipCount:Integer;

begin

……

zipFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),’.zip’,

[rfReplaceAll, rfIgnoreCase]);

fileName:= ExtractFileName(rpFileName);

fileName:= ChangeFileExt(fileName,’.*’);

try

VCLUnZip1.ZipName:= ‘./’+ zipFileName;

VCLUnZip1.DestDir:= ‘./’;

VCLUnZip1.OverwriteMode:= Always;

VCLUnZip1.ReadZip;

VCLUnZip1.FilesList.Add(fileName);

zipCount:= VCLUnZip1.UnZip;

if zipCount = 0 then

result:=false

else

result:=true;

except

on Exception do

result:=false;

end;

end;

函数unzipReportFiles的作用是将压缩包中的文件解压出来,供ACTIVEX使用。它与REPORT SERVER程序中的zipReportFiles刚好是个相反的过程。

DesignReport(rpFileName:String)方法:

function TreportAForm. DesignReport (rpFileName:String) :boolean;

var

dtfFileName,rpFileName:String;

fldlist:TStringList;

T: TStringField;

i:Integer;

begin

……

dtfFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),’.dtf’,

[rfReplaceAll, rfIgnoreCase]);//获得数据环境文件名

fldlist:=TStringList.Create;

fldlist.LoadFromFile(dtfFileName);

rpAdoquery.Fields.Clear;

for i := 0 to fldlist.Count – 1 do

begin

T := TStringField.Create(nil);

T.FieldName := fldlist[i];

T.Name := rpAdoquery.Name + T.FieldName;

rpAdoquery.Fields.add(T);

end;

FrReport1.LoadFromFile(rpFileName);

FrReport1.DesignReport;

end;

函数DesignReport先从.dtf(由REPORT SERVER生成)文件中恢复报表的数据环境,接着使用FASTREPORT的FrReport控件设计报表。在FASTREPORT中,对DataSet中的Field只关心名称(全部通过Variant类型处理),而并不关心数据类型,所以恢复报表的数据环境时,所有字段都当作String类型加入。样例假设报表只有一个名为rpAdoquery的DataSet。报表设计运行时窗口在ACTIVEX进程空间运行。

用户端设计好报表并保存后,需要将保存的报表文件(.frf)回送给服务器存储。文件上传对于大部分开发人员来说应该都是熟悉而简单的,该部分程序本文就省略了。

PrintReport(rpFileName:String)方法:

function TreportAForm. PrintReport (rpFileName:String) :boolean;

var

repFileName:String;

begin

……

repFileName:=StringReplace(rpFileName, ExtractFileExt(rpFileName),’.frp’,

[rfReplaceAll, rfIgnoreCase]);//获得已准备的报表文件名

try

frPreview1.clear;

FrReport1.Preview:=nil;

FrReport1.clear;

FrReport1.LoadPreparedReport(repFileName);

FrReport1.Preview :=frPreview1;

FrReport1.ShowPreparedReport;

result:=true;

except

on Exception do

result:=false;

end;

end;

函数PrintReport装入由REPORT SERVER运行好的报表.frp文件,通过调用FrReport的ShowPreparedReport方法在ACTIVEX端预览和打印。

方案实现方法的介绍结束。本方案具有的优点为:保持应用的结构形式不变(B/S),将C/S应用结构下已非常成熟的报表方案移植过来,使得在WEB应用中也可实现任意复杂的报表设计和打印,以及对打印效果进行精确控制。

今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显。关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序员需要去关注的事情。当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能。这里,我们不会讲过多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库。希望下面的这些优化技巧对你有用。

1. 为查询缓存优化你的查询

大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。

这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例:

1
2
3
4
5
6
// 查询缓存不开启
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
// 开启查询缓存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");

上面两条SQL语句的差别就是 CURDATE() ,MySQL的查询缓存对这个函数不起作用。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。

2. EXPLAIN 你的 SELECT 查询

使用 EXPLAIN 关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。

EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的……等等,等等。

挑一个你的SELECT语句(推荐挑选那个最复杂的,有多表联接的),把关键字EXPLAIN加到前面。你可以使用phpmyadmin来做这个事。然后,你会看到一张表格。下面的这个示例中,我们忘记加上了group_id索引,并且有表联接:

当我们为 group_id 字段加上索引后:

我们可以看到,前一个结果显示搜索了 7883 行,而后一个只是搜索了两个表的 9 和 16 行。查看rows列可以让我们找到潜在的性能问题。

3. 当只要一行数据时使用 LIMIT 1

当你查询表的有些时候,你已经知道结果只会有一条结果,但因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。

在这种情况下,加上 LIMIT 1 可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。

下面的示例,只是为了找一下是否有“中国”的用户,很明显,后面的会比前面的更有效率。(请注意,第一条中是Select *,第二条是Select 1)

1
2
3
4
5
6
7
8
9
10
11
// 没有效率的:
$r = mysql_query("SELECT * FROM user WHERE country = 'China'");
if (mysql_num_rows($r) > 0) {
    // ...
}
// 有效率的:
$r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1");
if (mysql_num_rows($r) > 0) {
    // ...
}

4. 为搜索字段建索引

索引并不一定就是给主键或是唯一的字段。如果在你的表中,有某个字段你总要会经常用来做搜索,那么,请为其建立索引吧。

从上图你可以看到那个搜索字串 “last_name LIKE ‘a%’”,一个是建了索引,一个是没有索引,性能差了4倍左右。

另外,你应该也需要知道什么样的搜索是不能使用正常的索引的。例如,当你需要在一篇大的文章中搜索一个词时,如: “WHERE post_content LIKE ‘%apple%’”,索引可能是没有意义的。你可能需要使用MySQL全文索引 或是自己做一个索引(比如说:搜索关键词或是Tag什么的)

5. 在Join表的时候使用相当类型的例,并将其索引

如果你的应用程序有很多 JOIN 查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。

而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)

1
2
3
4
5
6
// 在state中查找company
$r = mysql_query("SELECT company_name FROM users
    LEFT JOIN companies ON (users.state = companies.state)
    WHERE users.id = $user_id");
// 两个 state 字段应该是被建过索引的,而且应该是相当的类型,相同的字符集。

6. 千万不要 ORDER BY RAND()

想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你确不了解这样做有多么可怕的性能问题。

如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得不去执行RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit 1也无济于事(因为要排序)

下面的示例是随机挑一条记录

1
2
3
4
5
6
7
8
9
// 千万不要这样做:
$r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1");
// 这要会更好:
$r = mysql_query("SELECT count(*) FROM user");
$d = mysql_fetch_row($r);
$rand = mt_rand(0,$d[0] - 1);
$r = mysql_query("SELECT username FROM user LIMIT $rand, 1");

7. 避免 SELECT *

从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。

所以,你应该养成一个需要什么就取什么的好的习惯。

1
2
3
4
5
6
7
8
9
// 不推荐
$r = mysql_query("SELECT * FROM user WHERE user_id = 1");
$d = mysql_fetch_assoc($r);
echo "Welcome {$d['username']}";
// 推荐
$r = mysql_query("SELECT username FROM user WHERE user_id = 1");
$d = mysql_fetch_assoc($r);
echo "Welcome {$d['username']}";

8. 永远为每张表设置一个ID

我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志。

就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。

而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区……

在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课程ID叫“外键”其共同组成主键。

9. 使用 ENUM 而不是 VARCHAR

ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。

如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。

MySQL也有一个“建议”(见第十条)告诉你怎么去重新组织你的表结构。当你有一个 VARCHAR 字段时,这个建议会告诉你把其改成 ENUM 类型。使用 PROCEDURE ANALYSE() 你可以得到相关的建议。

10. 从 PROCEDURE ANALYSE() 取得建议

PROCEDURE ANALYSE() 会让 MySQL 帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的。

例如,如果你创建了一个 INT 字段作为你的主键,然而并没有太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改成 MEDIUMINT 。或是你使用了一个 VARCHAR 字段,因为数据不多,你可能会得到一个让你把它改成 ENUM 的建议。这些建议,都是可能因为数据不够多,所以决策做得就不够准。

在phpmyadmin里,你可以在查看表时,点击 “Propose table structure” 来查看这些建议

一定要注意,这些只是建议,只有当你的表里的数据越来越多时,这些建议才会变得准确。一定要记住,你才是最终做决定的人。

11. 尽可能的使用 NOT NULL

除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL。这看起来好像有点争议,请往下看。

首先,问问你自己“Empty”和“NULL”有多大的区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在 Oracle 里,NULL 和 Empty 的字符串是一样的!)

不要以为 NULL 不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。 当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。

下面摘自MySQL自己的文档:

“NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”

12. Prepared Statements

Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用 prepared statements 获得很多好处,无论是性能问题还是安全问题。

Prepared Statements 可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式”攻击。当然,你也可以手动地检查你的这些变量,然而,手动的检查容易出问题,而且很经常会被程序员忘了。当我们使用一些framework或是ORM的时候,这样的问题会好一些。

在性能方面,当一个相同的查询被使用多次的时候,这会为你带来可观的性能优势。你可以给这些Prepared Statements定义一些参数,而MySQL只会解析一次。

虽然最新版本的MySQL在传输Prepared Statements是使用二进制形势,所以这会使得网络传输非常有效率。

当然,也有一些情况下,我们需要避免使用Prepared Statements,因为其不支持查询缓存。但据说版本5.1后支持了。

在PHP中要使用prepared statements,你可以查看其使用手册:mysqli 扩展 或是使用数据库抽象层,如: PDO.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建 prepared statement
if ($stmt = $mysqli->prepare("SELECT username FROM user WHERE state=?")) {
    // 绑定参数
    $stmt->bind_param("s", $state);
    // 执行
    $stmt->execute();
    // 绑定结果
    $stmt->bind_result($username);
    // 移动游标
    $stmt->fetch();
    printf("%s is from %s\n", $username, $state);
    $stmt->close();
}

13. 无缓冲的查询

正常的情况下,当你在当你在你的脚本中执行一个SQL语句的时候,你的程序会停在那里直到没这个SQL语句返回,然后你的程序再往下继续执行。你可以使用无缓冲查询来改变这个行为。

关于这个事情,在PHP的文档中有一个非常不错的说明: mysql_unbuffered_query() 函数:

“mysql_unbuffered_query() sends the SQL query query to MySQL without automatically fetching and buffering the result rows as mysql_query() does. This saves a considerable amount of memory with SQL queries that produce large result sets, and you can start working on the result set immediately after the first row has been retrieved as you don’t have to wait until the complete SQL query has been performed.”

上面那句话翻译过来是说,mysql_unbuffered_query() 发送一个SQL语句到MySQL而并不像mysql_query()一样去自动fethch和缓存结果。这会相当节约很多可观的内存,尤其是那些会产生大量结果的查询语句,并且,你不需要等到所有的结果都返回,只需要第一行数据返回的时候,你就可以开始马上开始工作于查询结果了。

然而,这会有一些限制。因为你要么把所有行都读走,或是你要在进行下一次的查询前调用 mysql_free_result() 清除结果。而且, mysql_num_rows() 或 mysql_data_seek() 将无法使用。所以,是否使用无缓冲的查询你需要仔细考虑。

14. 把IP地址存成 UNSIGNED INT

很多程序员都会创建一个 VARCHAR(15) 字段来存放字符串形式的IP而不是整形的IP。如果你用整形来存放,只需要4个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的WHERE条件:IP between ip1 and ip2。

我们必需要使用UNSIGNED INT,因为 IP地址会使用整个32位的无符号整形。

而你的查询,你可以使用 INET_ATON() 来把一个字符串IP转成一个整形,并使用 INET_NTOA() 把一个整形转成一个字符串IP。在PHP中,也有这样的函数 ip2long() 和 long2ip()

1
$r = "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";

15. 固定长度的表会更快

如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。

固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。

并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。

使用“垂直分割”技术(见下一条),你可以分割你的表成为两个一个是定长的,一个则是不定长的。

16. 垂直分割

“垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。(以前,在银行做过项目,见过一张表有100多个字段,很恐怖)

示例一:在Users表中有一个字段是家庭地址,这个字段是可选字段,相比起,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改写这个字段。那么,为什么不把他放到另外一张表中呢? 这样会让你的表有更好的性能,大家想想是不是,大量的时候,我对于用户表来说,只有用户ID,用户名,口令,用户角色等会被经常使用。小一点的表总是会有好的性能。

示例二: 你有一个叫 “last_login” 的字段,它会在每次用户登录时被更新。但是,每次更新时会导致该表的查询缓存被清空。所以,你可以把这个字段放到另一个表中,这样就不会影响你对用户ID,用户名,用户角色的不停地读取了,因为查询缓存会帮你增加很多性能。

另外,你需要注意的是,这些被分出去的字段所形成的表,你不会经常性地去Join他们,不然的话,这样的性能会比不分割时还要差,而且,会是极数级的下降。

17. 拆分大的 DELETE 或 INSERT 语句

如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。

Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。

如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你泊WEB服务Crash,还可能会让你的整台服务器马上掛了。

所以,如果你有一个大的处理,你定你一定把其拆分,使用 LIMIT 条件是一个好的方法。下面是一个示例:

1
2
3
4
5
6
7
8
9
10
while (1) {
    //每次只做1000条
    mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
    if (mysql_affected_rows() == 0) {
        // 没得可删了,退出!
        break;
    }
    // 每次都要休息一会儿
    usleep(50000);
}

18. 越小的列会越快

对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把你的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。

参看 MySQL 的文档 Storage Requirements 查看所有的数据类型。

如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用 INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 会更经济一些。如果你不需要记录时间,使用 DATE 要比 DATETIME 好得多。

当然,你也需要留够足够的扩展空间,不然,你日后来干这个事,你会死的很难看,参看Slashdot的例子(2009年11月06日),一个简单的ALTER TABLE语句花了3个多小时,因为里面有一千六百万条数据。

19. 选择正确的存储引擎

在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。酷壳以前文章《MySQL: InnoDB 还是 MyISAM?》讨论和这个事情。

MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。

InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。

下面是MySQL的手册

20. 使用一个对象关系映射器(Object Relational Mapper)

使用 ORM (Object Relational Mapper),你能够获得可靠的性能增涨。一个ORM可以做的所有事情,也能被手动的编写出来。但是,这需要一个高级专家。

ORM 的最重要的是“Lazy Loading”,也就是说,只有在需要的去取值的时候才会去真正的去做。但你也需要小心这种机制的副作用,因为这很有可能会因为要去创建很多很多小的查询反而会降低性能。

ORM 还可以把你的SQL语句打包成一个事务,这会比单独执行他们快得多得多。

目前,个人最喜欢的PHP的ORM是:Doctrine

21. 小心“永久链接”

“永久链接”的目的是用来减少重新创建MySQL链接的次数。当一个链接被创建了,它会永远处在连接的状态,就算是数据库操作已经结束了。而且,自从我们的Apache开始重用它的子进程后——也就是说,下一次的HTTP请求会重用Apache的子进程,并重用相同的 MySQL 链接。

在理论上来说,这听起来非常的不错。但是从个人经验(也是大多数人的)上来说,这个功能制造出来的麻烦事更多。因为,你只有有限的链接数,内存问题,文件句柄数,等等。

而且,Apache 运行在极端并行的环境中,会创建很多很多的了进程。这就是为什么这种“永久链接”的机制工作地不好的原因。在你决定要使用“永久链接”之前,你需要好好地考虑一下你的整个系统的架构。

我有一个为AnyCPU编译的c#单元测试项目。我们的构建服务器是一个64位机器,并且安装了64位SQL Express实例。

测试项目使用与以下类似的代码来识别.MDF文件的路径:

    private string GetExpressPath()
    {
        RegistryKey sqlServerKey = Registry.LocalMachine.OpenSubKey( @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" );
        string sqlExpressKeyName = (string) sqlServerKey.GetValue( "SQLEXPRESS" );
        RegistryKey sqlInstanceSetupKey = sqlServerKey.OpenSubKey( sqlExpressKeyName + @"\Setup" );
        return sqlInstanceSetupKey.GetValue( "SQLDataRoot" ).ToString();
    }

此代码在我们的32位工作站上工作正常,并且在构建服务器上工作正常,直到我最近启用了与NCover的代码覆盖率分析。由于NCover使用32位COM组件,测试运行器(Gallio)以32位进程运行。

检查注册表,没有”Instance Names”键

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server

有没有办法在32位模式下运行的应用程序访问Wow6432Node之外的注册表?

最佳解决方案

在创建/打开注册表项时,必须使用KEY_WOW64_64KEY参数。但是,AFAIK对于注册表类是不可能的,但只能直接使用API​​。

This可能有助于您开始使用。

次佳解决方案

仍然使用.NET Framework 4.x在64位Windows下仍然支持注册表访问。以下代码通过Windows 7 64位进行测试。要访问64位注册表,可以使用:

string value64 = string.Empty; 
RegistryKey localKey = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry64); 
localKey = localKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); 
if (localKey != null) 
{ 
    value64 = localKey.GetValue("RegisteredOrganization").ToString(); 
} 
Console.WriteLine(String.Format("RegisteredOrganization [value64]: {0}",value64));

如果要访问32位注册表,请使用:

string value32 = string.Empty; 
RegistryKey localKey32 = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry32); 
localKey32 = localKey32.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); 
if (localKey32 != null) 
{ 
    value32 = localKey32.GetValue("RegisteredOrganization").ToString(); 
} 
Console.WriteLine(String.Format("RegisteredOrganization [value32]: {0}",value32));

不要混淆,两个版本都使用Microsoft.Win32.RegistryHive.LocalMachine作为第一个参数,您可以区分第二个参数(RegistryView.Registry64RegistryView.Registry32)是否使用64位或32位。

注意

  • 在64位Windows上,HKEY_LOCAL_MACHINE\Software\Wow6432Node包含在64位系统上运行的32位应用程序使用的值。只有真正的64位应用程序才能将它们的值直接存储在HKEY_LOCAL_MACHINE\Software中。子树Wow6432Node对于32位应用程序是完全透明的,32位应用程序依然看到HKEY_LOCAL_MACHINE\Software(它是一种重定向)。在旧版本的Windows以及32位Windows 7(和Vista 32位)中,子树Wow6432Node显然不存在。
  • 由于Windows 7(64位)中的错误,32位源代码版本始终返回”Microsoft”,无论您注册了哪个组织,而64位源代码版本返回正确的组织。

回到您提供的示例,使用以下方式访问64位分支:

RegistryKey localKey = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry64); 
RegistryKey sqlServerKey = localKey.OpenSubKey(
    @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL");
string sqlExpressKeyName = (string) sqlServerKey.GetValue("SQLEXPRESS");

提示:您可以使用Linqpad来测试Windows 7下的所有示例。它不需要安装。

在win7系统中很正常. 字体设置成display了, 我现在觉得好像不是字体的问题, 因为在xp中整个界面都和win7中有差别, 请帮忙看看问题出在哪里呢?

这是在win7中的截图, 都很正常的

这是xp中的截图, 请看左边的章节位置是一个listBox,显示很正常, 但是其他这些控件就不正常, 加入书签 按钮往左偏了一点,  上面那块黑色很毛糙的地方是一个rectangle , 右边 新宋体 那里是一个comboBox,这个控件边缘还多了一个浅蓝色的边框…. 显示文字的地方是一个richTextBox, 这些文字也是很不清楚, 都像波浪一样的 , 请帮忙看一下吧, 到底是什么地方出了问题

我又测了一下, 是在一部分xp系统上有问题, 有的很正常, 请问有没有可以解决的方法呢, 谢谢了…


Min Zhu

Min Zhu于 

你好,

根据你的描述,非常大的可能是显卡对WPF的兼容性的问题。建议你禁用这些机器上的硬件加速功能,观察一下问题是否仍然存在。如果问题得到修复了,那么就基本可以确认是显卡的问题,建议你先升级对应的显卡驱动程序到最新的版本。如果最新的显卡驱动程序仍然存在这样的问题,那就只能在这些机器上禁用硬件加速功能或者更新硬件了。

在mysql中查询数据能不区分大小写吗,下面小编给大家通过两种方案解决MySql查询不区分大小写,有需要的朋友可以借鉴下

当我们输入不管大小写都能查询到数据,例如:输入 aaa 或者aaA ,AAA都能查询同样的结果,说明查询条件对大小写不敏感。

解决方案一:

于是怀疑Mysql的问题。做个实验:直接使用客户端用sql查询数据库。 发现的确是大小不敏感 。

通过查询资料发现需要设置collate(校对) 。 collate规则

*_bin: 表示的是binary case sensitive collation,也就是说是区分大小写的
*_cs: case sensitive collation,区分大小写
*_ci: case insensitive collation,不区分大小写

解决方法。

1.可以将查询条件用binary()括起来。  比如:

select * from TableA where binary columnA ='aaa';

2. 可以修改该字段的collation 为 binary

比如:

ALTER TABLE TABLENAME MODIFY COLUMN COLUMNNAME VARCHAR(50) BINARY CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL;

解决方案二:

mysql查询默认是不区分大小写的 如:

select * from some_table where str=‘abc';
select * from some_table where str='ABC';

得到的结果是一样的,如果我们需要进行区分的话可以按照如下方法来做:

第一种方法:
要让mysql查询区分大小写,可以:

select * from some_table where binary str='abc'
select * from some_table where binary str='ABC'

第二方法:

在建表时时候加以标识

create table some_table(
   str char(20) binary 
)

原理:

对于CHAR、VARCHAR和TEXT类型,BINARY属性可以为列分配该列字符集的 校对规则。BINARY属性是指定列字符集的二元 校对规则的简写。排序和比较基于数值字符值。因此也就自然区分了大小写。