Excel導入導出

採用註解方式的導入導出,能讓你在項目中更便捷的使用java

1、安裝依賴

<dependency>
    <groupId>cn.gjing</groupId>
    <artifactId>tools-excel</artifactId>
    <version>1.1.4</version>
</dependency>

2、註解說明

一、@Excel

實體上使用,聲明Excel與該實體存在映射,註解參數以下git

參數 描述
value Excel文件名,優先級於方法傳入
type Excel文檔類型,默認XLS
style 導出的Excel樣式

二、@ExcelField

字段上使用,聲明Excel的列表頭會與該字段映射,註解參數以下github

參數 描述
value 列表頭名字
pattern 如何字段是時間類型的,且須要格式轉換,那麼則必定要設置
width 這個列表頭單元格的寬度,默認20 * 256,建議設置256的倍數

三、@DateValid

時間校驗註解,使用在字段上,代表在Excel文件中這個列表頭下指定行數的單元格會添加時間的校驗,XLSX類型文檔不支持,註解參數以下數組

參數 描述
validClass 校驗器Class
boxLastRow 數據校驗最多校驗多少行,默認是正文第一行
pattern 校驗的時間格式,默認yyyy-MM-dd
operatorType 操做類型,默認OperatorType.BETWEEN
expr1 表達式1,默認1970-01-01
expr2 表達式2,默認2999-01-01
showErrorBox 是否彈出錯誤框,默認true
showPromptBox 是否當即彈出,默認true
rank 提示框級別,默認Rank.WARING警告級別
errorTitle 錯誤框標題
errorContent 詳細錯誤內容

四、@ExplicitValid

下拉框選值,使用在字段上,代表在Excel文件中這個列表頭下指定行數的單元格會添加下拉框選項,註解參數以下app

參數 描述
validClass 校驗器Class
combobox 範圍值,數組類型
boxLastRow 數據校驗最多校驗多少行,默認是該列表頭下的正文第一行
showErrorBox 是否彈出錯誤框,默認true
showPromptBox 是否當即彈出,默認true
rank 提示框級別,默認Rank.WARING警告級別
errorTitle 錯誤框標題
errorContent 詳細錯誤內容

五、@NumericValid

數據類型校驗,使用在字段上,代表在Excel文件中這個列表頭下指定行數的單元格會添加數據類型的校驗,註解參數以下ide

參數 描述
validClass 校驗器Class
boxLastRow 數據校驗最多校驗多少行,默認是該列表頭下的正文第一行
operatorType 操做類型,默認OperatorType.GREATER_OR_EQUAL
validType 校驗類型,默認ValidType.INTEGER
expr1 表達式1,在表達式2前面,默認0
expr2 表達式2,在操做類型爲BETWEENNOT_BETWEEN狀況下必填
showErrorBox 是否彈出錯誤框,默認true
showPromptBox 是否當即彈出,默認true
rank 提示框級別,默認Rank.WARING警告級別
errorTitle 錯誤框標題
errorContent 詳細錯誤內容

六、@ExcelEnumConvert

枚舉轉換器,使用在枚舉類型的字段上,註解參數以下this

參數 描述
convert 實現了EnumConvert接口的類Class

3、使用說明

一、定義Excel對應的實體

只須要在實體類的字段上加上對應註解便可excel

@Data //這是lombok的註解,幫完成get、set等方法
@Excel("用戶列表")
public class User {
    @ExcelField("用戶Id")
    private Long id;

    @ExcelField("用戶名")
    private String userName;

    @ExcelField(value = "建立時間",pattern = "yyyy-MM-dd")
    private Date createTime;
}

二、Excel導出

若是隻是導出Excel模板,只須要在write()方法中傳入null便可code

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userService;

    @GetMapping("/user")
    @ApiOperation(value = "導出用戶")
    public void exportUser(HttpServletResponse response) {
        List<User> users = userService.userList();
        ExcelFactory.createWriter(User.class, response)
                .write(users)
                .flush();
    }
}

排除某些帶了@ExcelField註解的字段,只需在createWriter()方法中ignores參數指定你要忽略的字段,字段名要和實體一致xml

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userService;

    @GetMapping("/user")
    @ApiOperation(value = "導出用戶")
    public void exportUser(HttpServletResponse response) {
        List<User> users = userService.userList();
        ExcelFactory.createWriter(User.class, response,"id","createTime")
                .write(users)
                .flush();
    }
}

指定sheet導出,不指定的話會導出到默認名稱的sheet中

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userService;

    @GetMapping("/user")
    @ApiOperation(value = "導出用戶")
    public void exportUser(HttpServletResponse response) {
        List<User> users = userService.userList();
        ExcelFactory.createWriter(User.class, response)
                .write(users,"用戶sheet")
                .flush();
    }
}

若是須要將不一樣規則數據導入到不一樣的sheet中或者是在同一個sheet中分層,能夠屢次調用write()方法,若是不指定sheet的名稱則會寫入到默認名稱的sheet

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userService;

    @GetMapping("/user")
    @ApiOperation(value = "導出用戶")
    public void exportUser(HttpServletResponse response) {
        List<User> users = userService.userList();
        List<User> users2 = userService.userList();
        ExcelFactory.createWriter(User.class, response)
                .write(users)
                .write(users2,"sheet2")
                .flush();   
    }
}

指定大標題,該操做要在每次調用write()以前,僅對本次調用有效

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userService;

    @GetMapping("/user")
    @ApiOperation(value = "導出用戶")
    public void exportUser(HttpServletResponse response) {
        List<User> users = userService.userList();
        ExcelFactory.createWriter(User.class, response)
                //指定大標題佔用的行數和內容
                .bigTitle(() -> new BigTitle(3,"我是大標題"))
                .write(users)
                .flush();
    }
}

重置當前導出的Excel與實體的關聯,該操做要在調用write()以前

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userService;
    @Resource
    private OrderService orderService;

    @GetMapping("/user")
    @ApiOperation(value = "導出用戶")
    public void exportUser(HttpServletResponse response) {
        List<User> users = userService.userList();
        List<Order> orderList = orderService.orderList();
        ExcelFactory.createWriter(User.class, response)
                .write(users)
                //重置,若是要忽略某些字段不導出,在該方法 ignores 參數指定便可
                //注意點和上文介紹的導出一致
                .resetExcelClass(Order.class)
                .write(orderList)
                .flush();
    }
}

在整個鏈式調用完畢後,必定要在最後調用flush()方法,不然數據不會寫入到Excel文件中

三、Excel導入

經過get()方法獲取導入的數據,該方法爲最終操做,整個導入會結束

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userSerivce;

    @PostMapping("/user_import")
    @ApiOperation("導入")
    public ResponseEntity userImport(MultipartFile file) throws IOException {
        List<User> users = ExcelFactory.createReader(file.getInputStream(), User.class)
                //不指定sheet名稱,會去讀默認名稱的sheet
                .read()
                .get();
        userService.saveUserList(users);
        return ResponseEntity.ok("導入成功");
    }
}

不少時候咱們數據太多而寫在了不一樣的sheet中,這時可能須要分開讀取每一個sheet,使用上面的get()方法獲取導入的結果確定是不行的,這時能夠經過建立監聽者去監聽每次導入的結果並執行對應的操做

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userSerivce;

    @PostMapping("/user_import")
    @ApiOperation("導入")
    public ResponseEntity userImport(MultipartFile file) throws IOException {
        ExcelFactory.createReader(file.getInputStream(), User.class)
                .read()
                .listener(e -> userService.saveUserList(e))
                .read("sheet2")
                .listener(e -> userService.saveUserList(e));
        return ResponseEntity.ok("導入成功");
    }
}

導入的Excel存在大標題的話,須要指定列表頭開始的下標,對應Excel文件列表頭那一行左邊的數字序號,該操做要在調用read()前進行

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userSerivce;

    @PostMapping("/user_import")
    @ApiOperation("導入")
    public ResponseEntity userImport(MultipartFile file) throws IOException {
        List<User> users = ExcelFactory.createReader(file.getInputStream(), User.class)
                .headerIndex(2)
                .read()
                .get();
        userService.saveUserList(users);
        return ResponseEntity.ok("導入成功");
    }
}

限制讀取行數,該行數爲你要截止讀取到Excel文件的哪一行(包括本行),該操做要在調用read()前進行

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userSerivce;

    @PostMapping("/user_import")
    @ApiOperation("導入")
    public ResponseEntity userImport(MultipartFile file) throws IOException {
        List<User> users = ExcelFactory.createReader(file.getInputStream(), User.class)
                .endIndex(5)
                .read()
                .get();
        userService.saveUserList(users);
        return ResponseEntity.ok("導入成功");
    }
}

四、枚舉轉換器

實體存在枚舉類型的字段,在導入導出時須要指定枚舉轉換器,實現EnumConvert接口並重寫其中方法,導出會顯示你指定的內容,導入會轉爲指定的枚舉

/**
 * 性別
 *
 * @author Gjing
 **/
@Getter
public enum GenderEnum {
    MAN(1, "男"),
    WOMAN(2, "女");
    private int type;
    private String desc;

    GenderEnum(int type, String desc) {
        this.type = type;
        this.desc = desc;
    }

    /**
     * excel導入的時候轉換成枚舉用的
     *
     * @param s desc
     * @return GenderEnum
     */
    public static GenderEnum of(String s) {
        for (GenderEnum genderEnum : GenderEnum.values()) {
            if (genderEnum.getDesc().equals(s)) {
                return genderEnum;
            }
        }
        throw new NullPointerException("沒找到你的枚舉");
    }
    /**
     * 自定義枚舉轉換器
     */
    public static class MyExcelEnumConvert implements EnumConvert<GenderEnum, String> {
        @Override
        public GenderEnum toEntityAttribute(String s) {
            return of(s);
        }

        @Override
        public String toExcelAttribute(GenderEnum genderEnum) {
            return genderEnum.desc;
        }
    }
}

字段使用註解並指定你的轉換類

@Excel("用戶列表")
public class User {

    @ExcelField("性別")
    @ExcelEnumConvert(convert = GenderEnum.MyExcelEnumConvert.class)
    private GenderEnum genderEnum;
}

五、數據校驗

在導出模板時,有時候須要讓用戶填寫指定規則的內容,這時能夠進行數據格式校驗

@Excel("用戶列表")
public class User {
    @ExcelField("用戶Id")
    private Long id;

    @ExcelField("用戶名")
    //在當前列表頭下方的十行單元格進行數據校驗,必須輸入長度大於等於3的文本
    @NumericValid(validationType = TEXT_LENGTH, expr1 = "3", operatorType = GREATER_OR_EQUAL,boxLastRow = 10)
    private String userName;

    @ExcelField("性別")
    //指定該列表頭下的第一行單元格只能選取這兩個值
    @ExplicitValid(combobox = {"男","女"})
    @ExcelEnumConvert(convert = GenderEnum.MyExcelEnumConvert.class)
    private GenderEnum genderEnum;

    @ExcelField(value = "建立時間",pattern = "yyyy-MM-dd")
    //在當前列表頭下方的第一行單元格中,時間只能輸入在2019-10-11至2019-10-13範圍的時間
    @DateValid(expr1 = "2019-10-11",expr2 = "2019-10-13")
    private Date createTime;
}

4、拓展

以前講解的使用的一些功能都是採起默認實現的,有時候有點本身的想法,也能夠進行自定義其中的功能

一、自定義Excel樣式

實現ExcelStyle接口,裏面有設置大標題、正文、列表頭樣式的三個方法,選擇你要自定義的方法進行重寫便可,如下演示了自定義大標題樣式

/**
 * 本身定義的樣式
 * @author Gjing
 **/
public class MyExcelStyle implements ExcelStyle {

    @Override
    public CellStyle setTitleStyle(CellStyle cellStyle) {
        cellStyle.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.index);
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setWrapText(true);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        return cellStyle;
    }
}

在對應實體@Excel註解指定你自定義的樣式類

@Excel(value = "用戶列表",style = MyExcelStyle.class)
public class User {
    @ExcelField("用戶Id")
    private Long id;

    @ExcelField("用戶名")
    private String userName;

    @ExcelField(value = "建立時間",pattern = "yyyy-MM-dd")
    private Date createTime;
}

二、自定義校驗註解的邏輯

經過上文的介紹,知道一共有三種校驗的註解,這三個註解分別均可以自定義實現邏輯,實現ExcelValidation接口,並選擇你須要自定義哪些註解的邏輯

/**
 * 本身實現校驗邏輯
 * @author Gjing
 **/
public class MyValid implements ExcelValidation {
    @Override
    public void valid(DateValid dateValid, Sheet sheet, int firstRow, int firstCol, int lastCol) {

    }

    @Override
    public void valid(NumericValid numericValid, Sheet sheet, int firstRow, int firstCol, int lastCol) {

    }

    @Override
    public void valid(ExplicitValid explicitValid, Workbook workbook, Sheet sheet, int firstRow, int firstCol, int lastCol) {

    }
}

修改你實體類中對應註解的默認處理類validClass,沒有指定的註解仍是會走默認處理

@Excel("用戶列表")
public class User {
    @ExcelField("用戶Id")
    private Long id;

    @ExcelField("用戶名")
    @NumericValid(validClass = MyValid.class,validationType = TEXT_LENGTH, expr1 = "3", operatorType = GREATER_OR_EQUAL,boxLastRow = 10)
    private String userName;

    @ExcelField(value = "建立時間",pattern = "yyyy-MM-dd")
    //在當前列表頭下方的第一行單元格中,時間只能輸入在2019-10-11至2019-10-13範圍的時間
    @DateValid(expr1 = "2019-10-11",expr2 = "2019-10-13")
    private Date createTime;
}

三、自定義導出處理器

自定義導出的核心處理器,需實現ExcelWriterResolver接口並重寫其中的方法

/**
 * 自定義導出處理器
 * @author Gjing
 **/
public class MyWriteResolver implements ExcelWriterResolver {
    @Override
    public void write(List<?> list, Workbook workbook, String s, List<Field> list1, MetaStyle metaStyle, BigTitle bigTitle) {

    }

    @Override
    public void flush(HttpServletResponse httpServletResponse, String s) {

    }
}

在導出時重置處理器,該方法要在你全部鏈式操做以前調用

/**
 * @author Gjing
 **/
@RestController
@Api(tags = "用戶")
public class UserController {
    @Resource
    private UserService userService;

    @GetMapping("/user")
    @ApiOperation(value = "使用自定義的導出處理器導出用戶模板")
    public void exportUser(HttpServletResponse response) {
        ExcelFactory.createWriter(User.class, response)
                //重置處理器
                .resetResolver(MyWriteResolver::new)
                .write(null)
                .flush();
    }
}

四、自定義導入處理器

自定義導入處理器,需實現ExcelReaderResolver接口並重寫其中的方法

/**
 * 自定義導入處理器
 * @author Gjing
 **/
public class MyReaderResolver implements ExcelReaderResolver {
    @Override
    public void read(InputStream inputStream, Class<?> aClass, Listener<List<Object>> listener, int i, int i1, String s) {

    }
}

在導出的方法中重置處理器,該操做要在全部鏈式操做前調用

/**
 * @author Gjing
 **/
@RestController
public class UserController {
    @Resource
    private UserService userSerivce;

    @PostMapping("/user_import")
    @ApiOperation("導入")
    public ResponseEntity userImport(MultipartFile file) throws IOException {
        ExcelFactory.createReader(file.getInputStream(), User.class)
                .resetResolver(MyReaderResolver::new)
                .read()
                .listener(e -> userService.saveUserList(e))
        return ResponseEntity.ok("導入成功");
    }
}

源代碼地址:tools-excel
Demo地址:excel-demo