Selaa lähdekoodia

feat: 整合公共代码

zhangying 1 viikko sitten
vanhempi
commit
097aacbaed
62 muutettua tiedostoa jossa 4091 lisäystä ja 14 poistoa
  1. 19 0
      zjrs-service-backend/README.md
  2. 36 13
      zjrs-service-backend/pom.xml
  3. 88 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/datatype/CustomLocalDateDeserializer.java
  4. 34 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/datatype/CustomLocalDateSerializer.java
  5. 88 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/datatype/CustomLocalDateTimeDeserializer.java
  6. 34 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/datatype/CustomLocalDateTimeSerializer.java
  7. 17 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/ExcelBeansConfiguration.java
  8. 333 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/ExcelTemplate.java
  9. 193 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/builder/ExportBuilder.java
  10. 94 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/builder/ExportDefinition.java
  11. 124 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/builder/ImportBuilder.java
  12. 82 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/builder/ImportDefinition.java
  13. 31 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/exception/IllegalHeaderException.java
  14. 29 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/exception/IllegalSheetException.java
  15. 25 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/CellStyler.java
  16. 22 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/DiscreteMapper.java
  17. 94 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/DynamicHeaderMapper.java
  18. 58 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/FieldCellStyler.java
  19. 102 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/FieldMergeCellStyler.java
  20. 22 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/HeaderMapper.java
  21. 20 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/OriginalCellStyler.java
  22. 18 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/RowJudge.java
  23. 23 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/RowMapper.java
  24. 18 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/RowStyler.java
  25. 34 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SheetCoordinate.java
  26. 30 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SimpleDataCellStyler.java
  27. 18 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SimpleDataRowStyler.java
  28. 31 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SimpleHeaderCellStyler.java
  29. 18 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SimpleHeaderRowStyler.java
  30. 87 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/StaticHeaderMapper.java
  31. 57 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/StringHeaderMapper.java
  32. 160 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/util/ExcelUtils.java
  33. 48 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/util/MapperUtils.java
  34. 43 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/WordTemplate.java
  35. 40 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/builder/ExportBuilder.java
  36. 28 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/builder/ExportDefinition.java
  37. 21 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/exception/IllegalMarkException.java
  38. 26 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/util/NameUtil.java
  39. 656 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/util/POIUtil.java
  40. 150 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/utils/IdcardUtil.java
  41. 61 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/utils/SpringContextUtil.java
  42. 192 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/utils/StringUtil.java
  43. 19 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/Chinese.java
  44. 21 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/ChineseValidator.java
  45. 19 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/IdCard.java
  46. 411 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/IdCardValidator.java
  47. 19 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/Money.java
  48. 20 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/MoneyValidator.java
  49. 18 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/Number.java
  50. 19 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/NumberLetters.java
  51. 20 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/NumberLettersValidator.java
  52. 21 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/NumberValidator.java
  53. 18 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/PhoneNumber.java
  54. 21 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/PhoneNumberValidator.java
  55. 19 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/SpecialSymbols.java
  56. 20 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/SpecialSymbolsValidator.java
  57. 19 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/TelPhone.java
  58. 20 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/TelPhoneValidator.java
  59. 80 0
      zjrs-service-backend/src/main/java/com/zjrs/appcomm/web/ZjrsControllerAdvice.java
  60. 0 1
      zjrs-service-backend/src/main/resources/application.properties
  61. 3 0
      zjrs-service-backend/src/main/resources/application.yaml
  62. BIN
      zjrs-service-backend/src/main/resources/lib/leaf6-uni-cloud-starter-1.0.0-SinoBest.20.jar

+ 19 - 0
zjrs-service-backend/README.md

@@ -0,0 +1,19 @@
+# zjrs-service 说明
+
+## SinoGear的兼容覆盖
+为了兼容部分SinoGear的依赖,需要在项目中使用同package同Class名的方式进行覆盖。此部分覆盖的内容在包`cn.sinobest.sinogear`中。
+
+## 运行时依赖
+此工程是一个微服务的工程,运行需要依赖Eureka、Config配置中心、以及Zuul网关等组件。
+
+### 鉴权
+微服务的鉴权在Zuul网关及微服务本身中也会进行,并且鉴权是对URL一级进行的,所有访问的URL均需配置适当的权限才能正常访问。
+
+在配置权限的URL地址时(这里使用SinoGear的权限配置功能),需要谨记URL中必须含有微服务的对应路由信息,配置为`leaf.auth.client.client-authorization.route-path-prefix`,默认取值为`spring.application.name`。例如zjrs-zwnw-service需要配置一个路径为`/api/**`的权限,路由信息取默认值为zjrs-zwnw-service,那么需要写成`/zjrs-zwnw-service/api/**`。
+
+## 示例及规范
+《04_01开发类-金保工程二期云技术框架LEAF6.2开发标准规范&开发手册》第4章开发示例及第5章leaf6开发规范
+
+1、controller接收dto对象、调用bpo
+2、bpo将dto转为vo、注解校验、业务规则校验、调用blo
+3、blo调用mapper

+ 36 - 13
zjrs-service-backend/pom.xml

@@ -2,44 +2,67 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
+
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>3.5.11</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
+
     <groupId>com</groupId>
     <artifactId>zjrs</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>zjrs-service-backend</name>
     <description>zjrs-service-backend</description>
-    <url/>
-    <licenses>
-        <license/>
-    </licenses>
-    <developers>
-        <developer/>
-    </developers>
-    <scm>
-        <connection/>
-        <developerConnection/>
-        <tag/>
-        <url/>
-    </scm>
+
     <properties>
         <java.version>17</java.version>
     </properties>
+
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <!-- Hutool工具包 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>4.5.1</version>
+        </dependency>
+
+        <!-- leaf6-uni-cloud-starter 本地依赖 -->
+        <dependency>
+            <groupId>com.leaf6</groupId>
+            <artifactId>leaf6-uni-cloud-starter</artifactId>
+            <version>1.0.0-SinoBest.20</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/src/main/resources/lib/leaf6-uni-cloud-starter-1.0.0-SinoBest.20.jar</systemPath>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>4.1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>4.1.2</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 88 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/datatype/CustomLocalDateDeserializer.java

@@ -0,0 +1,88 @@
+package com.zjrs.appcomm.datatype;
+
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author Petty
+ */
+public class CustomLocalDateDeserializer extends JsonDeserializer<LocalDate> {
+
+    private DateTimeFormatter dateTimeFormatter;
+
+    public CustomLocalDateDeserializer() {
+        dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    }
+
+    /**
+     * Method that can be called to ask implementation to deserialize
+     * JSON content into the value type this serializer handles.
+     * Returned instance is to be constructed by method itself.
+     * <p>
+     * Pre-condition for this method is that the parser points to the
+     * first event that is part of value to deserializer (and which
+     * is never JSON 'null' literal, more on this below): for simple
+     * types it may be the only value; and for structured types the
+     * Object start marker or a FIELD_NAME.
+     * </p>
+     * <p>
+     * The two possible input conditions for structured types result
+     * from polymorphism via fields. In the ordinary case, Jackson
+     * calls this method when it has encountered an OBJECT_START,
+     * and the method implementation must advance to the next token to
+     * see the first field name. If the application configures
+     * polymorphism via a field, then the object looks like the following.
+     * <pre>
+     *      {
+     *          "@class": "class name",
+     *          ...
+     *      }
+     *  </pre>
+     * Jackson consumes the two tokens (the <tt>@class</tt> field name
+     * and its value) in order to learn the class and select the deserializer.
+     * Thus, the stream is pointing to the FIELD_NAME for the first field
+     * after the @class. Thus, if you want your method to work correctly
+     * both with and without polymorphism, you must begin your method with:
+     * <pre>
+     *       if (p.getCurrentToken() == JsonToken.START_OBJECT) {
+     *         p.nextToken();
+     *       }
+     *  </pre>
+     * This results in the stream pointing to the field name, so that
+     * the two conditions align.
+     * <p>
+     * Post-condition is that the parser will point to the last
+     * event that is part of deserialized value (or in case deserialization
+     * fails, event that was not recognized or usable, which may be
+     * the same event as the one it pointed to upon call).
+     * <p>
+     * Note that this method is never called for JSON null literal,
+     * and thus deserializers need (and should) not check for it.
+     *
+     * @param p    Parsed used for reading JSON content
+     * @param ctxt Context that can be used to access information about
+     *             this deserialization activity.
+     * @return Deserialized value
+     */
+    @Override
+    public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+        try {
+            String string = p.getText().trim();
+            if (StrUtil.isNotEmpty(string)) {
+                return LocalDate.parse(string, dateTimeFormatter);
+            } else {
+                return null;
+            }
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 34 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/datatype/CustomLocalDateSerializer.java

@@ -0,0 +1,34 @@
+package com.zjrs.appcomm.datatype;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author Petty
+ */
+public class CustomLocalDateSerializer extends JsonSerializer<LocalDate> {
+
+    private DateTimeFormatter dateTimeFormatter;
+
+    public CustomLocalDateSerializer() {
+        dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    }
+
+    /**
+     * Method that can be called to ask implementation to serialize
+     * values of type this serializer handles.
+     *
+     * @param value       Value to serialize; can <b>not</b> be null.
+     * @param gen         Generator used to output resulting Json content
+     * @param serializers Provider that can be used to get serializers for
+     */
+    @Override
+    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        gen.writeString(value.format(dateTimeFormatter));
+    }
+}

+ 88 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/datatype/CustomLocalDateTimeDeserializer.java

@@ -0,0 +1,88 @@
+package com.zjrs.appcomm.datatype;
+
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author Petty
+ */
+public class CustomLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
+
+    private DateTimeFormatter dateTimeFormatter;
+
+    public CustomLocalDateTimeDeserializer() {
+        dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    }
+
+    /**
+     * Method that can be called to ask implementation to deserialize
+     * JSON content into the value type this serializer handles.
+     * Returned instance is to be constructed by method itself.
+     * <p>
+     * Pre-condition for this method is that the parser points to the
+     * first event that is part of value to deserializer (and which
+     * is never JSON 'null' literal, more on this below): for simple
+     * types it may be the only value; and for structured types the
+     * Object start marker or a FIELD_NAME.
+     * </p>
+     * <p>
+     * The two possible input conditions for structured types result
+     * from polymorphism via fields. In the ordinary case, Jackson
+     * calls this method when it has encountered an OBJECT_START,
+     * and the method implementation must advance to the next token to
+     * see the first field name. If the application configures
+     * polymorphism via a field, then the object looks like the following.
+     * <pre>
+     *      {
+     *          "@class": "class name",
+     *          ...
+     *      }
+     *  </pre>
+     * Jackson consumes the two tokens (the <tt>@class</tt> field name
+     * and its value) in order to learn the class and select the deserializer.
+     * Thus, the stream is pointing to the FIELD_NAME for the first field
+     * after the @class. Thus, if you want your method to work correctly
+     * both with and without polymorphism, you must begin your method with:
+     * <pre>
+     *       if (p.getCurrentToken() == JsonToken.START_OBJECT) {
+     *         p.nextToken();
+     *       }
+     *  </pre>
+     * This results in the stream pointing to the field name, so that
+     * the two conditions align.
+     * <p>
+     * Post-condition is that the parser will point to the last
+     * event that is part of deserialized value (or in case deserialization
+     * fails, event that was not recognized or usable, which may be
+     * the same event as the one it pointed to upon call).
+     * <p>
+     * Note that this method is never called for JSON null literal,
+     * and thus deserializers need (and should) not check for it.
+     *
+     * @param p    Parsed used for reading JSON content
+     * @param ctxt Context that can be used to access information about
+     *             this deserialization activity.
+     * @return Deserialized value
+     */
+    @Override
+    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+        try {
+            String string = p.getText().trim();
+            if (StrUtil.isNotEmpty(string)) {
+                return LocalDateTime.parse(string, dateTimeFormatter);
+            } else {
+                return null;
+            }
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 34 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/datatype/CustomLocalDateTimeSerializer.java

@@ -0,0 +1,34 @@
+package com.zjrs.appcomm.datatype;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author Petty
+ */
+public class CustomLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
+
+    private DateTimeFormatter dateTimeFormatter;
+
+    public CustomLocalDateTimeSerializer() {
+        dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    }
+
+    /**
+     * Method that can be called to ask implementation to serialize
+     * values of type this serializer handles.
+     *
+     * @param value       Value to serialize; can <b>not</b> be null.
+     * @param gen         Generator used to output resulting Json content
+     * @param serializers Provider that can be used to get serializers for
+     */
+    @Override
+    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        gen.writeString(value.format(dateTimeFormatter));
+    }
+}

+ 17 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/ExcelBeansConfiguration.java

@@ -0,0 +1,17 @@
+package com.zjrs.appcomm.template.excel;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ExcelBeansConfiguration {
+
+	@Autowired private ObjectMapper objectMapper;
+	
+	@Bean("excelTemplate")
+	public ExcelTemplate excelTemplate() {
+		return new ExcelTemplate(objectMapper);
+	}
+}

+ 333 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/ExcelTemplate.java

@@ -0,0 +1,333 @@
+package com.zjrs.appcomm.template.excel;
+
+import com.zjrs.appcomm.template.excel.builder.ExportDefinition;
+import com.zjrs.appcomm.template.excel.builder.ImportDefinition;
+import com.zjrs.appcomm.template.excel.mapper.*;
+import com.zjrs.appcomm.template.excel.util.ExcelUtils;
+import com.zjrs.appcomm.template.excel.util.MapperUtils;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.poi.ss.usermodel.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 操作Excel的工具,目前包括导出和导入excel功能
+ * 
+ * <p>
+ * 导出功能范例(无离散数据):
+ * <blockquote><pre>
+ * List<Example> datas = new ArrayList<>();
+ * datas.add(new Example(...));
+ * datas.add(new Example(...));
+ * ExportDefinition<?, Example> exportDefinition = ExportDefinition.builder(null, Example.class)
+ * 		.listData(datas) // 注入列表数据
+ * 		.build();
+ * byte[] b = excelTemplate.exportFile(exportDefinition);
+ * </pre></blockquote>
+ * 
+ * <p>
+ * 导出功能范例(有离散数据):
+ * <blockquote><pre>
+ * List<Example> datas = new ArrayList<>(...);
+ * datas.add(new Example(...));
+ * datas.add(new Example(...));
+ * DExample dData = new DExample();
+ * ExportDefinition<DExample, Example> exportDefinition = ExportDefinition.builder(DExample.class, Example.class)
+ * 		.putDiscreteFieldCoordinate("d1", ?, ?)
+ * 		.putDiscreteFieldCoordinate("d2", ?, ?)
+ * 		.listFields(new String[] { "l1", "l2", "l3", "l4", "l5" })
+ * 		.listData(datas) // 注入列表数据
+ * 		.build();
+ * byte[] b = excelTemplate.exportFile(exportDefinition);
+ * </pre></blockquote><p>
+ * 
+ * <p>
+ * 导入功能范例(无离散数据):
+ * <blockquote><pre>
+ * </pre></blockquote>
+ * 
+ * <p>
+ * 导入功能范例(有离散数据):
+ * <blockquote><pre>
+ * </pre></blockquote>
+ * @author froms
+ *
+ * 其他:<br />
+ * 1.针对导出时列需要进行相同值合并的需求,可以使用ExportBuilder的addListDataCellStyler(field, new FieldMergeCellStyler(relatedField))方法进行配置
+ */
+
+public class ExcelTemplate {
+
+	private static final Logger LOG = LoggerFactory.getLogger(ExcelTemplate.class);
+	
+	private ObjectMapper objectMapper;
+	
+	public ExcelTemplate() {
+		this.objectMapper = new ObjectMapper();
+	}
+	
+	public ExcelTemplate(ObjectMapper objectMapper) {
+		this.objectMapper = objectMapper;
+	}
+	
+	/**
+	 * 导出Excel数据
+	 * @param <D> 离散数据的定义
+	 * @param <L> 列表数据行的定义
+	 * @param exportDefinition 导出Excel数据定义
+	 * @throws Exception 
+	 */
+	public <D, L> byte[] exportFile(ExportDefinition<D, L> exportDefinition) throws Exception {
+		/* 开始处理离散数据 */
+		if (exportDefinition.getDiscreteData() != null) {
+			exportDiscreteData(exportDefinition.getSheet(), exportDefinition.getDiscreteFieldCoordinates(), exportDefinition.getDiscreteDefaultDataStyler(), exportDefinition.getDiscreteData());
+		}
+		
+		/* 开始处理列表表头数据 */
+		if (!ObjectUtils.isEmpty(exportDefinition.getListHeaders())) {
+			exportHeaderData(exportDefinition.getSheet(), exportDefinition.getListHeaderRowIndex(), exportDefinition.getListHeaders(), exportDefinition.getListFields(), exportDefinition.getListHeaderRowStyler(), exportDefinition.getListHeaderCellStyler());
+		}
+		/* 开始处理列表数据 */
+		if (exportDefinition.getListData() != null) {
+			moveListTemplate(exportDefinition.getSheet(), exportDefinition.getListDataRowIndex(), exportDefinition.getListData());
+			copyListTemplate(exportDefinition.getSheet(), exportDefinition.getListDataRowIndex(), exportDefinition.getListData());
+			
+			exportListRowData(exportDefinition.getSheet(), exportDefinition.getListDataRowIndex(), exportDefinition.getListFields(), exportDefinition.getListDataRowStyler(), exportDefinition.getListDataCellStyler(), exportDefinition.getListData());
+		}
+		
+		try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+			exportDefinition.getSheet().getWorkbook().write(os);
+			return os.toByteArray();
+		}
+	}
+	
+	private <D> void exportDiscreteData(Sheet sheet, Map<String, SheetCoordinate> discreteFieldCoordinates, CellStyler discreteDefaultDataStyler, D discreteData) throws Exception {
+		Map<String, Object> discreteMap = MapperUtils.object2Map(discreteData, objectMapper);
+		for (Map.Entry<String, SheetCoordinate> discreteFieldCoordinate: discreteFieldCoordinates.entrySet()) {
+			String name = discreteFieldCoordinate.getKey();
+			SheetCoordinate coordinate = discreteFieldCoordinate.getValue();
+			Object discreteValue = discreteMap.get(name);
+			
+			Row row = ExcelUtils.getRow(sheet, coordinate.getRow(), true);
+			Cell cell = ExcelUtils.getCell(row, coordinate.getCol(), true);
+			boolean doValue = true;
+			if (coordinate.getStyler() == null) {
+				if (discreteDefaultDataStyler != null) {
+					doValue = discreteDefaultDataStyler.style(cell, discreteValue, name, -1, coordinate.getRow(), coordinate.getCol(), discreteMap);
+				}
+			} else {
+				doValue = coordinate.getStyler().style(cell, discreteValue, name, -1, coordinate.getRow(), coordinate.getCol(), discreteMap);
+			}
+			if (doValue) {
+				ExcelUtils.setCellValue(cell, discreteValue);
+			}
+		}
+	}
+	private void exportHeaderData(Sheet sheet, int listHeaderRowIndex, String[][] listHeaders, String[] listFields, RowStyler listHeaderRowStyler, CellStyler listHeaderCellStyler) {
+		for (int i = 0; i < listHeaders.length; i++) {
+			String[] listOneHeaders = listHeaders[i];
+			int rowIndex = listHeaderRowIndex + i;
+			Row headerRow = ExcelUtils.getRow(sheet, rowIndex, true);
+			boolean doCell = listHeaderRowStyler.style(headerRow, 0);
+			if (doCell) {
+				for (int j = 0; j < listOneHeaders.length; j++) {
+					String header = listOneHeaders[j];
+					Cell headerCell = ExcelUtils.getCell(headerRow, j, true);
+					boolean doValue = listHeaderCellStyler.style(headerCell, header, j < listFields.length ? listFields[j] : null, i, rowIndex, j, null);
+					if (doValue) {
+						ExcelUtils.setCellValue(headerCell, header);
+					}
+				}
+			}
+		}
+	}
+	private <L> void moveListTemplate(Sheet sheet, int listDataRowIndex, List<L> listData) {
+		/* 下移模板故有内容,为数据腾出空间 */
+		if (listData.size() > 1 && listDataRowIndex + 1 <= sheet.getLastRowNum()) {
+			sheet.shiftRows(listDataRowIndex + 1, sheet.getLastRowNum(), listData.size() - 1, true, true);
+		}
+	}
+	private <L> void copyListTemplate(Sheet sheet, int listDataRowIndex, List<L> listData) {
+		/* 复制模板行 */
+		Row templateRow = ExcelUtils.getRow(sheet, listDataRowIndex, true);
+		for (int i = 1; i < listData.size(); i++) {
+			Row dataRow = ExcelUtils.getRow(sheet, listDataRowIndex + i, true);
+			dataRow.setHeightInPoints(templateRow.getHeightInPoints());
+			for (int j = 0; j < templateRow.getLastCellNum(); j++) {
+				Cell templateCell = templateRow.getCell(j);
+				Cell dataCell = dataRow.getCell(j);
+				if (templateCell == null) {
+					if (dataCell != null) {
+						dataRow.removeCell(dataCell);
+					}
+				} else {
+					if (dataCell == null) {
+						dataCell = dataRow.createCell(j);
+					}
+					dataCell.setCellStyle(templateCell.getCellStyle());
+				}
+			}
+		}
+	}
+	private <L> void exportListRowData(Sheet sheet, int listDataRowIndex, String[] listFields, RowStyler listDataRowStyler, CellStyler listDataCellStyler, List<L> listData) throws Exception {
+		for (int i = 0; i < listData.size(); i++) {
+			Map<String, Object> listMap = MapperUtils.object2Map(listData.get(i), objectMapper);
+			int rowIndex = listDataRowIndex + i;
+			Row dataRow = ExcelUtils.getRow(sheet, rowIndex, true);
+			boolean doCell = true;
+			if (listDataRowStyler != null) {
+				doCell = listDataRowStyler.style(dataRow, i);
+			}
+			if (doCell) {
+				exportListCellData(dataRow, i, rowIndex, listFields, listDataCellStyler, listMap);
+			}
+		}
+	}
+	private void exportListCellData(Row dataRow, int dataIndex, int rowIndex, String[] listFields, CellStyler listDataCellStyler, Map<?, ?> listMap) {
+		Cell cell = dataRow.createCell(dataIndex);
+		CellStyle oldCellStyle = cell.getCellStyle();
+		CellStyle newCellStyle = cell.getSheet().getWorkbook().createCellStyle();
+		newCellStyle.cloneStyleFrom(oldCellStyle);
+		newCellStyle.setBorderTop(BorderStyle.THIN);
+		newCellStyle.setBorderBottom(BorderStyle.THIN);
+		newCellStyle.setBorderLeft(BorderStyle.THIN);
+		newCellStyle.setBorderRight(BorderStyle.THIN);
+		for (int j = 0; j < listFields.length; j++) {
+			String field = listFields[j];
+			Cell dataCell = ExcelUtils.getCell(dataRow, j, true);
+			Object value = listMap.get(field);
+			boolean doValue = true;
+			if (listDataCellStyler != null) {
+				if (listDataCellStyler instanceof SimpleDataCellStyler){
+					dataCell.setCellStyle(newCellStyle);
+					doValue = true;
+				}else{
+					doValue = listDataCellStyler.style(dataCell, value, field, dataIndex, rowIndex, j, listMap);
+				}
+			}
+			if (doValue) {
+				ExcelUtils.setCellValue(dataCell, value);
+			}
+		}
+	}
+	
+	/**
+	 * 导入Excel数据
+	 * @param <D> 离散数据的定义
+	 * @param <L> 行数据的定义
+	 * @param importDefinition 导入Excel数据定义
+	 * @return 成功返回true,失败返回false
+	 */
+	public <D, L> void importFile(ImportDefinition<D, L> importDefinition) {
+		if (importDefinition.getDiscreteClass() != null) {
+			importDiscreteData(importDefinition.getSheet(), importDefinition.getDiscreteFieldCoordinates(), importDefinition.getDiscreteMapper(), importDefinition.getDiscreteClass());
+		}
+		
+		if (importDefinition.getListClass() != null) {
+			Row[] rows = new Row[importDefinition.getHeaderRowCount()];
+			for (int i = 0; i < importDefinition.getHeaderRowCount(); i++) {
+				rows[i] = ExcelUtils.getRow(importDefinition.getSheet(), importDefinition.getHeaderRowIndex() + i, false);
+			}
+			String[] names = importDefinition.getHeaderMapper().mapper(rows);
+			
+			importListData(importDefinition.getSheet(), names, importDefinition.isCalculateMergedCell(), importDefinition.getDataRowIndex(), importDefinition.getListFilter(), importDefinition.getListfinishedJudge(), importDefinition.getRowMapper(), importDefinition.getListClass());
+		}
+	}
+	
+	private <D> void importDiscreteData(Sheet sheet, Map<String, SheetCoordinate> discreteFieldCoordinates, DiscreteMapper<D> discreteMapper, Class<D> discreteType) {
+		Map<String, Cell> discreteCellMap = new HashMap<>();
+		Map<String, Object> discreteMap = new HashMap<>();
+		for (Map.Entry<String, SheetCoordinate> discreteFieldCoordinate: discreteFieldCoordinates.entrySet()) {
+			String name = discreteFieldCoordinate.getKey();
+			SheetCoordinate coordinate = discreteFieldCoordinate.getValue();
+			
+			Cell cell = null;
+			Row row = ExcelUtils.getRow(sheet, coordinate.getRow(), false);
+			if (row != null) {
+				cell = ExcelUtils.getCell(row, coordinate.getCol(), false);
+			}
+			discreteCellMap.put(name, cell);
+			discreteMap.put(name, ExcelUtils.getCellValue(cell));
+		}
+		D d = null;
+		try {
+			d = objectMapper.convertValue(discreteMap, discreteType);
+		} catch (Exception e) {
+			LOG.debug("导入离散数据转换异常", e);
+		}
+		discreteMapper.mapper(d, discreteMap, discreteCellMap);
+	}
+	
+	private <L> void importListData(Sheet sheet, String[] names, boolean calculateMergedCell, int dataRowIndex, RowJudge listFilter, RowJudge listfinishedJudge, RowMapper<L> rowMapper, Class<L> listType) {
+		int dataIndex = 0;
+		for (int i = dataRowIndex; i <= sheet.getLastRowNum(); i++) {
+			Row dataRow = ExcelUtils.getRow(sheet, i, false);
+			if (dataRow == null) {
+				LOG.debug("跳过空行(行空)");
+				continue;
+			}
+			if (listfinishedJudge != null) {
+				boolean judge = listfinishedJudge.judge(dataRow, i);
+				if (judge) {
+					break;
+				}
+			}
+			if (listFilter != null) {
+				boolean judge = listFilter.judge(dataRow, i);
+				if (judge) {
+					continue;
+				}
+			}
+			
+			int num = importListCellData(dataRow, calculateMergedCell, dataIndex, i, names, rowMapper, listType);
+			dataIndex += num;
+		}
+	}
+	private <L> int importListCellData(Row dataRow, boolean calculateMergedCell, int dataIndex, int rowIndex, String[] names, RowMapper<L> rowMapper, Class<L> listType) {
+		boolean isDataEmpty = true;
+		Map<String, Cell> cellMap = new HashMap<>();
+		Map<String, Object> valuesMap = new HashMap<>();
+		for (int j = 0; j < dataRow.getLastCellNum(); j++) {
+			if (j >= names.length) {
+				LOG.debug("跳过无用列");
+				break;
+			}
+			String name = names[j];
+			if (StringUtils.isEmpty(name)) {
+				continue;
+			}
+			Cell cell = null;
+			if (calculateMergedCell) {
+				cell = ExcelUtils.getCellMerged(dataRow.getSheet(), rowIndex, j);
+			} else {
+				cell = ExcelUtils.getCell(dataRow, j, false);
+			}
+			Object value = ExcelUtils.getCellValue(cell);
+			if (!StringUtils.isEmpty(value)) {
+				isDataEmpty = false;
+			}
+			cellMap.put(name, cell);
+			valuesMap.put(name, value);
+		}
+		if (isDataEmpty) {
+			LOG.debug("跳过空行(数据空)");
+			return 0;
+		}
+		
+		L t = null;
+		try {
+			t = objectMapper.convertValue(valuesMap, listType);
+		} catch (Exception e) {
+			LOG.debug("导入列表数据转换异常", e);
+		}
+		rowMapper.mapper(t, valuesMap, cellMap, dataIndex, rowIndex);
+		return 1;
+	}
+}

+ 193 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/builder/ExportBuilder.java

@@ -0,0 +1,193 @@
+package com.zjrs.appcomm.template.excel.builder;
+
+import com.zjrs.appcomm.template.excel.mapper.CellStyler;
+import com.zjrs.appcomm.template.excel.mapper.FieldCellStyler;
+import com.zjrs.appcomm.template.excel.mapper.RowStyler;
+import com.zjrs.appcomm.template.excel.mapper.SheetCoordinate;
+import com.zjrs.appcomm.template.excel.mapper.SimpleDataCellStyler;
+import com.zjrs.appcomm.template.excel.mapper.SimpleDataRowStyler;
+import com.zjrs.appcomm.template.excel.mapper.SimpleHeaderCellStyler;
+import com.zjrs.appcomm.template.excel.mapper.SimpleHeaderRowStyler;
+import com.zjrs.appcomm.template.excel.util.ExcelUtils;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExportBuilder<D, L> {
+	
+	private ExportDefinition<D, L> exportDefinition;
+	public ExportBuilder(Class<D> discreteClass, Class<L> listClass) {
+		this.exportDefinition = new ExportDefinition<>(discreteClass, listClass);
+		this.exportDefinition.setListHeaderRowIndex(0);
+		this.exportDefinition.setListHeaderRowStyler(new SimpleHeaderRowStyler());
+		this.exportDefinition.setListHeaderCellStyler(new SimpleHeaderCellStyler());
+		this.exportDefinition.setListDataRowIndex(1);
+		this.exportDefinition.setListDataRowStyler(new SimpleDataRowStyler());
+		this.exportDefinition.setListDataCellStyler(new SimpleDataCellStyler());
+	}
+	
+	public ExportDefinition<D, L> build() {
+		return exportDefinition;
+	}
+	
+	/* Excel中Sheet */
+	public ExportBuilder<D, L> sheet(Sheet sheet) {
+		exportDefinition.setSheet(sheet);
+		return this;
+	}
+	public ExportBuilder<D, L> sheet(Workbook workbook, String sheetName) {
+		Sheet sheet = ExcelUtils.getSheet(workbook, sheetName, true);
+		return sheet(sheet);
+	}
+	public ExportBuilder<D, L> sheet(InputStream is, String sheetName) throws IOException, InvalidFormatException {
+		Workbook workbook = WorkbookFactory.create(is);
+		return sheet(workbook, sheetName);
+	}
+	public ExportBuilder<D, L> sheet(byte[] content, String sheetName) throws IOException, InvalidFormatException {
+		try (InputStream is = new ByteArrayInputStream(content)) {
+			return sheet(is, sheetName);
+		}
+	}
+	public ExportBuilder<D, L> sheet(String sheetName) throws IOException {
+		Workbook workbook = new XSSFWorkbook();
+		return sheet(workbook, sheetName);
+	}
+	
+	/* 离散数据 */
+	public ExportBuilder<D, L> discreteData(D discreteData) {
+		exportDefinition.setDiscreteData(discreteData);
+		return this;
+	}
+	
+	/* 离散数据坐标 */
+	public ExportBuilder<D, L> discreteFieldCoordinates(Map<String, SheetCoordinate> discreteFieldCoordinates) {
+		exportDefinition.setDiscreteFieldCoordinates(discreteFieldCoordinates);
+		return this;
+	}
+	public ExportBuilder<D, L> putDiscreteFieldCoordinate(String fieldName, SheetCoordinate discreteFieldCoordinate) {
+		if (exportDefinition.getDiscreteFieldCoordinates() == null) {
+			exportDefinition.setDiscreteFieldCoordinates(new HashMap<>());
+		}
+		exportDefinition.getDiscreteFieldCoordinates().put(fieldName, discreteFieldCoordinate);
+		return this;
+	}
+	public ExportBuilder<D, L> putDiscreteFieldCoordinate(String fieldName, int row, int col) {
+		putDiscreteFieldCoordinate(fieldName, new SheetCoordinate(row, col));
+		return this;
+	}
+	public ExportBuilder<D, L> putDiscreteFieldCoordinate(String fieldName, int row, int col, CellStyler styler) {
+		putDiscreteFieldCoordinate(fieldName, new SheetCoordinate(row, col, styler));
+		return this;
+	}
+	
+	/* 离散数据单元格默认样式处理 */
+	public ExportBuilder<D, L> discreteDefaultDataStyler(CellStyler discreteDefaultDataStyler) {
+		exportDefinition.setDiscreteDefaultDataStyler(discreteDefaultDataStyler);
+		return this;
+	}
+	
+	/* 列表数据表头所在行下标 */
+	public ExportBuilder<D, L> listHeaderRowIndex(int listHeaderRowIndex) {
+		exportDefinition.setListHeaderRowIndex(listHeaderRowIndex);
+		return this;
+	}
+	
+	/* 列表数据表头名称 */
+	public ExportBuilder<D, L> listHeaders(String[][] listHeaders) {
+		exportDefinition.setListHeaders(listHeaders);
+		return this;
+	}
+	public ExportBuilder<D, L> listHeaders(String[] listHeaders) {
+		exportDefinition.setListHeaders(new String[][] { listHeaders });
+		return this;
+	}
+	public ExportBuilder<D, L> listHeaders(List<?> listHeaders) {
+		if (listHeaders.size() == 0) {
+			listHeaders(new String[0]);
+		}
+		if (listHeaders.get(0) instanceof String) {
+			listHeaders(listHeaders.toArray(new String[0]));
+		} else if (listHeaders.get(0) instanceof List) {
+			String[][] listHeadersA = new String[listHeaders.size()][];
+			for (int i = 0; i < listHeaders.size(); i++) {
+				listHeadersA[i] = ((List<?>) listHeaders.get(i)).toArray(new String[0]);
+			}
+			listHeaders(listHeadersA);
+		}
+		return this;
+	}
+	
+	/* 列表数据字段映射 */
+	public ExportBuilder<D, L> listFields(String[] listFields) {
+		exportDefinition.setListFields(listFields);
+		return this;
+	}
+	public ExportBuilder<D, L> listFields(List<String> listFields) {
+		listFields(listFields.toArray(new String[0]));
+		return this;
+	}
+	
+	/* 列表数据表头行样式处理 */
+	public ExportBuilder<D, L> listHeaderRowStyler(RowStyler listHeaderRowStyler) {
+		exportDefinition.setListHeaderRowStyler(listHeaderRowStyler);
+		return this;
+	}
+	
+	/* 列表数据表头单元格样式处理 */
+	public ExportBuilder<D, L> listHeaderCellStyler(CellStyler listHeaderCellStyler) {
+		exportDefinition.setListHeaderCellStyler(listHeaderCellStyler);
+		return this;
+	}
+	
+	/* 列表数据的起始行下标 */
+	public ExportBuilder<D, L> listDataRowIndex(int listDataRowIndex) {
+		exportDefinition.setListDataRowIndex(listDataRowIndex);
+		return this;
+	}
+	
+	/* 列表数据 */
+	public ExportBuilder<D, L> listData(List<L> listData) {
+		exportDefinition.setListData(listData);
+		return this;
+	}
+	
+	/* 列表数据行样式处理 */
+	public ExportBuilder<D, L> listDataRowStyler(RowStyler listDataRowStyler) {
+		exportDefinition.setListDataRowStyler(listDataRowStyler);
+		return this;
+	}
+	
+	/* 列表数据单元格样式处理 */
+	public ExportBuilder<D, L> listDataCellStyler(CellStyler listDataCellStyler) {
+		exportDefinition.setListDataCellStyler(listDataCellStyler);
+		return this;
+	}
+	public ExportBuilder<D, L> addListDataCellStyler(CellStyler listDataCellStyler) {
+		addListDataCellStyler(FieldCellStyler.MUTIL_FIELD, listDataCellStyler);
+		return this;
+	}
+	public ExportBuilder<D, L> addListDataCellStyler(String field, CellStyler listDataCellStyler) {
+		CellStyler source = exportDefinition.getListDataCellStyler();
+		if (source == null) {
+			source = new FieldCellStyler();
+			listDataCellStyler(source);
+		} else if (!(source instanceof FieldCellStyler)) {
+			FieldCellStyler fieldCellStyler = new FieldCellStyler();
+			fieldCellStyler.addCellStyler(FieldCellStyler.MUTIL_FIELD, source);
+			listDataCellStyler(fieldCellStyler);
+			source = fieldCellStyler;
+		}
+		FieldCellStyler fieldCellStyler = (FieldCellStyler) source;
+		fieldCellStyler.addCellStyler(field, listDataCellStyler);
+		return this;
+	}
+}

+ 94 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/builder/ExportDefinition.java

@@ -0,0 +1,94 @@
+package com.zjrs.appcomm.template.excel.builder;
+
+import com.zjrs.appcomm.template.excel.mapper.CellStyler;
+import com.zjrs.appcomm.template.excel.mapper.RowStyler;
+import com.zjrs.appcomm.template.excel.mapper.SheetCoordinate;
+import org.apache.poi.ss.usermodel.Sheet;
+
+import java.util.List;
+import java.util.Map;
+
+public class ExportDefinition<D, L> {
+	
+	public static <D, L> ExportBuilder<D, L> builder(Class<D> discreteClass, Class<L> listClass) {
+		return new ExportBuilder<>(discreteClass, listClass);
+	}
+	public ExportDefinition(Class<D> discreteClass, Class<L> listClass) {
+		setDiscreteClass(discreteClass);
+		setListClass(listClass);
+	}
+	
+	private Class<D> discreteClass;
+	public Class<D> getDiscreteClass() { return discreteClass; }
+	public void setDiscreteClass(Class<D> discreteClass) { this.discreteClass = discreteClass; }
+	
+	private Class<L> listClass;
+	public Class<L> getListClass() { return listClass; }
+	public void setListClass(Class<L> listClass) { this.listClass = listClass; }
+
+	/* Excel中Sheet */
+	private Sheet sheet;
+	public Sheet getSheet() { return sheet; }
+	public void setSheet(Sheet sheet) { this.sheet = sheet; }
+	
+	/* 离散数据 */
+	private D discreteData;
+	public D getDiscreteData() { return discreteData; }
+	public void setDiscreteData(D discreteData) { this.discreteData = discreteData; }
+	
+	/* 离散数据坐标 */
+	private Map<String, SheetCoordinate> discreteFieldCoordinates;
+	public Map<String, SheetCoordinate> getDiscreteFieldCoordinates() { return discreteFieldCoordinates; }
+	public void setDiscreteFieldCoordinates(Map<String, SheetCoordinate> discreteFieldCoordinates) { this.discreteFieldCoordinates = discreteFieldCoordinates; }
+	
+	/* 离散数据单元格默认样式处理 */
+	private CellStyler discreteDefaultDataStyler;
+	public CellStyler getDiscreteDefaultDataStyler() { return discreteDefaultDataStyler; }
+	public void setDiscreteDefaultDataStyler(CellStyler discreteDefaultDataStyler) { this.discreteDefaultDataStyler = discreteDefaultDataStyler; }
+	
+	/* 列表数据表头所在行下标 */
+	private int listHeaderRowIndex;
+	public int getListHeaderRowIndex() { return listHeaderRowIndex; }
+	public void setListHeaderRowIndex(int listHeaderRowIndex) { this.listHeaderRowIndex = listHeaderRowIndex; }
+	
+	/* 列表数据表头名称 */
+	private String[][] listHeaders;
+	public String[][] getListHeaders() { return listHeaders; }
+	public void setListHeaders(String[][] listHeaders) { this.listHeaders = listHeaders; }
+	
+	/* 列表数据字段映射 */
+	private String[] listFields;
+	public String[] getListFields() { return listFields; }
+	public void setListFields(String[] listFields) { this.listFields = listFields; }
+	
+	/* 列表数据表头行样式处理 */
+	private RowStyler listHeaderRowStyler;
+	public RowStyler getListHeaderRowStyler() { return listHeaderRowStyler; }
+	public void setListHeaderRowStyler(RowStyler listHeaderRowStyler) { this.listHeaderRowStyler = listHeaderRowStyler; }
+	
+	/* 列表数据表头单元格样式处理 */
+	private CellStyler listHeaderCellStyler;
+	public CellStyler getListHeaderCellStyler() { return listHeaderCellStyler; }
+	public void setListHeaderCellStyler(CellStyler listHeaderCellStyler) { this.listHeaderCellStyler = listHeaderCellStyler; }
+	
+	/* 列表数据的起始行下标 */
+	private int listDataRowIndex;
+	public int getListDataRowIndex() { return listDataRowIndex; }
+	public void setListDataRowIndex(int listDataRowIndex) { this.listDataRowIndex = listDataRowIndex; }
+	
+	/* 列表数据 */
+	private List<L> listData;
+	public List<L> getListData() { return listData; }
+	public void setListData(List<L> listData) { this.listData = listData; }
+	
+	/* 列表数据行样式处理 */
+	private RowStyler listDataRowStyler;
+	public RowStyler getListDataRowStyler() { return listDataRowStyler; }
+	public void setListDataRowStyler(RowStyler listDataRowStyler) { this.listDataRowStyler = listDataRowStyler; }
+	
+	/* 列表数据单元格样式处理 */
+	private CellStyler listDataCellStyler;
+	public CellStyler getListDataCellStyler() { return listDataCellStyler; }
+	public void setListDataCellStyler(CellStyler listDataCellStyler) { this.listDataCellStyler = listDataCellStyler; }
+	
+}

+ 124 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/builder/ImportBuilder.java

@@ -0,0 +1,124 @@
+package com.zjrs.appcomm.template.excel.builder;
+
+import com.zjrs.appcomm.template.excel.mapper.*;
+import com.zjrs.appcomm.template.excel.util.ExcelUtils;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ImportBuilder<D, L> {
+	
+	private ImportDefinition<D, L> importDefinition;
+	public ImportBuilder(Class<D> discreteClass, Class<L> listClass) {
+		this.importDefinition = new ImportDefinition<>(discreteClass, listClass);
+		this.importDefinition.setHeaderRowIndex(0);
+		this.importDefinition.setHeaderRowCount(1);
+		this.importDefinition.setDataRowIndex(1);
+		this.importDefinition.setCalculateMergedCell(false);
+	}
+	
+	public ImportDefinition<D, L> build() {
+		return importDefinition;
+	}
+	
+	/* Excel中Sheet */
+	public ImportBuilder<D, L> sheet(Sheet sheet) {
+		importDefinition.setSheet(sheet);
+		return this;
+	}
+	public ImportBuilder<D, L> sheet(Workbook workbook, String sheetName) {
+		Sheet sheet = ExcelUtils.getSheet(workbook, sheetName, false);
+		return sheet(sheet);
+	}
+	public ImportBuilder<D, L> sheet(InputStream is, String sheetName) throws IOException, InvalidFormatException {
+		Workbook workbook = WorkbookFactory.create(is);
+		return sheet(workbook, sheetName);
+	}
+	public ImportBuilder<D, L> sheet(byte[] content, String sheetName) throws IOException, InvalidFormatException {
+		try (InputStream is = new ByteArrayInputStream(content)) {
+			return sheet(is, sheetName);
+		}
+	}
+	
+	/* 离散数据坐标 */
+	public ImportBuilder<D, L> discreteFieldCoordinates(Map<String, SheetCoordinate> discreteFieldCoordinates) {
+		importDefinition.setDiscreteFieldCoordinates(discreteFieldCoordinates);
+		return this;
+	}
+	public ImportBuilder<D, L> putDiscreteFieldCoordinate(String fieldName, SheetCoordinate discreteFieldCoordinate) {
+		if (importDefinition.getDiscreteFieldCoordinates() == null) {
+			importDefinition.setDiscreteFieldCoordinates(new HashMap<>());
+		}
+		importDefinition.getDiscreteFieldCoordinates().put(fieldName, discreteFieldCoordinate);
+		return this;
+	}
+	public ImportBuilder<D, L> putDiscreteFieldCoordinate(String fieldName, int row, int col) {
+		putDiscreteFieldCoordinate(fieldName, new SheetCoordinate(row, col));
+		return this;
+	}
+	public ImportBuilder<D, L> putDiscreteFieldCoordinate(String fieldName, int row, int col, CellStyler styler) {
+		putDiscreteFieldCoordinate(fieldName, new SheetCoordinate(row, col, styler));
+		return this;
+	}
+	
+	/* 处理离散数据的Mapper */
+	public ImportBuilder<D, L> discreteMapper(DiscreteMapper<D> discreteMapper) {
+		importDefinition.setDiscreteMapper(discreteMapper);
+		return this;
+	}
+	
+	/* 导入数据的表头所在行下标(比如第一行就是表头,则传入0) */
+	public ImportBuilder<D, L> headerRowIndex(int headerRowIndex) {
+		importDefinition.setHeaderRowIndex(headerRowIndex);
+		return this;
+	}
+	
+	/* 导入数据的表头一共有几行(比如只有一行表头,则传入1,若是两行构成的多行表头,则传入2) */
+	public ImportBuilder<D, L> headerRowCount(int headerRowCount) {
+		importDefinition.setHeaderRowCount(headerRowCount);
+		return this;
+	}
+	
+	/* 定义表头转换用的映射(主要针对动态表头而使用,对于静态表头可以直接传入固定值)(一般静态表头可使用StaticHeaderMapper、一般动态表头可使用DynamicHeaderMapper) */
+	public ImportBuilder<D, L> headerMapper(HeaderMapper headerMapper) {
+		importDefinition.setHeaderMapper(headerMapper);
+		return this;
+	}
+	
+	/* 是否进行合并单元格修正 */
+	public ImportBuilder<D, L> calculateMergedCell(boolean calculateMergedCell) {
+		importDefinition.setCalculateMergedCell(calculateMergedCell);
+		return this;
+	}
+	
+	/* 需要导入的数据所在行的起始下标(比如第二行就是数据,则传入1) */
+	public ImportBuilder<D, L> dataRowIndex(int dataRowIndex) {
+		importDefinition.setDataRowIndex(dataRowIndex);
+		return this;
+	}
+	
+	/* 行数据过滤器 */
+	public ImportBuilder<D, L> listFilter(RowJudge listFilter) {
+		importDefinition.setListFilter(listFilter);
+		return this;
+	}
+	
+	/* 行数据终止器 */
+	public ImportBuilder<D, L> listfinishedJudge(RowJudge listfinishedJudge) {
+		importDefinition.setListfinishedJudge(listfinishedJudge);
+		return this;
+	}
+	
+	/* 处理每行数据的Mapper */
+	public ImportBuilder<D, L> rowMapper(RowMapper<L> rowMapper) {
+		importDefinition.setRowMapper(rowMapper);
+		return this;
+	}
+}

+ 82 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/builder/ImportDefinition.java

@@ -0,0 +1,82 @@
+package com.zjrs.appcomm.template.excel.builder;
+
+import com.zjrs.appcomm.template.excel.mapper.*;
+import org.apache.poi.ss.usermodel.Sheet;
+
+import java.util.Map;
+
+public class ImportDefinition<D, L> {
+	
+	public static <D, L> ImportBuilder<D, L> builder(Class<D> discreteClass, Class<L> listClass) {
+		return new ImportBuilder<>(discreteClass, listClass);
+	}
+	
+	public ImportDefinition(Class<D> discreteClass, Class<L> listClass) {
+		setDiscreteClass(discreteClass);
+		setListClass(listClass);
+	}
+	
+	private Class<D> discreteClass;
+	public Class<D> getDiscreteClass() { return discreteClass; }
+	public void setDiscreteClass(Class<D> discreteClass) { this.discreteClass = discreteClass; }
+	
+	private Class<L> listClass;
+	public Class<L> getListClass() { return listClass; }
+	public void setListClass(Class<L> listClass) { this.listClass = listClass; }
+
+	/* Excel中Sheet */
+	private Sheet sheet;
+	public Sheet getSheet() { return sheet; }
+	public void setSheet(Sheet sheet) { this.sheet = sheet; }
+	
+	/* 离散数据坐标 */
+	private Map<String, SheetCoordinate> discreteFieldCoordinates;
+	public Map<String, SheetCoordinate> getDiscreteFieldCoordinates() { return discreteFieldCoordinates; }
+	public void setDiscreteFieldCoordinates(Map<String, SheetCoordinate> discreteFieldCoordinates) { this.discreteFieldCoordinates = discreteFieldCoordinates; }
+	
+	/* 处理离散数据的Mapper */
+	private DiscreteMapper<D> discreteMapper;
+	public DiscreteMapper<D> getDiscreteMapper() { return discreteMapper; }
+	public void setDiscreteMapper(DiscreteMapper<D> discreteMapper) { this.discreteMapper = discreteMapper; }
+	
+	/* 导入数据的表头所在行下标(比如第一行就是表头,则传入0) */
+	private int headerRowIndex;
+	public int getHeaderRowIndex() { return headerRowIndex; }
+	public void setHeaderRowIndex(int headerRowIndex) { this.headerRowIndex = headerRowIndex; }
+	
+	/* 导入数据的表头一共有几行(比如只有一行表头,则传入1,若是两行构成的多行表头,则传入2) */
+	private int headerRowCount;
+	public int getHeaderRowCount() { return headerRowCount; }
+	public void setHeaderRowCount(int headerRowCount) { this.headerRowCount = headerRowCount; }
+	
+	/* 定义表头转换用的映射(主要针对动态表头而使用,对于静态表头可以直接传入固定值)(一般静态表头可使用StaticHeaderMapper、一般动态表头可使用DynamicHeaderMapper) */
+	private HeaderMapper headerMapper;
+	public HeaderMapper getHeaderMapper() { return headerMapper; }
+	public void setHeaderMapper(HeaderMapper headerMapper) { this.headerMapper = headerMapper; }
+	
+	/* 是否进行合并单元格修正 */
+	private boolean calculateMergedCell;
+	public boolean isCalculateMergedCell() { return calculateMergedCell; }
+	public void setCalculateMergedCell(boolean calculateMergedCell) { this.calculateMergedCell = calculateMergedCell; }
+
+	/* 需要导入的数据所在行的起始下标(比如第二行就是数据,则传入1) */
+	private int dataRowIndex;
+	public int getDataRowIndex() { return dataRowIndex; }
+	public void setDataRowIndex(int dataRowIndex) { this.dataRowIndex = dataRowIndex; }
+	
+	/* 行数据过滤器 */
+	private RowJudge listFilter;
+	public RowJudge getListFilter() { return listFilter; }
+	public void setListFilter(RowJudge listFilter) { this.listFilter = listFilter; }
+	
+	/* 行数据终止器 */
+	private RowJudge listfinishedJudge;
+	public RowJudge getListfinishedJudge() { return listfinishedJudge; }
+	public void setListfinishedJudge(RowJudge listfinishedJudge) { this.listfinishedJudge = listfinishedJudge; }
+	
+	/* 处理每行数据的Mapper */
+	private RowMapper<L> rowMapper;
+	public RowMapper<L> getRowMapper() { return rowMapper; }
+	public void setRowMapper(RowMapper<L> rowMapper) { this.rowMapper = rowMapper; }
+	
+}

+ 31 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/exception/IllegalHeaderException.java

@@ -0,0 +1,31 @@
+package com.zjrs.appcomm.template.excel.exception;
+
+import com.zjrs.appcomm.utils.StringUtil;
+
+/**
+ * 异常表头异常
+ * @author froms
+ *
+ */
+public class IllegalHeaderException extends IllegalArgumentException {
+
+	private static final long serialVersionUID = 6627653606445491334L;
+	
+	/** 异常的表头 */
+	private String[] illegalHeaders;	//TODO: 异常的表头到底是完整内容还是仅仅异常的内容
+	/**
+	 * 获取异常的表头
+	 * @return 异常的表头
+	 */
+	public String[] getIllegalHeaders() { return illegalHeaders; }
+
+	/**
+	 * 异常表头异常
+	 * @param illegalHeaders 异常的表头
+	 */
+	public IllegalHeaderException(String... illegalHeaders) {
+		super(StringUtil.join(illegalHeaders, ","));
+		this.illegalHeaders = illegalHeaders;
+	}
+	
+}

+ 29 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/exception/IllegalSheetException.java

@@ -0,0 +1,29 @@
+package com.zjrs.appcomm.template.excel.exception;
+
+/**
+ * 异常Sheet异常
+ * @author froms
+ *
+ */
+public class IllegalSheetException extends IllegalArgumentException {
+
+	private static final long serialVersionUID = 3580570791107151964L;
+	
+	/** 异常的Sheet */
+	private String illegalName;
+	/**
+	 * 获取异常的Sheet
+	 * @return 异常的Sheet
+	 */
+	public String getIllegalName() { return illegalName; }
+
+	/**
+	 * 异常Sheet异常
+	 * @param illegalHeaders 异常的Sheet
+	 */
+	public IllegalSheetException(String illegalName) {
+		super(illegalName);
+		this.illegalName = illegalName;
+	}
+	
+}

+ 25 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/CellStyler.java

@@ -0,0 +1,25 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Cell;
+
+import java.util.Map;
+
+/**
+ * 单元格样式处理
+ * @author froms
+ *
+ */
+public interface CellStyler {
+	/**
+	 * 处理单元格样式
+	 * @param cell 需要处理样式的单元格
+	 * @param value 将要填充的数据(当返回值为true时,将自动填充数据,否则需要自行进行数据填充)
+	 * @param field 映射的字段
+	 * @param dataIndex 当前行所在的数据下标(非Sheet中的行下标)
+	 * @param rowIndex 当前行所在的sheet下标
+	 * @param cellIndex 当前单元格所在的列下标
+	 * @param row 将要填充的数据的兄弟完整数据(针对表头时,该值始终为null)
+	 * @return 是否后续继续进行单元格内容填充
+	 */
+	public boolean style(Cell cell, Object value, String field, int dataIndex, int rowIndex, int cellIndex, Map<?, ?> row);
+}

+ 22 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/DiscreteMapper.java

@@ -0,0 +1,22 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Cell;
+
+import java.util.Map;
+
+/**
+ * Excel处理离散数据的Mapper
+ * @author froms
+ *
+ * @param <T> 离散数据的定义
+ */
+public interface DiscreteMapper<D> {
+	
+	/**
+	 * 处理离散数据
+	 * @param d 已经填充好数据的离散数据
+	 * @param discreteValuesMap 原始的数据Map
+	 * @param discreteCellMap Excel原始单元格映射
+	 */
+	public void mapper(D d, Map<String, Object> discreteValuesMap, Map<String, Cell> discreteCellMap);
+}

+ 94 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/DynamicHeaderMapper.java

@@ -0,0 +1,94 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import com.zjrs.appcomm.template.excel.exception.IllegalHeaderException;
+import org.springframework.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Excel动态表头Mapper
+ * @author froms
+ *
+ */
+public class DynamicHeaderMapper implements StringHeaderMapper {
+
+	/** 表头名与字段名的映射Mapper(key为Excel中表头名,value为字段field名) */
+	private Map<String, String> fieldsMapper;
+	public Map<String, String> getFieldsMapper() { return fieldsMapper; }
+	public void setFieldsMapper(Map<String, String> fieldsMapper) { this.fieldsMapper = fieldsMapper; }
+
+	/** 是否忽略Excel中缺少的表头 */
+	private boolean ignoreLessHeaders;
+	public boolean isIgnoreLessHeaders() { return ignoreLessHeaders; }
+	public void setIgnoreLessHeaders(boolean ignoreLessHeaders) { this.ignoreLessHeaders = ignoreLessHeaders; }
+
+	/** 是否忽略Excel中多出的表头 */
+	private boolean ignoreMoreHeaders;
+	public boolean isIgnoreMoreHeaders() { return ignoreMoreHeaders; }
+	public void setIgnoreMoreHeaders(boolean ignoreMoreHeaders) { this.ignoreMoreHeaders = ignoreMoreHeaders; }
+
+	/**
+	 * Excel动态表头Mapper(默认校验多出的表头,忽略缺少的表头)
+	 * @param fieldsMapper 表头名与字段名的映射Mapper(key为Excel中表头名,value为字段field名)
+	 */
+	public DynamicHeaderMapper(Map<String, String> fieldsMapper) {
+		this(fieldsMapper, true, false);
+	}
+	
+	/**
+	 * Excel动态表头Mapper(可配置是否校验表头)
+	 * @param fieldsMapper 表头名与字段名的映射Mapper(key为Excel中表头名,value为字段field名)
+	 * @param ignoreLessHeaders 是否忽略Excel中缺少的表头
+	 * @param ignoreMoreHeaders 是否忽略Excel中多出的表头
+	 */
+	public DynamicHeaderMapper(Map<String, String> fieldsMapper, boolean ignoreLessHeaders, boolean ignoreMoreHeaders) {
+		this.fieldsMapper = fieldsMapper;
+		this.ignoreLessHeaders = ignoreLessHeaders;
+		this.ignoreMoreHeaders = ignoreMoreHeaders;
+	}
+	
+	@Override
+	public String[] mapper(String[] headers) throws IllegalHeaderException {
+		Map<String, String> tFieldsMapper = new HashMap<String, String>(fieldsMapper);
+		
+		String[] fields = new String[headers.length];
+		for (int i = 0; i < headers.length; i++) {
+			String header = headers[i];
+			if (StringUtils.isEmpty(header)) {
+				continue;
+			}
+			String field = tFieldsMapper.remove(header);
+			if (StringUtils.isEmpty(field)) {
+				if (ignoreMoreHeaders) {
+					throw new IllegalHeaderException(headers);
+				}
+				continue;
+			}
+			fields[i] = field;
+		}
+		
+		if (ignoreLessHeaders) {
+			Set<String> headersLess = tFieldsMapper.keySet();
+			for (String header: headersLess) {
+				if (!StringUtils.isEmpty(header)) {
+					throw new IllegalHeaderException(headers);
+				}
+			}
+		}
+		
+		int realFieldLength = fields.length;
+		for (; realFieldLength >= 0; realFieldLength--) {
+			if (!StringUtils.isEmpty(fields[realFieldLength - 1])) {
+				break;
+			}
+		}
+		if (realFieldLength == fields.length) {
+			return fields;
+		} else {
+			return Arrays.copyOfRange(fields, 0, realFieldLength);
+		}
+	}
+}

+ 58 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/FieldCellStyler.java

@@ -0,0 +1,58 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Cell;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FieldCellStyler implements CellStyler {
+
+	private Map<String, List<CellStyler>> fieldCellStylerMap = new HashMap<>();
+	public static final String MUTIL_FIELD = "";
+	
+	public void addCellStyler(String field, CellStyler cellStyler) {
+		List<CellStyler> list = fieldCellStylerMap.computeIfAbsent(field, key -> new ArrayList<>());
+		list.add(cellStyler);
+	}
+	public int removeCellStyler(String field, CellStyler cellStyler) {
+		List<CellStyler> list = fieldCellStylerMap.get(field);
+		int num = 0;
+		if (list != null) {
+			for (int i = list.size() - 1; i >= 0; i--) {
+				if (list.get(i).equals(cellStyler)) {
+					list.remove(i);
+					num++;
+				}
+			}
+			if (list.isEmpty()) {
+				fieldCellStylerMap.remove(field);
+			}
+		}
+		return num;
+	}
+	
+	@Override
+	public boolean style(Cell cell, Object value, String field, int dataIndex, int rowIndex, int cellIndex, Map<?, ?> row) {
+		boolean doValue = true;
+		
+		List<CellStyler> list = fieldCellStylerMap.get(field);
+		if (list != null) {
+			for (CellStyler cellStyler: list) {
+				boolean tempDoValue = cellStyler.style(cell, value, field, dataIndex, rowIndex, cellIndex, row);
+				doValue = doValue && tempDoValue;
+			}
+		}
+		
+		list = fieldCellStylerMap.get(MUTIL_FIELD);
+		if (list != null) {
+			for (CellStyler cellStyler: list) {
+				boolean tempDoValue = cellStyler.style(cell, value, field, dataIndex, rowIndex, cellIndex, row);
+				doValue = doValue && tempDoValue;
+			}
+		}
+		return doValue;
+	}
+
+}

+ 102 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/FieldMergeCellStyler.java

@@ -0,0 +1,102 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 单列中行合并逻辑
+ */
+public class FieldMergeCellStyler implements CellStyler {
+
+	private Object[] lastValue;
+	private int lastRowIndex;
+	private int lastCellIndex;
+	private CellRangeAddress currentRange;
+	private int currentRangeIndex;
+
+	private String[] relatedField;
+
+	/**
+	 *
+	 * @param relatedField 需要关联的相关字段(用于防止不必要的单元格合并)
+	 */
+	public FieldMergeCellStyler(String... relatedField) {
+		this.relatedField = relatedField;
+	}
+	
+	@Override
+	public boolean style(Cell cell, Object value, String field, int dataIndex, int rowIndex, int cellIndex, Map<?, ?> row) {
+		Object[] currentValues = values(value, row);
+		if (lastValue == null) {
+			lastValue = currentValues;
+			lastRowIndex = rowIndex;
+			lastCellIndex = cellIndex;
+			currentRange = null;
+		} else {
+			if (isEquals(lastValue, currentValues)) {
+				if (currentRange == null) {
+					currentRange = new CellRangeAddress(lastRowIndex, rowIndex, lastCellIndex, cellIndex);
+					currentRangeIndex = cell.getSheet().addMergedRegionUnsafe(currentRange);
+				} else {
+					if (currentRangeIndex >= cell.getSheet().getNumMergedRegions()
+							|| !currentRange.equals(cell.getSheet().getMergedRegion(currentRangeIndex))) {
+						/* 当多列存在合并需求时,存在合并配置的角标混乱的情况 */
+						List<CellRangeAddress> rangeList = cell.getSheet().getMergedRegions();
+						for (int i = 0; i < rangeList.size(); i++) {
+							if (currentRange.equals(rangeList.get(i))) {
+								currentRangeIndex = i;
+								break;
+							}
+						}
+					}
+					cell.getSheet().removeMergedRegion(currentRangeIndex);
+					currentRange.setLastRow(rowIndex);
+					currentRange.setLastColumn(cellIndex);
+					currentRangeIndex = cell.getSheet().addMergedRegionUnsafe(currentRange);
+				}
+				return false;
+			} else {
+				lastValue = currentValues;
+				lastRowIndex = rowIndex;
+				lastCellIndex = cellIndex;
+				currentRange = null;
+			}
+		}
+		return true;
+	}
+
+	private Object[] values(Object value, Map<?, ?> row) {
+		Object[] values = new Object[relatedField.length + 1];
+		for (int i = 0; i < relatedField.length; i++) {
+			values[i] = row.get(relatedField[i]);
+		}
+		values[values.length - 1] = value;
+		return values;
+	}
+
+	private boolean isEquals(Object[] o1, Object[] o2) {
+		if (o1.length != o2.length) {
+			return false;
+		} else {
+			for (int i = 0; i < o1.length; i++) {
+				if (!isEquals(o1[i], o2[i])) {
+					return false;
+				}
+			}
+			return true;
+		}
+	}
+
+	private boolean isEquals(Object o1, Object o2) {
+		if (o1 == null && o2 == null) {
+			return true;
+		} else if (o1 == null || o2 == null) {
+			return false;
+		} else {
+			return o1.equals(o2);
+		}
+	}
+}

+ 22 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/HeaderMapper.java

@@ -0,0 +1,22 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import com.zjrs.appcomm.template.excel.exception.IllegalHeaderException;
+import org.apache.poi.ss.usermodel.Row;
+
+/**
+ * Excel表头Mapper
+ * @author froms
+ *
+ */
+public interface HeaderMapper {
+	/**
+	 * 根据传入的表头名称转换为字段的字段名。
+	 * 返回的数组长度与传入的数组长度可以不一致:
+	 * 比如当传入的末尾若干表头是不需要的时,则可以对数组长度进行裁剪至自己需要的长度;
+	 * 比如当传入的开头或中间若干表头是不需要的时,则可以对相应下标填写空
+	 * @param rows 从Excel中解析出的行数组
+	 * @return 实际映射数据需要的字段名数组
+	 * @throws IllegalHeaderException 当发现异常的表头时抛出该异常
+	 */
+	public String[] mapper(Row[] rows) throws IllegalHeaderException;
+}

+ 20 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/OriginalCellStyler.java

@@ -0,0 +1,20 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Cell;
+
+import java.util.Map;
+
+/**
+ * 原始数据单元格样式处理
+ * 什么都不做
+ * @author froms
+ *
+ */
+public class OriginalCellStyler implements CellStyler {
+
+	@Override
+	public boolean style(Cell cell, Object value, String field, int dataIndex, int rowIndex, int cellIndex, Map<?, ?> row) {
+		return true;
+	}
+
+}

+ 18 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/RowJudge.java

@@ -0,0 +1,18 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Row;
+
+/**
+ * Excel行判断器
+ * @author froms
+ *
+ */
+public interface RowJudge {
+	/**
+	 * 判断每行数据
+	 * @param row Excel行
+	 * @param rowIndex 行的下标
+	 * @return 判断是否通过
+	 */
+	public boolean judge(Row row, int rowIndex);
+}

+ 23 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/RowMapper.java

@@ -0,0 +1,23 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Cell;
+
+import java.util.Map;
+
+/**
+ * Excel处理每行数据的Mapper
+ * @author froms
+ *
+ * @param <T> 行数据的定义
+ */
+public interface RowMapper<T> {
+	/**
+	 * 处理每行数据
+	 * @param t 已经填充好数据的行数据(当数据转换错误的情况下,该值为null)
+	 * @param valuesMap 原始的数据Map
+	 * @param cellMap Excel原始单元格映射
+	 * @param dataIndex 当前数据的下标
+	 * @param rowIndex 当前数据所在Excel的行下标(Excel中的空行数据会被忽略,因此该值存在跳号的可能性)
+	 */
+	public void mapper(T t, Map<String, Object> valuesMap, Map<String, Cell> cellMap, int dataIndex, int rowIndex);
+}

+ 18 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/RowStyler.java

@@ -0,0 +1,18 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Row;
+
+/**
+ * 行样式处理
+ * @author froms
+ *
+ */
+public interface RowStyler {
+	/**
+	 * 处理行样式
+	 * @param row 需要处理样式的行
+	 * @param rowIndex 当前行所在的数据下标(非Sheet中的行下标)
+	 * @return 是否后续继续进行行内容填充
+	 */
+	public boolean style(Row row, int rowIndex);
+}

+ 34 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SheetCoordinate.java

@@ -0,0 +1,34 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+/**
+ * Sheet坐标信息
+ * @author froms
+ *
+ */
+public class SheetCoordinate {
+	
+	/* 行坐标(从0开始计算) */
+	private int row;
+	public int getRow() { return row; }
+	public void setRow(int row) { this.row = row; }
+
+	/* 列坐标(从0开始计算) */
+	private int col;
+	public int getCol() { return col; }
+	public void setCol(int col) { this.col = col; }
+
+	/* 所在单元格样式(可以为null) */
+	private CellStyler styler;
+	public CellStyler getStyler() { return styler; }
+	public void setStyler(CellStyler styler) { this.styler = styler; }
+	
+	public SheetCoordinate(int row, int col) {
+		this(row, col, null);
+	}
+	public SheetCoordinate(int row, int col, CellStyler styler) {
+		this.row = row;
+		this.col = col;
+		this.styler = styler;
+	}
+	
+}

+ 30 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SimpleDataCellStyler.java

@@ -0,0 +1,30 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+
+import java.util.Map;
+
+/**
+ * 简单的数据单元格样式处理
+ * 仅仅增加边框
+ * @author froms
+ *
+ */
+public class SimpleDataCellStyler implements CellStyler {
+
+	@Override
+	public boolean style(Cell cell, Object value, String field, int dataIndex, int rowIndex, int cellIndex, Map<?, ?> row) {
+		CellStyle oldCellStyle = cell.getCellStyle();
+		CellStyle newCellStyle = cell.getSheet().getWorkbook().createCellStyle();
+		newCellStyle.cloneStyleFrom(oldCellStyle);
+		newCellStyle.setBorderTop(BorderStyle.THIN);
+		newCellStyle.setBorderBottom(BorderStyle.THIN);
+		newCellStyle.setBorderLeft(BorderStyle.THIN);
+		newCellStyle.setBorderRight(BorderStyle.THIN);
+		cell.setCellStyle(newCellStyle);
+		return true;
+	}
+
+}

+ 18 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SimpleDataRowStyler.java

@@ -0,0 +1,18 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Row;
+
+/**
+ * 简单的数据行样式处理
+ * 暂不做处理
+ * @author froms
+ *
+ */
+public class SimpleDataRowStyler implements RowStyler {
+
+	@Override
+	public boolean style(Row row, int rowIndex) {
+		return true;
+	}
+
+}

+ 31 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SimpleHeaderCellStyler.java

@@ -0,0 +1,31 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.*;
+
+import java.util.Map;
+
+/**
+ * 简单的表头单元格样式处理
+ * 仅仅增加边框、修改填充色、水平居中
+ * @author froms
+ *
+ */
+public class SimpleHeaderCellStyler implements CellStyler {
+
+	@Override
+	public boolean style(Cell cell, Object value, String field, int dataIndex, int rowIndex, int cellIndex, Map<?, ?> row) {
+		CellStyle oldCellStyle = cell.getCellStyle();
+		CellStyle newCellStyle = cell.getSheet().getWorkbook().createCellStyle();
+		newCellStyle.cloneStyleFrom(oldCellStyle);
+		newCellStyle.setBorderTop(BorderStyle.THIN);
+		newCellStyle.setBorderBottom(BorderStyle.THIN);
+		newCellStyle.setBorderLeft(BorderStyle.THIN);
+		newCellStyle.setBorderRight(BorderStyle.THIN);
+		newCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+		newCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+		newCellStyle.setAlignment(HorizontalAlignment.CENTER);
+		cell.setCellStyle(newCellStyle);
+		return true;
+	}
+
+}

+ 18 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/SimpleHeaderRowStyler.java

@@ -0,0 +1,18 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import org.apache.poi.ss.usermodel.Row;
+
+/**
+ * 简单的表头行样式处理
+ * 暂不做处理
+ * @author froms
+ *
+ */
+public class SimpleHeaderRowStyler implements RowStyler {
+
+	@Override
+	public boolean style(Row row, int rowIndex) {
+		return true;
+	}
+
+}

+ 87 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/StaticHeaderMapper.java

@@ -0,0 +1,87 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import com.zjrs.appcomm.template.excel.exception.IllegalHeaderException;
+import org.springframework.util.StringUtils;
+
+/**
+ * Excel静态表头Mapper
+ * @author froms
+ *
+ */
+public class StaticHeaderMapper implements StringHeaderMapper {
+
+	/** 静态表头Mapper字段 */
+	private String[] fields;
+	public String[] getFields() { return fields; }
+	public void setFields(String[] fields) { this.fields = fields; }
+
+	/** 期望的静态表头 */
+	private String[] expectHeaders;
+	public String[] getExpectHeaders() { return expectHeaders; }
+	public void setExpectHeaders(String[] expectHeaders) { this.expectHeaders = expectHeaders; }
+
+	/** 是否忽略Excel中多余的表头 */
+	private boolean ignoreMoreHeaders;
+	public boolean isIgnoreMoreHeaders() { return ignoreMoreHeaders; }
+	public void setIgnoreMoreHeaders(boolean ignoreMoreHeaders) { this.ignoreMoreHeaders = ignoreMoreHeaders; }
+
+	/**
+	 * Excel静态表头Mapper(不校验期望表头)
+	 * @param fields 静态表头Mapper字段
+	 */
+	public StaticHeaderMapper(String[] fields) {
+		this(fields, null, true);
+	}
+	
+	/**
+	 * Excel静态表头Mapper(校验期望表头)
+	 * @param fields 静态表头Mapper字段
+	 * @param expectHeaders 期望的静态表头
+	 * @param ignoreMoreHeaders 是否忽略Excel中多余的表头
+	 */
+	public StaticHeaderMapper(String[] fields, String[] expectHeaders, boolean ignoreMoreHeaders) {
+		this.fields = fields;
+		this.expectHeaders = expectHeaders;
+		this.ignoreMoreHeaders = ignoreMoreHeaders;
+	}
+	
+	@Override
+	public String[] mapper(String[] headers) throws IllegalHeaderException {
+		if (this.expectHeaders != null) {
+			if (headers.length < this.expectHeaders.length) {
+				throw new IllegalHeaderException(headers);
+			}
+			for (int i = 0; i < this.expectHeaders.length; i++) {
+				if (!isHeaderEquals(this.expectHeaders[i], headers[i]) ) {
+					throw new IllegalHeaderException(headers);
+				}
+			}
+			if (!this.ignoreMoreHeaders) {
+				for (int i = this.expectHeaders.length; i < headers.length; i++) {
+					if (!StringUtils.isEmpty(headers[i]) ) {
+						throw new IllegalHeaderException(headers);
+					}
+				}
+			}
+		}
+		return this.fields;
+	}
+
+	/**
+	 * 判断表头是否相等
+	 * @param header1 表头1
+	 * @param header2 表头2
+	 * @return 是否相等
+	 */
+	private boolean isHeaderEquals(String header1, String header2) {
+		if (StringUtils.isEmpty(header1)) {
+			if (StringUtils.isEmpty(header2)) {
+				return true;
+			} else {
+				return false;
+			}
+		} else {
+			return header1.equals(header2);
+		}
+	}
+}

+ 57 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/mapper/StringHeaderMapper.java

@@ -0,0 +1,57 @@
+package com.zjrs.appcomm.template.excel.mapper;
+
+import com.zjrs.appcomm.template.excel.exception.IllegalHeaderException;
+import com.zjrs.appcomm.template.excel.util.ExcelUtils;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Excel表头Mapper
+ * @author froms
+ *
+ */
+public interface StringHeaderMapper extends HeaderMapper {
+	/**
+	 * 根据传入的表头名称转换为字段的字段名。
+	 * 返回的数组长度与传入的数组长度可以不一致:
+	 * 比如当传入的末尾若干表头是不需要的时,则可以对数组长度进行裁剪至自己需要的长度;
+	 * 比如当传入的开头或中间若干表头是不需要的时,则可以对相应下标填写空
+	 * @param titles 从Excel中解析出的表头数组
+	 * @return 实际映射数据需要的字段名数组
+	 * @throws IllegalHeaderException 当发现异常的表头时抛出该异常
+	 */
+	public String[] mapper(String[] headers) throws IllegalHeaderException;
+	
+	@Override
+	default String[] mapper(Row[] rows) throws IllegalHeaderException {
+		List<String> headerList = new ArrayList<>();
+		for (Row headerRow: rows) {
+			if (headerRow == null) {
+				throw new IllegalHeaderException();
+			}
+			for (int j = 0; j < headerRow.getLastCellNum(); j++) {
+				Cell cell = ExcelUtils.getCell(headerRow, j, false);
+				String value = null;
+				if (cell != null) {
+					value = ExcelUtils.getStringCellValue(cell);
+				}
+				if (j == headerList.size()) {
+					headerList.add(value);
+				} else if (j < headerList.size()) {
+					if (!StringUtils.isEmpty(value)) {
+						headerList.set(j, value);
+					}
+				} else {
+					throw new AssertionError("分析表头出错");
+				}
+			}
+		}
+		String[] headers = new String[headerList.size()];
+		headerList.toArray(headers);
+		return mapper(headers);
+	}
+}

+ 160 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/util/ExcelUtils.java

@@ -0,0 +1,160 @@
+package com.zjrs.appcomm.template.excel.util;
+
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Calendar;
+import java.util.Date;
+
+public class ExcelUtils {
+
+	public static String getStringCellValue(Cell cell) {
+		if (cell == null) {
+			return "";
+		}
+		CellType cellType = (cell.getCellType() == CellType.FORMULA ? cell.getCachedFormulaResultType() : cell.getCellType());
+		if (cellType == null) {
+			throw new AssertionError("异常的单元格类型: null");
+		}
+		switch (cellType) {
+			case NUMERIC:
+				if (DateUtil.isCellDateFormatted(cell)) {
+					return cell.getLocalDateTimeCellValue() + "";	// TODO: 有待进一步格式化
+				} else {
+					return BigDecimal.valueOf(cell.getNumericCellValue()).toPlainString();	// TODO: 有待进一步格式化
+				}
+			case STRING:
+				return cell.getStringCellValue();
+			case FORMULA:
+				throw new AssertionError("异常的单元格类型: " + cellType.name());
+			case BLANK:
+				return "";
+			case BOOLEAN:
+				return Boolean.toString(cell.getBooleanCellValue()).toUpperCase();
+			case ERROR:
+				return FormulaError.forInt(cell.getErrorCellValue()).name();
+			case _NONE:
+				throw new AssertionError("异常的单元格类型: " + cellType.name());
+			default:
+				throw new AssertionError("异常的单元格类型: " + cellType.name());
+		}
+	}
+
+	public static Object getCellValue(Cell cell) {
+		if (cell == null) {
+			return null;
+		}
+		CellType cellType = (cell.getCellType() == CellType.FORMULA ? cell.getCachedFormulaResultType() : cell.getCellType());
+		if (cellType == null) {
+			throw new AssertionError("异常的单元格类型: null");
+		}
+		switch (cellType) {
+			case NUMERIC:
+				if (DateUtil.isCellDateFormatted(cell)) {
+					return cell.getLocalDateTimeCellValue();
+				} else {
+					return new BigDecimal(BigDecimal.valueOf(cell.getNumericCellValue()).toPlainString());
+				}
+			case STRING:
+				return cell.getStringCellValue();
+			case FORMULA:
+				throw new AssertionError("异常的单元格类型: " + cellType.name());
+			case BLANK:
+				return "";
+			case BOOLEAN:
+				return cell.getBooleanCellValue();
+			case ERROR:
+				return FormulaError.forInt(cell.getErrorCellValue());
+			case _NONE:
+				throw new AssertionError("异常的单元格类型: " + cellType.name());
+			default:
+				throw new AssertionError("异常的单元格类型: " + cellType.name());
+		}
+	}
+
+	public static void setCellValue(Cell cell, Object value) {
+		if (value == null) {
+			cell.setBlank();
+			return;
+		}
+
+		if (value instanceof CharSequence) {
+			CharSequence v = (CharSequence) value;
+			if (v.length() == 0) {
+				cell.setBlank();
+			} else {
+				cell.setCellValue(v.toString());
+			}
+		} else if (value instanceof LocalDateTime) {
+			cell.setCellValue((LocalDateTime) value);
+		} else if (value instanceof LocalDate) {
+			cell.setCellValue((LocalDate) value);
+		} else if (value instanceof LocalTime) {
+			LocalDateTime v = LocalDateTime.of(LocalDate.of(1970, 1, 1), (LocalTime) value);
+			cell.setCellValue(v);
+		} else if (value instanceof Date) {
+			cell.setCellValue((Date) value);
+		} else if (value instanceof Calendar) {
+			cell.setCellValue((Calendar) value);
+		} else if (value instanceof Number) {
+			double v = ((Number) value).doubleValue();
+			cell.setCellValue(v);
+		} else if (value instanceof Boolean) {
+			cell.setCellValue((boolean) value);
+		} else if (value instanceof RichTextString) {
+			cell.setCellValue((RichTextString) value);
+		} else {
+			String v = value.toString();
+			if (v.length() == 0) {
+				cell.setBlank();
+			} else {
+				cell.setCellValue(v);
+			}
+		}
+	}
+
+	public static Sheet getSheet(Workbook workbook, String sheetName, boolean createIfNull) {
+		if (StringUtils.isEmpty(sheetName)) {
+			return workbook.getSheetAt(workbook.getActiveSheetIndex());
+		} else {
+			Sheet sheet = workbook.getSheet(sheetName);
+			if (sheet == null && createIfNull) {
+				sheet = workbook.createSheet(sheetName);
+			}
+			return sheet;
+		}
+	}
+
+	public static Row getRow(Sheet sheet, int index, boolean createIfNull) {
+		Row row = sheet.getRow(index);
+		if (row == null && createIfNull) {
+			row = sheet.createRow(index);
+		}
+		return row;
+	}
+
+	public static Cell getCell(Row row, int index, boolean createIfNull) {
+		Cell cell = row.getCell(index);
+		if (cell == null && createIfNull) {
+			cell = row.createCell(index);
+		}
+		return cell;
+	}
+
+	public static Cell getCellMerged(Sheet sheet, int rowIndex, int cellIndex) {
+		for (CellRangeAddress cellRangeAddress: sheet.getMergedRegions()) {
+			if (rowIndex >= cellRangeAddress.getFirstRow() && rowIndex <= cellRangeAddress.getLastRow()
+					&& cellIndex >= cellRangeAddress.getFirstColumn() && cellIndex <= cellRangeAddress.getLastColumn()) {
+				return sheet.getRow(cellRangeAddress.getFirstRow()).getCell(cellRangeAddress.getFirstColumn());
+			}
+		}
+		return sheet.getRow(rowIndex).getCell(cellIndex);
+	}
+
+	private ExcelUtils() {}
+}

+ 48 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/excel/util/MapperUtils.java

@@ -0,0 +1,48 @@
+package com.zjrs.appcomm.template.excel.util;
+
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.PropertyWriter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class MapperUtils {
+	
+	public static Map<String, Object> object2Map(Object obj, ObjectMapper mapper) throws Exception {
+		if (obj == null) {
+			return Collections.emptyMap();
+		}
+		Map<String, Object> map = new HashMap<>();
+		if (obj instanceof Map) {
+			Map<?, ?> objMap = (Map<?, ?>) obj;
+			for (Map.Entry<?, ?> entry: objMap.entrySet()) {
+				if (entry.getKey() != null) {
+					map.put(entry.getKey().toString(), entry.getValue());
+				}
+			}
+		} else {
+			SerializerProvider provider = mapper.getSerializerProviderInstance();
+			JsonSerializer<Object> serializer = provider.findTypedValueSerializer(obj.getClass(), true, null);
+			Iterator<PropertyWriter> it = serializer.properties();
+			
+			while (it.hasNext()) {
+				PropertyWriter pw = it.next();
+				if (pw instanceof BeanPropertyWriter) {
+					BeanPropertyWriter bpw = (BeanPropertyWriter) pw;
+					Object value = bpw.get(obj);
+					map.put(bpw.getName(), value);
+				} else if (pw != null) {
+					throw new UnsupportedOperationException(pw.getClass().toString());
+				}
+			}
+		}
+		return map;
+	}
+	
+	private MapperUtils() {}
+}

+ 43 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/WordTemplate.java

@@ -0,0 +1,43 @@
+package com.zjrs.appcomm.template.word;
+
+import com.zjrs.appcomm.template.word.builder.ExportDefinition;
+import com.zjrs.appcomm.template.word.util.POIUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * 操作Word的工具,目前仅仅包括导出Word功能,且需要配置word模板
+ * 
+ * <p>
+ * 范例:
+ * <blockquote><pre>
+ * DExample dData = new DExample();
+ * ExportDefinition<DExample> exportDefinition = ExportDefinition.builder(DExample.class)
+ * 		.document(?)
+ * 		.data(dData) // 注入数据
+ * 		.build();
+ * byte[] b = wordTemplate.exportFile(exportDefinition);
+ * </pre></blockquote><p>
+ * 配置word模板方法:<br />
+ * 1.在需要填充离散数据的位置通过${fieldName}方式配置,如注入的数据中存在getName获取数据的方法,则word模板中适当位置填写${name};<br />
+ * 2.在列表中需要填充列表的位置通过${parentName:item.childName}方式配置(模板中只需要保留一行模板即可),如注入的数据中存在getList方法获取列表数据,且其内存在getName获取数据的方法,则word模板中适当位置填写${list:item.name};<br />
+ * 3.在非列表中需要填充列表的位置通过${parentName:item.childName}方式配置(模板中需要在需要重复的位置前增加${parentName:begin}占位符,在需要重复的位置后增加${parentName:end}占位符),如注入的数据中存在getList方法获取列表数据,且其内存在getName获取数据的方法,则word模板中适当位置填写${list:item.name};<br />
+ * 4.在需要填充图片的位置通过${fieldName}方式配置其图片对象名称,注入数据必须是byte[]类型的图片内容(目前仅仅支持png和jpg类型图片)。
+ * @author froms
+ *
+ */
+public class WordTemplate {
+
+	public <E> void fillFile(ExportDefinition<E> exportDefinition) {
+		POIUtil.handleDocument(exportDefinition.getDocument(), exportDefinition.getData());
+	}
+	
+	public <E> byte[] exportFile(ExportDefinition<E> exportDefinition) throws IOException {
+		fillFile(exportDefinition);
+		try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+			exportDefinition.getDocument().write(os);
+			return os.toByteArray();
+		}
+	}
+}

+ 40 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/builder/ExportBuilder.java

@@ -0,0 +1,40 @@
+package com.zjrs.appcomm.template.word.builder;
+
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ExportBuilder<E> {
+	
+	private ExportDefinition<E> exportDefinition;
+	public ExportBuilder(Class<E> dataClass) {
+		this.exportDefinition = new ExportDefinition<>(dataClass);
+	}
+	
+	public ExportDefinition<E> build() {
+		return exportDefinition;
+	}
+
+	/** docx对象 */
+	public ExportBuilder<E> document(XWPFDocument document) {
+		exportDefinition.setDocument(document);
+		return this;
+	}
+	public ExportBuilder<E> document(InputStream is) throws IOException {
+		XWPFDocument document = new XWPFDocument(is);
+		return document(document);
+	}
+	public ExportBuilder<E> sheet(byte[] content) throws IOException {
+		try (InputStream is = new ByteArrayInputStream(content)) {
+			return document(is);
+		}
+	}
+
+	/** 数据 */
+	public ExportBuilder<E> data(E data) {
+		exportDefinition.setData(data);
+		return this;
+	}
+}

+ 28 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/builder/ExportDefinition.java

@@ -0,0 +1,28 @@
+package com.zjrs.appcomm.template.word.builder;
+
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+
+public class ExportDefinition<E> {
+	
+	public static <E> ExportBuilder<E> builder(Class<E> dataClass) {
+		return new ExportBuilder<>(dataClass);
+	}
+	public ExportDefinition(Class<E> dataClass) {
+		setDataClass(dataClass);
+	}
+	
+	private Class<E> dataClass;
+	public Class<E> getDataClass() { return dataClass; }
+	public void setDataClass(Class<E> dataClass) { this.dataClass = dataClass; }
+	
+	/** 数据 */
+	private E data;
+	public E getData() { return data; }
+	public void setData(E data) { this.data = data; }
+
+	/** docx对象 */
+	private XWPFDocument document;
+	public XWPFDocument getDocument() { return document; }
+	public void setDocument(XWPFDocument document) { this.document = document; }
+	
+}

+ 21 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/exception/IllegalMarkException.java

@@ -0,0 +1,21 @@
+package com.zjrs.appcomm.template.word.exception;
+
+public class IllegalMarkException extends IllegalArgumentException {
+
+	private static final long serialVersionUID = 5827854409482666951L;
+
+	private String mark;
+	public String getMark() { return mark; }
+	public void setMark(String mark) { this.mark = mark; }
+
+	public IllegalMarkException(String message, String mark) {
+		super(message);
+		this.mark = mark;
+	}
+
+	public IllegalMarkException(String message, String mark, Throwable cause) {
+		super(message + "(" + mark + ")", cause);
+		this.mark = mark;
+	}
+	
+}

+ 26 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/util/NameUtil.java

@@ -0,0 +1,26 @@
+package com.zjrs.appcomm.template.word.util;
+
+public class NameUtil {
+
+	public static String toFirstUpperCase(String name) {
+		return name.substring(0, 1).toUpperCase() + name.substring(1);
+	}
+	
+	public static String toFirstLowerCase(String name) {
+		return name.substring(0, 1).toLowerCase() + name.substring(1);
+	}
+	
+	public static String toUpperCaseCamelCase(String name) {
+		String[] nameWord = name.split("_");
+		String nameC = "";
+		for (int i = 0; i < nameWord.length; i++) {
+			nameC += nameWord[i].substring(0, 1).toUpperCase() + nameWord[i].substring(1).toLowerCase();
+		}
+		return nameC;
+	}
+
+	public static String toLowerCaseCamelCase(String name) {
+		String nameC = toUpperCaseCamelCase(name);
+		return nameC.substring(0, 1).toLowerCase() + nameC.substring(1);
+	}
+}

+ 656 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/template/word/util/POIUtil.java

@@ -0,0 +1,656 @@
+package com.zjrs.appcomm.template.word.util;
+
+import com.zjrs.appcomm.template.word.exception.IllegalMarkException;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.xwpf.usermodel.*;
+import org.apache.xmlbeans.XmlCursor;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
+import org.openxmlformats.schemas.drawingml.x2006.picture.CTPicture;
+import org.openxmlformats.schemas.drawingml.x2006.picture.CTPictureNonVisual;
+import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
+import org.springframework.util.ObjectUtils;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+
+public class POIUtil {
+
+	/** mark的开始标记 */
+	public static final String MARK_PREFIX = "${";
+	/** mark的伪属性分隔标记 */
+	public static final String MARK_PSEUDO = ":";
+	/** mark的属性分割标记 */
+	public static final String MARK_ATTRIBUTE = ".";
+	/** mark的结束标记 */
+	public static final String MARK_POSTFIX = "}";
+	
+	public enum Pseudo {
+		BEGIN, END, ITEM, INDEX;
+	}
+	
+	public static void handleDocument(XWPFDocument document, Object data) {
+		List<IBodyElement> sortedParagraphAndTable = new ArrayList<>(document.getBodyElements());
+
+		List<Integer> indexList = new ArrayList<>();
+
+		replaceBodyElementsMarks(sortedParagraphAndTable, data, indexList);
+	}
+
+	private static final int RETURN_ACTION_OK = 0;
+	private static final int RETURN_ACTION_REPEAT = 1;
+	private static final int RETURN_ACTION_OVER = 2;
+
+	private static int replaceBodyElementsMarks(List<IBodyElement> sortedParagraphAndTable, Object data, List<Integer> indexList) {
+		for (int i = 0; i < sortedParagraphAndTable.size(); i++) {
+			IBodyElement paragraphOrTable = sortedParagraphAndTable.get(i);
+			int action = replaceBodyElementMarks(paragraphOrTable, data, indexList);
+			if (action == RETURN_ACTION_REPEAT) {
+				int endIndex = findRepeatBodyElements(sortedParagraphAndTable, i);
+				List<IBodyElement> repeatBodyElements = new ArrayList<>();
+				for (int j = endIndex; j >= i; j--) {
+					repeatBodyElements.add(0, sortedParagraphAndTable.remove(j));
+				}
+				XWPFParagraph beginParagraph = (XWPFParagraph) repeatBodyElements.remove(0);
+				XWPFParagraph endParagraph = (XWPFParagraph) repeatBodyElements.remove(repeatBodyElements.size() - 1);
+
+				indexList.add(0);
+				int repeatIndexListI = indexList.size() - 1;
+				while (true) {
+					List<IBodyElement> newBodyElements = copyBodyElements(repeatBodyElements, beginParagraph);
+					int repeatAction = replaceBodyElementsMarks(newBodyElements, data, indexList);
+					if (repeatAction == RETURN_ACTION_OVER) {
+						removeBodyElements(newBodyElements);
+						indexList.remove(indexList.size() - 1);
+						break;
+					} else {
+						indexList.set(repeatIndexListI, indexList.get(repeatIndexListI) + 1);
+					}
+				}
+				removeParagraph(endParagraph);
+				removeBodyElements(repeatBodyElements);
+				removeParagraph(beginParagraph);
+			} else if (action == RETURN_ACTION_OVER) {
+				removeBodyElements(sortedParagraphAndTable);
+				return RETURN_ACTION_OVER;
+			}
+		}
+		return RETURN_ACTION_OK;
+	}
+	
+	private static void removeBodyElements(List<IBodyElement> bodyElements) {
+		for (int i = bodyElements.size() - 1; i >= 0; i--) {
+			IBodyElement bodyElement = bodyElements.get(i);
+			if (bodyElement instanceof XWPFParagraph) {
+				removeParagraph((XWPFParagraph) bodyElement);
+			} else if (bodyElement instanceof XWPFTable) {
+				removeTable((XWPFTable) bodyElement);
+			}
+		}
+	}
+	
+	private static void removeParagraph(XWPFParagraph paragraph) {
+		XWPFDocument document = paragraph.getDocument();
+		for (int i = 0; i < document.getBodyElements().size(); i++) {
+			if (document.getBodyElements().get(i).equals(paragraph)) {
+				document.removeBodyElement(i);
+				break;
+			}
+		}
+	}
+	
+	private static void removeTable(XWPFTable table) {
+		XWPFDocument document = table.getBody().getXWPFDocument();
+		for (int i = 0; i < document.getBodyElements().size(); i++) {
+			if (document.getBodyElements().get(i).equals(table)) {
+				document.removeBodyElement(i);
+				break;
+			}
+		}
+	}
+	
+	private static List<IBodyElement> copyBodyElements(List<IBodyElement> bodyElements, XWPFParagraph paragraph) {
+		List<IBodyElement> newBodyElements = new ArrayList<>();
+		for (IBodyElement bodyElement: bodyElements) {
+			if (bodyElement instanceof XWPFParagraph) {
+				newBodyElements.add(copyParagraph((XWPFParagraph) bodyElement, paragraph.getCTP().newCursor()));
+			} else if (bodyElement instanceof XWPFTable) {
+				newBodyElements.add(copyTable((XWPFTable) bodyElement, paragraph.getCTP().newCursor()));
+			}
+		}
+		return newBodyElements;
+	}
+	
+	private static XWPFParagraph copyParagraph(XWPFParagraph paragraph, XmlCursor cursor) {
+		XWPFParagraph newParagraph = paragraph.getDocument().insertNewParagraph(cursor);
+		if (paragraph.getCTP().getPPr() != null) {
+			newParagraph.getCTP().addNewPPr().set(paragraph.getCTP().getPPr().copy());
+		}
+		for (XWPFRun sourceRun: paragraph.getRuns()) {
+			XWPFRun targetRun = newParagraph.createRun();
+			targetRun.getCTR().addNewRPr().set(sourceRun.getCTR().getRPr().copy());
+			targetRun.setText(getText(sourceRun), 0);
+		}
+		return newParagraph;
+	}
+	
+	private static XWPFTable copyTable(XWPFTable table, XmlCursor cursor) {
+		XWPFDocument doc = table.getBody().getXWPFDocument();
+		XWPFTable newTable = doc.insertNewTbl(cursor);
+		newTable.removeRow(0);
+		newTable.getCTTbl().getTblPr().set(table.getCTTbl().getTblPr().copy());
+		for (int r = 0; r < table.getRows().size(); r++) {
+			XWPFTableRow row = table.getRows().get(r);
+			copyRow(newTable, row, r);
+		}
+		return newTable;
+	}
+	
+	private static XWPFTableRow copyRow(XWPFTable table, XWPFTableRow row, int pos) {
+		XWPFTableRow newRow = table.insertNewTableRow(pos);
+		if (row.getCtRow().getTrPr() != null) {
+			newRow.getCtRow().addNewTrPr().set(row.getCtRow().getTrPr().copy());
+		}
+		for (XWPFTableCell sourceCell: row.getTableCells()) {
+			XWPFTableCell targetCell = newRow.addNewTableCell();
+			targetCell.getCTTc().addNewTcPr().set(sourceCell.getCTTc().getTcPr().copy());
+			targetCell.removeParagraph(0);
+			for (XWPFParagraph sourceParagraph: sourceCell.getParagraphs()) {
+				XWPFParagraph targetParagraph = targetCell.addParagraph();
+				targetParagraph.getCTP().addNewPPr().set(sourceParagraph.getCTP().getPPr().copy());
+				for (XWPFRun sourceRun: sourceParagraph.getRuns()) {
+					XWPFRun targetRun = targetParagraph.createRun();
+					targetRun.getCTR().addNewRPr().set(sourceRun.getCTR().getRPr().copy());
+					targetRun.setText(getText(sourceRun), 0);
+				}
+			}
+		}
+		return newRow;
+	}
+	
+	private static int findRepeatBodyElements(List<IBodyElement> sortedParagraphAndTable, int beginIndex) {
+		int subBeginNum = 0;
+		for (int i = beginIndex + 1; i < sortedParagraphAndTable.size(); i++) {
+			IBodyElement paragraphOrTable = sortedParagraphAndTable.get(i);
+			if (paragraphOrTable instanceof XWPFParagraph) {
+				Set<String> marks = POIUtil.findMarks((XWPFParagraph) paragraphOrTable);
+				for (String mark: marks) {
+					String[] markSeg = mark.split("\\" + MARK_ATTRIBUTE);
+					String[] markPse = markSeg[markSeg.length - 1].split(MARK_PSEUDO);
+					Pseudo pseudo = null;
+					if (markPse.length >= 2) {
+						try {
+							pseudo = Pseudo.valueOf(markPse[1].toUpperCase());
+						} catch (RuntimeException e) {
+							throw new IllegalMarkException("mark伪属性未知", mark, e);
+						}
+					}
+					if (pseudo == Pseudo.BEGIN) {
+						subBeginNum++;
+					} else if (pseudo == Pseudo.END) {
+						if (subBeginNum == 0) {
+							return i;
+						} else {
+							subBeginNum--;
+						}
+					}
+				}
+			}
+		}
+		throw new IllegalMarkException("mark伪属性BEGIN未配对", null);
+	}
+
+	private static int replaceBodyElementMarks(IBodyElement paragraphOrTable, Object data, List<Integer> indexList) {
+		if (paragraphOrTable instanceof XWPFParagraph) {
+			int action = replaceParagraphMarks((XWPFParagraph) paragraphOrTable, data, indexList);
+			if (action == RETURN_ACTION_REPEAT) {
+				return RETURN_ACTION_REPEAT;
+			} else if (action == RETURN_ACTION_OVER) {
+				return RETURN_ACTION_OVER;
+			}
+		} else if (paragraphOrTable instanceof XWPFTable) {
+			replaceTableMarks((XWPFTable) paragraphOrTable, data, indexList);
+		}
+		return RETURN_ACTION_OK;
+	}
+
+	private static int replaceParagraphMarks(XWPFParagraph paragraph, Object data, List<Integer> indexList) {
+		Set<String> marks = POIUtil.findMarks(paragraph);
+		for (String mark: marks) {
+			String[] markSeg = mark.split("\\" + MARK_ATTRIBUTE);
+			String[] markPse = markSeg[markSeg.length - 1].split(MARK_PSEUDO);
+			Pseudo pseudo = null;
+			if (markPse.length >= 2) {
+				try {
+					pseudo = Pseudo.valueOf(markPse[1].toUpperCase());
+				} catch (RuntimeException e) {
+					throw new IllegalMarkException("mark伪属性未知", mark, e);
+				}
+			}
+			if (pseudo == Pseudo.BEGIN) {
+				return RETURN_ACTION_REPEAT;
+			} else if (pseudo == Pseudo.END) {
+				throw new IllegalMarkException("mark伪属性END未配对", mark);
+			} else {
+				try {
+					String d = reflectData(mark, data, indexList, String.class);
+					POIUtil.replaceOneText(paragraph, MARK_PREFIX + mark + MARK_POSTFIX, d);
+				} catch (NeedRepeat e) {
+					return RETURN_ACTION_REPEAT;
+				} catch (NoMoreData e) {
+					return RETURN_ACTION_OVER;
+				}
+			}
+		}
+		for (XWPFRun run: paragraph.getRuns()) {
+			String mark = findPictureMark(run);
+			if (mark != null) {
+				byte[] d = reflectData(mark, data, indexList, byte[].class);
+				try {
+					replacePicture(run, d);
+				} catch (InvalidFormatException e) {
+					throw new RuntimeException(e);
+				}
+			}
+		}
+		return RETURN_ACTION_OK;
+	}
+
+	private static void replaceTableMarks(XWPFTable table, Object data, List<Integer> indexList) {
+		int needDeleteRowTemplate = -1;
+		int needDeleteRowOver = -1;
+		for (int rowI = 0; rowI < table.getRows().size(); rowI++) {
+			XWPFTableRow row = table.getRows().get(rowI);
+			cellLine: for (XWPFTableCell cell: row.getTableCells()) {
+				for (XWPFParagraph paragraph: cell.getParagraphs()) {
+					int action = replaceParagraphMarks(paragraph, data, indexList);
+					if (action == RETURN_ACTION_REPEAT) {
+						needDeleteRowTemplate = rowI;
+						indexList.add(-1);
+						break cellLine;
+					} else if (action == RETURN_ACTION_OVER) {
+						needDeleteRowOver = rowI;
+					}
+				}
+			}
+			if (needDeleteRowTemplate != -1 && needDeleteRowOver == -1) {
+				copyRow(table, table.getRows().get(needDeleteRowTemplate), rowI + 1);
+				indexList.add(indexList.remove(indexList.size() - 1) + 1);
+			}
+		}
+		if (needDeleteRowTemplate != -1) {
+			table.removeRow(needDeleteRowOver);
+			table.removeRow(needDeleteRowTemplate);
+			indexList.remove(indexList.size() - 1);
+		}
+	}
+	
+	private static class NeedRepeat extends RuntimeException {
+		private static final long serialVersionUID = 7504615595418414701L;
+	}
+	private static class NoMoreData extends RuntimeException {
+		private static final long serialVersionUID = -3558032302829950554L;
+	}
+
+	private static <E> E reflectData(String mark, Object data, List<Integer> indexList, Class<E> clazz) {
+		Object currentData = data;
+		int indexI = 0;
+		String[] markSeg = mark.split("\\" + MARK_ATTRIBUTE);
+		for (int i = 0; i < markSeg.length; i++) {
+			String markS = markSeg[i];
+			String[] markPse = markS.split(MARK_PSEUDO);
+			Pseudo pseudo = null;
+			if (markPse.length == 2) {
+				try {
+					pseudo = Pseudo.valueOf(markPse[1].toUpperCase());
+				} catch (RuntimeException e) {
+					throw new IllegalMarkException("mark伪属性未知", mark, e);
+				}
+			} else if (markPse.length > 2) {
+				throw new IllegalMarkException("mark伪属性个数过多", mark);
+			}
+			if (i == markSeg.length - 1) {
+				if (pseudo == null) {
+					if (String.class.equals(clazz)) {
+						return (E) reflectStringData(mark, markPse[0], currentData);
+					} else if (byte[].class.equals(clazz)) {
+						return (E) reflectByteArrayData(mark, markPse[0], currentData);
+					} else {
+						throw new IllegalArgumentException("" + clazz);
+					}
+				} else if (pseudo == Pseudo.INDEX) {
+					List<?> list = reflectListData(mark, markPse[0], currentData);
+					if (indexI >= indexList.size()) {
+						throw new NeedRepeat();
+					}
+					int listI = indexList.get(indexI);
+					if (listI >= list.size()) {
+						throw new NoMoreData();
+					} else {
+						if (String.class.equals(clazz)) {
+							return (E) ((listI + 1) + "");
+						} else if (byte[].class.equals(clazz)) {
+							throw new IllegalMarkException("该mark不应出现在图片中", mark);
+						} else {
+							throw new IllegalArgumentException("" + clazz);
+						}
+					}
+				} else if (pseudo == Pseudo.ITEM) {
+					List<?> list = reflectListData(mark, markPse[0], currentData);
+					if (indexI >= indexList.size()) {
+						throw new NeedRepeat();
+					}
+					int listI = indexList.get(indexI);
+					if (listI >= list.size()) {
+						throw new NoMoreData();
+					} else {
+						if (String.class.equals(clazz)) {
+							return (E) ObjectUtils.getDisplayString(reflectListItemData(list, listI, mark));
+						} else if (byte[].class.equals(clazz)) {
+							return (E) reflectListItemData(list, listI, mark);
+						} else {
+							throw new IllegalArgumentException("" + clazz);
+						}
+					}
+				} else {
+					throw new IllegalMarkException("mark伪属性错误", mark);
+				}
+			} else {
+				if (pseudo == null) {
+					currentData = reflectData(mark, markPse[0], currentData);
+					if (currentData == null) {
+						if (String.class.equals(clazz)) {
+							return (E) "";
+						} else if (byte[].class.equals(clazz)) {
+							throw new IllegalMarkException("mark对应数据不能为null", mark);
+						} else {
+							throw new IllegalArgumentException("" + clazz);
+						}
+					}
+				} else if (pseudo == Pseudo.INDEX) {
+					throw new IllegalMarkException("错误mark", mark);
+				} else if (pseudo == Pseudo.ITEM) {
+					List<?> list = reflectListData(mark, markPse[0], currentData);
+					int listI = -1;
+					if (indexI >= indexList.size()) {
+						throw new NeedRepeat();
+					} else {
+						listI = indexList.get(indexI);
+					}
+					if (listI >= list.size()) {
+						throw new NoMoreData();
+					} else {
+						currentData = reflectListItemData(list, listI, mark);
+						indexI++;
+						if (currentData == null) {
+							if (String.class.equals(clazz)) {
+								return (E) "";
+							} else if (byte[].class.equals(clazz)) {
+								throw new IllegalMarkException("mark对应数据不能为null", mark);
+							} else {
+								throw new IllegalArgumentException("" + clazz);
+							}
+						}
+					}
+				} else {
+					throw new IllegalMarkException("mark伪属性错误", mark);
+				}
+			}
+		}
+		throw new IllegalMarkException("mark遍历数据异常", mark);
+	}
+	
+	private static Object reflectListItemData(List<?> list, int index, String mark) {
+		try {
+			return list.get(index);
+		} catch (ArrayIndexOutOfBoundsException e) {
+			System.err.println("找不到指定ListItem数据: " + mark);
+			return MARK_PREFIX + mark + MARK_POSTFIX;
+		}
+	}
+	
+	private static String reflectStringData(String mark, String markS, Object data) {
+		return ObjectUtils.getDisplayString(reflectData(mark, markS, data));
+	}
+	private static byte[] reflectByteArrayData(String mark, String markS, Object data) {
+		return (byte[]) reflectData(mark, markS, data);
+	}
+	
+	private static List<?> reflectListData(String mark, String markS, Object data) {
+		Object d = reflectData(mark, markS, data);
+		if (d == null) {
+			return Collections.emptyList();
+		} else if (d instanceof List) {
+			return (List<?>) d;
+		} else {
+			throw new IllegalMarkException("mark对应数据类型异常", mark);
+		}
+	}
+	
+	private static Object reflectData(String mark, String markS, Object data) {
+		String getMethod = "get" + NameUtil.toFirstUpperCase(markS);
+		try {
+			Method method = data.getClass().getMethod(getMethod);
+			return method.invoke(data);
+		} catch (SecurityException | NoSuchMethodException e) {
+			throw new IllegalMarkException("无mark对应数据", mark, e);
+		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+			throw new IllegalMarkException("获取mark对应数据异常", mark, e);
+		}
+	}
+	
+	public static String findPictureMark(XWPFRun run) {
+		List<XWPFPicture> pictures = run.getEmbeddedPictures();
+		if (pictures.isEmpty() || pictures.get(0) == null) {
+			return null;
+		}
+		CTPicture picture = pictures.get(0).getCTPicture();
+		if (picture == null) {
+			return null;
+		}
+		CTPictureNonVisual pictureNonVisual = picture.getNvPicPr();
+		if (pictureNonVisual == null) {
+			return null;
+		}
+		CTNonVisualDrawingProps nonVisualDrawingProps = pictureNonVisual.getCNvPr();
+		if (nonVisualDrawingProps == null) {
+			return null;
+		}
+		String name = nonVisualDrawingProps.getName();
+		if (name != null && name.startsWith(MARK_PREFIX) && name.endsWith(MARK_POSTFIX)) {
+			return name.substring(MARK_PREFIX.length(), name.length() - MARK_POSTFIX.length());
+		} else {
+			return null;
+		}
+	}
+	
+	public static Set<String> findMarks(XWPFParagraph paragraph) {
+		Set<String> markSet = new HashSet<>();
+		String text = getText(paragraph);
+		int index = 0;
+		while (true) {
+			int preIndex = text.indexOf(MARK_PREFIX, index);
+			if (preIndex < 0) {
+				break;
+			}
+			int postIndex = text.indexOf(MARK_POSTFIX, preIndex + MARK_PREFIX.length());
+			if (postIndex < 0) {
+				break;
+			}
+			String mark = text.substring(preIndex + MARK_PREFIX.length(), postIndex);
+			preIndex = mark.lastIndexOf(MARK_PREFIX);
+			if (preIndex >= 0) {
+				mark = mark.substring(preIndex + MARK_PREFIX.length());
+			}
+			if (mark.length() > 0) {
+				markSet.add(mark);
+			}
+			index = postIndex + MARK_POSTFIX.length();
+		}
+		return markSet;
+	}
+	
+	public static boolean replaceOneText(XWPFParagraph paragraph, String key, String value) {
+		/* 没有可替换的数据时直接返回 */
+		String paragraphText = getText(paragraph);
+		int index = paragraphText.indexOf(key);
+		if (index < 0) {
+			return false;
+		}
+		
+		List<XWPFRun> runs = paragraph.getRuns();
+		int runBeginIndex = -1;	// 需要替换的字符串所在的起始run
+		int runBeginStrIndex = -1;	// 需要替换你的字符串所在起始run的下标
+		int runEndIndex = -1;	// 需要替换的字符串所在的结束run
+		int runEndStrIndex = -1;	// 需要替换的字符串所在结束run的下标
+		
+		int runIndexT = 0;	// 遍历run的临时下标
+		int runStrLengthT = 0;	// 当前共遍历的字符串长度
+		for (; runIndexT < runs.size(); runIndexT++) {
+			int lastRunStrLengthT = runStrLengthT;
+			runStrLengthT += getText(runs.get(runIndexT)).length();
+			/* 找到需要替换的字符串后记录其所在的位置 */
+			if (runStrLengthT > index) {
+				runBeginIndex = runIndexT;
+				runBeginStrIndex = index - lastRunStrLengthT;
+				/* 当起始run与结束run在同一个run时,则记录结束run的位置 */
+				if (runStrLengthT >= index + key.length()) {
+					runEndIndex = runIndexT;
+					runEndStrIndex = runBeginStrIndex + key.length();
+				}
+				break;
+			}
+		}
+		/* 当起始run与结束run不在同一个run时,继续进行寻找 */
+		if (runEndIndex == -1) {
+			for (runIndexT++; runIndexT < runs.size(); runIndexT++) {
+				int lastRunStrLengthT = runStrLengthT;
+				runStrLengthT += getText(runs.get(runIndexT)).length();
+				/* 找到需要替换的字符串的结束run的位置 */
+				if (runStrLengthT >= index + key.length()) {
+					runEndIndex = runIndexT;
+					runEndStrIndex = index + key.length() - lastRunStrLengthT;
+					break;
+				}
+			}
+		}
+		
+		if (runBeginIndex == runEndIndex) {
+			/* 当需要替换的字符串的起始run与结束run在同一个run时,则直接进行替换 */
+			XWPFRun run = runs.get(runBeginIndex);
+			String runText = getText(run);
+			runText = runText.substring(0, runBeginStrIndex) + value + runText.substring(runEndStrIndex);	// TODO: 替换换行数据时,第二行存在行首空格
+			run.setText(runText, 0);
+		} else {
+			/* 当需要替换的字符串的起始run与结束run不在同一个run时,则起始run进行替换,后续run均进行删除 */
+			XWPFRun runBegin = runs.get(runBeginIndex);
+			String runBeginText = getText(runBegin);
+			runBeginText = runBeginText.substring(0, runBeginStrIndex) + value;	// TODO: 替换换行数据时,第二行存在行首空格
+			runBegin.setText(runBeginText, 0);
+			
+			XWPFRun runEnd = runs.get(runEndIndex);
+			String runEndText = getText(runEnd);
+			runEndText = runEndText.substring(runEndStrIndex);
+			runEnd.setText(runEndText, 0);
+			
+			/* 删除多于的run */
+			for (int i = runEndIndex - 1; i > runBeginIndex; i--) {
+				paragraph.removeRun(i);
+			}
+		}
+		
+		return true;
+	}
+	
+	private static void replacePicture(XWPFRun run, byte[] picture) throws InvalidFormatException {
+		String id = run.getDocument().addPictureData(picture, getPictureType(picture));
+		
+		CTR ctr = run.getCTR();
+		if (ctr == null) {
+			return;
+		}
+		List<CTDrawing> drawingList = ctr.getDrawingList();
+		if (drawingList.isEmpty() || drawingList.get(0) == null) {
+			return;
+		}
+		List<CTInline> inlineList = drawingList.get(0).getInlineList();
+		if (inlineList.isEmpty() || inlineList.get(0) == null) {
+			return;
+		}
+		CTGraphicalObject graphic = inlineList.get(0).getGraphic();
+		if (graphic == null) {
+			return;
+		}
+		CTGraphicalObjectData graphicalData = graphic.getGraphicData();
+		if (graphicalData == null) {
+			return;
+		}
+		Node graphicalDataNode = graphicalData.getDomNode();
+		if (graphicalDataNode == null) {
+			return;
+		}
+		for (int graphicalDataChildIndex = 0; graphicalDataChildIndex < graphicalDataNode.getChildNodes().getLength(); graphicalDataChildIndex++) {
+			Node graphicalDataChild = graphicalDataNode.getChildNodes().item(graphicalDataChildIndex);
+			if (graphicalDataChild.getNodeName().equals("pic:pic")) {
+				for (int picChildIndex = 0; picChildIndex < graphicalDataChild.getChildNodes().getLength(); picChildIndex++) {
+					Node picChild = graphicalDataChild.getChildNodes().item(picChildIndex);
+					if (picChild.getNodeName().equals("pic:blipFill")) {
+						for (int blipFillChildIndex = 0; blipFillChildIndex < picChild.getChildNodes().getLength(); blipFillChildIndex++) {
+							Node blipFillChild = picChild.getChildNodes().item(blipFillChildIndex);
+							if (blipFillChild.getNodeName().equals("a:blip")) {
+								NamedNodeMap blipAttributes = blipFillChild.getAttributes();
+								Node blipAttributesNode = blipAttributes.getNamedItem("r:embed");
+								blipAttributesNode.setNodeValue(id);
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	private static int getPictureType(byte[] b) {
+		if (b.length >= 8 && b[0] == (byte) 0x89 && b[1] == (byte) 0x50 && b[2] == (byte) 0x4E && b[3] == (byte) 0x47 && b[4] == (byte) 0x0D && b[5] == (byte) 0x0A && b[6] == (byte) 0x1A && b[7] == (byte) 0x0A) {
+			return Document.PICTURE_TYPE_PNG;
+		} else if (b.length >= 4 && b[0] == (byte) 0xff && b[1] == (byte) 0xd8 && b[2] == (byte) 0xff && b[3] == (byte) 0xd9) {
+			return Document.PICTURE_TYPE_JPEG;
+		} else if (b.length >= 4 && b[0] == (byte) 0xff && b[1] == (byte) 0xd8 && b[2] == (byte) 0xff && b[3] == (byte) 0xe0) {
+			return Document.PICTURE_TYPE_JPEG;
+		} else if (b.length >= 4 && b[0] == (byte) 0xff && b[1] == (byte) 0xd8 && b[2] == (byte) 0xff && b[3] == (byte) 0xe1) {
+			return Document.PICTURE_TYPE_JPEG;
+		} else if (b.length >= 4 && b[0] == (byte) 0xff && b[1] == (byte) 0xd8 && b[2] == (byte) 0xff && b[3] == (byte) 0xe8) {
+			return Document.PICTURE_TYPE_JPEG;
+		}else {
+			String bStr = "";
+			for (int i = 0; i < 8 && i < b.length; i++) {
+				bStr += b[i] + ",";
+			}
+			if (b.length > 16) {
+				bStr += "...,";
+			}
+			for (int i = Math.max(b.length - 8, 8); i < b.length; i++) {
+				bStr += b[i] + ",";
+			}
+			throw new IllegalArgumentException("不支持的图片文件类型: " + bStr);
+		}
+	}
+	
+	private static String getText(XWPFParagraph paragraph) {
+		StringBuilder sb = new StringBuilder();
+		for (XWPFRun run: paragraph.getRuns()) {
+			sb.append(getText(run));
+		}
+		return sb.toString();
+		// return paragraph.getText(); 为兼容旧版POI调整方法
+	}
+	private static String getText(XWPFRun run) {
+		// return run.text(); 为兼容旧版POI调整方法
+		return run.toString();
+	}
+}

+ 150 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/utils/IdcardUtil.java

@@ -0,0 +1,150 @@
+package com.zjrs.appcomm.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class IdcardUtil {
+    /**
+     * 身份证-加权因子
+     */
+    static final int[] SFZ_JIAQUANYINZI = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7,
+            9, 10, 5, 8, 4, 2};
+    /**
+     * 身份证-校验码
+     */
+    static final char[] SFZ_JIAOYANGMA = {'1', '0', 'X', '9', '8', '7', '6',
+            '5', '4', '3', '2'};
+    /**
+     * 日期YYYYMMDD格式的规则校验类
+     */
+    static final Pattern DATE_PATTERN = Pattern.compile("\\d{8}?");
+    /**
+     * 日期YYYYMM格式的格式化类(兼校验)
+     */
+    static final SimpleDateFormat CSRQ_FORMAT = new SimpleDateFormat("yyyyMMdd");
+
+    static ThreadLocal<SimpleDateFormat> LOCAL_FORMAT = new ThreadLocal<SimpleDateFormat>();
+
+    /**
+     * 校验身份证
+     *
+     * @param idcard 身份证
+     * @return {
+     * code  0校验通过,1校验不通过
+     * msg   校验信息
+     * sex   性别 (校验通过)
+     * birthday 出生日期 (校验通过)
+     * }
+     */
+    public static final Map<String, String> checkIdcard(String idcard) {
+        Map<String, String> resultMap = new HashMap<>();
+        StringBuilder t_gmsfz = new StringBuilder();
+        StringBuilder msg = new StringBuilder();
+        String sex = null;
+        String birthday = null;
+
+        t_gmsfz.append(idcard);
+        if (t_gmsfz.length() != 15 && t_gmsfz.length() != 18) {
+            msg.append("身份证").append(idcard).append("的长度有误,只能是15位或者18位。");
+        } else {
+            // 15位转18位
+            if (t_gmsfz.length() == 15) {
+                t_gmsfz.insert(6, "19");
+            }
+            // 1. 对前17位数字本体码加权求和
+            int li_sum = 0;
+            for (int i = 0; i < 17; i++) {
+                li_sum += Integer.parseInt(t_gmsfz.substring(i, i + 1))
+                        * SFZ_JIAQUANYINZI[i];
+            }
+            // 2. 以11对计算结果取模
+            int li_mod = li_sum % 11;
+            // 3. 根据模的值得到对应的校验码
+            char jiaoyangma = SFZ_JIAOYANGMA[li_mod];
+
+            // 如果17位身份证,那么拼接校验码即可
+            if (t_gmsfz.length() == 17) {
+                t_gmsfz.append(jiaoyangma);
+            } else if (t_gmsfz.charAt(17) != jiaoyangma) {// 比对校验位,看是否相符
+                msg.append("身份证").append(idcard).append("的校验码有误。");
+            }
+            // 获取出生日期
+            birthday = t_gmsfz.substring(6, 14);
+            // 获取性别,顺序码,奇数是男性,偶数是女性
+            sex = (Integer.parseInt(t_gmsfz.substring(16, 17)) & 1) == 0 ? "2"
+                    : "1";
+
+            // 校验出生日期
+            if (!checkDate(birthday)) {
+                msg.append("身份证").append(idcard).append("中的出生日期不合法。");
+            }
+            // 校验年龄,一出生日期不能是未来时间,二、年龄不能大于150岁
+            // /获取当前时间
+            if (CSRQ_FORMAT.format(new Date()).compareTo(birthday) < 0) {
+                msg.append("身份证").append(idcard).append("中的出生日期不合法。");
+            }
+        }
+        if (msg.length() > 0) {
+            //校验不通过
+            resultMap.put("code", "1");
+            resultMap.put("msg", msg.toString());
+            return resultMap;
+        }
+        //校验通过
+        resultMap.put("code", "0");
+        resultMap.put("msg", "校验通过");
+        resultMap.put("birthday", birthday);
+        resultMap.put("sex", sex);
+        return resultMap;
+    }
+
+    /**
+     * 校验社会统一信用代码
+     *
+     * @param str 社会统一信用代码
+     * @return
+     */
+    public static boolean checkTyshxydm(String str) {
+        Pattern zzjgdmR = Pattern.compile("[0-9A-Z]{18}");
+        Matcher zzjgdmM = zzjgdmR.matcher(str);
+        if (zzjgdmM.matches()) {
+            return true;
+        }
+        return false;
+
+    }
+
+    /**
+     * 检查日期字符串(格式YYYYMMDD)是否合法日期
+     *
+     * @param date 日期字符串(格式YYYYMMDD)
+     * @return true 表示合法,false表示不合法
+     */
+    public static boolean checkDate(String date) {
+        // 空或者空串一律返回false
+        if (date == null || date.length() == 0) {
+            return false;
+        }
+        // 日期类型必须全部是数字
+        if (!DATE_PATTERN.matcher(date).matches()) {
+            return false;
+        } else {
+            try {
+                SimpleDateFormat CSRQ_FORMAT = LOCAL_FORMAT.get();
+                if (CSRQ_FORMAT == null) {
+                    CSRQ_FORMAT = new SimpleDateFormat("yyyyMMdd");
+                    LOCAL_FORMAT.set(CSRQ_FORMAT);
+                }
+                CSRQ_FORMAT.parse(date);
+            } catch (ParseException e) {
+                return false;
+            }
+            return true;
+        }
+    }
+}

+ 61 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/utils/SpringContextUtil.java

@@ -0,0 +1,61 @@
+package com.zjrs.appcomm.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Pettyfer
+ */
+@Component
+public class SpringContextUtil implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        SpringContextUtil.applicationContext = applicationContext;
+    }
+
+    // 获取applicationContext
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+    // 通过name获取Bean
+    public static Object getBean(String name) {
+        return getApplicationContext().getBean(name);
+    }
+
+    //通过name,以及Clazz返回指定的Bean
+    public static <T> T getBean(String name, Class<T> clazz) {
+        return getApplicationContext().getBean(name, clazz);
+    }
+
+    /**
+     * 获取配置文件配置项的值
+     *
+     * @param key 配置项key
+     */
+    public static String getEnvironmentProperty(String key) {
+        return getApplicationContext().getEnvironment().getProperty(key);
+    }
+
+    /**
+     * 获取配置文件配置项的值,未获取到时返回默认值
+     *
+     * @param key 配置项key
+     */
+    public static String getEnvironmentProperty(String key, String defaultVal) {
+        return getEnvironmentProperty(key) == null ? defaultVal : getEnvironmentProperty(key);
+    }
+
+    /**
+     * 获取spring.profiles.active
+     */
+    public static String getActiveProfile() {
+        return getApplicationContext().getEnvironment().getActiveProfiles()[0];
+    }
+
+}

+ 192 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/utils/StringUtil.java

@@ -0,0 +1,192 @@
+package com.zjrs.appcomm.utils;
+
+import org.springframework.util.StringUtils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class StringUtil {
+	
+	private static final String CUT_TAIL = "...";
+	
+	/**
+	 * 对字符串按UTF8进行按字节的截断
+	 * @param str 需要截断的字符串
+	 * @param maxByteSize 需要截断的最大字节数
+	 * @return 截断结果
+	 */
+	public static String substring(String str, int maxByteSize) {
+		return substring(str, StandardCharsets.UTF_8, maxByteSize);
+	}
+	/**
+	 * 对字符串进行按字节的截断
+	 * @param str 需要截断的字符串
+	 * @param charset 字节转换的字符集
+	 * @param maxByteSize 需要截断的最大字节数
+	 * @return 截断结果
+	 */
+	public static String substring(String str, Charset charset, int maxByteSize) {
+		if (str == null) {
+			return null;
+		}
+		byte[] strByte = str.getBytes(charset);
+		if (strByte.length <= maxByteSize) {
+			return str;
+		}
+		byte[] strNewByte = Arrays.copyOfRange(str.getBytes(), 0, maxByteSize);
+		String newStr = null;
+		for (int bLength = strNewByte.length; bLength >= 0; bLength--) {
+			newStr = new String(strNewByte, 0, bLength, charset);
+			if (str.startsWith(newStr)) {
+				System.out.println(bLength);
+				break;
+			}
+		}
+		return newStr;
+	}
+	
+	/**
+	 * 将异常转换为字符串
+	 * @param ex 需要转换为字符串的异常
+	 * @return 转换结果
+	 */
+	public static String toString(Exception ex) {
+		if (ex == null) {
+			return null;
+		}
+		StringWriter sw = new StringWriter();
+		try (PrintWriter pw = new PrintWriter(sw)) {
+			ex.printStackTrace(pw);
+		}
+		return sw.toString();
+	}
+	
+	/**
+	 * 对字符串进行截断,当字符串超长需要截断时,将拼入“...”
+	 * @param str 需要截断的字符串
+	 * @param maxLength 需要截断的最大长度
+	 * @return 截断结果
+	 */
+	public static String cutWithTail(String str, int maxLength) {
+		if (str == null) {
+			return null;
+		} else if (str.length() > maxLength) {
+			return str.substring(0, maxLength - CUT_TAIL.length()) + CUT_TAIL;
+		} else {
+			return str;
+		}
+	}
+	
+	/**
+	 * 将异常进行截断,当字符串超长需要截断时,将拼入“...”
+	 * @param ex 需要截断的异常
+	 * @param maxLength 需要截断的最大长度
+	 * @return 截断结果
+	 */
+	public static String cutWithTail(Exception ex, int maxLength) {
+		return cutWithTail(toString(ex), maxLength);
+	}
+	
+	/**
+	 * 对字符串按UTF8进行按字节的截断,当字符串超长需要截断时,将拼入“...”
+	 * @param str 需要截断的字符串
+	 * @param maxByteSize 需要截断的最大字节数
+	 * @return 截断结果
+	 */
+	public static String cutWithTailByByte(String str, int maxByteSize) {
+		return cutWithTailByByte(str, StandardCharsets.UTF_8, maxByteSize);
+	}
+	
+	/**
+	 * 对字符串进行按字节的截断,当字符串超长需要截断时,将拼入“...”
+	 * @param str 需要截断的字符串
+	 * @param charset 字节转换的字符集
+	 * @param maxByteSize 需要截断的最大字节数
+	 * @return 截断结果
+	 */
+	public static String cutWithTailByByte(String str, Charset charset, int maxByteSize) {
+		if (str == null) {
+			return null;
+		} else if (str.getBytes(charset).length > maxByteSize) {
+			return substring(str, charset, maxByteSize - CUT_TAIL.getBytes(charset).length) + CUT_TAIL;
+		} else {
+			return str;
+		}
+	}
+	
+	/**
+	 * 对异常按UTF8进行按字节的截断,当字符串超长需要截断时,将拼入“...”
+	 * @param ex 需要截断的异常
+	 * @param maxByteSize 需要截断的最大字节数
+	 * @return 截断结果
+	 */
+	public static String cutWithTailByByte(Exception ex, int maxByteSize) {
+		return cutWithTailByByte(ex, StandardCharsets.UTF_8, maxByteSize);
+	}
+	
+	/**
+	 * 对异常进行按字节的截断,当字符串超长需要截断时,将拼入“...”
+	 * @param ex 需要截断的异常
+	 * @param charset 字节转换的字符集
+	 * @param maxByteSize 需要截断的最大字节数
+	 * @return 截断结果
+	 */
+	public static String cutWithTailByByte(Exception ex, Charset charset, int maxByteSize) {
+		return cutWithTailByByte(toString(ex), charset, maxByteSize);
+	}
+	
+	/**
+	 * 对字符串数组进行拼接
+	 * @param array 需要拼接的字符串数组
+	 * @param separator 拼接分隔符
+	 * @return 对字符串数组进行拼接后的结果
+	 */
+	public static String join(String[] array, String separator) {
+		StringBuilder sb = new StringBuilder();
+		for (String str: array) {
+			sb.append(separator).append(str);
+		}
+		if (sb.length() > 0) {
+			return sb.substring(separator.length());
+		}
+		return "";
+	}
+	
+	/**
+	 * 不含正则的字符串分隔
+	 * @param str 需要分割的字符串
+	 * @param separator 分割符
+	 * @param ignoreEmpty 是否忽略空串
+	 * @return 字符串分隔结果
+	 */
+	public static List<String> split(String str, String separator, boolean ignoreEmpty) {
+		if (StringUtils.isEmpty(separator)) {
+			throw new IllegalArgumentException("separator不能为空");
+		}
+		List<String> strList = new ArrayList<String>();
+		int i = 0;
+		do {
+			int index = str.indexOf(separator, i);
+			if (index < 0) {
+				String subStr = str.substring(i);
+				if (!(ignoreEmpty && StringUtils.isEmpty(subStr))) {
+					strList.add(subStr);
+				}
+				break;
+			} else {
+				String subStr = str.substring(i, index);
+				if (!(ignoreEmpty && StringUtils.isEmpty(subStr))) {
+					strList.add(subStr);
+				}
+				i = index + separator.length();
+			}
+		} while (true);
+		return strList;
+	}
+	
+}

+ 19 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/Chinese.java

@@ -0,0 +1,19 @@
+package com.zjrs.appcomm.validation;
+
+import jakarta.validation.Constraint;
+import java.lang.annotation.*;
+
+/**
+ * @author Pettyfer
+ */
+@Constraint(validatedBy = ChineseValidator.class)
+@Documented
+@Target({ElementType.PARAMETER,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Chinese {
+
+    String message() default "只允许输入中文";
+    Class[] groups() default {};
+    Class[] payload() default {};
+
+}

+ 21 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/ChineseValidator.java

@@ -0,0 +1,21 @@
+package com.zjrs.appcomm.validation;
+
+import cn.hutool.core.util.StrUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 纯中文验证
+ * @author Pettyfer
+ */
+public class ChineseValidator implements ConstraintValidator<Chinese, String> {
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StrUtil.isEmpty(value)) {
+            return true;
+        }
+        return value.matches("[^\u4E00-\u9FA5]");
+    }
+}

+ 19 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/IdCard.java

@@ -0,0 +1,19 @@
+package com.zjrs.appcomm.validation;
+
+import jakarta.validation.Constraint;
+import java.lang.annotation.*;
+
+/**
+ * @author Pettyfer
+ */
+@Constraint(validatedBy = IdCardValidator.class)
+@Documented
+@Target({ElementType.PARAMETER,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IdCard {
+
+    String message() default "身份证格式有误";
+    Class[] groups() default {};
+    Class[] payload() default {};
+
+}

+ 411 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/IdCardValidator.java

@@ -0,0 +1,411 @@
+package com.zjrs.appcomm.validation;
+
+import cn.hutool.core.util.StrUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 纯中文验证
+ * @author Pettyfer
+ */
+public class IdCardValidator implements ConstraintValidator<IdCard, String> {
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StrUtil.isEmpty(value)) {
+            return true;
+        }
+        if(value.length() == 15 || value.length() == 18) {
+            return isValidatedAllIdcard(value);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * <pre>
+     * 省、直辖市代码表:
+     *     11 : 北京  12 : 天津  13 : 河北       14 : 山西  15 : 内蒙古
+     *     21 : 辽宁  22 : 吉林  23 : 黑龙江  31 : 上海  32 : 江苏
+     *     33 : 浙江  34 : 安徽  35 : 福建       36 : 江西  37 : 山东
+     *     41 : 河南  42 : 湖北  43 : 湖南       44 : 广东  45 : 广西      46 : 海南
+     *     50 : 重庆  51 : 四川  52 : 贵州       53 : 云南  54 : 西藏
+     *     61 : 陕西  62 : 甘肃  63 : 青海       64 : 宁夏  65 : 新疆
+     *     71 : 台湾
+     *     81 : 香港  82 : 澳门
+     *     91 : 国外
+     * </pre>
+     */
+    private static String[] cityCode = { "11", "12", "13", "14", "15", "21",
+            "22", "23", "31", "32", "33", "34", "35", "36", "37", "41", "42",
+            "43", "44", "45", "46", "50", "51", "52", "53", "54", "61", "62",
+            "63", "64", "65", "71", "81", "82", "91" };
+
+    /**
+     * 每位加权因子
+     */
+    private static int power[] = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5,
+            8, 4, 2 };
+
+    /**
+     * 验证所有的身份证的合法性
+     *
+     * @param idcard
+     *            身份证
+     * @return 合法返回true,否则返回false
+     */
+    public static boolean isValidatedAllIdcard(String idcard) {
+        if (idcard == null || "".equals(idcard)) {
+            return false;
+        }
+        int s=15;
+        if (idcard.length() == s) {
+            return validate15IDCard(idcard);
+        }
+        int s1=18;
+        if(idcard.length()==s1) {
+            return validate18Idcard(idcard);
+        }
+        return false;
+
+    }
+
+    /**
+     * <p>
+     * 判断18位身份证的合法性
+     * </p>
+     * 根据〖中华人民共和国国家标准GB11643-1999〗中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。
+     * 排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
+     * <p>
+     * 顺序码: 表示在同一地址码所标识的区域范围内,对同年、同月、同 日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配 给女性。
+     * </p>
+     * <p>
+     * 1.前1、2位数字表示:所在省份的代码; 2.第3、4位数字表示:所在城市的代码; 3.第5、6位数字表示:所在区县的代码;
+     * 4.第7~14位数字表示:出生年、月、日; 5.第15、16位数字表示:所在地的派出所的代码;
+     * 6.第17位数字表示性别:奇数表示男性,偶数表示女性;
+     * 7.第18位数字是校检码:也有的说是个人信息码,一般是随计算机的随机产生,用来检验身份证的正确性。校检码可以是0~9的数字,有时也用x表示。
+     * </p>
+     * <p>
+     * 第十八位数字(校验码)的计算方法为: 1.将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4
+     * 2 1 6 3 7 9 10 5 8 4 2
+     * </p>
+     * <p>
+     * 2.将这17位数字和系数相乘的结果相加。
+     * </p>
+     * <p>
+     * 3.用加出来和除以11,看余数是多少
+     * </p>
+     * 4.余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3
+     * 2。
+     * <p>
+     * 5.通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2。
+     * </p>
+     *
+     * @param idcard
+     * @return
+     */
+    public static boolean validate18Idcard(String idcard) {
+        if (idcard == null) {
+            return false;
+        }
+
+        // 非18位为假
+        int s=18;
+        if (idcard.length() != s) {
+            return false;
+        }
+        // 获取前17位
+        String idcard17 = idcard.substring(0, 17);
+
+        // 前17位全部为数字
+        if (!isDigital(idcard17)) {
+            return false;
+        }
+
+        String provinceid = idcard.substring(0, 2);
+        // 校验省份
+        if (!checkProvinceid(provinceid)) {
+            return false;
+        }
+
+        // 校验出生日期
+        String birthday = idcard.substring(6, 14);
+
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+
+        try {
+            Date birthDate = sdf.parse(birthday);
+            String tmpDate = sdf.format(birthDate);
+            // 出生年月日不正确
+            if (!tmpDate.equals(birthday)) {
+                return false;
+            }
+
+        } catch (Exception e1) {
+
+            return false;
+        }
+
+        // 获取第18位
+        String idcard18Code = idcard.substring(17, 18);
+
+        char c[] = idcard17.toCharArray();
+
+        int bit[] = converCharToInt(c);
+
+        int sum17 = 0;
+
+        sum17 = getPowerSum(bit);
+
+        // 将和值与11取模得到余数进行校验码判断
+        String checkCode = getCheckCodeBySum(sum17);
+        if (null == checkCode) {
+            return false;
+        }
+        // 将身份证的第18位与算出来的校码进行匹配,不相等就为假
+        if (!idcard18Code.equalsIgnoreCase(checkCode)) {
+            return false;
+        }
+        //System.out.println("正确");
+        return true;
+    }
+
+    /**
+     * 校验15位身份证
+     *
+     * <pre>
+     * 只校验省份和出生年月日
+     * </pre>
+     *
+     * @param idcard
+     * @return
+     */
+    public static boolean validate15IDCard(String idcard) {
+        if (idcard == null) {
+            return false;
+        }
+        // 非15位为假
+        int s=15;
+        if (idcard.length() != s) {
+            return false;
+        }
+
+        // 15全部为数字
+        if (!isDigital(idcard)) {
+            return false;
+        }
+
+        String provinceid = idcard.substring(0, 2);
+        // 校验省份
+        if (!checkProvinceid(provinceid)) {
+            return false;
+        }
+
+        String birthday = idcard.substring(6, 12);
+
+        SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
+
+        try {
+            Date birthDate = sdf.parse(birthday);
+            String tmpDate = sdf.format(birthDate);
+            // 身份证日期错误
+            if (!tmpDate.equals(birthday)) {
+                return false;
+            }
+
+        } catch (Exception e1) {
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 将15位的身份证转成18位身份证
+     *
+     * @param idcard
+     * @return
+     */
+    public static String convertIdcarBy15bit(String idcard) {
+        if (idcard == null) {
+            return null;
+        }
+
+        // 非15位身份证
+        int s=15;
+        if (idcard.length() != s) {
+            return null;
+        }
+
+        // 15全部为数字
+        if (!isDigital(idcard)) {
+            return null;
+        }
+
+        String provinceid = idcard.substring(0, 2);
+        // 校验省份
+        if (!checkProvinceid(provinceid)) {
+            return null;
+        }
+
+        String birthday = idcard.substring(6, 12);
+
+        SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
+
+        Date birthdate = null;
+        try {
+            birthdate = sdf.parse(birthday);
+            String tmpDate = sdf.format(birthdate);
+            // 身份证日期错误
+            if (!tmpDate.equals(birthday)) {
+                return null;
+            }
+
+        } catch (Exception e1) {
+            return null;
+        }
+
+        Calendar cday = Calendar.getInstance();
+        cday.setTime(birthdate);
+        String year = String.valueOf(cday.get(Calendar.YEAR));
+
+        String idcard17 = idcard.substring(0, 6) + year + idcard.substring(8);
+
+        char c[] = idcard17.toCharArray();
+        String checkCode = "";
+
+        // 将字符数组转为整型数组
+        int bit[] = converCharToInt(c);
+
+        int sum17 = 0;
+        sum17 = getPowerSum(bit);
+
+        // 获取和值与11取模得到余数进行校验码
+        checkCode = getCheckCodeBySum(sum17);
+
+        // 获取不到校验位
+        if (null == checkCode) {
+            return null;
+        }
+        // 将前17位与第18位校验码拼接
+        idcard17 += checkCode;
+        return idcard17;
+    }
+
+    /**
+     * 校验省份
+     *
+     * @param provinceid
+     * @return 合法返回TRUE,否则返回FALSE
+     */
+    private static boolean checkProvinceid(String provinceid) {
+        for (String id : cityCode) {
+            if (id.equals(provinceid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 数字验证
+     *
+     * @param str
+     * @return
+     */
+    private static boolean isDigital(String str) {
+        return str.matches("^[0-9]*$");
+    }
+
+    /**
+     * 将身份证的每位和对应位的加权因子相乘之后,再得到和值
+     *
+     * @param bit
+     * @return
+     */
+    private static int getPowerSum(int[] bit) {
+
+        int sum = 0;
+
+        if (power.length != bit.length) {
+            return sum;
+        }
+
+        for (int i = 0; i < bit.length; i++) {
+            for (int j = 0; j < power.length; j++) {
+                if (i == j) {
+                    sum = sum + bit[i] * power[j];
+                }
+            }
+        }
+        return sum;
+    }
+
+    /**
+     * 将和值与11取模得到余数进行校验码判断
+     *
+     * @param sum17
+     * @return 校验位
+     */
+    private static String getCheckCodeBySum(int sum17) {
+        String checkCode = null;
+        switch (sum17 % 11) {
+            case 10:
+                checkCode = "2";
+                break;
+            case 9:
+                checkCode = "3";
+                break;
+            case 8:
+                checkCode = "4";
+                break;
+            case 7:
+                checkCode = "5";
+                break;
+            case 6:
+                checkCode = "6";
+                break;
+            case 5:
+                checkCode = "7";
+                break;
+            case 4:
+                checkCode = "8";
+                break;
+            case 3:
+                checkCode = "9";
+                break;
+            case 2:
+                checkCode = "x";
+                break;
+            case 1:
+                checkCode = "0";
+                break;
+            case 0:
+                checkCode = "1";
+                break;
+            default:
+        }
+        return checkCode;
+    }
+
+    /**
+     * 将字符数组转为整型数组
+     *
+     * @param c
+     * @return
+     * @throws NumberFormatException
+     */
+    private static int[] converCharToInt(char[] c) throws NumberFormatException {
+        int[] a = new int[c.length];
+        int k = 0;
+        for (char temp : c) {
+            a[k++] = Integer.parseInt(String.valueOf(temp));
+        }
+        return a;
+    }
+}

+ 19 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/Money.java

@@ -0,0 +1,19 @@
+package com.zjrs.appcomm.validation;
+
+import jakarta.validation.Constraint;
+import java.lang.annotation.*;
+
+/**
+ * @author Pettyfer
+ */
+@Constraint(validatedBy = MoneyValidator.class)
+@Documented
+@Target({ElementType.PARAMETER,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Money {
+
+    String message() default "错误的金额";
+    Class[] groups() default {};
+    Class[] payload() default {};
+
+}

+ 20 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/MoneyValidator.java

@@ -0,0 +1,20 @@
+package com.zjrs.appcomm.validation;
+
+import cn.hutool.core.util.StrUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 验证金额格式
+ * @author Pettyfer
+ */
+public class MoneyValidator implements ConstraintValidator<Money, String>  {
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StrUtil.isEmpty(value)) {
+            return true;
+        }
+        return value.matches("^([1-9]\\d{0,9}|0)([.]?|(\\.\\d{0,6})?)$");
+    }
+}

+ 18 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/Number.java

@@ -0,0 +1,18 @@
+package com.zjrs.appcomm.validation;
+
+import jakarta.validation.Constraint;
+import java.lang.annotation.*;
+
+/**
+ * 验证是否为手机号码
+ * @author Petty
+ */
+@Constraint(validatedBy = NumberValidator.class)
+@Documented
+@Target({ElementType.PARAMETER,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Number {
+    String message() default "无效数字";
+    Class[] groups() default {};
+    Class[] payload() default {};
+}

+ 19 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/NumberLetters.java

@@ -0,0 +1,19 @@
+package com.zjrs.appcomm.validation;
+
+import jakarta.validation.Constraint;
+import java.lang.annotation.*;
+
+/**
+ * @author Pettyfer
+ */
+@Constraint(validatedBy = NumberLettersValidator.class)
+@Documented
+@Target({ElementType.PARAMETER,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NumberLetters {
+
+    String message() default "只允许输入数字和字母";
+    Class[] groups() default {};
+    Class[] payload() default {};
+
+}

+ 20 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/NumberLettersValidator.java

@@ -0,0 +1,20 @@
+package com.zjrs.appcomm.validation;
+
+import cn.hutool.core.util.StrUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 数字和字母格式验证
+ * @author Pettyfer
+ */
+public class NumberLettersValidator implements ConstraintValidator<NumberLetters, String> {
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StrUtil.isEmpty(value)) {
+            return true;
+        }
+        return value.matches("^[A-Za-z0-9]+$");
+    }
+}

+ 21 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/NumberValidator.java

@@ -0,0 +1,21 @@
+package com.zjrs.appcomm.validation;
+
+import cn.hutool.core.util.StrUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 手机号码验证逻辑
+ * @author Petty
+ */
+public class NumberValidator implements ConstraintValidator<Number, String> {
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        if (StrUtil.isEmpty(value)) {
+            return true;
+        }
+        return value.matches("^[0-9]*$");
+    }
+
+}

+ 18 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/PhoneNumber.java

@@ -0,0 +1,18 @@
+package com.zjrs.appcomm.validation;
+
+import jakarta.validation.Constraint;
+import java.lang.annotation.*;
+
+/**
+ * 验证是否为手机号码
+ * @author Petty
+ */
+@Constraint(validatedBy = PhoneNumberValidator.class)
+@Documented
+@Target({ElementType.PARAMETER,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PhoneNumber {
+    String message() default "无效的手机号码";
+    Class[] groups() default {};
+    Class[] payload() default {};
+}

+ 21 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/PhoneNumberValidator.java

@@ -0,0 +1,21 @@
+package com.zjrs.appcomm.validation;
+
+import cn.hutool.core.util.StrUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 手机号码验证逻辑
+ * @author Petty
+ */
+public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        if (StrUtil.isEmpty(value)) {
+            return true;
+        }
+        return value.matches("^[1][3-9][0-9]{9}$");
+    }
+
+}

+ 19 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/SpecialSymbols.java

@@ -0,0 +1,19 @@
+package com.zjrs.appcomm.validation;
+
+import jakarta.validation.Constraint;
+import java.lang.annotation.*;
+
+/**
+ * @author Pettyfer
+ */
+@Constraint(validatedBy = SpecialSymbolsValidator.class)
+@Documented
+@Target({ElementType.PARAMETER,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SpecialSymbols {
+
+    String message() default "不允许特殊字符";
+    Class[] groups() default {};
+    Class[] payload() default {};
+
+}

+ 20 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/SpecialSymbolsValidator.java

@@ -0,0 +1,20 @@
+package com.zjrs.appcomm.validation;
+
+import cn.hutool.core.util.StrUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 特殊字符判断
+ * @author Pettyfer
+ */
+public class SpecialSymbolsValidator implements ConstraintValidator<SpecialSymbols, String> {
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StrUtil.isEmpty(value)) {
+            return true;
+        }
+        return !value.matches("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?]");
+    }
+}

+ 19 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/TelPhone.java

@@ -0,0 +1,19 @@
+package com.zjrs.appcomm.validation;
+
+import jakarta.validation.Constraint;
+import java.lang.annotation.*;
+
+/**
+ * @author Pettyfer
+ */
+@Constraint(validatedBy = TelPhoneValidator.class)
+@Documented
+@Target({ElementType.PARAMETER,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TelPhone {
+
+    String message() default "联系电话格式错误";
+    Class[] groups() default {};
+    Class[] payload() default {};
+
+}

+ 20 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/validation/TelPhoneValidator.java

@@ -0,0 +1,20 @@
+package com.zjrs.appcomm.validation;
+
+import cn.hutool.core.util.StrUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 联系电话格式验证
+ * @author Pettyfer
+ */
+public class TelPhoneValidator implements ConstraintValidator<TelPhone, String> {
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StrUtil.isEmpty(value)) {
+            return true;
+        }
+        return value.matches("^(\\(\\d{3,4}\\)|\\d{3,4}-|\\s)?\\d{7,14}$")||value.matches("^[1][3-9][0-9]{9}$");
+    }
+}

+ 80 - 0
zjrs-service-backend/src/main/java/com/zjrs/appcomm/web/ZjrsControllerAdvice.java

@@ -0,0 +1,80 @@
+//package com.zjrs.appcomm.web;
+//
+//import org.mohrss.leaf.core.framework.web.controller.AjaxResponse;
+//import org.mohrss.leaf.core.framework.web.exception.AppException;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.context.MessageSourceResolvable;
+//import org.springframework.core.annotation.Order;
+//import org.springframework.http.HttpStatus;
+//import org.springframework.http.ResponseEntity;
+//import org.springframework.http.converter.HttpMessageNotReadableException;
+//import org.springframework.validation.BindException;
+//import org.springframework.web.bind.MethodArgumentNotValidException;
+//import org.springframework.web.bind.annotation.ControllerAdvice;
+//import org.springframework.web.bind.annotation.ExceptionHandler;
+//import org.springframework.web.bind.annotation.ResponseBody;
+//
+//import java.util.stream.Collectors;
+//
+///**
+// * 湛江人社统一异常处理
+// *
+// * @author yuanzhen
+// */
+//@ControllerAdvice
+//@Order(1)
+//@ResponseBody
+//public class ZjrsControllerAdvice {
+//    private static final Logger logger = LoggerFactory.getLogger(ZjrsControllerAdvice.class);
+//
+//    private String defaultMessage = "系统异常";
+//
+//    /**
+//     * 构造响应参数
+//     */
+//    private ResponseEntity<AjaxResponse> getResponseEntity(HttpStatus httpStatus, String message) {
+//        AjaxResponse ajaxResponse = new AjaxResponse();
+//        ajaxResponse.setMsg(message);
+//        ajaxResponse.setAppcode("1");
+//        return new ResponseEntity<AjaxResponse>(ajaxResponse, httpStatus);
+//    }
+//
+//    @ExceptionHandler(AppException.class)
+//    public ResponseEntity<AjaxResponse> processAppException(AppException e) {
+//        return getResponseEntity(HttpStatus.OK, e.getMessage());
+//    }
+//
+//    @ExceptionHandler(HttpMessageNotReadableException.class)
+//    public ResponseEntity<AjaxResponse> processHttpMessageNotReadableException(HttpMessageNotReadableException e){
+//        return getResponseEntity(HttpStatus.BAD_REQUEST, "格式错误");
+//    }
+//
+//    @ExceptionHandler({MethodArgumentNotValidException.class})
+//    public ResponseEntity<AjaxResponse> processMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+//        return getResponseEntity(HttpStatus.BAD_REQUEST, e.getBindingResult().getAllErrors().stream().map(MessageSourceResolvable::getDefaultMessage)
+//                .collect(Collectors.joining(",")));
+//    }
+//
+//    @ExceptionHandler({BindException.class})
+//    public ResponseEntity<AjaxResponse> processBindException(BindException e) {
+//        return getResponseEntity(HttpStatus.BAD_REQUEST, e.getBindingResult().getAllErrors().stream().map(MessageSourceResolvable::getDefaultMessage)
+//                .collect(Collectors.joining(",")));
+//    }
+//
+//    @ExceptionHandler({Exception.class})
+//    public ResponseEntity<AjaxResponse> processException(Exception e) {
+//        return processThrowable(e);
+//    }
+//
+//    @ExceptionHandler({Error.class})
+//    public ResponseEntity<AjaxResponse> processError(Error e) {
+//        return processThrowable(e);
+//    }
+//
+//    @ExceptionHandler(Throwable.class)
+//    public ResponseEntity<AjaxResponse> processThrowable(Throwable e) {
+//        logger.error(defaultMessage, e);
+//        return getResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, defaultMessage);
+//    }
+//}

+ 0 - 1
zjrs-service-backend/src/main/resources/application.properties

@@ -1 +0,0 @@
-spring.application.name=zjrs-service-backend

+ 3 - 0
zjrs-service-backend/src/main/resources/application.yaml

@@ -0,0 +1,3 @@
+spring:
+  application:
+    name: zjrs-service-backend

BIN
zjrs-service-backend/src/main/resources/lib/leaf6-uni-cloud-starter-1.0.0-SinoBest.20.jar