diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..25b312e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+# http://editorconfig.org
+root = true
+
+# 空格替代Tab缩进在各种编辑工具下效果一致
+[*]
+indent_style = space
+indent_size = 4
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{json,yml,yaml}]
+indent_size = 2
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/pom.xml b/pom.xml
index 6a95a85..7594b36 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,8 +28,8 @@
         <pagehelper.boot.version>1.4.6</pagehelper.boot.version>
         <pagehelper.version>5.3.3</pagehelper.version>
         <fastjson.version>2.0.34</fastjson.version>
-        <oshi.version>6.4.3</oshi.version>
-        <commons.io.version>2.11.0</commons.io.version>
+        <oshi.version>6.4.4</oshi.version>
+        <commons.io.version>2.13.0</commons.io.version>
         <commons.collections.version>3.2.2</commons.collections.version>
         <poi.version>5.2.3</poi.version>
         <easyexcel.version>3.3.2</easyexcel.version>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java
index 99a0dc6..9cbfe3e 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/CaptchaController.java
@@ -70,11 +70,6 @@ public class CaptchaController
         }
         RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
 
-//        AjaxResult ajax = AjaxResult.success();
-//        ajax.put("uuid", uuid);
-//        ajax.put("img", captcha.getImageBase64());
-//        return ajax;
-
         captchaVo.setUuid(uuid);
         captchaVo.setImg(captcha.getImageBase64());
         return AjaxResult.success(captchaVo);
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index d222cfc..cf0206c 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -118,7 +118,7 @@ pagehelper:
 # SpringDoc配置
 springdoc:
   #需要扫描的包,可以配置多个,使用逗号分割
-  packages-to-scan: com.ruoyi.demo
+  packages-to-scan: com.ruoyi
   paths-to-exclude:  #配置不包含在swagger文档中的api
     - /api/test/**
     - /api/mockito/data
@@ -145,13 +145,22 @@ springdoc:
       name: 数据小王子
       email: 738981257@qq.com
       url: https://gitee.com/dataprince/ruoyi-flex
-    components:
+  components:
       # 鉴权方式配置
-      security-schemes:
-        apiKey:
-          type: APIKEY
-          in: HEADER
-          name: ${sa-token.token-name}
+    security-schemes:
+      apiKey:
+        type: APIKEY
+        in: HEADER
+        name: ${sa-token.token-name}
+  group-configs:
+    - group: 1.演示模块
+      packages-to-scan: com.ruoyi.demo
+    - group: 2.通用模块
+      packages-to-scan: com.ruoyi.common
+    - group: 3.系统模块
+      packages-to-scan: com.ruoyi.system
+    - group: 4.代码生成模块
+      packages-to-scan: com.ruoyi.generator
 
 # 防止XSS攻击
 xss:
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelHandlerAdapter.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelHandlerAdapter.java
index ef51756..f4364e7 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelHandlerAdapter.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelHandlerAdapter.java
@@ -1,19 +1,24 @@
 package com.ruoyi.common.core.utils.poi;
 
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Workbook;
+
 /**
  * Excel数据格式处理适配器
- * 
+ *
  * @author ruoyi
  */
 public interface ExcelHandlerAdapter
 {
     /**
      * 格式化
-     * 
+     *
      * @param value 单元格数据值
      * @param args excel注解args参数组
+     * @param cell 单元格对象
+     * @param wb 工作簿对象
      *
      * @return 处理后的值
      */
-    Object format(Object value, String[] args);
+    Object format(Object value, String[] args, Cell cell, Workbook wb);
 }
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java
index 93aacdb..498d2c4 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java
@@ -88,13 +88,12 @@ import com.ruoyi.common.core.utils.reflect.ReflectUtils;
  *
  * @author ruoyi
  */
-public class ExcelUtil<T>
-{
+public class ExcelUtil<T> {
     private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
 
     public static final String FORMULA_REGEX_STR = "=|-|\\+|@";
 
-    public static final String[] FORMULA_STR = { "=", "-", "+", "@" };
+    public static final String[] FORMULA_STR = {"=", "-", "+", "@"};
 
     /**
      * 用于dictType属性数据存储,避免重复查缓存
@@ -196,8 +195,7 @@ public class ExcelUtil<T>
      */
     public String[] excludeFields;
 
-    public ExcelUtil(Class<T> clazz)
-    {
+    public ExcelUtil(Class<T> clazz) {
         this.clazz = clazz;
     }
 
@@ -207,15 +205,12 @@ public class ExcelUtil<T>
      * @param fields 列属性名 示例[单个"name"/多个"id","name"]
      * @throws Exception
      */
-    public void hideColumn(String... fields)
-    {
+    public void hideColumn(String... fields) {
         this.excludeFields = fields;
     }
 
-    public void init(List<T> list, String sheetName, String title, Excel.Type type)
-    {
-        if (list == null)
-        {
+    public void init(List<T> list, String sheetName, String title, Excel.Type type) {
+        if (list == null) {
             list = new ArrayList<T>();
         }
         this.list = list;
@@ -231,15 +226,12 @@ public class ExcelUtil<T>
     /**
      * 创建excel第一行标题
      */
-    public void createTitle()
-    {
-        if (StringUtils.isNotEmpty(title))
-        {
+    public void createTitle() {
+        if (StringUtils.isNotEmpty(title)) {
             subMergedFirstRowNum++;
             subMergedLastRowNum++;
             int titleLastCol = this.fields.size() - 1;
-            if (isSubList())
-            {
+            if (isSubList()) {
                 titleLastCol = titleLastCol + subFields.size() - 1;
             }
             Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
@@ -254,16 +246,13 @@ public class ExcelUtil<T>
     /**
      * 创建对象的子列表名称
      */
-    public void createSubHead()
-    {
-        if (isSubList())
-        {
+    public void createSubHead() {
+        if (isSubList()) {
             subMergedFirstRowNum++;
             subMergedLastRowNum++;
             Row subRow = sheet.createRow(rownum);
             int excelNum = 0;
-            for (Object[] objects : fields)
-            {
+            for (Object[] objects : fields) {
                 Excel attr = (Excel) objects[1];
                 Cell headCell1 = subRow.createCell(excelNum);
                 headCell1.setCellValue(attr.name());
@@ -272,8 +261,7 @@ public class ExcelUtil<T>
             }
             int headFirstRow = excelNum - 1;
             int headLastRow = headFirstRow + subFields.size() - 1;
-            if (headLastRow > headFirstRow)
-            {
+            if (headLastRow > headFirstRow) {
                 sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow));
             }
             rownum++;
@@ -286,20 +274,18 @@ public class ExcelUtil<T>
      * @param is 输入流
      * @return 转换后集合
      */
-    public List<T> importExcel(InputStream is) throws Exception
-    {
+    public List<T> importExcel(InputStream is) throws Exception {
         return importExcel(is, 0);
     }
 
     /**
      * 对excel表单默认第一个索引名转换成list
      *
-     * @param is 输入流
+     * @param is       输入流
      * @param titleNum 标题占用行数
      * @return 转换后集合
      */
-    public List<T> importExcel(InputStream is, int titleNum) throws Exception
-    {
+    public List<T> importExcel(InputStream is, int titleNum) throws Exception {
         return importExcel(StringUtils.EMPTY, is, titleNum);
     }
 
@@ -307,77 +293,62 @@ public class ExcelUtil<T>
      * 对excel表单指定表格索引名转换成list
      *
      * @param sheetName 表格索引名
-     * @param titleNum 标题占用行数
-     * @param is 输入流
+     * @param titleNum  标题占用行数
+     * @param is        输入流
      * @return 转换后集合
      */
-    public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
-    {
+    public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception {
         this.type = Excel.Type.IMPORT;
         this.wb = WorkbookFactory.create(is);
         List<T> list = new ArrayList<T>();
         // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet
         Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
-        if (sheet == null)
-        {
+        if (sheet == null) {
             throw new IOException("文件sheet不存在");
         }
         boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);
         Map<String, PictureData> pictures;
-        if (isXSSFWorkbook)
-        {
+        if (isXSSFWorkbook) {
             pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);
-        }
-        else
-        {
+        } else {
             pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);
         }
         // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1
         int rows = sheet.getLastRowNum();
 
-        if (rows > 0)
-        {
+        if (rows > 0) {
             // 定义一个map用于存放excel列的序号和field.
             Map<String, Integer> cellMap = new HashMap<String, Integer>();
             // 获取表头
             Row heard = sheet.getRow(titleNum);
-            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
-            {
+            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) {
                 Cell cell = heard.getCell(i);
-                if (StringUtils.isNotNull(cell))
-                {
+                if (StringUtils.isNotNull(cell)) {
                     String value = this.getCellValue(heard, i).toString();
                     cellMap.put(value, i);
-                }
-                else
-                {
+                } else {
                     cellMap.put(null, i);
                 }
             }
             // 有数据时才处理 得到类的所有field.
             List<Object[]> fields = this.getFields();
             Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
-            for (Object[] objects : fields)
-            {
+            for (Object[] objects : fields) {
                 Excel attr = (Excel) objects[1];
                 Integer column = cellMap.get(attr.name());
-                if (column != null)
-                {
+                if (column != null) {
                     fieldsMap.put(column, objects);
                 }
             }
-            for (int i = titleNum + 1; i <= rows; i++)
-            {
+            for (int i = titleNum + 1; i <= rows; i++) {
                 // 从第2行开始取数据,默认第一行是表头.
                 Row row = sheet.getRow(i);
                 // 判断当前行是否是空行
-                if (isRowEmpty(row))
-                {
+                if (isRowEmpty(row)) {
                     continue;
                 }
                 T entity = null;
-                for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet())
-                {
+                for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()) {
                     Object val = this.getCellValue(row, entry.getKey());
 
                     // 如果不存在实例则新建.
@@ -387,89 +358,52 @@ public class ExcelUtil<T>
                     Excel attr = (Excel) entry.getValue()[1];
                     // 取得类型,并根据对象类型设置值.
                     Class<?> fieldType = field.getType();
-                    if (String.class == fieldType)
-                    {
+                    if (String.class == fieldType) {
                         String s = Convert.toStr(val);
-                        if (StringUtils.endsWith(s, ".0"))
-                        {
+                        if (StringUtils.endsWith(s, ".0")) {
                             val = StringUtils.substringBefore(s, ".0");
-                        }
-                        else
-                        {
+                        } else {
                             String dateFormat = field.getAnnotation(Excel.class).dateFormat();
-                            if (StringUtils.isNotEmpty(dateFormat))
-                            {
+                            if (StringUtils.isNotEmpty(dateFormat)) {
                                 val = parseDateToStr(dateFormat, val);
-                            }
-                            else
-                            {
+                            } else {
                                 val = Convert.toStr(val);
                             }
                         }
-                    }
-                    else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
-                    {
+                    } else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) {
                         val = Convert.toInt(val);
-                    }
-                    else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
-                    {
+                    } else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) {
                         val = Convert.toLong(val);
-                    }
-                    else if (Double.TYPE == fieldType || Double.class == fieldType)
-                    {
+                    } else if (Double.TYPE == fieldType || Double.class == fieldType) {
                         val = Convert.toDouble(val);
-                    }
-                    else if (Float.TYPE == fieldType || Float.class == fieldType)
-                    {
+                    } else if (Float.TYPE == fieldType || Float.class == fieldType) {
                         val = Convert.toFloat(val);
-                    }
-                    else if (BigDecimal.class == fieldType)
-                    {
+                    } else if (BigDecimal.class == fieldType) {
                         val = Convert.toBigDecimal(val);
-                    }
-                    else if (Date.class == fieldType)
-                    {
-                        if (val instanceof String)
-                        {
+                    } else if (Date.class == fieldType) {
+                        if (val instanceof String) {
                             val = DateUtils.parseDate(val);
-                        }
-                        else if (val instanceof Double)
-                        {
+                        } else if (val instanceof Double) {
                             val = DateUtil.getJavaDate((Double) val);
                         }
-                    }
-                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
-                    {
+                    } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
                         val = Convert.toBool(val, false);
                     }
-                    if (StringUtils.isNotNull(fieldType))
-                    {
+                    if (StringUtils.isNotNull(fieldType)) {
                         String propertyName = field.getName();
-                        if (StringUtils.isNotEmpty(attr.targetAttr()))
-                        {
+                        if (StringUtils.isNotEmpty(attr.targetAttr())) {
                             propertyName = field.getName() + "." + attr.targetAttr();
-                        }
-                        else if (StringUtils.isNotEmpty(attr.readConverterExp()))
-                        {
+                        } else if (StringUtils.isNotEmpty(attr.readConverterExp())) {
                             val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
-                        }
-                        else if (StringUtils.isNotEmpty(attr.dictType()))
-                        {
+                        } else if (StringUtils.isNotEmpty(attr.dictType())) {
                             val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
-                        }
-                        else if (!attr.handler().equals(ExcelHandlerAdapter.class))
-                        {
-                            val = dataFormatHandlerAdapter(val, attr);
-                        }
-                        else if (Excel.ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures))
-                        {
+                        } else if (!attr.handler().equals(ExcelHandlerAdapter.class)) {
+                            val = dataFormatHandlerAdapter(val, attr, null);
+                        } else if (Excel.ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) {
                             PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());
-                            if (image == null)
-                            {
+                            if (image == null) {
                                 val = "";
-                            }
-                            else
-                            {
+                            } else {
                                 byte[] data = image.getData();
                                 val = FileUtils.writeImportBytes(data);
                             }
@@ -486,25 +420,23 @@ public class ExcelUtil<T>
     /**
      * 对list数据源将其里面的数据导入到excel表单
      *
-     * @param list 导出数据集合
+     * @param list      导出数据集合
      * @param sheetName 工作表的名称
      * @return 结果
      */
-    public AjaxResult exportExcel(List<T> list, String sheetName)
-    {
+    public AjaxResult exportExcel(List<T> list, String sheetName) {
         return exportExcel(list, sheetName, StringUtils.EMPTY);
     }
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
      *
-     * @param list 导出数据集合
+     * @param list      导出数据集合
      * @param sheetName 工作表的名称
-     * @param title 标题
+     * @param title     标题
      * @return 结果
      */
-    public AjaxResult exportExcel(List<T> list, String sheetName, String title)
-    {
+    public AjaxResult exportExcel(List<T> list, String sheetName, String title) {
         this.init(list, sheetName, title, Excel.Type.EXPORT);
         return exportExcel();
     }
@@ -512,27 +444,25 @@ public class ExcelUtil<T>
     /**
      * 对list数据源将其里面的数据导入到excel表单
      *
-     * @param response 返回数据
-     * @param list 导出数据集合
+     * @param response  返回数据
+     * @param list      导出数据集合
      * @param sheetName 工作表的名称
      * @return 结果
      */
-    public void exportExcel(HttpServletResponse response, List<T> list, String sheetName)
-    {
+    public void exportExcel(HttpServletResponse response, List<T> list, String sheetName) {
         exportExcel(response, list, sheetName, StringUtils.EMPTY);
     }
 
     /**
      * 对list数据源将其里面的数据导入到excel表单
      *
-     * @param response 返回数据
-     * @param list 导出数据集合
+     * @param response  返回数据
+     * @param list      导出数据集合
      * @param sheetName 工作表的名称
-     * @param title 标题
+     * @param title     标题
      * @return 结果
      */
-    public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title)
-    {
+    public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title) {
         response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
         response.setCharacterEncoding("utf-8");
         this.init(list, sheetName, title, Excel.Type.EXPORT);
@@ -545,8 +475,7 @@ public class ExcelUtil<T>
      * @param sheetName 工作表的名称
      * @return 结果
      */
-    public AjaxResult importTemplateExcel(String sheetName)
-    {
+    public AjaxResult importTemplateExcel(String sheetName) {
         return importTemplateExcel(sheetName, StringUtils.EMPTY);
     }
 
@@ -554,11 +483,10 @@ public class ExcelUtil<T>
      * 对list数据源将其里面的数据导入到excel表单
      *
      * @param sheetName 工作表的名称
-     * @param title 标题
+     * @param title     标题
      * @return 结果
      */
-    public AjaxResult importTemplateExcel(String sheetName, String title)
-    {
+    public AjaxResult importTemplateExcel(String sheetName, String title) {
         this.init(null, sheetName, title, Excel.Type.IMPORT);
         return exportExcel();
     }
@@ -569,8 +497,7 @@ public class ExcelUtil<T>
      * @param sheetName 工作表的名称
      * @return 结果
      */
-    public void importTemplateExcel(HttpServletResponse response, String sheetName)
-    {
+    public void importTemplateExcel(HttpServletResponse response, String sheetName) {
         importTemplateExcel(response, sheetName, StringUtils.EMPTY);
     }
 
@@ -578,11 +505,10 @@ public class ExcelUtil<T>
      * 对list数据源将其里面的数据导入到excel表单
      *
      * @param sheetName 工作表的名称
-     * @param title 标题
+     * @param title     标题
      * @return 结果
      */
-    public void importTemplateExcel(HttpServletResponse response, String sheetName, String title)
-    {
+    public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) {
         response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
         response.setCharacterEncoding("utf-8");
         this.init(null, sheetName, title, Excel.Type.IMPORT);
@@ -594,19 +520,13 @@ public class ExcelUtil<T>
      *
      * @return 结果
      */
-    public void exportExcel(HttpServletResponse response)
-    {
-        try
-        {
+    public void exportExcel(HttpServletResponse response) {
+        try {
             writeSheet();
             wb.write(response.getOutputStream());
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             log.error("导出Excel异常{}", e.getMessage());
-        }
-        finally
-        {
+        } finally {
             IOUtils.closeQuietly(wb);
         }
     }
@@ -616,24 +536,18 @@ public class ExcelUtil<T>
      *
      * @return 结果
      */
-    public AjaxResult exportExcel()
-    {
+    public AjaxResult exportExcel() {
         OutputStream out = null;
-        try
-        {
+        try {
             writeSheet();
             String filename = encodingFilename(sheetName);
             out = new FileOutputStream(getAbsoluteFile(filename));
             wb.write(out);
             return AjaxResult.success(filename);
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             log.error("导出Excel异常{}", e.getMessage());
             throw new UtilException("导出Excel失败,请联系网站管理员!");
-        }
-        finally
-        {
+        } finally {
             IOUtils.closeQuietly(wb);
             IOUtils.closeQuietly(out);
         }
@@ -642,37 +556,29 @@ public class ExcelUtil<T>
     /**
      * 创建写入数据到Sheet
      */
-    public void writeSheet()
-    {
+    public void writeSheet() {
         // 取出一共有多少个sheet.
         int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));
-        for (int index = 0; index < sheetNo; index++)
-        {
+        for (int index = 0; index < sheetNo; index++) {
             createSheet(sheetNo, index);
 
             // 产生一行
             Row row = sheet.createRow(rownum);
             int column = 0;
             // 写入各个字段的列头名称
-            for (Object[] os : fields)
-            {
+            for (Object[] os : fields) {
                 Field field = (Field) os[0];
                 Excel excel = (Excel) os[1];
-                if (Collection.class.isAssignableFrom(field.getType()))
-                {
-                    for (Field subField : subFields)
-                    {
+                if (Collection.class.isAssignableFrom(field.getType())) {
+                    for (Field subField : subFields) {
                         Excel subExcel = subField.getAnnotation(Excel.class);
                         this.createHeadCell(subExcel, row, column++);
                     }
-                }
-                else
-                {
+                } else {
                     this.createHeadCell(excel, row, column++);
                 }
             }
-            if (Excel.Type.EXPORT.equals(type))
-            {
+            if (Excel.Type.EXPORT.equals(type)) {
                 fillExcelData(index, row);
                 addStatisticsRow();
             }
@@ -683,55 +589,43 @@ public class ExcelUtil<T>
      * 填充excel数据
      *
      * @param index 序号
-     * @param row 单元格行
+     * @param row   单元格行
      */
     @SuppressWarnings("unchecked")
-    public void fillExcelData(int index, Row row)
-    {
+    public void fillExcelData(int index, Row row) {
         int startNo = index * sheetSize;
         int endNo = Math.min(startNo + sheetSize, list.size());
         int rowNo = (1 + rownum) - startNo;
-        for (int i = startNo; i < endNo; i++)
-        {
+        for (int i = startNo; i < endNo; i++) {
             rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo;
             row = sheet.createRow(rowNo);
             // 得到导出对象.
             T vo = (T) list.get(i);
             Collection<?> subList = null;
-            if (isSubList())
-            {
-                if (isSubListValue(vo))
-                {
+            if (isSubList()) {
+                if (isSubListValue(vo)) {
                     subList = getListCellValue(vo);
                     subMergedLastRowNum = subMergedLastRowNum + subList.size();
-                }
-                else
-                {
+                } else {
                     subMergedFirstRowNum++;
                     subMergedLastRowNum++;
                 }
             }
             int column = 0;
-            for (Object[] os : fields)
-            {
+            for (Object[] os : fields) {
                 Field field = (Field) os[0];
                 Excel excel = (Excel) os[1];
-                if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList))
-                {
+                if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)) {
                     boolean subFirst = false;
-                    for (Object obj : subList)
-                    {
-                        if (subFirst)
-                        {
+                    for (Object obj : subList) {
+                        if (subFirst) {
                             rowNo++;
                             row = sheet.createRow(rowNo);
                         }
                         List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
                         int subIndex = 0;
-                        for (Field subField : subFields)
-                        {
-                            if (subField.isAnnotationPresent(Excel.class))
-                            {
+                        for (Field subField : subFields) {
+                            if (subField.isAnnotationPresent(Excel.class)) {
                                 subField.setAccessible(true);
                                 Excel attr = subField.getAnnotation(Excel.class);
                                 this.addCell(attr, row, (T) obj, subField, column + subIndex);
@@ -741,9 +635,7 @@ public class ExcelUtil<T>
                         subFirst = true;
                     }
                     this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size();
-                }
-                else
-                {
+                } else {
                     this.addCell(excel, row, vo, field, column++);
                 }
             }
@@ -756,8 +648,7 @@ public class ExcelUtil<T>
      * @param wb 工作薄对象
      * @return 样式列表
      */
-    private Map<String, CellStyle> createStyles(Workbook wb)
-    {
+    private Map<String, CellStyle> createStyles(Workbook wb) {
         // 写入各条记录,每条记录对应excel表中的一行
         Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
         CellStyle style = wb.createCellStyle();
@@ -809,15 +700,12 @@ public class ExcelUtil<T>
      * @param wb 工作薄对象
      * @return 自定义样式列表
      */
-    private Map<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles)
-    {
+    private Map<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles) {
         Map<String, CellStyle> headerStyles = new HashMap<String, CellStyle>();
-        for (Object[] os : fields)
-        {
+        for (Object[] os : fields) {
             Excel excel = (Excel) os[1];
             String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor());
-            if (!headerStyles.containsKey(key))
-            {
+            if (!headerStyles.containsKey(key)) {
                 CellStyle style = wb.createCellStyle();
                 style.cloneStyleFrom(styles.get("data"));
                 style.setAlignment(HorizontalAlignment.CENTER);
@@ -842,15 +730,12 @@ public class ExcelUtil<T>
      * @param wb 工作薄对象
      * @return 自定义样式列表
      */
-    private Map<String, CellStyle> annotationDataStyles(Workbook wb)
-    {
+    private Map<String, CellStyle> annotationDataStyles(Workbook wb) {
         Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
-        for (Object[] os : fields)
-        {
+        for (Object[] os : fields) {
             Excel excel = (Excel) os[1];
             String key = StringUtils.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor());
-            if (!styles.containsKey(key))
-            {
+            if (!styles.containsKey(key)) {
                 CellStyle style = wb.createCellStyle();
                 style.setAlignment(excel.align());
                 style.setVerticalAlignment(VerticalAlignment.CENTER);
@@ -878,20 +763,17 @@ public class ExcelUtil<T>
     /**
      * 创建单元格
      */
-    public Cell createHeadCell(Excel attr, Row row, int column)
-    {
+    public Cell createHeadCell(Excel attr, Row row, int column) {
         // 创建列
         Cell cell = row.createCell(column);
         // 写入列信息
         cell.setCellValue(attr.name());
         setDataValidation(attr, row, column);
         cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
-        if (isSubList())
-        {
+        if (isSubList()) {
             // 填充默认样式,防止合并单元格样式失效
             sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor())));
-            if (attr.needMerge())
-            {
+            if (attr.needMerge()) {
                 sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column));
             }
         }
@@ -902,41 +784,31 @@ public class ExcelUtil<T>
      * 设置单元格信息
      *
      * @param value 单元格值
-     * @param attr 注解相关
-     * @param cell 单元格信息
+     * @param attr  注解相关
+     * @param cell  单元格信息
      */
-    public void setCellVo(Object value, Excel attr, Cell cell)
-    {
-        if (Excel.ColumnType.STRING == attr.cellType())
-        {
+    public void setCellVo(Object value, Excel attr, Cell cell) {
+        if (Excel.ColumnType.STRING == attr.cellType()) {
             String cellValue = Convert.toStr(value);
             // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。
-            if (StringUtils.startsWithAny(cellValue, FORMULA_STR))
-            {
+            if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) {
                 cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0");
             }
-            if (value instanceof Collection && StringUtils.equals("[]", cellValue))
-            {
+            if (value instanceof Collection && StringUtils.equals("[]", cellValue)) {
                 cellValue = StringUtils.EMPTY;
             }
             cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix());
-        }
-        else if (Excel.ColumnType.NUMERIC == attr.cellType())
-        {
-            if (StringUtils.isNotNull(value))
-            {
+        } else if (Excel.ColumnType.NUMERIC == attr.cellType()) {
+            if (StringUtils.isNotNull(value)) {
                 cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));
             }
-        }
-        else if (Excel.ColumnType.IMAGE == attr.cellType())
-        {
+        } else if (Excel.ColumnType.IMAGE == attr.cellType()) {
             ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1);
             String imagePath = Convert.toStr(value);
-            if (StringUtils.isNotEmpty(imagePath))
-            {
+            if (StringUtils.isNotEmpty(imagePath)) {
                 byte[] data = ImageUtils.getImage(imagePath);
                 getDrawingPatriarch(cell.getSheet()).createPicture(anchor,
-                        cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
+                    cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
             }
         }
     }
@@ -944,10 +816,8 @@ public class ExcelUtil<T>
     /**
      * 获取画布
      */
-    public static Drawing<?> getDrawingPatriarch(Sheet sheet)
-    {
-        if (sheet.getDrawingPatriarch() == null)
-        {
+    public static Drawing<?> getDrawingPatriarch(Sheet sheet) {
+        if (sheet.getDrawingPatriarch() == null) {
             sheet.createDrawingPatriarch();
         }
         return sheet.getDrawingPatriarch();
@@ -956,15 +826,11 @@ public class ExcelUtil<T>
     /**
      * 获取图片类型,设置图片插入类型
      */
-    public int getImageType(byte[] value)
-    {
+    public int getImageType(byte[] value) {
         String type = FileTypeUtils.getFileExtendName(value);
-        if ("JPG".equalsIgnoreCase(type))
-        {
+        if ("JPG".equalsIgnoreCase(type)) {
             return Workbook.PICTURE_TYPE_JPEG;
-        }
-        else if ("PNG".equalsIgnoreCase(type))
-        {
+        } else if ("PNG".equalsIgnoreCase(type)) {
             return Workbook.PICTURE_TYPE_PNG;
         }
         return Workbook.PICTURE_TYPE_JPEG;
@@ -973,26 +839,18 @@ public class ExcelUtil<T>
     /**
      * 创建表格样式
      */
-    public void setDataValidation(Excel attr, Row row, int column)
-    {
-        if (attr.name().indexOf("注:") >= 0)
-        {
+    public void setDataValidation(Excel attr, Row row, int column) {
+        if (attr.name().indexOf("注:") >= 0) {
             sheet.setColumnWidth(column, 6000);
-        }
-        else
-        {
+        } else {
             // 设置列宽
             sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
         }
-        if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0)
-        {
-            if (attr.combo().length > 15 || StringUtils.join(attr.combo()).length() > 255)
-            {
+        if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0) {
+            if (attr.combo().length > 15 || StringUtils.join(attr.combo()).length() > 255) {
                 // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到
                 setXSSFValidationWithHidden(sheet, attr.combo(), attr.prompt(), 1, 100, column, column);
-            }
-            else
-            {
+            } else {
                 // 提示信息或只能选择不能输入的列内容.
                 setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column);
             }
@@ -1002,20 +860,16 @@ public class ExcelUtil<T>
     /**
      * 添加单元格
      */
-    public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
-    {
+    public Cell addCell(Excel attr, Row row, T vo, Field field, int column) {
         Cell cell = null;
-        try
-        {
+        try {
             // 设置行高
             row.setHeight(maxHeight);
             // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
-            if (attr.isExport())
-            {
+            if (attr.isExport()) {
                 // 创建cell
                 cell = row.createCell(column);
-                if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge())
-                {
+                if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) {
                     CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column);
                     sheet.addMergedRegion(cellAddress);
                 }
@@ -1027,41 +881,27 @@ public class ExcelUtil<T>
                 String readConverterExp = attr.readConverterExp();
                 String separator = attr.separator();
                 String dictType = attr.dictType();
-                if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
-                {
+                if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) {
                     cell.setCellValue(parseDateToStr(dateFormat, value));
-                }
-                else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value))
-                {
+                } else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) {
                     cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));
-                }
-                else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value))
-                {
-                    if (!sysDictMap.containsKey(dictType + value))
-                    {
+                } else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) {
+                    if (!sysDictMap.containsKey(dictType + value)) {
                         String lable = convertDictByExp(Convert.toStr(value), dictType, separator);
                         sysDictMap.put(dictType + value, lable);
                     }
                     cell.setCellValue(sysDictMap.get(dictType + value));
-                }
-                else if (value instanceof BigDecimal && -1 != attr.scale())
-                {
+                } else if (value instanceof BigDecimal && -1 != attr.scale()) {
                     cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());
-                }
-                else if (!attr.handler().equals(ExcelHandlerAdapter.class))
-                {
-                    cell.setCellValue(dataFormatHandlerAdapter(value, attr));
-                }
-                else
-                {
+                } else if (!attr.handler().equals(ExcelHandlerAdapter.class)) {
+                    cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell));
+                } else {
                     // 设置列类型
                     setCellVo(value, attr, cell);
                 }
                 addStatisticsData(column, Convert.toStr(value), attr);
             }
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             log.error("导出Excel失败{}", e);
         }
         return cell;
@@ -1070,35 +910,30 @@ public class ExcelUtil<T>
     /**
      * 设置 POI XSSFSheet 单元格提示或选择框
      *
-     * @param sheet 表单
-     * @param textlist 下拉框显示的内容
+     * @param sheet         表单
+     * @param textlist      下拉框显示的内容
      * @param promptContent 提示内容
-     * @param firstRow 开始行
-     * @param endRow 结束行
-     * @param firstCol 开始列
-     * @param endCol 结束列
+     * @param firstRow      开始行
+     * @param endRow        结束行
+     * @param firstCol      开始列
+     * @param endCol        结束列
      */
     public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow,
-            int firstCol, int endCol)
-    {
+                                      int firstCol, int endCol) {
         DataValidationHelper helper = sheet.getDataValidationHelper();
         DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1");
         CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
         DataValidation dataValidation = helper.createValidation(constraint, regions);
-        if (StringUtils.isNotEmpty(promptContent))
-        {
+        if (StringUtils.isNotEmpty(promptContent)) {
             // 如果设置了提示信息则鼠标放上去提示
             dataValidation.createPromptBox("", promptContent);
             dataValidation.setShowPromptBox(true);
         }
         // 处理Excel兼容性问题
-        if (dataValidation instanceof XSSFDataValidation)
-        {
+        if (dataValidation instanceof XSSFDataValidation) {
             dataValidation.setSuppressDropDownArrow(true);
             dataValidation.setShowErrorBox(true);
-        }
-        else
-        {
+        } else {
             dataValidation.setSuppressDropDownArrow(false);
         }
         sheet.addValidationData(dataValidation);
@@ -1107,20 +942,18 @@ public class ExcelUtil<T>
     /**
      * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框).
      *
-     * @param sheet 要设置的sheet.
-     * @param textlist 下拉框显示的内容
+     * @param sheet         要设置的sheet.
+     * @param textlist      下拉框显示的内容
      * @param promptContent 提示内容
-     * @param firstRow 开始行
-     * @param endRow 结束行
-     * @param firstCol 开始列
-     * @param endCol 结束列
+     * @param firstRow      开始行
+     * @param endRow        结束行
+     * @param firstCol      开始列
+     * @param endCol        结束列
      */
-    public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol)
-    {
+    public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) {
         String hideSheetName = "combo_" + firstCol + "_" + endCol;
         Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据
-        for (int i = 0; i < textlist.length; i++)
-        {
+        for (int i = 0; i < textlist.length; i++) {
             hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]);
         }
         // 创建名称,可被其他单元格引用
@@ -1134,20 +967,16 @@ public class ExcelUtil<T>
         CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
         // 数据有效性对象
         DataValidation dataValidation = helper.createValidation(constraint, regions);
-        if (StringUtils.isNotEmpty(promptContent))
-        {
+        if (StringUtils.isNotEmpty(promptContent)) {
             // 如果设置了提示信息则鼠标放上去提示
             dataValidation.createPromptBox("", promptContent);
             dataValidation.setShowPromptBox(true);
         }
         // 处理Excel兼容性问题
-        if (dataValidation instanceof XSSFDataValidation)
-        {
+        if (dataValidation instanceof XSSFDataValidation) {
             dataValidation.setSuppressDropDownArrow(true);
             dataValidation.setShowErrorBox(true);
-        }
-        else
-        {
+        } else {
             dataValidation.setSuppressDropDownArrow(false);
         }
 
@@ -1160,32 +989,24 @@ public class ExcelUtil<T>
      * 解析导出值 0=男,1=女,2=未知
      *
      * @param propertyValue 参数值
-     * @param converterExp 翻译注解
-     * @param separator 分隔符
+     * @param converterExp  翻译注解
+     * @param separator     分隔符
      * @return 解析后值
      */
-    public static String convertByExp(String propertyValue, String converterExp, String separator)
-    {
+    public static String convertByExp(String propertyValue, String converterExp, String separator) {
         StringBuilder propertyString = new StringBuilder();
         String[] convertSource = converterExp.split(",");
-        for (String item : convertSource)
-        {
+        for (String item : convertSource) {
             String[] itemArray = item.split("=");
-            if (StringUtils.containsAny(propertyValue, separator))
-            {
-                for (String value : propertyValue.split(separator))
-                {
-                    if (itemArray[0].equals(value))
-                    {
+            if (StringUtils.containsAny(propertyValue, separator)) {
+                for (String value : propertyValue.split(separator)) {
+                    if (itemArray[0].equals(value)) {
                         propertyString.append(itemArray[1] + separator);
                         break;
                     }
                 }
-            }
-            else
-            {
-                if (itemArray[0].equals(propertyValue))
-                {
+            } else {
+                if (itemArray[0].equals(propertyValue)) {
                     return itemArray[1];
                 }
             }
@@ -1197,32 +1018,24 @@ public class ExcelUtil<T>
      * 反向解析值 男=0,女=1,未知=2
      *
      * @param propertyValue 参数值
-     * @param converterExp 翻译注解
-     * @param separator 分隔符
+     * @param converterExp  翻译注解
+     * @param separator     分隔符
      * @return 解析后值
      */
-    public static String reverseByExp(String propertyValue, String converterExp, String separator)
-    {
+    public static String reverseByExp(String propertyValue, String converterExp, String separator) {
         StringBuilder propertyString = new StringBuilder();
         String[] convertSource = converterExp.split(",");
-        for (String item : convertSource)
-        {
+        for (String item : convertSource) {
             String[] itemArray = item.split("=");
-            if (StringUtils.containsAny(propertyValue, separator))
-            {
-                for (String value : propertyValue.split(separator))
-                {
-                    if (itemArray[1].equals(value))
-                    {
+            if (StringUtils.containsAny(propertyValue, separator)) {
+                for (String value : propertyValue.split(separator)) {
+                    if (itemArray[1].equals(value)) {
                         propertyString.append(itemArray[0] + separator);
                         break;
                     }
                 }
-            }
-            else
-            {
-                if (itemArray[1].equals(propertyValue))
-                {
+            } else {
+                if (itemArray[1].equals(propertyValue)) {
                     return itemArray[0];
                 }
             }
@@ -1234,12 +1047,11 @@ public class ExcelUtil<T>
      * 解析字典值
      *
      * @param dictValue 字典值
-     * @param dictType 字典类型
+     * @param dictType  字典类型
      * @param separator 分隔符
      * @return 字典标签
      */
-    public static String convertDictByExp(String dictValue, String dictType, String separator)
-    {
+    public static String convertDictByExp(String dictValue, String dictType, String separator) {
         //return DictUtils.getDictLabel(dictType, dictValue, separator);
         return "";
     }
@@ -1248,12 +1060,11 @@ public class ExcelUtil<T>
      * 反向解析值字典值
      *
      * @param dictLabel 字典标签
-     * @param dictType 字典类型
+     * @param dictType  字典类型
      * @param separator 分隔符
      * @return 字典值
      */
-    public static String reverseDictByExp(String dictLabel, String dictType, String separator)
-    {
+    public static String reverseDictByExp(String dictLabel, String dictType, String separator) {
         //return DictUtils.getDictValue(dictType, dictLabel, separator);
         return "";
     }
@@ -1265,16 +1076,12 @@ public class ExcelUtil<T>
      * @param excel 数据注解
      * @return
      */
-    public String dataFormatHandlerAdapter(Object value, Excel excel)
-    {
-        try
-        {
+    public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) {
+        try {
             Object instance = excel.handler().newInstance();
-            Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class });
-            value = formatMethod.invoke(instance, value, excel.args());
-        }
-        catch (Exception e)
-        {
+            Method formatMethod = excel.handler().getMethod("format", new Class[]{Object.class, String[].class, Cell.class, Workbook.class});
+            value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb);
+        } catch (Exception e) {
             log.error("不能格式化数据 " + excel.handler(), e.getMessage());
         }
         return Convert.toStr(value);
@@ -1283,21 +1090,15 @@ public class ExcelUtil<T>
     /**
      * 合计统计信息
      */
-    private void addStatisticsData(Integer index, String text, Excel entity)
-    {
-        if (entity != null && entity.isStatistics())
-        {
+    private void addStatisticsData(Integer index, String text, Excel entity) {
+        if (entity != null && entity.isStatistics()) {
             Double temp = 0D;
-            if (!statistics.containsKey(index))
-            {
+            if (!statistics.containsKey(index)) {
                 statistics.put(index, temp);
             }
-            try
-            {
+            try {
                 temp = Double.valueOf(text);
-            }
-            catch (NumberFormatException e)
-            {
+            } catch (NumberFormatException e) {
             }
             statistics.put(index, statistics.get(index) + temp);
         }
@@ -1306,18 +1107,15 @@ public class ExcelUtil<T>
     /**
      * 创建统计行
      */
-    public void addStatisticsRow()
-    {
-        if (statistics.size() > 0)
-        {
+    public void addStatisticsRow() {
+        if (statistics.size() > 0) {
             Row row = sheet.createRow(sheet.getLastRowNum() + 1);
             Set<Integer> keys = statistics.keySet();
             Cell cell = row.createCell(0);
             cell.setCellStyle(styles.get("total"));
             cell.setCellValue("合计");
 
-            for (Integer key : keys)
-            {
+            for (Integer key : keys) {
                 cell = row.createCell(key);
                 cell.setCellStyle(styles.get("total"));
                 cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
@@ -1329,8 +1127,7 @@ public class ExcelUtil<T>
     /**
      * 编码文件名
      */
-    public String encodingFilename(String filename)
-    {
+    public String encodingFilename(String filename) {
         filename = UUID.randomUUID() + "_" + filename + ".xlsx";
         return filename;
     }
@@ -1340,12 +1137,10 @@ public class ExcelUtil<T>
      *
      * @param filename 文件名称
      */
-    public String getAbsoluteFile(String filename)
-    {
+    public String getAbsoluteFile(String filename) {
         String downloadPath = RuoYiConfig.getDownloadPath() + filename;
         File desc = new File(downloadPath);
-        if (!desc.getParentFile().exists())
-        {
+        if (!desc.getParentFile().exists()) {
             desc.getParentFile().mkdirs();
         }
         return downloadPath;
@@ -1354,28 +1149,22 @@ public class ExcelUtil<T>
     /**
      * 获取bean中的属性值
      *
-     * @param vo 实体对象
+     * @param vo    实体对象
      * @param field 字段
      * @param excel 注解
      * @return 最终的属性值
      * @throws Exception
      */
-    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
-    {
+    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception {
         Object o = field.get(vo);
-        if (StringUtils.isNotEmpty(excel.targetAttr()))
-        {
+        if (StringUtils.isNotEmpty(excel.targetAttr())) {
             String target = excel.targetAttr();
-            if (target.contains("."))
-            {
+            if (target.contains(".")) {
                 String[] targets = target.split("[.]");
-                for (String name : targets)
-                {
+                for (String name : targets) {
                     o = getValue(o, name);
                 }
-            }
-            else
-            {
+            } else {
                 o = getValue(o, target);
             }
         }
@@ -1390,10 +1179,8 @@ public class ExcelUtil<T>
      * @return value
      * @throws Exception
      */
-    private Object getValue(Object o, String name) throws Exception
-    {
-        if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name))
-        {
+    private Object getValue(Object o, String name) throws Exception {
+        if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) {
             Class<?> clazz = o.getClass();
             Field field = clazz.getDeclaredField(name);
             field.setAccessible(true);
@@ -1405,8 +1192,7 @@ public class ExcelUtil<T>
     /**
      * 得到所有定义字段
      */
-    private void createExcelField()
-    {
+    private void createExcelField() {
         this.fields = getFields();
         this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
         this.maxHeight = getRowHeight();
@@ -1415,27 +1201,21 @@ public class ExcelUtil<T>
     /**
      * 获取字段注解信息
      */
-    public List<Object[]> getFields()
-    {
+    public List<Object[]> getFields() {
         List<Object[]> fields = new ArrayList<Object[]>();
         List<Field> tempFields = new ArrayList<>();
         tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
         tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
-        for (Field field : tempFields)
-        {
-            if (!ArrayUtils.contains(this.excludeFields, field.getName()))
-            {
+        for (Field field : tempFields) {
+            if (!ArrayUtils.contains(this.excludeFields, field.getName())) {
                 // 单注解
-                if (field.isAnnotationPresent(Excel.class))
-                {
+                if (field.isAnnotationPresent(Excel.class)) {
                     Excel attr = field.getAnnotation(Excel.class);
-                    if (attr != null && (attr.type() == Excel.Type.ALL || attr.type() == type))
-                    {
+                    if (attr != null && (attr.type() == Excel.Type.ALL || attr.type() == type)) {
                         field.setAccessible(true);
-                        fields.add(new Object[] { field, attr });
+                        fields.add(new Object[]{field, attr});
                     }
-                    if (Collection.class.isAssignableFrom(field.getType()))
-                    {
+                    if (Collection.class.isAssignableFrom(field.getType())) {
                         subMethod = getSubMethod(field.getName(), clazz);
                         ParameterizedType pt = (ParameterizedType) field.getGenericType();
                         Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
@@ -1444,17 +1224,14 @@ public class ExcelUtil<T>
                 }
 
                 // 多注解
-                if (field.isAnnotationPresent(Excels.class))
-                {
+                if (field.isAnnotationPresent(Excels.class)) {
                     Excels attrs = field.getAnnotation(Excels.class);
                     Excel[] excels = attrs.value();
-                    for (Excel attr : excels)
-                    {
+                    for (Excel attr : excels) {
                         if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr())
-                                && (attr != null && (attr.type() == Excel.Type.ALL || attr.type() == type)))
-                        {
+                            && (attr != null && (attr.type() == Excel.Type.ALL || attr.type() == type))) {
                             field.setAccessible(true);
-                            fields.add(new Object[] { field, attr });
+                            fields.add(new Object[]{field, attr});
                         }
                     }
                 }
@@ -1466,11 +1243,9 @@ public class ExcelUtil<T>
     /**
      * 根据注解获取最大行高
      */
-    public short getRowHeight()
-    {
+    public short getRowHeight() {
         double maxHeight = 0;
-        for (Object[] os : this.fields)
-        {
+        for (Object[] os : this.fields) {
             Excel excel = (Excel) os[1];
             maxHeight = Math.max(maxHeight, excel.height());
         }
@@ -1480,8 +1255,7 @@ public class ExcelUtil<T>
     /**
      * 创建一个工作簿
      */
-    public void createWorkbook()
-    {
+    public void createWorkbook() {
         this.wb = new SXSSFWorkbook(500);
         this.sheet = wb.createSheet();
         wb.setSheetName(0, sheetName);
@@ -1492,13 +1266,11 @@ public class ExcelUtil<T>
      * 创建工作表
      *
      * @param sheetNo sheet数量
-     * @param index 序号
+     * @param index   序号
      */
-    public void createSheet(int sheetNo, int index)
-    {
+    public void createSheet(int sheetNo, int index) {
         // 设置工作表的名称.
-        if (sheetNo > 1 && index > 0)
-        {
+        if (sheetNo > 1 && index > 0) {
             this.sheet = wb.createSheet();
             this.createTitle();
             wb.setSheetName(index, sheetName + index);
@@ -1508,58 +1280,39 @@ public class ExcelUtil<T>
     /**
      * 获取单元格值
      *
-     * @param row 获取的行
+     * @param row    获取的行
      * @param column 获取单元格列号
      * @return 单元格值
      */
-    public Object getCellValue(Row row, int column)
-    {
-        if (row == null)
-        {
+    public Object getCellValue(Row row, int column) {
+        if (row == null) {
             return row;
         }
         Object val = "";
-        try
-        {
+        try {
             Cell cell = row.getCell(column);
-            if (StringUtils.isNotNull(cell))
-            {
-                if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA)
-                {
+            if (StringUtils.isNotNull(cell)) {
+                if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) {
                     val = cell.getNumericCellValue();
-                    if (DateUtil.isCellDateFormatted(cell))
-                    {
+                    if (DateUtil.isCellDateFormatted(cell)) {
                         val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换
-                    }
-                    else
-                    {
-                        if ((Double) val % 1 != 0)
-                        {
+                    } else {
+                        if ((Double) val % 1 != 0) {
                             val = new BigDecimal(val.toString());
-                        }
-                        else
-                        {
+                        } else {
                             val = new DecimalFormat("0").format(val);
                         }
                     }
-                }
-                else if (cell.getCellType() == CellType.STRING)
-                {
+                } else if (cell.getCellType() == CellType.STRING) {
                     val = cell.getStringCellValue();
-                }
-                else if (cell.getCellType() == CellType.BOOLEAN)
-                {
+                } else if (cell.getCellType() == CellType.BOOLEAN) {
                     val = cell.getBooleanCellValue();
-                }
-                else if (cell.getCellType() == CellType.ERROR)
-                {
+                } else if (cell.getCellType() == CellType.ERROR) {
                     val = cell.getErrorCellValue();
                 }
 
             }
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             return val;
         }
         return val;
@@ -1571,17 +1324,13 @@ public class ExcelUtil<T>
      * @param row 判断的行
      * @return
      */
-    private boolean isRowEmpty(Row row)
-    {
-        if (row == null)
-        {
+    private boolean isRowEmpty(Row row) {
+        if (row == null) {
             return true;
         }
-        for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++)
-        {
+        for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) {
             Cell cell = row.getCell(i);
-            if (cell != null && cell.getCellType() != CellType.BLANK)
-            {
+            if (cell != null && cell.getCellType() != CellType.BLANK) {
                 return false;
             }
         }
@@ -1591,21 +1340,17 @@ public class ExcelUtil<T>
     /**
      * 获取Excel2003图片
      *
-     * @param sheet 当前sheet对象
+     * @param sheet    当前sheet对象
      * @param workbook 工作簿对象
      * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData
      */
-    public static Map<String, PictureData> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook)
-    {
+    public static Map<String, PictureData> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) {
         Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>();
         List<HSSFPictureData> pictures = workbook.getAllPictures();
-        if (!pictures.isEmpty())
-        {
-            for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren())
-            {
+        if (!pictures.isEmpty()) {
+            for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) {
                 HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor();
-                if (shape instanceof HSSFPicture)
-                {
+                if (shape instanceof HSSFPicture) {
                     HSSFPicture pic = (HSSFPicture) shape;
                     int pictureIndex = pic.getPictureIndex() - 1;
                     HSSFPictureData picData = pictures.get(pictureIndex);
@@ -1614,9 +1359,7 @@ public class ExcelUtil<T>
                 }
             }
             return sheetIndexPicMap;
-        }
-        else
-        {
+        } else {
             return sheetIndexPicMap;
         }
     }
@@ -1624,23 +1367,18 @@ public class ExcelUtil<T>
     /**
      * 获取Excel2007图片
      *
-     * @param sheet 当前sheet对象
+     * @param sheet    当前sheet对象
      * @param workbook 工作簿对象
      * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData
      */
-    public static Map<String, PictureData> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook)
-    {
+    public static Map<String, PictureData> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) {
         Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>();
-        for (POIXMLDocumentPart dr : sheet.getRelations())
-        {
-            if (dr instanceof XSSFDrawing)
-            {
+        for (POIXMLDocumentPart dr : sheet.getRelations()) {
+            if (dr instanceof XSSFDrawing) {
                 XSSFDrawing drawing = (XSSFDrawing) dr;
                 List<XSSFShape> shapes = drawing.getShapes();
-                for (XSSFShape shape : shapes)
-                {
-                    if (shape instanceof XSSFPicture)
-                    {
+                for (XSSFShape shape : shapes) {
+                    if (shape instanceof XSSFPicture) {
                         XSSFPicture pic = (XSSFPicture) shape;
                         XSSFClientAnchor anchor = pic.getPreferredSize();
                         CTMarker ctMarker = anchor.getFrom();
@@ -1657,30 +1395,21 @@ public class ExcelUtil<T>
      * 格式化不同类型的日期对象
      *
      * @param dateFormat 日期格式
-     * @param val 被格式化的日期对象
+     * @param val        被格式化的日期对象
      * @return 格式化后的日期字符
      */
-    public String parseDateToStr(String dateFormat, Object val)
-    {
-        if (val == null)
-        {
+    public String parseDateToStr(String dateFormat, Object val) {
+        if (val == null) {
             return "";
         }
         String str;
-        if (val instanceof Date)
-        {
+        if (val instanceof Date) {
             str = DateUtils.parseDateToStr(dateFormat, (Date) val);
-        }
-        else if (val instanceof LocalDateTime)
-        {
+        } else if (val instanceof LocalDateTime) {
             str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val));
-        }
-        else if (val instanceof LocalDate)
-        {
+        } else if (val instanceof LocalDate) {
             str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val));
-        }
-        else
-        {
+        } else {
             str = val.toString();
         }
         return str;
@@ -1689,31 +1418,25 @@ public class ExcelUtil<T>
     /**
      * 是否有对象的子列表
      */
-    public boolean isSubList()
-    {
+    public boolean isSubList() {
         return StringUtils.isNotNull(subFields) && subFields.size() > 0;
     }
 
     /**
      * 是否有对象的子列表,集合不为空
      */
-    public boolean isSubListValue(T vo)
-    {
+    public boolean isSubListValue(T vo) {
         return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0;
     }
 
     /**
      * 获取集合的值
      */
-    public Collection<?> getListCellValue(Object obj)
-    {
+    public Collection<?> getListCellValue(Object obj) {
         Object value;
-        try
-        {
-            value = subMethod.invoke(obj, new Object[] {});
-        }
-        catch (Exception e)
-        {
+        try {
+            value = subMethod.invoke(obj, new Object[]{});
+        } catch (Exception e) {
             return new ArrayList<Object>();
         }
         return (Collection<?>) value;
@@ -1722,22 +1445,18 @@ public class ExcelUtil<T>
     /**
      * 获取对象的子列表方法
      *
-     * @param name 名称
+     * @param name      名称
      * @param pojoClass 类对象
      * @return 子列表方法
      */
-    public Method getSubMethod(String name, Class<?> pojoClass)
-    {
+    public Method getSubMethod(String name, Class<?> pojoClass) {
         StringBuffer getMethodName = new StringBuffer("get");
         getMethodName.append(name.substring(0, 1).toUpperCase());
         getMethodName.append(name.substring(1));
         Method method = null;
-        try
-        {
-            method = pojoClass.getMethod(getMethodName.toString(), new Class[] {});
-        }
-        catch (Exception e)
-        {
+        try {
+            method = pojoClass.getMethod(getMethodName.toString(), new Class[]{});
+        } catch (Exception e) {
             log.error("获取对象异常{}", e.getMessage());
         }
         return method;
diff --git a/ruoyi-common/ruoyi-common-springdoc/pom.xml b/ruoyi-common/ruoyi-common-springdoc/pom.xml
index b5a6d90..08f75de 100644
--- a/ruoyi-common/ruoyi-common-springdoc/pom.xml
+++ b/ruoyi-common/ruoyi-common-springdoc/pom.xml
@@ -17,6 +17,10 @@
     </description>
 
     <dependencies>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
         <!-- springdoc -->
         <dependency>
             <groupId>org.springdoc</groupId>
diff --git a/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/security/SpringDocConfig.java b/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/security/SpringDocConfig.java
deleted file mode 100644
index 5e33463..0000000
--- a/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/security/SpringDocConfig.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.ruoyi.common.security;
-
-import io.swagger.v3.oas.models.Components;
-import io.swagger.v3.oas.models.ExternalDocumentation;
-import io.swagger.v3.oas.models.OpenAPI;
-import io.swagger.v3.oas.models.info.Info;
-import io.swagger.v3.oas.models.info.License;
-import io.swagger.v3.oas.models.security.SecurityRequirement;
-import io.swagger.v3.oas.models.security.SecurityScheme;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * SpringDoc API文档相关配置
- * @author  数据小王子 on 2023/07/24.
- */
-@Configuration
-public class SpringDocConfig {
-    @Bean
-    public OpenAPI openAPI() {
-        SecurityRequirement securityRequirement = new SecurityRequirement().addList("Authorization"); // 名字和创建的SecuritySchemes一致
-        List<SecurityRequirement> list = new ArrayList<>();
-        list.add(securityRequirement);
-        return new OpenAPI()
-                .components(new Components().addSecuritySchemes("Authorization", // key值
-                        new SecurityScheme()
-                                .type(SecurityScheme.Type.APIKEY) //请求认证类型
-                                .scheme("bearer").bearerFormat("JWT")
-                                .name("Authorization") //API key参数名
-                                .description("token令牌") //API key描述
-                                .in(SecurityScheme.In.HEADER))) //设置API key的存放位置
-                .security(list)
-                .info(new Info().title("Ruoyi-Flex API Doc")
-                        .description("Ruoyi-Flex SrpingDoc demo")
-                        .version("v4.1.2")
-                        .license(new License().name("MIT").url("https://opensource.org/licenses/MIT")))
-                .externalDocs(new ExternalDocumentation()
-                        .description("Ruoyi-Flex Wiki Documentation")
-                        .url("https://gitee.com/dataprince/ruoyi-flex"));
-    }
-
-}
diff --git a/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/config/SpringDocConfig.java b/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/config/SpringDocConfig.java
new file mode 100644
index 0000000..c143c9e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/config/SpringDocConfig.java
@@ -0,0 +1,128 @@
+package com.ruoyi.common.springdoc.config;
+
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.springdoc.handler.OpenApiHandler;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import lombok.RequiredArgsConstructor;
+import com.ruoyi.common.springdoc.config.properties.SpringDocProperties;
+import org.springdoc.core.configuration.SpringDocConfiguration;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.SecurityService;
+import org.springdoc.core.utils.PropertyResolverUtils;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * SpringDoc API文档相关配置
+ * @author  Lion Li
+ */
+@RequiredArgsConstructor
+@AutoConfiguration(before = SpringDocConfiguration.class)
+@EnableConfigurationProperties(SpringDocProperties.class)
+@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
+@Configuration
+public class SpringDocConfig {
+
+    private final ServerProperties serverProperties;
+
+    @Bean
+    @ConditionalOnMissingBean(OpenAPI.class)
+    public OpenAPI openApi(SpringDocProperties properties) {
+        OpenAPI openApi = new OpenAPI();
+        // 文档基本信息
+        SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
+        Info info = convertInfo(infoProperties);
+        openApi.info(info);
+        // 扩展文档信息
+        openApi.externalDocs(properties.getExternalDocs());
+        openApi.tags(properties.getTags());
+        openApi.paths(properties.getPaths());
+        openApi.components(properties.getComponents());
+        Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
+        List<SecurityRequirement> list = new ArrayList<>();
+        SecurityRequirement securityRequirement = new SecurityRequirement();
+        keySet.forEach(securityRequirement::addList);
+        list.add(securityRequirement);
+        openApi.security(list);
+
+        return openApi;
+    }
+
+    private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
+        Info info = new Info();
+        info.setTitle(infoProperties.getTitle());
+        info.setDescription(infoProperties.getDescription());
+        info.setContact(infoProperties.getContact());
+        info.setLicense(infoProperties.getLicense());
+        info.setVersion(infoProperties.getVersion());
+        return info;
+    }
+
+    /**
+     * 自定义 openapi 处理器
+     */
+    @Bean
+    public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
+                                         SecurityService securityParser,
+                                         SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+                                         Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
+                                         Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
+        return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
+    }
+
+    /**
+     * 对已经生成好的 OpenApi 进行自定义操作
+     */
+    @Bean
+    public OpenApiCustomizer openApiCustomizer() {
+        String contextPath = serverProperties.getServlet().getContextPath();
+        String finalContextPath;
+        if (StringUtils.isBlank(contextPath) || "/".equals(contextPath)) {
+            finalContextPath = "";
+        } else {
+            finalContextPath = contextPath;
+        }
+        // 对所有路径增加前置上下文路径
+        return openApi -> {
+            Paths oldPaths = openApi.getPaths();
+            if (oldPaths instanceof PlusPaths) {
+                return;
+            }
+            PlusPaths newPaths = new PlusPaths();
+            oldPaths.forEach((k, v) -> newPaths.addPathItem(finalContextPath + k, v));
+            openApi.setPaths(newPaths);
+        };
+    }
+
+    /**
+     * 单独使用一个类便于判断 解决springdoc路径拼接重复问题
+     *
+     * @author Lion Li
+     */
+    static class PlusPaths extends Paths {
+
+        public PlusPaths() {
+            super();
+        }
+    }
+
+
+}
diff --git a/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/config/properties/SpringDocProperties.java b/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/config/properties/SpringDocProperties.java
new file mode 100644
index 0000000..c26575a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/config/properties/SpringDocProperties.java
@@ -0,0 +1,94 @@
+package com.ruoyi.common.springdoc.config.properties;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+
+import java.util.List;
+
+/**
+ * swagger 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "springdoc")
+public class SpringDocProperties {
+
+    /**
+     * 文档基本信息
+     */
+    @NestedConfigurationProperty
+    private InfoProperties info = new InfoProperties();
+
+    /**
+     * 扩展文档地址
+     */
+    @NestedConfigurationProperty
+    private ExternalDocumentation externalDocs;
+
+    /**
+     * 标签
+     */
+    private List<Tag> tags = null;
+
+    /**
+     * 路径
+     */
+    @NestedConfigurationProperty
+    private Paths paths = null;
+
+    /**
+     * 组件
+     */
+    @NestedConfigurationProperty
+    private Components components = null;
+
+    /**
+     * <p>
+     * 文档的基础属性信息
+     * </p>
+     *
+     * @see io.swagger.v3.oas.models.info.Info
+     *
+     * 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来
+     */
+    @Data
+    public static class InfoProperties {
+
+        /**
+         * 标题
+         */
+        private String title = null;
+
+        /**
+         * 描述
+         */
+        private String description = null;
+
+        /**
+         * 联系人信息
+         */
+        @NestedConfigurationProperty
+        private Contact contact = null;
+
+        /**
+         * 许可证
+         */
+        @NestedConfigurationProperty
+        private License license = null;
+
+        /**
+         * 版本
+         */
+        private String version = null;
+
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/handler/OpenApiHandler.java b/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/handler/OpenApiHandler.java
new file mode 100644
index 0000000..8d65536
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-springdoc/src/main/java/com/ruoyi/common/springdoc/handler/OpenApiHandler.java
@@ -0,0 +1,252 @@
+package com.ruoyi.common.springdoc.handler;
+
+import cn.hutool.core.io.IoUtil;
+import io.swagger.v3.core.jackson.TypeNameResolver;
+import io.swagger.v3.core.util.AnnotationsUtils;
+import io.swagger.v3.oas.annotations.tags.Tags;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.SecurityService;
+import org.springdoc.core.utils.PropertyResolverUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.method.HandlerMethod;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 自定义 openapi 处理器
+ * 对源码功能进行修改 增强使用
+ */
+@Slf4j
+@SuppressWarnings("all")
+public class OpenApiHandler extends OpenAPIService {
+
+    /**
+     * The Basic error controller.
+     */
+    private static Class<?> basicErrorController;
+
+    /**
+     * The Security parser.
+     */
+    private final SecurityService securityParser;
+
+    /**
+     * The Mappings map.
+     */
+    private final Map<String, Object> mappingsMap = new HashMap<>();
+
+    /**
+     * The Springdoc tags.
+     */
+    private final Map<HandlerMethod, Tag> springdocTags = new HashMap<>();
+
+    /**
+     * The Open api builder customisers.
+     */
+    private final Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers;
+
+    /**
+     * The server base URL customisers.
+     */
+    private final Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers;
+
+    /**
+     * The Spring doc config properties.
+     */
+    private final SpringDocConfigProperties springDocConfigProperties;
+
+    /**
+     * The Cached open api map.
+     */
+    private final Map<String, OpenAPI> cachedOpenAPI = new HashMap<>();
+
+    /**
+     * The Property resolver utils.
+     */
+    private final PropertyResolverUtils propertyResolverUtils;
+
+    /**
+     * The javadoc provider.
+     */
+    private final Optional<JavadocProvider> javadocProvider;
+
+    /**
+     * The Context.
+     */
+    private ApplicationContext context;
+
+    /**
+     * The Open api.
+     */
+    private OpenAPI openAPI;
+
+    /**
+     * The Is servers present.
+     */
+    private boolean isServersPresent;
+
+    /**
+     * The Server base url.
+     */
+    private String serverBaseUrl;
+
+    /**
+     * Instantiates a new Open api builder.
+     *
+     * @param openAPI                   the open api
+     * @param securityParser            the security parser
+     * @param springDocConfigProperties the spring doc config properties
+     * @param propertyResolverUtils     the property resolver utils
+     * @param openApiBuilderCustomizers the open api builder customisers
+     * @param serverBaseUrlCustomizers  the server base url customizers
+     * @param javadocProvider           the javadoc provider
+     */
+    public OpenApiHandler(Optional<OpenAPI> openAPI, SecurityService securityParser,
+                          SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+                          Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
+                          Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
+                          Optional<JavadocProvider> javadocProvider) {
+        super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
+        if (openAPI.isPresent()) {
+            this.openAPI = openAPI.get();
+            if (this.openAPI.getComponents() == null)
+                this.openAPI.setComponents(new Components());
+            if (this.openAPI.getPaths() == null)
+                this.openAPI.setPaths(new Paths());
+            if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
+                this.isServersPresent = true;
+        }
+        this.propertyResolverUtils = propertyResolverUtils;
+        this.securityParser = securityParser;
+        this.springDocConfigProperties = springDocConfigProperties;
+        this.openApiBuilderCustomisers = openApiBuilderCustomizers;
+        this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
+        this.javadocProvider = javadocProvider;
+        if (springDocConfigProperties.isUseFqn())
+            TypeNameResolver.std.setUseFqn(true);
+    }
+
+    @Override
+    public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) {
+
+        Set<Tag> tags = new HashSet<>();
+        Set<String> tagsStr = new HashSet<>();
+
+        buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
+        buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
+
+        if (!CollectionUtils.isEmpty(tagsStr))
+            tagsStr = tagsStr.stream()
+                .map(str -> propertyResolverUtils.resolve(str, locale))
+                .collect(Collectors.toSet());
+
+        if (springdocTags.containsKey(handlerMethod)) {
+            Tag tag = springdocTags.get(handlerMethod);
+            tagsStr.add(tag.getName());
+            if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+                openAPI.addTagsItem(tag);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(tagsStr)) {
+            if (CollectionUtils.isEmpty(operation.getTags()))
+                operation.setTags(new ArrayList<>(tagsStr));
+            else {
+                Set<String> operationTagsSet = new HashSet<>(operation.getTags());
+                operationTagsSet.addAll(tagsStr);
+                operation.getTags().clear();
+                operation.getTags().addAll(operationTagsSet);
+            }
+        }
+
+        if (isAutoTagClasses(operation)) {
+
+
+            if (javadocProvider.isPresent()) {
+                String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
+                if (StringUtils.isNotBlank(description)) {
+                    Tag tag = new Tag();
+
+                    // 自定义部分 修改使用java注释当tag名
+                    List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
+                    // tag.setName(tagAutoName);
+                    tag.setName(list.get(0));
+                    operation.addTagsItem(list.get(0));
+
+                    tag.setDescription(description);
+                    if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+                        openAPI.addTagsItem(tag);
+                    }
+                }
+            } else {
+                String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
+                operation.addTagsItem(tagAutoName);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(tags)) {
+            // Existing tags
+            List<Tag> openApiTags = openAPI.getTags();
+            if (!CollectionUtils.isEmpty(openApiTags))
+                tags.addAll(openApiTags);
+            openAPI.setTags(new ArrayList<>(tags));
+        }
+
+        // Handle SecurityRequirement at operation level
+        io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
+            .getSecurityRequirements(handlerMethod);
+        if (securityRequirements != null) {
+            if (securityRequirements.length == 0)
+                operation.setSecurity(Collections.emptyList());
+            else
+                securityParser.buildSecurityRequirement(securityRequirements, operation);
+        }
+
+        return operation;
+    }
+
+    private void buildTagsFromMethod(Method method, Set<Tag> tags, Set<String> tagsStr, Locale locale) {
+        // method tags
+        Set<Tags> tagsSet = AnnotatedElementUtils
+            .findAllMergedAnnotations(method, Tags.class);
+        Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = tagsSet.stream()
+            .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
+        methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
+        if (!CollectionUtils.isEmpty(methodTags)) {
+            tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
+            List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
+            addTags(allTags, tags, locale);
+        }
+    }
+
+    private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<Tag> tags, Locale locale) {
+        Optional<Set<Tag>> optionalTagSet = AnnotationsUtils
+            .getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
+        optionalTagSet.ifPresent(tagsSet -> {
+            tagsSet.forEach(tag -> {
+                tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
+                tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
+                if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
+                    tags.add(tag);
+            });
+        });
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-springdoc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-springdoc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..5665d27
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-springdoc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.ruoyi.common.springdoc.config.SpringDocConfig