可配置化的excel導入

Excel導入在實際的開發(fā)中,經(jīng)常會用到鹅经,可是我們往往需要研究一番這個Excel表格的結(jié)構(gòu)才能夠著手寫Excel導入的代碼,而這份代碼有不能夠復用怎诫,常常導致一表一碼的情況瘾晃,為了提高開發(fā)效率,因此寫了些代碼和解決思路

大前提

每一份Excel表格都有唯一標示某列或某行業(yè)務含義的標示幻妓,例如列名蹦误,某單元格特定字符。

這是一份教學進度表

很明顯根據(jù)某些特定標示肉津,我是能夠知道哪個單元格是我想要的數(shù)據(jù)

解決思路

1.如何讓程序根據(jù)某一字段標識與導入實體類字段存在對應關(guān)系强胰,找出數(shù)據(jù)所在位置?
例如給出如下對應關(guān)系(參照上圖教學進度的妹沙,給出如下配置)

        List<ImportField> fields = new ArrayList<>();
        //公用字段
        fields.add(new ImportField("課程名稱","name"));
        fields.add(new ImportField("專業(yè)","major"));
        fields.add(new ImportField("年級","grade"));
        fields.add(new ImportField("班級","classes"));
        //多值字段
        fields.add(new ImportField("授課方式","type",true,1,0));
        fields.add(new ImportField("授課教師","teacher",true,1,0));
        fields.add(new ImportField("授課內(nèi)容","content",true,1,0));
        fields.add(new ImportField("上課地點","address",true,1,0));
        fields.add(new ImportField("周次","week",true,1,0));
        fields.add(new ImportField("星期","day",true,1,0));
        fields.add(new ImportField("節(jié)次","scope",true,1,0));

2.如何通過給定的映射關(guān)系遍歷Sheet和得到某一標識所在行和列偶洋?
首先我們需要定義一個導入配置,然后程序就是根據(jù)這一個導入配置去取數(shù)據(jù)

例如給出如下實體類

public class ImportField {
    //別名
    private String alias;
    //數(shù)據(jù)庫字段名稱或者實體類名稱
    private String name;
    //是否多個
    private boolean isMulti = false;
    //行偏移量
    private int xOffset=0;
    //列偏移量
    private int yOffset=0;
    //起始行
    private int row;
    //起始列
    private int col;
    //多值屬性記錄數(shù)
    private int multiCount = 0;
    //是否分析得到起始行列位置
    private boolean isComplete = false;

    public ImportField() {

     }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public boolean isMulti() {
        return isMulti;
    }

    public void setMulti(boolean multi) {
        isMulti = multi;
    }

    public int getxOffset() {
        return xOffset;
    }

    public void setxOffset(int xOffset) {
        this.xOffset = xOffset;
    }

    public int getyOffset() {
        return yOffset;
    }

    public void setyOffset(int yOffset) {
        this.yOffset = yOffset;
    }
    //計算所在列是需要加上行偏移量  
    public int getRow() {
        return row+xOffset;
    }

    public void setRow(int row) {
        this.row = row;
    }
    //計算所在列是需要加上列偏移量  
    public int getCol() {
        return col+yOffset;
    }

    public void setCol(int col) {
        this.col = col;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ImportField(String alias, String name, boolean isMulti, int xOffset, int yOffset) {
        this.alias = alias;
        this.name = name;
        this.isMulti = isMulti;
        this.xOffset = xOffset;
        this.yOffset = yOffset;
    }

    public ImportField(String alias, String name) {
        this.alias = alias;
        this.name = name;
    }

    public void setPosition(int row, int col){
        this.col = col;
        this.row = row;
    }

    public boolean isComplete() {
        return isComplete;
    }

    public void setComplete(boolean complete) {
        isComplete = complete;
    }

    public void getNextRow(){
        this.row ++;
    }

    public void addMultiCount(){
        this.multiCount++;
    }

    public int getMultiCount() {
        return multiCount;
    }

遍歷Excel得到標識位置所在行列

 //分析即將導入的文件
 /**
 * HashMap中包含
 *List<ImportField> fields
 *HSSFSheet
 */
private static HashMap<String,Object> analysisExcel(
InputStream inputStream, List<ImportField> fields)
 throws IOException,FormatException{
        HashMap<String,Object> analysisResult = new HashMap<>();
        int progress = 0;//掃描進度
        HSSFWorkbook hssfWorkbook = new HSSFWorkbook(inputStream);
        HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(0);//默認獲取第一個
        if (hssfSheet == null) {
            throw new FormatException("讀取模板文件為空距糖!");
        }
        for (int rowNum = 1; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
            HSSFRow hssfRow = hssfSheet.getRow(rowNum);
            if(hssfRow == null){
                continue;
            }
            for(int colNum = 0; colNum<hssfRow.getPhysicalNumberOfCells(); colNum++){
                HSSFCell cell = hssfRow.getCell(colNum);
                if(cell != null && StringUtils.isNoneBlank(cell.getStringCellValue())){
                    String val = cell.getStringCellValue().trim();
                    for(ImportField field : fields){
                        if(val.indexOf(field.getAlias()) != -1){
                            field.setPosition(cell.getRowIndex(),cell.getColumnIndex());
                            field.setComplete(true);
                            progress++;
                            if(progress == fields.size()){
                                //如果所需字段的位置都找到了玄窝,提前結(jié)束循環(huán)
                                analysisResult.put(FIELDS,fields);
                                analysisResult.put(SHEET,hssfSheet);
                                return analysisResult;
                            }
                        }
                    }

                }

            }
        }
        //如果沒有提前跳出遍歷,那么就是證明某個標識在Excel中沒找到
        String errorMsg = "";
        for (ImportField field : fields){
            if(!field.isComplete()){
                errorMsg = errorMsg+" "+field.getAlias();
            }
        }
        throw new FormatException("導入課程表格式錯誤悍引!不存在列"+errorMsg);
    }

3.讀取數(shù)據(jù)恩脂,返回指定實體類集合
通過以上的遍歷,可以得到含有行列位置的導入配置和一個Sheet對象

//導入excel
private static List<Map<String,String>> importExcel(HashMap<String,Object> analysisResult) 
throws FormatException{
        List<ImportField> fields = (List<ImportField>) analysisResult.get(FIELDS);
        HSSFSheet hssfSheet = (HSSFSheet) analysisResult.get(SHEET);

        if(fields == null || hssfSheet == null){
            throw new FormatException("分析導入文件異常");
        }
        //按行大小排序,因為是從第一行開始遍歷的讀取的趣斤,因此排序可以優(yōu)先讀取俩块,不用再倒回來讀取
        fields.sort(new Comparator<ImportField>() {
            @Override
            public int compare(ImportField o1, ImportField o2) {
                return o1.getRow()-o2.getRow();
            }
        });
        //獲取公共屬性和多值屬性
        HashMap<String,String> singleFields = new HashMap<>();
        HashMap<String,List<String>> multiFields = new HashMap<>();

        for (int rowNum = 1; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
            HSSFRow hssfRow = hssfSheet.getRow(rowNum);
            if(hssfRow == null){
                continue;
            }
            for(int colNum = 0; colNum<hssfRow.getPhysicalNumberOfCells(); colNum++){
                HSSFCell cell = hssfRow.getCell(colNum);
                if(cell == null){
                    continue;
                }
                String val =  cell.getStringCellValue();

                for(ImportField field : fields){
                    if (field.getRow() == rowNum && field.getCol() == colNum){
                        if(!field.isMulti()){
                            singleFields.put(field.getName(),val);
                        }else{
                            //excel表格在A4紙出現(xiàn)第二頁的情況下處理如下
                            if(val.indexOf(field.getAlias()) == -1 && StringUtils.isNoneBlank(val)){
                                if(!multiFields.containsKey(field.getName())){
                                    List<String> vals = new ArrayList<>();
                                    vals.add(val);
                                    multiFields.put(field.getName(),vals);
                                }else{
                                    multiFields.get(field.getName()).add(val);
                                }
                                field.addMultiCount();
                            }
                            field.getNextRow();//跳出循環(huán)后讀取下一行
                        }
                        break;
                    }
                }
            }
        }
        //至此我們得到了2個HashMap
        //公用屬性和多值屬性
        //那么每一個多值屬性的數(shù)量應該是相等的,否則這份Excel表格的據(jù)就不合格的浓领。
        int multiCount = 0;
        String errorMsg ="";
        String currentCol="";//選取的某一個多記錄屬性字段
        for(ImportField field : fields){
            if (field.isMulti()){
                if(multiCount == 0){
                    currentCol = field.getAlias();
                    multiCount = field.getMultiCount();
                }else {
                    if(field.getMultiCount() != multiCount){
                        errorMsg = errorMsg +","+field.getAlias()+"記錄數(shù)為:"+field.getMultiCount();
                    }
                }

            }
        }
        if(StringUtils.isNoneBlank(errorMsg)){
            throw new FormatException("導入課程表列["+errorMsg.substring(1)+"]與["+currentCol+"]記錄數(shù):"+multiCount+"不一致");
        }
      
        //將公用屬性放入多值屬性
        //例如
        //公用字段是
        //{“name”:"小明"}
        //多種字段是
        //{"phone":" [電話1玉凯,電話2,電話3]"}
        //結(jié)合之后變成
        //{[{“name”:"小明","phone":"[電話1]"},{“name”:"小明","phone":"[電話2]"},{“name”:"小明","phone":"[電話3]"}]}
        for(ImportField field : fields){
            if(!field.isMulti()){
                List<String> vals = new ArrayList<>();
                for(int i=0;i<multiCount;i++){
                    vals.add(singleFields.get(field.getName()));
                }
                multiFields.put(field.getName(),vals);
            }
        }
        List<Map<String,String>> list = new ArrayList<>();
        for(int i=0;i<multiCount;i++){
            HashMap<String,String> data = new HashMap<>();
            for(String key : multiFields.keySet()){
                List<String> vals = multiFields.get(key);
                data.put(key,vals.get(i));
            }
            list.add(data);
        }
        return list;
    }

4.通過上面的讀取可以得到List<HashMap<String,String>>,下面的問題就是HashMap轉(zhuǎn)實體類對象的問題了镊逝,我推薦一下這種方式

        <dependency>
            <groupId>net.sf.dozer</groupId>
            <artifactId>dozer</artifactId>
            <version>5.5.1</version>
        </dependency>

然后簡單封裝一下

public class BeanMapper {
    private static DozerBeanMapper dozer = new DozerBeanMapper();

    public BeanMapper() {
    }

    public static <T> T map(Object source, Class<T> destinationClass) {

        return dozer.map(source, destinationClass);
    }

    public static <T> List<T> mapList(Collection sourceList, Class<T> destinationClass) {
        ArrayList destinationList = Lists.newArrayList();
        Iterator i$ = sourceList.iterator();

        while(i$.hasNext()) {
            Object sourceObject = i$.next();
            Object destinationObject = dozer.map(sourceObject, destinationClass);
            destinationList.add(destinationObject);
        }
        return destinationList;
    }

    public static void copy(Object source, Object destinationObject) {
        dozer.map(source, destinationObject);
    }
}

然后一句話轉(zhuǎn)完
List<T> list = BeanMapper.mapList(data,target);

總結(jié)

第一次用markdown寫文章壮啊,感覺倍爽,代碼應該還有不完善的地方撑蒜,其實最好的方式是自定義一個注解歹啼,然后加上校驗,可是由于知識水平的限制座菠,暫時沒有什么進展狸眼。
完整的代碼
https://github.com/Mygraduate/Supervisor_Java.git

代碼所在位置

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浴滴,隨后出現(xiàn)的幾起案子拓萌,更是在濱河造成了極大的恐慌,老刑警劉巖升略,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件微王,死亡現(xiàn)場離奇詭異屡限,居然都是意外死亡,警方通過查閱死者的電腦和手機炕倘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門钧大,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人罩旋,你說我怎么就攤上這事啊央。” “怎么了涨醋?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵瓜饥,是天一觀的道長。 經(jīng)常有香客問我浴骂,道長乓土,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任靠闭,我火速辦了婚禮帐我,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘愧膀。我一直安慰自己拦键,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布檩淋。 她就那樣靜靜地躺著芬为,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蟀悦。 梳的紋絲不亂的頭發(fā)上媚朦,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音日戈,去河邊找鬼询张。 笑死,一個胖子當著我的面吹牛浙炼,可吹牛的內(nèi)容都是我干的份氧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼弯屈,長吁一口氣:“原來是場噩夢啊……” “哼蜗帜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起资厉,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤厅缺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湘捎,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡诀豁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了消痛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片且叁。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖秩伞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欺矫,我是刑警寧澤纱新,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站穆趴,受9級特大地震影響脸爱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜未妹,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一簿废、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧络它,春花似錦族檬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至点楼,卻和暖如春扫尖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掠廓。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工换怖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蟀瞧。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓沉颂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親黄橘。 傳聞我的和親對象是個殘疾皇子兆览,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)塞关,斷路器抬探,智...
    卡卡羅2017閱讀 134,701評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,288評論 25 707
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,444評論 0 17
  • 今早同學在群里說剛下完雨空氣很好线梗,趕緊起床去吸兩口,玩笑話怠益!也是當前的真實寫照仪搔,記得小時候的環(huán)境,有霧的天氣實屬...
    小泥壺閱讀 159評論 2 2
  • 剛剛夢到和你聊微信蜻牢, 你說烤咧,你快要結(jié)婚了。 未婚妻是我認識的學姐抢呆, 我打破腦袋想了想煮嫌, 就是那個, 發(fā)型像魯豫抱虐, ...
    造作生生閱讀 376評論 7 2