ManagementDay11(跨部门跨人员细粒度 数据报表JFreeChart 项目整个逻辑)

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包
        <!-- https://mvnrepository.com/artifact/jfree/jcommon -->
        <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
        <!-- jdbcTemplate模版的配置 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

        <!-- sqlDao的配置 -->
        <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");

            //组织复合要求的xml数据
            String content = genPieDataSet(list);

            //将拼接好的字符串写入data.xml
            writeXML("stat\\\\chart\\\\factorysale\\\\data.xml",content);

            //      this.writeXML("stat\\\\chart\\\\factorysale\\\\data.xml", this.genPieDataSet(this.execSQL("select factory_name,sum(amount) samount from contract_product_c group by factory_name order by samount desc")));
                    return "factorysale";
        }

        private List<String> execSQL(String sql) {

            //执行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();
        }

        /**
         * 写入xml
         * @param sb
         * @throws FileNotFoundException
         */
        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");
    //      fileUtil.createTxt(sPath, "stat\\chart\\factorysale\\data.xml",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">
        //必须从此转向,否则路径错误会导致读取配置xml和数据xml文件错误。
        var _date = new Date();
        //如果直接访问html会把数据当作静态资源记录 不能每次重新读取 javascript 直接提供的window
        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();//获取客户端ip地址

        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 {
        //1、执行sql语句,得到统计结果
        List<String> list = execSQL("select factory_name,sum(amount) samount from contract_product_c group by factory_name order by samount desc");

        //2、组织复合要求的xml数据
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        /**
         * {
                    "country": "USA",
                    "visits": 4025,
                    "color": "#FF0F00"
                }
         */
        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("]");

        //3、将json串放到值栈 在页面上读取字串
        super.put("factorySales", sb.toString());
        //将拼接好的字符串写入data.xml
//      writeXML("stat\\\\chart\\\\factorysale\\\\data.xml",content);

//      this.writeXML("stat\\\\chart\\\\factorysale\\\\data.xml", this.genPieDataSet(this.execSQL("select factory_name,sum(amount) samount from contract_product_c group by factory_name order by samount desc")));
        return "factorysale01";
    }
4、改写页面
     var chartData = ${factorySales};
     AmCharts.ready(function () {
    // SERIAL CHART
    chart = new AmCharts.AmSerialChart();
    chart.dataProvider = chartData;
    chart.categoryField = "factory";//修改为json串中的字段名
    // the following two lines makes chart 3D
    chart.depth3D = 20;
    chart.angle = 30;

    // AXES
    // category
    var categoryAxis = chart.categoryAxis;
    categoryAxis.labelRotation = 90;
    categoryAxis.dashLength = 5;
    categoryAxis.gridPosition = "start";

    // value
    var valueAxis = new AmCharts.ValueAxis();
    valueAxis.title = "sales";//标题
    valueAxis.dashLength = 5;
    chart.addValueAxis(valueAxis);

    // GRAPH
    var graph = new AmCharts.AmGraph();
    graph.valueField = "amount";//修改为json串中的字段名
    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);

    // CURSOR
    var chartCursor = new AmCharts.ChartCursor();
    chartCursor.cursorAlpha = 0;
    chartCursor.zoomable = false;
    chartCursor.categoryBalloonEnabled = false;
    chart.addChartCursor(chartCursor);

    chart.creditsPosition = "top-right";


    // WRITE
    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">
        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
        <property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
        <!-- 缓存 -->
        <property name="cacheManager" ref="shiroEhcacheManager"/>
    </bean> 

    <!-- 用户授权/认证信息Cache, 采用EhCache  缓存 -->
    <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判断,可以做循环,支持复杂的对象关联。还支持宏,还支持自定义函数。可以实现模版的编程,这样模版非常灵活

代码自动生成工具可以应用于任何项目
这个自动生成工具比较简单,日常的文件都可以,但对象关联的内容,和负责对象结构时必须人工的去改写代码

猜你喜欢

转载自blog.csdn.net/civilizationv/article/details/80467992