From 40d262e091d43b15c082260b7279daf4e89b2799 Mon Sep 17 00:00:00 2001
From: luohairen <3399054449@qq.com>
Date: 星期五, 29 十一月 2024 07:48:36 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 common/src/main/java/com/ycl/common/utils/DateUtils.java                                      |   35 
 common/src/main/java/com/ycl/common/enums/business/ProjectCategoryEnum.java                   |   51 
 common/src/main/java/com/ycl/common/utils/excel/core/ExcelResult.java                         |   26 
 common/src/main/java/com/ycl/common/utils/excel/service/DictService.java                      |   67 +
 common/src/main/java/com/ycl/common/utils/excel/core/DropDownOptions.java                     |  149 ++
 common/src/main/java/com/ycl/common/utils/excel/core/ExcelListener.java                       |   14 
 business/src/main/java/com/ycl/service/impl/ProjectInvestmentPolicyComplianceServiceImpl.java |    2 
 common/src/main/java/com/ycl/common/utils/excel/annotation/ExcelDictFormat.java               |   32 
 business/src/main/java/com/ycl/domain/query/ProjectInfoQuery.java                             |   36 
 common/src/main/java/com/ycl/common/utils/excel/OutputExcelUtils.java                         |  217 +++
 common/src/main/java/com/ycl/common/utils/excel/core/DefaultExcelResult.java                  |   73 +
 business/src/main/resources/mapper/ProjectInfoMapper.xml                                      |  170 +-
 common/src/main/java/com/ycl/common/utils/excel/utils/ValidatorUtils.java                     |   36 
 common/src/main/java/com/ycl/common/utils/CopyUtils.java                                      |   48 
 common/src/main/java/com/ycl/common/utils/excel/core/CellMergeStrategy.java                   |  157 ++
 business/src/main/java/com/ycl/domain/excel/ProjectExcelTemplate.java                         |  655 +++++++++++
 business/src/main/java/com/ycl/domain/vo/ProjectVO.java                                       |   17 
 common/src/main/java/com/ycl/common/utils/excel/core/ExcelDownHandler.java                    |  373 ++++++
 business/src/main/java/com/ycl/domain/vo/ProjectInfoVO.java                                   |   26 
 common/src/main/java/com/ycl/common/utils/excel/utils/JsonUtils.java                          |  169 ++
 common/src/main/java/com/ycl/common/utils/excel/convert/ExcelEnumConvert.java                 |   87 +
 common/src/main/java/com/ycl/common/utils/excel/convert/ExcelBigNumberConvert.java            |   52 
 business/src/main/java/com/ycl/domain/entity/ProjectInfo.java                                 |    6 
 common/src/main/java/com/ycl/common/utils/excel/annotation/CellMerge.java                     |   29 
 business/src/main/java/com/ycl/controller/ProjectInfoController.java                          |   17 
 business/src/main/java/com/ycl/service/impl/ProjectInfoServiceImpl.java                       |   84 +
 common/src/main/java/com/ycl/common/utils/excel/utils/ReflectUtils.java                       |   55 
 common/src/main/java/com/ycl/common/utils/excel/utils/StringUtils.java                        |  323 +++++
 common/pom.xml                                                                                |    9 
 common/src/main/java/com/ycl/common/utils/excel/annotation/ExcelEnumFormat.java               |   30 
 system/pom.xml                                                                                |    6 
 common/src/main/java/com/ycl/common/utils/excel/core/DefaultExcelListener.java                |  104 +
 common/src/main/java/com/ycl/common/utils/excel/utils/StreamUtils.java                        |  282 ++++
 33 files changed, 3,335 insertions(+), 102 deletions(-)

diff --git a/business/src/main/java/com/ycl/controller/ProjectInfoController.java b/business/src/main/java/com/ycl/controller/ProjectInfoController.java
index f2e314c..768ed26 100644
--- a/business/src/main/java/com/ycl/controller/ProjectInfoController.java
+++ b/business/src/main/java/com/ycl/controller/ProjectInfoController.java
@@ -4,6 +4,8 @@
 import com.ycl.common.group.Add;
 import com.ycl.common.group.Update;
 import com.ycl.common.utils.ProjectCodeGenerator;
+import com.ycl.common.utils.excel.OutputExcelUtils;
+import com.ycl.domain.excel.ProjectExcelTemplate;
 import com.ycl.domain.form.DocumentInfoForm;
 import com.ycl.domain.form.ProjectInfoForm;
 import com.ycl.domain.query.ProjectInfoQuery;
@@ -14,7 +16,9 @@
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletResponse;
 import javax.validation.constraints.NotEmpty;
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -104,4 +108,17 @@
     public Result getManagerFlag(@PathVariable("recordId") Integer recordId) {
         return projectInfoService.getManagerFlag(recordId);
     }
+
+    /**
+     * 瀵煎嚭妯℃澘
+     * @param response
+     * @return
+     */
+    @PostMapping("/export/template")
+    public void exportTemplate(HttpServletResponse response,
+                               @RequestBody List<String> fieldList
+    ) throws IOException {
+        OutputExcelUtils.export(response, "瀵煎叆妯℃澘", "椤圭洰淇℃伅", null, ProjectExcelTemplate.class ,fieldList);
+    }
+
 }
diff --git a/business/src/main/java/com/ycl/domain/entity/ProjectInfo.java b/business/src/main/java/com/ycl/domain/entity/ProjectInfo.java
index d5eec5c..2029fa3 100644
--- a/business/src/main/java/com/ycl/domain/entity/ProjectInfo.java
+++ b/business/src/main/java/com/ycl/domain/entity/ProjectInfo.java
@@ -49,9 +49,9 @@
     /** 鎶曡祫绫诲埆锛�0浼佷笟鎶曡祫锛�1鏀垮簻鎶曡祫锛�2澶栧晢鎶曡祫锛�3澧冨鎶曡祫锛� */
     private String investType;
 
-    @TableField("project_phase")
-    /** 椤圭洰闃舵(0鍌ㄥ瑙勫垝闃舵,  1椤圭洰鍓嶆湡闃舵,  2瀹炴柦闃舵,  3绔e伐鎶曠敤闃舵) */
-    private String projectPhase;
+//    @TableField("project_phase")
+//    /** 椤圭洰闃舵(0鍌ㄥ瑙勫垝闃舵,  1椤圭洰鍓嶆湡闃舵,  2瀹炴柦闃舵,  3绔e伐鎶曠敤闃舵) */
+//    private String projectPhase;
 
     @TableField("tag")
     /** 鏍囩 */
diff --git a/business/src/main/java/com/ycl/domain/excel/ProjectExcelTemplate.java b/business/src/main/java/com/ycl/domain/excel/ProjectExcelTemplate.java
new file mode 100644
index 0000000..58228b0
--- /dev/null
+++ b/business/src/main/java/com/ycl/domain/excel/ProjectExcelTemplate.java
@@ -0,0 +1,655 @@
+package com.ycl.domain.excel;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 瀵煎嚭妯℃澘绫�
+ *
+ * @Author: ljx
+ * @CreateTime: 2024-10-18 10:19
+ */
+
+@Data
+public class ProjectExcelTemplate {
+    private static final long serialVersionUID = 1L;
+
+                                                /* 椤圭洰绠$悊鍩虹淇℃伅琛� */
+
+    /**
+     * 椤圭洰鍚嶇О
+     */
+    @ExcelProperty(value = "椤圭洰鍚嶇О")
+    private String projectName;
+
+    /**
+     * 椤圭洰浠g爜
+     */
+    @ExcelProperty(value = "椤圭洰浠g爜")
+    private String projectCode;
+
+    /**
+     * 椤圭洰绫诲瀷
+     */
+    @ExcelProperty(value = "椤圭洰绫诲瀷")
+    private String projectType;
+
+    /**
+     * 椤圭洰鐘舵��
+     */
+    @ExcelProperty(value = "椤圭洰鐘舵��")
+    private String projectStatus;
+
+    /**
+     * 璧勯噾绫诲瀷
+     */
+    @ExcelProperty(value = "璧勯噾绫诲瀷")
+    private String fundType;
+
+    /**
+     * 鎶曡祫绫诲埆
+     */
+    @ExcelProperty(value = "鎶曡祫绫诲埆")
+    private String investType;
+
+    /**
+     * 閲嶇偣鍒嗙被
+     */
+    @ExcelProperty(value = "閲嶇偣鍒嗙被")
+    private String importanceType;
+
+    /**
+     * 椤圭洰闃舵
+     */
+    @ExcelProperty(value = "椤圭洰闃舵")
+    private String projectPhase;
+
+    /**
+     * 鏍囩
+     */
+    @ExcelProperty(value = "鏍囩")
+    private String tag;
+
+    /**
+     * 涓荤閮ㄩ棬
+     */
+    @ExcelProperty(value = "涓荤閮ㄩ棬")
+    private String competentDepartment;
+
+    /**
+     * 椤圭洰褰掑睘鍦�
+     */
+    @ExcelProperty(value = "椤圭洰褰掑睘鍦�")
+    private String projectLocation;
+
+    /**
+     * 缁忓害
+     */
+    @ExcelProperty(value = "缁忓害")
+    private String longitude;
+
+    /**
+     * 绾害
+     */
+    @ExcelProperty(value = "绾害")
+    private String latitude;
+
+    /**
+     * 绠$悊褰掑彛
+     */
+    @ExcelProperty(value = "绠$悊褰掑彛")
+    private String managementCentralization;
+
+    /**
+     * 椤圭洰鐢虫姤闃舵
+     */
+    @ExcelProperty(value = "椤圭洰鐢虫姤闃舵")
+    private String projectApplicationPhase;
+
+    /**
+     * 椤圭洰瀹℃壒绫诲瀷
+     */
+    @ExcelProperty(value = "椤圭洰瀹℃壒绫诲瀷")
+    private String projectApprovalType;
+
+    /**
+     * 鎶曡祫鐩綍
+     */
+    @ExcelProperty(value = "鎶曡祫鐩綍")
+    private String investmentCatalogue;
+
+    /**
+     * 瀹℃壒璁″垝涔�
+     */
+    @ExcelProperty(value = "瀹℃壒璁″垝涔︼紙闄勪欢鍚嶏級")
+    private String approvalPlan;
+
+    /**
+     * 鏄惁绔嬮」
+     */
+    @ExcelProperty(value = "鏄惁绔嬮」")
+    private String isSetProject;
+
+    /**
+     * 鎴愮珛鏃堕棿
+     */
+    @ExcelProperty(value = "鎴愮珛鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date setTime;
+
+    /**
+     * 璧嬬爜鐘舵��
+     */
+    @ExcelProperty(value = "璧嬬爜鐘舵��")
+    private String assignmentStatus;
+
+    /**
+     * 琛屾斂鍖哄垝
+     */
+    @ExcelProperty(value = "琛屾斂鍖哄垝")
+    private String area;
+
+    /**
+     * 涓爣鏃堕棿
+     */
+    @ExcelProperty(value = "涓爣鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date winTime;
+
+    /**
+     * 涓爣鍗曚綅
+     */
+    @ExcelProperty(value = "涓爣鍗曚綅")
+    private String winUnit;
+
+    /**
+     * 涓爣閲戦
+     */
+    @ExcelProperty(value = "涓爣閲戦")
+    private String winAmount;
+
+    /**
+     * 璇︾粏鍦板潃
+     */
+    @ExcelProperty(value = "璇︾粏鍦板潃")
+    private String address;
+
+    /**
+     * 寤鸿鍐呭
+     */
+    @ExcelProperty(value = "寤鸿鍐呭")
+    private String content;
+
+    /**
+     * 鑱旂郴鏂瑰紡
+     */
+    @ExcelProperty(value = "鑱旂郴鏂瑰紡")
+    private String contact;
+
+    /**
+     * 椤圭洰涓氫富鍗曚綅
+     */
+    @ExcelProperty(value = "椤圭洰涓氫富鍗曚綅")
+    private String projectOwnerUnit;
+
+    /**
+     * 璁″垝寮�宸ユ椂闂�
+     */
+    @ExcelProperty(value = "璁″垝寮�宸ユ椂闂�")
+    private Date planStartTime;
+
+    /**
+     * 璁″垝绔e伐鏃堕棿
+     */
+    @ExcelProperty(value = "璁″垝绔e伐鏃堕棿")
+    private Date planCompleteTime;
+
+    /**
+     * 椤圭洰鑱旂郴浜�
+     */
+    @ExcelProperty(value = "椤圭洰鑱旂郴浜�")
+    private String projectContactPerson;
+
+    /**
+     * 鏈勾璁″垝鎶曡祫
+     */
+    @ExcelProperty(value = "鏈勾璁″垝鎶曡祫")
+    private BigDecimal yearInvestAmount;
+
+
+                                        /* 鎶曡祫椤圭洰鍩虹淇℃伅琛� */
+
+    /**
+     * 寤鸿鍦扮偣鏄惁璺ㄥ煙
+     */
+    @ExcelProperty(value = "寤鸿鍦扮偣鏄惁璺ㄥ煙")
+    private String beCrossRegion;
+
+    /**
+     * 椤圭洰寤鸿鍦扮偣
+     */
+    @ExcelProperty(value = "寤鸿鍦扮偣")
+    private String constructionLocation;
+
+
+    /**
+     * 寤鸿璇︾粏鍦板潃
+     */
+    @ExcelProperty(value = "寤鸿璇︾粏鍦板潃")
+    private String detailedAddress;
+
+    /**
+     * 鏄惁鏄ˉ鐮侀」鐩�
+     */
+    @ExcelProperty(value = "鏄惁鏄ˉ鐮侀」鐩�")
+    private String beCompensationProject;
+
+    /**
+     * 琛ョ爜鍘熷洜
+     */
+    @ExcelProperty(value = "琛ョ爜鍘熷洜")
+    private String compensationReason;
+
+    /**
+     * 璁″垝寮�宸ユ椂闂�
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty(value = "璁″垝寮�宸ユ椂闂�")
+    private Date plannedStartDate;
+
+    /**
+     * 鎷熷缓鎴愭椂闂�
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty(value = "鎷熷缓鎴愭椂闂�")
+    private Date expectedCompletionDate;
+
+    /**
+     * 鍥芥爣琛屼笟鍒嗙被
+     */
+    @ExcelProperty(value = "鍥芥爣琛屼笟鍒嗙被")
+    private String nationalIndustryClassification;
+
+    /**
+     * 鎵�灞炶涓氬垎绫�
+     */
+    @ExcelProperty(value = "鎵�灞炶涓氬垎绫�")
+    private String industryClassification;
+
+    /**
+     * 椤圭洰寤鸿鎬ц川
+     */
+    @ExcelProperty(value = "椤圭洰寤鸿鎬ц川")
+    private String projectNature;
+
+    /**
+     * 椤圭洰灞炴��
+     */
+    @ExcelProperty(value = "椤圭洰灞炴��")
+    private String projectAttribute;
+
+    /**
+     * 鏄惁浣跨敤鍦熷湴
+     */
+    @ExcelProperty(value = "鏄惁浣跨敤鍦熷湴")
+    private String useEarth;
+
+    /**
+     * 涓昏寤鸿鍐呭鍙婅妯�
+     */
+    @ExcelProperty(value = "涓昏寤鸿鍐呭鍙婅妯�")
+    private String contentScale;
+
+    /**
+     * 寤虹骞冲彴浠g爜
+     */
+    @ExcelProperty(value = "寤虹骞冲彴浠g爜")
+    private String code;
+
+
+                                            /* 椤圭洰鎶曡祫鍙婅祫閲戞潵婧� */
+
+    /**
+     * 椤圭洰鎬绘姇璧勯
+     */
+    @ExcelProperty(value = "椤圭洰鎬绘姇璧勯")
+    private String totalInvestment;
+
+    /**
+     * 椤圭洰鏈噾
+     */
+    @ExcelProperty(value = "椤圭洰鏈噾")
+    private String principal;
+
+    /**
+     * 鏀垮簻鎶曡祫鎬婚
+     */
+    @ExcelProperty(value = "鏀垮簻鎶曡祫鎬婚")
+    private String governmentInvestmentTotal;
+
+    /**
+     * 涓ぎ鎶曡祫鎬婚
+     */
+    @ExcelProperty(value = "涓ぎ鎶曡祫鎬婚")
+    private String centralInvestmentTotal;
+
+    /**
+     * 涓ぎ棰勭畻鎶曡祫
+     */
+    @ExcelProperty(value = "涓ぎ棰勭畻鎶曡祫")
+    private String centralBudgetInvestment;
+
+    /**
+     * 涓ぎ璐㈡斂
+     */
+    @ExcelProperty(value = "涓ぎ璐㈡斂")
+    private String centralFiscalInvestment;
+
+    /**
+     * 涓ぎ涓撻」鍊哄埜绛归泦鐨勪笓椤瑰缓璁捐祫閲�
+     */
+    @ExcelProperty(value = "涓ぎ涓撻」鍊哄埜绛归泦鐨勪笓椤瑰缓璁捐祫閲�")
+    private String centralSpecialBondInvestment;
+
+    /**
+     * 涓ぎ涓撻」寤鸿鍩洪噾
+     */
+    @ExcelProperty(value = "涓ぎ涓撻」寤鸿鍩洪噾")
+    private String centralSpecialFundInvestment;
+
+    /**
+     * 鐪佺骇鎶曡祫鎬婚
+     */
+    @ExcelProperty(value = "鐪佺骇鎶曡祫鎬婚")
+    private String provincialInvestmentTotal;
+
+    /**
+     * 鐪侀绠楀唴鎶曡祫
+     */
+    @ExcelProperty(value = "鐪侀绠楀唴鎶曡祫")
+    private String provincialBudgetInvestment;
+
+    /**
+     * 鐪佽储鏀挎�у缓璁炬姇璧�
+     */
+    @ExcelProperty(value = "鐪佽储鏀挎�у缓璁炬姇璧�")
+    private String provincialFiscalInvestment;
+
+    /**
+     * 鐪佷笓椤瑰缓璁捐祫閲�
+     */
+    @ExcelProperty(value = "鐪佷笓椤瑰缓璁捐祫閲�")
+    private String provincialSpecialFundInvestment;
+
+    /**
+     * 甯傦紙宸烇級鎶曡祫鎬婚
+     */
+    @ExcelProperty(value = "甯傦紙宸烇級鎶曡祫鎬婚")
+    private String cityInvestmentTotal;
+
+    /**
+     * 甯傦紙宸烇級棰勭畻鍐呮姇璧�
+     */
+    @ExcelProperty(value = "甯傦紙宸烇級棰勭畻鍐呮姇璧�")
+    private String cityBudgetInvestment;
+
+    /**
+     * 甯傦紙宸烇級璐㈡斂鎬ф姇璧�
+     */
+    @ExcelProperty(value = "甯傦紙宸烇級璐㈡斂鎬ф姇璧�")
+    private String cityFiscalInvestment;
+
+    /**
+     * 甯傦紙宸烇級涓撻」璧勯噾
+     */
+    @ExcelProperty(value = "甯傦紙宸烇級涓撻」璧勯噾")
+    private String citySpecialFundInvestment;
+
+    /**
+     * 鍘匡紙甯傘�佸尯锛夋姇璧勬�婚
+     */
+    @ExcelProperty(value = "鍘匡紙甯傘�佸尯锛夋姇璧勬�婚")
+    private String countyInvestmentTotal;
+
+    /**
+     * 鍘匡紙甯傘�佸尯锛夐绠楀唴鎶曡祫
+     */
+    @ExcelProperty(value = "鍘匡紙甯傘�佸尯锛夐绠楀唴鎶曡祫")
+    private String countyBudgetInvestment;
+
+    /**
+     * 鍘匡紙甯傘�佸尯锛夎储鏀挎�у缓璁捐祫閲�
+     */
+    @ExcelProperty(value = "鍘匡紙甯傘�佸尯锛夎储鏀挎�у缓璁捐祫閲�")
+    private String countyFiscalInvestment;
+
+    /**
+     * 鍘匡紙甯傘�佸尯锛変笓椤硅祫閲�
+     */
+    @ExcelProperty(value = "鍘匡紙甯傘�佸尯锛変笓椤硅祫閲�")
+    private String countySpecialFundInvestment;
+
+    /**
+     * 鍥藉唴璐锋鎬婚
+     */
+    @ExcelProperty(value = "鍥藉唴璐锋鎬婚")
+    private String domesticLoanTotal;
+
+    /**
+     * 閾惰璐锋
+     */
+    @ExcelProperty(value = "閾惰璐锋")
+    private String bankLoan;
+
+    /**
+     * 澶栧晢鎶曡祫鎬婚
+     */
+    @ExcelProperty(value = "澶栧晢鎶曡祫鎬婚")
+    private String foreignInvestmentTotal;
+
+    /**
+     * 浼佷笟鑷鎬婚
+     */
+    @ExcelProperty(value = "浼佷笟鑷鎬婚")
+    private String enterpriseSelfRaisedTotal;
+
+    /**
+     * 鍏朵粬鎶曡祫鎬婚
+     */
+    @ExcelProperty(value = "鍏朵粬鎶曡祫鎬婚")
+    private String otherInvestmentTotal;
+
+
+                                        /* 椤圭洰锛堟硶浜猴級鍗曚綅鐧昏淇℃伅琛� */
+
+//    /**
+//     * 椤圭洰鎬绘姇璧勯(鏍规嵁鍓嶉潰鐨勬暟鎹~鍏�)
+//     */
+//    private BigDecimal totalInvestment;
+
+    /**
+     * 椤圭洰鍗曚綅
+     */
+    @ExcelProperty(value = "椤圭洰鍗曚綅")
+    private String projectUnit;
+
+    /**
+     * 椤圭洰鍗曚綅绫诲瀷
+     */
+    @ExcelProperty(value = "椤圭洰鍗曚綅绫诲瀷")
+    private String projectUnitType;
+
+    /**
+     * 鐧昏娉ㄥ唽绫诲瀷
+     */
+    @ExcelProperty(value = "鐧昏娉ㄥ唽绫诲瀷")
+    private String registrationType;
+
+    /**
+     * 鎺ц偂鎯呭喌
+     */
+    @ExcelProperty(value = "鎺ц偂鎯呭喌")
+    private String holdingSituation;
+
+    /**
+     * 璇佺収绫诲瀷
+     */
+    @ExcelProperty(value = "璇佺収绫诲瀷")
+    private String certificateType;
+
+    /**
+     * 璇佺収鍙风爜
+     */
+    @ExcelProperty(value = "璇佺収鍙风爜")
+    private String certificateNumber;
+
+    /**
+     * 娉ㄥ唽鍦板潃
+     */
+    @ExcelProperty(value = "娉ㄥ唽鍦板潃")
+    private String registeredAddress;
+
+    /**
+     * 娉ㄥ唽璧勯噾
+     */
+    @ExcelProperty(value = "娉ㄥ唽璧勯噾")
+    private BigDecimal registeredCapital;
+
+    /**
+     * 娉曚汉浠h〃
+     */
+    @ExcelProperty(value = "娉曚汉浠h〃")
+    private String legal_representative;
+
+    /**
+     * 鍥哄畾鐢佃瘽
+     */
+    @ExcelProperty(value = "鍥哄畾鐢佃瘽")
+    private String fixedPhone;
+
+    /**
+     * 娉曚汉韬唤璇�
+     */
+    @ExcelProperty(value = "娉曚汉韬唤璇�")
+    private String legalPersonIdcard;
+
+//    /**
+//     * 椤圭洰鑱旂郴浜猴紙鏍规嵁鍓嶉潰鐨勬暟鎹~鍏咃級
+//     */
+//    private String projectContactPerson;
+
+    /**
+     * 绉诲姩鐢佃瘽
+     */
+    @ExcelProperty(value = "绉诲姩鐢佃瘽")
+    private String phone;
+
+    /**
+     * 鑱旂郴浜鸿韩浠借瘉
+     */
+    @ExcelProperty(value = "鑱旂郴浜鸿韩浠借瘉")
+    private String contactIdcard;
+
+    /**
+     * 寰俊鍙�
+     */
+    @ExcelProperty(value = "寰俊鍙�")
+    private String wechat;
+
+    /**
+     * 鑱旂郴浜洪�氳鍦板潃
+     */
+    @ExcelProperty(value = "鑱旂郴浜洪�氳鍦板潃")
+    private String contactAddress;
+
+    /**
+     * 閭斂缂栫爜
+     */
+    @ExcelProperty(value = "閭斂缂栫爜")
+    private String postCode;
+
+    /**
+     * 鐢靛瓙閭
+     */
+    @ExcelProperty(value = "鐢靛瓙閭")
+    private String email;
+
+                                    /* 鎶曡祫椤圭洰浜т笟鏀跨瓥绗﹀悎鎯呭喌琛� */
+
+
+    /**
+     * 绗﹀悎浜т笟鏀跨瓥闄勪欢
+     */
+    @ExcelProperty(value = "绗﹀悎浜т笟鏀跨瓥(闄勪欢鍚�)")
+    private String policyComplianceAttachment;
+
+    /**
+     * 鏄惁灞炰簬銆婁骇涓氱粨鏋勮皟鏁存寚瀵肩洰褰曘�嬩笅鐨勯」鐩�
+     */
+    @ExcelProperty(value = "灞炰簬銆婁骇涓氱粨鏋勮皟鏁存寚瀵肩洰褰曘�嬩笅鐨勯」鐩�")
+    private String belongsToIndustryAdjustmentDirectory;
+
+    /**
+     * 鏄惁灞炰簬鏈垪鍏ャ�婁骇涓氱粨鏋勮皟鏁存寚瀵肩洰褰曘�嬬殑鍏佽绫婚」鐩�
+     */
+    @ExcelProperty(value = "灞炰簬鏈垪鍏ャ�婁骇涓氱粨鏋勮皟鏁存寚瀵肩洰褰曘�嬬殑鍏佽绫婚」鐩�")
+    private String belongsToAllowedProjects;
+
+    /**
+     * 鏄惁灞炰簬銆婅タ閮ㄥ湴鍖洪紦鍔辩被浜т笟鐩綍銆嬬殑椤圭洰
+     */
+    @ExcelProperty(value = "灞炰簬銆婅タ閮ㄥ湴鍖洪紦鍔辩被浜т笟鐩綍銆嬬殑椤圭洰")
+    private String belongsToWesternEncouragedDirectory;
+
+    /**
+     * 鏄惁涓嶅睘浜庝骇涓氭斂绛栫姝㈡姇璧勫缓璁炬垨瀹炶鏍稿噯銆佸鎵圭鐞嗙殑椤圭洰
+     */
+    @ExcelProperty(value = "涓嶅睘浜庝骇涓氭斂绛栫姝㈡姇璧勫缓璁炬垨瀹炶鏍稿噯銆佸鎵圭鐞嗙殑椤圭洰")
+    private String notBannedOrControlledProject;
+
+    /**
+     * 濉姤淇℃伅鏄惁鐪熷疄
+     */
+    @ExcelProperty(value = "濉姤淇℃伅鏄惁鐪熷疄")
+    private String informationIsTrue;
+
+    /**
+     * 涓撻」瑙勫垝澶嶅悎鎯呭喌
+     */
+    @ExcelProperty(value = "涓撻」瑙勫垝澶嶅悎鎯呭喌")
+    private String specialPlanningCompliance;
+
+    /**
+     * 椤圭洰鑳借�楁儏鍐�
+     */
+    @ExcelProperty(value = "椤圭洰鑳借�楁儏鍐�")
+    private String energyConsumption;
+
+    /**
+     * 椤圭洰骞寸患鍚堣兘婧愭秷璐归噺锛堟爣鍑嗙叅褰撻噺鍊硷級
+     */
+    @ExcelProperty(value = "椤圭洰骞寸患鍚堣兘婧愭秷璐归噺锛堟爣鍑嗙叅褰撻噺鍊硷級")
+    private BigDecimal annualEnergyConsumption;
+
+    /**
+     * 椤圭洰骞寸數鍔涙秷鑰楅噺锛堟爣鍑嗙叅褰撻噺鍊硷級
+     */
+    @ExcelProperty(value = "椤圭洰骞寸數鍔涙秷鑰楅噺锛堟爣鍑嗙叅褰撻噺鍊硷級")
+    private BigDecimal annualElectricityConsumption;
+
+
+    /* 鐩稿叧鏂囦功 */
+    @ExcelProperty(value = "鐩稿叧鏂囦功锛堥檮浠跺悕锛�")
+    private String documents;
+
+
+}
diff --git a/business/src/main/java/com/ycl/domain/query/ProjectInfoQuery.java b/business/src/main/java/com/ycl/domain/query/ProjectInfoQuery.java
index 071663f..d3247c9 100644
--- a/business/src/main/java/com/ycl/domain/query/ProjectInfoQuery.java
+++ b/business/src/main/java/com/ycl/domain/query/ProjectInfoQuery.java
@@ -1,8 +1,12 @@
 package com.ycl.domain.query;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ycl.system.domain.base.AbsQuery;
 import io.swagger.annotations.ApiModel;
 import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
 
 /**
  * 椤圭洰绠$悊鍩虹淇℃伅琛ㄦ煡璇�
@@ -13,5 +17,37 @@
 @Data
 @ApiModel(value = "ProjectInfo鏌ヨ鍙傛暟", description = "椤圭洰绠$悊鍩虹淇℃伅琛ㄦ煡璇㈠弬鏁�")
 public class ProjectInfoQuery extends AbsQuery {
+    //椤圭洰绫诲埆
+    private String projectCategory;
+    //椤圭洰鍚嶇О
+    private String projectName;
+    //椤圭洰浠g爜
+    private String projectCode;
+    //椤圭洰绫诲瀷
+    private String projectType;
+    //閲嶇偣鍒嗙被
+    private String importanceType;
+    //椤圭洰鏍囩
+    private String tag;
+    //椤圭洰鐘舵��
+    private String projectStatus;
+    //椤圭洰鐮�
+    private String projectColorCode;
+    //鍏宠仈鐘舵��
+    private String assignmentStatus;
+    //璧勯噾绫诲瀷
+    private String fundType;
+    //椤圭洰闃舵
+    private String projectPhase;
+    //鎶曡祫绫诲埆
+    private String investType;
+    //琛屾斂鍖哄垝
+    private String area;
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date projectStartTime;
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date projectEndTime;
+    //浠庢湁娌℃湁娴佺▼鍒ゆ柇鏄瓨鍌ㄨ繕鏄棭鏈�
+    private String reserveOrPrevious;
 }
 
diff --git a/business/src/main/java/com/ycl/domain/vo/ProjectInfoVO.java b/business/src/main/java/com/ycl/domain/vo/ProjectInfoVO.java
index ee49ea6..1f73b75 100644
--- a/business/src/main/java/com/ycl/domain/vo/ProjectInfoVO.java
+++ b/business/src/main/java/com/ycl/domain/vo/ProjectInfoVO.java
@@ -67,7 +67,7 @@
     /** 涓荤閮ㄩ棬(瀵瑰簲瀹℃壒閮ㄩ棬id) */
     @ApiModelProperty("涓荤閮ㄩ棬(瀵瑰簲瀹℃壒閮ㄩ棬id)")
     private List<Long> competentDepartmentList;
-
+    private String competentDepartment;
     /** 琛屾斂鍖哄煙 */
     @ApiModelProperty("琛屾斂鍖哄煙")
     private String area;
@@ -75,7 +75,7 @@
     /** 绠$悊褰掑彛  (0鍩烘湰寤鸿(鍙戞敼),  1鏇存柊鏀归��(缁忎俊),  2鍗曠函璐疆(鍙戞敼),  3淇℃伅鍖�(鍙戞敼),  4鍏朵粬鎶曡祫) */
     @ApiModelProperty("绠$悊褰掑彛  (0鍩烘湰寤鸿(鍙戞敼),  1鏇存柊鏀归��(缁忎俊),  2鍗曠函璐疆(鍙戞敼),  3淇℃伅鍖�(鍙戞敼),  4鍏朵粬鎶曡祫)")
     private List<String> managementCentralizationList;
-
+    private String managementCentralization;
     /** 椤圭洰瀹℃壒绫诲瀷 */
     @ApiModelProperty("椤圭洰瀹℃壒绫诲瀷")
     private String projectApprovalType;
@@ -159,6 +159,11 @@
 
     @ApiModelProperty("鏂囦欢")
     private List<File> fileList;
+    private Long processId;
+    private ProjectInvestmentInfoVO projectInvestmentInfo;
+    private ProjectInvestmentFundingVO projectInvestmentFunding;
+    private ProjectUnitRegistrationInfoVO projectUnitRegistrationInfo;
+    private ProjectInvestmentPolicyComplianceVO projectInvestmentPolicyCompliance;
 
     public static ProjectInfoVO getVoByEntity(@NonNull ProjectInfo entity, ProjectInfoVO vo) {
         if(vo == null) {
@@ -180,5 +185,20 @@
         }
         return vo;
     }
-
+    //杞崲瀛楃涓查泦鍚堝瓧娈�
+    public static void transform(@NonNull ProjectInfoVO vo) {
+        //涓荤閮ㄩ棬杞垚list
+        String competentDepartment = vo.getCompetentDepartment();
+        if(!StringUtils.isBlank(competentDepartment)){
+            List<Long> list = Arrays.stream(competentDepartment.split(","))
+                    .map(Long::parseLong)
+                    .collect(Collectors.toList());
+            vo.setCompetentDepartmentList(list);
+        }
+        //绠$悊褰掑彛杞崲
+        String managementCentralization = vo.getManagementCentralization();
+        if(!StringUtils.isBlank(managementCentralization)){
+            vo.setManagementCentralizationList(Arrays.asList(managementCentralization.split(",")));
+        }
+    }
 }
diff --git a/business/src/main/java/com/ycl/domain/vo/ProjectVO.java b/business/src/main/java/com/ycl/domain/vo/ProjectVO.java
new file mode 100644
index 0000000..119abd9
--- /dev/null
+++ b/business/src/main/java/com/ycl/domain/vo/ProjectVO.java
@@ -0,0 +1,17 @@
+package com.ycl.domain.vo;
+
+import com.ycl.domain.excel.ProjectExcelTemplate;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ProjectVO extends ProjectExcelTemplate {
+    private Long id;
+    /** 鐘舵�佺爜 */
+    private String projectColorCode;
+    private List<Long> competentDepartmentList;
+    private List<String> managementCentralizationList;
+
+    private Long processId;
+}
diff --git a/business/src/main/java/com/ycl/service/impl/ProjectInfoServiceImpl.java b/business/src/main/java/com/ycl/service/impl/ProjectInfoServiceImpl.java
index 05a25e8..3adc8f2 100644
--- a/business/src/main/java/com/ycl/service/impl/ProjectInfoServiceImpl.java
+++ b/business/src/main/java/com/ycl/service/impl/ProjectInfoServiceImpl.java
@@ -1,10 +1,14 @@
 package com.ycl.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ycl.common.base.Result;
 import com.ycl.common.enums.business.FileTypeEnum;
+import com.ycl.common.enums.business.ProjectCategoryEnum;
+import com.ycl.common.utils.CopyUtils;
+import com.ycl.common.utils.DateUtils;
 import com.ycl.common.utils.SecurityUtils;
 import com.ycl.domain.entity.File;
 import com.ycl.domain.entity.ProjectInfo;
@@ -39,6 +43,7 @@
     private final ProjectInfoMapper projectInfoMapper;
     private final FileService fileService;
     private final FileMapper fileMapper;
+
     /**
      * 娣诲姞
      *
@@ -56,7 +61,7 @@
         baseMapper.insert(entity);
         //娣诲姞鏂囦欢
         List<File> fileList = form.getFileList();
-        fileList.forEach(item->{
+        fileList.forEach(item -> {
             item.setBusId(entity.getId());
             item.setType(FileTypeEnum.PROJECT_INFO);
         });
@@ -76,20 +81,21 @@
         ProjectInfo entity = baseMapper.selectById(form.getId());
         // 涓虹┖鎶汭llegalArgumentException锛屽仛鍏ㄥ眬寮傚父澶勭悊
         Assert.notNull(entity, "璁板綍涓嶅瓨鍦�");
-        ProjectInfoForm.getEntityByForm(form,entity);
+        ProjectInfoForm.getEntityByForm(form, entity);
         Long userId = SecurityUtils.getUserId();
         entity.setUpdateBy(userId);
         //鏇存柊椤圭洰淇℃伅
         baseMapper.updateById(entity);
         List<File> fileList = form.getFileList();
-        fileList.forEach(item->{
+        fileList.forEach(item -> {
+            item.setId(null);
             item.setBusId(entity.getId());
             item.setType(FileTypeEnum.PROJECT_INFO);
         });
         //鍒犻櫎鍘熸湁鏂囦欢
         QueryWrapper<File> fileQueryWrapper = new QueryWrapper<>();
-        fileQueryWrapper.eq("type",FileTypeEnum.PROJECT_INFO.getType());
-        fileQueryWrapper.eq("bus_id",entity.getId());
+        fileQueryWrapper.eq("type", FileTypeEnum.PROJECT_INFO.getType());
+        fileQueryWrapper.eq("bus_id", entity.getId());
         fileMapper.delete(fileQueryWrapper);
         //鏇挎崲鎴愮幇鏈�
         fileService.saveBatch(fileList);
@@ -130,17 +136,54 @@
      */
     @Override
     public Result page(ProjectInfoQuery query) {
-        IPage<ProjectInfo> page = PageUtil.getPage(query, ProjectInfo.class);
+        if (query.getProjectStartTime() != null) {
+            query.setProjectStartTime(DateUtils.getDayStart(query.getProjectStartTime()));
+        }
+        if (query.getProjectEndTime() != null) {
+            query.setProjectEndTime(DateUtils.getDayEnd(query.getProjectEndTime()));
+        }
+        String projectCategory = query.getProjectCategory();
+        if (ProjectCategoryEnum.RESERVE.getType().equals(projectCategory)) {
+            query.setProjectStatus(ProjectCategoryEnum.RESERVE.getStatus());
+            query.setReserveOrPrevious(ProjectCategoryEnum.RESERVE.getCode());
+        } else if (ProjectCategoryEnum.PREVIOUS.getType().equals(projectCategory)) {
+            query.setProjectStatus(ProjectCategoryEnum.PREVIOUS.getStatus());
+            query.setReserveOrPrevious(ProjectCategoryEnum.PREVIOUS.getCode());
+        } else if (ProjectCategoryEnum.FINISH.getType().equals(projectCategory)) {
+            query.setProjectStatus(ProjectCategoryEnum.FINISH.getStatus());
+        } else if (ProjectCategoryEnum.EXCEPTION.getType().equals(projectCategory)) {
+            //TODO
+            //鍏堟煡鍑哄紓甯告祦绋嬫垨鑰呭紓甯歌繘搴︽垨鑰呭紓甯歌鍒掔殑projectId鍜屽紓甯哥绫�
+            //閫氳繃projectId鏌ュ嚭椤圭洰鏁版嵁
+            //琛ュ厖鐩稿簲鐨勫紓甯告暟鎹紙寮傚父绉嶇被銆佸紓甯告祦绋嬭妭鐐圭瓑锛�
+            List<ProjectVO> list = new ArrayList<>();
+            return Result.ok().data(list).total(0);
+        }
+
+        IPage<ProjectInfoVO> page = PageUtil.getPage(query, ProjectInfoVO.class);
         baseMapper.getPage(page, query);
-        List<ProjectInfo> records = page.getRecords();
-        List<ProjectInfoVO> list = records.stream()
-                .map(entity -> {
-                    ProjectInfoVO vo = ProjectInfoVO.getVoByEntity(entity, null);
+        List<ProjectInfoVO> records = page.getRecords();
+        List<ProjectVO> list = new ArrayList<>();
+        records.forEach(vo -> {
+                    ProjectInfoVO.transform(vo);
                     vo.setProjectColorCode("green");
-                    return vo;
-                })
-                .collect(Collectors.toList());
+            ProjectVO projectVO = new ProjectVO();
+            copyToProjectVO(vo,projectVO);
+            //缈昏瘧椤圭洰闃舵
+            String phase = ProjectCategoryEnum.getPhaseByProjectStatus(projectVO.getProjectStatus(), projectVO.getProcessId() != null);
+            projectVO.setProjectPhase(phase);
+            list.add(projectVO);
+        });
         return Result.ok().data(list).total(page.getTotal());
+    }
+
+    private void copyToProjectVO(ProjectInfoVO vo,ProjectVO projectVO) {
+        //蹇界暐null鍊肩殑澶嶅埗
+        CopyUtils.copyNoNullProperties(vo, projectVO);
+        if(vo.getProjectInvestmentFunding()!=null) CopyUtils.copyNoNullProperties(vo.getProjectInvestmentFunding(),projectVO);
+        if(vo.getProjectInvestmentInfo()!=null)  CopyUtils.copyNoNullProperties(vo.getProjectInvestmentInfo(),projectVO);
+        if(vo.getProjectUnitRegistrationInfo()!=null)  CopyUtils.copyNoNullProperties(vo.getProjectUnitRegistrationInfo(),projectVO);
+        if(vo.getProjectInvestmentPolicyCompliance()!=null)  CopyUtils.copyNoNullProperties(vo.getProjectInvestmentPolicyCompliance(),projectVO);
     }
 
     /**
@@ -155,8 +198,8 @@
         Assert.notNull(entity, "璁板綍涓嶅瓨鍦�");
         ProjectInfoVO vo = ProjectInfoVO.getVoByEntity(entity, null);
         QueryWrapper<File> fileQueryWrapper = new QueryWrapper<>();
-        fileQueryWrapper.eq("type",FileTypeEnum.PROJECT_INFO.getType());
-        fileQueryWrapper.eq("bus_id",vo.getId());
+        fileQueryWrapper.eq("type", FileTypeEnum.PROJECT_INFO.getType());
+        fileQueryWrapper.eq("bus_id", vo.getId());
         List<File> files = fileMapper.selectList(fileQueryWrapper);
         vo.setFileList(files);
         return Result.ok().data(vo);
@@ -213,8 +256,8 @@
     public Result docDetail(Integer id) {
         DocumentInfoForm documentInfoForm = new DocumentInfoForm();
         QueryWrapper<File> fileQueryWrapper = new QueryWrapper<>();
-        fileQueryWrapper.eq("type",FileTypeEnum.DOCUMENT_INFO.getType());
-        fileQueryWrapper.eq("bus_id",id);
+        fileQueryWrapper.eq("type", FileTypeEnum.DOCUMENT_INFO.getType());
+        fileQueryWrapper.eq("bus_id", id);
         List<File> files = fileMapper.selectList(fileQueryWrapper);
         documentInfoForm.setFileList(files);
         return Result.ok().data(documentInfoForm);
@@ -223,14 +266,15 @@
     @Override
     public Result addDoc(DocumentInfoForm form) {
         List<File> fileList = form.getFileList();
-        fileList.forEach(item->{
+        fileList.forEach(item -> {
+            item.setId(null);
             item.setBusId(form.getProjectId());
             item.setType(FileTypeEnum.DOCUMENT_INFO);
         });
         //鍒犻櫎鍘熸湁鏂囦欢
         QueryWrapper<File> fileQueryWrapper = new QueryWrapper<>();
-        fileQueryWrapper.eq("type",FileTypeEnum.DOCUMENT_INFO.getType());
-        fileQueryWrapper.eq("bus_id",form.getProjectId());
+        fileQueryWrapper.eq("type", FileTypeEnum.DOCUMENT_INFO.getType());
+        fileQueryWrapper.eq("bus_id", form.getProjectId());
         fileMapper.delete(fileQueryWrapper);
         //鏇挎崲鎴愮幇鏈�
         fileService.saveBatch(fileList);
diff --git a/business/src/main/java/com/ycl/service/impl/ProjectInvestmentPolicyComplianceServiceImpl.java b/business/src/main/java/com/ycl/service/impl/ProjectInvestmentPolicyComplianceServiceImpl.java
index ddf8c91..0d7ef45 100644
--- a/business/src/main/java/com/ycl/service/impl/ProjectInvestmentPolicyComplianceServiceImpl.java
+++ b/business/src/main/java/com/ycl/service/impl/ProjectInvestmentPolicyComplianceServiceImpl.java
@@ -56,6 +56,7 @@
         //娣诲姞鏂囦欢
         List<File> fileList = form.getFileList();
         fileList.forEach(item->{
+            item.setId(null);
             item.setBusId(entity.getId());
             item.setType(FileTypeEnum.INVEST_POLICY);
         });
@@ -80,6 +81,7 @@
         baseMapper.updateById(entity);
         List<File> fileList = form.getFileList();
         fileList.forEach(item->{
+            item.setId(null);
             item.setBusId(entity.getId());
             item.setType(FileTypeEnum.INVEST_POLICY);
         });
diff --git a/business/src/main/resources/mapper/ProjectInfoMapper.xml b/business/src/main/resources/mapper/ProjectInfoMapper.xml
index 4beeb90..0da2550 100644
--- a/business/src/main/resources/mapper/ProjectInfoMapper.xml
+++ b/business/src/main/resources/mapper/ProjectInfoMapper.xml
@@ -3,44 +3,15 @@
 <mapper namespace="com.ycl.mapper.ProjectInfoMapper">
 
     <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
-    <resultMap id="BaseResultMap" type="com.ycl.domain.entity.ProjectInfo">
-        <id column="id" property="id"/>
-        <result column="project_name" property="projectName" />
-        <result column="project_code" property="projectCode" />
-        <result column="content" property="content" />
-        <result column="project_type" property="projectType" />
-        <result column="project_status" property="projectStatus" />
-        <result column="fund_type" property="fundType" />
-        <result column="invest_type" property="investType" />
-        <result column="project_phase" property="projectPhase" />
-        <result column="tag" property="tag" />
-        <result column="competent_department" property="competentDepartment" />
-        <result column="area" property="area" />
-        <result column="management_centralization" property="managementCentralization" />
-        <result column="project_approval_type" property="projectApprovalType" />
-        <result column="importance_type" property="importanceType" />
-        <result column="year" property="year" />
-        <result column="year_invest_amount" property="yearInvestAmount" />
-        <result column="create_project_time" property="createProjectTime" />
-        <result column="plan_start_time" property="planStartTime" />
-        <result column="plan_complete_time" property="planCompleteTime" />
-        <result column="win_unit" property="winUnit" />
-        <result column="win_amount" property="winAmount" />
-        <result column="win_time" property="winTime" />
-        <result column="project_address" property="projectAddress" />
-        <result column="longitude" property="longitude" />
-        <result column="latitude" property="latitude" />
-        <result column="project_owner_unit" property="projectOwnerUnit" />
-        <result column="project_contact_person" property="projectContactPerson" />
-        <result column="contact" property="contact" />
-        <result column="gmt_create" property="gmtCreate" />
-        <result column="gmt_update" property="gmtUpdate" />
-        <result column="update_by" property="updateBy" />
-        <result column="create_by" property="createBy" />
+    <resultMap id="resultMap" type="com.ycl.domain.vo.ProjectInfoVO" autoMapping="true">
+        <association property="projectInvestmentInfo" javaType="com.ycl.domain.vo.ProjectInvestmentInfoVO" autoMapping="true" columnPrefix="TPII_"/>
+        <association property="projectInvestmentFunding" javaType="com.ycl.domain.vo.ProjectInvestmentFundingVO" autoMapping="true" columnPrefix="TPIF_"/>
+        <association property="projectUnitRegistrationInfo" javaType="com.ycl.domain.vo.ProjectUnitRegistrationInfoVO" autoMapping="true" columnPrefix="TPURI_"/>
+        <association property="projectInvestmentPolicyCompliance" javaType="com.ycl.domain.vo.ProjectInvestmentPolicyComplianceVO" autoMapping="true" columnPrefix="TPIPC_"/>
     </resultMap>
 
 
-    <select id="getById" resultMap="BaseResultMap">
+    <select id="getById" resultType="com.ycl.domain.entity.ProjectInfo">
         SELECT
             TPI.project_name,
             TPI.project_code,
@@ -82,45 +53,104 @@
     </select>
 
 
-    <select id="getPage" resultMap="BaseResultMap">
+    <select id="getPage" resultMap="resultMap">
         SELECT
-            TPI.project_name,
-            TPI.project_code,
-            TPI.content,
-            TPI.project_type,
-            TPI.project_status,
-            TPI.fund_type,
-            TPI.invest_type,
-            TPI.project_phase,
-            TPI.tag,
-            TPI.competent_department,
-            TPI.area,
-            TPI.management_centralization,
-            TPI.project_approval_type,
-            TPI.importance_type,
-            TPI.year,
-            TPI.year_invest_amount,
-            TPI.create_project_time,
-            TPI.plan_start_time,
-            TPI.plan_complete_time,
-            TPI.win_unit,
-            TPI.win_amount,
-            TPI.win_time,
-            TPI.project_address,
-            TPI.longitude,
-            TPI.latitude,
-            TPI.project_owner_unit,
-            TPI.project_contact_person,
-            TPI.contact,
-            TPI.gmt_create,
-            TPI.gmt_update,
-            TPI.update_by,
-            TPI.create_by,
-            TPI.id
+            TPI.*,TPP.process_instance_id as processId,
+            TPIF.total_investment  as TPIF_totalInvestment,
+            TPIF.principal as TPIF_principal,TPIF.government_investment_total as TPIF_government_investment_total,TPIF.central_investment_total as TPIF_central_investment_total,
+            TPIF.central_budget_investment as TPIF_central_budget_investment,TPIF.central_fiscal_investment as TPIF_central_fiscal_investment,
+            TPIF.central_special_bond_investment as TPIF_central_special_bond_investment,TPIF.central_special_fund_investment as TPIF_central_special_fund_investment,
+            TPIF.provincial_investment_total as TPIF_provincial_investment_total,TPIF.provincial_budget_investment as TPIF_provincial_budget_investment,
+            TPIF.provincial_fiscal_investment as TPIF_provincial_fiscal_investment,TPIF.provincial_special_fund_investment as TPIF_provincial_special_fund_investment,
+            TPIF.city_investment_total as TPIF_city_investment_total,TPIF.city_budget_investment as TPIF_city_budget_investment,TPIF.city_fiscal_investment as TPIF_city_fiscal_investment,
+            TPIF.city_special_fund_investment as TPIF_city_special_fund_investment,TPIF.county_investment_total as TPIF_county_investment_total,TPIF.county_budget_investment as TPIF_county_budget_investment,
+            TPIF.county_fiscal_investment as TPIF_county_fiscal_investment,TPIF.county_special_fund_investment as TPIF_county_special_fund_investment,
+            TPIF.domestic_loan_total as TPIF_domestic_loan_total,TPIF.bank_loan as TPIF_bank_loan,TPIF.foreign_investment_total as TPIF_foreign_investment_total,
+            TPIF.enterprise_self_raised_total as TPIF_enterprise_self_raised_total,TPIF.other_investment_total as TPIF_other_investment_total,
+            TPII.be_cross_region as TPII_be_cross_region,TPII.construction_location as TPII_construction_location,
+            TPII.detailed_address as TPII_detailed_address,TPII.be_compensation_project as TPII_be_compensation_project,TPII.compensation_reason as TPII_compensation_reason,
+            TPII.planned_start_date as TPII_planned_start_date,TPII.expected_completion_date as TPII_expected_completion_date,
+            TPII.national_industry_classification as TPII_national_industry_classification,TPII.industry_classification as TPII_industry_classification,TPII.project_nature as TPII_project_nature,
+            TPII.project_attribute as TPII_project_attribute,TPII.use_earth as TPII_use_earth,TPII.content_scale as TPII_content_scale,TPII.code as TPII_code,
+            TPIPC.belongs_to_industry_adjustment_directory as TPIPC_belongs_to_industry_adjustment_directory,TPIPC.belongs_to_western_encouraged_directory as TPIPC_belongs_to_western_encouraged_directory,
+            TPIPC.not_banned_or_controlled_project as TPIPC_not_banned_or_controlled_project,TPIPC.information_is_true as TPIPC_information_is_true,
+            TPIPC.special_planning_compliance as TPIPC_special_planning_compliance,TPIPC.annual_energy_consumption as TPIPC_annual_energy_consumption,TPIPC.annual_electricity_consumption as TPIPC_annual_electricity_consumption,
+            TPIPC.energy_check as TPIPC_energy_check,TPIPC.no_only_check_type as TPIPC_no_only_check_type,TPIPC.remarks as TPIPC_remarks,
+            TPURI.total_investment as TPURI_total_investment,
+            TPURI.project_unit as TPURI_project_unit,
+            TPURI.project_unit_type as TPURI_project_unit_type,
+            TPURI.registration_type as TPURI_registration_type,
+            TPURI.holding_situation as TPURI_holding_situation,
+            TPURI.certificate_type  as TPURI_certificate_type,
+            TPURI.certificate_number as TPURI_certificate_number,
+            TPURI.registered_address as TPURI_registered_address,
+            TPURI.registered_capital as TPURI_registered_capital,
+            TPURI.legal_representative as TPURI_legal_representative,
+            TPURI.fixed_phone as TPURI_fixed_phone,
+            TPURI.legal_person_idcard as TPURI_legal_person_idcard,
+            TPURI.project_contact_person as TPURI_project_contact_person,
+            TPURI.phone as TPURI_phone,
+            TPURI.contact_idcard as TPURI_contact_idcard,
+            TPURI.wechat as TPURI_wechat,
+            TPURI.contact_address as TPURI_contact_address,
+            TPURI.post_code as TPURI_post_code,
+            TPURI.email as TPURI_email
         FROM
             t_project_info TPI
-        WHERE
+        LEFT JOIN t_project_investment_funding TPIF ON TPI.id = TPIF.project_id and TPIF.deleted = 0
+        LEFT JOIN t_project_investment_info TPII ON TPI.id = TPII.project_id and TPII.deleted = 0
+        LEFT JOIN t_project_investment_policy_compliance TPIPC ON TPI.id = TPIPC.project_id and TPIPC.deleted = 0
+        LEFT JOIN t_project_unit_registration_info TPURI ON TPI.id = TPURI.project_id and TPURI.deleted = 0
+        LEFT JOIN t_project_process TPP ON TPI.id = TPP.project_id and TPP.deleted = 0
+        <where>
             TPI.deleted = 0
+            <if test="query.projectName !=null and query.projectName!=''">
+                and TPI.project_name like concat('%',#{query.projectName},'%')
+            </if>
+            <if test="query.projectCode !=null and query.projectCode!=''">
+                and TPI.project_code like concat('%',#{query.projectCode},'%')
+            </if>
+            <if test="query.projectType !=null and query.projectType!=''">
+                and TPI.project_type = #{query.projectType}
+            </if>
+            <if test="query.importanceType !=null and query.importanceType!=''">
+                and TPI.importance_type = #{query.importanceType}
+            </if>
+            <if test="query.tag !=null and query.tag!=''">
+                and TPI.tag like concat('%',#{query.tag},'%')
+            </if>
+            <if test="query.projectStatus !=null and query.projectStatus!=''">
+                and TPI.project_status = #{query.projectStatus}
+            </if>
+            <if test="query.projectPhase !=null and query.projectPhase!=''">
+                and TPI.project_phase = #{query.projectPhase}
+            </if>
+<!--            <if test=" assignmentStatus !=null and assignmentStatus!=''">-->
+<!--                and TPI.project_phase = #{projectPhase}-->
+<!--            </if>-->
+            <if test="query.fundType !=null and query.fundType!=''">
+                and TPI.fund_type = #{query.fundType}
+            </if>
+            <if test="query.projectPhase !=null and query.projectPhase!=''">
+                and TPI.project_phase = #{query.projectPhase}
+            </if>
+            <if test="query.investType !=null and query.investType!=''">
+                and TPI.invest_type = #{query.investType}
+            </if>
+            <if test="query.area !=null and query.area!=''">
+                and TPI.area = #{query.area}
+            </if>
+            <if test="query.projectStartTime !=null and query.projectEndTime !=null">
+                and TPI.create_project_time between #{query.projectStartTime} and #{query.projectEndTime}
+            </if>
+            <if test="query.reserveOrPrevious != null and query.reserveOrPrevious == 'reserve'">
+                and TPP.process_instance_id is null
+            </if>
+            <if test="query.reserveOrPrevious != null and query.reserveOrPrevious == 'previous'">
+                and TPP.process_instance_id is not null
+            </if>
+        </where>
+        order by TPI.gmt_create
     </select>
 
 </mapper>
diff --git a/common/pom.xml b/common/pom.xml
index 99d8e16..7ee7272 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -128,7 +128,16 @@
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
+        <!-- easy excel -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+        </dependency>
 
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-crypto</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/common/src/main/java/com/ycl/common/enums/business/ProjectCategoryEnum.java b/common/src/main/java/com/ycl/common/enums/business/ProjectCategoryEnum.java
new file mode 100644
index 0000000..f152d46
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/enums/business/ProjectCategoryEnum.java
@@ -0,0 +1,51 @@
+package com.ycl.common.enums.business;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum ProjectCategoryEnum {
+
+    RESERVE("1", "pendding", "鍌ㄥ椤圭洰","reserve","鍌ㄥ瑙勫垝闃舵"),
+    PREVIOUS("2", "pendding","鍓嶆湡椤圭洰","previous","椤圭洰鍓嶆湡闃舵"),
+    IMPLEMENT("3", "working,stop","瀹炴柦椤圭洰","implement","瀹炴柦闃舵"),
+    FINISH("4", "finish","绔e伐椤圭洰","finish","绔e伐鎶曠敤闃舵"),
+    EXCEPTION("5", "","寮傚父椤圭洰","exception","");
+
+
+    private final String type;
+    private final String status;
+    private final String name;
+    private final String code;
+    private  final String desc;
+
+    //鍒ゆ柇浼犲叆鏁版嵁鏄惁瀛樺湪鍥涚椤圭洰鎺ㄨ繘鐘舵��
+    public static boolean isValidType(String type) {
+        for (ProjectCategoryEnum status : ProjectCategoryEnum.values()) {
+            if (status.getDesc().equals(type) && ObjectUtil.notEqual(type,ProjectCategoryEnum.EXCEPTION.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 鑾峰彇椤圭洰闃舵
+     * @param projectStatus
+     * @param hasProcess 鏄惁鍚姩娴佺▼
+     * @return
+     */
+    public static String getPhaseByProjectStatus(String projectStatus, boolean hasProcess) {
+        for (ProjectCategoryEnum projectCategoryEnum : ProjectCategoryEnum.values()) {
+            if (hasProcess && PREVIOUS.status.contains(projectStatus)) {
+                return PREVIOUS.desc;
+            }
+            if (projectCategoryEnum.status.contains(projectStatus)) {
+                return projectCategoryEnum.desc;
+            }
+        }
+        return null;
+    }
+}
diff --git a/common/src/main/java/com/ycl/common/utils/CopyUtils.java b/common/src/main/java/com/ycl/common/utils/CopyUtils.java
new file mode 100644
index 0000000..5faafab
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/CopyUtils.java
@@ -0,0 +1,48 @@
+package com.ycl.common.utils;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
+
+import java.beans.PropertyDescriptor;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 鑷畾涔夊鍒跺伐鍏风被
+ */
+public class CopyUtils {
+        /**
+         * 鎵�鏈変负绌哄�肩殑灞炴�ч兘涓峜opy
+         *
+         * @param source
+         * @param target
+         */
+        public static void copyNoNullProperties(Object source, Object target) {
+            BeanUtils.copyProperties(source, target, getNullField(source));
+        }
+
+        /**
+         * 鑾峰彇灞炴�т腑涓虹┖鐨勫瓧娈�
+         *
+         * @param target
+         * @return
+         */
+        private static String[] getNullField(Object target) {
+            BeanWrapper beanWrapper = new BeanWrapperImpl(target);
+            PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
+            Set<String> notNullFieldSet = new HashSet<>();
+            if (propertyDescriptors.length > 0) {
+                for (PropertyDescriptor p : propertyDescriptors) {
+                    String name = p.getName();
+                    Object value = beanWrapper.getPropertyValue(name);
+                    if (Objects.isNull(value)) {
+                        notNullFieldSet.add(name);
+                    }
+                }
+            }
+            String[] notNullField = new String[notNullFieldSet.size()];
+            return notNullFieldSet.toArray(notNullField);
+        }
+}
diff --git a/common/src/main/java/com/ycl/common/utils/DateUtils.java b/common/src/main/java/com/ycl/common/utils/DateUtils.java
index b600bc6..43eaf68 100644
--- a/common/src/main/java/com/ycl/common/utils/DateUtils.java
+++ b/common/src/main/java/com/ycl/common/utils/DateUtils.java
@@ -1,6 +1,7 @@
 package com.ycl.common.utils;
 
 import java.lang.management.ManagementFactory;
+import java.sql.Timestamp;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
@@ -9,7 +10,10 @@
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.Date;
+import java.util.Objects;
+
 import org.apache.commons.lang3.time.DateFormatUtils;
+import org.springframework.lang.Nullable;
 
 /**
  * 鏃堕棿宸ュ叿绫�
@@ -188,4 +192,35 @@
         ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
         return Date.from(zdt.toInstant());
     }
+
+    /**
+     * 鑾峰彇鏌愬ぉ鐨勫紑濮嬫椂闂�
+     *
+     * @param date
+     * @return 2023-01-01 00:00:00
+     */
+
+    public static Date getDayStart(@Nullable Date date) {
+        if (Objects.isNull(date)) {
+            date = new Date();
+        }
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("GMT+8"));
+        LocalDateTime of = LocalDateTime.of(localDateTime.getYear(), localDateTime.getMonth(), localDateTime.getDayOfMonth(), 0, 0, 0);
+        return Timestamp.valueOf(of);
+    }
+
+    /**
+     * 鑾峰彇鏌愬ぉ鐨勭粨鏉熸椂闂�
+     *
+     * @param date
+     * @return 2023-01-01 23:59:59
+     */
+    public static Date getDayEnd(@Nullable Date date) {
+        if (Objects.isNull(date)) {
+            date = new Date();
+        }
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+        LocalDateTime of = LocalDateTime.of(localDateTime.getYear(), localDateTime.getMonth(), localDateTime.getDayOfMonth(), 23, 59, 59);
+        return Timestamp.valueOf(of);
+    }
 }
diff --git a/common/src/main/java/com/ycl/common/utils/excel/OutputExcelUtils.java b/common/src/main/java/com/ycl/common/utils/excel/OutputExcelUtils.java
new file mode 100644
index 0000000..68d0bac
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/OutputExcelUtils.java
@@ -0,0 +1,217 @@
+package com.ycl.common.utils.excel;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ZipUtil;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
+import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import com.ycl.common.utils.excel.convert.ExcelBigNumberConvert;
+import com.ycl.common.utils.excel.core.CellMergeStrategy;
+import com.ycl.common.utils.excel.core.DropDownOptions;
+import com.ycl.common.utils.excel.core.ExcelDownHandler;
+import com.ycl.common.utils.file.FileUtils;
+import org.apache.commons.codec.Charsets;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotNull;
+import java.io.*;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @Author: ljx
+ * @CreateTime: 2024-10-18 10:13
+ */
+
+public class OutputExcelUtils {
+
+    /**
+     * 蹇界暐閮ㄥ垎瀵煎嚭瀛楁鏂规硶
+     * @param response
+     * @param fileName 鏂囦欢鍚嶇О
+     * @param sheetName sheet鍚嶇О
+     * @param dataList 闇�瑕佸鍑虹殑鏁版嵁
+     * @param clazz 绫�
+     * @param
+     * @param <T>
+     * @throws IOException
+     */
+    public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> dataList, Class<T> clazz, List<String> fieldNames) throws IOException {
+        response.setContentType("application/zip");
+        response.setCharacterEncoding(Charsets.UTF_8.name());
+        fileName = URLEncoder.encode(fileName, Charsets.UTF_8);
+        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".zip");
+        Set<Integer> selectedIndexes = getSelectFields(fieldNames, clazz);
+
+        //涓嬭浇妯℃澘
+        downloadTemplate(response, sheetName, dataList, clazz, selectedIndexes);
+
+
+        // 涓存椂鍚戣祫婧愭枃浠跺す鍐� 鍚嶄负template鐨勬枃浠跺す
+//        ClassPathResource classPathResource = new ClassPathResource("/template/test.xls");
+//        File file = classPathResource.getFile();
+//        try(FileOutputStream fileOutputStream = new FileOutputStream(file,false)) {
+//            EasyExcel.write(fileOutputStream, clazz).sheet(sheetName).doWrite(dataList);
+//        }
+//
+//        ClassPathResource classPathResource1 = new ClassPathResource("/template");
+//        File zip = ZipUtil.zip(classPathResource1.getFile());
+//        byte[] bytes;
+//        try (FileInputStream fileInputStream = new FileInputStream(zip)) {
+//            bytes = fileInputStream.readAllBytes();
+//        }
+//        response.getOutputStream().write(bytes);
+
+//        if (selectedIndexes.size() > 0) {
+//            EasyExcel.write(response.getOutputStream(), clazz).excludeColumnIndexes(selectedIndexes).sheet(sheetName).doWrite(dataList);
+//        } else {
+//            EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(dataList);
+//        }
+    }
+
+    private synchronized static <T> void downloadTemplate(HttpServletResponse response, String sheetName, List<T> dataList, Class<T> clazz, Set<Integer> columnIndex) throws IOException {
+
+        File tempDir = null;
+        try {
+            // 鍒涘缓涓存椂鐩綍
+            tempDir = Files.createTempDirectory("temp").toFile();
+
+            File templateDir = new File(tempDir, "template");
+            if (!templateDir.exists()) {
+                templateDir.mkdirs();
+            }
+
+            // 鍒涘缓 Excel 鏂囦欢
+            File excelFile = new File(templateDir, "excel.xlsx");
+            if (!excelFile.exists()) {
+                excelFile.createNewFile();
+            }
+
+            // 鍐欏叆 Excel 妯℃澘鏁版嵁
+            try (FileOutputStream fileOutputStream = new FileOutputStream(excelFile, false)) {
+                EasyExcel.write(fileOutputStream, clazz).includeColumnIndexes(columnIndex).sheet(sheetName).doWrite(dataList);
+            }
+
+            // 鍒涘缓闄勪欢鐩綍
+            File attachmentDir = new File(templateDir, "attachment");
+            if (!attachmentDir.exists()) {
+                attachmentDir.mkdirs();
+            }
+
+            // 鎵撳寘 ZIP 鏂囦欢
+            File zipFile = ZipUtil.zip(templateDir);
+            byte[] zipBytes = Files.readAllBytes(zipFile.toPath());
+
+            // 灏� ZIP 鏂囦欢鍐欏叆鍝嶅簲
+            try(ServletOutputStream outputStream = response.getOutputStream()) {
+                outputStream.write(zipBytes);
+            }
+        } finally {
+            deleteDirectoryOrFile(tempDir);
+        }
+    }
+
+    private static void deleteDirectoryOrFile(File file) {
+        if (ObjectUtil.isNull(file)) {
+            return;
+        }
+
+        if (file.isDirectory()) {
+            File[] files = file.listFiles();
+            if (files != null) {
+                for (File f : files) {
+                    deleteDirectoryOrFile(f);
+                }
+            }
+        }
+        file.delete();
+
+    }
+
+    /**
+     * 瀵煎嚭妯℃澘
+     * @param response
+     * @param fileName
+     * @param sheetName
+     * @param dataList
+     * @param clazz
+     * @param  fieldNames
+     */
+    public static <T> void exportTemplate(HttpServletResponse response,String fileName, String sheetName, List<T> dataList, Class<T> clazz, List<String> fieldNames) throws IOException {
+        Set<Integer> selectedIndexes = getSelectFields(fieldNames, clazz);
+
+        resetResponse(fileName, response);
+
+        exportExcel(dataList, sheetName, clazz, false, response.getOutputStream(), null, selectedIndexes);
+    }
+
+    public static <T> @NotNull Set<Integer> getSelectFields(List<String> fieldNames, Class<T> clazz) {
+        Set<Integer> selectedIndexes = new HashSet<>();
+        if (CollUtil.isNotEmpty(fieldNames)) {
+            // 鍙嶅皠鑾峰彇瀛楁灞炴��
+            Field[] declaredFields = clazz.getDeclaredFields();
+//            // 鍖归厤闇�瑕佸鍏ョ殑瀛楁
+            for (int i = 0; i < declaredFields.length; i++) {
+                if (fieldNames.contains(declaredFields[i].getName())) {
+                    // 鑾峰彇闇�瑕佸鍏ョ殑瀛楁涓嬫爣
+                    selectedIndexes.add(i);
+                }
+            }
+        }
+        return selectedIndexes;
+
+    }
+
+    /**
+     * 瀵煎嚭excel
+     *
+     * @param list      瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @param clazz     瀹炰綋绫�
+     * @param merge     鏄惁鍚堝苟鍗曞厓鏍�
+     * @param os        杈撳嚭娴�
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
+                                       OutputStream os, List<DropDownOptions> options, Set<Integer> selectedIndexes) {
+        ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
+            .autoCloseStream(false)
+            .includeColumnIndexes(selectedIndexes)
+            // 鑷姩閫傞厤
+            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+            // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+            .registerConverter(new ExcelBigNumberConvert())
+            .sheet(sheetName);
+        if (merge) {
+            // 鍚堝苟澶勭悊鍣�
+            builder.registerWriteHandler(new CellMergeStrategy(list, true));
+        }
+        // 娣诲姞涓嬫媺妗嗘搷浣�
+        builder.registerWriteHandler(new ExcelDownHandler(options));
+        builder.doWrite(list);
+    }
+
+    /**
+     * 閲嶇疆鍝嶅簲浣�
+     */
+    private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
+        String filename = encodingFilename(sheetName);
+        FileUtils.setAttachmentResponseHeader(response, filename);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
+    }
+
+    /**
+     * 缂栫爜鏂囦欢鍚�
+     */
+    public static String encodingFilename(String filename) {
+        return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
+    }
+
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/annotation/CellMerge.java b/common/src/main/java/com/ycl/common/utils/excel/annotation/CellMerge.java
new file mode 100644
index 0000000..1b78f92
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/annotation/CellMerge.java
@@ -0,0 +1,29 @@
+package com.ycl.common.utils.excel.annotation;
+
+import com.ycl.common.utils.excel.core.CellMergeStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * excel 鍒楀崟鍏冩牸鍚堝苟(鍚堝苟鍒楃浉鍚岄」)
+ * <p>
+ * 闇�鎼厤 {@link CellMergeStrategy} 绛栫暐浣跨敤
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+
+    /**
+     * col index
+     */
+    int index() default -1;
+
+    /**
+     * 鍚堝苟闇�瑕佷緷璧栫殑鍏朵粬瀛楁鍚嶇О
+     */
+    String[] mergeBy() default {};
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/annotation/ExcelDictFormat.java b/common/src/main/java/com/ycl/common/utils/excel/annotation/ExcelDictFormat.java
new file mode 100644
index 0000000..7205c59
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/annotation/ExcelDictFormat.java
@@ -0,0 +1,32 @@
+package com.ycl.common.utils.excel.annotation;
+
+
+import com.ycl.common.utils.excel.utils.StringUtils;
+
+import java.lang.annotation.*;
+
+/**
+ * 瀛楀吀鏍煎紡鍖�
+ *
+ * @author Lion Li
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+    /**
+     * 濡傛灉鏄瓧鍏哥被鍨嬶紝璇疯缃瓧鍏哥殑type鍊� (濡�: sys_user_sex)
+     */
+    String dictType() default "";
+
+    /**
+     * 璇诲彇鍐呭杞〃杈惧紡 (濡�: 0=鐢�,1=濂�,2=鏈煡)
+     */
+    String readConverterExp() default "";
+
+    /**
+     * 鍒嗛殧绗︼紝璇诲彇瀛楃涓茬粍鍐呭
+     */
+    String separator() default StringUtils.SEPARATOR;
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/annotation/ExcelEnumFormat.java b/common/src/main/java/com/ycl/common/utils/excel/annotation/ExcelEnumFormat.java
new file mode 100644
index 0000000..3682609
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/annotation/ExcelEnumFormat.java
@@ -0,0 +1,30 @@
+package com.ycl.common.utils.excel.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏋氫妇鏍煎紡鍖�
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+    /**
+     * 瀛楀吀鏋氫妇绫诲瀷
+     */
+    Class<? extends Enum<?>> enumClass();
+
+    /**
+     * 瀛楀吀鏋氫妇绫讳腑瀵瑰簲鐨刢ode灞炴�у悕绉帮紝榛樿涓篶ode
+     */
+    String codeField() default "code";
+
+    /**
+     * 瀛楀吀鏋氫妇绫讳腑瀵瑰簲鐨則ext灞炴�у悕绉帮紝榛樿涓簍ext
+     */
+    String textField() default "text";
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/convert/ExcelBigNumberConvert.java b/common/src/main/java/com/ycl/common/utils/excel/convert/ExcelBigNumberConvert.java
new file mode 100644
index 0000000..50735ea
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/convert/ExcelBigNumberConvert.java
@@ -0,0 +1,52 @@
+package com.ycl.common.utils.excel.convert;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * 澶ф暟鍊艰浆鎹�
+ * Excel 鏁板�奸暱搴︿綅15浣� 澶т簬15浣嶇殑鏁板�艰浆鎹綅瀛楃涓�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelBigNumberConvert implements Converter<Long> {
+
+    @Override
+    public Class<Long> supportJavaTypeKey() {
+        return Long.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        return Convert.toLong(cellData.getData());
+    }
+
+    @Override
+    public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNotNull(object)) {
+            String str = Convert.toStr(object);
+            if (str.length() > 15) {
+                return new WriteCellData<>(str);
+            }
+        }
+        WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
+        cellData.setType(CellDataTypeEnum.NUMBER);
+        return cellData;
+    }
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/convert/ExcelEnumConvert.java b/common/src/main/java/com/ycl/common/utils/excel/convert/ExcelEnumConvert.java
new file mode 100644
index 0000000..b2479fb
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/convert/ExcelEnumConvert.java
@@ -0,0 +1,87 @@
+package com.ycl.common.utils.excel.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import com.ycl.common.utils.excel.annotation.ExcelEnumFormat;
+import com.ycl.common.utils.reflect.ReflectUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鏋氫妇鏍煎紡鍖栬浆鎹㈠鐞�
+ *
+ * @author Liang
+ */
+@Slf4j
+public class ExcelEnumConvert implements Converter<Object> {
+
+    @Override
+    public Class<Object> supportJavaTypeKey() {
+        return Object.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return null;
+    }
+
+    @Override
+    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        cellData.checkEmpty();
+        // Excel涓~鍏ョ殑鏄灇涓句腑鎸囧畾鐨勬弿杩�
+        Object textValue = switch (cellData.getType()) {
+            case STRING, DIRECT_STRING, RICH_TEXT_STRING -> cellData.getStringValue();
+            case NUMBER -> cellData.getNumberValue();
+            case BOOLEAN -> cellData.getBooleanValue();
+            default -> throw new IllegalArgumentException("鍗曞厓鏍肩被鍨嬪紓甯�!");
+        };
+        // 濡傛灉鏄┖鍊�
+        if (ObjectUtil.isNull(textValue)) {
+            return null;
+        }
+        Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
+        // 浠嶫ava杈撳嚭鑷矱xcel鏄痗ode杞瑃ext
+        // 鍥犳浠嶦xcel杞琂ava搴旇灏唗ext涓巆ode瀵硅皟
+        Map<Object, Object> enumTextToCodeMap = new HashMap<>();
+        enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
+        // 搴旇浠巘ext -> code涓煡鎵�
+        Object codeValue = enumTextToCodeMap.get(textValue);
+        return Convert.convert(contentProperty.getField().getType(), codeValue);
+    }
+
+    @Override
+    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNull(object)) {
+            return new WriteCellData<>("");
+        }
+        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
+        String value = Convert.toStr(enumValueMap.get(object), "");
+        return new WriteCellData<>(value);
+    }
+
+    private Map<Object, String> beforeConvert(ExcelContentProperty contentProperty) {
+        ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
+        Map<Object, String> enumValueMap = new HashMap<>();
+        Enum<?>[] enumConstants = anno.enumClass().getEnumConstants();
+        for (Enum<?> enumConstant : enumConstants) {
+            Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
+            String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
+            enumValueMap.put(codeValue, textValue);
+        }
+        return enumValueMap;
+    }
+
+    private ExcelEnumFormat getAnnotation(Field field) {
+        return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class);
+    }
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/core/CellMergeStrategy.java b/common/src/main/java/com/ycl/common/utils/excel/core/CellMergeStrategy.java
new file mode 100644
index 0000000..eb38e47
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/core/CellMergeStrategy.java
@@ -0,0 +1,157 @@
+package com.ycl.common.utils.excel.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.metadata.Head;
+import com.alibaba.excel.write.handler.WorkbookWriteHandler;
+import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
+import com.alibaba.excel.write.merge.AbstractMergeStrategy;
+import com.ycl.common.utils.excel.annotation.CellMerge;
+import com.ycl.common.utils.excel.utils.ReflectUtils;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+/**
+ * 鍒楀�奸噸澶嶅悎骞剁瓥鐣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
+
+    private final List<CellRangeAddress> cellList;
+    private final boolean hasTitle;
+    private int rowIndex;
+
+    public CellMergeStrategy(List<?> list, boolean hasTitle) {
+        this.hasTitle = hasTitle;
+        // 琛屽悎骞跺紑濮嬩笅鏍�
+        this.rowIndex = hasTitle ? 1 : 0;
+        this.cellList = handle(list, hasTitle);
+    }
+
+    @Override
+    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
+        //鍗曞厓鏍煎啓鍏ヤ簡,閬嶅巻鍚堝苟鍖哄煙,濡傛灉璇ell鍦ㄥ尯鍩熷唴,浣嗛潪棣栬,鍒欐竻绌�
+        final int rowIndex = cell.getRowIndex();
+        if (CollUtil.isNotEmpty(cellList)){
+            for (CellRangeAddress cellAddresses : cellList) {
+                final int firstRow = cellAddresses.getFirstRow();
+                if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
+                    cell.setBlank();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
+        //褰撳墠琛ㄦ牸鍐欏畬鍚庯紝缁熶竴鍐欏叆
+        if (CollUtil.isNotEmpty(cellList)){
+            for (CellRangeAddress item : cellList) {
+                context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
+            }
+        }
+    }
+
+    @SneakyThrows
+    private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
+        List<CellRangeAddress> cellList = new ArrayList<>();
+        if (CollUtil.isEmpty(list)) {
+            return cellList;
+        }
+        Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
+
+        // 鏈夋敞瑙g殑瀛楁
+        List<Field> mergeFields = new ArrayList<>();
+        List<Integer> mergeFieldsIndex = new ArrayList<>();
+        for (int i = 0; i < fields.length; i++) {
+            Field field = fields[i];
+            if (field.isAnnotationPresent(CellMerge.class)) {
+                CellMerge cm = field.getAnnotation(CellMerge.class);
+                mergeFields.add(field);
+                mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
+                if (hasTitle) {
+                    ExcelProperty property = field.getAnnotation(ExcelProperty.class);
+                    rowIndex = Math.max(rowIndex, property.value().length);
+                }
+            }
+        }
+
+        Map<Field, RepeatCell> map = new HashMap<>();
+        // 鐢熸垚涓や袱鍚堝苟鍗曞厓鏍�
+        for (int i = 0; i < list.size(); i++) {
+            for (int j = 0; j < mergeFields.size(); j++) {
+                Field field = mergeFields.get(j);
+                Object val = ReflectUtils.invokeGetter(list.get(i), field.getName());
+
+                int colNum = mergeFieldsIndex.get(j);
+                if (!map.containsKey(field)) {
+                    map.put(field, new RepeatCell(val, i));
+                } else {
+                    RepeatCell repeatCell = map.get(field);
+                    Object cellValue = repeatCell.getValue();
+                    if (cellValue == null || "".equals(cellValue)) {
+                        // 绌哄�艰烦杩囦笉鍚堝苟
+                        continue;
+                    }
+
+                    if (!cellValue.equals(val)) {
+                        if ((i - repeatCell.getCurrent() > 1)) {
+                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
+                        }
+                        map.put(field, new RepeatCell(val, i));
+                    } else if (i == list.size() - 1) {
+                        if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
+                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
+                        }
+                    } else if (!isMerge(list, i, field)) {
+                        if ((i - repeatCell.getCurrent() > 1)) {
+                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
+                        }
+                        map.put(field, new RepeatCell(val, i));
+                    }
+                }
+            }
+        }
+        return cellList;
+    }
+
+    private boolean isMerge(List<?> list, int i, Field field) {
+        boolean isMerge = true;
+        CellMerge cm = field.getAnnotation(CellMerge.class);
+        final String[] mergeBy = cm.mergeBy();
+        if (StrUtil.isAllNotBlank(mergeBy)) {
+            //姣斿褰撳墠list(i)鍜宭ist(i - 1)鐨勫悇涓睘鎬у�间竴涓�姣斿 濡傛灉鍏ㄤ负鐪� 鍒欎负鐪�
+            for (String fieldName : mergeBy) {
+                final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
+                final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
+                if (!Objects.equals(valPre, valCurrent)) {
+                    //渚濊禆瀛楁濡傛湁浠讳竴涓嶇瓑鍊�,鍒欐爣璁颁负涓嶅彲鍚堝苟
+                    isMerge = false;
+                }
+            }
+        }
+        return isMerge;
+    }
+
+    @Data
+    @AllArgsConstructor
+    static class RepeatCell {
+
+        private Object value;
+
+        private int current;
+
+    }
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/core/DefaultExcelListener.java b/common/src/main/java/com/ycl/common/utils/excel/core/DefaultExcelListener.java
new file mode 100644
index 0000000..04390be
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/core/DefaultExcelListener.java
@@ -0,0 +1,104 @@
+package com.ycl.common.utils.excel.core;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+import com.alibaba.excel.exception.ExcelAnalysisException;
+import com.alibaba.excel.exception.ExcelDataConvertException;
+import com.ycl.common.utils.excel.utils.JsonUtils;
+import com.ycl.common.utils.excel.utils.StreamUtils;
+import com.ycl.common.utils.excel.utils.ValidatorUtils;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Excel 瀵煎叆鐩戝惉
+ *
+ * @author Yjoioooo
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor
+public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {
+
+    /**
+     * 鏄惁Validator妫�楠岋紝榛樿涓烘槸
+     */
+    private Boolean isValidate = Boolean.TRUE;
+
+    /**
+     * excel 琛ㄥご鏁版嵁
+     */
+    private Map<Integer, String> headMap;
+
+    /**
+     * 瀵煎叆鍥炴墽
+     */
+    private ExcelResult<T> excelResult;
+
+    public DefaultExcelListener(boolean isValidate) {
+        this.excelResult = new DefaultExcelResult<>();
+        this.isValidate = isValidate;
+    }
+
+    /**
+     * 澶勭悊寮傚父
+     *
+     * @param exception ExcelDataConvertException
+     * @param context   Excel 涓婁笅鏂�
+     */
+    @Override
+    public void onException(Exception exception, AnalysisContext context) throws Exception {
+        String errMsg = null;
+        if (exception instanceof ExcelDataConvertException excelDataConvertException) {
+            // 濡傛灉鏄煇涓�涓崟鍏冩牸鐨勮浆鎹㈠紓甯� 鑳借幏鍙栧埌鍏蜂綋琛屽彿
+            Integer rowIndex = excelDataConvertException.getRowIndex();
+            Integer columnIndex = excelDataConvertException.getColumnIndex();
+            errMsg = StrUtil.format("绗瑊}琛�-绗瑊}鍒�-琛ㄥご{}: 瑙f瀽寮傚父<br/>",
+                rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
+            if (log.isDebugEnabled()) {
+                log.error(errMsg);
+            }
+        }
+        if (exception instanceof ConstraintViolationException constraintViolationException) {
+            Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
+            String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", ");
+            errMsg = StrUtil.format("绗瑊}琛屾暟鎹牎楠屽紓甯�: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
+            if (log.isDebugEnabled()) {
+                log.error(errMsg);
+            }
+        }
+        excelResult.getErrorList().add(errMsg);
+        throw new ExcelAnalysisException(errMsg);
+    }
+
+    @Override
+    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
+        this.headMap = headMap;
+        log.debug("瑙f瀽鍒颁竴鏉¤〃澶存暟鎹�: {}", JsonUtils.toJsonString(headMap));
+    }
+
+    @Override
+    public void invoke(T data, AnalysisContext context) {
+        if (isValidate) {
+            ValidatorUtils.validate(data);
+        }
+        excelResult.getList().add(data);
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext context) {
+        log.debug("鎵�鏈夋暟鎹В鏋愬畬鎴愶紒");
+    }
+
+    @Override
+    public ExcelResult<T> getExcelResult() {
+        return excelResult;
+    }
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/core/DefaultExcelResult.java b/common/src/main/java/com/ycl/common/utils/excel/core/DefaultExcelResult.java
new file mode 100644
index 0000000..d61b3ab
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/core/DefaultExcelResult.java
@@ -0,0 +1,73 @@
+package com.ycl.common.utils.excel.core;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 榛樿excel杩斿洖瀵硅薄
+ *
+ * @author Yjoioooo
+ * @author Lion Li
+ */
+public class DefaultExcelResult<T> implements ExcelResult<T> {
+
+    /**
+     * 鏁版嵁瀵硅薄list
+     */
+    @Setter
+    private List<T> list;
+
+    /**
+     * 閿欒淇℃伅鍒楄〃
+     */
+    @Setter
+    private List<String> errorList;
+
+    public DefaultExcelResult() {
+        this.list = new ArrayList<>();
+        this.errorList = new ArrayList<>();
+    }
+
+    public DefaultExcelResult(List<T> list, List<String> errorList) {
+        this.list = list;
+        this.errorList = errorList;
+    }
+
+    public DefaultExcelResult(ExcelResult<T> excelResult) {
+        this.list = excelResult.getList();
+        this.errorList = excelResult.getErrorList();
+    }
+
+    @Override
+    public List<T> getList() {
+        return list;
+    }
+
+    @Override
+    public List<String> getErrorList() {
+        return errorList;
+    }
+
+    /**
+     * 鑾峰彇瀵煎叆鍥炴墽
+     *
+     * @return 瀵煎叆鍥炴墽
+     */
+    @Override
+    public String getAnalysis() {
+        int successCount = list.size();
+        int errorCount = errorList.size();
+        if (successCount == 0) {
+            return "璇诲彇澶辫触锛屾湭瑙f瀽鍒版暟鎹�";
+        } else {
+            if (errorCount == 0) {
+                return StrUtil.format("鎭枩鎮紝鍏ㄩ儴璇诲彇鎴愬姛锛佸叡{}鏉�", successCount);
+            } else {
+                return "";
+            }
+        }
+    }
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/core/DropDownOptions.java b/common/src/main/java/com/ycl/common/utils/excel/core/DropDownOptions.java
new file mode 100644
index 0000000..aa0d9f7
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/core/DropDownOptions.java
@@ -0,0 +1,149 @@
+package com.ycl.common.utils.excel.core;
+
+import cn.hutool.core.util.StrUtil;
+import com.ycl.common.exception.ServiceException;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * <h1>Excel涓嬫媺鍙�夐」</h1>
+ * 娉ㄦ剰锛氫负纭繚涓嬫媺妗嗚В鏋愭纭紝浼犲�煎姟蹇呬娇鐢╟reateOptionValue()鍋氫负鍊肩殑鎷兼帴
+ *
+ * @author Emil.Zhang
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@SuppressWarnings("unused")
+public class DropDownOptions {
+    /**
+     * 涓�绾т笅鎷夋墍鍦ㄥ垪index锛屼粠0寮�濮嬬畻
+     */
+    private int index = 0;
+    /**
+     * 浜岀骇涓嬫媺鎵�鍦ㄧ殑index锛屼粠0寮�濮嬬畻锛屼笉鑳戒笌涓�绾х浉鍚�
+     */
+    private int nextIndex = 0;
+    /**
+     * 涓�绾т笅鎷夋墍鍖呭惈鐨勬暟鎹�
+     */
+    private List<String> options = new ArrayList<>();
+    /**
+     * 浜岀骇涓嬫媺鎵�鍖呭惈鐨勬暟鎹甅ap
+     * <p>浠ユ瘡涓�涓竴绾ч�夐」鍊间负Key锛屾瘡涓竴绾ч�夐」瀵瑰簲鐨勪簩绾ф暟鎹负Value</p>
+     */
+    private Map<String, List<String>> nextOptions = new HashMap<>();
+    /**
+     * 鍒嗛殧绗�
+     */
+    private static final String DELIMITER = "_";
+
+    /**
+     * 鍒涘缓鍙湁涓�绾х殑涓嬫媺閫�
+     */
+    public DropDownOptions(int index, List<String> options) {
+        this.index = index;
+        this.options = options;
+    }
+
+    /**
+     * <h2>鍒涘缓姣忎釜閫夐」鍙�夊��</h2>
+     * <p>娉ㄦ剰锛氫笉鑳戒互鏁板瓧锛岀壒娈婄鍙峰紑澶达紝閫夐」涓笉鍙互鍖呭惈浠讳綍杩愮畻绗﹀彿</p>
+     *
+     * @param vars 鍙�夊�煎唴鍖呭惈鐨勫弬鏁�
+     * @return 鍚堣鐨勫彲閫夊��
+     */
+    public static String createOptionValue(Object... vars) {
+        StringBuilder stringBuffer = new StringBuilder();
+        String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
+        for (int i = 0; i < vars.length; i++) {
+            String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
+            if (!var.matches(regex)) {
+                throw new ServiceException("閫夐」鏁版嵁涓嶇鍚堣鍒欙紝浠呭厑璁镐娇鐢ㄤ腑鑻辨枃瀛楃浠ュ強鏁板瓧");
+            }
+            stringBuffer.append(var);
+            if (i < vars.length - 1) {
+                // 鐩磋嚦鏈�鍚庝竴涓墠锛岄兘浠浣滀负鍒囧壊绾�
+                stringBuffer.append(DELIMITER);
+            }
+        }
+        if (stringBuffer.toString().matches("^\\d_*$")) {
+            throw new ServiceException("绂佹浠ユ暟瀛楀紑澶�");
+        }
+        return stringBuffer.toString();
+    }
+
+    /**
+     * 灏嗗鐞嗗悗鍚堢悊鐨勫彲閫夊�艰В鏋愪负鍘熷鐨勫弬鏁�
+     *
+     * @param option 缁忚繃澶勭悊鍚庣殑鍚堢悊鐨勫彲閫夐」
+     * @return 鍘熷鐨勫弬鏁�
+     */
+    public static List<String> analyzeOptionValue(String option) {
+        return StrUtil.split(option, DELIMITER, true, true);
+    }
+
+    /**
+     * 鍒涘缓绾ц仈涓嬫媺閫夐」
+     *
+     * @param parentList                  鐖跺疄浣撳彲閫夐」鍘熷鏁版嵁
+     * @param parentIndex                 鐖朵笅鎷夐�変綅缃�
+     * @param sonList                     瀛愬疄浣撳彲閫夐」鍘熷鏁版嵁
+     * @param sonIndex                    瀛愪笅鎷夐�変綅缃�
+     * @param parentHowToGetIdFunction    鐖剁被濡備綍鑾峰彇鍞竴鏍囪瘑
+     * @param sonHowToGetParentIdFunction 瀛愮被濡備綍鑾峰彇鐖剁被鐨勫敮涓�鏍囪瘑
+     * @param howToBuildEveryOption       濡備綍鐢熸垚涓嬫媺閫夊唴瀹�
+     * @return 绾ц仈涓嬫媺閫夐」
+     */
+    public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
+                                                         int parentIndex,
+                                                         List<T> sonList,
+                                                         int sonIndex,
+                                                         Function<T, Number> parentHowToGetIdFunction,
+                                                         Function<T, Number> sonHowToGetParentIdFunction,
+                                                         Function<T, String> howToBuildEveryOption) {
+        DropDownOptions parentLinkSonOptions = new DropDownOptions();
+        // 鍏堝垱寤虹埗绫荤殑涓嬫媺
+        parentLinkSonOptions.setIndex(parentIndex);
+        parentLinkSonOptions.setOptions(
+            parentList.stream()
+                .map(howToBuildEveryOption)
+                .collect(Collectors.toList())
+        );
+        // 鎻愬彇鐖�-瀛愮骇鑱斾笅鎷�
+        Map<String, List<String>> sonOptions = new HashMap<>();
+        // 鐖剁骇渚濇嵁鑷繁鐨処D鍒嗙粍
+        Map<Number, List<T>> parentGroupByIdMap =
+            parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
+        // 閬嶅巻姣忎釜瀛愰泦锛屾彁鍙栧埌Map涓�
+        sonList.forEach(everySon -> {
+            if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
+                // 鎵惧埌瀵瑰簲鐨勪笂绾�
+                T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
+                // 鎻愬彇鍚嶇О鍜孖D浣滀负Key
+                String key = howToBuildEveryOption.apply(parentObj);
+                // Key瀵瑰簲鐨刅alue
+                List<String> thisParentSonOptionList;
+                if (sonOptions.containsKey(key)) {
+                    thisParentSonOptionList = sonOptions.get(key);
+                } else {
+                    thisParentSonOptionList = new ArrayList<>();
+                    sonOptions.put(key, thisParentSonOptionList);
+                }
+                // 寰�Value涓坊鍔犲綋鍓嶅瓙闆嗛�夐」
+                thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
+            }
+        });
+        parentLinkSonOptions.setNextIndex(sonIndex);
+        parentLinkSonOptions.setNextOptions(sonOptions);
+        return parentLinkSonOptions;
+    }
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/core/ExcelDownHandler.java b/common/src/main/java/com/ycl/common/utils/excel/core/ExcelDownHandler.java
new file mode 100644
index 0000000..47ad684
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/core/ExcelDownHandler.java
@@ -0,0 +1,373 @@
+package com.ycl.common.utils.excel.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.EnumUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.excel.metadata.FieldCache;
+import com.alibaba.excel.metadata.FieldWrapper;
+import com.alibaba.excel.util.ClassUtils;
+import com.alibaba.excel.write.handler.SheetWriteHandler;
+import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
+import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
+import com.ycl.common.exception.ServiceException;
+import com.ycl.common.utils.excel.annotation.ExcelDictFormat;
+import com.ycl.common.utils.excel.annotation.ExcelEnumFormat;
+import com.ycl.common.utils.excel.service.DictService;
+import com.ycl.common.utils.excel.utils.StreamUtils;
+import com.ycl.common.utils.excel.utils.StringUtils;
+import com.ycl.common.utils.spring.SpringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.ss.util.WorkbookUtil;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+/**
+ * <h1>Excel琛ㄦ牸涓嬫媺閫夋搷浣�</h1>
+ * 鑰冭檻鍒颁笅鎷夐�夎繃澶氬彲鑳藉鑷碋xcel鎵撳紑缂撴參鐨勯棶棰橈紝鍙牎楠屽墠1000琛�
+ * <p>
+ * 鍗冲彧鏈夊墠1000琛岀殑鏁版嵁鍙互鐢ㄤ笅鎷夋锛岃秴鍑虹殑鑷閫氳繃闄愬埗鏁版嵁閲忕殑褰㈠紡锛岀浜屾杈撳嚭
+ *
+ * @author Emil.Zhang
+ */
+@Slf4j
+public class ExcelDownHandler implements SheetWriteHandler {
+
+    /**
+     * Excel琛ㄦ牸涓殑鍒楀悕鑻辨枃
+     * 浠呬负浜嗚В鏋愬垪鑻辨枃锛岀姝慨鏀�
+     */
+    private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    /**
+     * 鍗曢�夋暟鎹甋heet鍚�
+     */
+    private static final String OPTIONS_SHEET_NAME = "options";
+    /**
+     * 鑱斿姩閫夋嫨鏁版嵁Sheet鍚嶇殑澶�
+     */
+    private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
+    /**
+     * 涓嬫媺鍙�夐」
+     */
+    private final List<DropDownOptions> dropDownOptions;
+    /**
+     * 褰撳墠鍗曢�夎繘搴�
+     */
+    private int currentOptionsColumnIndex;
+    /**
+     * 褰撳墠鑱斿姩閫夋嫨杩涘害
+     */
+    private int currentLinkedOptionsSheetIndex;
+    private final DictService dictService;
+
+    public ExcelDownHandler(List<DropDownOptions> options) {
+        this.dropDownOptions = options;
+        this.currentOptionsColumnIndex = 0;
+        this.currentLinkedOptionsSheetIndex = 0;
+        this.dictService = SpringUtils.getBean(DictService.class);
+    }
+
+    /**
+     * <h2>寮�濮嬪垱寤轰笅鎷夋暟鎹�</h2>
+     * 1.閫氳繃瑙f瀽浼犲叆鐨凘ExcelProperty鍚岀骇鏄惁鏍囨敞鏈堾DropDown閫夐」
+     * 濡傛灉鏈変笖璁剧疆浜唙alue鍊硷紝鍒欏皢鍏剁洿鎺ョ疆涓轰笅鎷夊彲閫夐」
+     * <p>
+     * 2.鎴栬�呭湪璋冪敤ExcelUtil鏃舵寚瀹氫簡鍙�夐」锛屽皢渚濇嵁浼犲叆鐨勫彲閫夐」鍋氫笅鎷�
+     * <p>
+     * 3.浜岃�呭苟瀛橈紝娉ㄦ剰璋冪敤鏂瑰紡
+     */
+    @Override
+    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
+        Sheet sheet = writeSheetHolder.getSheet();
+        // 寮�濮嬭缃笅鎷夋 HSSFWorkbook
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        Workbook workbook = writeWorkbookHolder.getWorkbook();
+        FieldCache fieldCache = ClassUtils.declaredFields(writeWorkbookHolder.getClazz(), writeWorkbookHolder);
+        for (Map.Entry<Integer, FieldWrapper> entry : fieldCache.getSortedFieldMap().entrySet()) {
+            Integer index = entry.getKey();
+            FieldWrapper wrapper = entry.getValue();
+            Field field = wrapper.getField();
+            // 寰幆瀹炰綋涓殑姣忎釜灞炴��
+            // 鍙�夌殑涓嬫媺鍊�
+            List<String> options = new ArrayList<>();
+            if (field.isAnnotationPresent(ExcelDictFormat.class)) {
+                // 濡傛灉鎸囧畾浜咢ExcelDictFormat锛屽垯浣跨敤瀛楀吀鐨勯�昏緫
+                ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
+                String dictType = format.dictType();
+                String converterExp = format.readConverterExp();
+                if (StringUtils.isNotBlank(dictType)) {
+                    // 濡傛灉浼犻�掍簡瀛楀吀鍚嶏紝鍒欎緷鎹瓧鍏稿缓绔嬩笅鎷�
+                    Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
+                        .orElseThrow(() -> new ServiceException(String.format("瀛楀吀 %s 涓嶅瓨鍦�", dictType)))
+                        .values();
+                    options = new ArrayList<>(values);
+                } else if (StringUtils.isNotBlank(converterExp)) {
+                    // 濡傛灉鎸囧畾浜嗙‘鍒囩殑鍊硷紝鍒欑洿鎺ヨВ鏋愮‘鍒囩殑鍊�
+                    List<String> strList = StringUtils.splitList(converterExp, format.separator());
+                    options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]);
+                }
+            } else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
+                // 鍚﹀垯濡傛灉鎸囧畾浜咢ExcelEnumFormat锛屽垯浣跨敤鏋氫妇鐨勯�昏緫
+                ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
+                List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
+                options = StreamUtils.toList(values, String::valueOf);
+            }
+            if (ObjectUtil.isNotEmpty(options)) {
+                // 浠呭綋涓嬫媺鍙�夐」涓嶄负绌烘椂鎵ц
+                if (options.size() > 20) {
+                    // 杩欓噷闄愬埗濡傛灉鍙�夐」澶т簬20锛屽垯浣跨敤棰濆琛ㄥ舰寮�
+                    dropDownWithSheet(helper, workbook, sheet, index, options);
+                } else {
+                    // 鍚﹀垯浣跨敤鍥哄畾鍊煎舰寮�
+                    dropDownWithSimple(helper, sheet, index, options);
+                }
+            }
+        }
+        if (CollUtil.isEmpty(dropDownOptions)) {
+            return;
+        }
+        dropDownOptions.forEach(everyOptions -> {
+            // 濡傛灉浼犻�掍簡涓嬫媺妗嗛�夋嫨鍣ㄥ弬鏁�
+            if (!everyOptions.getNextOptions().isEmpty()) {
+                // 褰撲簩绾ч�夐」涓嶄负绌烘椂锛屼娇鐢ㄩ澶栧叧鑱旇〃鐨勫舰寮�
+                dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
+            } else if (everyOptions.getOptions().size() > 10) {
+                // 褰撲竴绾ч�夐」鍙傛暟涓暟澶т簬10锛屼娇鐢ㄩ澶栬〃鐨勫舰寮�
+                dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
+            } else if (everyOptions.getOptions().size() != 0) {
+                // 褰撲竴绾ч�夐」涓暟涓嶄负绌猴紝浣跨敤榛樿褰㈠紡
+                dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
+            }
+        });
+    }
+
+    /**
+     * <h2>绠�鍗曚笅鎷夋</h2>
+     * 鐩存帴灏嗗彲閫夐」鎷兼帴涓烘寚瀹氬垪鐨勬暟鎹牎楠屽��
+     *
+     * @param celIndex 鍒梚ndex
+     * @param value    涓嬫媺閫夊彲閫夊��
+     */
+    private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
+        if (ObjectUtil.isEmpty(value)) {
+            return;
+        }
+        this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(ArrayUtil.toArray(value, String.class)));
+    }
+
+    /**
+     * <h2>棰濆琛ㄦ牸褰㈠紡鐨勭骇鑱斾笅鎷夋</h2>
+     *
+     * @param options 棰濆琛ㄦ牸褰㈠紡瀛樺偍鐨勪笅鎷夊彲閫夐」
+     */
+    private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) {
+        String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
+        // 鍒涘缓鑱斿姩涓嬫媺鏁版嵁琛�
+        Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
+        // 灏嗕笅鎷夎〃闅愯棌
+        workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
+        // 瀹屽杽妯悜鐨勪竴绾ч�夐」鏁版嵁琛�
+        List<String> firstOptions = options.getOptions();
+        Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
+
+        // 鍒涘缓鍚嶇О绠$悊鍣�
+        Name name = workbook.createName();
+        // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+        name.setNameName(linkedOptionsSheetName);
+        // 浠ユí鍚戠涓�琛屽垱寤轰竴绾т笅鎷夋嫾鎺ュ紩鐢ㄤ綅缃�
+        String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
+            linkedOptionsSheetName,
+            getExcelColumnName(0),
+            getExcelColumnName(firstOptions.size())
+        );
+        // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+        name.setRefersToFormula(firstOptionsFunction);
+        // 璁剧疆鏁版嵁鏍¢獙涓哄簭鍒楁ā寮忥紝寮曠敤鐨勬槸鍚嶇О绠$悊鍣ㄤ腑鐨勫埆鍚�
+        this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
+
+        for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) {
+            // 鍏堟彁鍙栦富琛ㄤ腑涓�绾т笅鎷夌殑鍒楀悕
+            String firstOptionsColumnName = getExcelColumnName(columIndex);
+            // 涓�娆″惊鐜槸姣忎竴涓竴绾ч�夐」
+            int finalI = columIndex;
+            // 鏈寰幆鐨勪竴绾ч�夐」鍊�
+            String thisFirstOptionsValue = firstOptions.get(columIndex);
+            // 鍒涘缓绗竴琛岀殑鏁版嵁
+            Optional.ofNullable(linkedOptionsDataSheet.getRow(0))
+                // 濡傛灉涓嶅瓨鍦ㄥ垯鍒涘缓绗竴琛�
+                .orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
+                // 绗竴琛屽綋鍓嶅垪
+                .createCell(columIndex)
+                // 璁剧疆鍊间负褰撳墠涓�绾ч�夐」鍊�
+                .setCellValue(thisFirstOptionsValue);
+
+            // 绗簩琛屽紑濮嬶紝璁剧疆绗簩绾у埆閫夐」鍙傛暟
+            List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
+            if (CollUtil.isEmpty(secondOptions)) {
+                // 蹇呴』淇濊瘉鑷冲皯鏈変竴涓叧鑱旈�夐」锛屽惁鍒欏皢瀵艰嚧Excel瑙f瀽閿欒
+                secondOptions = Collections.singletonList("鏆傛棤_0");
+            }
+
+            // 浠ヨ涓�绾ч�夐」鍊煎垱寤哄瓙鍚嶇О绠$悊鍣�
+            Name sonName = workbook.createName();
+            // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+            sonName.setNameName(thisFirstOptionsValue);
+            // 浠ョ浜岃璇ュ垪鏁版嵁鎷兼帴寮曠敤浣嶇疆
+            String sonFunction = String.format("%s!$%s$2:$%s$%d",
+                linkedOptionsSheetName,
+                firstOptionsColumnName,
+                firstOptionsColumnName,
+                secondOptions.size() + 1
+            );
+            // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+            sonName.setRefersToFormula(sonFunction);
+            // 鏁版嵁楠岃瘉涓哄簭鍒楁ā寮忥紝寮曠敤鍒版瘡涓�涓富琛ㄤ腑鐨勪簩绾ч�夐」浣嶇疆
+            // 鍒涘缓瀛愰」鐨勫悕绉扮鐞嗗櫒锛屽彧鏄负浜嗕娇寰桬xcel鍙互璇嗗埆鍒版暟鎹�
+            String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex());
+            for (int i = 0; i < 100; i++) {
+                // 浠ヤ竴绾ч�夐」瀵瑰簲鐨勪富浣撴墍鍦ㄤ綅缃垱寤轰簩绾т笅鎷�
+                String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1);
+                // 浜岀骇鍙兘涓昏〃姣忎竴琛岀殑姣忎竴鍒楁坊鍔犱簩绾ф牎楠�
+                markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
+            }
+
+            for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) {
+                // 浠庣浜岃寮�濮嬪~鍏呬簩绾ч�夐」
+                int finalRowIndex = rowIndex + 1;
+                int finalColumIndex = columIndex;
+
+                Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex))
+                    // 娌℃湁鍒欏垱寤�
+                    .orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex));
+                Optional
+                    // 鍦ㄦ湰绾т竴绾ч�夐」鎵�鍦ㄧ殑鍒�
+                    .ofNullable(row.getCell(finalColumIndex))
+                    // 涓嶅瓨鍦ㄥ垯鍒涘缓
+                    .orElseGet(() -> row.createCell(finalColumIndex))
+                    // 璁剧疆浜岀骇閫夐」鍊�
+                    .setCellValue(secondOptions.get(rowIndex));
+            }
+        }
+
+        currentLinkedOptionsSheetIndex++;
+    }
+
+    /**
+     * <h2>棰濆琛ㄦ牸褰㈠紡鐨勬櫘閫氫笅鎷夋</h2>
+     * 鐢变簬涓嬫媺妗嗗彲閫夊�兼暟閲忚繃澶氾紝涓烘彁鍗嘐xcel鎵撳紑鏁堢巼锛屼娇鐢ㄩ澶栬〃鏍煎舰寮忓仛涓嬫媺
+     *
+     * @param celIndex 涓嬫媺閫�
+     * @param value    涓嬫媺閫夊彲閫夊��
+     */
+    private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
+        // 鍒涘缓涓嬫媺鏁版嵁琛�
+        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
+            .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
+        // 灏嗕笅鎷夎〃闅愯棌
+        workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
+        // 瀹屽杽绾靛悜鐨勪竴绾ч�夐」鏁版嵁琛�
+        for (int i = 0; i < value.size(); i++) {
+            int finalI = i;
+            // 鑾峰彇姣忎竴閫夐」琛岋紝濡傛灉娌℃湁鍒欏垱寤�
+            Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
+                .orElseGet(() -> simpleDataSheet.createRow(finalI));
+            // 鑾峰彇鏈骇閫夐」瀵瑰簲鐨勯�夐」鍒楋紝濡傛灉娌℃湁鍒欏垱寤�
+            Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
+                .orElseGet(() -> row.createCell(currentOptionsColumnIndex));
+            // 璁剧疆鍊�
+            cell.setCellValue(value.get(i));
+        }
+
+        // 鍒涘缓鍚嶇О绠$悊鍣�
+        Name name = workbook.createName();
+        // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+        String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
+        name.setNameName(nameName);
+        // 浠ョ旱鍚戠涓�鍒楀垱寤轰竴绾т笅鎷夋嫾鎺ュ紩鐢ㄤ綅缃�
+        String function = String.format("%s!$%s$1:$%s$%d",
+            OPTIONS_SHEET_NAME,
+            getExcelColumnName(currentOptionsColumnIndex),
+            getExcelColumnName(currentOptionsColumnIndex),
+            value.size());
+        // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+        name.setRefersToFormula(function);
+        // 璁剧疆鏁版嵁鏍¢獙涓哄簭鍒楁ā寮忥紝寮曠敤鐨勬槸鍚嶇О绠$悊鍣ㄤ腑鐨勫埆鍚�
+        this.markOptionsToSheet(helper, sheet, celIndex, helper.createFormulaListConstraint(nameName));
+        currentOptionsColumnIndex++;
+    }
+
+    /**
+     * 鎸傝浇涓嬫媺鐨勫垪锛屼粎闄愪竴绾ч�夐」
+     */
+    private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex,
+                                    DataValidationConstraint constraint) {
+        // 璁剧疆鏁版嵁鏈夋晥鎬у姞杞藉湪鍝釜鍗曞厓鏍间笂,鍥涗釜鍙傛暟鍒嗗埆鏄細璧峰琛屻�佺粓姝㈣銆佽捣濮嬪垪銆佺粓姝㈠垪
+        CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex);
+        markDataValidationToSheet(helper, sheet, constraint, addressList);
+    }
+
+    /**
+     * 鎸傝浇涓嬫媺鐨勫垪锛屼粎闄愪簩绾ч�夐」
+     */
+    private void markLinkedOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer rowIndex,
+                                          Integer celIndex, DataValidationConstraint constraint) {
+        // 璁剧疆鏁版嵁鏈夋晥鎬у姞杞藉湪鍝釜鍗曞厓鏍间笂,鍥涗釜鍙傛暟鍒嗗埆鏄細璧峰琛屻�佺粓姝㈣銆佽捣濮嬪垪銆佺粓姝㈠垪
+        CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, celIndex, celIndex);
+        markDataValidationToSheet(helper, sheet, constraint, addressList);
+    }
+
+    /**
+     * 搴旂敤鏁版嵁鏍¢獙
+     */
+    private void markDataValidationToSheet(DataValidationHelper helper, Sheet sheet,
+                                           DataValidationConstraint constraint, CellRangeAddressList addressList) {
+        // 鏁版嵁鏈夋晥鎬у璞�
+        DataValidation dataValidation = helper.createValidation(constraint, addressList);
+        // 澶勭悊Excel鍏煎鎬ч棶棰�
+        if (dataValidation instanceof XSSFDataValidation) {
+            //鏁版嵁鏍¢獙
+            dataValidation.setSuppressDropDownArrow(true);
+            //閿欒鎻愮ず
+            dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
+            dataValidation.createErrorBox("鎻愮ず", "姝ゅ�间笌鍗曞厓鏍煎畾涔夋暟鎹笉涓�鑷�");
+            dataValidation.setShowErrorBox(true);
+            //閫夊畾鎻愮ず
+            dataValidation.createPromptBox("濉啓璇存槑锛�", "濉啓鍐呭鍙兘涓轰笅鎷変腑鏁版嵁锛屽叾浠栨暟鎹皢瀵艰嚧瀵煎叆澶辫触");
+            dataValidation.setShowPromptBox(true);
+            sheet.addValidationData(dataValidation);
+        } else {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * <h2>渚濇嵁鍒梚ndex鑾峰彇鍒楀悕鑻辨枃</h2>
+     * 渚濇嵁鍒梚ndex杞崲涓篍xcel涓殑鍒楀悕鑻辨枃
+     * <p>渚嬪绗�1鍒楋紝index涓�0锛岃В鏋愬嚭鏉ヤ负A鍒�</p>
+     * 绗�27鍒楋紝index涓�26锛岃В鏋愪负AA鍒�
+     * <p>绗�28鍒楋紝index涓�27锛岃В鏋愪负AB鍒�</p>
+     *
+     * @param columnIndex 鍒梚ndex
+     * @return 鍒梚ndex鎵�鍦ㄥ緱鑻辨枃鍚�
+     */
+    private String getExcelColumnName(int columnIndex) {
+        // 26涓�寰幆鐨勬鏁�
+        int columnCircleCount = columnIndex / 26;
+        // 26涓�寰幆鍐呯殑浣嶇疆
+        int thisCircleColumnIndex = columnIndex % 26;
+        // 26涓�寰幆鐨勬鏁板ぇ浜�0锛屽垯瑙嗕负鏍忓悕鑷冲皯涓や綅
+        String columnPrefix = columnCircleCount == 0
+            ? StrUtil.EMPTY
+            : StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
+        // 浠�26涓�寰幆鍐呭彇瀵瑰簲鐨勬爮浣嶅悕
+        String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
+        // 灏嗕簩鑰呮嫾鎺ュ嵆涓烘渶缁堢殑鏍忎綅鍚�
+        return columnPrefix + columnNext;
+    }
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/core/ExcelListener.java b/common/src/main/java/com/ycl/common/utils/excel/core/ExcelListener.java
new file mode 100644
index 0000000..76ba16c
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/core/ExcelListener.java
@@ -0,0 +1,14 @@
+package com.ycl.common.utils.excel.core;
+
+import com.alibaba.excel.read.listener.ReadListener;
+
+/**
+ * Excel 瀵煎叆鐩戝惉
+ *
+ * @author Lion Li
+ */
+public interface ExcelListener<T> extends ReadListener<T> {
+
+    ExcelResult<T> getExcelResult();
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/core/ExcelResult.java b/common/src/main/java/com/ycl/common/utils/excel/core/ExcelResult.java
new file mode 100644
index 0000000..36d9c8e
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/core/ExcelResult.java
@@ -0,0 +1,26 @@
+package com.ycl.common.utils.excel.core;
+
+import java.util.List;
+
+/**
+ * excel杩斿洖瀵硅薄
+ *
+ * @author Lion Li
+ */
+public interface ExcelResult<T> {
+
+    /**
+     * 瀵硅薄鍒楄〃
+     */
+    List<T> getList();
+
+    /**
+     * 閿欒鍒楄〃
+     */
+    List<String> getErrorList();
+
+    /**
+     * 瀵煎叆鍥炴墽
+     */
+    String getAnalysis();
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/service/DictService.java b/common/src/main/java/com/ycl/common/utils/excel/service/DictService.java
new file mode 100644
index 0000000..801189e
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/service/DictService.java
@@ -0,0 +1,67 @@
+package com.ycl.common.utils.excel.service;
+
+import java.util.Map;
+
+/**
+ * 閫氱敤 瀛楀吀鏈嶅姟
+ *
+ * @author Lion Li
+ */
+public interface DictService {
+
+    /**
+     * 鍒嗛殧绗�
+     */
+    String SEPARATOR = ",";
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏稿�艰幏鍙栧瓧鍏告爣绛�
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictValue 瀛楀吀鍊�
+     * @return 瀛楀吀鏍囩
+     */
+    default String getDictLabel(String dictType, String dictValue) {
+        return getDictLabel(dictType, dictValue, SEPARATOR);
+    }
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏告爣绛捐幏鍙栧瓧鍏稿��
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictLabel 瀛楀吀鏍囩
+     * @return 瀛楀吀鍊�
+     */
+    default String getDictValue(String dictType, String dictLabel) {
+        return getDictValue(dictType, dictLabel, SEPARATOR);
+    }
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏稿�艰幏鍙栧瓧鍏告爣绛�
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictValue 瀛楀吀鍊�
+     * @param separator 鍒嗛殧绗�
+     * @return 瀛楀吀鏍囩
+     */
+    String getDictLabel(String dictType, String dictValue, String separator);
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏告爣绛捐幏鍙栧瓧鍏稿��
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictLabel 瀛楀吀鏍囩
+     * @param separator 鍒嗛殧绗�
+     * @return 瀛楀吀鍊�
+     */
+    String getDictValue(String dictType, String dictLabel, String separator);
+
+    /**
+     * 鑾峰彇瀛楀吀涓嬫墍鏈夌殑瀛楀吀鍊间笌鏍囩
+     *
+     * @param dictType 瀛楀吀绫诲瀷
+     * @return dictValue涓簁ey锛宒ictLabel涓哄�肩粍鎴愮殑Map
+     */
+    Map<String, String> getAllDictByDictType(String dictType);
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/utils/JsonUtils.java b/common/src/main/java/com/ycl/common/utils/excel/utils/JsonUtils.java
new file mode 100644
index 0000000..3303c55
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/utils/JsonUtils.java
@@ -0,0 +1,169 @@
+package com.ycl.common.utils.excel.utils;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.ycl.common.utils.spring.SpringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JSON 宸ュ叿绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JsonUtils {
+
+    private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
+
+    public static ObjectMapper getObjectMapper() {
+        return OBJECT_MAPPER;
+    }
+
+    /**
+     * 灏嗗璞¤浆鎹负JSON鏍煎紡鐨勫瓧绗︿覆
+     *
+     * @param object 瑕佽浆鎹㈢殑瀵硅薄
+     * @return JSON鏍煎紡鐨勫瓧绗︿覆锛屽鏋滃璞′负null锛屽垯杩斿洖null
+     * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烰SON澶勭悊寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+     */
+    public static String toJsonString(Object object) {
+        if (ObjectUtil.isNull(object)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.writeValueAsString(object);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓烘寚瀹氱被鍨嬬殑瀵硅薄
+     *
+     * @param text  JSON鏍煎紡鐨勫瓧绗︿覆
+     * @param clazz 瑕佽浆鎹㈢殑鐩爣瀵硅薄绫诲瀷
+     * @param <T>   鐩爣瀵硅薄鐨勬硾鍨嬬被鍨�
+     * @return 杞崲鍚庣殑瀵硅薄锛屽鏋滃瓧绗︿覆涓虹┖鍒欒繑鍥瀗ull
+     * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+     */
+    public static <T> T parseObject(String text, Class<T> clazz) {
+        if (StringUtils.isEmpty(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 灏嗗瓧鑺傛暟缁勮浆鎹负鎸囧畾绫诲瀷鐨勫璞�
+     *
+     * @param bytes 瀛楄妭鏁扮粍
+     * @param clazz 瑕佽浆鎹㈢殑鐩爣瀵硅薄绫诲瀷
+     * @param <T>   鐩爣瀵硅薄鐨勬硾鍨嬬被鍨�
+     * @return 杞崲鍚庣殑瀵硅薄锛屽鏋滃瓧鑺傛暟缁勪负绌哄垯杩斿洖null
+     * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+     */
+    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
+        if (ArrayUtil.isEmpty(bytes)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(bytes, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓烘寚瀹氱被鍨嬬殑瀵硅薄锛屾敮鎸佸鏉傜被鍨�
+     *
+     * @param text          JSON鏍煎紡鐨勫瓧绗︿覆
+     * @param typeReference 鎸囧畾绫诲瀷鐨凾ypeReference瀵硅薄
+     * @param <T>           鐩爣瀵硅薄鐨勬硾鍨嬬被鍨�
+     * @return 杞崲鍚庣殑瀵硅薄锛屽鏋滃瓧绗︿覆涓虹┖鍒欒繑鍥瀗ull
+     * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+     */
+    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, typeReference);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓篋ict瀵硅薄
+     *
+     * @param text JSON鏍煎紡鐨勫瓧绗︿覆
+     * @return 杞崲鍚庣殑Dict瀵硅薄锛屽鏋滃瓧绗︿覆涓虹┖鎴栬�呬笉鏄疛SON鏍煎紡鍒欒繑鍥瀗ull
+     * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+     */
+    public static Dict parseMap(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
+        } catch (MismatchedInputException e) {
+            // 绫诲瀷涓嶅尮閰嶈鏄庝笉鏄痡son
+            return null;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓篋ict瀵硅薄鐨勫垪琛�
+     *
+     * @param text JSON鏍煎紡鐨勫瓧绗︿覆
+     * @return 杞崲鍚庣殑Dict瀵硅薄鐨勫垪琛紝濡傛灉瀛楃涓蹭负绌哄垯杩斿洖null
+     * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+     */
+    public static List<Dict> parseArrayMap(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 灏咼SON鏍煎紡鐨勫瓧绗︿覆杞崲涓烘寚瀹氱被鍨嬪璞$殑鍒楄〃
+     *
+     * @param text  JSON鏍煎紡鐨勫瓧绗︿覆
+     * @param clazz 瑕佽浆鎹㈢殑鐩爣瀵硅薄绫诲瀷
+     * @param <T>   鐩爣瀵硅薄鐨勬硾鍨嬬被鍨�
+     * @return 杞崲鍚庣殑瀵硅薄鐨勫垪琛紝濡傛灉瀛楃涓蹭负绌哄垯杩斿洖绌哄垪琛�
+     * @throws RuntimeException 濡傛灉杞崲杩囩▼涓彂鐢烮O寮傚父锛屽垯鎶涘嚭杩愯鏃跺紓甯�
+     */
+    public static <T> List<T> parseArray(String text, Class<T> clazz) {
+        if (StringUtils.isEmpty(text)) {
+            return new ArrayList<>();
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/utils/ReflectUtils.java b/common/src/main/java/com/ycl/common/utils/excel/utils/ReflectUtils.java
new file mode 100644
index 0000000..c6bcfa9
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/utils/ReflectUtils.java
@@ -0,0 +1,55 @@
+package com.ycl.common.utils.excel.utils;
+
+import cn.hutool.core.util.ReflectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.lang.reflect.Method;
+
+/**
+ * 鍙嶅皠宸ュ叿绫�. 鎻愪緵璋冪敤getter/setter鏂规硶, 璁块棶绉佹湁鍙橀噺, 璋冪敤绉佹湁鏂规硶, 鑾峰彇娉涘瀷绫诲瀷Class, 琚獳OP杩囩殑鐪熷疄绫荤瓑宸ュ叿鍑芥暟.
+ *
+ * @author Lion Li
+ */
+@SuppressWarnings("rawtypes")
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ReflectUtils extends ReflectUtil {
+
+    private static final String SETTER_PREFIX = "set";
+
+    private static final String GETTER_PREFIX = "get";
+
+    /**
+     * 璋冪敤Getter鏂规硶.
+     * 鏀寔澶氱骇锛屽锛氬璞″悕.瀵硅薄鍚�.鏂规硶
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeGetter(Object obj, String propertyName) {
+        Object object = obj;
+        for (String name : StringUtils.split(propertyName, ".")) {
+            String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+            object = invoke(object, getterMethodName);
+        }
+        return (E) object;
+    }
+
+    /**
+     * 璋冪敤Setter鏂规硶, 浠呭尮閰嶆柟娉曞悕銆�
+     * 鏀寔澶氱骇锛屽锛氬璞″悕.瀵硅薄鍚�.鏂规硶
+     */
+    public static <E> void invokeSetter(Object obj, String propertyName, E value) {
+        Object object = obj;
+        String[] names = StringUtils.split(propertyName, ".");
+        for (int i = 0; i < names.length; i++) {
+            if (i < names.length - 1) {
+                String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+                object = invoke(object, getterMethodName);
+            } else {
+                String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+                Method method = getMethodByName(object.getClass(), setterMethodName);
+                invoke(object, method, value);
+            }
+        }
+    }
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/utils/StreamUtils.java b/common/src/main/java/com/ycl/common/utils/excel/utils/StreamUtils.java
new file mode 100644
index 0000000..968774f
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/utils/StreamUtils.java
@@ -0,0 +1,282 @@
+package com.ycl.common.utils.excel.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * stream 娴佸伐鍏风被
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StreamUtils {
+
+    /**
+     * 灏哻ollection杩囨护
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   杩囨护鏂规硶
+     * @return 杩囨护鍚庣殑list
+     */
+    public static <E> List<E> filter(Collection<E> collection, Predicate<E> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+        return collection.stream().filter(function).collect(Collectors.toList());
+    }
+
+    /**
+     * 鎵惧埌娴佷腑婊¤冻鏉′欢鐨勭涓�涓厓绱�
+     *
+     * @param collection 闇�瑕佹煡璇㈢殑闆嗗悎
+     * @param function   杩囨护鏂规硶
+     * @return 鎵惧埌绗﹀悎鏉′欢鐨勭涓�涓厓绱狅紝娌℃湁鍒欒繑鍥瀗ull
+     */
+    public static <E> E findFirst(Collection<E> collection, Predicate<E> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return null;
+        }
+        return collection.stream().filter(function).findFirst().orElse(null);
+    }
+
+    /**
+     * 鎵惧埌娴佷腑浠绘剰涓�涓弧瓒虫潯浠剁殑鍏冪礌
+     *
+     * @param collection 闇�瑕佹煡璇㈢殑闆嗗悎
+     * @param function   杩囨护鏂规硶
+     * @return 鎵惧埌绗﹀悎鏉′欢鐨勪换鎰忎竴涓厓绱狅紝娌℃湁鍒欒繑鍥瀗ull
+     */
+    public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return Optional.empty();
+        }
+        return collection.stream().filter(function).findAny();
+    }
+
+    /**
+     * 灏哻ollection鎷兼帴
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   鎷兼帴鏂规硶
+     * @return 鎷兼帴鍚庣殑list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function) {
+        return join(collection, function, StringUtils.SEPARATOR);
+    }
+
+    /**
+     * 灏哻ollection鎷兼帴
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   鎷兼帴鏂规硶
+     * @param delimiter  鎷兼帴绗�
+     * @return 鎷兼帴鍚庣殑list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
+        if (CollUtil.isEmpty(collection)) {
+            return StringUtils.EMPTY;
+        }
+        return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
+    }
+
+    /**
+     * 灏哻ollection鎺掑簭
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param comparing  鎺掑簭鏂规硶
+     * @return 鎺掑簭鍚庣殑list
+     */
+    public static <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+        return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
+    }
+
+    /**
+     * 灏哻ollection杞寲涓虹被鍨嬩笉鍙樼殑map<br>
+     * <B>{@code Collection<V>  ---->  Map<K,V>}</B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param key        V绫诲瀷杞寲涓篕绫诲瀷鐨刲ambda鏂规硶
+     * @param <V>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @return 杞寲鍚庣殑map
+     */
+    public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+    }
+
+    /**
+     * 灏咰ollection杞寲涓簃ap(value绫诲瀷涓巆ollection鐨勬硾鍨嬩笉鍚�)<br>
+     * <B>{@code Collection<E> -----> Map<K,V>  }</B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param key        E绫诲瀷杞寲涓篕绫诲瀷鐨刲ambda鏂规硶
+     * @param value      E绫诲瀷杞寲涓篤绫诲瀷鐨刲ambda鏂规硶
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @param <V>        map涓殑value绫诲瀷
+     * @return 杞寲鍚庣殑map
+     */
+    public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収瑙勫垯(姣斿鏈夌浉鍚岀殑鐝骇id)鍒嗙被鎴恗ap<br>
+     * <B>{@code Collection<E> -------> Map<K,List<E>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key        鍒嗙被鐨勮鍒�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+            .stream().filter(Objects::nonNull)
+            .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収涓や釜瑙勫垯(姣斿鏈夌浉鍚岀殑骞寸骇id,鐝骇id)鍒嗙被鎴愬弻灞俶ap<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,List<E>>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key1       绗竴涓垎绫荤殑瑙勫垯
+     * @param key2       绗簩涓垎绫荤殑瑙勫垯
+     * @param <E>        闆嗗悎鍏冪礌绫诲瀷
+     * @param <K>        绗竴涓猰ap涓殑key绫诲瀷
+     * @param <U>        绗簩涓猰ap涓殑key绫诲瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1, Function<E, U> key2) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+            .stream().filter(Objects::nonNull)
+            .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収涓や釜瑙勫垯(姣斿鏈夌浉鍚岀殑骞寸骇id,鐝骇id)鍒嗙被鎴愬弻灞俶ap<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,E>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key1       绗竴涓垎绫荤殑瑙勫垯
+     * @param key2       绗簩涓垎绫荤殑瑙勫垯
+     * @param <T>        绗竴涓猰ap涓殑key绫诲瀷
+     * @param <U>        绗簩涓猰ap涓殑key绫诲瀷
+     * @param <E>        collection涓殑娉涘瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
+        if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+            .stream().filter(Objects::nonNull)
+            .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
+    }
+
+    /**
+     * 灏哻ollection杞寲涓篖ist闆嗗悎锛屼絾鏄袱鑰呯殑娉涘瀷涓嶅悓<br>
+     * <B>{@code Collection<E>  ------>  List<T> } </B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   collection涓殑娉涘瀷杞寲涓簂ist娉涘瀷鐨刲ambda琛ㄨ揪寮�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <T>        List涓殑娉涘瀷
+     * @return 杞寲鍚庣殑list
+     */
+    public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        return collection
+            .stream()
+            .map(function)
+            .filter(Objects::nonNull)
+            // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+            .collect(Collectors.toList());
+    }
+
+    /**
+     * 灏哻ollection杞寲涓篠et闆嗗悎锛屼絾鏄袱鑰呯殑娉涘瀷涓嶅悓<br>
+     * <B>{@code Collection<E>  ------>  Set<T> } </B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   collection涓殑娉涘瀷杞寲涓簊et娉涘瀷鐨刲ambda琛ㄨ揪寮�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <T>        Set涓殑娉涘瀷
+     * @return 杞寲鍚庣殑Set
+     */
+    public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
+        if (CollUtil.isEmpty(collection) || function == null) {
+            return CollUtil.newHashSet();
+        }
+        return collection
+            .stream()
+            .map(function)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toSet());
+    }
+
+
+    /**
+     * 鍚堝苟涓や釜鐩稿悓key绫诲瀷鐨刴ap
+     *
+     * @param map1  绗竴涓渶瑕佸悎骞剁殑 map
+     * @param map2  绗簩涓渶瑕佸悎骞剁殑 map
+     * @param merge 鍚堝苟鐨刲ambda锛屽皢key  value1 value2鍚堝苟鎴愭渶缁堢殑绫诲瀷,娉ㄦ剰value鍙兘涓虹┖鐨勬儏鍐�
+     * @param <K>   map涓殑key绫诲瀷
+     * @param <X>   绗竴涓� map鐨剉alue绫诲瀷
+     * @param <Y>   绗簩涓� map鐨剉alue绫诲瀷
+     * @param <V>   鏈�缁坢ap鐨剉alue绫诲瀷
+     * @return 鍚堝苟鍚庣殑map
+     */
+    public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
+        if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
+            return MapUtil.newHashMap();
+        } else if (MapUtil.isEmpty(map1)) {
+            map1 = MapUtil.newHashMap();
+        } else if (MapUtil.isEmpty(map2)) {
+            map2 = MapUtil.newHashMap();
+        }
+        Set<K> key = new HashSet<>();
+        key.addAll(map1.keySet());
+        key.addAll(map2.keySet());
+        Map<K, V> map = new HashMap<>();
+        for (K t : key) {
+            X x = map1.get(t);
+            Y y = map2.get(t);
+            V z = merge.apply(x, y);
+            if (z != null) {
+                map.put(t, z);
+            }
+        }
+        return map;
+    }
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/utils/StringUtils.java b/common/src/main/java/com/ycl/common/utils/excel/utils/StringUtils.java
new file mode 100644
index 0000000..8cf75f8
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/utils/StringUtils.java
@@ -0,0 +1,323 @@
+package com.ycl.common.utils.excel.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.util.AntPathMatcher;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 瀛楃涓插伐鍏风被
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+    public static final String SEPARATOR = ",";
+
+    public static final String SLASH = "/";
+
+    /**
+     * 鑾峰彇鍙傛暟涓嶄负绌哄��
+     *
+     * @param str defaultValue 瑕佸垽鏂殑value
+     * @return value 杩斿洖鍊�
+     */
+    public static String blankToDefault(String str, String defaultValue) {
+        return StrUtil.blankToDefault(str, defaultValue);
+    }
+
+    /**
+     * * 鍒ゆ柇涓�涓瓧绗︿覆鏄惁涓虹┖涓�
+     *
+     * @param str String
+     * @return true锛氫负绌� false锛氶潪绌�
+     */
+    public static boolean isEmpty(String str) {
+        return StrUtil.isEmpty(str);
+    }
+
+    /**
+     * * 鍒ゆ柇涓�涓瓧绗︿覆鏄惁涓洪潪绌轰覆
+     *
+     * @param str String
+     * @return true锛氶潪绌轰覆 false锛氱┖涓�
+     */
+    public static boolean isNotEmpty(String str) {
+        return !isEmpty(str);
+    }
+
+    /**
+     * 鍘荤┖鏍�
+     */
+    public static String trim(String str) {
+        return StrUtil.trim(str);
+    }
+
+    /**
+     * 鎴彇瀛楃涓�
+     *
+     * @param str   瀛楃涓�
+     * @param start 寮�濮�
+     * @return 缁撴灉
+     */
+    public static String substring(final String str, int start) {
+        return substring(str, start, str.length());
+    }
+
+    /**
+     * 鎴彇瀛楃涓�
+     *
+     * @param str   瀛楃涓�
+     * @param start 寮�濮�
+     * @param end   缁撴潫
+     * @return 缁撴灉
+     */
+    public static String substring(final String str, int start, int end) {
+        return StrUtil.sub(str, start, end);
+    }
+
+    /**
+     * 鏍煎紡鍖栨枃鏈�, {} 琛ㄧず鍗犱綅绗�<br>
+     * 姝ゆ柟娉曞彧鏄畝鍗曞皢鍗犱綅绗� {} 鎸夌収椤哄簭鏇挎崲涓哄弬鏁�<br>
+     * 濡傛灉鎯宠緭鍑� {} 浣跨敤 \\杞箟 { 鍗冲彲锛屽鏋滄兂杈撳嚭 {} 涔嬪墠鐨� \ 浣跨敤鍙岃浆涔夌 \\\\ 鍗冲彲<br>
+     * 渚嬶細<br>
+     * 閫氬父浣跨敤锛歠ormat("this is {} for {}", "a", "b") -> this is a for b<br>
+     * 杞箟{}锛� format("this is \\{} for {}", "a", "b") -> this is {} for a<br>
+     * 杞箟\锛� format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
+     *
+     * @param template 鏂囨湰妯℃澘锛岃鏇挎崲鐨勯儴鍒嗙敤 {} 琛ㄧず
+     * @param params   鍙傛暟鍊�
+     * @return 鏍煎紡鍖栧悗鐨勬枃鏈�
+     */
+    public static String format(String template, Object... params) {
+        return StrUtil.format(template, params);
+    }
+
+    /**
+     * 鏄惁涓篽ttp(s)://寮�澶�
+     *
+     * @param link 閾炬帴
+     * @return 缁撴灉
+     */
+    public static boolean ishttp(String link) {
+        return Validator.isUrl(link);
+    }
+
+    /**
+     * 瀛楃涓茶浆set
+     *
+     * @param str 瀛楃涓�
+     * @param sep 鍒嗛殧绗�
+     * @return set闆嗗悎
+     */
+    public static Set<String> str2Set(String str, String sep) {
+        return new HashSet<>(str2List(str, sep, true, false));
+    }
+
+    /**
+     * 瀛楃涓茶浆list
+     *
+     * @param str         瀛楃涓�
+     * @param sep         鍒嗛殧绗�
+     * @param filterBlank 杩囨护绾┖鐧�
+     * @param trim        鍘绘帀棣栧熬绌虹櫧
+     * @return list闆嗗悎
+     */
+    public static List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) {
+        List<String> list = new ArrayList<>();
+        if (isEmpty(str)) {
+            return list;
+        }
+
+        // 杩囨护绌虹櫧瀛楃涓�
+        if (filterBlank && isBlank(str)) {
+            return list;
+        }
+        String[] split = str.split(sep);
+        for (String string : split) {
+            if (filterBlank && isBlank(string)) {
+                continue;
+            }
+            if (trim) {
+                string = trim(string);
+            }
+            list.add(string);
+        }
+
+        return list;
+    }
+
+    /**
+     * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀寘鍚寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆鍚屾椂涓插拷鐣ュぇ灏忓啓
+     *
+     * @param cs                  鎸囧畾瀛楃涓�
+     * @param searchCharSequences 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+     * @return 鏄惁鍖呭惈浠绘剰涓�涓瓧绗︿覆
+     */
+    public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) {
+        return StrUtil.containsAnyIgnoreCase(cs, searchCharSequences);
+    }
+
+    /**
+     * 椹煎嘲杞笅鍒掔嚎鍛藉悕
+     */
+    public static String toUnderScoreCase(String str) {
+        return StrUtil.toUnderlineCase(str);
+    }
+
+    /**
+     * 鏄惁鍖呭惈瀛楃涓�
+     *
+     * @param str  楠岃瘉瀛楃涓�
+     * @param strs 瀛楃涓茬粍
+     * @return 鍖呭惈杩斿洖true
+     */
+    public static boolean inStringIgnoreCase(String str, String... strs) {
+        return StrUtil.equalsAnyIgnoreCase(str, strs);
+    }
+
+    /**
+     * 灏嗕笅鍒掔嚎澶у啓鏂瑰紡鍛藉悕鐨勫瓧绗︿覆杞崲涓洪┘宄板紡銆傚鏋滆浆鎹㈠墠鐨勪笅鍒掔嚎澶у啓鏂瑰紡鍛藉悕鐨勫瓧绗︿覆涓虹┖锛屽垯杩斿洖绌哄瓧绗︿覆銆� 渚嬪锛欻ELLO_WORLD->HelloWorld
+     *
+     * @param name 杞崲鍓嶇殑涓嬪垝绾垮ぇ鍐欐柟寮忓懡鍚嶇殑瀛楃涓�
+     * @return 杞崲鍚庣殑椹煎嘲寮忓懡鍚嶇殑瀛楃涓�
+     */
+    public static String convertToCamelCase(String name) {
+        return StrUtil.upperFirst(StrUtil.toCamelCase(name));
+    }
+
+    /**
+     * 椹煎嘲寮忓懡鍚嶆硶 渚嬪锛歶ser_name->userName
+     */
+    public static String toCamelCase(String s) {
+        return StrUtil.toCamelCase(s);
+    }
+
+    /**
+     * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀尮閰嶆寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆
+     *
+     * @param str  鎸囧畾瀛楃涓�
+     * @param strs 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+     * @return 鏄惁鍖归厤
+     */
+    public static boolean matches(String str, List<String> strs) {
+        if (isEmpty(str) || CollUtil.isEmpty(strs)) {
+            return false;
+        }
+        for (String pattern : strs) {
+            if (isMatch(pattern, str)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 鍒ゆ柇url鏄惁涓庤鍒欓厤缃�:
+     * ? 琛ㄧず鍗曚釜瀛楃;
+     * * 琛ㄧず涓�灞傝矾寰勫唴鐨勪换鎰忓瓧绗︿覆锛屼笉鍙法灞傜骇;
+     * ** 琛ㄧず浠绘剰灞傝矾寰�;
+     *
+     * @param pattern 鍖归厤瑙勫垯
+     * @param url     闇�瑕佸尮閰嶇殑url
+     */
+    public static boolean isMatch(String pattern, String url) {
+        AntPathMatcher matcher = new AntPathMatcher();
+        return matcher.match(pattern, url);
+    }
+
+    /**
+     * 鏁板瓧宸﹁竟琛ラ綈0锛屼娇涔嬭揪鍒版寚瀹氶暱搴︺�傛敞鎰忥紝濡傛灉鏁板瓧杞崲涓哄瓧绗︿覆鍚庯紝闀垮害澶т簬size锛屽垯鍙繚鐣� 鏈�鍚巗ize涓瓧绗︺��
+     *
+     * @param num  鏁板瓧瀵硅薄
+     * @param size 瀛楃涓叉寚瀹氶暱搴�
+     * @return 杩斿洖鏁板瓧鐨勫瓧绗︿覆鏍煎紡锛岃瀛楃涓蹭负鎸囧畾闀垮害銆�
+     */
+    public static String padl(final Number num, final int size) {
+        return padl(num.toString(), size, '0');
+    }
+
+    /**
+     * 瀛楃涓插乏琛ラ綈銆傚鏋滃師濮嬪瓧绗︿覆s闀垮害澶т簬size锛屽垯鍙繚鐣欐渶鍚巗ize涓瓧绗︺��
+     *
+     * @param s    鍘熷瀛楃涓�
+     * @param size 瀛楃涓叉寚瀹氶暱搴�
+     * @param c    鐢ㄤ簬琛ラ綈鐨勫瓧绗�
+     * @return 杩斿洖鎸囧畾闀垮害鐨勫瓧绗︿覆锛岀敱鍘熷瓧绗︿覆宸﹁ˉ榻愭垨鎴彇寰楀埌銆�
+     */
+    public static String padl(final String s, final int size, final char c) {
+        final StringBuilder sb = new StringBuilder(size);
+        if (s != null) {
+            final int len = s.length();
+            if (s.length() <= size) {
+                sb.append(String.valueOf(c).repeat(size - len));
+                sb.append(s);
+            } else {
+                return s.substring(len - size, len);
+            }
+        } else {
+            sb.append(String.valueOf(c).repeat(Math.max(0, size)));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 鍒囧垎瀛楃涓�(鍒嗛殧绗﹂粯璁ら�楀彿)
+     *
+     * @param str 琚垏鍒嗙殑瀛楃涓�
+     * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+     */
+    public static List<String> splitList(String str) {
+        return splitTo(str, Convert::toStr);
+    }
+
+    /**
+     * 鍒囧垎瀛楃涓�
+     *
+     * @param str       琚垏鍒嗙殑瀛楃涓�
+     * @param separator 鍒嗛殧绗�
+     * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+     */
+    public static List<String> splitList(String str, String separator) {
+        return splitTo(str, separator, Convert::toStr);
+    }
+
+    /**
+     * 鍒囧垎瀛楃涓茶嚜瀹氫箟杞崲(鍒嗛殧绗﹂粯璁ら�楀彿)
+     *
+     * @param str    琚垏鍒嗙殑瀛楃涓�
+     * @param mapper 鑷畾涔夎浆鎹�
+     * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+     */
+    public static <T> List<T> splitTo(String str, Function<? super Object, T> mapper) {
+        return splitTo(str, SEPARATOR, mapper);
+    }
+
+    /**
+     * 鍒囧垎瀛楃涓茶嚜瀹氫箟杞崲
+     *
+     * @param str       琚垏鍒嗙殑瀛楃涓�
+     * @param separator 鍒嗛殧绗�
+     * @param mapper    鑷畾涔夎浆鎹�
+     * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+     */
+    public static <T> List<T> splitTo(String str, String separator, Function<? super Object, T> mapper) {
+        if (isBlank(str)) {
+            return new ArrayList<>(0);
+        }
+        return StrUtil.split(str, separator)
+            .stream()
+            .filter(Objects::nonNull)
+            .map(mapper)
+            .collect(Collectors.toList());
+    }
+
+}
diff --git a/common/src/main/java/com/ycl/common/utils/excel/utils/ValidatorUtils.java b/common/src/main/java/com/ycl/common/utils/excel/utils/ValidatorUtils.java
new file mode 100644
index 0000000..9ac6f62
--- /dev/null
+++ b/common/src/main/java/com/ycl/common/utils/excel/utils/ValidatorUtils.java
@@ -0,0 +1,36 @@
+package com.ycl.common.utils.excel.utils;
+
+import com.ycl.common.utils.spring.SpringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Validator;
+import java.util.Set;
+
+/**
+ * Validator 鏍¢獙妗嗘灦宸ュ叿
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ValidatorUtils {
+
+    private static final Validator VALID = SpringUtils.getBean(Validator.class);
+
+    /**
+     * 瀵圭粰瀹氬璞¤繘琛屽弬鏁版牎楠岋紝骞舵牴鎹寚瀹氱殑鏍¢獙缁勮繘琛屾牎楠�
+     *
+     * @param object 瑕佽繘琛屾牎楠岀殑瀵硅薄
+     * @param groups 鏍¢獙缁�
+     * @throws ConstraintViolationException 濡傛灉鏍¢獙涓嶉�氳繃锛屽垯鎶涘嚭鍙傛暟鏍¢獙寮傚父
+     */
+    public static <T> void validate(T object, Class<?>... groups) {
+        Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
+        if (!validate.isEmpty()) {
+            throw new ConstraintViolationException("鍙傛暟鏍¢獙寮傚父", validate);
+        }
+    }
+
+}
diff --git a/system/pom.xml b/system/pom.xml
index 18ddae0..1482ed7 100644
--- a/system/pom.xml
+++ b/system/pom.xml
@@ -22,12 +22,6 @@
             <artifactId>knife4j-spring-boot-starter</artifactId>
         </dependency>
 
-        <!-- easy excel -->
-        <dependency>
-            <groupId>com.alibaba</groupId>
-            <artifactId>easyexcel</artifactId>
-        </dependency>
-
 
         <!-- 楠岃瘉鐮� -->
         <dependency>

--
Gitblit v1.8.0