电力系统介绍

国家电力系统需解决问题

一:(国电设备管理)
 项目介绍:
一、技术设施维护:包括设备购置计划管理、设备管理、设备校准检修管理。
1、设备购置计划:主要用来管理电力监测中心计划购置的设备(未购置),包括CRUD等功能;对已经购置的设备需要将状态从“未购置”变为“已购置”;并实现推迟购置计划,实现在时间上的顺延;实现批量导入功能,将大批量的数据以excel的形式导入到数据库;并可按照条件将数据导出(excel格式),导出的字段可通过系统灵活的进行配置。
2、设备管理:设备信息管理主要是对监测中心的设备进行统一的维护和管理,并可将设备按照条件导出excel报表。
3、设备校准和检修:对监测中心正在使用的设备定期的进行校准和检修,定期备份校准和检修的结果,并将校准和检修的结果录入系统。并实现上传文件、导出等功能。
二、技术资料管理:管理监测中心正在使用设备的相关技术资料,技术资料以附件的形式存在。
三、站点运行管理:包括站点的基本信息管理、站点运行情况管理、站点的维护情况管理。
1、站点基本信息管理:主要管理电力公司各个站点的基本信息情况。用站点的运行情况反映各站点下设备的运行情况,对站点的数据信息可进行批量导入和批量导出。
2、站点运行情况管理:控制管理站点的运行情况,主要包括站点的故障类型、故障持续时长、故障处理状态、故障处理结果等,通过查询条件反映站点在各个时间段的运行情况,对站点运行情况和故障类型做图表分析。
3、站点维护情况管理:包括对站点维护计划和维护情况两部分,维护计划用于方便提示用户定期对站点做维护,使得站点能正常运行,疏而不漏;维护情况则为出现故障的站点录入故障信息,以及出现问题后如何排除故障的处理信息等。
四、监测中心建筑物信息管理:管理监测中心所有建筑物。包括房屋建筑,道路,围墙等建筑物信息和各个建筑物的维修信息。
五、系统管理:该功能针对系统管理员开放或对检测中心的有关领导开放,包括用户、角色、权限的管理和分配;维护项目中使用的元数据(即数据字典);还包括系统运行监控功能,所谓运行监控是对站点、设备的运行情况进行维护和检查,出现问题后要及时报警,并将实时检查的数据放置系统首页;还包括系统的日志和审计功能,记录系统各个环节的使用情况,保证系统的安全运行。
六、审批流转:在审批流程管理模块中实现对“设备购置计划”、“设备费用报销”等审核流程的部署和定制,根据流程定义的规则实现了对公司的重要事件、文件等信息的审批流转;其中审核文件模板、待办审核文件以附件的形式在流程中传递,由报审人下载模板,按公司模板要求填写文件,并报审领导;在流程审批过程中,领导对上传的文件下载后进行审核。

1:说说你最近做的这个项目的背景?
《国家电力监测中心设备资源管理系统》是国家电力管理中心根据业务需求发展需要,建立的以设备信息管理为核心,其他管理为辅助的业务数据管理和查询系统。所有数据均以WEB的形式直接录入系统,数据录入及汇总后,为各级用户提供各种统计数据信息,辅助本部门相应决策,提高业务工作效率。
国家电网设备资源管理系统,一旦开发完成电力公司各个所属单位以及分公司均可以使用,系统会针对不同的单位和站点完成部署,每个单位和站点维护设备由自己去配置使用,但是总数据由统一数据库维护,实现数据共享和查询。
2:你的表结构是如何设计的?为什么要这么设计?
为了项目开发方便、高效,增强表之间的关联,数据库采用主外键的主从表关系设计,操作数据库采用hibernate进行数据库表和字段的映射
一:仪器设备管理:
设备购置计划中的设备与设备信息是一对一关系,电力设备管理中的所有设备均有购置计划中获取,购置计划中的设备需要领导审批,审批通过对设备进行购买,购买后将设备放置到设备信息表中维护,购置计划可以使用批量添加设备功能,将大批量的设备导入到设备表的数据库中,方便用户的操作。
电力仪器设备表与设备校准/检修表是1对多的关系,根据需求,一个设备要定期进行校准和检修,如果在监控中发现设备问题,并要将出现问题的原因进行分析、总结,同时将校准和检修的记录添加到设备校准/检修表中,在这里,因为校准记录和检修记录都包括时间、批注、校准人\检修人等字段,为了增强表中字段的重用性,所以经项目组研究后将2个表(校准表和检修表)合并成1个表,采用一个标识用来记录是校准信息还是检修信息,这样的好处在于使用1个表,维护起来方便。
二:资料图书管理
图书资料管理表,根据需求,所有电力设备的使用说明书以及电力设备运行维护操作的帮助说明书等都要放到图书资料管理中,由管理员上传,其他人负责下载查看(指定查询条件,根据所属单位和图纸类别)。附件表的结构如图
表名(中文) 表名(英文) 字段前缀
图书资料附件表 Elec_FileUpload Elec_
序号 字段名 类型 长度 NULL 说明
1 SeqID int no 主键ID
2 ProjID varchar 50 yes 带有附件的工程ID(所属单位)
3 BelongTo varchar 50 yes 所属模块1-0,1-1,1-2,2-0(图纸类别)
4 FileName varchar 50 yes 文件名
5 FileURL varchar 1000 yes 文件路径
6 ProgressTime varchar 20 上传时间
7 Comment varchar 500 备注
8 IsDelete varchar 10 是否删除
9 CreateEmpID varchar 50 创建人
10 CreateDate datetime 创建时间
其中projID字段是供所属单位字段查询使用,belongTo字段供图纸类别字段查询使用
但是您是不是觉得这样命名很别扭,这是因为我们这里采用1个表来维护所有的文件上传对应的业务附件。
例如,我们项目中会多次用到文件上传,如校准记录、检修记录、站点运行情况、设备运行情况都是以附件的形式传递和查看,为了减少附件表的重复定义,增强表的重用性,传统做法是一个业务表都会对应附件表,这样附件表会很多,所以我们设计使用同一张表完成所有业务的附件上传,即都放置到一个“图书资料附件表”中。我们看一下数据库的设计
表名(中文) 表名(英文) 字段前缀
图书资料附件表 Elec_FileUpload Elec_
序号 字段名 类型 长度 NULL 说明
1 SeqID int no 主键ID
2 ProjID varchar 50 yes 带有附件的工程ID(所属单位)
3 BelongTo varchar 50 yes 所属模块1-0,1-1,1-2,2-0(图纸类别)
4 FileName varchar 50 yes 文件名
5 FileURL varchar 1000 yes 文件路径
6 ProgressTime varchar 20 上传时间
7 Comment varchar 500 备注
8 IsDelete varchar 10 是否删除
9 CreateEmpID varchar 50 创建人
10 CreateDate datetime 创建时间
其中projID字段表示带有附件业务表的主键ID,belongTo字段表示所属模块,即该附件应该存放的模块编号,该编号由开发人员定义,但是不能重复。例如:校准记录编号为“1-1”、检修记录编号为“1-2”、站点运行情况编号为“1-3”、设备运行情况编号为“1-4”,这样查询附件是就可以通过这两个字段,查询每个模块表中对应的主键ID的业务,即这个业务表单对应的所有附件。
三:站点管理
所有电力仪器设备的运行和维护统一要放置到每个单位的站点下,那么站点的运行和监控就成为客户比较关心的话题。
站点基本信息表,包括维护站点的代号、名称、地点、厂家、通讯方式、使用日期等信息。
站点在维护过程中需要先制定站点的维护计划,即提前申请维护站点计划,方便对某一地区进行通知,站点维护计划和站点信息组成多对一的关系,即一个站点会存在多个维护计划,方便客户针对站点添加数据,并按照计划日期对站点进行考察和维护。
按照计划维护站点过程中,将站点的运行和维护情况录入数据库表中存储,开发设计站点运行情况表,其中站点基本信息表和站点运行情况表是1对多的关系,站点运行情况表中包括上报年月、故障发生时间、故障描述,同时也包括故障处理时间、处理方法和手段、故障原因等字段,这样设计方便与对站点进行故障的分析和统计,方便对站点进行解决方案的收集和汇总,为站点的运行维护故障提供解决方案。同时也通过时间对站点的故障时长进行汇总,电力公司领导通过汇总的故障时长计算该站点的运行情况是否达标,是否符合公司内部期望的要求。
四:检测台建筑管理
检测台建筑物表,用来维护电力公司所有的建筑物信息。包括管理监测中心所有建筑物。包括房屋建筑,道路,围墙等建筑物信息和各个建筑物的维修信息。字段包括:创始时间、大修时间、使用时间、扩建面积。合理维护电力设备建筑物信息,达到设备、站点、建筑物合理优化。
五:系统管理
用户信息表
表名(中文) 表名(英文) 字段前缀
用户登录信息表 Elec_User Elec_
序号 字段名 类型 长度 NULL 说明
1 UserID varchar 50 no 主键ID
2 JctID varchar 50 yes 所属单位code(对应数据字典)
3 UserName varchar 50 yes 用户姓名
4 LogonName varchar 50 yes 登录名
5 LogonPwd varchar 50 yes 密码
6 SexID varchar 10 yes 性别
7 Birthday datetime yes 出生日期
8 Adress varchar 100 yes 联系地址
9 ContactTel varchar 50 yes 联系电话
10 Email varchar 50 yes 电子邮箱
11 Mobile varchar 50 yes 手机
12 IsDuty varchar 10 yes 是否在职
13 OnDutyDate datetime yes 入职时间
14 OffDutyDate datetime yes 离职时间
15 Comment varchar 500 yes 备注
16 IsDelete varchar 10 yes 是否删除,0表示正常
17 CreatEmpID varchar 50 yes 创建人ID
18 CreateDate datetime yes 创建时间
19 LastEmpID varchar 50 yes 修改人ID
20 LastDate datetime 修改时间

角色信息表
表名(中文) 表名(英文) 字段前缀
角色信息表 Elec_Role Elec_
序号 字段名 类型 长度 NULL 说明
1 roleID varchar 50 no 主键ID
2 roleName varchar 50 yes 角色名称
权限信息
表名(中文) 表名(英文) 字段前缀
权限信息表 Elec_Role Elec_
序号 字段名 类型 长度 NULL 说明
1 mid varchar 50 no 权限code(aa,ab,ac等)
2 pid varchar 50 yes 父级权限code (0,aa等)
3 name varchar 50 yes 权限名称
4 url varchar 500 yes 权限访问路径url(ztree插件中的属性)
5 icon varchar 100 yes 权限显示图表(ztree插件中的属性)
6 target varchar 50 yes 权限url访问目标区域(ztree插件中的属性)
7 isMenu boolean yes 权限是否是菜单
8 isParent boolean yes 权限是否父级权限(ztree插件中的属性)
用户角色关联表
表名(中文) 表名(英文) 字段前缀
用户角色关联表(多对多中间表)即联合主键 Elec_User_Role Elec_
序号 字段名 类型 长度 NULL 说明
1 userID varchar 50 no 用户ID
2 roleID varchar 50 yes 角色ID
角色权限关联表
表名(中文) 表名(英文) 字段前缀
角色权限关联表(多对多中间表)即联合主键 Elec_Role_Popedom Elec_
序号 字段名 类型 长度 NULL 说明
1 roleID varchar 50 yes 角色ID
2 mid varchar 50 yes 权限code(aa,ab,ac等)
3 pid varchar 50 yes 父级权限code (0,aa等)

首页运行监控信息表:将当天录入的站点和设备的运行情况录入后在系统登录首页中进行显示
表名(中文) 表名(英文) 字段前缀
运行监控信息表 Elec_commonmsg Elec_
序号 字段名 类型 长度 NULL 说明
1 UserID varchar 50 no 用户ID
2 StationRun varchar 1000 yes 站点运行情况
3 DevRun varchar 1000 yes 设备运行情况
4 CreateEmpCode varchar 50 yes 创建人
5 CreateDate datetime yes 创建日期
数据字典表:贯穿整个项目的数据核心
表名(中文) 表名(英文) 字段前缀
数据字典表 Elec_SystemDDL Elec_
序号 字段名 类型 长度 NULL 说明
1 seqID Int 50 no 主键ID
2 Keyword varchar 20 yes 查询关键字
3 DdlCode Int 100 yes 数据字典的code值
4 DdlName varchar 50 yes 数据字典的value值
存放数据结构为:

六:报表管理(设置字段的动态导出)
完成对数据库各个业务表字段的动态设置,方便页面动态显示每个字段,方便做excel文件字段动态设置导出
在页面中使用:

其中未导出字段列表和导出字段列表加起来是所有的用户表字段。

数据库设计:完成对字段的动态设置:
表名(中文) 表名(英文) 字段前缀
导出字段设置表 Elec_ExportFields Elec_
序号 字段名 类型 长度 NULL 说明
1 BelongTo varchar 10 no 所属模块(如1-0,1-1,2-1等)
2 ExpNameList varchar 5000 yes 导出名称列表(中文)用“#”分开
3 ExpFieldName varchar 5000 yes 导出字段名称(字段名)用“#”分开
4 NoExpNameList varchar 5000 yes 未导出名称列表(中文)用“#”分开
5 NoExpFieldName datetime yes 未导出字段名称(字段名)用“#”分开

【保存】后,数据库存放结果:

其中:
expNameList表示中文名称,即显示的文件的标题名称
登录名#用户姓名#性别#联系电话#职位#入职时间
expFieldName表示导出字段的英文数据库表对应的列名称
logonName#userName#sexID#contactTel#postID#onDutyDate
我们可以按照该数据库的设置,动态导出excel报表数据结果:

3:这个项目为用户提供了哪些服务?包括哪些模块
【技术设施维护管理,图书资料管理,站点运行管理】
(1) 运行监控模块:填写站点运行情况、设备运行情况
使用文本编辑器ckeditor+ckfinder,实现对运行情况的数据编辑,支持图片视频的上传和下载,在系统首页可以查看其运行情况数据。
在首页中显示当天的运行监控结果。
(2) 图书资料管理:从系统中搜索图书资料
使用lucene进行全文检索,搜索索引库数据,实现大量数据的搜索查询,提高查询的性能和效率,并实现对检索结果的数据截取(即摘要)和文字高亮。
使用struts2的文件上传拦截器完成多文件的上传,上传支持多种文件类型
使用struts2的返回类型完成文件下载
(3) 设备校准记录,设备检修记录,站点运行监控报告模块
【1】使用POI完成对excel报表的导出
【2】下载导入模板,添加数据,使用JXL完成对excel报表的导入
【3】在导入、导出功能中实现了excel报表对大批量数据的分页功能!并实现对文件数据的动态导出,对导出文件的显示效果进行了优化。
【4】在导入、导出过程中,每当查询数据字典进行数据项编号和值转换的过程中,使用了hibernate的二级缓存(ecache缓存),大大提高了检索策略,尤其在报表导入、导出、加载数据项的时候,对数据录入准确性的校验、比对,在性能上优化了数据的检索速度和效率。
【5】在导入、导出过程中,由于数据量较大,而且同时还需要对数据进行校验,所有为了给客户提供导入、导出的感官效果,使用ajax在页面上开启另一个线程,这个线程用来计算导入、导出的执行条数的百分比情况,将信息友好的显示在页面上,实现一个百分比效果的进度条。

JXL和POI的区别:
1:JXL的api更加灵活,api更容易被开发人员理解,操作时,可以使用sheet对象的getCell(i, j);获取某行某列的值,将值放置到集合对象中,方便完成数据的批量导入
2:但是JXL技术只支持.xls的格式,不支持office2007的格式,即xlsx,所以我们提供给客户的模板都要求是.xls的格式,要求客户按照模板填写和导入,并添加一定限制,解决该问题
(4) 设备校准记录,设备检修记录,站点运行监控报告模块
使用Jfreechart技术对设备的校准和检修情况做分析和统计,饼图、柱状图、线状图综合分析每个设备的周、月、年的运行情况(即一个月出现故障几次、报修几次、维修周期多长时间),并由技术人员统一根据数据做故障分析。
而且在站点基本信息模块中,使用FusionChartFree技术实现flash效果的数据统计,统计每个站点的运行情况,并通过比较得出每个站点的供电情况,站点的持续供电时间,以及故障持续时间等,统计站点的运行情况和维护情况,并得到客户的好评。
(5) 数据字典模块
数据字典将所有业务表中用作统计功能的字段,都从数据字典中调用,存放
时存放数据项的编号,而显示时显示数据项的值,保证了数据库表字段存储的数据安全。根据不同的数据类型存放不同数据项的值。
使用数据字典的过程中,除了本系统部署在各个站点调用之外,还使用webservice的远程技术,北京国家电网总公司作为服务器端使用axis远程发布电力系统功能元数据,即数据字典的数据,并提供wsdl的使用说明书,供各个分公司调用数据,达到所有元数据的统一。例如各个分公司的设备监控平台,元数据统计指标都依赖于我们的系统,就需要调用我们元数据用作数据汇总,如监控指标、故障类型等数据。
(6) 设备购置计划,设备费用报销,站点维护计划模块,
在以上业务审批流转模块中,使用jbpm4.4(Activiti)完成对工作流的制定和控制,实现审批流程的灵活定制,模板方便上传和下载,对流程进行定制,发布,在流程图中实现顺序执行、分签、会签、指定transition分配任务等操作,并在各个环节中,给申请人和审核人提供了流程实例运行信息的动态监控功能,帮助审核人查看当前流程图的位置,前端使用highslide js(ajax)用于图片显示,实现动画效果。
(7) 分页服务
根据项目经理要求,自己开发并实现了一个js框架(核心操作使用ajax),在页面上可以实现异步请求,不用使整个页面刷新,就可以得到分页的效果,给js已被项目组认可,应用于系统的分页功能、图书管理、站点和设备监控、角色功能、数据字典维护等功能
分页具体效果如下:
2个页面index.jsp和list.jsp
index.jap中定义2个Form表单,Form1表单中设置查询的条件(传递查询条件参数),Form2表单中显示分页查询的结果列表。
list.jsp中存放查询后的分页结果,将查询后的分页结果(即list.jsp)的全部内容放置到form2表单中。
即传递表单Form1中元素的值作为参数到服务器,在服务器端组织查询条件,并进行分页查询,将查询的结果跳转到list.jsp中,并显示对应的结果,最后通过操作js将list.jsp的全部内容显示到index.jsp的Form2中,完成分页。
开发后的js并可以进行扩展:项目中在站点和设备运行情况中,一个页面可使用多个Form表单,在Form1中选择站点和设备的名称,在同一个页面对应Form2、Form3的表单中,显示对应的站点和设备的监控情况。

4:你承担这个项目的那些核心模块?
需求分析,需求设计,编码
设备仪器管理(设备购置,设备运行维护,设备监控,设备校准,设备检修),资料图书管理(资料图书上传,下载,查看),站点运行管理(站点基本信息,站点运行情况,站点维护情况),系统管理(用户、角色、权限、授权、系统权限控制)
审批流转模块管理(设备购置、设备费用报销实现审核流程操作)

这里参加的模块越多,越能说明你在项目中的地位。
5:这些模块的实现思路说一下?
(1)仪器设备管理
在做仪器设备管理的购置计划模块中,将设备购置计划数据填写后,使用JBPM工作流,将业务人员填写好的设备购置计划单按照定义好的流程执行,由公司内部领导审核,审核通过,按照填写的购置时间和购置地点,以及预算金额去执行,在购置计划中,提供【购置】按钮,可以将购置完成的设备,复制一份数据放置到仪器设备管理中,如果不是着急购置的设备,提供【计划顺延】功能,推迟设备购置时间,推迟时间的长短通过参数的设置,将设备购置计划顺延后,到指定时间后再进行提示购买。
在做设备购置计划中,为了方便客户批量添加设备购置计划,添加批量导入功能,将所有的设备购置单填写在excel报表中,然后执行批量导入,将购置计划的清单全部导入到数据库中,这样方便领导做批量审核和批量购置。并对导入的细节层层校验,保证数据录入数据库的绝对准确。
设备管理中提供查询功能,针对每个分公司,每个站点查询对应设备,并将查询的设备以报表的形式导出,针对需求,在【导出】时实现动态设置和显示excel报表上的字段,这样是因为客户的需求是要求针对每个单位对设备类型的要求不同和打印不同的excel报表,这样设置可以使得打印的excel报表字段显示更加灵活,为了追求导出的性能,在导出中使用到数据字典转换的字段全部采用hibernate的二级缓存进行检索,而且优化sql,全部使用一条sql语句完成对所有字段数据的检索和导出,为了实现大批量数据导出,添加导出excel的算法,在excel报表中实现分页,这样解决了报表每个工作表中行,列数据有限的局限性,更好的支持excel报表的导出功能,为了让客户看到导出报表的执行速率,使用ajax完成进度条,在后台进行计算,进度条上显示导出数据的百分比情况。
设备校准检修模块中,针对仪器设备添加校准和检修的记录,针对需求,一个设备可以添加多个校准记录和检修记录,所以先针对查询条件,查询对应的设备信息,在后边添加新增,修改,删除,查看等功能,灵活的对一个设备完成填写校准记录和检修记录。同时由于校准和检修的数据差不多,所以采用一个表完成2个业务的功能操作,使用标识字段判断究竟保存的校准记录还是检修记录;系统提供校准记录和检修记录的文档管理,使得每个设备按照定义好的校准周期和检修周期的参数设置运行,根据每个设备类型和种类,自动设置校准周期和检修周期(例如1周,1个月,1年等),将在周期内生成的校准报告和检修报告可以以附件的形式上传到指定设备中,用于领导的下载和查阅。
针对需求,实现批量添加校准记录和检修记录功能,使用所属单位和设备类型作为条件,选择对应的设备(这里要求该设备的品牌和类型要一致),实现批量添加设备校准记录和检修记录功能,可以填写一个校准记录和检修记录,同时批量添加到多个设备中,实现用户的简化操作。
资料图书管理中,根据需求,这个模块的数据量很大,而且检索频率很高,所有仪器设备和站点维护相关文档全部要放置到该模块进行管理,所以我觉得可以采用附件的形式管理,不要求客户每次录入资料图书信息,只是以一份文档的电子版存在即可,在页面中指定所属单位和图纸的种类,完成文件上传,搜索时为了搜索的准确性,并提高搜索效率,采用lucene技术,检索时使用索引库检索,这样的好处是实现了对搜索数据提取摘要和文字高亮,由于检索的是索引库,索引性能要比检索数据库要快,支持的检索方式也比数据库灵活,但是这里开发时要注意,一定让数据库的数据和索引库要同步,否则不能搜索出我们想要的结果。
审批流转模块中,根据需求,这个业务是将设备的购置计划单或者设备费用报销单填写后,交给公司经理和财务审核,审核通过后按照申请去执行,为了让流程按部就班执行,决定使用JBPM(Activiti)技术,用工作流的方式处理流程的定义、管理和执行。
系统管理中,实现用户管理(即使用该系统的用户),角色管理列出系统中所有的用户和权限,并对每个用户进行授权,权限表采用树型菜单的数据设置,符合jquery的ztree加载格式。将jquery的ztree插件用到的属性设置到权限表中,这样实现了ztree加载json数据的灵活性,实现权限菜单的动态设置和显示,而且方便实现细颗粒的权限控制。并使用过滤器控制粗颗粒度权限控制(精确Session级别),使用struts2的自定义拦截器实现细颗粒度权限控制,将每个访问Action类的方法上定义注解,注解上对应权限表的权限数据给定的惟一访问权限的标识(权限code和父级权限code),我们再访问Action的方法时,如果当前用户具有权限可以访问,如果不具有权限跳转到错误页面,由错误页面提示错误信息,5秒后跳转到系统首页,完成后好提示。
运行监控模块的需求是:用来监控当前系统所属分公司单位下的设备运行情况,以及当前分公司下管理每个站点的运行情况,每15分钟将监控的报告录入系统,这样可以通过选择,针对某个设备或者某个站点去监控其运行情况,如果没有异常会由系统自动录入数据,表示无异常(使用spring的quartz调度器,完成定时任务,查询最后一次录入数据的时间,如果间隔时间大于15分钟,会自动录入一条数据,表示没有出现异常),如果出现异常,需要人工录入数据,并指定出现异常的设备名称和站点,并要用特殊的图片或者符号标出异常,并在系统首页进行显示,并且给各个管理者发送邮件,尽快解决问题。
运行监控模块中为了方便查看当天站点运行情况和设备运行情况,将当天运行情况生成报告,或者以表格的形式输出,再或者以视频的形式查看,使用ckeditor+ckfinder文本编辑器,实现对数据格式的多样性优化,客户看了更加直观。但是考虑文本编辑器的原理是将html标签进行存储,这样数据库表中列存放的数据量很大,所以我建议将数据进行分段存储
原理如下:

操作:

6:项目中哪些功能模块涉及到了大数据量访问(高并发问题)你是怎么解决的?
暂时没有高并发,因为我们的系统部署后在每个分公司的站点都会部署系统的系统,所以每个站点和设备运行情况比较独立,每个分公司的站点负责维护不同数据,所以不会出现数据高并发。
惟一大数据操作就是资料图书管理模块,但是我采用lucene技术,先查询索引库再查询数据库的方式,解决了大批量数据的检索,上传文档也是根据所属单位和图纸类别上传,每个文档的管理也不会出现冲突。

7:你碰到了哪些问题?你是怎么解决的?
1:在做报表导入,导出的时候,如果固定excel字段格式,显得不够灵活,针对不同公司,不同站点导出excel的字段不同,所以需要实现报表字段的动态设置,所以实现【导出设置】、【导入设置】功能。
【导出设置】功能原理分析,及解决方案:
(1)数据库表,定义一个导出设置表,表中5个字段,分别是所属模块、导出字段的中文名,导出字段的英文名,未导出字段的中文名,未导出字段的英文名,存放的数据是excel字段显示的中文名称,和使用hql语句和sql语句的数据字典的英文名称。

(2)需求分析
在页面上实现设置导出的字段列表,保存导出字段列表的内容,动态的导出excel文档

(3)数据库存放的方式

注意:

操作:

【导入设置】功能原理:
分析:导入和未导入字典的设置

保存数据库的结果:(字段显示的是导入中文字段和导入英文字段)

即:按照导入的中文字段生成excel模板
操作步骤如下:

2:在文件导入、导出过程中,由于数据量很大,有些时候还要做必要的校验,所以比较浪费时间,客户等待时间也很长,即使添加了进度条客户也不知道还要执行多久才能结束操作,于是我决定使用ajax开发一个多线程服务,一个线程处理数据,一个线程计算数据执行操作进度,并同时换算成百分比,在页面中显示。百分比效果的进度条让客户在操作报表的时候得心应手,而且可以自行安排时间,减少了不必要的等待。

:3:文件上传,由于我们的系统多处使用附件上传,资料图书管理,设备运行、设备校准报告,设备检修报告和站点运行,站点监控报告均支持附件上传,为了统一维护附件表,我提出将所有附件都放置到同一个表中维护
表结构设计:
表名(中文) 表名(英文) 字段前缀
图书资料附件表/其他模块附件表 Elec_FileUpload Elec_
序号 字段名 类型 长度 NULL 说明
1 SeqID int no 主键ID
2 ProjID varchar 50 yes 带有附件的工程ID(所属单位)
3 BelongTo varchar 50 yes 所属模块1-0,1-1,1-2,2-0(图纸类别)
4 FileName varchar 50 yes 文件名
5 FileURL varchar 1000 yes 文件路径
6 ProgressTime varchar 20 上传时间
7 Comment varchar 500 备注
8 IsDelete varchar 10 是否删除
9 CreateEmpID varchar 50 创建人
10 CreateDate datetime 创建时间

资料图书管理模块上传附件
字段表示
2 ProjID varchar 50 yes 带有附件的工程ID(所属单位)
3 BelongTo varchar 50 yes 所属模块1-0,1-1,1-2,2-0(图纸类别)
其他业务模块上传附件
2 ProjID varchar 50 yes 带有附件的工程ID(所属单位)
3 BelongTo varchar 50 yes 所属模块1-0,1-1,1-2,2-0(图纸类别)
这样维护统一将所有的附件放置到一张表中维护
同时为了将附件按照时间文件夹维护,满足附件在服务器上的规范管理,决定将文件上传完成以下要求:
1:完成文件上传
1:将上传的文件统一放置到upload的文件夹下
2:将每天上传的文件,使用日期格式的文件夹分开,将每个业务的模块放置统一文件夹下
3:上传的文件名要指定唯一,可以使用UUID的方式,也可以使用日期作为文件名
4:封装一个文件上传的方法,该方法可以支持多文件的上传,即支持各种格式文件的上传
5:保存路径path的时候,使用相对路径进行保存,这样便于项目的可移植性

代码封装如下,面试时简单回答如何操作的即可:
public class FileUploadUtils {

//完成文件上传,同时返回上传文件的路径path(相对路径)
/**
 * 完成文件上传
	  1:将上传的文件统一放置到upload的文件夹下
	  2:将每天上传的文件,使用日期格式的文件夹分开,并使得每个业务模块用文件夹的方式分开
	  3:上传的文件名要指定唯一,可以使用UUID的方式,也可以使用日期作为文件名
	  4:封装一个文件上传的方法,该方法可以支持多文件的上传,即支持各种格式文件的上传
	  5:保存路径path的时候,使用相对路径进行保存,这样便于项目的可移植性

 */
public static String fileUploadReturnPath(File upload, String uploadFileName,String model) {
	//获取upload的文件夹
	String basepath = ServletActionContext.getServletContext().getRealPath("/upload");
	//指定日期格式的文件夹(yyyy/MM/dd)
	String datepath = DateUtils.dateToString(new Date());
	//文件后缀
	String perfix = uploadFileName.substring(uploadFileName.lastIndexOf("."));
	//文件名(格式:ADFSDFSDFA@#$@#$@DSDFS12321.doc)
	String filename = UUID.randomUUID().toString()+perfix;
	
	//判断当前日期文件夹是否存在,如果不存在,创建一个日期的文件夹
	String modelPath = basepath+datepath+"/"+model;
	File dateFile = new File(modelPath);
	if(!dateFile.exists()){
		dateFile.mkdirs();
	}
	
	//目标文件
	File destFile = new File(modelPath+"/"+filename);	
	//文件上传
	upload.renameTo(destFile);
	//返回相对路径
	return "/upload"+datepath+filename;
}

}

4:在导入、导出报表的时候,为了做大批量数据的操作,开发实现excel报表的分页功能,解决了excel每个单元表格中存放数据的有限性(65536行和256列),和同事一起写出一个导出和导入的算法,将每个单元格作为一页,解决了导入、导出数据的局限性。
5:为了解决导入,导出数据的性能优化,拼写sql语句,争取将所有数据都使用1条sql语句完成。而且在数据转换的时候,使用缓存(hibernate的二级缓存)处理,从缓存中获取想要的数据,而不是从数据库,这样加快了检索的性能。
6:本来图书资料管理中没有考虑使用lucene进行检索,就是根据所属单位和图纸类别上传对应设备和站点的信息资源,由公司员工下载查看,但是在开发后期我发现这样操作性能有时候很慢,而且搜索到的结果不是客户想要的,于是在网上搜集lucene的资料,决定使用lucene操作索引库的方式去做,结果这样做确实加快了搜索性能,而且搜索的数据更加准确,lucene的分词器和高亮器在这个模块上得到非常好的体现,并得到了用户的好评。
7:根据需求,在设备购置和费用报销过程中,需要每个分公司单位的负责人审核签字,完成审核,由于之前审核流程不是很复杂,只需各自部门经理和站点总负责人签字,所有开始没有考虑使用JBPM(Activiti),使用申请单中提供标识字段完成审核,但是项目开发中期客户决定由财务再进入审核,用来判断设备购置和报销金额是否合理,这样改动起来影响较大,还不排除客户之后会如何改变流程的执行,所有决定采用JBPM(Activiti)技术,客户在页面上方便选择下一个任务的办理人(使用流程变量),让流程根据指定连线给下一个任务的办理人,由办理人审核,如果通过,就按照申请的信息执行,如果不通过回退到申请(或者指定审核不通过,由申请人重新填写单据,发出申请),此时由设备购置科室人员再次提交申请,领导再次审核,直至完成流程。从而实现了流程更加灵活控制,而且还可以应对各种需求变更。

8:做完这个项目你有哪些收货?
1:经历了软件生命周期的每个阶段,立项,需求调用,需求设计,编码,测试,上线,维护,验收等阶段,对每个阶段要做的事情有深刻的经验和总结。
2:学到了如何分析问题,设计问题,解决问题的能力。
3:实践和巩固了很多技术点的开发,如lucene,JBPM(Activiti)工作流,webservice(axis2)服务,struts2,hibernate,spring,javascript,jquery,ajax,poi报表,jxl报表,JFreechart报表,FusionChartsFree(FCF)报表等
4:在设计阶段,根据需求设计数据库表的能力得到了加强,SQL语句的拼装和优化也得到了很多锻炼和提高,业务代码的封装也更加严谨。
5:项目中大量使用js特效,ajax,jquery,在javascript和jquery上得到了巩固和加强
6:为了控制系统安全,项目经理让我研究权限管理控制,挑战很大,我使用拦截器、过滤器、定义注解对应权限的思想设计并实现了权限控制,保证系统的访问安全

总结:项目不大,但是学到和用到的技术知识很多,非常高兴,也认识了很多同事和朋友,也希望我会的技术能贵公司带来帮助

二:项目中具体模块技术点抽取(国电设备管理)
1:SSH框架整合
第一步:创建数据库(格式:UTF-8)
创建表:

第二步:创建项目(格式:UTF-8)
导入jar包(SSH)

第三步:持久层
(1)在cn.itcast.elec.domain中创建ElecText.java
public class ElecText implements java.io.Serializable {

private String textID;		//主键ID
private String textName;  	//测试名称
private Date textDate;		//测试日期
private String textRemark;	//测试备注

public String getTextID() {
	return textID;
}
public void setTextID(String textID) {
	this.textID = textID;
}
public String getTextName() {
	return textName;
}
public void setTextName(String textName) {
	this.textName = textName;
}
public Date getTextDate() {
	return textDate;
}
public void setTextDate(Date textDate) {
	this.textDate = textDate;
}
public String getTextRemark() {
	return textRemark;
}
public void setTextRemark(String textRemark) {
	this.textRemark = textRemark;
}

}
(2)在cn.itcast.elec.domain目录下,创建ElecText.java对应的映射文件(ElecText.hbm.xml)

<?xml version="1.0" encoding="UTF-8"?>

(3)在src下创建hibernate.cfg.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?> com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/itcastElec?useUnicode=true&characterEncoding=utf8 root root
	<!-- 其他的配置 -->
	<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
	<property name="hibernate.hbm2ddl.auto">update</property>
	<property name="hibernate.show_sql">true</property>
	
	<!-- 加载映射文件 -->
	<mapping resource="cn/itcast/elec/domain/ElecText.hbm.xml"/>
</session-factory>
(4)使用junit在test包中进行测试(并且导入log4j的配置文件) public class TestHibernate { /**测试保存*/ @Test public void save(){ Configuration configuration = new Configuration(); configuration.configure();//加载类路径hibernate.cfg.xml和映射文件 SessionFactory sf = configuration.buildSessionFactory(); Session s = sf.openSession(); Transaction tr = s.beginTransaction();
	//测试操作对象的过程,就是操作数据库表
	ElecText elecText = new ElecText();
	elecText.setTextName("测试Hibernate名称");
	elecText.setTextDate(new Date());
	elecText.setTextRemark("测试Hibernate备注");
	s.save(elecText);
	
	tr.commit();
	s.close();
}

}

第四步:DAO层
(1)在cn.itcast.elec.dao中创建2个接口(公用接口和业务接口)
 公用接口:
public interface ICommonDao {
void save(T entity);
}
 业务接口(需要继承公共接口,并且指定泛型T所对应的对象:
public interface IElecTextDao extends ICommonDao {
public static final String SERVICE_NAME = “cn.itcast.elec.dao.impl.ElecTextDaoImpl”;
}
(2)在cn.itcast.elec.dao.impl中创建2个接口的实现类
 公用类(需要继承HibernateDaoSupport,这样可以方便使用HibernateTemplate对象):
public class CommonDaoImpl extends HibernateDaoSupport implements ICommonDao {

/**使用@Resource注入SessionFactory*/
@Resource(name="sessionFactory")
public final void setSessionFactoryDi(SessionFactory sessionFactory) {
	this.setSessionFactory(sessionFactory);
}

/**保存*/
public void save(T entity) {
	this.getHibernateTemplate().save(entity);
}

}
 业务类(需要继承公用类,这样可以使用公用类中的定义的方法)
@Repository(IElecTextDao.SERVICE_NAME)
public class ElecTextDaoImpl extends CommonDaoImpl implements IElecTextDao {

}

(3)在src创建spring的配置文件(beans.xml)

<?xml version="1.0" encoding="UTF-8"?>

<!-- 1:开启对注解的支持,组件的自动扫描,扫描在类上定义的@Controller @Service @Repositiry注解,范围是cn.itcast.elec的包 -->
<context:component-scan base-package="cn.itcast.elec"/>
<!-- 2:? 先不写,大家想想项目中应该添加什么?-->
<!-- 3:创建SessionFactory,这是spring整合hibernate的核心 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
	<!-- 加载类路径下的hibernate.cfg.xml -->
classpath:hibernate.cfg.xml
<!-- 也可以使用spring的配置文件的方式管理事务 
<tx:advice id="aa" transaction-manager="trManager">
	<tx:attributes>
		<tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
		<tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
		<tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
		<tx:method name="*" read-only="true"/>
	</tx:attributes>
</tx:advice>
<aop:config>
	<aop:pointcut expression="execution(* cn.itcast.elec.service..*.*(..))" id="bb"/>
	<aop:advisor advice-ref="aa" pointcut-ref="bb"/>
</aop:config>-->

(4)使用junit完成测试
public class TestDao {

/**测试保存*/
@Test
public void save(){
    //加载类路径下的spring容器
	ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
    //调用接口
	IElecTextDao elecTextDao = (IElecTextDao) ac.getBean(IElecTextDao.SERVICE_NAME);
	//操作对象
	ElecText elecText = new ElecText();
	elecText.setTextName("测试Dao名称");
	elecText.setTextDate(new Date());
	elecText.setTextRemark("测试Dao备注");
	elecTextDao.save(elecText);
}

}
注意:如果数据没有保存,需要设置事务自动提交:在hibernate.cfg.xml中添加:

true

第五步:Service层
(1) 在cn.itcast.elec.service中创建接口:
public interface IElecTextService {
public static final String SERVICE_NAME = “cn.itcast.elec.service.impl.ElecTextServiceImpl”;
void saveElecText(ElecText elecText);
}

(2) 在cn.itcast.elec.service.impl中创建接口的实现类:
@Service(IElecTextService.SERVICE_NAME)
@Transactional(readOnly=true)
public class ElecTextServiceImpl implements IElecTextService {

@Resource(name=IElecTextDao.SERVICE_NAME)
private IElecTextDao elecTextDao;

@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,readOnly=false)
public void saveElecText(ElecText elecText) {
	elecTextDao.save(elecText);
}

}
(3) 使用junit测试
public class TestService {

/**测试保存*/
@Test
public void save(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
	IElecTextService elecTextService = (IElecTextService) ac.getBean(IElecTextService.SERVICE_NAME);
	//操作对象
	ElecText elecText = new ElecText();
	elecText.setTextName("测试Service名称");
	elecText.setTextDate(new Date());
	elecText.setTextRemark("测试Service备注");
	elecTextService.saveElecText(elecText);
}

}
由于spring提供的声明式事务处理,进行事务的控制,在业务层的类和方法上定义@Transactional(),同时去掉hibernate.cfg.xml中的配置,由spring统一控制。去掉的代码是:

true

第六步:控制层(MVC)
(1)在cn.itcast.elec.web.action中创建Action(业务Action)类和BaseAction(公用Action)
 Action类:(注意:这里要设置成多例,即@Scope(value=prototype),因为struts2的Action是多实例,多线程)
@Controller(“elecTextAction”)
@Scope(value=“prototype”)
public class ElecTextAction extends BaseAction{

ElecText elecText = this.getModel();

@Resource(name=IElecTextService.SERVICE_NAME)
private IElecTextService elecTextService;

/**执行保存*/
public String save(){
	//保存
	elecTextService.saveElecText(elecText);
	return "save";
}

}
 BaseAction类(封装模型驱动对象,HttpServletRequest和HttpServletResponse对象):
public class BaseAction extends ActionSupport implements ModelDriven,ServletRequestAware,ServletResponseAware {

protected HttpServletRequest request;
protected HttpServletResponse response;

T entity;

public BaseAction(){
	/**泛型转换成真实类型(范类转换)*/
	Class entityClass = TUtils.getTClass(this.getClass());
	try {
		entity = (T) entityClass.newInstance();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

public T getModel() {
	return entity;
}

public void setServletRequest(HttpServletRequest req) {
	this.request = req;
}

public void setServletResponse(HttpServletResponse res) {
	this.response = res;
}

}

(2)在cn.itcast.elec.util包下创建公用类(泛型转换)。
泛型转换的目的子类传递真实对象类型,在父类中使用泛型转换成真实对象类型。
以后util包下封装的就是公用类。
public class TUtils {

/**泛型转换成真实类型(范类转换)*/
public static Class getTClass(Class entity) {
	ParameterizedType parameterizedType = (ParameterizedType) entity.getGenericSuperclass();
	Class entityClass = (Class) parameterizedType.getActualTypeArguments()[0];
	return entityClass;
}

}

(3)在src下创建struts2的配置文件struts.xml

<?xml version="1.0" encoding="UTF-8"?> /system/textAdd.jsp

(4)在web.xml中添加配置:




struts2
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter


struts2
/*



contextConfigLocation
classpath:beans.xml


org.springframework.web.context.ContextLoaderListener


index.jsp


(5)导入css,script,jsp,images进行测试
使用system/textAdd.jsp进行测试:页面如图

点击【保存】完成。测试保存ElecText对象。
SSH整体架构图:

2:底层代码封装
 Dao类封装
对公用、频繁调用查询数据库表的方法进行抽取,使用T型代表传递的持久化对象,可以实现根据DAO对应的持久化对象,来操作CommonDao中定义的对应持久化对象的方法。
public class CommonDaoImpl extends HibernateDaoSupport implements ICommonDao {
/*泛型转换,获取真实对象实体/
Class entityClass = TUtils.getTClass(this.getClass());

/**使用@Resource注入SessionFactory*/
@Resource(name="sessionFactory")
public final void setSessionFactoryDi(SessionFactory sessionFactory) {
	this.setSessionFactory(sessionFactory);
}

/**保存*/
public void save(T entity) {
	this.getHibernateTemplate().save(entity);
}

/**更新*/
public void update(T entity) {
	this.getHibernateTemplate().update(entity);
}

/**使用主键ID,查询对象*/
public T findObjectByID(Serializable id) {
	return (T) this.getHibernateTemplate().get(entityClass, id);
}

/**使用主键ID,删除对象(删除一个或者多个对象)*/
public void deleteObjectByIDs(Serializable... ids) {
	if(ids!=null && ids.length>0){
		for(Serializable id:ids){
			Object entity = this.findObjectByID(id);
			this.getHibernateTemplate().delete(entity);
		}
	}
}

/**使用封装对象的集合,批量删除对象*/
public void deleteObjectByCollection(List<T> list) {
	this.getHibernateTemplate().deleteAll(list);
}

/**测试指定查询条件,查询结果集(不分页)*/
/**
这里1=1的目的是方便在Service层拼装sql或者hql语句,连接统一使用and
 * SELECT o FROM ElecText o WHERE 1=1             #Dao层填写
		AND o.textName LIKE '%张%'                  #Service拼装
		AND o.textRemark LIKE '%张%'                #Service拼装
		ORDER BY o.textDate ASC,o.textName desc   #Service拼装
 */
public List<T> findCollectionByConditionNoPage(String condition,
		final Object[] params, Map<String, String> orderby) {
	String hql = "SELECT o FROM "+entityClass.getSimpleName()+" o WHERE 1=1";
	String orderByHql = this.initOrderByHql(orderby);
	final String finalHql = hql + condition + orderByHql;
	//执行hql语句
	/**方式一:直接使用HibernateTemplate的find()方法,find方法支持执行hql语句*/

// List list = this.getHibernateTemplate().find(finalHql, params);
/*方式二:获取SessionFactory,在获取Session/
// SessionFactory sf = this.getHibernateTemplate().getSessionFactory();
// Session s = sf.getCurrentSession();
// Query query = s.createQuery(finalHql);
// query.setParameter(0, params[0]);
// query.setParameter(1, params[1]);
// List list = query.list();
/*方式三:使用hibernateTemplate调用回调函数/
List list = this.getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
Query query = session.createQuery(finalHql);
if(params!=null && params.length>0){
for(int i=0;i<params.length;i++){
query.setParameter(i, params[i]);
}
}
return query.list();
}
});
return list;
}

/**组织排序语句,将Map集合转换成String类型*/
private String initOrderByHql(Map<String, String> orderby) {
	StringBuffer buffer = new StringBuffer("");
	if(orderby!=null && orderby.size()>0){
		buffer.append(" ORDER BY ");
		for(Map.Entry<String, String> map:orderby.entrySet()){
			buffer.append(map.getKey()+" "+map.getValue()+",");
		}
		//删除最后一个逗号
		buffer.deleteCharAt(buffer.length()-1);
	}
	return buffer.toString();
}

}

子类调用只需要继承CommonDaoImpl即可,无需在定义相似的方法:
@Repository(IElecTextDao.SERVICE_NAME)
public class ElecTextDaoImpl extends CommonDaoImpl implements IElecTextDao {

}
 Action类封装
由于Action类使用javabean对象(VO对象)用来接收页面传递的参数,在struts2中叫做模型驱动对象,将模型驱动对象放置到BaseAction类中,这样只需要传递真正的类型,对应BaseAction类中的T型就会访问并找到对应的真实VO对象,使得每个业务Action类的调用VO对象只需要定义,无需实现。
BaseAction类
public class BaseAction extends ActionSupport implements ModelDriven,ServletRequestAware,ServletResponseAware {

protected HttpServletRequest request;
protected HttpServletResponse response;

T entity;

public BaseAction(){
	/**泛型转换成真实类型(范类转换)*/
	Class entityClass = TUtils.getTClass(this.getClass());
	try {
		entity = (T) entityClass.newInstance();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

public T getModel() {
	return entity;
}

public void setServletRequest(HttpServletRequest req) {
	this.request = req;
}

public void setServletResponse(HttpServletResponse res) {
	this.response = res;
}

}
业务Action类
@Controller(“elecTextAction”)
@Scope(value=“prototype”)
public class ElecTextAction extends BaseAction{

ElecText elecText = this.getModel();

@Resource(name=IElecTextService.SERVICE_NAME)
private IElecTextService elecTextService;

/**执行保存*/
public String save(){
	//保存
	elecTextService.saveElecText(elecText);
	return "save";
}

}

 Utils类封装

  1. 报表导入、导出操作
  2. Lucene指定索引库位置,存放索引库,查询索引库操作
  3. 分页操作等
    3:数据库“运行监控”设计

使用Ckeditor+ckFinder文本编辑器实现:
如果项目中使用文本编辑器,那么字段无法存放过大数据怎么办?
解决方案:

操作:

4:运行监控进度条与FCK文本编辑器添加
 进度条

实现方式
有2种,
 一种是单纯的进度条
 一种是带有百分比效果的进度条(使用ajax技术):
分析原理:

面试的问到:如何实现:

 文本编辑器
将fckEdit部署到myeclipse下,发布项目,并运行fckEdit的项目。
效果,如图:

注意:如果项目加载大量的js操作,使用eclipse或者myeclipse开发都会在右下角去校验js,此时会非常浪费时间。由于我们这里使用的CkEditor+CKFinder也使用很多的js,所有要去掉校验,否则大家开发的时候都把时间用在了校验上。

解决方案:
在myeclipse中选择window点击属性,配置Validation属性,去掉对应的复选框的

同时:点击项目,点击Builders属性,配置,去掉对应的复选框的

如果以后大家开发extjs项目,或者开发jquery easyUI的项目,要求按照该方法去配置。
这里注意:CKEditor表示文本编辑器,效果:
此时不支持文件上传
要想支持文件上传,可以使用CKFinder整合CKEditor,在这个插件中定义了文件上传的功能。

这里强调,可以改变ckeditor包中config.js的配置,可以改变文本编辑器的显示效果:
例如:
config.skin = ‘office2003’; //编辑器皮肤样式
// 使用基础工具栏
//config.toolbar = “Basic”;
// 使用全能工具栏
//config.toolbar = “Full”;
// 使用自定义工具栏
config.toolbar =
[
[‘Source’, ‘Preview’, ‘-’],
[‘Cut’, ‘Copy’, ‘Paste’, ‘PasteText’, ‘PasteFromWord’, ],
[‘Undo’, ‘Redo’, ‘-’, ‘Find’, ‘Replace’, ‘-’, ‘SelectAll’, ‘RemoveFormat’],
[‘Image’, ‘Flash’, ‘Table’, ‘HorizontalRule’, ‘Smiley’, ‘SpecialChar’,‘PageBreak’],
‘/’,
[‘Bold’, ‘Italic’, ‘Underline’, ‘-’, ‘Subscript’, ‘Superscript’],
[‘NumberedList’, ‘BulletedList’, ‘-’, ‘Outdent’, ‘Indent’, ‘Blockquote’],
[‘JustifyLeft’, ‘JustifyCenter’, ‘JustifyRight’, ‘JustifyBlock’],
[‘Link’, ‘Unlink’, ‘Anchor’],
‘/’,
[‘Format’, ‘Font’, ‘FontSize’],
[‘TextColor’, ‘BGColor’],
[‘Maximize’, ‘ShowBlocks’, ‘-’, ‘About’]
];

用来定义编辑器显示工具栏的效果:

5:数据字典数据库设计

存放的数据:

6:使用webservice(axis2)发布数据字典远程服务并调用
北京国家电网总公司作为服务器端使用webservice的axis远程技术发布电力系统功能元数据,供各个分公司调用,比如各个分公司的设备监控平台,元数据统计指标都依赖于本系统,如监控指标、故障类型等。
需求:

实质上分三步操作:
创建一个服务器端(总部电力系统),和一个客户端(分公司)
第一步:使用服务器端提供的接口,生成.wsdl文件
第二步:使用.wsdl文件,在电力系统中生成服务器端的代码
第三步:使用.wsdl文件
(http://localhost:8080/itcast0306elec/services/IWebSystemDDLService?wsdl)
生成客户端的代码

7:用户管理数据库设计

这里一个用户对应多个用户职称附件,所有用户表和用户职称附件表是一个一对多的关系。

8:性能优化:hibernate二级缓存,提供查询效率
优点:对应查询结果相同的数据,可以减少频繁检索数据库的操作。
分析:
项目中使用数据字典的时候,经常会遇见

  • 使用数据类型和数据项的编号,获取数据项的值
  • 使用数据类型和数据项的值,获取数据项的编号
  • 使用数据类型,加载对应数据类型的下的集合
    解决方案:
    使用hibernate的二级缓存优化。
    二级缓存中存放的数据结构

二级缓存整合项目:
第一步:导入jar包:
需要引入三个jar包
在hibernate下能找到
hibernate-distribution-3.5.6-Final\lib\optional\ehcache\ehcache-1.5.0.jar
在srping下能找到
…\lib\concurrent\backport-util-concurrent.jar
…\lib\jakarta-commons\commons-logging.jar

第二步:在hibernate.cfg.xml中添加配置:

<?xml version="1.0" encoding="UTF-8"?> true org.hibernate.cache.EhCacheProvider true
	<!-- 加载映射文件 -->
	<mapping resource="cn/itcast/elec/domain/ElecSystemDDL.hbm.xml"/>
	<!-- 指定使用二级缓存的类 放在maping下面 -->
  	<!-- 配置类级别的二级缓存 -->
  	<class-cache usage="read-write" class="cn.itcast.elec.domain.ElecSystemDDL"/>
</session-factory>

第三步:在DAO中执行的hql语句的时候,调用setCacheable(true),例如:
public List findCollectionByConditionNoPageWithCache(String condition,
final Object[] params, Map<String, String> orderby) {
String hql = “SELECT o FROM “+entityClass.getSimpleName()+” o WHERE 1=1”;
String orderByHql = this.initOrderByHql(orderby);
final String finalHql = hql + condition + orderByHql;
//执行hql语句
/*方式三/
List list = this.getHibernateTemplate().execute(new HibernateCallback() {

		public Object doInHibernate(Session session)
				throws HibernateException, SQLException {
			Query query = session.createQuery(finalHql);
			if(params!=null && params.length>0){
				for(int i=0;i<params.length;i++){
					query.setParameter(i, params[i]);
				}
			}
			query.setCacheable(true);
			return query.list();
		}
	});
	return list;
}

9:角色管理数据库设计

所以项目页面的设计:使用角色查找权限和用户:

需要的表

10:角色权限控制系统
(1)使用角色控制系统功能键的URL连接
在访问页面之前,判断当前用户具有的角色,如果是系统管理员就传递系统管理员的URL连接到页面上,在页面的按钮或者连接上动态设置URL连接,这样就实现同一个按钮不同操作功能。
(2)使用权限控制页面上的菜单是否显示
 struts2标签
 自定义标签
代码:
(1) 使用struts2标签:
<s:set value="%{#session.globle_popedom}" var=“popedom” scope=“request”></s:set>
<s:if test="#request.popedom.contains(‘ec’)">

</s:if>
(2) 使用自定义标签:
1、在WebRoot/WEB-INF下建立RoleTagLib.tld文件:内容
<?xml version="1.0" encoding="UTF-8"?>

1.0
f
http://openhome.cc/jstl/fake

if
cn.itcast.elec.util.ConditionalTagUtil
scriptless


pattern
String



2、|在cn.itcast.elec.util中创建ConditionalTagUtil
package cn.itcast.elec.util;

	import java.io.IOException;
	
	import javax.servlet.jsp.JspException;
	import javax.servlet.jsp.tagext.SimpleTagSupport;
	
	import org.apache.struts2.ServletActionContext;
	
	public class ConditionalTagUtil extends SimpleTagSupport {
		private String pattern;
		
		/**标签中要处理的内容*/
		@Override
		public void doTag() throws JspException, IOException {
			String popedom = (String) ServletActionContext.getRequest().getSession().getAttribute("globle_popedom");
			// <u:if pattern="aa">
			if(popedom.contains(pattern)){
				this.getJspBody().invoke(null);
			}
		}
		public void setPattern(String pattern) {
			this.pattern = pattern;
		}
	}

3、在jsp页面中使用
<%@taglib uri="/WEB-INF/RoleTagLib.tld" prefix=“u” %>
<u:if pattern=“ec”>

</u:if>

11:jquery插件ztree实现左侧树状菜单
第一步:在left.jsp中,导入jquery的插件js和jquery的ztree插件js,然后再导入样式

Left.jsp中使用
  • ,定义id和class,树型结构显示就是在
    • 组件上显示的

第二步:在treeMenu.js中定义:使用jqery的ajax加载,设置树型结构的属性,同时使用ajax访问服务器,返回树型结构菜单数据,数据格式是json的数据格式
var menu = {
setting: {
isSimpleData: true,
treeNodeKey: “mid”,
treeNodeParentKey: “pid”,
showLine: true,
root: {
isRoot: true,
nodes: []
}
},
loadMenuTree:function(){
$.post(“elecMenuAction_showMenu.do”,{},function(data){
$("#menuTree").zTree(menu.setting, data);
});

}

};

$().ready(function(){
menu.loadMenuTree();
});

第三步:在Action中添加:将返回的list放置到struts2的栈顶对象中,这样使用struts2提供的json插件包,实现对List集合转换成json数组到页面
public String showMenu(){
//获取Session中存放的权限字符串(格式:aa@ab@ac)
String popedom = (String) request.getSession().getAttribute(“globle_popedom”);
//1:查询当前用户所具有的功能权限,使用权限,查询权限表,返回List
List list = elecRoleService.findPopedomListByUser(popedom);
//2:将list放置到栈顶,栈顶的对象转换成json数组的形式
ValueStackUtils.setValueStack(list);
return “showMenu”;
}

第四步:(hql语句嵌套查询),Service类定义:
public List findPopedomListByUser(String popedom) {
//hql语句和sql语句的嵌套查询
String condition = " and o.mid IN(’"+popedom.replace("@", “’,’”)+"’) AND isMenu = ?";
Object [] params = {true};
Map<String, String> orderby = new LinkedHashMap<String, String>();
orderby.put(“o.mid”, “asc”);
List list = elecPopedomDao.findCollectionByConditionNoPage(condition, params, orderby);
return list;
}

第五步:在struts.xml中添加:这样返回的结果就是json数组

12:使用过滤器完成粗颗粒权限控制
分析:
精确到Session的权限控制(判断Session是否存在)
使用过滤器完成粗颗粒的权限控制,如果Session不存在就跳转到首页,如果存在可以通过URL链接访问到对应的操作。
第一步:定义一个过滤器:
public class SystemFilter implements Filter {

/**web容器启动的时候,执行的方法*/
//存放没有Session之前,需要放行的连接
List<String> list = new ArrayList<String>();
public void init(FilterConfig config) throws ServletException {
	list.add("/index.jsp");
	list.add("/image.jsp");
	list.add("/system/elecMenuAction_menuHome.do");
}

/**每次访问URL连接的时候,先执行过滤器的doFilter的方法*/
public void doFilter(ServletRequest req, ServletResponse res,
		FilterChain chain) throws IOException, ServletException {
	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;
	//获取访问的连接地址
	String path = request.getServletPath();
	//在访问首页index.jsp页面之前,先从Cookie中获取name,password的值,并显示在页面上(完成记住我)
	this.forwordIndexPage(path,request);
	//如果访问的路径path包含在放行的List的存放的连接的时候,此时需要放行
	if(list.contains(path)){
		chain.doFilter(request, response);
		return;
	}
	//获取用户登录的Session
	ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");
	//放行
	if(elecUser!=null){
		chain.doFilter(request, response);
		return;
	}
	//重定向到登录页面
	response.sendRedirect(request.getContextPath()+"/index.jsp");
}

/**销毁*/
public void destroy() {

}

/**在访问首页index.jsp页面之前,先从Cookie中获取name,password的值,并显示在页面上(完成记住我)*/
private void forwordIndexPage(String path, HttpServletRequest request) {
	if(path!=null && path.equals("/index.jsp")){
		String name = "";
		String password = "";
		String checked = "";
		Cookie [] cookies = request.getCookies();
		if(cookies!=null && cookies.length>0){
			for(Cookie cookie:cookies){
				if(cookie.getName().equals("name")){
					name = cookie.getValue();
					/**
					 * 如果name出现中文,对中文进行解码
					 */
					try {
						name = URLDecoder.decode(name, "UTF-8");
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					}
					checked = "checked";
				}
				if(cookie.getName().equals("password")){
					password = cookie.getValue();
				}
			}
		}
		request.setAttribute("name", name);
		request.setAttribute("password", password);
		request.setAttribute("checked", checked);
	}
}

}

第二步:在web容器中添加对应的过滤器:

<filter>
	<filter-name>SystemFilter</filter-name>
	<filter-class>cn.itcast.elec.util.SystemFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>SystemFilter</filter-name>
	<url-pattern>*.do</url-pattern>
	<url-pattern>*.jsp</url-pattern>
</filter-mapping>

问题:不够友好,访问链接,最好提示【非法操作,系统将会5秒后跳转到登录页面】
修改过滤器类中的操作:
(1)在过滤器中的init方法中添加2个放行的连接:
list.add("/error.jsp");
list.add("/system/elecMenuAction_logout.do");
(2)在doFilter的方法重定向到error.jsp
将:
//重定向到登录页面
response.sendRedirect(request.getContextPath()+"/index.jsp");
修改成:
//重定向到error.jsp(5秒跳转到登录名页面)
response.sendRedirect(request.getContextPath()+"/error.jsp");
(3)error.jsp的内容:

注意:Session不应该在服务器一直不清空,如果Session过多,会导致Session压力大,系统变慢,于是要求10分钟如果不操作系统,将Session自动清空。

10

粗颗粒的权限控制的面试:
 使用过滤器
 在过滤器中定义放行的连接,因为不是每个操作都会存在Session
 在过滤器中获取登录后存放的Session,如果Session不为空,则放行,即可以操作定义的业务功能,如果Session为空,则跳转到登录页面。

Session失效,处理ajax请求
电力项目使用的是struts+hibernate+spring框架,当session超时时,如果不是ajax请求,使用过滤器很简单就能实现跳到指定的错误页面(error.jsp)。但是ajax请求就会有问题。Session失效的时候,点击到ajax请求就会弹出一些页面源码文件,即重定向error.jsp中的内容。
项目创建个一个过滤器,来判断session超时(粗颗粒的权限控制)。用户登录后会保存用户信息在一个session里。
方案一:对于jquery的ajax和ext调用ajax,可以在过滤器中使用:
String requested =request.getHeader(“X-Requested-With”):
如果requested的值是null,则表示是http请求。
如果requested的值是XMLHttpRequest,则表示的是ajax请求。
方案二:对于传统方式的ajax,例如项目中的pub.js定义的ajax,此时
如果使用String requested = request.getHeader(“X-Requested-With”); 获取的值也是null,则不能判断是否是ajax请求了。但是可以使用传递参数的方式判断是否调用了ajax请求:
例如在dictionaryIndex.jsp的js代码中定义:
Pub.submitActionWithForm(‘Form2’,’${pageContext.request.contextPath }/system/elecSystemDDLAction_edit.do?ajaxflag=1’,‘Form1’);
则可以在过滤器中使用:String ajaxflag = request.getParameter(“ajaxflag”);判断是否调是ajax请求。
如果ajaxflag的值是1表示的时候ajax请求。
如果ajaxflag的值是null,则表示http请求。

操作步骤
第一步修改:
根据以上分析,过滤器中使用如下代码可以判断(红色代码是修改的部分):
/*访问URL连接之前,都会先执行doFilter/
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

	//获取访问的servlet连接
	String path = request.getServletPath();
	//在跳转到index.jsp之前,要从Cookie中初始化用户名和密码
	this.indexPathInitCookie(path,request);
	//此时需要放行
	if(list.contains(path)){
		chain.doFilter(request, response);
		return;
	}
	//判断Session是否存在
	ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");
	if(elecUser!=null){//session存在
		chain.doFilter(request, response);
		return;
	}
	//判断当前请求是否是ajax请求
	//
	String requested = request.getHeader("X-Requested-With"); 
	String ajaxflag = request.getParameter("ajaxflag");
	if(requested!=null && requested.equalsIgnoreCase("XMLHttpRequest") || ajaxflag!=null && ajaxflag.equals("1")){
		//表示使用ajax请求
		PrintWriter out = response.getWriter();
		out.println("404");
		out.close();
	}
	else{
		//跳转到登录页面
		response.sendRedirect(request.getContextPath()+"/error.jsp");
	}
	
}

第二步修改:
在页面中的代码,例如userAdd.jsp页面,可以使用:
function findJctUnit(o){
//货物所属单位的文本内容
var jct = $(o).find(“option:selected”).text();
KaTeX parse error: Expected '}', got 'EOF' at end of input: …location.href="{pageContext.request.contextPath }/error.jsp";//定向到登录页面
}
else{
//先删除单位名称的下拉菜单,但是请选择要留下
$("#jctUnitID option").remove();
if(data!=null && data.length>0){
for(var i=0;i<data.length;i++){
var ddlCode = data[i].ddlCode;
var ddlName = data[i].ddlName;
//添加到单位名称的下拉菜单中
var $option = $("");
$option.attr(“value”,ddlCode);
$option.text(ddlName);
KaTeX parse error: Expected 'EOF', got '#' at position 3: ("#̲jctUnitID").app…option);
}
}
}
});

}

function checkUser(o){
var logonName = $(o).val();
//以登录名作为查询条件,查询该登录名是否在数据库表中存在记录
KaTeX parse error: Expected '}', got 'EOF' at end of input: …location.href="{pageContext.request.contextPath }/error.jsp";//定向到登录页面
}
else{
if(data1){
$("#check").html(“登录名不能为空”);
$(o)[0].focus();
$("#BT_Submit").attr(“disabled”,“none”);
}
else if(data
2){
$("#check").html(“登录名已经存在”);
$(o)[0].focus();
$("#BT_Submit").attr(“disabled”,“none”);
}
else{
$("#check").html(“登录名可以使用”);
$("#BT_Submit").attr(“disabled”,"");
}
}

		   }
		});
	
}

第三步修改:
在pub.js中定义:
Pub.getReadyStateHandler =function getReadyStateHandler(req, eleid,responseXmlHandler) {

return function () {
if (req.readyState == 4) {
if (req.status == 200) {
//404表示session已经过期跳转到错误页面,然后跳转到登录页面
if(req.responseText==404){
parent.location.href="${pageContext.request.contextPath }/error.jsp";//定向到登录页面
}
else{
responseXmlHandler(req.responseText,eleid);
}

  } else {
    
    alert("HTTP error: "+req.status);
    return false;
  }
}

}
}

完成操作
13:使用struts2的拦截器和注解完成细颗粒度权限控制
操作:
1:在util包下创建注解的类AnnotationLimit,用来控制在Action类中的方法,使Action中方法对应惟一的权限标识
例如:在Action的方法上定义:@AnnotationLimit(mid=“an”,pid=“am”)
package cn.itcast.elec.util;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**

  • 自定义注解
    */
    //被这个注解修饰的注解,利用反射,将其他的注解读取出来
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AnnotationLimit {
    String mid(); //子模块模块名称
    String pid(); //父模块操作名称
    }

2:在util包下创建拦截器ErrorAndLimitInterceptor,代码如下:
package cn.itcast.elec.util;

import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.StrutsStatics;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import cn.itcast.elec.domain.ElecRolePopedom;
import cn.itcast.elec.domain.ElecUser;
import cn.itcast.elec.service.IElecRoleService;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

@SuppressWarnings(“serial”)
public class ErrorAndLimitInterceptor extends MethodFilterInterceptor {

public void init() {

}

/**
 * 过滤器过滤url(.do和.jsp)
 * 拦截器拦截url(.do)
 *   actioninvocation.invoke():调用struts2的Action的方法,并返回String类型的对应的返回值
 */
public String doIntercept(ActionInvocation actioninvocation) {
	//把自定义错误信息 放置到request中
	HttpServletRequest request = (HttpServletRequest) actioninvocation
					.getInvocationContext().get(StrutsStatics.HTTP_REQUEST);
	try {
		//获取请求的action对象
		Object action = actioninvocation.getAction();
		//获取请求的方法的名称
		String methodName = actioninvocation.getProxy().getMethod();
		//获取action中的方法的封装类(action中的方法没有参数)
		Method method = action.getClass().getMethod(methodName, null);
		
		String result = null; // Action的返回值   
		//在完成跳转Action之前完成细颗粒权限控制,控制Action的每个方法
		//检查注解,是否可以操作权限的URL
		boolean flag = isCheckLimit(request,method);
		if(flag){
			// 运行被拦截的Action,期间如果发生异常会被catch住   
			result = actioninvocation.invoke();
		}
		else{
			request.setAttribute("errorMsg", "对不起!您没有权限操作此功能!");
			return "errorMsg";
		}
		return result;
	} catch (Exception e) {
		/**  
		 * 处理异常  
		 */
		String errorMsg = "出现错误信息,请查看日志!";
		//通过instanceof判断到底是什么异常类型   
		if (e instanceof RuntimeException) {
			//未知的运行时异常   
			RuntimeException re = (RuntimeException) e;
			//re.printStackTrace();
			errorMsg = re.getMessage().trim();
		}
		/**  
		 * 发送错误消息到页面  
		 */
		request.setAttribute("errorMsg", errorMsg);

		/**  
		 * log4j记录日志  
		 */
		Log log = LogFactory
				.getLog(actioninvocation.getAction().getClass());
		log.error(errorMsg, e);
		return "errorMsg";
	}// ...end of catch   
}

public void destroy() {

}

/**验证细颗粒权限控制*/
public boolean isCheckLimit(HttpServletRequest request, Method method) {
	if(method == null){
		return false;
	}
	//获取当前的登陆用户
	ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");
	if(elecUser == null){
		return false;
	}
	
	//获取当前登陆用户的角色(一个用户可以对应多个角色)
	Hashtable<String, String> ht = (Hashtable)request.getSession().getAttribute("globle_role");
	if(ht == null){
		return false;
	}
	//处理注解,判断方法上是否存在注解(注解的名称为:AnnotationLimit)
	/*
	 * 例如:
	 * 	@AnnotationLimit(mid="aa",pid="0")
        public String home(){
	 */
	boolean isAnnotationPresent = method.isAnnotationPresent(AnnotationLimit.class);
	
	//不存在注解(此时不能操作该方法)
	if(!isAnnotationPresent){
		return false;
	}
	
	//存在注解(调用注解)
	AnnotationLimit limit = method.getAnnotation(AnnotationLimit.class);
	
	//获取注解上的值
	String mid = limit.mid();  //权限子模块名称
	String pid = limit.pid();  //权限父操作名称
	
	/**
	 * 如果登陆用户的角色id+注解上的@AnnotationLimit(mid="aa",pid="0")
	 *   * 在elec_role_popedom表中存在   flag=true,此时可以访问Action的方法;
	 *   * 在elec_role_popedom表中不存在 flag=false,此时不能访问Action的方法;
	 */
	boolean flag = false;
	//拦截器中加载spring容器,从而获取Service类,使用Service类查询对应的用户信息
	WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
	IElecRoleService elecRoleService = (IElecRoleService)wac.getBean(IElecRoleService.SERVICE_NAME);
	//遍历角色ID
	if(ht!=null && ht.size()>0){
		for(Iterator<Entry<String, String>> ite = ht.entrySet().iterator();ite.hasNext();){
			Entry<String, String> entry = ite.next();
			//获取角色ID
			String roleID = entry.getKey();
			flag = elecRoleService.findRolePopedomByID(roleID, mid, pid);
			if(flag){
				break;
			}
		}
	}
	return flag;
}

}

3:在ElecRoleService类下创建新增方法,使用角色ID,权限code,父级权限code作为联合主键查询角色权限表,判断当前用户是否可以访问该操作。
/*使用角色ID,子权限编号,父权限编号,查询角色权限表的所有数据/
public boolean findRolePopedomByID(String roleID,String mid,String pid) {
//组织查询条件
String condition = “”;
List paramsList = new ArrayList();
//角色ID
if(StringUtils.isNotBlank(roleID)){
condition += " and o.roleID = ?";
paramsList.add(roleID);
}
//子权限名称
if(StringUtils.isNotBlank(mid)){
condition += " and o.mid = ?";
paramsList.add(mid);
}
//父权限名称
if(StringUtils.isNotBlank(pid)){
condition += " and o.pid = ?";
paramsList.add(pid);
}
Object [] params = paramsList.toArray();
//查询对应的角色权限信息
List list = elecRolePopedomDao.findCollectionByConditionNoPage(condition, params, null);
boolean flag = false;
if(list!=null && list.size()>0){
flag = true;
}
return flag;
}

4:在struts.xml中定义自定义拦截器:放置在package下








menuHome,title,left,change,loading,logout,alermStation,alermDevice,showMenu







/close.jsp
/errorMsg.jsp



5:在Action的方法上定义:
@AnnotationLimit(mid=“an”,pid=“am”)
public String home(){

}

要求:登录操作ElecMenuAction类中的方法,要求不需要添加到struts2的自定义拦截器中
此时可以在struts.xml中定义:

问题:为什么在struts2的拦截器中都要使用roleID,mid,pid去查询一遍数据库,而为什么不从Session中获取mid的值和注解上定义的mid的值进行比较呢?
回答:此时不安全,如果盗用账户,登录系统(一定有Session),即可以操作每一个执行的方法。但是由于挂失后,数据库存放的数据发生变化,操作每一个功能之前都会先查询权限,这样查询才能保证数据安全。

细颗粒的权限控制的面试:
 使用struts2的拦截器
 定义一个注解(mid和pid)
 每个Action类的方法上添加注解(mid=””,pid=””),表示方法的惟一标识
在struts2的拦截器中,从Session中获取角色ID,获取Action类方法上的注解(mid和pid),使用角色ID,mid和pid查询角色权限表,判断当前用户是否可以操作该方法。
14:系统中的异常处理+日志备份(使用struts2的拦截器)
操作:
在j2ee项目中,系统内部难免会出现一些异常,如果把异常放任不管直接打印到浏览器可能会让用户感觉莫名其妙,也有可能让某些用户找到破解系统的方法。 所以不要在页面上输出错误信息,使用log日志的方式处理异常并记录异常。
就拿struts2+hibernate+spring项目说明:通常一个页面请求到后台以后,首先是到action(也就是所谓mvc的controller),在action层会调用业务逻辑service,servce层会调用持久层dao获取数据。最后执行结果会汇总到action,然后通过action控制转发到指定页面,执行流程如下图所示:

而这三层其实都有可能发生异常,比如dao层可能会有SQLException,service可能会有NullPointException,action可能会有IOException,一但发生异常并且程序员未做处理,那么该层不会再往下执行,而是向调用自己的方法抛出异常,如果dao、service、action层都未处理异常的话,异常信息会抛到服务器,然后服务器会把异常直接打印到页面,结果就会如下图所示:

  其实这种错误对于客户来说毫无意义,因为他们通常是看不懂这是什么意思的。

刚学java的时候,我们处理异常通常两种方法:
① 直接throws,放任不管;
② 写try…catch,在catch块中不作任何操作,或者仅仅printStackTrace()把异常打印到控制台。
第一种方法最后就造就了上图的结果(不符合操作);而第二种方法更不好:页面不报错,但是也不执行用户的请求,简单的说,其实这就是bug。
那么发生异常到底应该怎么办呢?我想在大家对java异常有一定了解以后,会知道:异常应该在action控制转发之前尽量处理,同时记录log日志,然后在页面以友好的错误提示告诉用户出错了,如。

然后我们回到刚才处理异常的地方,如果大家积累了一些项目经验以后会发现使用上面那种处理异常的方式可能还不够灵活:
①因为spring把大多数非运行时异常都转换成运行时异常(RuntimeException)最后导致程序员根本不知道什么地方应该进行try…catch操作
②每个方法都重复写try…catch,而且catch块内的代码都很相似,这明显做了很多重复工作而且还很容易出错。
使用truts2拦截器定义异常拦截器用来解决上述问题,如下图所示:

首先我的action类、service类和dao类如果有必要捕获异常,我都会try…catch,catch块内不记录log,通常是抛出一个新异常,并且注明错误信息,由拦截器来抛出异常信息,并写入log日志文件:

Struts2自定义拦截器的操作:
在struts2的配置文件下添加:

然后在异常拦截器对异常进行处理,看下面的代码:

拦截器的Java代码
package cn.itcast.elec.util;

import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.StrutsStatics;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

@SuppressWarnings(“serial”)
public class ErrorAndLimitInterceptor extends MethodFilterInterceptor {

public void init() {

}

/**
 * 过滤器过滤url(.do和.jsp)
 * 拦截器拦截url(.do)
 *   actioninvocation.invoke():调用struts2的Action的方法,并返回String类型的对应的返回值
 */
public String doIntercept(ActionInvocation actioninvocation) {
	//把自定义错误信息 放置到request中
	HttpServletRequest request = (HttpServletRequest) actioninvocation
					.getInvocationContext().get(StrutsStatics.HTTP_REQUEST);
	try {
		//获取请求的action对象
		Object action = actioninvocation.getAction();
		//获取请求的方法的名称
		String methodName = actioninvocation.getProxy().getMethod();
		//获取action中的方法的封装类(action中的方法没有参数)
		Method method = action.getClass().getMethod(methodName, null);
		// Action的返回值   
		String result = null; 
		
		result = actioninvocation.invoke();
		
		return result;
	} catch (Exception e) {
		/**  
		 * 处理异常  
		 */
		String errorMsg = "出现错误信息,请查看日志!";
		//通过instanceof判断到底是什么异常类型   
		if (e instanceof RuntimeException) {
			//未知的运行时异常   
			RuntimeException re = (RuntimeException) e;
			//re.printStackTrace();
			errorMsg = re.getMessage().trim();
		}
		/**  
		 * 发送错误消息到页面  
		 */
		request.setAttribute("errorMsg", errorMsg);

		/**  
		 * log4j记录日志  
		 */
		Log log = LogFactory
				.getLog(actioninvocation.getAction().getClass());
		log.error(errorMsg, e);
		return "errorMsg";
	}// ...end of catch   
}

public void destroy() {

}

}

最后在errorMsg.JSP页面显示具体的错误消息即可:
以上方式可以拦截后台代码所有的异常,但如果出现数据库连接异常时不
能被捕获的,大家可以使用struts2的全局异常处理机制来处理:

Struts2配置文件代码

上面这是一个很简单的异常拦截器,大家可以使用自定义异常,那样会更灵活一些。
以上异常拦截器可以使用其它很多技术替换:比如spring aop,servlet filter等,根据项目实际情况处理。

辅助:log4j.properties文件的内容:

direct log messages to stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

direct messages to file hibernate.log

#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

set log levels - for more verbose logging change ‘info’ to ‘debug’

log4j.rootLogger=error, stdout

#log4j.logger.org.hibernate=info
#log4j.logger.org.hibernate=debug

log HQL query parser activity

#log4j.logger.org.hibernate.hql.ast.AST=debug

log just the SQL

#log4j.logger.org.hibernate.SQL=debug

log JDBC bind parameters

#log4j.logger.org.hibernate.type=info
#log4j.logger.org.hibernate.type=debug

log schema export/update

#log4j.logger.org.hibernate.tool.hbm2ddl=debug

log HQL parse trees

#log4j.logger.org.hibernate.hql=debug

log cache activity

#log4j.logger.org.hibernate.cache=debug

log transaction activity

#log4j.logger.org.hibernate.transaction=debug

log JDBC resource acquisition

#log4j.logger.org.hibernate.jdbc=debug

enable the following line if you want to track down connection

leakages when using DriverManagerConnectionProvider

#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace

log4j.rootLogger= error, A1, R
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout

Print the date in ISO 8601 format

#log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
#log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %m%n
log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %l “#” %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=/elec.log
log4j.appender.R.MaxFileSize=1000KB

Keep one backup file

log4j.appender.R.MaxBackupIndex=10
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

项目中的异常处理面试:
 使用struts2的拦截器
 在拦截器中的doIntercept()方法定义try… catch异常
 如果Action、Service、Dao没有抛出异常,则在try模块中指定正确操作的页面,例如:
result = actioninvocation.invoke();
return result;
result跳转到正确的页面
 如果Action、Service、Dao抛出异常,则在catch模块中,获取异常,使用log4j存放到指定的日志文件中,通过return “errorMsg”;跳转到错误页面。

15:Activiti工作流解析工作流流程文件(流程部署、定义、流程实例、任务、流程变量)
一:流程定义和部署对象相关
Deployment 部署对象
1、一次部署的多个文件的信息。对于不需要的流程可以删除和修改。
2、对应的表:
act_re_deployment:部署对象表
act_re_procdef:流程定义表
act_ge_bytearray:资源文件表
act_ge_property:主键生成策略表

ProcessDefinition 流程定义
1、解析.bpmn后得到的流程定义规则的信息,工作流系统就是按照流程定义的规则执行的。

相关的表:
#部署对象和流程定义相关的表
SELECT * FROM act_re_deployment #部署对象表

SELECT * FROM act_re_procdef #流程定义表

SELECT * FROM act_ge_bytearray #资源文件表

SELECT * FROM act_ge_property #部署ID主键生成策略表

二:流程实例,执行对象,任务相关
Execution 执行对象
按流程定义的规则执行一次的过程.
对应的表:
act_ru_execution: 正在执行的信息
act_hi_procinst:已经执行完的历史流程实例信息
act_hi_actinst:存放历史所有完成的活动
ProcessInstance 流程实例
特指流程从开始到结束的那个最大的执行分支,一个执行的流程中,流程实例只有1个。

注意
(1)如果是单例流程,执行对象ID就是流程实例ID
(2)如果一个流程有分支和聚合,那么执行对象ID和流程实例ID就不相同(明天参考act_ru_execution表的数据变化)
(3)一个流程中,流程实例只有1个,执行对象可以存在多个。

Task 任务
执行到某任务环节时生成的任务信息。
对应的表:
act_ru_task:正在执行的任务信息
act_hi_taskinst:已经执行完的历史任务信息

相关的数据库表:管理流程执行的

SELECT * FROM act_ru_execution #正在执行的执行对象表

SELECT * FROM act_hi_procinst #历史的流程实例表

SELECT * FROM act_ru_task #正在执行的任务表(只有当前活动时userTask节点的时候,任务表才有数据)

SELECT * FROM act_hi_taskinst #历史任务表,值存放任务节点(只有当前活动时userTask节点的时候,任务表才有数据)

SELECT * FROM act_hi_actinst #历史活动表

三:流程变量
• 1:流程变量
作用1:在流程执行或者任务执行的过程中,用于设置和获取变量,使用流程变量在流程传递的过程中传递业务参数。
作用2:指定连线的条件完成任务,让流程传递指定连线的下一个任务环节
作用3:动态指定任务的办理人
对应的表:
act_ru_variable:正在执行的流程变量表
act_hi_varinst:流程变量历史表

四:历史查询
由于数据库中保存着历史信息以及正在运行的流程实例信息,在实际项目中对已完成任务的查看频率远不及对代办和可接任务的查看,所以在activiti采用分开管理,把正在运行的交给RuntimeService、TaskService管理,而历史数据交给HistoryService来管理。
这样做的好处在于,加快流程执行的速度,因为正在执行的流程的表中数据不会很大。
16:使用ajax分页
原理分析:
userIndex.jsp

userList.jsp

原理:
pub.js(dom对象的ajax封装)
1:在userIndex.jsp中存在2个Form表单(Form1和Form2)
2:传递表单Form1中的元素作为参数传递给服务器的参数,在服务器端进行处理,将处理后的结果(即分页后的结果)显示在userList.jsp中
3:将userList.jsp的内容,放置到userIndex.jsp的Form2中
整合项目步骤:
第一步:导入2个java文件,放置到util包下

表示:

PageBean中的内容:
private int pageNo;//存放当前页
private boolean firstPage;//当前页是否是第一页:是:true
private boolean lastPage; //当前页是否是最后一页:是:true
private int sumPage;//总页数
private int pageSize ;//当前页最多显示几条记录
private int totalResult ;//总记录数

第二步:导入一个js文件,page.js放置到script包下

第三步:修改userIndex.jsp的内容:
(1)添加:

(2)修改【查询】按钮的连接
  

(3)在Form1的表单中添加2个隐藏域
<s:hidden name=“initPage” value=“1”></s:hidden>

<s:hidden name=“pageNO”></s:hidden>
(4)在Form2的表单中,加载pageUI.jsp,这个jsp页面显示分页的相关信息,添加:
<%@include file="/WEB-INF/page/pageUI.jsp" %>
第四步:将userIndex.jsp的Form2表单的内容,单独提取出来,命名为userList.jsp
第五步:在struts.xml中添加:
/WEB-INF/page/system/userList.jsp
第六步:在Action类中添加:
//判断:什么情况调整到userList.jsp,什么情况调整到userIndex.jsp
String initPage = request.getParameter(“initPage”);
if(initPage!=null && initPage.equals(“1”)){
//调整到userList.jsp(执行ajax的分页)
return “list”;
}
第七步:在Service类中添加:
PageInfo pageInfo = new PageInfo(ServletActionContext.getRequest());

  • currentPageNo:表示当前页(从PageNO中获取)
  • pageSize:表示默认该页最多显示多少条记录
  • req:存放request对象
    List list = elecUserDao.findCollectionByConditionWithPage(condition, params, orderby,pageInfo);
    ServletActionContext.getRequest().setAttribute(“page”,pageInfo.getPageBean());
    第八步:在Dao类中添加:
    pageInfo.setTotalResult(query.list().size());
  • totalResult:存放总记录数
  • totalPage:存放总页数
    query.setFirstResult(pageInfo.getBeginResult());//当前页从第几条开始检索,默认是0,0表示第1条
  • currentPageNo:存放当前页;
  • beginResult:表示当前页从第几条开始检索,默认是0;
  • pageSize:表示当前页做多显示的记录数;
    query.setMaxResults(pageInfo.getPageSize());//表示当前页最多显示多少条记录

17:Poi导出、jxl导入
(1)Poi报表导出
【1】POI报表整合项目
第一步:导入jar包:

第二步:导入java文件(使用poi生成excel报表,放置到输出流中),类放置到util包下

第三步:在userIndex.jsp中定义:
(1)添加按钮
  

  

(2)js代码:
//导出excel
function exportExcel(){
var userName = document.getElementById(“userName”).value;
userName = encodeURI(userName,“UTF-8”);
userName = encodeURI(userName,“UTF-8”);
var jctID = document.getElementById(“jctID”).value;
var onDutyDateBegin = document.getElementById(“onDutyDateBegin”).value;
var onDutyDateEnd = document.getElementById(“onDutyDateEnd”).value;
openWindow(’${pageContext.request.contextPath }/system/elecUserAction_exportExcel.do?userName=’+userName+’&jctID=’+jctID+’&onDutyDateBegin=’+onDutyDateBegin+’&onDutyDateEnd=’+onDutyDateEnd,‘700’,‘400’)
}

第四步:在Action类中定义:
/**
* @Name: exportExcel
* @Description: 用户列表的信息动态导出excel
* @Author: 刘洋(作者)
* @Version: V1.00 (版本号)
* @Create Date:
* @Parameters: 无
* @Return: String:null(不使用struts2的方式,完成流的操作)
*/
public String exportExcel() throws Exception{
//构造2个数据集合
//构造excel的标题
ArrayList fieldName = elecUserService.findExcelFiledName();
//构造excel的数据
ArrayList<ArrayList> fieldData = elecUserService.findExcelFieldData(elecUser);

	//使用ExcelFileGenerator完成导出
	ExcelFileGenerator excelFileGenerator = new ExcelFileGenerator(fieldName,fieldData);
	OutputStream os = response.getOutputStream();
	//导出excel建议加上重置输出流,可以不加该代码,但是如果不加必须要保证输出流中不应该在存在其他数据,否则导出会有问题
	response.reset();
	//配置:
	//文件名
	String fileName = "用户报表("+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+").xls";
	fileName = new String(fileName.getBytes("gbk"),"iso-8859-1");
	response.setContentType("application/vnd.ms-excel");
	response.setHeader("Content-disposition", "attachment;filename="+fileName);
	response.setBufferSize(1024);
	
	//导出excel的操作
	excelFileGenerator.expordExcel(os);
	return null;
}

/**  
* @Name: exportExcel
* @Description: 用户列表的信息动态导出excel
* @Author: 刘洋(作者)
* @Version: V1.00 (版本号)
* @Create Date: 
* @Parameters: 无
* @Return: String:success(使用struts2的方式,完成流的操作)
*/
public String exportExcel() throws Exception{
	//构造2个数据集合
	//构造excel的标题
	ArrayList<String> fieldName = elecUserService.findExcelFiledName();
	//构造excel的数据
	ArrayList<ArrayList<String>> fieldData = elecUserService.findExcelFieldData(elecUser);
	
	//使用ExcelFileGenerator完成导出
	ExcelFileGenerator excelFileGenerator = new ExcelFileGenerator(fieldName,fieldData);
	ByteArrayOutputStream os = new ByteArrayOutputStream();
	//导出excel建议加上重置输出流,可以不加该代码,但是如果不加必须要保证输出流中不应该在存在其他数据,否则导出会有问题
	response.reset();
	//配置:
	//文件名
	String fileName = "用户报表("+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+").xls";
	fileName = new String(fileName.getBytes("gbk"),"iso-8859-1");
	request.setAttribute("filename", fileName);
	
	//导出excel的操作
	excelFileGenerator.expordExcel(os);
	
	//将输入流的文件,放置到栈顶的Inputstream中
	byte [] buf = os.toByteArray();
	ByteArrayInputStream in = new ByteArrayInputStream(buf);
	elecUser.setInputStream(in);
	return "success";
}

第五步:在Service类中定义(构造数据集合):
/**
* @Name: findExcelFiledName
* @Description: 构造excel的标题内容
* @Author: 刘洋(作者)
* @Version: V1.00 (版本号)
* @Parameters: 无
* @Return: ArrayList:excel的标题:
* ArrayList fieldName
fieldName.add(“登录名”);
fieldName.add(“用户姓名”);

*/
public ArrayList findExcelFiledName() {
//查询导出设置表,主键是用户管理5-1的设置
ElecExportFields elecExportFields = elecExportFieldsDao.findObjectByID(“5-1”);
//获取导出中文的名称
String zName = elecExportFields.getExpNameList();
//将String类型的字符,按照#号分割成集合
List list = StringToListUtils.stringToList(zName, “#”);
ArrayList filedName = new ArrayList(list);
return filedName;
}

/**  
* @Name: findExcelFieldData
* @Description: 构造excel的数据内容
* @Author: 刘洋(作者)
* @Version: V1.00 (版本号)
* @Create Date: 
* @Parameters: ElecUser:VO对象
* @Return: ArrayList<ArrayList<String>> fieldData:存放的时候所有的数据
			  ArrayList<String> data1:存放的是每一条的数据
			    data1.add("liubei");
			    data1.add("刘备");
			    ...
			  ArrayList<String> data2:存放的是每一条的数据
			    data2.add("zhugeliang");
			    data2.add("诸葛亮");
			    …
			  fieldData.add(data1);
			  fieldData.add(data2);
*/
public ArrayList<ArrayList<String>> findExcelFieldData(ElecUser elecUser) {
	//返回最后的结果集
	ArrayList<ArrayList<String>> fieldData = new ArrayList<ArrayList<String>>();
	
	//查询导出设置表,主键是用户管理5-1的设置
	ElecExportFields elecExportFields = elecExportFieldsDao.findObjectByID("5-1");
	//获取导出中文的名称
	String zName = elecExportFields.getExpNameList();
	//将String类型的字符,按照#号分割成集合
	List<String> zlist = StringToListUtils.stringToList(zName, "#");
	//获取导出英文的名称
	String eName = elecExportFields.getExpFieldName();
	//将英文字段的#号替换成逗号
	String selectCondition = eName.replace("#", ",");
	
	//封装查询条件
	String condition = "";
	List<Object> paramsList = new ArrayList<Object>();
	//姓名
	String userName = elecUser.getUserName();
	//处理乱码
	try {
		userName = URLDecoder.decode(userName, "UTF-8");
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	}
	if(StringUtils.isNotBlank(userName)){
		condition += " and o.userName like ?";
		paramsList.add("%"+userName+"%");
	}
	//所属单位
	if(StringUtils.isNotBlank(elecUser.getJctID())){
		condition += " and o.jctID = ?";
		paramsList.add(elecUser.getJctID());
	}
	//入职开始时间
	if(elecUser.getOnDutyDateBegin()!=null){
		condition += " and o.onDutyDate >= ?";
		paramsList.add(elecUser.getOnDutyDateBegin());
	}
	//入职结束时间
	if(elecUser.getOnDutyDateEnd()!=null){
		condition += " and o.onDutyDate <= ?";
		paramsList.add(elecUser.getOnDutyDateEnd());
	}
	Object [] params = paramsList.toArray();
	//排序:按照入职时间升序
	Map<String, String> orderby = new LinkedHashMap<String, String>();
	orderby.put("o.onDutyDate", "asc");
	//处理查询,list总存放的时候所有的记录
	List list = elecUserDao.findCollectionByConditionNoPageWithSelectCondition(condition, params, orderby,selectCondition);
	//遍历list
	if(list!=null && list.size()>0){
		for(int i=0;i<list.size();i++){
			//统一使用数组的方式存放每一条的数据
			Object [] arrays = null;
			//如果投影查询是多个字段,返回的是Object数组对象
			if(selectCondition.contains(",")){
				arrays = (Object[]) list.get(i);
			}
			//如果投影查询是一个字段,返回的是Object对象
			else{
				arrays = new Object[1];
				arrays[0] = list.get(i);
			}
			//封装成每一条的数据
			ArrayList<String> data = new ArrayList<String>();
			if(arrays!=null && arrays.length>0){
				for(int j=0;j<arrays.length;j++){
					//获取每个字段的值
					Object o = arrays[j];
					//数据字典的转换(由于使用的中文和英文要一一对应)
					if(zlist!=null && zlist.get(j).equals("性别") || zlist.get(j).equals("所属单位") || zlist.get(j).equals("职位") || zlist.get(j).equals("是否在职")){
						data.add(o!=null?elecSystemDDLDao.findDdlNameByKeywordAndDdlCode(zlist.get(j), o.toString()):"");
					}
					else{
						data.add(o!=null?o.toString():"");
					}
				}
			}
			fieldData.add(data);
		}
	}
	return fieldData;
}

第六步:使用struts2方式的导出
(1)配置struts.xml

application/vnd.ms-excel
inputStream
attachment;filename="${#request.filename}.xls"
1024

(2)在模型驱动的对象中添加:
//导出excel文件的流
private InputStream inputStream;

public InputStream getInputStream() {
	return inputStream;
}
public void setInputStream(InputStream inputStream) {
	this.inputStream = inputStream;
}

【2】POI报表的几个核心对象

【3】POI报表(应对面试)
面试的时候,如果问到使用poi报表如何处理数据导出的?

管理员:设置【导出设置】
1:js实现左移右移,配置导出的字段
2:目的就是配置导出的excel字段,包括中文字段,英文数据库的字段
3:中文字段用来显示excel的标题,英文字段用来拼写hql语句(或者sql语句)的查询投影的部分,将查询的数据显示到excel中
业务人员:点击【导出】
1:导出excel报表,支持查询条件
2:根据导出设置的配置,完成对字段的动态设置和导出(亮点)
3:导出excel的时候,支持分页(亮点)
* 分页操作可以支持excel的大批量的数据导出(一个sheet能放置的行是有限的)
4:对导出的性能进行优化,使用hibernate的二级缓存
* 如果能使用一条sql语句,就不多条语句查询结果(联合查询)
5:增强导出excel报表的效果,添加样式

(2)Jxl报表导入
【1】JXL报表整合项目
第一步:导入jar包:

第二步:导入java文件(完成封装),从文件中获取Excel的数据,读取excel的数据,写到集合中,将类放置到util包下。

第三步:导入jsp文件,实现文件上传,使用<s:file>标签

指定导入模板,用户在导入模板上填写数据,从导入模板获取数据并读取数据,导入到数据库中。
第四步:在userIndex.jsp中定义:
  

第五步:在struts.xml中定义:跳转到导入excel的页面(文件上传)
/WEB-INF/page/system/userImport.jsp

第六步:在Action类中定义:
/**
* @Name: importPage
* @Description: 跳转到导入excel报表的页面
* @Author: 刘洋(作者)
* @Version: V1.00 (版本号)
* @Create Date:
* @Parameters: 无
* @Return: String:跳转到system/userImport.jsp
*/
public String importPage(){
return “importPage”;
}

/**  
* @Name: importData
* @Description: 从excel中读取数据,将数据保存到数据库表中
* @Author: 刘洋(作者)
* @Version: V1.00 (版本号)
* @Create Date: 
* @Parameters: 无
* @Return: String:跳转到system/userImport.jsp
*/
public String importData() throws Exception{
	//获取上传的文件File
	File formFile = elecUser.getFile();
	GenerateSqlFromExcel fromExcel = new GenerateSqlFromExcel();
	ArrayList<String[]> data = fromExcel.generateUserSql(formFile);
	//定义一个错误的集合,String用来存放错误的信息
	List<String> errorList = new ArrayList<String>();
	//组织PO对象,完成保存
	List<ElecUser> list = this.convertFromExcelToUser(data,errorList);
	//说明存在了错误信息
	if(errorList!=null && errorList.size()>0){
		//将错误的信息在页面中显示
		request.setAttribute("errorList", errorList);
	}
	//说明不存在错误信息
	else{
		//执行批量的保存
		elecUserService.saveUserList(list);
	}
	return "importPage";
}

/**组织PO对象的集合,校验的方法*/
private List<ElecUser> convertFromExcelToUser(ArrayList<String[]> data,List<String> errorList) {
	//定义返回的用户PO的集合
	List<ElecUser> list = new ArrayList<ElecUser>();
	if(data!=null && data.size()>0){
		for(int i=0;i<data.size();i++){
			//每一行的数据,格式:gj	123	郭靖	男	北京	北京中关村	是	1982-1-10	部门经理
			String arrays [] = data.get(i);
			//组织成对象,满足数据库保存字段的格式要正确
			ElecUser elecUser = new ElecUser();
			//登录名
			if(StringUtils.isNotBlank(arrays[0])){
				String message = elecUserService.checkUserByLogonName(arrays[0]);
				if(message!=null && message.equals("3")){
					elecUser.setLogonName(arrays[0]);
				}
				else{
					errorList.add("第"+(i+2)+"行,第"+(0+1)+"列,登录名在数据库中出现重复!");
				}
			}
			else{
				errorList.add("第"+(i+2)+"行,第"+(0+1)+"列,登录名为空!");
			}
			//密码
			//添加默认密码
			if(StringUtils.isBlank(arrays[1])){
				arrays[1] = "123";
			}
			if(StringUtils.isNotBlank(arrays[1])){
				MD5keyBean md5keyBean = new MD5keyBean();
				String md5LogonPwd = md5keyBean.getkeyBeanofStr(arrays[1]);
				elecUser.setLogonPwd(md5LogonPwd);
			}
			
			//用户姓名
			if(StringUtils.isNotBlank(arrays[2])){
				elecUser.setUserName(arrays[2]);
			}
			
			//性别
			if(StringUtils.isNotBlank(arrays[3])){
				//数据字典转换
				String ddlCode = elecSystemDDLService.findDdlCodeByKeywordAndDdlName("性别", arrays[3]);
				if(StringUtils.isNotBlank(ddlCode)){
					elecUser.setSexID(ddlCode);
				}
				else{
					errorList.add("第"+(i+2)+"行,第"+(3+1)+"列,性别转换出现异常!");
				}
				
			}
			else{
				errorList.add("第"+(i+2)+"行,第"+(3+1)+"列,性别不能为空!");
			}
			//所属单位
			if(StringUtils.isNotBlank(arrays[4])){
				//数据字典转换
				String ddlCode = elecSystemDDLService.findDdlCodeByKeywordAndDdlName("所属单位", arrays[4]);
				if(StringUtils.isNotBlank(ddlCode)){
					elecUser.setJctID(ddlCode);
				}
				else{
					errorList.add("第"+(i+2)+"行,第"+(4+1)+"列,所属单位转换出现异常!");
				}
				
			}
			else{
				errorList.add("第"+(i+2)+"行,第"+(4+1)+"列,所属单位不能为空!");
			}
			
			//联系地址
			if(StringUtils.isNotBlank(arrays[5])){
				elecUser.setAddress(arrays[5]);
			}
			
			//是否在职
			if(StringUtils.isNotBlank(arrays[6])){
				//数据字典转换
				String ddlCode = elecSystemDDLService.findDdlCodeByKeywordAndDdlName("是否在职", arrays[6]);
				if(StringUtils.isNotBlank(ddlCode)){
					elecUser.setIsDuty(ddlCode);
				}
				else{
					errorList.add("第"+(i+2)+"行,第"+(6+1)+"列,是否在职转换出现异常!");
				}
				
			}
			else{
				errorList.add("第"+(i+2)+"行,第"+(6+1)+"列,是否在职不能为空!");
			}
			
			//出生时间
			if(StringUtils.isNotBlank(arrays[7])){
				Date birthday = DateUtils.stringToDate(arrays[7]);
				elecUser.setBirthday(birthday);
			}
			
			//职位
			if(StringUtils.isNotBlank(arrays[8])){
				//数据字典转换
				String ddlCode = elecSystemDDLService.findDdlCodeByKeywordAndDdlName("职位", arrays[8]);
				if(StringUtils.isNotBlank(ddlCode)){
					elecUser.setPostID(ddlCode);
				}
				else{
					errorList.add("第"+(i+2)+"行,第"+(8+1)+"列,职位转换出现异常!");
				}
				
			}
			else{
				errorList.add("第"+(i+2)+"行,第"+(8+1)+"列,职位不能为空!");
			}
			list.add(elecUser);
		}
	}
	return list;
}

第七步:在Service类中定义:
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,readOnly=false)
public void saveUserList(List list) {
elecUserDao.saveObjectList(list);
}
第八步:在Dao类中定义:
/*批量保存对象集合/
public void saveObjectList(List list) {
this.getHibernateTemplate().saveOrUpdateAll(list);
}

【2】JXL报表的几个核心对象

【3】JXL报表(应对面试)
面试的时候,如果问到如何实现excel报表数据导入的?即导入流程:
管理员:填写【导入模板】
1:将模板上传到项目中
业务人员:点击【下载】,下载管理员上传的导入模板
业务人员:针对导入模板,填写数据,点击【导入】,将excel中的数据读取出来,导入到数据库中
1:使用jxl技术,读取excel中的数据,放置到ArrayList集合中
2:读取数据,支持excel报表的分页
3:从excel中读取数据的同时,添加数据校验,校验内容包括(数据字典转换,站点位置坐标,设备编号名称是否存在等)
4:如果校验没有问题,批量导入到数据库中
5:如果校验出现问题,在页面中提示【某行、某列】数据出现什么问题,由客户根据提示进行修改,修改完成之后,再执行导入

【4】扩展:excel字段实现动态导入
分析:导入和未导入字典的设置

保存数据库的结果:(字段显示的是导入中文字段和导入英文字段)

即:按照导入的中文字段生成excel模板
操作步骤如下:

18:JFreechart图表技术、fusioncharts的Flash效果图形报表
(1)Jfreechart图表
项目中使用,能否在指定盘的文件夹下生成图片,在页面中使用标签加载图片
例如:在D盘生成一张图片,在页面上使用
//在D盘生成图片
File file = new File(“D:/chart.png”);
try {
ChartUtilities.saveChartAsPNG(file, chart, 600, 500);
} catch (IOException e) {
e.printStackTrace();
}

整合项目的操作步骤:
第一步:导入jar包

第二步:导入一个jap的文件,userReport.jsp,跳转到页面后传递生成图片的文件名,用来加载图片:

第三步:
在userIndex.jsp中定义:
  
在Action中定义:
public String chartUser(){
List<Object []> list = elecUserService.chartUser(“所属单位”,“jctID”);
//使用Jfreechart生成图片,将图片生成到chart的文件夹下,将图片的名称放置到request的对象中,名称filename
String filename = ChartUtils.createBarChart(list);//返回文件名
request.setAttribute(“filename”, filename);
return “chartUser”;
}
第四步:
在Dao中定义:使用聚合函数进行分组统计。
public List<Object[]> chartUser(String zName, String eName) {
final String sql = “SELECT a.keyword,a.ddlName,COUNT(a.ddlCode) FROM elec_systemddl a " +
" INNER JOIN elec_user b ON a.ddlCode = b.”+eName+" AND a.keyword=’"+zName+"’ " +
" WHERE b.isDuty = ‘1’ " +
" GROUP BY a.keyword,a.ddlName " +
" ORDER BY a.ddlCode ASC";
List<Object[]> list = this.getHibernateTemplate().execute(new HibernateCallback() {

		public Object doInHibernate(Session session)
				throws HibernateException, SQLException {
			SQLQuery query = session.createSQLQuery(sql)
					.addScalar("keyword", Hibernate.STRING)
					.addScalar("ddlName", Hibernate.STRING)
					.addScalar("COUNT(a.ddlCode)", Hibernate.INTEGER);
			//标量查询(如果使用sql语句,如果用连接查询,有可能2张表的字段出现冲突,就要使用投影查询)
			return query.list();
		}
		
	});
	return list;
}

第五步:封装ChartUtils的工具类,生成各种图形
public class ChartUtils {

/**生成柱状图,返回文件名(格式,日期时分秒)*/
public static String createBarChart(List<Object[]> list) {
	//图形的数据集合
	DefaultCategoryDataset dataset = new DefaultCategoryDataset();
	if(list!=null && list.size()>0){
		for(Object[] o:list){
			//a.keyword,a.ddlName,COUNT(a.ddlCode)
			dataset.addValue(Double.parseDouble(o[2].toString()), o[0].toString(), o[1].toString());
		}
	}
	//工厂模式
	JFreeChart chart = ChartFactory.createBarChart3D(
			"用户统计报表(所属单位)",		//图形的主标题 
			"所属单位名称",				//X轴外的标签名称 
			"数量",						//Y轴外的标签名称 
			dataset, 					//图形的数据集合
			PlotOrientation.VERTICAL,	//图表的显示形式(水平/垂直) 
			true,						//是否显示子标题 
			true,						//是否在图形上生成提示工具 
			true						//是否点击生成URL
		);
	//处理主标题的乱码
	chart.getTitle().setFont(new Font("宋体", Font.BOLD, 18));
	//处理子标题的乱码
	chart.getLegend().setItemFont(new Font("宋体", Font.BOLD, 15));
	//获取图表区域对象
	/**
	 * 图表区域对象:
	 *  (1)使用断点:
	 *  (2)使用System.out.println(chart.getPlot())
	 *  (3)使用API文档
	 */
	CategoryPlot categoryPlot = (CategoryPlot) chart.getPlot();
	//获取X轴对象
	CategoryAxis3D categoryAxis3D = (CategoryAxis3D)categoryPlot.getDomainAxis();
	//获取Y轴对象
	NumberAxis3D numberAxis3D = (NumberAxis3D) categoryPlot.getRangeAxis();
	//X轴外的乱码
	categoryAxis3D.setLabelFont(new Font("宋体", Font.BOLD, 15));
	//X轴上的乱码
	categoryAxis3D.setTickLabelFont(new Font("宋体", Font.BOLD, 15));
	//Y轴外的乱码
	numberAxis3D.setLabelFont(new Font("宋体", Font.BOLD, 15));
	//Y轴上的乱码
	numberAxis3D.setTickLabelFont(new Font("宋体", Font.BOLD, 15));
	//设置Y轴上的刻度以1为单位
	numberAxis3D.setAutoTickUnitSelection(false);
	NumberTickUnit unit = new NumberTickUnit(1);
	numberAxis3D.setTickUnit(unit);
	
	//获取绘图区域对象
	BarRenderer3D barRenderer3D = (BarRenderer3D)categoryPlot.getRenderer();
	//让图形变得苗条点
	barRenderer3D.setMaximumBarWidth(0.08);
	
	//让在图形上生成数字
	barRenderer3D.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
	barRenderer3D.setBaseItemLabelsVisible(true);
	barRenderer3D.setBaseItemLabelFont(new Font("宋体", Font.BOLD, 15));
	
	//在项目中的chart的文件夹下生成图片
	//获取chart的文件夹
	String basePath = ServletActionContext.getServletContext().getRealPath("/chart");
	//文件名(日期)
	String filename = DateFormatUtils.format(new Date(), "yyyyMMddHHmmss")+".png";
	File file = new File(basePath+"/"+filename);
	try {
		ChartUtilities.saveChartAsPNG(file, chart, 600, 500);
	} catch (IOException e) {
		e.printStackTrace();
	}
	return filename;
}

}

第六步:在userReport.jsp中加载:

(2)Fusioncharts图表
【1】概述
FusionCharts是InfoSoft Global公司的一个产品,InfoSoft Global 公司是专业的Flash图形方案提供商,他们还有几款其他的,基于Flash技术的产品,都非常的漂亮。FusionCharts Free则是FusionCharts提供的一个免费版本,虽然免费,功能依然强大,图形类型依然丰富。
FusionCharts free 是一个跨平台,跨浏览器的flash图表组件解决方案,能够被 ASP.NET, ASP, PHP, JSP, ColdFusion, Ruby on Rails, 简单 HTML 页面甚至PPT调用。你不需要知道任何关于flash编程的知识,你只需要知道你所用的编程语言就可以了。
FusionCharts free 目前最新版本是v2.1,主要做了以下改动:
• 增加了使用jsp和Ruby on Rails来集成FusionCharts的代码和文档。
• FusionCharts DOM更加容易地把图表加载到你的页面上。
• 修改了.Net的使用代码和文档。
• 增加了新的PHP API ,并修复了一些BUG。
• 修改了FusionCharts.js ,以便可以支持双引号。(那就是说以前不支持?)
• 增加了在FusionCharts使用UTF-8编码的示例。
【2】下载及安装
下载
你可以在下面的地址下载它。
文件不大,共25.8M。将FusionCharts Free(以下简称FCF)解压到任意一个目录后,点击目录下的index.html,就可以打开FCF的文档。
目录结构

现在我们就来看看这个下载包里面都有些什么东西。
1:SWF文件(创建图形主要靠它们了)
所有的SWF文件(共22个)都在FusionChartsFree>Charts文件夹>(FCF)的文件。如果你需要在你的web应用里创建图形,那么就把这些SWF文件都拷到你的应用下面。
2:FusionCharts JavaScript文件
FusionCharts JavaScript文件放在FusionChartsFree>JSClass文件夹。这些文件能够帮你用一种友好的方式把图形嵌入到html页面。
3:示例代码
所有的代码都放在FusionChartsFree>Code文件夹。
4:图形例子
我们创建了一些图形例子,放在FusionChartsFree>Gallery文件夹。你也可以通过文档左边的菜单Sample Charts来访问它。
5:文档
文档就放在FusionChartsFree>Contents文件夹,你可以直接点击FusionChartsFree下面的index.html来访问。
安装
了解了下载包的目录结构以后,我们再来看看如何安装FCF到web应用里。
只需要两个步骤即可完成安装。
1、在你的web应用根目录下新建一个叫fusionCharts的文件夹。(注意:当然并不是说它一定要叫这个名字,也不是一定要在根目录下)
2、把所有的SWF文件(前面是FCF的文件即可)都拷贝到这个fusionCharts里。
3、将FusionChartsFree\JSClass中的FusionCharts.js放置到项目的script包下
【3】整合项目:
第一步:把要用的swf文件放到WebRoot下的某个地方,创建一个fusionCharts的文件夹。
第二步:在后台的java文件中准备数据,例如:

第三步:在jsp中引用js,例如:
Jsp页面:

【4】原理:生成各种图形,主要靠XML格式的数据。

生成图形的XML数据:

所有开发FCF报表,就是组织xml数据。
【5】Xml数据的参数支持的类型:

19:数据库设计

(1)技术设施维护管理
1
表名(中文) 表名(英文) 字段前缀
仪器设备表 Elec_Device Elec_
序号 字段名 类型 长度 NULL 说明
1 DevID varchar 50 no 主键ID
2 DevPlanID varchar 50 yes 外键ID
3 JctID varchar 50 yes 所属单位code(对应数据字典)
4 DevName varchar 50 yes 设备名称
5 DevType varchar 10 yes 设备类型
6 Trademark varchar 50 yes 品牌
7 SpecType varchar 50 yes 规格类型
8 ProduceHome varchar 50 yes 厂家
9 ProduceArea varchar 50 yes 产地
10 Useness varchar 50 yes 用途
11 Quality varchar 10 yes 数量
12 UseUnit varchar 50 yes 使用单位
13 DevExpense numeric 20,2 yes 金额
14 AdjustPeriod varchar 50 yes 校准周期(数字)
15 OverhaulPeriod varchar 50 yes 检修周期(数字)
16 Configure varchar 50 yes 配置
17 DevState varchar 100 yes 设备状态code(对应数据字典)
18 RunDescribe varchar 10 yes 运行情况描述
19 Comment varchar 500 yes 备注
20 UseDate datetime 使用日期
21 Isdelete varchar 10 是否删除,0表示正常
22 CreateEmpID varchar 50 创建人
23 CreateDate datetime 创建时间
24 LastEmpID varchar 50 修改人
25 LastDate datetime 修改时间
26 Qunit varchar 10 数量单位
27 APUnit varchar 10 校准单位
28 OPUnit varchar 10 检修单位
29 APState varchar 10 是否校准(0未校准,1已校准)
30 OPState varchar 10 是否检修(0未检修,1已检修)
2
表名(中文) 表名(英文) 字段前缀
仪器设备购置计划表 Elec_Device_Plan Elec_
序号 字段名 类型 长度 NULL 说明
1 DevPlanID varchar 50 no 购置计划ID,主键ID
2 JctID varchar 50 yes 所属单位code(对应数据字典)
3 DevName varchar 100 yes 计划购置设备名称
4 DevType varchar 10 yes 设备类型code(对应数据字典)
5 Trademark varchar 50 yes 品牌
6 SpecType varchar 50 yes 规格型号
7 ProduceHome varchar 50 yes 厂家
8 ProduceArea varchar 50 yes 产地
9 Useness varchar 50 yes 用途
10 Quality varchar 10 yes 数量
11 UseUnit varchar 50 yes 使用单位
12 DevExpense numeric 20,2 yes 金额
13 PlanDate datetime 计划时间
14 AdjustPeriod Varchar 50 yes 校准周期
15 OverhaulPeriod varchar 50 yes 检修周期
16 Configure varchar 50 yes 配置
17 Comment varchar 500 yes 备注
18 PurchaseState varchar 10 购买状态(0表示计划购买)
(1表示购买,此时在仪器设备表中添加记录)
19 Isdelete varchar 10 是否删除,0表示未删除
20 CreateEmpID varchar 50 创建人ID
21 CreateDate datetime 创建时间
22 LastEmpID varchar 50 修改人ID
23 LastDate datetime 修改时间
24 Qunit varchar 10 数量单位
25 APUnit varchar 10 校准周期单位
26 OPUnit varchar 10 检修周期单位
3
表名(中文) 表名(英文) 字段前缀
仪器设备校准检修表 Elec_Overhaul_Record Elec_
序号 字段名 类型 长度 NULL 说明
1 seqID Int no 仪器校准,主键ID
2 DevID varchar 50 yes 设备ID
3 IsAdjust varchar 10 yes 是否校准,0未校准,1已校准
4 AdjustDate datetime yes 校准日期
5 OverhaulDate datetime yes 检修日期
6 IsHaving varchar 10 yes 是否检修,0未检修,1已检修
7 Record varchar 500 yes 记录描述
8 Comment varchar 500 yes 备注
9 IsDelete varchar 10 yes 是否删除,0表示未删除
10 CreateEmpID varchar 50 yes 创建人
11 CreateDate datetime yes 创建日期
12 LastEmpID varchar 50 yes 修改人
13 LastDate datetime yes 修改日期

(2)技术资料图纸管理
见:附件表
(3)站点设备运行管理
5
表名(中文) 表名(英文) 字段前缀
站点基本信息表 Elec_Station Elec_
序号 字段名 类型 长度 NULL 说明
1 StationID varchar 50 no 站点ID,主键ID
2 JctID varchar 50 yes 所属单位code(对应数据字典)
3 StationCode varchar 50 yes 站点代号
4 StationName varchar 50 yes 站点名称
5 JCFrequency varchar 100 yes 安装地点
6 ProduceHome varchar 50 yes 生产厂家
7 ContactType varchar 50 yes 通讯方式
8 UseStartDate Datetime yes 使用日期
9 Comment Varchar 500 yes 备注
10 IsDelete Varchar 10 yes 是否删除,0表示未删除
11 CreateEmpID Varchar 50 yes 创建人
12 CreateDate Datetime yes 创建日期
13 LastEmpID varchar 50 yes 修改人
14 LastDate Datetime yes 修改日期
15 StationType Varchar 50 yes 站点类别code(对应数据字典)
16 AttributionGround varchar 50 yes 归属地
6

表名(中文) 表名(英文) 字段前缀
站点运行情况表 Elec_StationBug Elec_
序号 字段名 类型 长度 NULL 说明
1 BugID Int no 运行情况,主键ID
2 StationID varchar 50 yes 站点ID
3 JctID varchar 50 yes 所属单位code(对应数据字典)
4 SbMonth varchar 50 yes 上报年月
5 BugType varchar 10 yes 故障类型code(对应数据字典)
6 OccurDate varchar 50 yes 发生时间
7 LauncherName varchar 50 yes 站点代号
8 BugDescribe varchar 500 yes 故障描述
9 ResolveDate Varchar 50 yes 处理时间
10 ResolveMethod Varchar 500 yes 处理方法
11 BugReason Varchar 500 yes 故障原因
12 Comment varchar 500 yes 备注,恢复情况
13 IsDelete varchar 10 yes 是否删除,0表示未删除
14 CreateEmpID varchar 50 yes 创建人
15 CreateDate Datetime yes 创建日期
16 LastEmpID varchar 50 yes 修改人
17 LastDate Datetime yes 修改日期
7
表名(中文) 表名(英文) 字段前缀
站点维护计划情况表 Elec_MaintainPlan Elec_
序号 字段名 类型 长度 NULL 说明
1 PlanID varchar 50 no 站点维护情况,主键ID
2 StationID varchar 50 no 站点ID,主键ID
3 JctID varchar 50 yes 所属单位code(对应数据字典)
4 OccurDate datetime yes 计划时间
5 MainContent varchar 500 yes 内容
6 Comment varchar 500 yes 备注

(4)检测台建筑管理
8
表名(中文) 表名(英文) 字段前缀
建筑物表 Elec_JctBuild Elec_
序号 字段名 类型 长度 NULL 说明
1 BuildID varchar 50 no 主键ID
2 JctID varchar 50 no 所属单位code(对应数据字典)
3 BuildName varchar 50 yes 建筑名称
4 BuildType varchar 10 yes 建筑类型code(对应数据字典)
5 BuildStartDate datetime yes 创始时间
6 DxDate datetime yes 大修时间
7 UseDate datetime yes 使用时间
8 BuildLayer varchar 50 yes 建筑层数
9 BuildArea varchar 50 yes 建筑面积
10 ExtendBuildDate datetime yes 扩建时间
11 ExtendBuildArea varchar 50 yes 扩建面积
12 BuildExpense numeric 20,2 yes 造价
13 Comment varchar 500 yes 备注
14 IsDelete varchar 10 yes 是否删除,0表示正常
15 CreateEmpID varchar 50 yes 创建人
16 CreateDate datetime yes 创建时间
17 LastEmpID varchar 50 yes 修改人
18 LastDate datetime yes 修改时间

(5)系统管理
9
表名(中文) 表名(英文) 字段前缀
用户登录信息表 Elec_User Elec_
序号 字段名 类型 长度 NULL 说明
1 UserID varchar 50 no 主键ID
2 JctID varchar 50 yes 所属单位code(对应数据字典)
3 UserName varchar 50 yes 用户姓名
4 LogonName varchar 50 yes 登录名
5 LogonPwd varchar 50 yes 密码
6 SexID varchar 10 yes 性别
7 Birthday datetime yes 出生日期
8 Adress varchar 100 yes 联系地址
9 ContactTel varchar 50 yes 联系电话
10 Email varchar 50 yes 电子邮箱
11 Mobile varchar 50 yes 手机
12 IsDuty varchar 10 yes 是否在职
13 OnDutyDate datetime yes 入职时间
14 OffDutyDate datetime yes 离职时间
15 Comment varchar 500 yes 备注
16 IsDelete varchar 10 yes 是否删除,0表示正常
17 CreatEmpID varchar 50 yes 创建人ID
18 CreateDate datetime yes 创建时间
19 LastEmpID varchar 50 yes 修改人ID
20 LastDate datetime 修改时间
10
表名(中文) 表名(英文) 字段前缀
角色信息表 Elec_Role Elec_
序号 字段名 类型 长度 NULL 说明
1 roleID varchar 50 no 主键ID
2 roleName varchar 50 yes 角色名称
11
表名(中文) 表名(英文) 字段前缀
权限信息表 Elec_Role Elec_
序号 字段名 类型 长度 NULL 说明
1 mid varchar 50 no 权限code(aa,ab,ac等)
2 pid varchar 50 yes 父级权限code (0,aa等)
3 name varchar 50 yes 权限名称
4 url varchar 500 yes 权限访问路径url(ztree插件中的属性)
5 icon varchar 100 yes 权限显示图表(ztree插件中的属性)
6 target varchar 50 yes 权限url访问目标区域(ztree插件中的属性)
7 isMenu boolean yes 权限是否是菜单
8 isParent boolean yes 权限是否父级权限(ztree插件中的属性)
12
表名(中文) 表名(英文) 字段前缀
用户角色关联表(多对多中间表)即联合主键 Elec_User_Role Elec_
序号 字段名 类型 长度 NULL 说明
1 userID varchar 50 no 用户ID
2 roleID varchar 50 yes 角色ID
13
表名(中文) 表名(英文) 字段前缀
角色权限关联表(多对多中间表)即联合主键 Elec_Role_Popedom Elec_
序号 字段名 类型 长度 NULL 说明
1 roleID varchar 50 yes 角色ID
2 mid varchar 50 yes 权限code(aa,ab,ac等)
3 pid varchar 50 yes 父级权限code (0,aa等)

11
表名(中文) 表名(英文) 字段前缀
用户权限信息表 Elec_Role_Popedom Elec_
序号 字段名 类型 长度 NULL 说明
1 RoleID varchar 50 no 主键ID
2 Popedomcode Varchar 50 yes 配置文件中权限的编码code的字符串连接
3 Comments varchar 500 yes 备注
4 IsDelete varchar 10 yes 是否删除
5 CreateEmpCode varchar 50 yes 创建人
6 CreateDate datetime yes 创建日期
11
表名(中文) 表名(英文) 字段前缀
用户角色信息表 Elec_User_Role Elec_
序号 字段名 类型 长度 NULL 说明
1 UserID varchar 50 no 用户ID
2 RoleID varchar 50 yes 角色code(对应数据字典)
3 SeqID int 50 yes 主键ID
4 Comments varchar 500 yes 备注
5 CreateEmpCode varchar 50 yes 创建人
6 CreateDate datetime yes 创建日期
12
表名(中文) 表名(英文) 字段前缀
运行监控表 Elec_commonmsg Elec_
序号 字段名 类型 长度 NULL 说明
1 UserID varchar 50 no 用户ID
2 StationRun varchar 1000 yes 站点运行情况
3 DevRun varchar 1000 yes 设备运行情况
4 CreateEmpCode varchar 50 yes 创建人
5 CreateDate datetime yes 创建日期

(6)导出字段设置表
13
表名(中文) 表名(英文) 字段前缀
导出字段设置表 Elec_ExportFields Elec_
序号 字段名 类型 长度 NULL 说明
1 BelongTo varchar 10 no 所属模块(如1-0,1-1,2-1等)
2 ExpNameList varchar 5000 yes 导出名称列表(中文)用“#”分开
3 ExpFieldName varchar 5000 yes 导出字段名称(字段名)用“#”分开
4 NoExpNameList varchar 5000 yes 未导出名称列表(中文)用“#”分开
5 NoExpFieldName datetime yes 未导出字段名称(字段名)用“#”分开
BelongTo:表示模块
仪器设备管理 1-1
设备校准检修 1-2
资料图纸管理 2-1

举例
用户管理:5-1
ExpNameList:导出字段的名称——登录名#用户姓名#出生日期#联系电话
ExpFieldName:需要导出的字段——logonname#username#birthday#ContextTel

NoExpNameList:不需要导出的字段名称——所属单位#性别
NoExpFieldName:不需要导出的字段——jctID#sexID

优势:
组织excel的头部信息
1、 查询5-1
2、 查询ExpNameName ,使用.split(“#”),就是显示的excel的头部信息
3、 查询ExpFieldList,使用.replace(“#”,”,”), select logonname,username,birthday from elec_user,查询所有数据
(7)附件表(资料图纸管理)
14
表名(中文) 表名(英文) 字段前缀
附件表 Elec_FileUpload Elec_
序号 字段名 类型 长度 NULL 说明
1 SeqID int no 主键ID
2 ProjID varchar 50 yes 带有附件的工程ID(所属单位)
3 BelongTo varchar 50 yes 所属模块1-0,1-1,1-2,2-0(图纸类别)
4 FileName varchar 50 yes 文件名
5 FileURL varchar 1000 yes 文件路径
6 ProgressTime varchar 20 上传时间
7 Comment varchar 500 备注
8 IsDelete varchar 10 是否删除
9 CreateEmpID varchar 50 创建人
10 CreateDate datetime 创建时间
主键ID 带有附件的工程ID BelongTO 文件路径
1 12345 1-1 XXXXX

(8)数据字典
15
表名(中文) 表名(英文) 字段前缀
数据字典表 Elec_SystemDDL Elec_
序号 字段名 类型 长度 NULL 说明
1 seqID Int 50 no 主键ID
2 Keyword varchar 20 yes 查询关键字
3 DdlCode Int 100 yes 数据字典的code值
4 DdlName varchar 50 yes 数据字典的value值

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值