├── .gitignore ├── src └── main │ └── java │ └── com │ └── wuwenze │ └── poi │ ├── exception │ ├── ExcelKitEncounterNoNeedXmlException.java │ ├── ExcelKitReadConverterException.java │ ├── ExcelKitWriteConverterException.java │ ├── ExcelKitXmlAnalyzeException.java │ ├── ExcelKitAnnotationAnalyzeException.java │ ├── ExcelKitConfigFileNotFoundException.java │ ├── ExcelKitConfigAnalyzeFailureException.java │ └── ExcelKitRuntimeException.java │ ├── config │ └── Options.java │ ├── validator │ ├── Validator.java │ ├── EmailValidator.java │ └── MobileValidator.java │ ├── handler │ └── ExcelReadHandler.java │ ├── convert │ ├── ReadConverter.java │ └── WriteConverter.java │ ├── annotation │ ├── Excel.java │ └── ExcelField.java │ ├── pojo │ ├── ExcelErrorField.java │ ├── ExcelMapping.java │ └── ExcelProperty.java │ ├── util │ ├── PathUtil.java │ ├── RegexUtil.java │ ├── Const.java │ ├── DateUtil.java │ ├── BeanUtil.java │ ├── ValidatorUtil.java │ └── POIUtil.java │ ├── ExcelKit.java │ ├── xlsx │ ├── ExcelXlsxWriter.java │ └── ExcelXlsxReader.java │ └── factory │ └── ExcelMappingFactory.java ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | #eclipse files 2 | .classpath 3 | .project 4 | 5 | /bin/ 6 | /target/ 7 | /.settings/ 8 | 9 | #idea files 10 | target/ 11 | *.iml 12 | *.ipr 13 | *.iws 14 | .idea/ -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/exception/ExcelKitEncounterNoNeedXmlException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.exception; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public class ExcelKitEncounterNoNeedXmlException extends ExcelKitRuntimeException { 22 | 23 | private static final long serialVersionUID = 4096057792690617204L; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/config/Options.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.config; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public interface Options { 22 | 23 | /** 24 | * 指定excel单元格的下拉框数据源, 用于规范生成Excel模板的数据校验 25 | * 26 | * @return ["option1", "option2"] 27 | */ 28 | String[] get(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/validator/Validator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.validator; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public interface Validator { 22 | 23 | /** 24 | * 验证单元格的值, 若验证失败, 请返回错误消息. 25 | * 26 | * @param value 当前单元格的值 27 | * @return null or errorMessage 28 | */ 29 | String valid(Object value); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/exception/ExcelKitReadConverterException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.exception; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public class ExcelKitReadConverterException extends ExcelKitRuntimeException { 22 | 23 | private static final long serialVersionUID = 2832313288988433562L; 24 | 25 | public ExcelKitReadConverterException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/exception/ExcelKitWriteConverterException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.exception; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public class ExcelKitWriteConverterException extends ExcelKitRuntimeException { 22 | 23 | private static final long serialVersionUID = 7989007279778794434L; 24 | 25 | public ExcelKitWriteConverterException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/exception/ExcelKitXmlAnalyzeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.exception; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public class ExcelKitXmlAnalyzeException extends ExcelKitRuntimeException { 22 | 23 | private static final long serialVersionUID = 2433077029852196265L; 24 | 25 | public ExcelKitXmlAnalyzeException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/handler/ExcelReadHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.handler; 17 | 18 | import com.wuwenze.poi.pojo.ExcelErrorField; 19 | import java.util.List; 20 | 21 | /** 22 | * @author wuwenze 23 | */ 24 | public interface ExcelReadHandler { 25 | 26 | void onSuccess(int sheetIndex, int rowIndex, T entity); 27 | 28 | void onError(int sheetIndex, int rowIndex, List errorFields); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/exception/ExcelKitAnnotationAnalyzeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.exception; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public class ExcelKitAnnotationAnalyzeException extends ExcelKitRuntimeException { 22 | 23 | private static final long serialVersionUID = -7412071094510468644L; 24 | 25 | public ExcelKitAnnotationAnalyzeException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/exception/ExcelKitConfigFileNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.exception; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public class ExcelKitConfigFileNotFoundException extends ExcelKitRuntimeException { 22 | 23 | private static final long serialVersionUID = -5061679627805728661L; 24 | 25 | public ExcelKitConfigFileNotFoundException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/convert/ReadConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.convert; 17 | 18 | import com.wuwenze.poi.exception.ExcelKitReadConverterException; 19 | 20 | /** 21 | * @author wuwenze 22 | */ 23 | public interface ReadConverter { 24 | 25 | /** 26 | * 将value转换成指定的值, 读取时映射到实体中 27 | * 28 | * @param value 当前单元格的值 29 | * @return 转换后的值 30 | */ 31 | Object convert(Object value) throws ExcelKitReadConverterException; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/convert/WriteConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.convert; 17 | 18 | import com.wuwenze.poi.exception.ExcelKitWriteConverterException; 19 | 20 | /** 21 | * @author wuwenze 22 | */ 23 | public interface WriteConverter { 24 | 25 | /** 26 | * 将value转换成指定的值, 用于写入excel表格中 27 | * 28 | * @param value 当前单元格的值 29 | * @return 转换后的值 30 | */ 31 | String convert(Object value) throws ExcelKitWriteConverterException; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/annotation/Excel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.annotation; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | /** 24 | * @author wuwenze 25 | */ 26 | @Retention(RetentionPolicy.RUNTIME) 27 | @Target(ElementType.TYPE) 28 | public @interface Excel { 29 | 30 | String value() default ""; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/validator/EmailValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.validator; 17 | 18 | import com.wuwenze.poi.util.ValidatorUtil; 19 | 20 | /** 21 | * @author wuwenze 22 | */ 23 | public class EmailValidator implements Validator { 24 | 25 | @Override 26 | public String valid(Object value) { 27 | String valueString = (String) value; 28 | return ValidatorUtil.isEmail(valueString) ? null : "[" + valueString + "]不是正确的EMail."; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/validator/MobileValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.validator; 17 | 18 | import com.wuwenze.poi.util.ValidatorUtil; 19 | 20 | /** 21 | * @author wuwenze 22 | */ 23 | public class MobileValidator implements Validator { 24 | 25 | @Override 26 | public String valid(Object value) { 27 | String valueString = (String) value; 28 | return ValidatorUtil.isMobile(valueString) ? null : "[" + valueString + "]不是正确的手机号码."; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/exception/ExcelKitConfigAnalyzeFailureException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.exception; 17 | 18 | /** 19 | * @author wuwenze 20 | */ 21 | public class ExcelKitConfigAnalyzeFailureException extends ExcelKitRuntimeException { 22 | 23 | private static final long serialVersionUID = -6037535579660494996L; 24 | 25 | public ExcelKitConfigAnalyzeFailureException(String message) { 26 | super(message); 27 | } 28 | 29 | public ExcelKitConfigAnalyzeFailureException(Throwable cause) { 30 | super(cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/pojo/ExcelErrorField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.pojo; 17 | 18 | import lombok.AllArgsConstructor; 19 | import lombok.Builder; 20 | import lombok.Data; 21 | import lombok.NoArgsConstructor; 22 | import lombok.ToString; 23 | 24 | /** 25 | * @author wuwenze 26 | */ 27 | @Data 28 | @Builder 29 | @ToString 30 | @NoArgsConstructor 31 | @AllArgsConstructor 32 | public class ExcelErrorField { 33 | 34 | private Integer cellIndex; 35 | private String name; 36 | private String column; 37 | private String errorMessage; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/pojo/ExcelMapping.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.pojo; 17 | 18 | import java.util.List; 19 | import lombok.AllArgsConstructor; 20 | import lombok.Builder; 21 | import lombok.Data; 22 | import lombok.NoArgsConstructor; 23 | import lombok.ToString; 24 | 25 | /** 26 | * @author wuwenze 27 | */ 28 | @Data 29 | @Builder 30 | @ToString 31 | @NoArgsConstructor 32 | @AllArgsConstructor 33 | public class ExcelMapping { 34 | 35 | private String name; 36 | private List propertyList; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/exception/ExcelKitRuntimeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.wuwenze.poi.exception; 16 | 17 | /** 18 | * @author wuwenze 19 | */ 20 | public class ExcelKitRuntimeException extends RuntimeException { 21 | 22 | private static final long serialVersionUID = 1059413765208343152L; 23 | 24 | public ExcelKitRuntimeException() { 25 | } 26 | 27 | public ExcelKitRuntimeException(String message) { 28 | super(message); 29 | } 30 | 31 | public ExcelKitRuntimeException(String message, Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | public ExcelKitRuntimeException(Throwable cause) { 36 | super(cause); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/util/PathUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.util; 17 | 18 | import java.io.File; 19 | import lombok.AccessLevel; 20 | import lombok.NoArgsConstructor; 21 | 22 | /** 23 | * @author wuwenze 24 | */ 25 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 26 | public class PathUtil { 27 | 28 | public static String getClasspath() { 29 | return PathUtil.class.getResource("/").getPath(); 30 | } 31 | 32 | public static String getFilePathByClasspath(String name) { 33 | return PathUtil.getClasspath() + name; 34 | } 35 | 36 | public static File getFileByClasspath(String name) { 37 | return new File(PathUtil.getFilePathByClasspath(name)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/pojo/ExcelProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.pojo; 17 | 18 | import com.wuwenze.poi.config.Options; 19 | import com.wuwenze.poi.convert.ReadConverter; 20 | import com.wuwenze.poi.convert.WriteConverter; 21 | import com.wuwenze.poi.validator.Validator; 22 | import lombok.AllArgsConstructor; 23 | import lombok.Builder; 24 | import lombok.Data; 25 | import lombok.NoArgsConstructor; 26 | import lombok.ToString; 27 | 28 | /** 29 | * @author wuwenze 30 | */ 31 | @Data 32 | @Builder 33 | @ToString 34 | @NoArgsConstructor 35 | @AllArgsConstructor 36 | public class ExcelProperty { 37 | 38 | private String name; 39 | private String column; 40 | private Boolean required; 41 | private Short width; 42 | private String comment; 43 | private Integer maxLength; 44 | private String dateFormat; 45 | private Options options; 46 | private String writeConverterExp; 47 | private WriteConverter writeConverter; 48 | private String readConverterExp; 49 | private ReadConverter readConverter; 50 | private String regularExp; 51 | private String regularExpMessage; 52 | private Validator validator; 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/util/RegexUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.util; 17 | 18 | import com.google.common.cache.CacheBuilder; 19 | import com.google.common.cache.CacheLoader; 20 | import com.google.common.cache.LoadingCache; 21 | import java.util.concurrent.ExecutionException; 22 | import java.util.regex.Pattern; 23 | import lombok.AccessLevel; 24 | import lombok.NoArgsConstructor; 25 | 26 | /** 27 | * @author wuwenze 28 | */ 29 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 30 | public class RegexUtil { 31 | 32 | private final static LoadingCache mRegexPatternLoadingCache = 33 | CacheBuilder.newBuilder() 34 | .maximumSize(5) 35 | .build(new CacheLoader() { 36 | 37 | @Override 38 | public Pattern load(String pattern) { 39 | return Pattern.compile(pattern); 40 | } 41 | }); 42 | 43 | public static Boolean isMatches(String pattern, Object value) { 44 | try { 45 | String valueString = (String) value; 46 | return RegexUtil.mRegexPatternLoadingCache.get(pattern).matcher(valueString).matches(); 47 | } catch (ExecutionException e) { 48 | e.printStackTrace(); 49 | } 50 | return false; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/util/Const.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.util; 17 | 18 | import lombok.AccessLevel; 19 | import lombok.NoArgsConstructor; 20 | 21 | /** 22 | * @author wuwenze 23 | */ 24 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 25 | public final class Const { 26 | 27 | public static final String ENCODING = "UTF-8"; 28 | public static final String XLSX_SUFFIX = ".xlsx"; 29 | public static final String XLSX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 30 | public static final String XLSX_HEADER_KEY = "Content-disposition"; 31 | public static final String XLSX_HEADER_VALUE_TEMPLATE = "attachment; filename=%s"; 32 | public static final String XLSX_DEFAULT_EMPTY_CELL_VALUE = "$EMPTY_CELL$"; 33 | public static final Integer XLSX_DEFAULT_BEGIN_READ_ROW_INDEX = 1; 34 | public static final String SAX_PARSER_CLASS = "org.apache.xerces.parsers.SAXParser"; 35 | public static final String SAX_C_ELEMENT = "c"; 36 | public static final String SAX_R_ATTR = "r"; 37 | public static final String SAX_T_ELEMENT = "t"; 38 | public static final String SAX_S_ATTR_VALUE = "s"; 39 | public static final String SAX_RID_PREFIX = "rId"; 40 | public static final String SAX_ROW_ELEMENT = "row"; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/util/DateUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.util; 17 | 18 | import com.google.common.cache.CacheBuilder; 19 | import com.google.common.cache.CacheLoader; 20 | import com.google.common.cache.LoadingCache; 21 | import java.text.SimpleDateFormat; 22 | import java.util.Date; 23 | import java.util.Locale; 24 | import java.util.concurrent.ExecutionException; 25 | import lombok.AccessLevel; 26 | import lombok.NoArgsConstructor; 27 | 28 | 29 | /** 30 | * @author wuwenze 31 | */ 32 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 33 | public class DateUtil { 34 | 35 | public final static SimpleDateFormat ENGLISH_LOCAL_DF = new SimpleDateFormat( 36 | "EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH); 37 | private final static LoadingCache mDateFormatLoadingCache = 38 | CacheBuilder.newBuilder() 39 | .maximumSize(5) 40 | .build(new CacheLoader() { 41 | 42 | @Override 43 | public SimpleDateFormat load(String pattern) { 44 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); 45 | simpleDateFormat.setLenient(true); 46 | return simpleDateFormat; 47 | } 48 | }); 49 | 50 | public static Date parse(String pattern, Object value) throws Exception { 51 | String valueString = (String) value; 52 | return DateUtil.mDateFormatLoadingCache.get(pattern).parse(valueString); 53 | } 54 | 55 | public static String format(String pattern, Date value) { 56 | try { 57 | return DateUtil.mDateFormatLoadingCache.get(pattern).format(value); 58 | } catch (ExecutionException e) { 59 | e.printStackTrace(); 60 | } 61 | return value.toString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/util/BeanUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.util; 17 | 18 | import lombok.AccessLevel; 19 | import lombok.NoArgsConstructor; 20 | import org.apache.commons.beanutils.ConvertUtils; 21 | import org.apache.commons.beanutils.PropertyUtilsBean; 22 | import org.apache.commons.beanutils.converters.DateConverter; 23 | 24 | import java.lang.reflect.InvocationTargetException; 25 | 26 | /** 27 | * @author wuwenze 28 | */ 29 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 30 | public class BeanUtil extends org.apache.commons.beanutils.BeanUtils { 31 | 32 | public static void setComplexProperty(Object bean, String name, Object value) 33 | throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { 34 | //修复日期为空时bug 35 | ConvertUtils.register(new DateConverter(null), java.util.Date.class); 36 | if (!name.contains(".")) { 37 | BeanUtil.setProperty(bean, name, value); 38 | return; 39 | } 40 | PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); 41 | String[] propertyLevels = name.split("\\."); 42 | String parentPropertyName = ""; 43 | for (int i = 0; i < propertyLevels.length; i++) { 44 | String p = propertyLevels[i]; 45 | parentPropertyName = (parentPropertyName.equals("") ? p : parentPropertyName + "." + p); 46 | if (i < (propertyLevels.length - 1) && 47 | propertyUtilsBean.getProperty(bean, parentPropertyName) != null) { 48 | continue; 49 | } 50 | Class parentClass = propertyUtilsBean.getPropertyType(bean, parentPropertyName); 51 | if (i < (propertyLevels.length - 1)) { 52 | BeanUtil.setProperty(bean, 53 | parentPropertyName, parentClass.getConstructor().newInstance()); 54 | } else { 55 | BeanUtil.setProperty(bean, parentPropertyName, 56 | parentClass.getConstructor(String.class).newInstance(value)); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/util/ValidatorUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.util; 17 | 18 | import java.util.regex.Pattern; 19 | import lombok.AccessLevel; 20 | import lombok.NoArgsConstructor; 21 | 22 | /** 23 | * @author wuwenze 24 | */ 25 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 26 | public class ValidatorUtil { 27 | 28 | private static final Pattern PATTERN_NUMERIC = Pattern.compile("^(-?\\d+)(\\.\\d+)?$"); 29 | private static final Pattern PATTERN_EMAIL = Pattern 30 | .compile("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$"); 31 | private static final Pattern PATTERN_MOBILE_PHONE = Pattern.compile("^(1)\\d{10}$"); 32 | 33 | public static boolean match(String value, String regularExp) { 34 | return ValidatorUtil.isEmpty(value) ? false 35 | : Pattern.compile(regularExp).matcher(value).matches(); 36 | } 37 | 38 | /** 39 | * 判断是否为浮点数或者整数 40 | * 41 | * @param value 字符串 42 | * @return true Or false 43 | */ 44 | public static boolean isNumeric(String value) { 45 | return ValidatorUtil.isEmpty(value) ? false 46 | : ValidatorUtil.PATTERN_NUMERIC.matcher(value).matches(); 47 | } 48 | 49 | /** 50 | * 判断是否为正确的邮件格式 51 | * 52 | * @param value 字符串 53 | * @return true Or false 54 | */ 55 | public static boolean isEmail(String value) { 56 | return ValidatorUtil.isEmpty(value) ? false 57 | : ValidatorUtil.PATTERN_EMAIL.matcher(value).matches(); 58 | } 59 | 60 | /** 61 | * 判断字符串是否为合法手机号 62 | * 63 | * @param value 字符串 64 | * @return true Or false 65 | */ 66 | public static boolean isMobile(String value) { 67 | return ValidatorUtil.isEmpty(value) ? false 68 | : ValidatorUtil.PATTERN_MOBILE_PHONE.matcher(value).matches(); 69 | } 70 | 71 | /** 72 | * 判断是否为数字 73 | * 74 | * @param value 字符串 75 | * @return true Or false 76 | */ 77 | public static boolean isNumber(String value) { 78 | try { 79 | Integer.parseInt(value); 80 | } catch (Exception ex) { 81 | return false; 82 | } 83 | return true; 84 | } 85 | 86 | /** 87 | * 判断字符串是否为空 88 | * 89 | * @param value 字符串 90 | * @return true Or false 91 | */ 92 | public static boolean isEmpty(String value) { 93 | return null == value || "".equals(value.trim()) || "null".equalsIgnoreCase(value.trim()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/annotation/ExcelField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.annotation; 17 | 18 | import com.wuwenze.poi.config.Options; 19 | import com.wuwenze.poi.convert.ReadConverter; 20 | import com.wuwenze.poi.convert.WriteConverter; 21 | import com.wuwenze.poi.validator.Validator; 22 | import java.lang.annotation.ElementType; 23 | import java.lang.annotation.Retention; 24 | import java.lang.annotation.RetentionPolicy; 25 | import java.lang.annotation.Target; 26 | 27 | /** 28 | * @author wuwenze 29 | */ 30 | @Retention(RetentionPolicy.RUNTIME) 31 | @Target(ElementType.FIELD) 32 | public @interface ExcelField { 33 | 34 | /** 35 | * @return 单元格名称(如 : id字段显示为 ' 编号 ') 默认为字段名 36 | */ 37 | String value() default ""; 38 | 39 | /** 40 | * 属性名, 仅在复杂数据类型时配置. 41 | *
 42 |    *   (At)ExcelField(name="user.name");
 43 |    *   private User user;
 44 |    * 
45 | * 46 | * @return 属性名 47 | */ 48 | String name() default ""; 49 | 50 | /** 51 | * @return 单元格宽度[仅限表头] 默认-1(自动计算列宽) 52 | */ 53 | short width() default -1; 54 | 55 | /** 56 | * @return 是否必填 57 | */ 58 | boolean required() default false; 59 | 60 | /** 61 | * @return 批注信息, 生成模板时生效 62 | */ 63 | String comment() default ""; 64 | 65 | /** 66 | * @return 最大长度, 读取时生效, 默认不限制 67 | */ 68 | int maxLength() default -1; 69 | 70 | /** 71 | * 日期格式, 如: yyyy/MM/dd 72 | * 73 | * @return 日期格式 74 | */ 75 | String dateFormat() default ""; 76 | 77 | /** 78 | * @return 下拉框数据源, 生成模板和验证数据时生效 79 | */ 80 | Class options() default Void.class; 81 | 82 | /** 83 | * 写入内容转换表达式 (如: 1=男,2=女), 与 writeConverter 二选一(优先级0) 84 | * 85 | * @return 写入内容转换表达式 86 | * @see ExcelField#writeConverter() 87 | */ 88 | String writeConverterExp() default ""; 89 | 90 | /** 91 | * 写入内容转换器, 与 writeConverterExp 二选一(优先级1) 92 | * 93 | * @return 写入内容转换器 94 | * @see ExcelField#writeConverterExp() 95 | */ 96 | Class writeConverter() default Void.class; 97 | 98 | /** 99 | * 读取内容转表达式 (如: 男=1,女=2), 与 readConverter 二选一(优先级0) 100 | * 101 | * @return 读取内容转表达式 102 | * @see ExcelField#readConverter() 103 | */ 104 | String readConverterExp() default ""; 105 | 106 | /** 107 | * 读取内容转换器, 与 readConverterExp 二选一(优先级1) 108 | * 109 | * @return 读取内容转换器 110 | * @see ExcelField#readConverterExp() 111 | */ 112 | Class readConverter() default Void.class; 113 | 114 | /** 115 | * 正则表达式, 读取时生效, 与 validator 二选一(优先级0) 116 | * 117 | * @return 正则表达式 118 | * @see ExcelField#validator() 119 | */ 120 | String regularExp() default ""; 121 | 122 | /** 123 | * 正则表达式验证失败时的错误消息, regularExp 配置后生效 124 | * 125 | * @return 正则表达式验证失败时的错误消息 126 | * @see ExcelField#regularExp() 127 | */ 128 | String regularExpMessage() default ""; 129 | 130 | /** 131 | * 自定义验证器, 读取时生效, 与 regularExp 二选一(优先级1) 132 | * 133 | * @return 自定义验证器 134 | * @see ExcelField#regularExp() 135 | */ 136 | Class validator() default Void.class; 137 | 138 | class Void implements Options, ReadConverter, WriteConverter, Validator { 139 | 140 | @Override 141 | public String[] get() { 142 | return new String[0]; 143 | } 144 | 145 | @Override 146 | public String convert(Object value) { 147 | return null; 148 | } 149 | 150 | @Override 151 | public String valid(Object value) { 152 | return null; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.wuwenze 8 | ExcelKit 9 | 2.0.72 10 | jar 11 | ExcelKit 12 | http://gitee.com/wuwenze/ExcelKit 13 | Excel导入导出工具(简单、好用且轻量级的海量Excel文件导入导出解决方案.) 14 | 15 | 16 | wuwenze 17 | https://wuwenze.com 18 | wenzewoo@gmail.com 19 | 20 | 21 | 22 | 23 | The Apache Software License, Version 2.0 24 | http://www.apache.org/licenses/LICENSE-2.0.txt 25 | 26 | 27 | 28 | scm:git:git@gitee.com:wuwenze/ExcelKit.git 29 | scm:git:git@gitee.com:wuwenze/ExcelKit.git 30 | git@gitee.com:wuwenze/ExcelKit.git 31 | 32 | 33 | 34 | UTF-8 35 | 1.6 36 | 3.17 37 | 1.6.1 38 | 1.1.6 39 | 2.11.0 40 | 18.0 41 | 42 | 43 | 44 | 45 | org.apache.poi 46 | poi-ooxml 47 | ${poi-version} 48 | 49 | 50 | org.apache.poi 51 | poi-ooxml-schemas 52 | ${poi-version} 53 | 54 | 55 | dom4j 56 | dom4j 57 | ${dom4j-version} 58 | 59 | 60 | jaxen 61 | jaxen 62 | ${jaxen-version} 63 | 64 | 65 | xerces 66 | xercesImpl 67 | ${xerces-version} 68 | 69 | 70 | xml-apis 71 | xml-apis 72 | 2.0.2 73 | 74 | 75 | com.google.guava 76 | guava 77 | 18.0 78 | 79 | 80 | org.projectlombok 81 | lombok 82 | 1.16.10 83 | provided 84 | 85 | 86 | javax.servlet 87 | servlet-api 88 | 2.5 89 | provided 90 | 91 | 92 | commons-beanutils 93 | commons-beanutils 94 | 1.9.3 95 | 96 | 97 | junit 98 | junit 99 | 4.12 100 | test 101 | 102 | 103 | 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-compiler-plugin 109 | 110 | ${jdk-version} 111 | ${jdk-version} 112 | ${encoding} 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | jdk-profile 121 | 122 | true 123 | ${jdk-version} 124 | 125 | 126 | ${jdk-version} 127 | ${jdk-version} 128 | ${jdk-version} 129 | 130 | 131 | 132 | release 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-source-plugin 138 | 2.2.1 139 | 140 | 141 | package 142 | 143 | jar-no-fork 144 | 145 | 146 | 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-javadoc-plugin 151 | 2.9.1 152 | 153 | 154 | package 155 | 156 | jar 157 | 158 | 159 | 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-gpg-plugin 164 | 1.5 165 | 166 | 167 | verify 168 | 169 | sign 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | sonatype 179 | https://oss.sonatype.org/content/repositories/snapshots/ 180 | 181 | 182 | sonatype 183 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/util/POIUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.util; 17 | 18 | import com.wuwenze.poi.config.Options; 19 | import com.wuwenze.poi.exception.ExcelKitRuntimeException; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.OutputStream; 23 | import javax.servlet.http.HttpServletResponse; 24 | import lombok.AccessLevel; 25 | import lombok.NoArgsConstructor; 26 | import org.apache.poi.ss.usermodel.DataValidation; 27 | import org.apache.poi.ss.usermodel.DataValidationConstraint; 28 | import org.apache.poi.ss.usermodel.DataValidationHelper; 29 | import org.apache.poi.ss.util.CellRangeAddressList; 30 | import org.apache.poi.xssf.streaming.SXSSFCell; 31 | import org.apache.poi.xssf.streaming.SXSSFRow; 32 | import org.apache.poi.xssf.streaming.SXSSFSheet; 33 | import org.apache.poi.xssf.streaming.SXSSFWorkbook; 34 | 35 | /** 36 | * @author wuwenze 37 | */ 38 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 39 | public class POIUtil { 40 | 41 | private static final int mDefaultRowAccessWindowSize = 100; 42 | 43 | 44 | private static SXSSFWorkbook newSXSSFWorkbook(int rowAccessWindowSize) { 45 | return new SXSSFWorkbook(rowAccessWindowSize); 46 | } 47 | 48 | public static SXSSFWorkbook newSXSSFWorkbook() { 49 | return POIUtil.newSXSSFWorkbook(POIUtil.mDefaultRowAccessWindowSize); 50 | } 51 | 52 | public static SXSSFSheet newSXSSFSheet(SXSSFWorkbook wb, String sheetName) { 53 | return wb.createSheet(sheetName); 54 | } 55 | 56 | public static SXSSFRow newSXSSFRow(SXSSFSheet sheet, int index) { 57 | return sheet.createRow(index); 58 | } 59 | 60 | public static SXSSFCell newSXSSFCell(SXSSFRow row, int index) { 61 | return row.createCell(index); 62 | } 63 | 64 | public static void setColumnWidth( 65 | SXSSFSheet sheet, int index, Short width, String value) { 66 | boolean widthNotHaveConfig = (null == width || width == -1); 67 | if (widthNotHaveConfig && !ValidatorUtil.isEmpty(value)) { 68 | sheet.setColumnWidth(index, (short) (value.length() * 2048)); 69 | } else { 70 | width = widthNotHaveConfig ? 200 : width; 71 | sheet.setColumnWidth(index, (short) (width * 35.7)); 72 | } 73 | } 74 | 75 | public static void setColumnCellRange(SXSSFSheet sheet, Options options, 76 | int firstRow, int endRow, 77 | int firstCell, int endCell) { 78 | if (null != options) { 79 | String[] datasource = options.get(); 80 | if (null != datasource && datasource.length > 0) { 81 | if (datasource.length > 100) { 82 | throw new ExcelKitRuntimeException("Options item too much."); 83 | } 84 | 85 | DataValidationHelper validationHelper = sheet.getDataValidationHelper(); 86 | DataValidationConstraint explicitListConstraint = validationHelper 87 | .createExplicitListConstraint(datasource); 88 | CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCell, 89 | endCell); 90 | DataValidation validation = validationHelper 91 | .createValidation(explicitListConstraint, regions); 92 | validation.setSuppressDropDownArrow(true); 93 | validation.createErrorBox("提示", "请从下拉列表选取"); 94 | validation.setShowErrorBox(true); 95 | sheet.addValidationData(validation); 96 | } 97 | } 98 | } 99 | 100 | public static void write(SXSSFWorkbook wb, OutputStream out) { 101 | try { 102 | if (null != out) { 103 | wb.write(out); 104 | out.flush(); 105 | } 106 | } catch (IOException e) { 107 | e.printStackTrace(); 108 | } finally { 109 | if (null != out) { 110 | try { 111 | out.close(); 112 | } catch (IOException e) { 113 | e.printStackTrace(); 114 | } 115 | } 116 | } 117 | } 118 | 119 | public static void download( 120 | SXSSFWorkbook wb, HttpServletResponse response, String filename) { 121 | try { 122 | OutputStream out = response.getOutputStream(); 123 | response.setContentType(Const.XLSX_CONTENT_TYPE); 124 | response.setHeader(Const.XLSX_HEADER_KEY, 125 | String.format(Const.XLSX_HEADER_VALUE_TEMPLATE, filename)); 126 | POIUtil.write(wb, out); 127 | } catch (IOException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | 132 | public static Object convertByExp(Object propertyValue, String converterExp) 133 | throws Exception { 134 | try { 135 | String[] convertSource = converterExp.split(","); 136 | for (String item : convertSource) { 137 | String[] itemArray = item.split("="); 138 | if (itemArray[0].equals(propertyValue)) { 139 | return itemArray[1]; 140 | } 141 | } 142 | } catch (Exception e) { 143 | throw e; 144 | } 145 | return propertyValue; 146 | } 147 | 148 | public static int countNullCell(String ref, String ref2) { 149 | // excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD 150 | String xfd = ref.replaceAll("\\d+", ""); 151 | String xfd_1 = ref2.replaceAll("\\d+", ""); 152 | 153 | xfd = POIUtil.fillChar(xfd, 3, '@', true); 154 | xfd_1 = POIUtil.fillChar(xfd_1, 3, '@', true); 155 | 156 | char[] letter = xfd.toCharArray(); 157 | char[] letter_1 = xfd_1.toCharArray(); 158 | int res = 159 | (letter[0] - letter_1[0]) * 26 * 26 + (letter[1] - letter_1[1]) * 26 + (letter[2] 160 | - letter_1[2]); 161 | return res - 1; 162 | } 163 | 164 | private static String fillChar(String str, int len, char let, boolean isPre) { 165 | int len_1 = str.length(); 166 | if (len_1 < len) { 167 | if (isPre) { 168 | for (int i = 0; i < (len - len_1); i++) { 169 | str = let + str; 170 | } 171 | } else { 172 | for (int i = 0; i < (len - len_1); i++) { 173 | str = str + let; 174 | } 175 | } 176 | } 177 | return str; 178 | } 179 | 180 | public static void checkExcelFile(File file) { 181 | String filename = null != file ? file.getAbsolutePath() : null; 182 | if (null == filename || !file.exists()) { 183 | throw new ExcelKitRuntimeException("Excel file[" + filename + "] does not exist."); 184 | } 185 | if (!filename.endsWith(Const.XLSX_SUFFIX)) { 186 | throw new ExcelKitRuntimeException( 187 | "[" + filename + "]Only .xlsx formatted files are supported."); 188 | } 189 | } 190 | 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/ExcelKit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi; 17 | 18 | import com.wuwenze.poi.exception.ExcelKitRuntimeException; 19 | import com.wuwenze.poi.factory.ExcelMappingFactory; 20 | import com.wuwenze.poi.handler.ExcelReadHandler; 21 | import com.wuwenze.poi.pojo.ExcelMapping; 22 | import com.wuwenze.poi.util.Const; 23 | import com.wuwenze.poi.util.POIUtil; 24 | import com.wuwenze.poi.xlsx.ExcelXlsxReader; 25 | import com.wuwenze.poi.xlsx.ExcelXlsxWriter; 26 | import java.io.File; 27 | import java.io.FileInputStream; 28 | import java.io.InputStream; 29 | import java.io.OutputStream; 30 | import java.net.URLEncoder; 31 | import java.util.List; 32 | import javax.servlet.http.HttpServletResponse; 33 | import lombok.AccessLevel; 34 | import lombok.NoArgsConstructor; 35 | import org.apache.poi.xssf.streaming.SXSSFWorkbook; 36 | 37 | /** 38 | * @author wuwenze 39 | */ 40 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 41 | public class ExcelKit { 42 | 43 | private Class mClass = null; 44 | private HttpServletResponse mResponse = null; 45 | private OutputStream mOutputStream = null; 46 | private Integer mMaxSheetRecords = 50000; 47 | private String mCurrentOptionMode = ExcelKit.MODE_EXPORT; 48 | private final static String MODE_EXPORT = "$MODE_EXPORT$"; 49 | private final static String MODE_BUILD = "$MODE_BUILD$"; 50 | private final static String MODE_IMPORT = "$MODE_IMPORT$"; 51 | 52 | /** 53 | * 使用此构造器来执行浏览器导出 54 | * 55 | * @param clazz 导出实体对象 56 | * @param response 原生 response 对象, 用于响应浏览器下载 57 | * @return ExcelKit obj. 58 | * @see ExcelKit#downXlsx(List, boolean) 59 | */ 60 | public static ExcelKit $Export(Class clazz, HttpServletResponse response) { 61 | return new ExcelKit(clazz, response); 62 | } 63 | 64 | public void downXlsx(List data, boolean isTemplate) { 65 | if (!mCurrentOptionMode.equals(ExcelKit.MODE_EXPORT)) { 66 | throw new ExcelKitRuntimeException( 67 | "请使用com.wuwenze.poi.ExcelKit.$Export(Class clazz, HttpServletResponse response)构造器初始化参数."); 68 | } 69 | try { 70 | ExcelMapping excelMapping = ExcelMappingFactory.get(mClass); 71 | ExcelXlsxWriter excelXlsxWriter = new ExcelXlsxWriter(excelMapping, 72 | mMaxSheetRecords); 73 | SXSSFWorkbook workbook = excelXlsxWriter.generateXlsxWorkbook(data, isTemplate); 74 | String fileName = isTemplate ? (excelMapping.getName() + "-导入模板.xlsx") 75 | : (excelMapping.getName() + "-导出结果.xlsx"); 76 | POIUtil.download(workbook, mResponse, URLEncoder.encode(fileName, Const.ENCODING)); 77 | } catch (Throwable e) { 78 | throw new ExcelKitRuntimeException("downXlsx error", e); 79 | } 80 | } 81 | 82 | 83 | /** 84 | * 使用此构造器来执行构建文件流. 85 | * 86 | * @param clazz 导出实体对象 87 | * @param outputStream 输出流 88 | * @return ExcelKit obj. 89 | * @see ExcelKit#writeXlsx(List, boolean) 90 | */ 91 | public static ExcelKit $Builder(Class clazz, OutputStream outputStream) { 92 | return new ExcelKit(clazz, outputStream); 93 | } 94 | 95 | public void writeXlsx(List data, boolean isTemplate) { 96 | if (!mCurrentOptionMode.equals(ExcelKit.MODE_BUILD)) { 97 | throw new ExcelKitRuntimeException( 98 | "请使用com.wuwenze.poi.ExcelKit.$Builder(Class clazz, OutputStream outputStream)构造器初始化参数."); 99 | } 100 | ExcelMapping excelMapping = ExcelMappingFactory.get(mClass); 101 | ExcelXlsxWriter excelXlsxWriter = new ExcelXlsxWriter(excelMapping, 102 | mMaxSheetRecords); 103 | SXSSFWorkbook workbook = excelXlsxWriter.generateXlsxWorkbook(data, isTemplate); 104 | POIUtil.write(workbook, mOutputStream); 105 | } 106 | 107 | /** 108 | * 使用此构造器来执行Excel文件导入. 109 | * 110 | * @param clazz 导出实体对象 111 | * @return ExcelKit obj. 112 | * @see ExcelKit#readXlsx(File, Integer, ExcelReadHandler) 113 | * @see ExcelKit#readXlsx(InputStream, Integer, ExcelReadHandler) 114 | * @see ExcelKit#readXlsx(File, ExcelReadHandler) 115 | * @see ExcelKit#readXlsx(InputStream, ExcelReadHandler) 116 | */ 117 | public static ExcelKit $Import(Class clazz) { 118 | return new ExcelKit(clazz); 119 | } 120 | 121 | 122 | public void readXlsx(File excelFile, ExcelReadHandler excelReadHandler) { 123 | readXlsx(excelFile, -1, excelReadHandler); 124 | } 125 | 126 | public void readXlsx(File excelFile, Integer sheetIndex, 127 | ExcelReadHandler excelReadHandler) { 128 | try { 129 | InputStream inputStream = new FileInputStream(excelFile); 130 | readXlsx(inputStream, sheetIndex, excelReadHandler); 131 | } catch (Throwable e) { 132 | throw new ExcelKitRuntimeException("readXlsx error", e); 133 | } 134 | } 135 | 136 | public void readXlsx(InputStream inputStream, ExcelReadHandler excelReadHandler) { 137 | readXlsx(inputStream, -1, excelReadHandler); 138 | } 139 | 140 | public void readXlsx(InputStream inputStream, Integer sheetIndex, 141 | ExcelReadHandler excelReadHandler) { 142 | if (!mCurrentOptionMode.equals(ExcelKit.MODE_IMPORT)) { 143 | throw new ExcelKitRuntimeException( 144 | "请使用com.wuwenze.poi.ExcelKit.$Import(Class clazz)构造器初始化参数."); 145 | } 146 | ExcelMapping excelMapping = ExcelMappingFactory.get(mClass); 147 | ExcelXlsxReader excelXlsxReader = new ExcelXlsxReader(mClass, excelMapping, 148 | excelReadHandler); 149 | if (sheetIndex >= 0) { 150 | excelXlsxReader.process(inputStream, sheetIndex); 151 | return; 152 | } 153 | excelXlsxReader.process(inputStream); 154 | } 155 | 156 | public ExcelKit setMaxSheetRecords(Integer mMaxSheetRecords) { 157 | this.mMaxSheetRecords = mMaxSheetRecords; 158 | return this; 159 | } 160 | 161 | protected ExcelKit(Class clazz) { 162 | this(clazz, null, null); 163 | mCurrentOptionMode = ExcelKit.MODE_IMPORT; 164 | } 165 | 166 | protected ExcelKit(Class clazz, OutputStream outputStream) { 167 | this(clazz, outputStream, null); 168 | mCurrentOptionMode = ExcelKit.MODE_BUILD; 169 | } 170 | 171 | protected ExcelKit(Class clazz, HttpServletResponse response) { 172 | this(clazz, null, response); 173 | mCurrentOptionMode = ExcelKit.MODE_EXPORT; 174 | } 175 | 176 | protected ExcelKit( 177 | Class clazz, OutputStream outputStream, HttpServletResponse response) { 178 | mClass = clazz; 179 | mOutputStream = outputStream; 180 | mResponse = response; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/xlsx/ExcelXlsxWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.xlsx; 17 | 18 | import com.wuwenze.poi.convert.WriteConverter; 19 | import com.wuwenze.poi.exception.ExcelKitRuntimeException; 20 | import com.wuwenze.poi.pojo.ExcelMapping; 21 | import com.wuwenze.poi.pojo.ExcelProperty; 22 | import com.wuwenze.poi.util.DateUtil; 23 | import com.wuwenze.poi.util.POIUtil; 24 | import com.wuwenze.poi.util.ValidatorUtil; 25 | import java.text.ParseException; 26 | import java.util.Date; 27 | import java.util.List; 28 | import org.apache.commons.beanutils.BeanUtils; 29 | import org.apache.poi.hssf.util.HSSFColor; 30 | import org.apache.poi.ss.usermodel.BorderStyle; 31 | import org.apache.poi.ss.usermodel.CellStyle; 32 | import org.apache.poi.ss.usermodel.Comment; 33 | import org.apache.poi.ss.usermodel.DataFormat; 34 | import org.apache.poi.ss.usermodel.FillPatternType; 35 | import org.apache.poi.ss.usermodel.Font; 36 | import org.apache.poi.ss.usermodel.HorizontalAlignment; 37 | import org.apache.poi.xssf.streaming.SXSSFCell; 38 | import org.apache.poi.xssf.streaming.SXSSFDrawing; 39 | import org.apache.poi.xssf.streaming.SXSSFRow; 40 | import org.apache.poi.xssf.streaming.SXSSFSheet; 41 | import org.apache.poi.xssf.streaming.SXSSFWorkbook; 42 | import org.apache.poi.xssf.usermodel.XSSFClientAnchor; 43 | import org.apache.poi.xssf.usermodel.XSSFRichTextString; 44 | 45 | /** 46 | * @author wuwenze 47 | */ 48 | public class ExcelXlsxWriter { 49 | 50 | private final ExcelMapping mExcelMapping; 51 | private final Integer mMaxSheetRecords; 52 | 53 | public ExcelXlsxWriter(ExcelMapping excelMapping, Integer maxSheetRecords) { 54 | mExcelMapping = excelMapping; 55 | mMaxSheetRecords = maxSheetRecords; 56 | } 57 | 58 | /** 59 | * 构建xlsxWorkbook对象 60 | * 61 | * @param data 数据集 62 | * @param isTemplate 是否是导出模板 63 | * @return SXSSFWorkbook 64 | */ 65 | public SXSSFWorkbook generateXlsxWorkbook(List data, boolean isTemplate) { 66 | SXSSFWorkbook workbook = POIUtil.newSXSSFWorkbook(); 67 | List propertyList = mExcelMapping.getPropertyList(); 68 | double sheetNo = Math.ceil(data.size() / (double) mMaxSheetRecords); 69 | for (int index = 0; index <= (sheetNo == 0.0 ? sheetNo : sheetNo - 1); index++) { 70 | String sheetName = mExcelMapping.getName() + (index == 0 ? "" : "_" + index); 71 | SXSSFSheet sheet = generateXlsxHeader(workbook, propertyList, sheetName, isTemplate); 72 | if (null != data && data.size() > 0) { 73 | int startNo = index * mMaxSheetRecords; 74 | int endNo = Math.min(startNo + mMaxSheetRecords, data.size()); 75 | for (int i = startNo; i < endNo; i++) { 76 | SXSSFRow bodyRow = POIUtil.newSXSSFRow(sheet, i + 1 - startNo); 77 | for (int j = 0; j < propertyList.size(); j++) { 78 | SXSSFCell cell = POIUtil.newSXSSFCell(bodyRow, j); 79 | ExcelXlsxWriter.buildCellValueByExcelProperty(cell, data.get(i), propertyList.get(j)); 80 | } 81 | } 82 | } 83 | } 84 | return workbook; 85 | } 86 | 87 | private SXSSFSheet generateXlsxHeader(SXSSFWorkbook workbook, 88 | List propertyList, 89 | String sheetName, boolean isTemplate) { 90 | SXSSFDrawing sxssfDrawing = null; 91 | SXSSFSheet sheet = POIUtil.newSXSSFSheet(workbook, sheetName); 92 | SXSSFRow headerRow = POIUtil.newSXSSFRow(sheet, 0); 93 | 94 | for (int i = 0; i < propertyList.size(); i++) { 95 | ExcelProperty property = propertyList.get(i); 96 | SXSSFCell cell = POIUtil.newSXSSFCell(headerRow, i); 97 | POIUtil.setColumnWidth(sheet, i, property.getWidth(), property.getColumn()); 98 | if (isTemplate) { 99 | // cell range 100 | POIUtil.setColumnCellRange(sheet, property.getOptions(), 1, mMaxSheetRecords, i, i); 101 | // cell comment. 102 | if (null == sxssfDrawing) { 103 | sxssfDrawing = sheet.createDrawingPatriarch(); 104 | } 105 | if (!ValidatorUtil.isEmpty(property.getComment())) { 106 | // int col1, int row1, int col2, int row2 107 | Comment cellComment = sxssfDrawing.createCellComment(// 108 | new XSSFClientAnchor(0, 0, 0, 0, i, 0,i+2, i+2)); 109 | XSSFRichTextString xssfRichTextString = new XSSFRichTextString( 110 | property.getComment()); 111 | Font commentFormatter = workbook.createFont(); 112 | xssfRichTextString.applyFont(commentFormatter); 113 | cellComment.setString(xssfRichTextString); 114 | cell.setCellComment(cellComment); 115 | } 116 | } 117 | cell.setCellStyle(getHeaderCellStyle(workbook)); 118 | String headerColumnValue = property.getColumn(); 119 | if (isTemplate && null != property.getRequired() && property.getRequired()) { 120 | headerColumnValue = (headerColumnValue + "[*]"); 121 | } 122 | cell.setCellValue(headerColumnValue); 123 | } 124 | return sheet; 125 | } 126 | 127 | private static void buildCellValueByExcelProperty(SXSSFCell cell, Object entity, 128 | ExcelProperty property) { 129 | Object cellValue; 130 | try { 131 | cellValue = BeanUtils.getProperty(entity, property.getName()); 132 | } catch (Throwable e) { 133 | throw new ExcelKitRuntimeException(e); 134 | } 135 | if (null != cellValue) { 136 | String dateFormat = property.getDateFormat(); 137 | if (!ValidatorUtil.isEmpty(dateFormat)) { 138 | if (cellValue instanceof Date) { 139 | cell.setCellValue(DateUtil.format(dateFormat, (Date) cellValue)); 140 | } else if (cellValue instanceof String) { 141 | try { 142 | Date parse = DateUtil.ENGLISH_LOCAL_DF.parse((String) cellValue); 143 | cell.setCellValue(DateUtil.format(dateFormat, parse)); 144 | } catch (ParseException e) { 145 | e.printStackTrace(); 146 | } 147 | return; 148 | } 149 | } 150 | // writeConverterExp && writeConverter 151 | String writeConverterExp = property.getWriteConverterExp(); 152 | WriteConverter writeConverter = property.getWriteConverter(); 153 | if (!ValidatorUtil.isEmpty(writeConverterExp)) { 154 | try { 155 | cellValue = POIUtil.convertByExp(cellValue, writeConverterExp); 156 | } catch (Throwable e) { 157 | throw new ExcelKitRuntimeException(e); 158 | } 159 | } else if (null != writeConverter) { 160 | cell.setCellValue(writeConverter.convert(cellValue)); 161 | return; 162 | } 163 | cell.setCellValue(String.valueOf(cellValue)); 164 | } 165 | } 166 | 167 | private CellStyle mHeaderCellStyle = null; 168 | 169 | 170 | public CellStyle getHeaderCellStyle(SXSSFWorkbook wb) { 171 | if (null == mHeaderCellStyle) { 172 | mHeaderCellStyle = wb.createCellStyle(); 173 | Font font = wb.createFont(); 174 | mHeaderCellStyle.setFillForegroundColor((short) 12); 175 | mHeaderCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); 176 | mHeaderCellStyle.setBorderTop(BorderStyle.DOTTED); 177 | mHeaderCellStyle.setBorderRight(BorderStyle.DOTTED); 178 | mHeaderCellStyle.setBorderBottom(BorderStyle.DOTTED); 179 | mHeaderCellStyle.setBorderLeft(BorderStyle.DOTTED); 180 | mHeaderCellStyle.setAlignment(HorizontalAlignment.LEFT);// 对齐 181 | mHeaderCellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.GREEN.getIndex()); 182 | mHeaderCellStyle.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.GREEN.getIndex()); 183 | font.setColor(HSSFColor.HSSFColorPredefined.WHITE.getIndex()); 184 | // 应用标题字体到标题样式 185 | mHeaderCellStyle.setFont(font); 186 | //设置单元格文本形式 187 | DataFormat dataFormat = wb.createDataFormat(); 188 | mHeaderCellStyle.setDataFormat(dataFormat.getFormat("@")); 189 | } 190 | return mHeaderCellStyle; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExcelKit 2 | > 简单、好用且轻量级的海量Excel文件导入导出解决方案。 3 | 4 | # 注意 5 | ## 停止维护,建议使用EasyExcel:https://github.com/alibaba/easyexcel 6 | 7 | ## POM.xml 8 | ```xml 9 | 10 | com.wuwenze 11 | ExcelKit 12 | 2.0.72 13 | 14 | ``` 15 | 16 | ## 示例 17 | ### 1. ExcelMapping (配置Excel与实体之间的映射关系) 18 | 19 | > 现支持两种配置方式: 注解 或者 XML 20 | ```java 21 | @Excel("用户信息") 22 | public class User { 23 | 24 | @ExcelField(value = "编号", width = 30) 25 | private Integer userId; 26 | 27 | @ExcelField(// 28 | value = "用户名",// 29 | required = true,// 30 | validator = UsernameValidator.class,// 31 | comment = "请填写用户名,最大长度为12,且不能重复" 32 | ) 33 | private String username; 34 | 35 | @ExcelField(value = "密码", required = true, maxLength = 32) 36 | private String password; 37 | 38 | @ExcelField(value = "邮箱", validator = UserEmailValidator.class) 39 | private String email; 40 | 41 | @ExcelField(// 42 | value = "性别",// 43 | readConverterExp = "未知=0,男=1,女=2",// 44 | writeConverterExp = "0=未知,1=男,2=女",// 45 | options = SexOptions.class// 46 | ) 47 | private Integer sex; 48 | 49 | @ExcelField(// 50 | value = "用户组",// 51 | name = "userGroup.name",// 52 | options = UserGroupNameOptions.class 53 | ) 54 | private UserGroup userGroup; 55 | 56 | @ExcelField(value = "创建时间", dateFormat = "yyyy/MM/dd HH:mm:ss") 57 | private Date createAt; 58 | 59 | @ExcelField(// 60 | value = "自定义字段",// 61 | maxLength = 80,// 62 | comment = "可以乱填,但是长度不能超过80,导入时最终会转换为数字",// 63 | writeConverter = CustomizeFieldWriteConverter.class,// 写文件时,将数字转字符串 64 | readConverter = CustomizeFieldReadConverter.class// 读文件时,将字符串转数字 65 | ) 66 | private Integer customizeField; 67 | 68 | // Getter and Setter .. 69 | } 70 | ``` 71 | 72 | > XML 配置方式, 必须需将 xml 文件放置在`classpath:excel-mapping/{entityClassName}.xml` 73 | 74 | ```java 75 | public class User2 { 76 | 77 | private Integer userId; 78 | private String username; 79 | private String password; 80 | private String email; 81 | private Integer sex; 82 | private UserGroup userGroup; 83 | private Date createAt; 84 | private Integer customizeField; 85 | 86 | // Getter and Setter .. 87 | } 88 | ``` 89 | 90 | > classpath:excel-mapping/com.wuwenze.entity.User2.xml 91 | ```xml 92 | 93 | 94 | 95 | 102 | 103 | 104 | 111 | 112 | 113 | 121 | 122 | ``` 123 | 以上两种方式2选一即可, ExcelKit 会`优先装载 XML 配置文件`. 124 | 125 | ### 2. 实现相关的转换器 126 | > 通过实现`com.wuwenze.poi.config.Options`自定义导入模板的下拉框数据源。 127 | 128 | ```java 129 | public class UserGroupNameOptions implements Options { 130 | 131 | @Override 132 | public String[] get() { 133 | return new String[]{"管理组", "普通会员组", "游客"}; 134 | } 135 | } 136 | ``` 137 | 138 | > 通过实现`com.wuwenze.poi.validator.Validator`自定义单元格导入时的验证规则。 139 | ```java 140 | public class UsernameValidator implements Validator { 141 | final List ERROR_USERNAME_LIST = Lists.newArrayList( 142 | "admin", "root", "master", "administrator", "sb" 143 | ); 144 | 145 | @Override 146 | public String valid(Object o) { 147 | String username = (String) o; 148 | if (username.length() > 12) { 149 | return "用户名不能超过12个字符。"; 150 | } 151 | 152 | if (ERROR_USERNAME_LIST.contains(username)) { 153 | return "用户名非法,不允许使用。"; 154 | } 155 | return null; 156 | } 157 | } 158 | ``` 159 | 160 | > 实现`com.wuwenze.poi.convert.WriteConverter`以及`com.wuwenze.poi.convert.ReadConverter`单元格读写转换器。 161 | ```java 162 | public class CustomizeFieldWriteConverter implements WriteConverter { 163 | 164 | /** 165 | * 写文件时,将值进行转换(此处示例为将数值拼接为指定格式的字符串) 166 | */ 167 | @Override 168 | public String convert(Object o) throws ExcelKitWriteConverterException { 169 | return (o + "_convertedValue"); 170 | } 171 | } 172 | 173 | public class CustomizeFieldReadConverter implements ReadConverter { 174 | 175 | /** 176 | * 读取单元格时,将值进行转换(此处示例为计算单元格字符串char的总和) 177 | */ 178 | @Override 179 | public Object convert(Object o) throws ExcelKitReadConverterException { 180 | String value = (String) o; 181 | 182 | int convertedValue = 0; 183 | for (char c : value.toCharArray()) { 184 | convertedValue += Integer.valueOf(c); 185 | } 186 | return convertedValue; 187 | } 188 | } 189 | ``` 190 | 191 | ### 3. 一行代码构建 Excel 导入模板 192 | > 使用 ExcelKit 提供的API 构建导入模板, 会根据配置生成批注, 下拉框等 193 | ``` java 194 | // 生成导入模板(含3条示例数据) 195 | @RequestMapping(value = "/downTemplate", method = RequestMethod.GET) 196 | public void downTemplate(HttpServletResponse response) { 197 | List userList = DbUtil.getUserList(3); 198 | ExcelKit.$Export(User.class, response).downXlsx(userList, true); 199 | } 200 | ``` 201 | ![](https://cdn.nlark.com/yuque/0/2019/png/243237/1550539800515-e9714f25-e415-4e70-a4b9-2b5a229dfce0.png) 202 | ![](https://cdn.nlark.com/yuque/0/2019/png/243237/1550539833934-6b7b2ca8-c7a0-4872-a1a9-722fc5c403d1.png) 203 | 204 | 205 | ### 4. 执行文件导入 206 | > 使用边读边处理的方式, 无需担心内存溢出, 也不用理会 Excel 文件到底有多大. 207 | 208 | ``` java 209 | @RequestMapping(value = "/importUser", method = RequestMethod.POST) 210 | public ResponseEntity importUser(@RequestParam MultipartFile file) 211 | throws IOException { 212 | long beginMillis = System.currentTimeMillis(); 213 | 214 | List successList = Lists.newArrayList(); 215 | List> errorList = Lists.newArrayList(); 216 | 217 | ExcelKit.$Import(User.class) 218 | .readXlsx(file.getInputStream(), new ExcelReadHandler() { 219 | 220 | @Override 221 | public void onSuccess(int sheetIndex, int rowIndex, User entity) { 222 | successList.add(entity); // 单行读取成功,加入入库队列。 223 | } 224 | 225 | @Override 226 | public void onError(int sheetIndex, int rowIndex, 227 | List errorFields) { 228 | // 读取数据失败,记录了当前行所有失败的数据 229 | errorList.add(MapUtil.newHashMap(// 230 | "sheetIndex", sheetIndex,// 231 | "rowIndex", rowIndex,// 232 | "errorFields", errorFields// 233 | )); 234 | } 235 | }); 236 | 237 | // TODO: 执行successList的入库操作。 238 | 239 | return ResponseEntity.ok(MapUtil.newHashMap( 240 | "data", successList, 241 | "haveError", !CollectionUtil.isEmpty(errorList), 242 | "error", errorList, 243 | "timeConsuming", (System.currentTimeMillis() - beginMillis) / 1000L 244 | )); 245 | } 246 | ``` 247 | 248 | > 全部导入成功示例: 249 | 250 | ![](https://cdn.nlark.com/yuque/0/2019/png/243237/1550539857806-7dd5b511-4cfe-43f7-8d82-f76bd831e7af.png) 251 | 252 | > 部分导入失败示例(包含错误信息): 253 | 254 | ![](https://cdn.nlark.com/yuque/0/2019/png/243237/1550539878743-a0cc24a2-1f30-4ccc-8d14-225a3bad30f5.png) 255 | 256 | 257 | ### 5. 一行代码执行 Excel 批量导出. 258 | > 基于 Apache POI SXSSF 系列API实现导出, 大幅优化导出性能. 259 | 260 | ``` java 261 | @RequestMapping(value = "/downXlsx", method = RequestMethod.GET) 262 | public void downXlsx(HttpServletResponse response) { 263 | long beginMillis = System.currentTimeMillis(); 264 | List userList = DbUtil.getUserList(100000);// 生成10w条测试数据 265 | ExcelKit.$Export(User.class, response).downXlsx(userList, false); 266 | log.info("#ExcelKit.$Export success, size={},timeConsuming={}s",// 267 | userList.size(), (System.currentTimeMillis() - beginMillis) / 1000L); 268 | } 269 | ``` 270 | ![](https://cdn.nlark.com/yuque/0/2019/png/243237/1550539922063-7681b3df-6ccf-4f42-939b-269c84b8f8bc.png) 271 | 需要注意的是,虽然ExcelKit针对导出做了大量优化,但导出数据也需要量力而行。 272 | ![](https://cdn.nlark.com/yuque/0/2019/png/243237/1550539967120-9eff43bf-d225-4268-8685-2b441f7024d5.png) 273 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/factory/ExcelMappingFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.factory; 17 | 18 | import com.google.common.cache.CacheBuilder; 19 | import com.google.common.cache.CacheLoader; 20 | import com.google.common.cache.LoadingCache; 21 | import com.google.common.collect.Lists; 22 | import com.wuwenze.poi.annotation.Excel; 23 | import com.wuwenze.poi.annotation.ExcelField; 24 | import com.wuwenze.poi.exception.ExcelKitAnnotationAnalyzeException; 25 | import com.wuwenze.poi.exception.ExcelKitConfigAnalyzeFailureException; 26 | import com.wuwenze.poi.exception.ExcelKitConfigFileNotFoundException; 27 | import com.wuwenze.poi.exception.ExcelKitXmlAnalyzeException; 28 | import com.wuwenze.poi.pojo.ExcelMapping; 29 | import com.wuwenze.poi.pojo.ExcelProperty; 30 | import com.wuwenze.poi.util.BeanUtil; 31 | import com.wuwenze.poi.util.PathUtil; 32 | import com.wuwenze.poi.util.ValidatorUtil; 33 | import java.io.File; 34 | import java.lang.reflect.Field; 35 | import java.util.Iterator; 36 | import java.util.List; 37 | import lombok.AccessLevel; 38 | import lombok.NoArgsConstructor; 39 | import org.dom4j.Attribute; 40 | import org.dom4j.Document; 41 | import org.dom4j.Element; 42 | import org.dom4j.io.SAXReader; 43 | 44 | /** 45 | * @author wuwenze 46 | */ 47 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 48 | public class ExcelMappingFactory { 49 | 50 | private final static LoadingCache, ExcelMapping> mExcelMappingLoadingCache = 51 | CacheBuilder.newBuilder() 52 | .maximumSize(100) 53 | .build(new CacheLoader, ExcelMapping>() { 54 | @Override 55 | public ExcelMapping load(Class key) { 56 | return ExcelMappingFactory.loadExcelMappingByClass(key); 57 | } 58 | }); 59 | private final static List mClazzFields = Lists 60 | .newArrayList("options", "writeConverter", "readConverter", "validator"); 61 | private final static List mRequeridAttrs = Lists.newArrayList("name"); 62 | 63 | /** 64 | * 获取指定实体的Excel映射信息 65 | * 66 | * @param clazz 实体 67 | * @return ExcelMapping映射对象 68 | */ 69 | public static ExcelMapping get(Class clazz) { 70 | try { 71 | return ExcelMappingFactory.mExcelMappingLoadingCache.get(clazz); 72 | } catch (Exception e) { 73 | throw new ExcelKitConfigAnalyzeFailureException(e); 74 | } 75 | } 76 | 77 | private static ExcelMapping loadExcelMappingByClass(Class clazz) { 78 | // 1. 从配置文件加载 (classpath:excel-mapping/className.xml) 79 | ExcelMapping excelMapping = null; 80 | boolean xmlConfigFileNotFound = false; 81 | String loadExcelMappingFailedMessage = null; 82 | try { 83 | excelMapping = ExcelMappingFactory.loadExcelMappingByXml(clazz.getName()); 84 | } catch (Exception e) { 85 | xmlConfigFileNotFound = e instanceof ExcelKitConfigFileNotFoundException; 86 | loadExcelMappingFailedMessage = e.getMessage(); 87 | } 88 | // 2. 从注解加载配置信息 (当配置文件未找到时) 89 | if (null == excelMapping && xmlConfigFileNotFound) { 90 | try { 91 | excelMapping = ExcelMappingFactory.loadExcelMappingByAnnotation(clazz); 92 | } catch (Exception e) { 93 | loadExcelMappingFailedMessage = e.getMessage(); 94 | } 95 | } 96 | // 3. 加载配置信息失败. 97 | if (null == excelMapping && null != loadExcelMappingFailedMessage) { 98 | throw new ExcelKitConfigAnalyzeFailureException(loadExcelMappingFailedMessage); 99 | } 100 | return excelMapping; 101 | } 102 | 103 | private static ExcelMapping loadExcelMappingByAnnotation(Class clazz) 104 | throws IllegalAccessException, InstantiationException { 105 | ExcelMapping excelMapping = new ExcelMapping(); 106 | Excel excel = clazz.getAnnotation(Excel.class); 107 | if (null == excel) { 108 | throw new ExcelKitAnnotationAnalyzeException( 109 | "[" + clazz.getName() + "] @Excel annotations not found."); 110 | } 111 | excelMapping.setName(excel.value()); 112 | ExcelProperty excelMappingProperty; 113 | Field[] fields = clazz.getDeclaredFields(); 114 | List propertyList = Lists.newArrayList(); 115 | for (Field field : fields) { 116 | ExcelField excelField = field.getAnnotation(ExcelField.class); 117 | if (null != excelField) { 118 | Class emptyClazz = ExcelField.Void.class; 119 | excelMappingProperty = ExcelProperty.builder() 120 | .name(ValidatorUtil.isEmpty(excelField.name()) ? field.getName() : excelField.name()) 121 | .required(excelField.required()) 122 | .column( 123 | ValidatorUtil.isEmpty(excelField.value()) ? field.getName() : excelField.value()) 124 | .comment(excelField.comment()) 125 | .maxLength(excelField.maxLength()) 126 | .width(excelField.width()) 127 | .dateFormat(excelField.dateFormat()) 128 | .options(excelField.options() != emptyClazz ? excelField.options().newInstance() : null) 129 | .writeConverterExp(excelField.writeConverterExp()) 130 | .writeConverter(excelField.writeConverter() != emptyClazz ? excelField.writeConverter() 131 | .newInstance() : null) 132 | .readConverterExp(excelField.readConverterExp()) 133 | .readConverter( 134 | excelField.readConverter() != emptyClazz ? excelField.readConverter().newInstance() 135 | : null) 136 | .regularExp(excelField.regularExp()) 137 | .regularExpMessage(excelField.regularExpMessage()) 138 | .validator( 139 | excelField.validator() != emptyClazz ? excelField.validator().newInstance() : null) 140 | .build(); 141 | propertyList.add(excelMappingProperty); 142 | } 143 | } 144 | if (propertyList.isEmpty()) { 145 | throw new ExcelKitAnnotationAnalyzeException( 146 | "[" + clazz.getName() + "] @ExcelField annotations not found."); 147 | } 148 | excelMapping.setPropertyList(propertyList); 149 | return excelMapping; 150 | } 151 | 152 | private static ExcelMapping loadExcelMappingByXml(String clazzName) throws Exception { 153 | ExcelMapping excelMapping = new ExcelMapping(); 154 | File config = PathUtil 155 | .getFileByClasspath(String.format("excel-mapping/%s.xml", clazzName)); 156 | String configFile = "classpath:excel-mapping/" + config.getName(); 157 | if (!config.exists()) { 158 | throw new ExcelKitConfigFileNotFoundException( 159 | "[" + configFile + "] not found."); 160 | } 161 | SAXReader reader = new SAXReader(); 162 | Document document = reader.read(config); 163 | Element rootElement = document.getRootElement(); 164 | if (!"excel-mapping".equals(rootElement.getName())) { 165 | throw new ExcelKitXmlAnalyzeException( 166 | "[" + configFile + "] not found."); 167 | } 168 | Attribute nameAttr = rootElement.attribute("name"); 169 | if (null == nameAttr) { 170 | throw new ExcelKitXmlAnalyzeException( 171 | "[" + configFile + "] attribute \"name\" not found."); 172 | } 173 | excelMapping.setName(nameAttr.getValue()); 174 | List propertyList = Lists.newArrayList(); 175 | Iterator elementIterator = rootElement.elementIterator(); 176 | while (elementIterator.hasNext()) { 177 | Element element = elementIterator.next(); 178 | if ("property".equals(element.getName())) { 179 | List attributes = element.attributes(); 180 | ExcelMappingFactory.checkXmlPropertyRequiredAttr(configFile, attributes); 181 | 182 | ExcelProperty excelMappingProperty = null; 183 | for (Attribute attribute : attributes) { 184 | if (null == excelMappingProperty) { 185 | excelMappingProperty = new ExcelProperty(); 186 | } 187 | String name = attribute.getName(); 188 | String value = attribute.getValue(); 189 | BeanUtil.setComplexProperty(excelMappingProperty, name, 190 | ExcelMappingFactory.validAndGetPropertyValue(configFile, name, value)); 191 | } 192 | if (null != excelMappingProperty) { 193 | propertyList.add(excelMappingProperty); 194 | } 195 | } 196 | } 197 | if (propertyList.isEmpty()) { 198 | throw new ExcelKitXmlAnalyzeException( 199 | "[" + configFile + "] not found."); 200 | } 201 | excelMapping.setPropertyList(propertyList); 202 | return excelMapping; 203 | } 204 | 205 | private static void checkXmlPropertyRequiredAttr(String configFile, 206 | List attributes) { 207 | Integer containsCount = 0; 208 | for (Attribute attr : attributes) { 209 | if (ExcelMappingFactory.mRequeridAttrs.contains(attr.getName())) { 210 | containsCount++; 211 | } 212 | } 213 | if (containsCount != ExcelMappingFactory.mRequeridAttrs.size()) { 214 | throw new ExcelKitXmlAnalyzeException( 215 | "[" + configFile + "] missing required attributes: " 216 | + ExcelMappingFactory.mRequeridAttrs 217 | .toString()); 218 | } 219 | } 220 | 221 | private static Object validAndGetPropertyValue( 222 | String configFile, String name, String value) { 223 | String messageTemplate = String 224 | .format("[%s] Analyze failed: ", configFile, name, value); 225 | if (ExcelMappingFactory.mClazzFields.contains(name)) { 226 | try { 227 | return Class.forName(value).newInstance(); 228 | } catch (Exception e) { 229 | throw new ExcelKitXmlAnalyzeException(messageTemplate + e.getMessage()); 230 | } 231 | } 232 | if ("writeConverterExp".equals(name) || "readConverterExp".equals(name)) { 233 | for (String item : value.split(",")) { 234 | if (!item.contains("=")) { 235 | throw new ExcelKitXmlAnalyzeException(messageTemplate 236 | + "Converter Expression error, Reference:[\"1=男,2=女\" or \"男=1,女=2\"]."); 237 | } 238 | } 239 | } 240 | return value; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/com/wuwenze/poi/xlsx/ExcelXlsxReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, 吴汶泽 (wenzewoo@gmail.com). 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package com.wuwenze.poi.xlsx; 17 | 18 | import com.google.common.collect.Lists; 19 | import com.google.common.collect.Maps; 20 | import com.wuwenze.poi.config.Options; 21 | import com.wuwenze.poi.convert.ReadConverter; 22 | import com.wuwenze.poi.exception.ExcelKitEncounterNoNeedXmlException; 23 | import com.wuwenze.poi.exception.ExcelKitReadConverterException; 24 | import com.wuwenze.poi.exception.ExcelKitRuntimeException; 25 | import com.wuwenze.poi.handler.ExcelReadHandler; 26 | import com.wuwenze.poi.pojo.ExcelErrorField; 27 | import com.wuwenze.poi.pojo.ExcelMapping; 28 | import com.wuwenze.poi.pojo.ExcelProperty; 29 | import com.wuwenze.poi.util.*; 30 | import com.wuwenze.poi.validator.Validator; 31 | import org.apache.poi.openxml4j.exceptions.OpenXML4JException; 32 | import org.apache.poi.openxml4j.opc.OPCPackage; 33 | import org.apache.poi.ss.usermodel.BuiltinFormats; 34 | import org.apache.poi.ss.usermodel.DataFormatter; 35 | import org.apache.poi.xssf.eventusermodel.XSSFReader; 36 | import org.apache.poi.xssf.model.SharedStringsTable; 37 | import org.apache.poi.xssf.model.StylesTable; 38 | import org.apache.poi.xssf.usermodel.XSSFCellStyle; 39 | import org.apache.poi.xssf.usermodel.XSSFRichTextString; 40 | import org.xml.sax.Attributes; 41 | import org.xml.sax.InputSource; 42 | import org.xml.sax.SAXException; 43 | import org.xml.sax.XMLReader; 44 | import org.xml.sax.helpers.DefaultHandler; 45 | import org.xml.sax.helpers.XMLReaderFactory; 46 | 47 | import java.io.IOException; 48 | import java.io.InputStream; 49 | import java.util.Date; 50 | import java.util.Iterator; 51 | import java.util.List; 52 | import java.util.Map; 53 | 54 | /** 55 | * @author wuwenze 56 | */ 57 | public class ExcelXlsxReader extends DefaultHandler { 58 | 59 | private Integer mCurrentSheetIndex = -1, mCurrentRowIndex = 0, mCurrentCellIndex = 0; 60 | private ExcelCellType mNextCellType = ExcelCellType.STRING; 61 | private String mCurrentCellRef, mPreviousCellRef, mMaxCellRef; 62 | private SharedStringsTable mSharedStringsTable; 63 | private String mPreviousCellValue; 64 | private StylesTable mStylesTable; 65 | private Boolean mNextIsString = false; 66 | private Short mFormatIndex; 67 | private String mFormatString; 68 | 69 | private final ExcelMapping mExcelMapping; 70 | private final ExcelReadHandler mExcelReadHandler; 71 | private final Class mEntityClass; 72 | private final List mExcelRowObjectData = Lists.newArrayList(); 73 | private Integer mBeginReadRowIndex = Const.XLSX_DEFAULT_BEGIN_READ_ROW_INDEX; 74 | private final Object mEmptyCellValue = Const.XLSX_DEFAULT_EMPTY_CELL_VALUE; 75 | 76 | private final DataFormatter formatter = new DataFormatter(); 77 | 78 | 79 | public ExcelXlsxReader(Class entityClass,// 80 | ExcelMapping excelMapping, // 81 | ExcelReadHandler excelReadHandler) { 82 | this(entityClass, excelMapping, null, excelReadHandler); 83 | } 84 | 85 | public ExcelXlsxReader(Class entityClass,// 86 | ExcelMapping excelMapping, // 87 | Integer beginReadRowIndex,// 88 | ExcelReadHandler excelReadHandler) { 89 | mEntityClass = entityClass; 90 | mExcelMapping = excelMapping; 91 | if (null != beginReadRowIndex) { 92 | mBeginReadRowIndex = beginReadRowIndex; 93 | } 94 | mExcelReadHandler = excelReadHandler; 95 | } 96 | 97 | public void process(String fileName) throws ExcelKitRuntimeException { 98 | try { 99 | processAll(OPCPackage.open(fileName)); 100 | } catch (Exception e) { 101 | throw new ExcelKitRuntimeException("Only .xlsx formatted files are supported.", e); 102 | } 103 | } 104 | 105 | public void process(InputStream in) throws ExcelKitRuntimeException { 106 | try { 107 | processAll(OPCPackage.open(in)); 108 | } catch (Exception e) { 109 | throw new ExcelKitRuntimeException("Only .xlsx formatted files are supported.", e); 110 | } 111 | } 112 | 113 | private void processAll(OPCPackage pkg) 114 | throws IOException, OpenXML4JException, SAXException { 115 | XSSFReader xssfReader = new XSSFReader(pkg); 116 | mStylesTable = xssfReader.getStylesTable(); 117 | SharedStringsTable sst = xssfReader.getSharedStringsTable(); 118 | XMLReader parser = this.fetchSheetParser(sst); 119 | Iterator sheets = xssfReader.getSheetsData(); 120 | while (sheets.hasNext()) { 121 | mCurrentRowIndex = 0; 122 | mCurrentSheetIndex++; 123 | InputStream sheet = sheets.next(); 124 | InputSource sheetSource = new InputSource(sheet); 125 | parser.parse(sheetSource); 126 | sheet.close(); 127 | } 128 | pkg.close(); 129 | } 130 | 131 | public void process(String fileName, int sheetIndex) throws ExcelKitRuntimeException { 132 | try { 133 | this.processBySheet(sheetIndex, OPCPackage.open(fileName)); 134 | } catch (Exception e) { 135 | throw new ExcelKitRuntimeException("Only .xlsx formatted files are supported.", e); 136 | } 137 | } 138 | 139 | public void process(InputStream in, int sheetIndex) throws ExcelKitRuntimeException { 140 | try { 141 | this.processBySheet(sheetIndex, OPCPackage.open(in)); 142 | } catch (Exception e) { 143 | throw new ExcelKitRuntimeException("Only .xlsx formatted files are supported.", e); 144 | } 145 | } 146 | 147 | private void processBySheet(int sheetIndex, OPCPackage pkg) 148 | throws IOException, OpenXML4JException, SAXException { 149 | XSSFReader r = new XSSFReader(pkg); 150 | SharedStringsTable sst = r.getSharedStringsTable(); 151 | 152 | XMLReader parser = fetchSheetParser(sst); 153 | 154 | // 根据 rId# 或 rSheet# 查找sheet 155 | InputStream sheet = r.getSheet(Const.SAX_RID_PREFIX + (sheetIndex + 1)); 156 | mCurrentSheetIndex++; 157 | InputSource sheetSource = new InputSource(sheet); 158 | try { 159 | parser.parse(sheetSource); 160 | } catch (ExcelKitEncounterNoNeedXmlException e) { 161 | sheet = r.getSheet(Const.SAX_RID_PREFIX + (sheetIndex + 3)); 162 | sheetSource = new InputSource(sheet); 163 | parser.parse(sheetSource); 164 | } 165 | sheet.close(); 166 | pkg.close(); 167 | } 168 | 169 | @Override 170 | public void startElement( 171 | String uri, String localName, String name, Attributes attributes) { 172 | if ("sst".equals(name) || "styleSheet".equals(name)) { 173 | throw new ExcelKitEncounterNoNeedXmlException(); 174 | } 175 | // c => 单元格 176 | if (Const.SAX_C_ELEMENT.equals(name)) { 177 | String ref = attributes.getValue(Const.SAX_R_ATTR); 178 | // 前一个单元格的位置 179 | mPreviousCellRef = null == mPreviousCellRef ? ref : mCurrentCellRef; 180 | // 当前单元格的位置 181 | mCurrentCellRef = ref; 182 | // Figure out if the value is an index in the SST 183 | String cellType = attributes.getValue(Const.SAX_T_ELEMENT); 184 | String cellStyleStr = attributes.getValue(Const.SAX_S_ATTR_VALUE); 185 | mNextIsString = (null != cellType && cellType.equals(Const.SAX_S_ATTR_VALUE)); 186 | // 设定单元格类型 187 | this.setNextCellType(cellType, cellStyleStr); 188 | } 189 | mPreviousCellValue = ""; 190 | } 191 | 192 | 193 | @Override 194 | public void endElement(String uri, String localName, String name) { 195 | // Process the last contents as required. 196 | // Do now, as characters() may be called more than once 197 | if (mNextIsString) { 198 | int index = Integer.parseInt(mPreviousCellValue); 199 | mPreviousCellValue = new XSSFRichTextString(mSharedStringsTable.getEntryAt(index)) 200 | .toString(); 201 | mNextIsString = false; 202 | } 203 | 204 | // 处理单元格数据 205 | if (Const.SAX_C_ELEMENT.equals(name)) { 206 | String value = this.getCellValue(mPreviousCellValue.trim()); 207 | 208 | // 空值补齐(中) 209 | if (!mCurrentCellRef.equals(mPreviousCellRef)) { 210 | for (int i = 0; i < POIUtil.countNullCell(mCurrentCellRef, mPreviousCellRef); 211 | i++) { 212 | mExcelRowObjectData.add(mCurrentCellIndex, mEmptyCellValue); 213 | mCurrentCellIndex++; 214 | } 215 | } 216 | mExcelRowObjectData.add(mCurrentCellIndex, value); 217 | mCurrentCellIndex++; 218 | } 219 | // 如果标签名称为 row ,这说明已到行尾,通知回调处理当前行的数据 220 | else if (Const.SAX_ROW_ELEMENT.equals(name)) { 221 | if (mCurrentRowIndex == 0) { 222 | mMaxCellRef = mCurrentCellRef; 223 | } 224 | // 空值补齐(后) 225 | if (null != mMaxCellRef) { 226 | for (int i = 0; i <= POIUtil.countNullCell(mMaxCellRef, mCurrentCellRef); i++) { 227 | mExcelRowObjectData.add(mCurrentCellIndex, mEmptyCellValue); 228 | mCurrentCellIndex++; 229 | } 230 | } 231 | try { 232 | this.performVerificationAndProcessFlowRow(); 233 | } catch (Exception e) { 234 | e.printStackTrace(); 235 | } finally { 236 | mExcelRowObjectData.clear(); 237 | mCurrentRowIndex++; 238 | mCurrentCellIndex = 0; 239 | mPreviousCellRef = null; 240 | mCurrentCellRef = null; 241 | } 242 | } 243 | } 244 | 245 | @Override 246 | public void characters(char[] chars, int start, int length) { 247 | mPreviousCellValue = mPreviousCellValue.concat(new String(chars, start, length)); 248 | } 249 | 250 | private XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException { 251 | XMLReader parser = XMLReaderFactory.createXMLReader(Const.SAX_PARSER_CLASS); 252 | mSharedStringsTable = sst; 253 | parser.setContentHandler(this); 254 | return parser; 255 | } 256 | 257 | enum ExcelCellType { 258 | BOOL, ERROR, FORMULA, INLINESTR, STRING, NUMBER, DATE, NULL 259 | } 260 | 261 | private void setNextCellType(String cellType, String cellStyleStr) { 262 | mNextCellType = ExcelCellType.STRING; 263 | mFormatIndex = -1; 264 | mFormatString = null; 265 | 266 | if ("b".equals(cellType)) { 267 | mNextCellType = ExcelCellType.BOOL; 268 | } else if ("e".equals(cellType)) { 269 | mNextCellType = ExcelCellType.ERROR; 270 | } else if ("inlineStr".equals(cellType)) { 271 | mNextCellType = ExcelCellType.INLINESTR; 272 | } else if ("s".equals(cellType)) { 273 | mNextCellType = ExcelCellType.STRING; 274 | } else if ("str".equals(cellType)) { 275 | mNextCellType = ExcelCellType.FORMULA; 276 | } 277 | if (null != cellStyleStr) { 278 | int styleIndex = Integer.parseInt(cellStyleStr); 279 | XSSFCellStyle style = mStylesTable.getStyleAt(styleIndex); 280 | mFormatIndex = style.getDataFormat(); 281 | mFormatString = style.getDataFormatString(); 282 | if ("m/d/yy".equals(mFormatString)) { 283 | mNextCellType = mNextCellType.DATE; 284 | mFormatString = "yyyy-MM-dd hh:mm:ss.SSS"; 285 | } 286 | if (null == mFormatString) { 287 | mNextCellType = mNextCellType.NULL; 288 | mFormatString = BuiltinFormats.getBuiltinFormat(mFormatIndex); 289 | } 290 | } 291 | } 292 | 293 | private String getCellValue(String value) { 294 | String thisStr; 295 | switch (mNextCellType) { 296 | case BOOL: 297 | return value.charAt(0) == '0' ? "FALSE" : "TRUE"; 298 | case ERROR: 299 | return "\"ERROR:" + value + '"'; 300 | case FORMULA: 301 | return '"' + value + '"'; 302 | case INLINESTR: 303 | return new XSSFRichTextString(value).toString(); 304 | case STRING: 305 | return String.valueOf(value); 306 | case NUMBER: 307 | if (mFormatString != null) { 308 | thisStr = formatter.formatRawCellContents(Double.parseDouble(value), mFormatIndex, mFormatString).trim(); 309 | } else { 310 | thisStr = value; 311 | } 312 | 313 | thisStr = thisStr.replace("_", "").trim(); 314 | break; 315 | case DATE: 316 | thisStr = formatter.formatRawCellContents(Double.parseDouble(value), mFormatIndex, mFormatString); 317 | // 对日期字符串作特殊处理 318 | thisStr = thisStr.replace(" ", "T"); 319 | break; 320 | default: 321 | thisStr = ""; 322 | break; 323 | } 324 | return thisStr; 325 | } 326 | 327 | private final static String CHECK_MAP_KEY_OF_VALUE = "CELL_VALUE"; 328 | private final static String CHECK_MAP_KEY_OF_ERROR = "CELL_ERROR"; 329 | 330 | private void performVerificationAndProcessFlowRow() throws Exception { 331 | if (mCurrentRowIndex >= mBeginReadRowIndex) { 332 | List propertyList = mExcelMapping.getPropertyList(); 333 | Integer excelRowDataSize = mExcelRowObjectData.size(); 334 | Integer excelMappingPropertySize = propertyList.size(); 335 | // 空值补齐(前) 336 | for (int i = 0; i < excelMappingPropertySize - excelRowDataSize; i++) { 337 | mExcelRowObjectData.add(i, mEmptyCellValue); 338 | } 339 | 340 | if (!this.rowObjectDataIsAllEmptyCellValue()) { 341 | Object entity = mEntityClass.newInstance(); 342 | List errorFields = Lists.newArrayList(); 343 | for (int i = 0; i < propertyList.size(); i++) { 344 | ExcelProperty property = propertyList.get(i); 345 | Map checkAndConvertPropertyRetMap = this.checkAndConvertProperty(i, property, mExcelRowObjectData.get(i)); 346 | Object errorFieldObject = checkAndConvertPropertyRetMap.get( 347 | ExcelXlsxReader.CHECK_MAP_KEY_OF_ERROR); 348 | if (null != errorFieldObject) { 349 | errorFields.add((ExcelErrorField) errorFieldObject); 350 | } 351 | if (errorFields.isEmpty()) { 352 | Object propertyValue = checkAndConvertPropertyRetMap.get( 353 | ExcelXlsxReader.CHECK_MAP_KEY_OF_VALUE); 354 | BeanUtil.setComplexProperty(entity, property.getName(), propertyValue); 355 | } 356 | } 357 | if (errorFields.isEmpty()) { 358 | mExcelReadHandler.onSuccess(mCurrentSheetIndex, mCurrentRowIndex, entity); 359 | return; 360 | } 361 | mExcelReadHandler.onError(mCurrentSheetIndex, mCurrentRowIndex, errorFields); 362 | } 363 | } 364 | } 365 | 366 | private boolean rowObjectDataIsAllEmptyCellValue() { 367 | int emptyObjectCount = 0; 368 | for (Object excelRowObjectData : mExcelRowObjectData) { 369 | if ((null == excelRowObjectData) // 370 | || excelRowObjectData.equals(mEmptyCellValue) // 371 | || ValidatorUtil.isEmpty((String) excelRowObjectData)) { 372 | emptyObjectCount++; 373 | } 374 | } 375 | return emptyObjectCount == mExcelRowObjectData.size(); 376 | } 377 | 378 | private Map checkAndConvertProperty(Integer cellIndex, 379 | ExcelProperty property, 380 | Object propertyValue) { 381 | 382 | if(null == propertyValue || ValidatorUtil.isEmpty((String) propertyValue) || Const.XLSX_DEFAULT_EMPTY_CELL_VALUE.equals(propertyValue)) { 383 | // required 384 | Boolean required = property.getRequired(); 385 | if (null != required && required) { 386 | return this.buildCheckAndConvertPropertyRetMap(cellIndex, property, propertyValue, "单元格的值必须填写"); 387 | } 388 | 389 | // empty cell doesn't need to check anymore 390 | return this.buildCheckAndConvertPropertyRetMap(cellIndex, property, null, null); 391 | } 392 | 393 | // maxLength 394 | Integer maxLength = property.getMaxLength(); 395 | if (-1 != maxLength) { 396 | if (String.valueOf(propertyValue).length() > maxLength) { 397 | return this.buildCheckAndConvertPropertyRetMap(cellIndex, property, propertyValue, "超过最大长度: " + maxLength); 398 | } 399 | } 400 | 401 | // dateFormat 402 | String dateFormat = property.getDateFormat(); 403 | if (!ValidatorUtil.isEmpty(dateFormat)) { 404 | try { 405 | // 时间格式转换后,直接返回。 406 | Date parseDateValue = DateUtil.parse(dateFormat, propertyValue); 407 | return this.buildCheckAndConvertPropertyRetMap(cellIndex, property, parseDateValue, null); 408 | } catch (Exception e) { 409 | return this.buildCheckAndConvertPropertyRetMap(// 410 | cellIndex, property, propertyValue, "时间格式解析失败 [" + dateFormat + "]"); 411 | } 412 | } 413 | 414 | // options 415 | Options options = property.getOptions(); 416 | if (null != options) { 417 | Object[] values = options.get(); 418 | if (null != values && values.length > 0) { 419 | boolean containInOptions = false; 420 | for (Object value : values) { 421 | if (propertyValue.equals(value)) { 422 | containInOptions = true; 423 | break; 424 | } 425 | } 426 | if (!containInOptions) { 427 | return this.buildCheckAndConvertPropertyRetMap(// 428 | cellIndex, property, propertyValue, "[" + propertyValue + "]不是规定的下拉框的值"); 429 | } 430 | } 431 | } 432 | 433 | // regularExp 434 | String regularExp = property.getRegularExp(); 435 | if (!ValidatorUtil.isEmpty(regularExp)) { 436 | if (!RegexUtil.isMatches(regularExp, propertyValue)) { 437 | String regularExpMessage = property.getRegularExpMessage(); 438 | String validErrorMessage = !ValidatorUtil.isEmpty(regularExpMessage) ? 439 | regularExpMessage : "正则表达式校验失败 [" + regularExp + "]"; 440 | return this.buildCheckAndConvertPropertyRetMap(// 441 | cellIndex, property, propertyValue, validErrorMessage); 442 | } 443 | } 444 | 445 | // validator 446 | Validator validator = property.getValidator(); 447 | if (null != validator) { 448 | String validErrorMessage = validator.valid(propertyValue); 449 | if (null != validErrorMessage) { 450 | return this.buildCheckAndConvertPropertyRetMap(// 451 | cellIndex, property, propertyValue, validErrorMessage); 452 | } 453 | } 454 | 455 | // readConverterExp && readConverter (按照优先级处理) 456 | String readConverterExp = property.getReadConverterExp(); 457 | ReadConverter readConverter = property.getReadConverter(); 458 | if (!ValidatorUtil.isEmpty(readConverterExp)) { 459 | try { 460 | Object convertPropertyValue = POIUtil.convertByExp(propertyValue, readConverterExp); 461 | return this.buildCheckAndConvertPropertyRetMap(// 462 | cellIndex, property, convertPropertyValue, null); 463 | } catch (Exception e) { 464 | return this.buildCheckAndConvertPropertyRetMap(// 465 | cellIndex, property, propertyValue, "由于readConverterExp表达式的值不规范导致转换失败"); 466 | } 467 | } else if (null != readConverter) { 468 | try { 469 | return this.buildCheckAndConvertPropertyRetMap(// 470 | cellIndex, property, readConverter.convert(propertyValue), null); 471 | } catch (ExcelKitReadConverterException e) { 472 | return this.buildCheckAndConvertPropertyRetMap(// 473 | cellIndex, property, propertyValue, e.getMessage()); 474 | } 475 | } 476 | return this.buildCheckAndConvertPropertyRetMap(cellIndex, property, propertyValue, null); 477 | } 478 | 479 | private Map buildCheckAndConvertPropertyRetMap(Integer cellIndex, 480 | ExcelProperty property,// 481 | Object propertyValue, String validErrorMessage) { 482 | Map resultMap = Maps.newHashMap(); 483 | resultMap.put(ExcelXlsxReader.CHECK_MAP_KEY_OF_VALUE, propertyValue); 484 | if (null != validErrorMessage) { 485 | resultMap.put(ExcelXlsxReader.CHECK_MAP_KEY_OF_ERROR, ExcelErrorField.builder()// 486 | .cellIndex(cellIndex)// 487 | .column(property.getColumn())// 488 | .name(property.getName())// 489 | .errorMessage(validErrorMessage)// 490 | .build()); 491 | } 492 | return resultMap; 493 | } 494 | } 495 | --------------------------------------------------------------------------------