• 今天内容安排:
    • 1、区域数据批量导入功能
      • jQuery OCUpload(一键上传插件)
      • 使用apache POI解析Excel文件
      • 使用Pinyin4J生成简码和城市编码
    • 2、实现区域的分页查询
    • 3、对分页代码重构
    • 4、添加分区(使用combobox下拉框)
    • 5、分区的组合条件分页查询
    • 6、分区数据导出功能

1、区域数据批量导入功能

1.1、jQuery OCUpload(一键上传插件)

  • ajax不能做文件上传。
    第一步:在jsp页面中引入插件的js文件
<!-- 引入一键上传控件的js文件 -->
<script type="text/javascript" 
    src="${pageContext.request.contextPath }/js/jquery.ocupload-1.1.2.js">
</script>

第二步:在页面中提供任意一个元素

     <input id="but1" type="button" value="上传">

第三步:调用该插件提供的upload方法,动态修改页面html代码

    <!-- 使用该控件 -->
    <script type="text/javascript">
        $(function() {
            $("#but1").upload({
                action'abc',  
                name'myFile'
            });
        });
    
</script>

动态修改页面html代码效果如下图所示:

day52_BOS项目_04-LMLPHP

1.2、使用apache POI解析Excel文件

  • Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
    第一步:导入poi-3.9-20121203.jar包
    第二步:测试代码如下:
package com.itheima.mytest;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.junit.Test;

public class POITest {
    /**
     * 使用POI解析Excel文件
     * @throws IOException 
     * @throws FileNotFoundException 
     */

    @Test
    public void test1() throws FileNotFoundException, IOException{
        HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(new File("E:\\test\\abc.xls")));
        HSSFSheet sheet = workbook.getSheetAt(0);
        for (Row row : sheet) {
            String v1 = row.getCell(0).getStringCellValue();
            String v2 = row.getCell(1).getStringCellValue();
            String v3 = row.getCell(2).getStringCellValue();
            String v4 = row.getCell(3).getStringCellValue();
            String v5 = row.getCell(4).getStringCellValue();
            System.out.println(v1 + " " + v2 + " " + v3 + " " + v4 + " " + v5);
        }
    }
}

控制台输出结果为:

区域编号 省份 城市 区域 邮编
QY001 北京市 北京市 东城区 110101
QY002 北京市 北京市 西城区 110102
QY003 北京市 北京市 朝阳区 110105
QY004 北京市 北京市 丰台区 110106
QY005 北京市 北京市 石景山区 110107
QY006 北京市 北京市 海淀区 110108
QY007 北京市 北京市 门头沟区 110109
QY008 北京市 北京市 房山区 110111
QY009 北京市 北京市 通州区 110112
QY010 北京市 北京市 顺义区 110113
QY011 北京市 北京市 昌平区 110114
QY012 北京市 北京市 大兴区 110115
QY013 北京市 北京市 怀柔区 110116
QY014 北京市 北京市 平谷区 110117
QY015 北京市 北京市 密云县 110228
QY016 北京市 北京市 延庆县 110229
......

第三步:在RegionAction中提供批量导入方法

package com.itheima.bos.web.action;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.struts2.ServletActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

import com.itheima.bos.domain.Region;
import com.itheima.bos.service.IRegionService;
import com.itheima.bos.web.action.base.BaseAction;

/**
 * 区域设置
 * @author Bruce
 *
 */

@Controller
@Scope("prototype")
public class RegionAction extends BaseAction<Region{

    @Autowired
    private IRegionService regionService;

    // 采用属性驱动的方式,接收上传过来的文件
    private File myFile;
    public void setMyFile(File myFile) {
        this.myFile = myFile;
    }

    /**
     * 批量导入Xls文件
     * @throws IOException 
     * @throws FileNotFoundException 
     */

    public String importXls() throws Exception {
        String flag = "1";
        try {
            // 使用POI解析Excel文件
            HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(myFile));
            // 获得第一个sheet页
            HSSFSheet sheet = workbook.getSheetAt(0);
            List<Region> list = new ArrayList<Region>();
            for (Row row : sheet) {
                int rowNum = row.getRowNum();
                if (rowNum == 0) {
                    // 第一行,标题行,忽略
                    continue;
                }
                String id = row.getCell(0).getStringCellValue();
                String province = row.getCell(1).getStringCellValue();
                String city = row.getCell(2).getStringCellValue();
                String district = row.getCell(3).getStringCellValue();
                String postcode = row.getCell(4).getStringCellValue();

                Region region = new Region(id, province, city, district, postcode, nullnullnull);
                list.add(region);
            }
            regionService.saveBatch(list);          
        } catch (Exception e) {
            flag = "0";
        }
        // 服务器响应给浏览器一个状态码,这种手法常用
        ServletActionContext.getResponse().setContentType("text/html;charset=UTF-8");
        ServletActionContext.getResponse().getWriter().print(flag);

        return "none";
    }
}

第四步:浏览器根据服务器响应回来的状态码,进行判断并给出提示信息

<!-- 使用上传控件 -->
<script type="text/javascript">
    $(function() {
        $("#button-import").upload({
            action'${pageContext.request.contextPath}/regionAction_importXls.action',  
            name'myFile',
            // 浏览器根据服务器响应回来的状态码,进行判断并给出提示信息
            onComplete: function(data{
                // alert(data);
                if (data == '1') {
                    // 文件上传成功
                    $.messager.alert("提示信息""区域数据导入成功!""info");
                } else {
                    // 文件上传失败
                    $.messager.alert("提示信息""区域数据导入失败!""warning");
                } 
            }
        });
    });
</script>

1.3、使用Pinyin4J生成简码和城市编码

第一步:导入pinyin4j-2.5.0.jar包,拷贝PinYin4jUtils.java工具类至utils包中
第二步:测试类代码如下:

package com.itheima.mytest;

import org.apache.commons.lang3.StringUtils;
import org.junit.Test;

import com.itheima.bos.utils.PinYin4jUtils;

public class Pinyin4JTest {
    @Test
    public void test1(){
        String province = "河北省";
        String city = "石家庄市";
        String district = "长安区";

        // 城市编码 --> shijiazhuang
        city  = city.substring(0, city.length() - 1);
        String[] stringToPinyin = PinYin4jUtils.stringToPinyin(city);
        String citycode = StringUtils.join(stringToPinyin, "");
        System.out.println(citycode);

        // 简码 --> HBSJZCA
        province  = province.substring(0, province.length() - 1);
        district  = district.substring(0, district.length() - 1);
        String info = province + city + district; // 河北石家庄长安
        String[] headByString = PinYin4jUtils.getHeadByString(info);
        String shortcode = StringUtils.join(headByString, "");
        System.out.println(shortcode);
    }
}

2、实现区域的分页查询

  • 代码同取派员的分页查询。

3、对分页代码重构

  • 在BaseAction中抽取PageBean对象,在BaseAction中提供setPage和setRows方法,并注入给PageBean对象
    // 采用属性驱动的方式,接收页面提交过来的参数
    protected PageBean pageBean = new PageBean();
    public void setPage(int page) {
        // 设置当前页码
        pageBean.setCurrentPage(page);
    }
    public void setRows(int rows) {
        // 设置每页显示记录数
        pageBean.setPageSize(rows);
    }
  • 在BaseAction中抽取条件查询对象
    // 设置离线条件查询对象,封装查询条件
    DetachedCriteria detachedCriteria = null;
  • 在BaseAction的构造方法中创建条件查询对象,并注入给PageBean对象
    如下图所示:
    day52_BOS项目_04-LMLPHP
  • 在BaseAction中抽取将PageBean对象转为json的方法
    /**
     * 将PageBean对象转为JSON格式的数据的方法
     * @param pageBean
     * @param excludes
     * @throws IOException
     */

    public void writePageBean2Json(PageBean pageBean, String[] excludes) throws IOException {
        // 步骤:先导入json-lib的jar包+依赖包,步骤链接:https://www.cnblogs.com/chenmingjun/p/9513143.html
        // 将PageBean对象转为JSON格式的数据响应给客户端浏览器进行显示
        // 排除不需要的数据和排除关联对象
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.setExcludes(new String[] {"currentPage""pageSize""detachedCriteria"});

        JSONObject jsonObject = JSONObject.fromObject(pageBean, jsonConfig);
        String json = jsonObject.toString();

        ServletActionContext.getResponse().setContentType("text/json;charset=UTF-8");
        ServletActionContext.getResponse().getWriter().print(json);
    }
  • 在RegionAction中使用分页方法
    /**
     * 分页查询
     * @throws IOException 
     */

    public String pageBean() throws IOException {
        // 调用方法,设置PageBean对象的其他属性
        regionService.pageBean(pageBean); // 注意:pageBean传的是对象的引用,调用该方法后,对象的属性就发生改变了
        // 调用将PageBean对象转为JSON格式的数据的方法
        this.writePageBean2Json(pageBean, new String[] {"currentPage""pageSize""detachedCriteria"}); // 实际做项目中,要把没用到的数据都给干掉,也就是说不需要显示的数据有很多
        return "none";
    }

4、使用jQuery EasyUI 下拉框combobox

day52_BOS项目_04-LMLPHP
第一步:在subarea.jsp中使用combobox下拉框展示区域数据到下拉框中
    <tr>
        <td>选择区域</td>
        <td>
            <input class="easyui-combobox" name="region.id"  
                data-options="valueField:'id',textField:'name',
                url:'${pageContext.request.contextPath}/regionAction_listajax1.action'"
 />
  
        </td>
    </tr>

效果如下图所示:

day52_BOS项目_04-LMLPHP
第二步:在RegionAction中提供listajax()方法,查询所有的区域数据,返回json数据,并将该方法抽取至BaseAction中
RegionAction.java
    /**
     * 查询所有的区域数据,返回json
     * @throws IOException 
     */

    public String listajax() throws IOException {
        List<Region> list = regionService.findAll();
        String[] exclude = new String[]{"subareas"}; // 实际做项目中,要把没用到的数据都给排除掉,也就是说不需要显示的数据有很多,本例中只排除掉了一个
        this.writeList2Json(list, exclude);
        return null;
    }

BaseAction.java

    /**
     * 将List集合对象转为JSON格式的数据的方法
     * @param list
     * @param exclude
     * @throws IOException 
     */

    public void writeList2Json(List list, String[] exclude) throws IOException {
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.setExcludes(new String[] {"currentPage""pageSize""detachedCriteria"});

        JSONArray jsonObject = JSONArray.fromObject(list, jsonConfig);
        String json = jsonObject.toString();

        ServletActionContext.getResponse().setContentType("text/json;charset=UTF-8");
        ServletActionContext.getResponse().getWriter().print(json);
    }

为了使返回的json中含有name字段,需要在Region类中提供getName()方法

    // 序列化一个对象的时候,找的是getter方法
    public String getName() {
        return province + city + district;
    }

浏览器返回的json数据效果如下图所示:

day52_BOS项目_04-LMLPHP
页面效果如下图所示:
day52_BOS项目_04-LMLPHP

5、添加分区

第一步:页面位置:/bos19/WebContent/WEB-INF/pages/base/subarea.jsp
为了便于处理,我们先将subarea.jsp中的分拣编码选项框删掉,该编号我们让其自动生成。
我们在Subarea.hbm.xml中更改主键生成策略,代码如下:

    <id name="id" type="java.lang.String">
        <column name="id" length="32" />
        <!-- generator:主键生成策略,uuid:生成32位的不重复随机字符串当做主键 -->
        <generator class="uuid" />
    </id>

第二步:为添加窗口中的“保存按钮”绑定事件

    <div class="datagrid-toolbar">
        <a id="save" icon="icon-save" href="#" class="easyui-linkbutton" plain="true" >保存</a>
        <script type="text/javascript">
            $(function() {
                $("#save").click(function() {
                    var v = $("#addSubareaForm").form("validate");
                    if (v) {
                        $("#addSubareaForm").submit();
                    }
                });
            });
        
</script>           
    </div>

第三步:创建SubareaAction类,提供add方法,处理分区添加动作
SubareaAction.java

package com.itheima.bos.web.action;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

import com.itheima.bos.domain.Subarea;
import com.itheima.bos.web.action.base.BaseAction;

/**
 * 分区设置
 * @author Bruce
 *
 */

@Controller
@Scope("prototype")
public class SubareaAction extends BaseAction<Subarea>{

    /**
     * 添加分区的方法
     * @return
     */

    public String add() {
        subareaService.save(model);
        return "list";
    }
}

在第三步之前,我们将所有的注入service,抽取至BaseAction中,将修饰符public改为protected,使其子类能够访问
BaseAction.java

    // 注入service
    @Autowired
    protected IUserService userServie;
    @Autowired
    protected IStaffService staffService;
    @Autowired
    protected IRegionService regionService;
    @Autowired
    protected ISubareaService subareaService;

第四步:配置struts.xml

    <!-- 分区管理:配置subareaAction-->
    <action name="subareaAction_*" class="subareaAction" method="{1}">
        <result name="list">/WEB-INF/pages/base/subarea.jsp</result>
    </action>

6、解决区域分页查询的bug

Method public java.lang.String org.apache.commons.lang.exception.NestableRuntimeException.getMessage(intthrew an exception when invoked on net.sf.json.JSONExceptionThere is a cycle in the hierarchy!
  • 延迟加载也称为懒加载,是Hibernate3关联关系对象默认的加载方式,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。简单理解为,只有在使用的时候,才会发出sql语句进行查询。
  • Hibernate中主要是通过代理(proxy)机制来实现延迟加载。我们在查询区域的时候,区域关联的分区没有立即查询,因为所有的关联查询默认都是延时加载(懒加载)。那么返回来的就是代理对象,而代理对象是不能被序列化的。
  • 如何解决呢?
    答:因为PageBean中的属性有集合list,此时的list集合中存放的是Region对象,而Region对象中又关联一个集合set,该set集合存放的Subareas对象,该Subareas对象默认是懒加载的,而此时我们没有用到Subareas对象的数据,所以我们就应该将其排除掉。
    this.writePageBean2Json(pageBean, new String[] {"currentPage""pageSize""detachedCriteria""subareas"}); 

7、实现分区分页查询(没有过滤条件)

  • 代码同区域的分页查询。
    小区别:当我们查询分区表的时候,需要立即去查询关联的区域表
    我们需要修改分区的Hibernate配置文件Subarea.hbm.xml中的加载时机,修改代码如下:
    <!-- lazy="false" 表示:当我们查询分区表的时候,立即去查询关联的区域表,即不使用懒加载,使用立即加载,就不返回代理对象了 -->
    <many-to-one lazy="false" name="region" class="com.itheima.bos.domain.Region" fetch="select">
        <column name="region_id" length="32" />
    </many-to-one>
    <many-to-one name="decidedzone" class="com.itheima.bos.domain.Decidedzone" fetch="select">
        <column name="decidedzone_id" length="32" />
    </many-to-one>

8、实现分区组合条件分页查询

EasyUI Datagrid 数据网格的load()方法:
  加载并显示第一页的行,如果指定 'param' 参数,它将替换 queryParams 属性。通常情况下,通过传递一些从参数进行查询,该方法被调用来从服务器加载新数据。
查询分区页面如下图所示:

day52_BOS项目_04-LMLPHP
第一步:为“查询按钮”绑定事件,调用datagrid的load()方法,重新发起ajax请求,并提交输入框参数,这里我们使用一个工具方法:将指定的表单中的输入项序列化为json对象
    // 工具方法:可以将指定的表单中的输入项序列化为json对象
    $.fn.serializeJson = function() {
        var serializeObj = {};
        var array = this.serializeArray();
        $(array).each(
            function() {
                if (serializeObj[this.name]) {
                    if ($.isArray(serializeObj[this.name])) {
                        serializeObj[this.name].push(this.value);
                    } else {
                        serializeObj[this.name] = [serializeObj[this.name], this.value];
                    }
                } else {
                    serializeObj[this.name] = this.value;
                }
            });
        return serializeObj;
    };

    // 绑定事件:执行查询
    $("#btn").click(function() {
        // 将表单序列化为json对象
        var p = $("#searchForm").serializeJson(); // json对象格式:{id:'xxx', name:'xxx', age:'xxx', ...}
        // 重新发起ajax请求,并提交新的参数(包括原来的参数)
        $("#grid").datagrid("load", p);
        // 关闭查询窗口
        $("#searchWindow").window("close");
    });

浏览器的调试截图:

day52_BOS项目_04-LMLPHP
第三步:修改SubareaAction中的分页查询方法,封装分页查询的条件
    /**
     * 分页查询
     * @return
     * @throws IOException 
     */

    public String pageQuery() throws IOException {
        // 由于提交的表单中新增了其他条件,所以我们在分页查询之前,需要封装条件
        DetachedCriteria detachedCriteria2 = pageBean.getDetachedCriteria();

        // QBC查询语句:当关联查询时,也即多表查询,需要创建别名,通过别名才可以访问关联对象的属性

        // 先根据分区的地址关键字进行模糊查询
        String addresskey = model.getAddresskey();
        if (StringUtils.isNotBlank(addresskey)) {
            detachedCriteria2.add(Restrictions.like("addresskey""%" + addresskey + "%"));
        }

        // 再根据 省/市/区 进行模糊查询
        Region region = model.getRegion();
        if (region != null) {
            // 创建别名,用于多表查询
            detachedCriteria2.createAlias("region""r");

            String province = region.getProvince();
            String city = region.getCity();
            String district = region.getDistrict();

            if (StringUtils.isNotBlank(province)) {
                // 根据 省 进行模糊查询(注意:关联查询)
                detachedCriteria2.add(Restrictions.like("r.province""%" + province + "%"));
            }
            if (StringUtils.isNotBlank(city)) {
                // 根据 市 进行模糊查询(注意:关联查询)
                detachedCriteria2.add(Restrictions.like("r.city""%" + city + "%"));
            }
            if (StringUtils.isNotBlank(district)) {
                // 根据 区 进行模糊查询(注意:关联查询)
                detachedCriteria2.add(Restrictions.like("r.district""%" + district + "%"));
            }
        }

        subareaService.pageQuery(pageBean);
        this.writePageBean2Json(pageBean, new String[] {"currentPage""pageSize""detachedCriteria""decidedzone""subareas"});
        return "none";
    }

9、分区数据导出功能

  • 导出Excel文件提供客户下载

第一步:为“导出”按钮绑定事件

    // 导出Excel文件,注意:文件下载必须是同步提交方式
    function doExport() {
        // get方式提交
        window.location.href = "${pageContext.request.contextPath}/subareaAction_exportXls.action";
    }

第二步:在SubareaAction中提供导出方法

    /**
     * 使用POI写入Excel文件,提供下载
     * @return
     * @throws IOException 
     */

    public String exportXls() throws IOException {
        List<Subarea> list = subareaService.findAll();

        // 在内存中创建一个Excel文件,通过输出流写到客户端提供下载
        HSSFWorkbook workbook = new HSSFWorkbook();
        // 创建一个sheet页
        HSSFSheet sheet = workbook.createSheet("分区数据");
        // 创建标题行
        HSSFRow headRow = sheet.createRow(0);
        // 设置标题行单元格的内容
        headRow.createCell(0).setCellValue("分区编号");
        headRow.createCell(1).setCellValue("区域编号");
        headRow.createCell(2).setCellValue("地址关键字");
        headRow.createCell(3).setCellValue("省市区");
        // 遍历list集合
        for (Subarea subarea : list) {
            HSSFRow dataRow = sheet.createRow(sheet.getLastRowNum() + 1);
            dataRow.createCell(0).setCellValue(subarea.getId());
            dataRow.createCell(1).setCellValue(subarea.getRegion().getId());
            dataRow.createCell(2).setCellValue(subarea.getAddresskey());

            Region region = subarea.getRegion();
            dataRow.createCell(3).setCellValue(region.getProvince() + region.getCity() + region.getDistrict());
        }

        String filename = "分区数据.xls";
        String agent = ServletActionContext.getRequest().getHeader("User-Agent"); // 浏览器类型
        filename = FileUtils.encodeDownloadFilename(filename, agent);

        // 一个流两个头
        ServletOutputStream out = ServletActionContext.getResponse().getOutputStream();
        String contentType = ServletActionContext.getServletContext().getMimeType(filename);
        ServletActionContext.getResponse().setContentType(contentType);
        ServletActionContext.getResponse().setHeader("content-disposition""attchment;filename=" + filename);
        workbook.write(out);

        return "none";
    }

浏览器界面效果图如下:

day52_BOS项目_04-LMLPHP
10-06 23:41