日前笔者进一步完善了邮政储蓄银行前端报表的电子存储及分析系统的项目开发,项目涉及跨平台数据分析及Visual C++ 2005的多种技术运用,感触颇多,现将此项目中的关键技术汇结成章供大家参考。
一、建立邮储报表
目前,各邮储机构的日常运作及日终数据汇总,会计事务等都离不开各类结构复杂的邮储报表;以往的大部分报表的处理皆以纸质方式人工存档,数据的审核及查询也都由人工完成,随着时间的推移,历史报表的存储和数据的整理、查询成为严重困扰各大邮储机构的核心问题;随着计算机技术的普及应用,各邮储机构的业务系统也纷纷向着全面的电子化方向进军,而业务数据也日趋集中化,但对日终数据的汇结和会计事务的处理仍离不开大量的纸质报表;因此建立标准统一的电子报表存储及分析系统成为迫切解决的关键问题。
笔者供职的邮政行业,储蓄银行业务的发展势头迅猛,本省已完成邮储统一版本软件建设工程并将前置数据上收至省中心,基于的系统平台是Linux 5.0,开发软件采用南天公司的AutoBranch 2.0;各地市局采用省一级应用软件前置集中的方式安装,由于报表存储在后台集中服务器上,网点批量申请打印报表,每个网点每天打印十几种报表,平均打印量在20张以上,对打印针头及机器磨损严重。我们根据目前的业务管理规定,结合实际情况,通过建设集中的报表管理及分析系统,将前台统版系统、事后监督系统中全部报表文件以电子方式存档,定期刻录光盘保存在媒体库中,并提供良好的操作界面方便检索报表及对报表进行结构分析,并为经营决策提供标准量化参考。
目前介于邮储统版软件的特点,每日报表文件所占用的磁盘容量较大且省中心定期清理中心主机上的数据,各地市局迫切需要建立自己的电子报表档案系统以方便历史数据的查询与提取分析,此项目的实施使邮政储蓄的报表管理由传统的手工方式变为规范的标准电子库管理模式,不仅可以大大节约生产成本,提高人员工作效率,同时可为经营提供科学准确的分析数据。根据我局的实际运用做效益评估如下: 采用电子文档方式存储报表,前后台对于无须手工交接的报表可以只申请不打印,大大节约耗材成本。大大降低前台存折打印机的故障率,提高机器的使用寿命。其中存折打印机使用期限每延长一年,将减少设备折旧费10万元。节约了前后台操作人员的报表打印时间,前台营业人员无须等待报表打印而耽误正常的业务办理,提高了人员的工作效率。电子光盘存档报表文件避免了以往用纸质方式保存报表的诸多不便之处,且数据保存时间长,易于存放不易丢失,也便于无用数据的销毁处理,每年可节约档案库面积3立方。对于交易日志信息等历史数据的检索采用计算机模糊查询的方式既快速便捷又准确,改变了以往人工查询纸质报表历史数据烦琐的工作模式,并为经营提供快速准确的参考数据。运行后维护成本极低。可用软件自动化方式对报表中的数据进行提取,并加以分析统计可为经营提供快速准确的参考数据。
二、存储格式转换
目前的邮储系统大多采用SCO Unix或Linux系统作为操作平台,相应的报表都是基于FILE STREAM格式,各邮储机构的前端操作人员都是通过业务软件的报表预览组件对各类报表进行查看及打印;建立邮储报表电子存储及分析系统的使用对象是后台管理人员,必须基于图文信息较强的Windows平台。因此,首要解决的问题是如何跨平台对电子报表进行存取并对其格式进行相应的转换。
通过仔细分析比较,笔者发现Windows平台下有一款内置的Web Browser组件,使用它可以很方便地对Unix平台下的FILE STREAM格式的文件进行直接提取等各类操作,此组件可进行灵活的扩展开发完成用户所需的各项功能,这也是微软Windows系统的Iexplor浏览器的核心I/O库组件,可以方便地实现跨平台的可视化数据分析.跨平台的转换思想主体是:使用Winsock方法通过Visual C++ 2005内置的ftp函数将Unix平台下的对应报表文件提取到本地指定的目录下,再使用Web Browser组件进行相应的存取操作。
1. 邮储统版软件前置报表文件格式
根据目前邮储统版软件的特点,各网点的机构节点信息存储在APP_PATH/print/目录下,各机构节点对应的报表文件存储在APP_PATH/print/机构号(如:340701001)目录下。根据各网点每日必须打印的报表,取出报表数据做出如下的格式分析:
以我局储蓄中心机构为例,机构代码为340701001.
交易日志登记簿文件名为:
tran_ej_3407010013408213011201.dat(为刚下载的纯数据文件)
tran_ej_3407010013408213011201.text.old(为打印预览后的报表文件格式)
通打储蓄分户账余额报告表文件名为:
rpt_acc_bal_0013407010011201.dat(为刚下载的纯数据文件)
rpt_acc_bal_0013407010011201.text.old(为打印预览后的报表文件格式)
经过分析比较发现前端软件生成的报表文件名称可按统一的规律格式抽象化提取,目前可分为如下两类:
文件名前缀+机构代码+四位日期+文件名后缀
文件名前缀+机构代码+柜员号+01+四位日期+文件名后缀
文件名前缀可抽象为各报表文件的名称头信息,如交易日志登记簿文件名前缀为tran_ej_,通打储蓄分户账余额报告表文件名前缀为rpt_acc_bal_001, 开销户登记簿文件名前缀为op_cls_001等等;机构代码为各网点的机构节点代码;四位日期格式为[月日]格式,如12月1日为1201;文件名后缀为.dat或.text.old.对于.dat格式的文件是刚下载的纯数据格式,而.text.old则为邮储前端软件从.dat格式转换而成的可从打印机输出的报表文件格式.
基于以上的分析,建立报表名称信息表,数据库表设计如图1所示。
图1 报表名称信息表
2. 基础数据库表设计
基于以上的分析,结合目前地市级邮储的机构状况,采用四级机构模式设计,机构表设计如图2所示。
图2绿卡机构信息表
这样的设计可以方便统计出各级机构的运营数据, 结合邮储前端软件报表的格式规律,部分报表的文件名称与各网点的柜员信息有关,因此需建立各邮储网点的柜员信息表如图3所示。
图3 邮储网点柜员信息表
考虑到本系统是跨平台传输数据,因此建立服务器信息表,设计如图4所示。
图4 服务器信息表
目前我省邮储软件已实现省一级的前置数据集中,我局系统上收后的服务器数据作如图5所示形式的存储。
图5 服务器存储形式
此存储数据是为了以函数编码方便读取前置服务器数据。
本系统采用每晚营业结束后,以控制台的钩子模块定时从前置服务器读取前一日的报表文件,并提取关键数据存入数据库中供经营分析使用.后台管理人员第二日上班时查看报表传输日志文件以防由于网络或服务器传输故障导致数据提取不完整,可通过手工提取操作完成数据传输工作。
为了方便传输日志文件的读取,建立日志存储的数据表,如图6所示。
图6 日志存储表结构
为了定时传输数据,设计定时数据库表,如图7所示。
图7 定时表结构
根据经营分析所需数据的实际情况,建立相关的数据库表,用于存储从前端报表的.dat格式文件中提取的关键数据.设计如图8和图9所示:
8 绿卡网点窗口流水记录
图9 代发工资明细记录
另外还有诸多经营分析相关的数据库表,暂不一一列出。
三、软件设计及核心编码
1. 报表文件存储目录设计
本软件的实施将使邮政储蓄的报表管理由传统的手工方式变为规范的标准电子库管理模式,因此系统设计的基础是建立基础的数据存放目录即报表文件的存储目录,采用Visual C++ 2005 开发工具,以DCOM技术在系统安装完成的第一步建立报表存储目录.
根据目前邮储统版软件报表目录的特点, 为方便文件存储, 在本系统安装文件目录下生成相应的机构名称目录用于存储从前置机上取下的报表数据. 目录结构如下:
本系统安装目录\log\机构名称\八位日期(YYYYMMDD) 用于存储每日的报表传输日志
本系统安装目录\report\机构名称\八位日期 用于存储各网点的报表文件
本系统安装目录\backup\服务器名称 用于存储各集中服务器的提取数据备份文件
目录建设的关键函数采用Win32系统函数库中的CreateDirectory(),此函数的操作如同直接在硬盘中建立指定的系统目录。实现的核心代码如下:
//建立邮储报表数据目录
char bufferpath[100];
GetModuleFileName(AfxGetInstanceHandle(),bufferpath,100);
CString strpath=bufferpath;
strpath.MakeLower();
{
CString stringpath=strpath.Left(strpath.Find("\\netcontrol.exe"));
stringpath+="\\report";
CreateDirectory(stringpath,NULL);
CString str_ti="正在创建";
str_ti+=stringpath;
str_ti+="目录";
m_ti.SetWindowText(str_ti);
}
完成以上设计后进入系统的核心架构设计,下面取部分模块分析。
- 网点报表处理
自动或手动下传单日或多日批量邮储前台报表,根据日期、网点名称或报表种类检索邮储前台报表,模糊查询条件不明确的邮储前台报表及日志,批量打印邮储前台报表,快速提取用户信息资料,方便用户历史交易查询。
以邮储报表单日手动传输模块为例, 编码设计思想如下:首先根据各项已设置的基础数据表信息结合报表文件格式信息表生成本日即将传输的报表文件名称及存放目录, 再根据生成好的单日报表目录使用Win32的FTP SOCKET系统函数直接从前置机中传输各网点的报表文件到本地报表服务器的指定目录下,在文件传输的过程中。根据需要提取纯数据文件中的指定数据导入本地数据库表中。流程如图10所示:
图10 网点报表流程
在生成单日报表文件传输表时,采用VC++中的ADO DCOM访问技术,可快速读取数据库信息并生成表格。核心代码如下:
::CoInitialize(NULL);
_ConnectionPtr MyDb;
_RecordsetPtr MySetjg;
_RecordsetPtr MySetbb;
MyDb.CreateInstance(__uuidof(Connection));
MySetjg.CreateInstance(__uuidof(Recordset));
MySetbb.CreateInstance(__uuidof(Recordset));
MyDb->Open("DSN=NETCONTROL;UID=admin;PWD=admin","","flyingtjf",-1);
MySetbb->Open(sqlsumbb.AllocSysString(),MyDb.GetInterfacePtr(),adOpenKeyset,adLockPessimistic,adCmdText);
……
for(int i=0;i<numjg;i++)
{
str_jgmc=(char*)(_bstr_t)(MySetjg->Fields->GetItem(_variant_t("机构名称"))->Value);
……
//生成报表存放目录
CString cmd_bbml=stringpath;
cmd_bbml+=str_jgmc;
cmd_bbml+="\\";
cmd_bbml+=str_bbrq;
CreateDirectory(cmd_bbml,NULL);
{
MySetbb->MoveFirst();
for(int j=0;j<numbb;j++)
{
str_bbmc=(char*)(_bstr_t)(MySetbb->Fields->GetItem(_variant_t("报表名称"))->Value);
……
MyDb->Execute(sqladdscbb.AllocSysString(),NULL,adCmdText|adExecuteNoRecords );
MySetbb->MoveNext();
}
}
MySetjg->MoveNext();
m_scprogress.SetPos(i+1);
CString title;
title.Format("%d%%",(i+1)*100/numjg);
//动态设置进度条标题
m_scprogress.SetBarCaption(title);
}
m_scprogress.SetBarCaption("100%");
……
m_ti.SetWindowText("报表文件名和存放目录生成成功!");
报表文件名称及目录生成完成后,开始从前置机读取报表文件并提取相应数据,此模块关键使用Win32的FTP SOCKET函数读取前置机的报表文件并传输到本地服务器中,在文件传输过程中使用Win32的文件操作函数提取报表文件的指定数据存入本地数据库中。核心代码如下:
CString str_ti="正在传输";
str_ti+=str_jgmc;
str_ti+=str_scrq;
str_ti+=str_bbmc;
str_ti+="报表";
m_ti.SetWindowText(str_ti);
CInternetSession* ses=new CInternetSession(AfxGetAppName(),1,PRE_CONFIG_INTERNET_Access);;
CFtpConnection* pFTP=ses->GetFtpConnection(str_ipdz,str_user,str_password);
if(pFTP!=NULL)
{
CFtpFileFind finder(pFTP);
pFTP->GetFile(str_yljwj,str_mbljwj);
}
……
m_ti.SetWindowText("报表传输及计算记录成功!");
生成网点定期、活期、定活两便、总合计户数及余额、现金收支状况、空白凭证领用状况等统计数据;可按时间段,网点名称等自定义条件查询或清理余额、现金收支及空白凭证等统计数据;可根据需要设置每个网点的现金收支限额,详细记录各类关键报表的明细数据入库。
在完成报表传输的同时,可提取部分数据并导入本地数据库中供经营分析使用。以提取网点的每日余额为例,核心代码如下:
FILE* fp;
if(fp=fopen(str_mbljwj,"r"))
{
CString ti="正在记录";
ti+=str_jgmc;
ti+=str_scrq;
ti+="余额";
m_ti.SetWindowText(ti);
//数据提取
CString str_dqhjhs,str_dqhjye,str_hqhjhs,str_hqhjye,str_dhlbhs,str_dhlbye;
CString str_hjzhs,str_hjzye;
char ch_linestring[500];
while(fgets(ch_linestring,500,fp))
{
CString str_linestring=ch_linestring;
if(str_linestring.Find("定期合计")>=0)
{
CString strfirst=str_linestring.Mid(str_linestring.Find("定期合计"));
CString str1=strfirst.Mid(strfirst.Find(_T('|'))+1);
CString str2=str1.Mid(str1.Find(_T('|'))+1);
str_dqhjhs=str1.Left(str1.Find(_T('|')));
str_dqhjye=str2.Left(str2.Find(_T('|')));
}
……
//导入数据库
……
MyDb->Execute(sql_calyeprev.AllocSysString(),NULL,adCmdText|adExecuteNoRecords );
MyDb->Execute(sql_calye.AllocSysString(),NULL,adCmdText|adExecuteNoRecords );
}
fclose(fp);
|