oracle 先分组 然后select改好别名 再排序
jrebel会改变xml的源码
一、回顾
出口报运的功能补充(批量数据更新)
购销合同打印的读程(14个问题)
读程的两种方法
为什么要查询购销合同下货物对象列表时,要考虑根据厂家名称进行排序,这样做可以减少回退次数,算法效率得到提高
任务调度Quartz
Quartz+spring整合实现定时任务调度
配置
1、自定义的任务类
2、定义任务对象(MethodInvokingJobDetailFactoryBean)
注入:targetObject targetMethod
3、定义触发器
类:CronTriggerFactoryBean
注入:jobDetail
4、定义总调度器
类:SchedulerFactoryBean
注入:triggers(list集合)
二、细粒度权限控制
1、传统项目一般做到主菜单,左侧菜单,按钮的控制就可以了
2、在粗粒度基础上,进一步控制到数据级别
1、自己添加的记录将来自己可以操作
2、部门经理可以查看当前部门员工所添加的记录
3、部门经理可以查看当前部门及下属部门所有员工添加的记录
4、跨部门跨人员的权限
三、早期的图形报表实现
1、使用Excel实现图形报表
1、从数据中统计出结果
2、打开excel,将数据复制进去,再插入图表就行
2、使用JFreeChart实现图表制作
是使用java开源方式写出来的一套专门制作图形的插件,
早期使用非常普遍。JFreeChart的文档和服务是收费的。
1、导入jar包
Maven工程的坐标 在jar包的位置打开cmd安装jar包
<dependency>
<groupId>jfree</groupId>
<artifactId>jcommon</artifactId>
<version>1.0.16</version>
</dependency>
mvn install:install-file -DgroupId=jfree -DartifactId=jcommon -Dversion=1.0.16 -Dpackaging=jar -Dfile=jcommon-1.0.16.jar
<dependency>
<groupId>jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.0.13</version>
</dependency>
mvn install:install-file -DgroupId=jfree -DartifactId=jfreechart -Dversion=1.0.13 -Dpackaging=jar -Dfile=jfreechart-1.0.13.jar
pom.xml文件中引入就上面两个dependency
WEB工程直接将2个jar包放入WEB-INF/lib文件夹
3、第三方,功能强大bird,润乾,数巨报表,水晶报表(.net)(收费)
4、js+flash占的市场比例大:amchart,highchart
3、amChart+Flash 动画实现图形报表
1、amChart实现图形报表,早期使用的是xml作为数据来源,新版本中进行调整,使用json数据作为数据来源
从数据来源可以看出,将来要使用amChart制作图表时,主要就是把data.xml中的数据替换为从数据库中查询出来的数据就可以了,就会涉及到操作xml文档,常用的xml操作方式(JDOM,DOM4J,SAX),用的最多的是DOM4J实现XML文档操作。但是本次项目不使用这些,而是把xml文档当成一个普通文件,直接使用流的方式,将一个组织好的符号XML结构的字符串写入到XML文件中,这样就可以不用频繁操作XML文件的结点
统计查询时更适合使用SQL语句,所以项目要支持SQL查询
添加相关的操作sql语句的工具类SqlDao
本次项目实现数据访问的DAO是通过Hibernate和jdbc操作sql语句
Hibernate进行CUD操作,同时会操作一级缓存。而在同时jdbc也对这条记录进行CUD操作,此时就会出现问题,因为jdbc是直接操作数据库的,不会及时更新Hibernate一级缓存,由此导致数据不一致
解决方案:jdbc使用sql语句只做查询
因为操作sql语句时,使用的是jdbcTemplate技术,所以就需要配置它,操作sql语句的工具类还要注入jdbcTemplate
1、配置jdbcTemplate(注入dataSource)
2、配置SqlDao工具类(注入jdbcTemplate)
加入最新配置applicationContext-dao.xml
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="sqlDao" class="my.springdao.SqlDao">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
2、添加处理的action
组织数据的对应关系
factory_name--》xml中的title samount--》value
相应配置文件的配置过程
3、生产饼形
public String factorysale() throws Exception {
List<String> list = execSQL("select factory_name,sum(amount) samount from contract_product_c group by factory_name order by samount desc");
String content = genPieDataSet(list);
writeXML("stat\\\\chart\\\\factorysale\\\\data.xml",content);
return "factorysale";
}
private List<String> execSQL(String sql) {
List<String> list = sqlDao.executeSQL(sql);
return list;
}
private String genPieDataSet(List<String> list) {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("<pie>");
for (int i = 0; i < list.size(); i++) {
sb.append(" <slice title=\""+list.get(i)+"\" pull_out=\"true\">"+list.get(++i)+"</slice>");
}
sb.append("</pie>");
return sb.toString();
}
private void writeXML(String fileName,String content) throws FileNotFoundException {
FileUtil fileUtil = new FileUtil();
String sPath = ServletActionContext.getServletContext().getRealPath("/");
fileUtil.createTxt(sPath, fileName,content, "UTF-8");
}
4、配置
本身应当返回html,但是浏览器对于静态资源会缓存,如果考虑通过中间的jsp页面做中转,就可以让action先返回到jsp,再回jsp跳转到html
<action name="statChartAction_*" class="statChartAction" method="{1}">
<result name="factorysale">/WEB-INF/pages/stat/chart/jStat.jsp?forward=factorysale</result>
<result name="factorysale01">/WEB-INF/pages/stat/chart/column3D.jsp</result>
<result name="onlineinfo">/WEB-INF/pages/stat/chart/jStat.jsp?forward=onlineinfo</result>
<result name="productsale">/WEB-INF/pages/stat/chart/jStat.jsp?forward=productsale</result>
</action>
5、在jStat页面中访问结果
<script type="text/javascript">
var _date = new Date();
window.location.href = "${pageContext.request.contextPath}/stat/chart/<%=request.getParameter("forward")%>/index.html?d="+_date;
</script>
系统访问压力图
1、准备数据
online_info_t表 指的是一天的24小时
login_log_p表,访问日志,当用户登录时就向这个表中添加一条记录
数据来源,可以再次修改登录操作
获取相关的数据,直接在login_log_p表中添加数据
ServletActionContext.getRequest().getRemoteAddr();
2、准备统计的sql语句
#14点(范围) 多少人在线
select count(*) c from login_log_p where to_char(login_time,'HH24') ='14';
每个时间片段(1小时一个片段)多少人在线
select to_char(login_time,'HH24') loginTime,count(*) from login_log_p group by to_char(login_time,'HH24') order by loginTime ;
select a.a1,nvl(b.c,0)
from
(select * from online_info_t) a left join
(select to_char(login_time,'HH24') a1,count(*) c from login_log_p group by to_char(login_time,'HH24') order by a1
) b
新版的amchart
1、加入相关的js/css
amchart的amcharts、images、style.css
2、将选好的页面放入到工程中
3、改写Action
public String factorysale() throws Exception {
List<String> list = execSQL("select factory_name,sum(amount) samount from contract_product_c group by factory_name order by samount desc");
StringBuilder sb = new StringBuilder();
sb.append("[");
String colors[] = {"#FF0F00","#FF6600","#FF9E01","#FCD202","#F8FF01"
,"#B0DE09","#04D215","#0D8ECF","#0D52D1","#2A0CD0"
,"#8A0CCF","#CD0D74","#754DEB","#DDDDDD","#999999"};
int j = 0;
for (int i = 0; i < list.size(); i++) {
sb.append("{").append("\"factory\":\"").append(list.get(i)).append("\",")
.append("\"amount\":\"").append(list.get((++i))).append("\",")
.append("\"color\":\"").append(colors[j++]).append("\"")
;
sb.append("}").append(",");
if (j>=colors.length) {
j = 0;
}
}
sb.delete(sb.length()-1, sb.length());
sb.append("]");
super.put("factorySales", sb.toString());
return "factorysale01";
}
4、改写页面
var chartData = ${factorySales};
AmCharts.ready(function () {
chart = new AmCharts.AmSerialChart();
chart.dataProvider = chartData;
chart.categoryField = "factory";
chart.depth3D = 20;
chart.angle = 30;
var categoryAxis = chart.categoryAxis;
categoryAxis.labelRotation = 90;
categoryAxis.dashLength = 5;
categoryAxis.gridPosition = "start";
var valueAxis = new AmCharts.ValueAxis();
valueAxis.title = "sales";
valueAxis.dashLength = 5;
chart.addValueAxis(valueAxis);
var graph = new AmCharts.AmGraph();
graph.valueField = "amount";
graph.colorField = "color";
graph.balloonText = "<span style='font-size:14px'>[[category]]: <b>[[value]]</b></span>";
graph.type = "column";
graph.lineAlpha = 0;
graph.fillAlphas = 1;
chart.addGraph(graph);
var chartCursor = new AmCharts.ChartCursor();
chartCursor.cursorAlpha = 0;
chartCursor.zoomable = false;
chartCursor.categoryBalloonEnabled = false;
chart.addChartCursor(chartCursor);
chart.creditsPosition = "top-right";
chart.write("chartdiv");
面试,项目叙述
分为两关
1、人力
2、技术经理:技术基础(面试)+项目经验(面试)
项目经验:
准备:
1、功能点,业务,定义
2、技术亮点(业务中牵出技术亮点)
3、让面试官感兴趣
业务:
货运管理,包括:购销合同,出口报运单,装箱,委托,发票,财务
购销合同:
关系合同是和生产厂家签订的合同。合同是由合同主信息和多个货物信息和多个附件信息组成。两级一对多。货物是玻璃杯,附件是玻璃杯上的装饰物,包
括:花纸、pvc、电镀等。在开发购销合同模块时,做了一个复杂的表单打印。包括打印log图片,多个货物的图片,包括线,合并单元格。打印时用户可以
执行一页纸打印一款货物还是两款货物。如果是两款货物,且是同一个生产厂家,可以打印到同一页,如果不是同一个生产厂家,必须另起一页。根据用户
的习惯,采用poi来输出内容到excel中
报运:
签到购销合同后,准备报关材料,进行报关。对应系统,只打印一个《商品出口报运单》,为报关提供货物信息。一个报运单来自多个合同,包括报运的主信息
,包括报运的货物信息。报运单下的货物信息来自合同,主要包括:货号,数量,包装单位,箱数(件数)等等,还包括:新增的多个字段:毛重、净重、长
、宽、高、出口单价、含税等等。在设计时进行了优化,采用了“打断设计”方法和“数据搬家”,实现数据快速查询,后续业务直接使用这里提供的数据
装箱,委托,发票,财务
报运通过海关批准后,准备货物运输装船。订箱。找一个货代公司,向船东订箱。找货代公司订拖车。拖车到码头,拉空箱子到生产厂家指定的仓库,在验
货员的监督下,先将货物装到纸箱中,然后装到集装箱中。拖车拉着箱子到海关指定的码头,卸货。货物风吹雨淋,等海关进行抽检,合格后海关给提单。
货物接着风吹雨淋,等待装船。船到港后,安排货物装船。(一种情况,当货物过了船舷,职责完成。还有一种到目的港后才完成)开船,到港口(可能是
一个小港口,在某个港口进行转船)进行卸货,货物在目的港风吹雨淋,等待客户来提货,在开船时,快递发票通知,通知客户货已经发了,支付剩余尾款
。同时将提单快递给用户。客户通过提单去码头提货。客户自己找拖车公司,拖车拉着货物到客户指定的仓库,卸货。拖车拉着空箱到码头,将箱子交给码头
。所有业务流程结束。
财务是内部进行核实,核实这单生意赚取多少利润,还要计算税
设计:
1、细粒度权限控制
现有软件系统都基本是基于角色权限控制
在系统中实现一个细粒度权限控制,通过部门、用户、角色、权限模块,动态指定角色拥有哪些模块访问的权力,动态指定用户有哪些角色。当权限发生变更
时,只需要修改角色,角色对应的用户它的权限就都修改了(例如:当系统中有1万个用户,如果用户直接控制权限,当权限发生变更,修改1万个用户的配
置;如果有了角色,只修改这个角色对应的权限所有继承这个角色的1万个用户就同时拥有了新的权限)
权限模块实现对访问系统的URL进行过滤,还实现主菜单、左侧菜单、按钮的控制;我们进行了更细致的控制,对数据也进行过滤,还对部分特殊的数据进行了加密。
(在每个主表或者单表中增加两个权限控制字段,包括:登录人的ID和登录人所在部门的id,可以实现多种控制)
例如:
普通用户登录后只能看到自己的信息
部门经理登录后可以看到本部门的信息
副总登录可以看到所有下属部门的信息
管理员可以看到所有的信息
通过上面两个字段,在新增记录时,记录下登录用户的信息;在配置用户信息时,增加一个“等级”字段。
上面实现数据的过滤
系统采用权限的权限安全控制架构,采用的是apache的shiro,比Spring Securtiy 安全框架使用起来要简洁很多,配置非常方便,功能也不弱。Shiro独立管理session,更容易实现单点登录
shiro功能:认证,授权
附带好处:加密,缓存
认证:
旧的系统是采用传统登录方式,将它改造成shiro来控制。在项目中引入jar,项目采用maven来实现。在pom文件中依赖jar。项目采用三大框架来实现S2SH
,shiro和spring进行整合。首先在spring核心配置文件中就要引入shiro的核心配置文件,然后要配置这个核心配置文件。在这个spring核心配置文件中
就要引入shiro的核心配置文件。然后要配置这个核心配置文件。在这个核心配置文件中主要配置SecurityManager安全管理,还要配置自定义的realm域(
realm实现shiro系统和业务系统结合点,在它当中可以实现互相通讯,主要实现认证和授权)配置URL验证,还要配置自定义密码加密算法,还可以配置二级缓存
运行,在web.xml中配置filter,配置后,相当于引入10个filter。日常主要使用URL中配置的anon和authc这两个filter。anon的filter实现不进行权限控制,auth必须实现权限控制
具体实现登录
改造旧的系统中的登录action,要引用subject.login()方法,登录,这时传递用户录入的用户名和密码给它,会自动调用自定义的realm的认证方法do
GetAuthenticationInfo,执行这里开发人员自己写的方法。在这里实现从业务系统查询用户的信息,将它传递给
new SimpleAuthenticationInfo(_user,_user.getPassword(),this.getName());
进行验证,内部通过传递进去的用户,获取用户的密码(这个密码加密后的密码),然后调用传入的密码参数,然后调用在shiro核心配置文件中配置的加密算法,
加密后,和用户的密码信息进行比较。比较如果相同,通过验证。Subject.login就会将当前用户信息保存到subject对象中,可以通过subject.getPrincipal()
获取当前的User.获取后就可以将它存放到session中,系统就可以按过去的访问的方法进行访问。
授权:
页面在解析shiro标签时,自动调用自定义realm的doGetAuthorizationInfo(),将标签上的name属性传递过去,<shiro:hasPermission name="货运管理">在这个方法中
,要调用业务系统的方法,去查询当前登录用户所配置的模块权限(提前做好一个方法,根据当前用户去查询它所拥有的权限功能串 CPERMISSION字段)
拿传入的name属性看它是否在这个权限串中,如果在就展现标签中的内容,如果不在,就不展示标签的内容
存在的问题:
每个登录用户都有权限串,每个都放在内存中,非常耗费资源
每个标签都会触发一次数据查询
shiro通过采用二级缓存来解决,系统配置ehcache缓存 applicationContext-shiro.xml
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="authRealm"/>
<property name="cacheManager" ref="shiroEhcacheManager"/>
</bean>
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
</bean>
applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
密码加密
MD5理论上不可逆
Shiro对md5进行增强
原有md5如果从数据库获得,看到密码后,可以做已知的md5加密后的串,来更新这个用户。就可以破解帐号来登录,破坏。
Shiro使用md5hash算法,给密码加密时增加难度,加salt,还增加加密次数。这个就使上面上面的方式无效,一般采用的salt要变化
加密次数不易过多,国道加密解密时间就拉长
数据表设计
打断设计
在关联层过多时,人为要减少关联层级,因为层级过多时查询效率非常低(懒加载在业务中需要子信息时,失效)在主表中增加一个打断字段,来保持两个
表之间关系(一般一对多)这个字段将多个值用特殊符号分隔存放进来,这样就可以进行跳跃查询。如果是传统的管理方式,通过管理对象才能查询它的子信息,例如:报运和购销合同,报运中要获取合同的子信息,也就是货物信息。
传统方式只能通过合同对象,再获取合同关联的货物信息;但打断设计后,可以直接通过在报运中的打断字段,形成一个in子查询串,直接去查询货物信息。
这样就跳过了合同表。这样查询效率会高一些。如果关联层级很多,比如现有的合同管理流程,包括:购销合同、出口报运、装箱单、委托书、发票通知、财务。
财务要获取前面流程的货物信息。如果按传统设计,财务去找发票,发票去找委托,这样查询非常低。经过两级打断,特殊一对一关联。最后实现,财务找装箱,
通过装箱的打断字段去找报运下的货物信息。在关联层级越多的情况下,这种方式的优势越能体现
特殊一对一
权限:用户和用户扩展信息一对一
考虑到系统使用用户量大,将登录表和用户详细信息表进行拆分。这样登录时,非常快,查询的信息少
装箱,委托,发票,财务
一个委托来自一个装箱,一个发票来自一个委托,一个财务统计来自一个发票。在做的时候,进行了优化。传统设计时层层一对一关联,或者将它们几个的
内容合成一张表。第一种设计获取数据必须层层关联来获取,当层级过多时,查询效率比较低。第二种,表的结构不清晰,开发人员不易理解。
优化后,将委托、发票、财务的主键设置为装箱的id。委托、发票、财务主键即外键
假定现在得到一个id,无论这个id是装箱,委托,发票,财务,都可以通过它来查询其他的对象。也实现了"跳跃查询"
技术亮点
细粒度权限
POI实现模版打印
系统根据用户习惯,采用excel方式,作为业务输出打印。打印时poi是有问题,打印时的代码量非常多,样式代码多(常保存,样式和字体对象创建过多)。
打印时非常难控制。改造后,使用模版来开发,在模版中定义列宽(POI操作列宽时有BUG,计算非常繁琐),还可以定义静态文件,例如标题部分采用静态文字的内容
,打印纸质方面,打印页面标题,页眉页脚,都可以在模版中设置。首先无需记忆复杂特殊api。设置字体、样式也可以直接在模版中定义。打印时先读取模版,只处理业务数据的打印即可
POI海量数据导出
系统中备份和恢复数据
操作excel主要有jxl和poi两种方式,jxl在处理数据时比早期的poi快(poi早期对象,处理时都是将加工的数据存放在内存中,如果数据量很多,很容易
造成堆溢出,同时它占用cpu和大量内存,导致其他业务也无法正常完成)poi在新版本中改善这个性能瓶颈,对大数据量的导出做了优化
使用ooxml技术,使用sxssf对象,当数据创建到指定数量时,自动写缓存,将它内容输出到临时文件中。这个临时文件是一个xml,相比内存中对象的结
构非常简单。只保留数据的信息。保存的数据量也非常少。这样就可以形成大数量的导出。例如:项目中,购销合同业务,有很多历史信息,积累了很多年,
数据量达到近百万。导致系统变慢,采用poi导出数据备份到excel文件中,将这部分历史信息从当前表中删除。在打印中就可以直接实现(excel单sheet可以支持1048576)
自动代码生成工具
应用freeMarker第三方工具类,实现模块开发。自定义一个代码模版,这个模版中自定义一些变量。日常主要类和页面的变量,例如:数据库表,表的字段
,类的名称,类的属性,这些可以直接调用。做好每个文件的模版后,在后台从数据库表中读取这个业务相关的字段,类型,备注。调用freeMarks。通过freeMarks配置config。
打开我们指定模版,然后将要生成这些文件对应的表名传递进去,通过数据库表名查询数据库,通过数据库提供的直接查询字段的SQL。这里比结果集的源数据多一个备注字段。
查询到这些信息,将他们封装到一个map集合中。key就是我们在模版中定义的变量,value就是数据中的内容。把这个map集合和模版传递给freeMarks。freeMarks进行模版中变量值的替换。
freeMarker非常强大,可以有标签,可以有标签嵌套。还可以有自定义的方法,例如:类名全大写,圈小写,首字母大写,首字母小姐(可以少定义变量),
可以if判断,可以做循环,支持复杂的对象关联。还支持宏,还支持自定义函数。可以实现模版的编程,这样模版非常灵活
代码自动生成工具可以应用于任何项目
这个自动生成工具比较简单,日常的文件都可以,但对象关联的内容,和负责对象结构时必须人工的去改写代码